Hello all and welcome back to the Autist’s Guide to Malware Analysis. Today’s topic will be anti-disasm/debugging.
Let’s jump in.
First, and obviously, you should know what I mean by “anti”. Anti-disasm/debug techniques are a form of obfuscation, so don’t think of them as some sort of show-stopping maneuver. Malware authors can throw them anywhere and everywhere, so be wary of this! The sole objective of code obfuscation is to slow down analysts, not stop them. Remember, you’re a pro that’s subscribed to BowTiedCrawfish, nothing can stop you.
Anti-Disassembly
There are 2 main algorithms that are used to produce disassembled code. Remember that disassemblers interpret precompiled code and attempt to produce assembly instructions. Both algorithms have their strengths and weaknesses.
Linear Sweep
Starts from one point in the program and continues down and interprets the assembly instructions at each contiguous byte.
Extremely fast and simple to implement.
Prone to anti-disassembly instruction emissions.
Flow-Oriented/Recursive Descent
Pseudo-emits the instructions and follows the logical path that they produce.
Extremely difficult to defend against.
Difficult to implement across architectures and compilers.
Can sometimes miss functions that are not called by the program.
Utilized by most commercial disassemblers such as IDA Pro.
For a linear sweep, a malware author can emit some trash bytes, JMP over them, and completely ruin a function’s disassembly instructions. Fortunately, you probably won’t find any modern disassemblers that will be caught by this.
Anti-debugger
Anti-debugging is much more prevalent in the modern malware space. Most (all) malware analysts lean on debugging/dynamic analysis as their main analysis technique, so defending against such is much more commonplace.
Guess what’s the easiest way to stop someone from debugging your code.
Go on, guess.
It’s easy. You detect if the code is being debugged, and halt the program if so. Riveting, I know.
There are several different methods to do such, so let’s go over them.
Using the Windows API
Should be pretty straightforward, there are 4 API methods to try and detect debugging.
IsDebuggerPresent
Very simple. Searches the Process Environment Block (PEB) and retrieves the value of the field
IsDebugged
.
CheckRemoteDebuggerPresent
Almost identical to
IsDebuggerPresent
, however this checks another process to see if it is being debugged (can be the currently running program).
NtQueryInformationProcess
This is a native API function. We have yet to discuss native API, but that’s a topic for another time.
In second parameter, you can dictate the time of information to retrieve. You would pass
ProcessDebugPort
into it to retrieve the port number that the process is being debugged on, 0 if it isn’t.
OutputDebugString
This one is a bit tricky as you have to set it up properly, here’s some code from Sikorski’s Practical Malware Analysis that does just this.
DWORD errorValue = 12345;
SetLastError(errorValue);
OutputDebugString("Test for Debugger");
if (GetLastError() == errorValue)
{
ExitProcess();
}
else
{
RunMaliciousPayload();
}
Now, if you were paying attention in the last 2 posts (property and behavioral analysis), you would know that it would be easy to figure out if a program is performing this techniques. Checking function imports could show you if, but you would have to patch out the functions that perform these checks. There is an OllyDbg script that does this, I’m sure, but I can’t find it at the time.
Manual Checks
A bit more sneaky that using Windows API, but you can check the PEB manually to detect debugging. Some debugger plugins can change the value of the IsDebugged
struct for you, which can render this method useless.
You can also check running processes. Fortnite, for example, will exit if you have IDA Pro open at all, even if you aren’t necessarily using it.
If you’ve read my post on hashing, you should understand checksums. Malware can perform checksums on its code section. Remember that software breakpoints replace the instruction byte with 0xCC, and thus would change the checksum of the code.
Timing
This is one of the most popular methods of anti-debugging. Placing a breakpoint halts a program, obviously, so performing a delta-timing maneuver can thwart debugging. E.g. “if this function takes longer than 2 seconds to run, exit”.
Manual Interruptions
Malware authors can place bytes into code manually while the program is running. Therefore, they can set their own breakpoints for you. Pretty funny idea, though I’ve never seen any malware in the wild that does this.
Walkthrough
On my Tor site is a new malware specimen called diverticulum. I actually wrote a very similar program for an assignment for class, so I was able to adjust it slightly for this use-case. This was inspired by a challenge that was in the 2021 Flare-On.
As always, I’m going to let you explore and see what you can discover on your own. Your goal with this program/challenge is to discover the key used with the program.
Remember your steps (Consider this cheat-sheet):
1. Static property analysis and theorizing
2. Static/Behavioral/Dynamic analysis
3. ???
4. Profit
Have you tried on your own yet? Yes? Good. Let’s walk through it.
Alright, you know the drill. PeStudio time. The presentation of the program .zip more or less gives away what kind of malware it is, but let’s do the basics for example reasons.
Nothing interesting in the libraries tab, but the functions tab is spicy.
Iterating files and writing to them, but not reading them. Seems a bit strange, don’t you think?
That’s an interesting looking string.
Let’s move on to behavioral analysis. All signs here point to ransomware, so let’s prove it.
If we execute the program, nothing seems to happen. We need to look deeper.
That doesn’t look nice. But wait, if we plainly execute the program, we aren’t debugging, so what gives?
This most likely slipped by you if you didn’t look at a decompiler. “If argc == 2 is 0, jmp” (argc != 2). We can just pass garbage as a parameter and figure out what the program does with it.
You may have also noticed these random bytes. I put that in there as an example of trash instructions that can thwart linear sweep algorithms.
First, let’s focus on the anti-debugger code. We can just patch that out.
If you’re using IDA, you may want to have opcodes showing. Options → General → Number of opcode bytes = 8 (or 10).
There are a few methods for patching out this logic. You can negate the logic (easiest) or simply NOP out the logic (idiot-proof). You can also JMP over code you don’t want to run (Chad move).
Patching programs will require you to know opcodes. Consider this manual.
You can try either of these 2 methods:
Negating the Logic
JZ, as you should know, stands for “jump if zero”. So we just change it to “jump if not zero”.
Click on the 74 and then click Edit → Patch program → Change byte. Change the 74 to 75 (JNZ).
Click OK.
JMPing Over the Logic
Click on the first byte at .text:00401273. (Might be different for you, click the start of the PUSH statements that setup the IsDebuggerPresent
call)
Edit → Patch program → Change byte
Change the first two bytes to EB 30. EB stands for short jump to a relative address, and 30 is the distance/offset to jump to.
loc_4012A5 is the location we want to reach. We will accomplish this with a near relative jump. There are other jump methods but this is easiest. I calculated the relative address (0x30) by subtracting the loc_4012A5 .text position by 00401273 and added +2 because this is a 2 byte instruction. Windows calculator helps here.
Click OK.
I should note that applying this patch broke my decompiler. There’s probably a way to fix what the hell just happened, but I just analyzed the assembly from here on out.
This is a lot to wrap your head around quickly, so don’t feel bad if you’re going “whaaaat?”.
Regardless, what just happened is that we jumped over a debugger check.
Do not forget to apply your patches. In IDA, Edit → Patch program → Apply patches to input file…
Last thing to do is to add an argument to increment argc
to 2.
Debugger → Process options → Insert something into parameters
Obviously this will be garbage, we just want to see how the program uses the argument.
Let’s start debugging again.
Running the debugger plainly (with no breakpoints) stops us here.
The CLI instruction clears the interrupt flag, meaning that you can no longer debug the program. This sucks, but we can just NOP out the instruction (NOP = 0x90).
This is an example of a manual check of the
IsDebugged
flag.
This is the last of the anti-debug code. Now we can actually reverse engineer this program.
What it does, is it takes the argument that the program was executed with, and performs a basic Viginère-style XOR cipher with it on every file in the program’s directory (excluding itself).
So, how do we go backwards from this?
Look closely at what was provided in the .zip. One of the files is called “thealphabet.txt”. It’s also 26 characters long. If I were a betting man, I would bet that this file once had letters a-z before it was encrypted. This cipher can be brute forced if you know what the input plaintext was beforehand, as is the nature of an XOR operation. So, that’s what we’ll do.
NOTE THAT EXECUTING THE PROGRAM SUCCESSFULLY WITH THE GARBAGE CIPHER 12345 WILL RE-ENCRYPT THE FILES. BE SURE TO ATTEMPT THE BRUTE FORCE WITH A FRESH COPY OF THE MALWARE AND ITS FILE PACKAGE.
Let’s begin:
YTTQVV^P_]^_]WWFFG@DLNAOLI
^^^^^^^^^^^^^^^^^^^^^^^^^^
abcdefghijklmnopqrstuvwxyz
==========================
86753098675309867530986753
This can be reduced down to 8675309.
Therefore, you can run the program with 8675309 passed as a parameter to decrypt all the files!
That’s it for this post. I had a lot of fun writing it, so I hope you have a lot of fun reverse engineering it!
Go!
-BowTiedCrawfish