CSAW_ESC_19 Quals Report
Report on analyzing x86 binary using GHIDRA SRE
Abstract - Brief Report on the methods used in analyzing the given binaries.
I. Introduction
In this report, we will look at cracking a simple x86 binary qualification.out
using reverse engineering tool Ghidra SRE.
A. Initial Analysis
Running ./qualification.out
results in no output.
Now we can move to reverse engineer the binary using Ghidra.
When starting up Ghidra, a Project Manager window will pop up and the rest of the windows doesn’t matter.
To create a new project :-
- File –> New Project –> Non-Shared Project
Select the project directory and give our project a name. After that the project will be displayed in project manager.
To import the binary file either use the Import File option from File or just drag and drop the binary file to the Project manager.
This will bring up the import dialog, make sure that the format was detected correctly and the other binary information such as the architecture and the bit size was detected. For instance, you can change to an entirely different architecture. In this case of this binary we can move on with what the Ghidra detected and press OK to import the binary file.
Once the import is done it will bring up the Import Result Summary, where you can see the number of functions, symbols, and other data. It will also show additional information where in this case libc was not found and unresolved external symbols which remain: 2
. For analyzing this binary file, fixing this additional issues are not required and we can step forward. After that, we double click the imported binary and start the CodeBrowser.
The first thing the Code Browser will ask is whether to analyze the binary or not. If we choose to yes it will bring up Analysis Option.
In here you can see Ghidra comes up with tons of analyzers, for example in ASCII String analyzer, that tries to detect ASCII strings in your binary and there are other options like setting the length of strings but in this case, we leave the default as it is and the only extra analyzing option we choose is Decompiler Parameter ID as it often improves our decompilation results. Hit the analyze button you will see the progress of your element in CodeBrowser. On small binary like this, analysis is pretty fast but on a bigger one, you will wait for a couple of minutes.
The main window in your CodeBrowser is called the Listing window, it contains your disassembling list and data. It can even list images and so on. When a function is selected in this window other elements will also get updated.
The next important window is the Decompilation Window, where you will find the decompilation results for the function which has been selected.
The Program Trees window contains the different sections from the binary, for example, the bss and data sections. Underneath it is the Symbol Tree window which contains Imports, Exports, Functions, Labels, Classes and Namespaces and underneath the Symbol Tree, there is Data Type Manager, where we can manage the data types and so on. There is also Console Window. Below the CodeBrowser, normally it contains the outputs of script.
II. Cracking the binary
A. Hex Dump
Now we can start reverse-engineering the binary. Another important window is the Bytes Window, which is the hex dump of the binary, by enabling the ASCII view we where able to find the one of the flag ‘<
B. Static Analysis
We start by searching the main function in the Symbol Tree, which is the entry point in the binary. Selecting the function will bring up the Listing and Decompile Window. Decompiler tries to infer the signature of the main function so we can edit the main function signature to:-
1 | int main(int argc, char *argv[]) |
Now the types and names are taken over from over signature. One problem was that the square bracket of the argv arguments was not treated as an array but instead as part of the function name. So we need to edit it again and change it into a pointer of a pointer.
1 | int main(int argc, char** argv) |
This will clean up the decompilation result massively. So let’s start going through our decompilation line by line.
Note: Selecting anything in the decompiler window will also automatically select in the listing.
1 |
|
if
clause checks the number of arguments supplied to the program. Which means we need to supply one argument to the program when its executed. As one of the arguments, is the program name.
Note: We can add comments over there by right-clicking at the required position and choose pre comment.
Now we can see that inside if
condition, a function is getting called named challengeFunction()
. Double-Click on the function name in the Decompiler window will change the function from main()
to challengeFunction()
.
One of Ghidra’s functionality is that it can rename the variable names and and edit its data type in Decompile window. So after renaming the varivaribles and data types the Decompile window will look like following image.
Which makes the code easier to understand.
The python code for the above function would look like this.
1 |
|
Now we can write a python script to find the required argument for the binary file for its to print"Great Job! The flag is what you entered"
1 |
|
Hence the required argument for the binary file is 21212121
.
C. Analysis Function List
By analysing the different function()
in the binary we can find a function named secretFunction()
.
From the decompilation table, it is clear that that the function just prints the string "The flag is <<shhimhiding>>"
, which was already found using hexdump.
D. Patch the given binary
As seen before, we know that the binary check for the correct argument to print one of the binary and one other function can be found from the secretFunction()
. Ghidra allows to patch specific instruction, so that we can change the functionality of the binary. In this case it is possible to call the secretFunction()
, which is not getting called in the binary or patch the challengeFunction()
so that any argument will result in successful outcomes.
Note: Issues :-
If you import an ELF binary with the format as Executable and Linking Format (ELF) and then export that binary, it creates a corrupted binary that segfaults due modified headers.
However, if you import it as “Raw binary” and manually select the language, then the exported file works as expected.
To Reproduce
- Steps to reproduce the behavior:
- Import the qualification.out ELF binary into your project (default settings).
- Right-Click it and click Export…
- Select Binary as the format.
- Export it.
- Make the exported binary executable.
- Run the exported binary.
Expected behavior
The exported binary should work instead of segfaulting (happens with multiple binaries that I’ve tested).
At first we patch the call to challengeFunction() to secretFunction() so that once we run the binary we get the output as The flag is <<shhimhiding>>
.
For patching a particular instruction in Ghidra, right-click and select patch instruction. In this case we just need to change the function address to the address of secretFunction()
and remove the argument check. After patching the code we need to export the modified code as binary.
We can also patch the challengeFunction()
, so that for any input the binary print the success message. So doing this particular task patching the JNZ instruction in the challengeFunction()
to JMP is only required.
After exporting the both the binary, when we execute them we get output as follows:
E. Analyze Strings
In Ghidra, there is a functionality Search for Strings. Once we select this option a pop up window will appear.
Providing the correct argument we can search for all the referential strings in the binary.
From this window we can find the location of the flags as we were able to find two strings which turn out to be interesting.
Conclusion
The binary can be reverse-engineered and exploited in a variety of ways, which is illustrated using the methods given above.