h3rcul35 was in a hurry to finish making challenges so he made a simple one. He says its a baby challenge, can you pwn it?

nc hack.bckdr.in 9036
32_chal
libc.so.6

So, we are given a binary and it claims to be a 32-bit file due to its name. Let’s check it by ourselves first.

It seems that it was telling the truth. It is a 32-bit ELF file. Now, we will run it to see how it works.

It prints a string first. Then, reads our input from stdin and print it back to us. Before moving to the reversing stuff, let’s check its protections.

As we see here, the stack is not executable. Now, we can start analyzing it further. I used gdb-peda for it, you can use your favourite disassembler/debugger (radare2, gdb, pwndbg, IDA, Hopper, etc.).

Now, things are getting tricky. First, it uses write and read which are not buffered, then uses printf which is buffered by default. I will explain why this is important soon, but first you should know that write function tries to write exactly n bytes. It does NOT stop when it encounters a newline or a null character. According to the code, it is going to write 16 bytes from 0x8048570. Let’s see those bytes.

Look at the end of it. It includes a newline (0xa) and a null character. We should be careful while writing our exploit. If we do read the line with recvline function, it wont read the null byte. It can create two different problems. First, our next receiving call might return empty string. Second, the null byte might be prepended to the next string we read. So, we should handle it carefully.

In order to exploit the program, we need to leak an address from libc. Due to ASLR protection, the base address of libc is always different (random). Since printf does not stop unless it encounters a null character, we can easily leak an address. However, the program immediately returns after printf which means we don’t have time to make use of that address. In order to overcome this problem, we will use Return-Oriented Programming (ROP).

We will first overwrite the stack such that it will return to [email protected] with parameters 1, [email protected], 4. Then, it will return to the main function again! What we are doing is leaking the address of read from Global Offset Table (GOT). At the beginning of the program, each entry in GOT points to Procedure Linkage Table (PLT) section. When a function is called for the first time, it goes to PLT section and its real address is calculated there. Afterwards, its GOT entry is updated with that address and it gets called. Next time the function gets called, it directly goes to the real location since its GOT entry is already updated.

Now, we will calculate the amount of padding required to overwrite the return address. I used De Bruijn pattern method this time. I created a pattern of 200 characters since the program reads 200 bytes as input.

We need a padding of 112 characters for the first iteration. You can also find the required padding for the second iteration with the same pattern method or you can just set some breakpoints and calculate it manually. These are the easy ways, we will not use either of them. Let’s think about what changes in the second iteration. We add 4 to the stack pointer by returning from main function. If we were using call instructions to call the functions, then eip+4 would be pushed onto the stack. Therefore, the stack pointer would be decreased by 4 and everything would be fine. In our case, we use retn instruction for both returning from a function and calling another function. The result is stack value gets increased all the time and never gets decreased back. Why is this important? Why do we care about the initial value of the stack pointer? To answer that question we need to look back at the main function.

This instruction clears the last 4 bits of the esp register. In other words, it rounds the esp register down to the closest multiple of 16. This is known as Stack Alignment.

How does this affect our padding? When we step into the main function, the return address is on the top of the stack. Then, we push ebp register onto the stack. Thus, the return address is at esp+4. If there were no stack alignment, we would simply continue with subtracting some value from esp register for our local variables including the input buffer. Our buffer’s length is fixed. So the distance between the beginning of the buffer and the return address would always be the same. The stack alignment kicks in just before we create space for our local variables on the stack. Let’s say the last digit of esp was initially 8. After the alignment, it will be 0 which means we substracted 8 before substracting our real value. It is like we created 2 integer variables (32-bit) before the buffer.

To sum up, the Stack Alignment can alter the distance between the buffer and the return address.

Let’s set a breakpoint on that and instruction and check the value of esp for the first iteration.

Initial esp value is 0xffffd288 which causes extra 8 bytes between the buffer and the return address since it is last hexadecimal digit is non-zero. While exploiting the binary, we will first return from main to the write, then we will return from write to main again. So, esp register will be incremented by 8 and the new value will be 0xffffd290. With this new value, there won’t be any additional bytes this time, so the required padding will be decreased to 112 – 8=104 characters.

Now, we can calculate the base address of libc by substracting the offset of read function from the leaked address of read. Then, we can use the offsets of ‘/bin/sh’ and system to calculate their runtime addresses. We will get their offsets from the given libc file.

Here is the exploit I wrote in python.

In the second payload “RETN” is just a dummy for the return address from system function. It is not important since we will get our shell instead of returning.

Now, there is a strange thing in the python code. After sending the first payload, we read our leaked address without reading the output of printf. At the beginning of this write-up, I told you printf is buffered whereas write is not. When the output is terminal, stdio buffer gets flushed whenever a newline character is encountered. However, our output is redirected which means the buffer won’t get flushed that easily. As a result, the program will first print the leaked address. If the buffer gets full or the program exits normally, it will print our inputs back. However, the program won’t exit since we will redirect it to system and get a shell. Therefore, we don’t need to read our input strings which are supposed to be printed back via printf.

Let’s run the script and get the flag.

Here we get flag{all_th3_b35t_y0u_successfully_started_s0lving_:P}.