House of loop
The challenge gives us 2 files: an executable and a libc version 2.27.
Based on the name of the challenge, I predicted that this challenge was about heap exploitation, which I have done quite a bit of them recently so this was the first challenge I tried to solve.
About the libc:
Its version is 2.27, so tcache is featured. I will not go in-depth into tcache, but I will pointed out the important parts that I used in pwning this challenge as we go through.
About the binary:
The program has 4 options: adding a note, viewing one, removing one and exit. So first thing first, I fired up IDA and started to analyze it. I will go straight into all the notable functions, and the program data structure (You can find the IDB file in the link above).
First, the add_note function:
This function first allocates a chunk with the size of 144 (which contains the general data of a note, I will call them main chunks), then reads 32 bytes of input into the title field and sets its 25th byte to zero (which is a little bit weird for now), reads another 96 bytes into the private field, allocates another chunk with the size that user can control (which contains the content of a note), and then reads the content into it. Finally, the loop there is just to set the pointer to the next note of the previous note to the address of the note it has just made, if it is the first note to be made then the address will be stored in a global variable First_Chunk_Ptr on bss. A quick note here is that when it reads input into the content, it doesn't clean it up first.
Analyzing this fuction and debugging a bit, I could clarify the data structure of the notes as follow:
The notes struct contains of 4 fields: the private data (96 bytes), the title (32 bytes), the pointer to its content (des) and the pointer to the next note. It is now clear that the notes' structure is a singedly linked list. Also at this point, I knew the purpose of setting the 25th byte of the title to zero: to prevent leaking the heap address stored at the des.
Next let's move on to the second function, view_note:
Not much to say about this one, it is really just simple. It traverses the linked list and print out each notes' title and content. And of course a function that prints out data like this will be valuable when we try to leak data later on.
Moving on to the final function we are interested in, the delete_note function:
This function deletes a single note by looping and comparing the titles to the user's input title that we wanna delete. The big thing to be noticed here is that when deleting a note, the only thing that this program does is freeing its content and its main chunk, without cleaning them up. This could lead to some real dangerous (and a bit silly) stuff which we will get into later.
One more thing before we go into the exploitation is that the read_input function doesn't automatically append null byte at the end of our input if it doesn't end with a newline and this is also super valuable.
Those are all of the things that we are interested in for now. Let's move on to the fun part.
Vulnearabilities and exploitation:
Note: All the chunk sizes that I use in this section does not consist of the 16 bytes metadata, I think it is more convenient doing it this way.
Now we get into the first phase of almost all heap exploitation: the leaking phase.
For this part, we will use the part that the add_note function doesn't clean the content up before reading input in it, allowing us to leak out what is already in there.
First, I added 2 notes (1 and 2) with the same content's size, then free them both in the order of 2 -> 1, now, in the chunk where note 1's content was should be a pointer to note 2's content. Therefore, I could just re-add note 1 with just one byte of input and then use the view_note function to leak out the heap address. Sweet! (I then also re-added note 2 to return the heap into an easy-to-control state).
The second thing we need to leak out here is the libc's address. As I said, I have done quite a bit of heap challenges so I knew that I could use almost the same method as leaking heap to leak libc. The different here is that if you want to have a libc's address on the heap, it must be an unsorted chunk, not a tcache chunk. The simplest way to do this is that because the program let me allocate the content of any size, I could just allocate a very big chunk of which the size exceeds the maximum tcache chunk's size of 1032 bytes, and by that I got an unsorted chunk. The leaking process for this is the same as for the heap. So then, I had done the leaking with the heap address and the libc address leaked.
Phase 2: getting an arbitrary write.
I was quite lucky with this one, because I actually noticed the critical bug of this program randomly while trying to do the leaking part.
So here is the bug: I added 2 notes, deleted the 1st one, re-added it and then called view_note. What happen was that the program goes into an infinite loop of printing out the 2 notes' content consecutively. The reason for this is that the delete_note function doesn't actually clear the data in the main chunk of a note, so after deleting the first one, its next chunk pointer will become a dangling pointer and is still there pointing at the second one, and after I re-added it, the second note's next chunk pointer would point back at the 1st because the 1st one became the final chunk now, so when I invoked view_note, the traversal loop just kept on going, causing the loop to go infinite.
With that figured out, my goal was to add a note with a corrupted next chunk pointer that points to a fake main chunk on the heap which has the content pointer that points to a freed chunk (which I called target_chunk), so that I could delete that note and got a double free on that already freed chunk and after that it was just an easy task to get an arbitrary write from there.
So here is what I did to achieve that:
1) Adding 2 notes which has the contents of size 96 and 40 and deleting them.
2) Adding a note with the content of size 144, so that the chunk that stored its content would be the main chunk of a deleted note, and then write the content into it so that the pointer to the fake_chunk would be in the place of where the next chunk pointer would be, delete it.
3) Adding a note with the content of size 200, now the main chunk of this note would be the chunk with the fake_chunk pointer that I had set up before, I also set up the fake_chunk here as the content of this note, which has the pointer to the target_chunk (which was the content chunk with the size of 96 that we have freed before).
4) The title of the fake_chunk would be just an empty string, so I went ahead and delete that one, and so I had a double-freed chunk of size 96.
5) Now I simply just used 3 more chunks of size 96 to get an arbitrary write in to __free_hook and overwrite it with one_gadget: the first chunk contains the address of __free_hook, the second one will put that adress into the bin, and the third one will write one_gadget into __free_hook (if you are not familiar with heap exploitation and don't understand what I just did, you can go ahead and read how2heap by shellphish).
6) Trigger a call to free and trigger the __free_hook.
Now you might wonder why I didn't choose the __free_hook as the fake_chunk or the target_chunk. In fact, those were my first ideas, but I had some issues with the context of libc and that didn't work so this was the approach I chose and it worked well.
So this is all about for this challenge, a nice and simple heap challenge I think. Now you know that you should always clear the data of a chunk when freeing it, or use calloc instead for the allocation :)) .
This challenge also comes with 2 files, an executable and the same libc as the previous. Looking at the name (inspired by pwnable.tw I think) and the checksec "Partial RELRO" + "Has RWX segments", I could already predict that this is a shellcode challenge. I am actually somewhat interested in these kind of challenges so I gave this a shot (and I did solve this challenge pretty early on in the tournament :P).
About the libc: the same as before, nothing more to say.
About the binary:
The program has only 3 options: adding a note, removing one and exit. So let's start right into the add_note function:
This function first asks us for an index of the note you want to add, it doesn't bind your input number so an unbound array indexing vulnearability is pretty clear here. Next, it asks us for a "number", which I found pretty confusing at first, but it is just that you can adding multiple consecutive notes that has the same content. This feature is pretty weird so keep that in mind because it will be useful. Moving on, it will check if our number is positive and less that the number of available notes (which is quite large so we don't have to care about it) or not, and if it is the case, it starts reading 8 bytes of input. After that is where the fun part begins: the function only proceeds to put our input on the heap (which is an executable segment) if our input's length is less than or equal to 2 bytes and it must consist of only alphanumeric characters and the "=" sign (I don't know why the "=" sign is there, I didn't use it, but maybe I missed something that lead to an easier solution because my solution was quite complex).
You can find all of the alphanumeric opcodes here.
Now for the delete_note function:
This function is pretty small, and it is also quite buggy (not a bug that you can exploit, just some brainfart by the author I think :v). This function reads in an index and if that index of the notes is NOT zero, it will put out "Can not delete blank note~~" (LOL), won't put that out otherwise, and then it just proceeds to free it anyway (LOLx2), it also sets the pointer at that index to NULL. Otherwise it is just a simple function that you can use to free an index.
Vulnearabilities and exploitation:
As you can see, this challenge doesn't come with a hard-to-find bug or anything, it is just like a shellcode puzzle that is pretty difficult to solve. So the general idea is that you can add a note at negative index, where the GOT is and make them point to the heap where you put your shellcode, with this you can change the behavior of any function you want. Of course our aim here is to use that to bypass those very strict restriction.
So the first thing I thought of and tried to achieve is to make strlen always return 0. That could be done by setting strlen@GOT at index -23 to point to our shellcode that somehow set rax to 0 and then ret. But for real, the fact that you can only use 2 bytes of alphanumeric shellcode is a pain in the a**. Because the chunks on the heap are seperate, if I would like to run a long shellcode there, there must be jump instructions, which require 2 bytes! That means if you jump, you can't do anything else, and also I couldn't find a way to set rax to 0 and ret with only 2 bytes.
Thinking for a while, I came up with 2 neat ideas: use the top chunk's size as opcodes (idea #1), and run past all the \xoo and \x21 (which is the chunks' size) opcodes instead of jumping (idea #2).
For (#1), the first opcode can be used, which is the LSB of the top chunk's size, must be of the form \xX1 (\x01, \x11, \x21, ...), and also I found that I could only use 2 opcodes (because the MSB can only be \x01 or \x02 which has no use). After some researching and testing I find a wonderful combination of 2 opcodes: \x91\xc3 (xchg eax, ecx; ret;). Now the only thing I needed to do was to set ecx to 0, which you may think is not that hard, but remember: only 2 alphanumeric opcodes at once, so this is where the (#2) came into play.
As I mentioned before, I couldn't find a way use jump, so I will run past all the \x00 and \x21, which are add BYTE PTR [rax], al; and and DWORD PTR [rax], eax; , respectively. So in order for that to work, our rax must be a pointer to a writable segment after the first 2 bytes of opcodes. So I started debugging, set a breakpoint at each strlen (there are 2 of them, one in the add_note and one in the check_alpha) to see the context of the registers and stack at each of them, they are as follow:
As you can see, the only writable memory that can be used are: the buffer stored in various registers, RBP, RSP. I tried RBP and RSP, but of course that it messed up the stack and return a __stack_chk_fail. So I had no choice but to use the buffer, which hurt a lot, I will explain why in a second. Remember what those \x00 and \x21 opcodes are? Yes, they will messed up the first 4 bytes of memory stored at rax. But really, I couldn't think of another way so I went up this path. So up to this point this is what I did to achieve a corrupted strlen@GOT:
1) allocate 591 padding chunks at index 0 (these are to set the top chunk's size to the desired value).
2) adding a note with opcode \x57\x58 (push rdi; pop rax;) at index 591, and a note with opcode \x53\x59 (push rbx; pop rcx;) at index 592.
3) Deleting the note at index 591 and then re-adding it at index -23 (strlen@GOT).
Note: You have to do it in this order, if you add the -23 index before 592, it will corrupt the strlen@GOT before it is completely setup. And moreover, after you have done all these, you are not allowed to allocate any more chunk BEFORE freeing one because that will mess up the top chunk.
From this point, I could say that I bypassed strlen, I could input 8 bytes of normal shellcode at a time then. From then, I could just do the same with another function to call a normal shellcode, but that was not the case, because the first 4 bytes of each of my shellcode would be ruined as I mentioned before, so I had to find a way to get around this. I couldn't calculate it to the desired value because the buffer is on the stack, which is affected by ASLR, so it is unpredictable. What I did was before corrupting the strlen, I set up a jump shellcode that jump past the ruined 4 bytes, and from then on, after corrupting strlen, I would only add in shellcode in the format of 4 bytes padding + 2 effective bytes + 2 bytes for the jump. So the third step had to be fixed up a lil bit:
3) Deleting the note at index 0 first, and then index 591, then re-adding at index -13 (exit@GOT) with the opcode \x75\x42 (jne 0x44;) and after that at index -23 as before.
Note: You have to free the 0 first, or else the remaining of the pointer that stored in a free tcache chunk will be in the way of \x00 slide after being re-added. Also I used exit@GOT because if I used free@GOT, I couldn't free up the chunks to add in more shellcode.
After that the exploit is straight-forward: I kept on freeing and re-adding the notes to put in a shellcode to syscall read(0, address, size) at a writeble segment, and then push it address and ret. Then I made a call to exit, read in an execve shellcode and this should give me a shell.