Overcoming Malware Analysis Evasion - Binary Patching
The process of making changes to a binary and modifying its instruction flow, from a malware analysts perspective, this can be utilised to overcome measures the malware author has put in place to evade analysis, such as detecting if the target is a virtual machine.
Example 1 - Rock, Paper, Scissors
Taken from the Huntress CTF, this is a good example of how effective binary patching can be.
The aim is to win a game of rock, paper, scissors against a program which knows your input.
The first step is to copy the binary to have an original and a copy to which we will make changes.
We’ll use Cutter to open the binary in write mode.
We identify that it is a Nim binary, and so navigate to the NimMainInner function, and follow that to main__main62.
Following the function we soon find what we’re interested in.
The program calls a ‘determineWinner__main_58’ function, followed by a test operation on register AL, and then performs a Conditional Jump (JNE - Jump if Not Equal)
Contextually, and through some conveniently named functions, we know that reversing this jump should mean that winning = losing, and losing = winning
We can reverse the jump by right-clicking the instruction
Close Cutter and execute the newly modified binary to see if the outputs have swapped.
Example 2 - Targeted Malware
Also taken from the Huntress CTF, crab rave is a challenge where you need to get a DLL to execute to present the flag.
Upon execution, nothing happens.
Just like before, we’ll make a copy of the file, and open it in Cutter with write mode enabled.
Traversing the program we find the ‘NtCheckOSArchitecture’ function, which, after a series of other functions and instructions, calls a function which injects the flag.
API Calls associated with process injection
Without worrying too much about that, we’ll go back to the ‘NtCheckOSArchitecture’ function and review the instructions in-between the calling of this inject flag function, and the start of NtCheckOSArchitecture.
Multiple whoami functions are called, and the result is compared against a value, if the value does not match what the program is expecting, the payload will not execute.
In this context, whoami is a rust crate used to query the username and hostname.
https://docs.rs/whoami/latest/whoami/
We’ll bypass these checks by making note of the address where the inject function is called and changing the very first jump instruction to jump to this address.
Now when we execute the DLL, the payload executes.
Example 3 - .NET Binary - Virtualisation Check
This example is taken from my NJRat blog post.
The binary does a basic check to decide if the host is in a virtualised environment by querying Win32_CacheMemory
If there is a value for Win32_CacheMemory, the program assumes the host is not a virtual machine and will execute the next function.
Upon execution in my virtual machine with no changes, nothing happens.
To overcome this, we open the binary in DNSpy, locate the class containing the function, right-click on the class in the assembly explorer and select edit class
We can simply change ‘if (!Program.VM())’ to ‘if (Program.VM())’, so that the binary will only execute if it’s running in a virtualised environment.
Once done, click compile, and save to a new binary
Now when the binary is executed, the payload executes fully and we see C2 communication and further files being dropped onto the host.