In part 7, we solved ABO2 in GHIDRA. In this part, we’ll use IDA FREE to solve ABO3.
As is the case with all of the ABOS, the goal is to run the calculator or some other executable that we want.
First, open ABO3 in IDA FREE to analyze it.
Searching for the PDB in the symbol server could produce the following error:
MS DIA SDK ERROR
If you do get this error, install the Visual C++ 2008 redistributable for 64 bits.
Once IDA is installed and restarted, load ABO3.
When it asks for the symbols, open them and then hit CTRL + F in the function window and type main to search.
Double click on the result that appears.
There is no decompiler in IDA FREE. It is only available in the PRO version. However, the source code may be in the CPP file. Even if we don't have the CPP file, we can still decompile it with the x64dbg.
There is a fn variable in the stack of the pointer function. This is initialized with the address of the system() function, which is used to execute a console command. If we pass it the name of an existing executable, if it can find it, it will execute it.
There is a 256 bytes buffer called buf.
When the program starts, the fn variable changes to save the address of the puts() function, which is used to print a text in the console.
Next, it calls gets() to fill the buffer called buf. Finally, it calls to execute the function whose address is saved in fn, which, if there is no overflow, will be puts(). It will then print the text that was entered as the second argument (argv) and will then exit the main without ever reaching the return address.
As the data input is completed through gets() you can overflow buf and step the value of the variable fn, so when you execute in fn(argv), you can jump where you want.
Let's now complete the static reversing in IDA as though we had not seen the source code.
STATIC ANALYSIS OF ABO3
Double-click on any variable in the function definition.
Look at the static representation of the stack.
There, you can see buf, a byte array variable (db= BYTES) whose length is 256 bytes. Below buf is fn, which is a 32 bits pointer variable with a length of 4 bytes or (dd= DWORD).
There we see all of the access to the variable fn. First, save the direction of the system function, and then save the address of the puts function. Finally, jump to execute the function whose value is saved in fn. If there is no overflow, it will jump to puts.
So what will be the puts argument?
argv is an array of pointers, each one pointing to a console argument.
argv points to the name of the executable.
argv points to the first argument.
argv points to the second argument.
And so on.
Let’s try to run it from a console using those arguments.
ABO3_Vs_2017.exe aaaa bbbb
argv points to the name of the executable =ABO3_Vs_2017.exe
argv points to the first argument = aaaa
argv points to the second argument= bbbb
This means that the argument that is passed to the puts function is controlled by you. Press ENTER, to pause the program, and it will print bbbb (argv ).
So, whatever function is stored in fn will be the argv and will be controlled by you.
Next, let’s consider how to fill buf and overflow it to overwrite fn.
You’ll need to send 256 bytes to fill buf and then the 4 bytes that will step on fn.
This should jump to execute at 0x41414141. Let's try it for ourselves to make sure.
Put a breakpoint when returning from the MessageBoxA.
Then run the script from a console.
Attach the process.
Once the message has been accepted, it will stop. You can see how it's going to save the system address in fn.
Double click on system to get the address of the function.
The executable is compiled with the embedded DLLs and system is inside the same executable. However, in this case, it does not belong to a dll.
When compiling it, keep a copy of the system API in the same executable. The same will happen with puts.
Press f8 and hover over fn to see the saved system() address.
Double-click on fn to set it to be a DWORD. Press the D key until it becomes a DWORD and it will show system.
Right click to see the system address.
Hit f8 again and save the puts address in fn. Hover the mouse over it to see it as DWORD, since the memory position where fn is has already changed.
Keep tracing to get the gets().
EAX will take the address of buf and will fill it up when passing over the gets().
Let’s keep tracing.
Move four to ECX and then:
This is equivalent to multiplying ECX by 2.
This means that ECX will be worth 8 after multiplying it by 2.
argv is then moved to EDX. Since you already know that it’s an array of pointers, this means each field will be 4 bytes.
Since we don't pass arguments, argv only has one field, which is a pointer to the name of the executable.
The number of argv fields matches the value of argc, which is the number of arguments passed.
Both are also arguments of the main function. In this function, argc is above argv because of the order in which the arguments were passed in the stack when main was called.
Double click on argv in the function definition. It will show the static representation of the stack and you will see the variable argc above argv.
If you double click on the argv variable, the code will take you to the memory address where it is when debugging.
Above it will be argc, which is 1.
So, this means the arguments in the script have to be passed. Once this is complete, launch it again.
The console arguments are passed after the name of the executable and separated by spaces.
Let's run it again and get to the same point where we were before.
Double click on argv in the code.
Transform them to DWORD by pressing D several times. You can see that argc is 3 because we passed three arguments, This means that argv is now an array of three-pointers to each argument string.
In the HEX VIEW, right click and select synchronize with > RDX.
From there, change it to Data format > Addresses with text.
Look at the array of pointers to strings. Each pointer points to an argument, with 0 being the name of the executable, 1 being the first argument (aaaa), and 2 being the second argument (bbbb).
Adding 8 to argv will move the EAX pointer to the second argument (bbbb).
This means that bbbb will be the argument of the function we'll jump to.
The address will be 0x41414141 if we pass it to DWORD.
In order to jump where we want and control the argument, instead of stepping on fn with
0x41414141, we need to overwrite it with the system address which is fixed and known because it is embedded in the executable.
Instead of the bbbb argument, put the calc string to run the calculator.
Running the script will execute the calculator.
You could also run another executable file. For example, let’s pull up notepad.
Attach the debugger and stop at the jump to run.
Once it’s passed to DWORD, you can see that it runs system.
Double-click on EAX.
You can turn it into a string using the A key.
This solves the ABO3 in IDA FREE. In Part 9, we’ll solve the ABO4 in radare.