In this next part, we will complete our analysis of Stack4 using IDA Free. In subsequent parts, we’ll complete ABOS exercises that delve deeper into the use of the different tools.
Those exercises are located in the ABOS Folder and will challenge us to open and run a calculator, notepad, or some other application.
There are four ABOS exercise folders. The first exercise will be completed using Radare2, the second with GHIDRA, the third with IDA Free and the fourth with Radare2 again.
But first, let’s finish our analysis of Stack4.
STACK4 in IDA FREE
Part four discussed the topic of invalid characters, and we saw two invalid characters—0x1a and 0xa—in the previous stack exercises. An example of these invalid characters can be seen below:
Static Analysis of Stack4 with IDA Free
We can see that the address of the buffer (buf) is the parameter of the gets function, so it will save whatever we write until we finish typing and press ENTER.
Below we can see the comparison of the cookie variable with the constant value 0x0d0a00. The cookie value does not ever change its value inside of the function. Since the cookie is unchangeable, it could never be worth the constant value, making it clear that we can’t get to YOU WIN if the program works correctly. Of course, the only way to reach that point is by exploiting the buffer overflow in the stack, as we saw in previous exercises.
We can see the buf length in the static representation of the stack by double clicking on any of the variables:
We can see that the length of buf is 80 bytes. If we send:
payload = 80* ’A’ + “BBBB”
This would change the cookie with the string “BBBB,” which is 0x42424242. The problem is that we need a cookie with the value 0x0d0a00 in order to get to YOU WIN. So, we should send the payload:
payload = 80* ‘A’ + “\x00\x0a\x0d\x00”
Let’s run it from pycharm or from a Windows console and see if we get to the YOU WIN point.
Below, we can see it does not print YOU WIN, and instead ends the program.
Next, we’ll debug using IDA Free. We’ll run the script again from the Windows console. MessageBoxA will pop up:
Now we have to set a breakpoint in IDA Free, after the call to MessageBoxA:
Now, using IDA Free, we need to go to Debugger - Attach to process:
In the list of processes, we must search for STACK4 and accept.
When it stops, we press the “play” button of “F9” to run it.
As it runs, we will accept the MessageBox.
Once it stops at the breakpoint, we should begin to trace with “F8” (Step Over) and go over the gets to see how it manages our payload.
If we hover the mouse over buf without clicking, we’ll see our payload.
Below is what we’ll see if we double click on buf.
If we want to determine the length of a string without counting one by one, press the “A” key in the first 41h to transform all the buffer in a string.
We can see that the last zero is part of the string (in C programming, a string is a series of characters ending with a NULL byte). If we right click – ARRAY, it will show us that the length is 81, including the last zero:
Press the Cancel button, as we only needed to find out the length. Now we can see that our 80 ‘A’s are entered, and what follows the next zero is different:
We can see that the buffer stopped at the byte 0x0a.
To see the part of payload that was just inserted, we can open HEX-VIEW window:
We can choose to synchronize with the content pointed by some record. In this case we’ll synchronize with IDA VIEW-RIP, which is the upper window. Once this is selected, I will see my data in a similar way to OLLYDBG DUMP view, as there is a column with memory addresses on the left and the content is on the right.
So as we can see below, not all the payload that we sent was entered. Additionally, if we look at the cookie value, we find that it is the 4 bytes after the “A”s. But if we hover the mouse over the cookie variable:
In order to alter the view of non-defined bytes without changing them from their current state and show the variable as DWORD, we should double click on it. Then, we’ll press “D,” changing db, dw, and finally dd (BYTE, WORD and DWORD).
So the value of the cookie variable is 0x400000 and as it is not equal to 0x0d0a00 it will not go to YOU WIN.
As mentioned earlier the problem is with the invalid character, 0x0a. It can’t be copied to the buffer because it cuts our payload and the correct equivalency is necessary to get to the YOU WIN. Now that we see it’s not possible to write the payload, how can we reach the YOU WIN point?
First, let’s stop the debugger.
Next we’ll put a breakpoint in the first instruction of the main function.
And this time we’ll run it without the script, directly from the beginning. We’ll also type the data in the prompt manually.
Once that stops at the breakpoint we need to press X so we can see where the main function is called from.
When that call is executed and entered in the main function after pushing the arguments to the stack, the RETURN ADDRESS is stored in the first place of the stack. When the main system finishes and gets the RETN instruction, the function will obtain the RETURN ADDRESS = 0x401290. This is where it should return, just after the call instruction we see above.
One of the most useful toolbars in IDA is JUMP. Once added, we can use it to go back and forth. In this exercise, we’ll use it to return to the main function where we were before:
Once there, we see that the RETURN ADDRESS is equal to 0x401290 since, as mentioned above, it was intended to return once the main function finished executing:
Next, the PUSH EBP is stored in the stack of the EBP of the caller function (the previous function) which called main. We’ll call it STORED EBP, which is the EBP of the parent function.
By pressing F8, we see how it is stored in the stack, moving the ESP value to EBP.
The call is executed for the HORIZON, which we can see by pressing F8:
Now that EBP is set to the HORIZON value that it will have in the function, ESP must go higher to allocate space for the local variables, cookie and buf. This is done with the instruction SUB ESP, 0x54.
Just to clarify, “ESP must go higher” is meant with regards to its position within the view—ESP will be placed above EBP in the debugger or disassembler.
However, memory is shown with the lowest memory values at the top of the stack view increasing in value as we move down the view. This means that the ESP value which is positioned higher in the view, will be lower in value than EBP.
Moving forward—when we talk about going higher or lower in the stack, this refers to its position. When we talk about increasing or decreasing, this refers to the value.
EBP remains with the HORIZON address and ESP will be above it, leaving 0x54 bytes of space for local variables:
This means that ESP will be 84 bytes higher, buf will be 80 and cookie will be 4.
In the static analysis of the IDA stack we can see the same thing. To go to the debugger, we have to double click on the definition of variables, in any of them:
So IDA show us the same representation of the stack. The HORIZON is at ZERO and we get the same that we’ve seen when debugging:
80 allocated bytes
4 allocated bytes
0x19FF28 (in my system)
0x19FF70 (in my system)
The RETURN ADDRESS (the place where programs will return after finishing the main function) is lower than buf, so if we calculate it precisely, we could modify this value and choose where it will return to. In this case, we will return it to the address that will print YOU WIN and then force it to return to where we want:
The function will finish in RETN and should return to 0x401290 as we saw previously, which is the value RETURN ADDRESS that was stored when we entered the function. But we will modify that value when overflowing buf, setting it to 0x401084. When executing RETN, we will return to the block where it prints YOU WIN.
Next, we need to calculate the distance to modify the RETURN ADDRESS. To start, let’s look at the static representation of the stack again.
We know that it will start copying from buf down:
We will mark from buf until the RETURN ADDRESS (r):
Now in the marked zone we should right click ARRAY, in order to see the number of bytes to copy until reaching that zone:
It should be 88 bytes, and then the 4 bytes will modify RETURN ADDRESS, so our payload should be something like:
payload = b"A" * 88 + b"\x84\x10\x40\x00"
So "\x84\x10\x40\x00" is the address where we want it to return 0x401084.
Stop the debugger, and run the script attaching IDA to a breakpoint set after MessageBoxA. Once we are attached, we’ll press F9, accept the MessageBoxA, and the debugger will stop at the breakpoint:
Let’s trace the program with F8, go over the gets, and go to the comparison with the cookie.
We can see that the cookie will not be 0x0d0a00. In this case it will be 0x41414141. (We can double click on the cookie and press the D key until it changes to dd to see it as a DWORD.) As those values are not the same, it will not go to YOU WIN.
The instruction JNZ prompts a conditional jump—in this case, if the two values are not equal, it will jump to 0x401091, as we see highlighted above with the green arrow.
Once we reach the EPILOGUE of the function, EAX will return with a zero value, which would be the return value of the function if it has one (all the functions return something in EAX, even void functions).
Then ESP returns to the HORIZON value, freeing the zone of cookie and buf variables. But, without deleting the content, it will return to the value of HORIZON by default. Additionally, the RETURN ADDRESS right below EBP STORED will return to where that value points.
We have the ESP and EBP values in the HORIZON, and below that is s, the EBP STORED, and r, the RETURN ADDRESS.
We see that when our payload overflowed, it modified EBP STORED with 0x41414141 and the RETURN ADDRESS that was previously 0x401290 is now 0x401084.
If we press F7 (STEP INTO) POP EBP will copy the value of EBP STORED of the parent function into EBP. The problem is that EBP STORED was modified with 0x41414141 when we overflowed the stack, which is now a different value from when PROLOGUE stored the real value with PUSH EBP.
This means that EBP will be 0x41414141 and the stack now will be aligned to do the RETN and return to the address 0x401084.
If we want the stack to show the upper value, we have to click on the stack window and click on the arrow to the right of ESP:
Now we see that the first value of the stack is 0x401084, so we press F7 to return.
We can reach YOU WIN without a correct cookie by forcing the program to return to the address we want by modifying the RETURN ADDRESS.
While this is a valid solution, the problem is that EBP will still have a value of 0x41414141. If we continue tracing, this will eventually crash the program.
To demonstrate, let’s continue tracing with F8.
We see that if we execute the program from PYCHARM, once it goes through printf it does not show the YOU WIN in the python console.
This is because the way that PYCHARM uses stdin and stdout is different. However, if we run it from a windows command prompt, even if the program crashes, it will show the YOU WIN!
Let’s continue tracing to see where it breaks:
We see that the program executes the EPILOGUE again, but now with EBP equal to 0x41414141. When it copies that value to ESP, it will crash trying to access the stack value with POP EBP from an address that is not allocated:
For people just starting out, this is a perfectly valid solution, as it doesn’t matter that the program crashes.
The solution that we’ll now examine does not execute code in the stack and doesn’t hardcode any address. This requires more knowledge and is harder, even for those at advanced levels.
First we’ll trace it a bit:
import sys from subprocess import Popen, PIPE payload = b"A" * 84 + b"\x58\x97\x41\x00"+ b"\xd5\x12\x40\x00" + b"GGGG" + b"\xc0\x18\x40\x00" + 8 * b"A" + b"\x84\x10\x40\x00" + (b"\xb3\x46\x40\x00")*10 p1 = Popen(r"STACK4_VS_2017.exe", stdin=PIPE) print ("PID: %s" % hex(p1.pid)) print ("Enter para continuar") p1.communicate(payload)
We’ll run it from a windows console and attach it after MessageBoxA appears:
Since the cookie is not the same, it will jump to 0x401091.
Then we’ll get to the RETN instruction.
We jump to 0x4012d5, which is code from the same executable, so we don’t have any problem with ASLR, DEP or anything.
This is going to execute a POP ECX-RET, which will accommodate the stack.
We see that our payload now contains a “GGGG” which is 0x47474747. This is an unimportant value that is moved to ECX. The next RET causes the program to jump to the next value of our payload, which is 0x4018c0.
The function it jumped to is called SEH_Prolog, and does the same work as the PROLOGUE function that we’ve already seen. It also matches the values of ESP and EBP, and will then move ESP up the value given as the argument.
Most importantly is that the LEA EBP, [ESP + XXX] will put EBP back on a stack value, fixing the problem that could it crash the program.
If we trace it, we can see that before LEA, the value of EBP is 0x419758.
After the LEA, the value of EBP is now 0x19ff38.
Now EBP has returned to the stack value that we want. As this depends on the ESP value, the previous POP ECX - RET moved it right where we needed it. We’ll now continue tracing:
Once we get the RET instruction, it will jump to 0x401084, which is the YOU WIN block, but now with the correct EBP. Let’s continue tracing:
We’ll go over with F8 tracing, and see the YOU WIN! Now we just need it to finish without crashing.
When it reaches the final RETN, we’ll make the program jump to exit:
We see that EBP was again set to a bad value. Instead of jumping to the first instruction of the function, we’ll jump elsewhere, as seen below:
This modifies EBP to the correct value of ESP. We can then continue to EXITPROCESS.
We arrive at the ExitProcess, where the program finishes without error!
We've now completed the analysis of Stack4.
Next time, in part 6, we will start with ABO1 using RADARE, where we will have to execute the calculator or the notepad.
What's The State of Pen Testing Today?
Find out in the comprehensive The 2022 Pen Testing Report, which surveyed cybersecurity professionals about pen testing, focusing on the strengths, needs, and challenges of pen testing, and the role it plays across organizations.