Bug hunting is the process of finding bugs in software or hardware. As people in the security domain, we use this term to describe the process of finding security-critical software bugs. Security-critical bugs also called software security vulnerabilities. These vulnerabilities might allow an attacker to remotely compromise systems, escalate local privileges, cross privilege boundaries, or otherwise wreak havoc on a system.
Software security vulnerabilities, and programs that take advantage of such vulnerabilities (known as exploits), get a lot of press coverage. In addition, numerous books and Internet resources describe the process of exploiting these vulnerabilities, and there are perpetual debates over how to disclose bug findings. Despite all this, surprisingly little has been published on the bug-hunting process itself.
Test Environment and Applications
We are gonna use the following systems and applications. The environment and target application are a bit old. But this is a good example to understand the bug hunting process.
- CoDeSys Gateway Server version 220.127.116.11
- Windows XP -The reason why we use this is, that there is no ASLR implemented. So won’t need to rebase the addresses in debugger and disassembler.
During the analysis, we will use the following tools:
In this research we are going to follow the below steps:
- Find the input points of the application
- Try to understand data flow
- Find a function that you can manipulate
- Try to manipulate the flow.
Analyzing Loaded Libraries
First of all, we can use WinDBG and attach the process gateway.exe.
First of all, we can use WinDBG and attach the process gateway.exe.
We can see loaded libraries when we attached the process.
As we see in the screenshot above, gateway.exe uses winsock32.dll and ws2_32.dll libraries. Those DLLs contain functions communicating on the network. At this point, we can open IDA and open the application that we want to analyze. Then we can check the imports tab and see the network functions used.
As we see the screenshot above, gateway.exe uses recv function to receive data from the network. So we can use this function as a start point. But, before analysis, we need to find out the TCP port that we need to use. For this, we can use TcpView application to see the port.
Thus we understand that the application gateway.exe uses port 1211 and recv function to receive data from the network. And we will use this entry point in our analysis.
Accessing from Network
To start the analysis, we need to know the memory address of recv function. Therefore we can use IDA and search for recv function. We can make a text search (Alt+T) for this. But for this search, we shouldn’t forget to select the “Find all occurrences” option for this search.
As you see in the above screenshot, the first memory address of recv function is 0x00409479. we can use WinDBG and set a breakpoint for recv function. At the same time, we can create a basic python script and send junk data to the application. The python code will be like this.
import socket import struct target = "localhost" port = 1211 data = "AABBCCDDEEFFHHII" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = sock.connect((target,port)) sock.send(data) sock.close()
And, to set a breakpoint we can use below command:
0:006> bp 00409479 0:006> g
When we run the python code we will see that the code hit the breakpoint address.
with the trace command in WinDbg, we can jump into recv function.
In the picture above we are located in recv function. Now that we can see stack backtrace.
Here we can see the function arguments of recv function. in the picture above 00000012 indicates that the data to be received. And, 011bff5c indicates the memory address that stores the data received.
You can visit here to see more information about the recv function. Also, you can see the below definition.
int recv( SOCKET s, char *buf, int len, int flags );
Now we will go through the end of recv function using pt command, and then we display the data at the address 011bff5c.
That is great. As you can see in the picture above, The data that we send can be seen in the application memory. So we know where the data we send has been stored in the memory.
Who Reads From Memory
The next step at this point, we need to find out who is using this memory address. So, we will use ba (break on access) command for this. Because it is very useful for this kind of case. ba allows us to see who is touching the variable or memory address. In our case, we will find out the function reads the memory address.
0.007>ba r1 011bff5c
We hit the breakpoint while the application reading the memory address. After the memory address was read, we can see a couple of instructions that we need to understand.
The first instruction is the bitwise AND operation with EDX register and 0FFFFh. This is more like a cleaning operation. After the operation, we see that the EDX register becomes 00004141. The second instruction is the CMP operation. This instruction compares EDX register with 0000dddd. That is a good point. If we use proper input data we can go deep in the application. Otherwise, the flow will go back to the beginning of the loop. Also, if you see the below picture, IDA shows the application flow. You can see more details in IDA.
Based on the information found above, we need to update our python code. So the python code will look like this:
import socket import struct target = "localhost" port = 1211 data = "\xdd\xdd" data += "AABBCCDDEEFFHHIIJJKKLLMMNNOOPPRRSS" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = sock.connect((target,port)) sock.send(data) sock.close()
This time, we will see that instruction pointer will not go back to the beginning of the loop.
The data we send can be seen in
we see the part of the data we sent in the instructions and registers in the flow, we can see that some part of the data we sent is being used in the flow. At this point, we can open IDA and see the upcoming calls that we can use for our analysis.
we can see in the instructions that there is another recv function in the flow. So we set a breakpoint for the second recv function and check the function parameters.
The breakpoint for the second recv has been hit. When we analyze the memory we see that the 0x49494848 has been set for the length parameter. Basically, we can assume that the first recv function is for defining data size and type for the second recv. So if we put correct parameters for the second recv, we can go through more deep in our analysis. let’s update the python code as follows.
import socket import struct target = "localhost" port = 1211 data = "\xdd\xdd" # comparison data += "AABBCCDDEEFF" data += struct.pack('<I', 0x00000200) # 512 Bytes # Data 1 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = sock.connect((target,port)) sock.send(data) # Data 2 data = "A" * 512 sock.send(data) sock.close()