Bsides Canberra '17 - noob_download

March 25 2017

This was a good little pwn challenge that OJ and some other great guys put on for the Bsides Canberra CTF.

Running the binary gave the following:

$ ./noob_download
Gimme the data: AAAA
Go on then, break me: BBBB

So there was two reads in of data into the program which was interesting.

The usual checksec to see what we are fighting against:

$ checksec noob_download
[!] Couldn't find relocations against PLT to get symbols
[*] '/home/shared/noob_download'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

So NX is enabled so no shellcode, and no PIE so we know .text addresses ahead of time, great.

Throwing the binary into radare2, lets have a look at the main function.

0x00400686      55             push rbp
0x00400687      4889e5         mov rbp, rsp
0x0040068a      4883ec40       sub rsp, 0x40               
0x0040068e      897ddc         mov dword [local_24h], edi
0x00400691      488975d0       mov qword [local_30h], rsi
0x00400695      488955c8       mov qword [local_38h], rdx
0x00400699      64488b042528.  mov rax, qword fs:[0x28]
0x004006a2      488945f8       mov qword [local_8h], rax
0x004006a6      31c0           xor eax, eax
0x004006a8      488b05710920.  mov rax, qword obj.stdout   
0x004006af      4889c1         mov rcx, rax             
0x004006b2      ba10000000     mov edx, 0x10               
0x004006b7      be01000000     mov esi, 1               
0x004006bc      bf23084000     mov edi, str.Gimme_the_data: 
0x004006c1      e8c2feffff     call sub.fwrite_248_588  
0x004006c6      488b05530920.  mov rax, qword obj.stdout   
0x004006cd      4889c7         mov rdi, rax         
0x004006d0      e8abfeffff     call sub.fflush_240_580    
0x004006d5      488b05540920.  mov rax, qword obj.stdin    
0x004006dc      4889c2         mov rdx, rax         
0x004006df      be20000000     mov esi, 0x20 
0x004006e4      bf40106000     mov edi, obj.whateva        
0x004006e9      e882feffff     call sub.fgets_224_570     
0x004006ee      488b052b0920.  mov rax, qword obj.stdout   
0x004006f5      4889c1         mov rcx, rax                
0x004006f8      ba16000000     mov edx, 0x16            
0x004006fd      be01000000     mov esi, 1               
0x00400702      bf34084000     mov edi, str.Go_on_then__break_me
0x00400707      e87cfeffff     call sub.fwrite_248_588  
0x0040070c      488b050d0920.  mov rax, qword obj.stdout   
0x00400713      4889c7         mov rdi, rax             
0x00400716      e865feffff     call sub.fflush_240_580    
0x0040071b      488b150e0920.  mov rdx, qword obj.stdin    
0x00400722      488d45e0       lea rax, [local_20h]
0x00400726      be90010000     mov esi, 0x190              
0x0040072b      4889c7         mov rdi, rax     
0x0040072e      e83dfeffff     call sub.fgets_224_570
0x00400733      b800000000     mov eax, 0
0x00400738      488b4df8       mov rcx, qword [local_8h]
0x0040073c      6448330c2528.  xor rcx, qword fs:[0x28]
0x00400745      7405           je 0x40074c
0x00400747      e814feffff     call sub.__stack_chk_fail_208_560
0x0040074c      c9             leave
0x0040074d      c3             ret

So the first read is an fgets that reads 0x20 bytes into 0x601040, and the second:

0x00400722      488d45e0       lea rax, [local_20h]
0x00400726      be90010000     mov esi, 0x190            
0x0040072b      4889c7         mov rdi, rax                
0x0040072e      e83dfeffff     call sub.fgets_224_570    

uses fgets to read in 0x190 bytes into a local variable on the stack.

What is interesting about the disassembly of the main function is that there is in fact a canary, as there is a canary check function. And this can be seen in action when we smash the stack too much on the second read.

Gimme the data: aaaa
Go on then, break me: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
*** stack smashing detected ***: ./noob_download terminated
Segmentation fault

So in this type of problem, we can see that there is a mention of the binary name in the stack smashing error. We can leverage this by smashing the stack so we can replace the pointer to the name to a pointer to some other string.

Dumping the strings in radare shows the flag is within the binary at a fixed address:

[0x00400590]> iz
vaddr=0x00400800 paddr=0x00000800 ordinal=000 sz=35 len=34 section=.rodata type=ascii string=BSIDES_CTF{FLAGISHEREONTHESERVER!}
vaddr=0x00400823 paddr=0x00000823 ordinal=001 sz=17 len=16 section=.rodata type=ascii string=Gimme the data:
vaddr=0x00400834 paddr=0x00000834 ordinal=002 sz=23 len=22 section=.rodata type=ascii string=Go on then, break me:

So if we replace the pointer to the binary name on the stack with this address we will leak the flag in the canary error message!

We first of all find the pointer to the binary name on the stack, and how much of the stack we need to smash to get there.

pwndbg> search /home/glenn/bs 
[stack] 0x7fffffffd67e 0x6c672f656d6f682f ('/home/gl') 
[stack] 0x7fffffffd90c 0x6c672f656d6f682f ('/home/gl') 
[stack] 0x7fffffffe878 0x6c672f656d6f682f ('/home/gl') 
[stack] 0x7fffffffea82 0x6c672f656d6f682f ('/home/gl') 
[stack] 0x7fffffffefbe 0x6c672f656d6f682f ('/home/gl')

pwndbg> search -8 0x7fffffffd67e 0x7ffff7dd23d8 0x7fffffffd67e 
[stack] 0x7fffffffd288 0x7fffffffd67e

pwndbg> search whereami 
[heap] 0x602420 'whereami\n' 
[stack] 0x7fffffffd180 'whereami\n'

pwndbg> distance 0x7fffffffd180 0x7fffffffd288 
0x7fffffffd180->0x7fffffffd288 is 0x108 bytes (0x21 words)

So we need to smash 0x108 bytes in the second read to get to the pointer to the binary name that is used in the error message when we smash the stack.

Doing this and replacing the pointer with the pointer to the flag works locally, but doesn't work remotely, due to the error message being send to STDERR and not STDOUT over the network, so the connection just terminates and we don't see the flag.

This took me a while to figure out, but I soon found out that to see the STDERR message on STDOUT, we have to set the environment variable LIBC_FATAL_STDERR_=1, and I did this by reading that string into the data section at the fixed address in the first read, and adding a pointer to this on the stack along with the other pointers to environment variables while smashing the stack to change the program name pointer.

So the stack was smashed and the binary leaked the flag as the program name over the network! (Sorry I didn't take any screenshots - but take my word for it).