Bypass sandboxing implementations such as seccomp in CTF pwn challenges with ROP
Environment Setup
OS: Ubuntu 20.04 VM
Tools: pwndbg, pwntools, python3
Target: malloc-testbed(https://github.com/limitedeternity/HeapLAB/tree/main/malloc_testbed)
Introduction
Recent CTF pwn challenges are more and more onerous, probably due to 内卷 among players.
It’s common to find sandboxing implementations such as seccomp in place, that only allows certain syscalls to take place.
Even after hijacking the flow of execution, we won’t be able to simply toss in a one_gadget
or system("/bin/sh")
to get an interactive shell.
Usually only open
read
and write
syscalls are available to use, and those are enough for us to read the flag.
I’ll document one way to achieve ORW by using the ROP chain near setcontext
, and briefly talk about two other ways to bypass seccomp.
In this post, malloc-testbed
is configured to use glibc 2.31
with a UAF bug, and libc already leaked.
This technique requires the following conditions to be met:
- libc base address and address of a writeable region leaked(for example: heap)
- control over a function pointer and it’s first argument(for example: __free_hook)
- ability to request enough space to house the rop chain(can be one big chunk or many smaller chunks)
Gadget Hunting
ROP obviously requires ROP gadgets.
I normally hunt for the gadgets with a tool called ROPgadget
, and objdump
objdump
gives more control over the gadgets shown in my opinion, since I haven’t read ROPgadget
‘s source code yet and have no idea what it’s doing.
However, objdump
does not perform misaligned parses on instructions, and thus is likely to miss out certain gadgets.
setcontext gadget:
1 | awae@ubuntu:~/Desktop/HeapLAB_Part2/malloc_testbed$ objdump -Mintel .links/libc.so.6 -d | grep -A30 "setcontext" |
objdump
shows the address of the gadget we are interested in, the long series of mov
instructions to set many registers based on an address held in the rdx
register.
In earlier versions of libc, the setcontext
gadget uses rdi
to set registers, which is directly controlled by us assuming we overwrote the __free_hook
, since the first argument on x86_64 Linux is always stored in the rdi
register.
However in more recent versions, the rdx
register is used.
In order to control the other registers, we have to find a way to control rdx
first.
I’ve seen this gadget being used in other writeups:
1 | mov rdx, [rdi+8] |
but I was unable to find it with objdump
or ROPgadget
Instead, I’ll use the following two gadgets found in __rpc_thread_key_cleanup
to gain control of the rdx
register.
I found them by the command objdump -Mintel .links/libc.so.6 -d | grep -A10 -B10 "call QWORD PTR \[rd"
.
The logic is, since we have to dereference rdx
and control the data there, rdx
should point to the heap.
Since we are passing rdi
as an argument to free
, it should hold an address of the heap too.
The only way we can retain execution flow after the gadget is through either a call
or jmp
gadget with rdx
or rdi
dereferenced as an argument.
The two gadgets are:
1 | 122192: 48 8b 47 38 mov rax,QWORD PTR [rdi+0x38] |
and
1 | 12219c: 48 8b 50 08 mov rdx,QWORD PTR [rax+0x8] |
By controlling rdi
, we can control the first call instruction to execute the second gadget which sets rdx
.
After setting rdx
, the second call instruction will call the setcontext
gadget.
The rest of the gadgets needed(pop gadgets and maybe syscall if you wish) should be easy to find with ROPgadget
.
Note: syscall ; ret
gadget can’t be found with ROPgadget
for some reason, so use objdump
for that.
Exploitation
Since this is just a proof of concept, exploitation is easy.
Tcache poisoning to overwrite __free_hook
and ROP to win.
setcontext_rop.py:
1 | #!/usr/bin/python3 |
There are of course many modifications available, such as using library functions instead of syscall, using many small chunks to house the ROP chain, not calling exit
and make your exploit ugly etc, but the crux is similar.
Alternatives
Depending on the amount of control, there are many more methods to bypass seccomp.
- If you have an arbitrary write primitive, one method will be to leak stack addresses using the
environ
variable, locate the return address and just write ROP chain on the stack. - If the sandbox permits, use an
mprotect
call to mark the heap as executable and pwn it the 90s way. - Social engineer the challenge creators
- Find a remote kernel 0day
Conclusion
Hooks are things of the past! Filestream exploitation and glibc 0day is king
学无止境 谦卑而行