Runs sample
.exe immediately closes
“Fuck”
Yep, we’re going further into the anti-disassembly/anti-debug/anti-VM/anti-EVERYTHING world. You should be proud of me. I wrote my own basic anti-reversing solution JUST FOR YOU ALL. It’s pretty cool; only took me a few hours.
Anyways, we’ve hardly scratched the surface with anti-reversing. It’s time to really get our hands dirty and catch up with all the shit they’ve got nowadays. We’re going x64 mode.
Before You Read
This is a lot of advanced material.
I highly advise reading these and have familiarized yourself with the Windows API. We’re gonna discuss a lot:
Basically all of the RTMA series until this one
What
First, let’s establish some terminology.
Anti-tamper
To “tamper” with something is to meddle with or “exert corrupt influence” upon it. An anti-tamper solution is to prevent this from happening. Anti-tamper solutions (Byfron, Themida, Denuvo, VMProtect) deploy anti-tamper functionality. To summarize, anti-tamper solutions make it difficult for someone to modify the program that the solution is protecting.Anti-debugger/anti-VM/anti-disassembly/anti-<etc>
Anti-tamper but specifically contained to the “anti-” subject.Anti-reverse
The summation of the above. Can also prevent inspection of the protected program.
There’s a little bit of nuance with those terms. I like to associate anti-reverse with anti-”spy” e.g. closing the program if you have Process Hacker open. Anti-tamper is more of a “don’t touch me”.
Anyways, we’re going to focus more on anti-reversing techniques. These can range vastly. On the bright side (if you’re a bad guy), it’s more of a cat-and-mouse game, so all you have to do is figure out what the anti-reversing solution doesn’t do. For example, if your target program doesn’t deploy a technique to scan executable memory pages, then it is at risk of a manual map injection (will be discussed in a later post).
The hard part is that you have to find out what the solution doesn’t do. This might be a bit of a headache if you’re attacking a program that requires a subscription model (or a game you pay for) as your account can be banned/blacklisted if they catch you messing with it.
Why
Because you’re a bad guy trying to hack Fortnite or you’re a good guy trying to understand anti-reversing methods.
How
Might as well get started with the walkthrough.
Methodology
Inspect the inner workings of self-defending program
Find a way to win the game
Requirements
A PE Explorer
Disassembler/Decompiler
A debugger (if you manage to disable the anti-debugger code)
MSVC++
Go to the Tor site and acquire advanceddefense.zip from the programs folder.
We will perform the standard analysis procedure.
Property Analysis
We can toss the program into PeStudio and be happy that it isn’t packed.
It uses 2 libraries, kernel32 and wintrust. Wintrust is an API that enables security and trust features for the program. Usually this is just verifying certificates, signatures, and authenticity of other programs. We can look at the imported functions to get a better idea of what the program does.
It Imports WinVerifyTrust, which performs verification of trust of object among a variety of options: signature, SSL/TLS, and certificates. In most cases, this just checks to see if a program is signed/has a valid signature. This is useful to know, perhaps this program checks to see if certain files are signed? I wonder which ones ;).
There’s also some basic process and module iteration imports, but we haven’t even run the program yet to see what it does. Go ahead and run it. I bet it crashes.
I should also note that the program may also crash depending on the kind of anti-virus software you have. I just have Windows defender which allowed it, so you may want to disable your AV if you aren’t using Windows AV.
It opens for maybe half a second and then crashes. Maybe try it from a command prompt?
Uh-oh. It scans running processes and has a blacklist. Let’s remove everything that’s decently reversing-related and then try and run it.
(Did this from my VM to showcase the feature)
Looks like this program also has anti-VM capabilities. At this point, you would deploy your suicide machine to run it, but since this isn’t malicious (I promise), you can just run it on your main machine, and play some ASCII snake!
Sadly, dynamic analysis looks to be a bit out of reach, so you’ll need to deploy some static techniques to figure out how to bypass the program’s defenses. The program isn’t packed, but if it was, you would just dump it and then start static analysis.
Remember to get your x64 IDA versions!
The first step will be to inspect the curious imports. Perhaps there will be some fun codestuffs where WinVerifyTrust is used.
Spoiler alert, it’s used in sub_140012CE0. It doesn’t do much, but it’s called by sub_1400126F0, which is a vtable function!
This looks like something interesting. Protector appears to be the structure that “does the protecting”, and here are a bunch of its functions. It might be hard to figure out what these functions do, but we’ll use imports to help us.
Let’s look back at sub_1400126F0 and sub_140012CE0. It looks like it iterates over all of the modules in the process and then calls WinVerifyTrust on them. Let’s see exactly what WinVerifyTrust is doing.
WinVerifyTrust’s functionality is dictated by a GUID structure. Here’s a hint, GUID::Data1 will tell you what’s going to happen.
0xAAC56B is the first part of WINTRUST_ACTION_GENERIC_VERIFY_V2, which simply verifies the signature of a file.
TLDR: yes, the program checks its modules for signatures. Unless you plan on buying a wincert or bypassing the detection, injecting is right out.
I bet you overlooked this, but let’s look back at sub_1400126F0.
“sub_14001B3E0(Filename, L".dll")
”
I wonder what that does. It looks like it checks to see if the file name contains “.dll”. If you look further at that function, it’s wcsstr
, which means that if the file name doesn’t contain “.dll”, then it doesn’t reach WinVerifyTrust. Keep that in your back pocket, because you may be able to inject a module with a different file extension. Or, you could just patch out that string. One last thing to note is that since it checks for loaded modules, you might be able to sneak in a manually mapped one to bypass it.
That’s that function, you should rename sub_1400126F0 to something a bit more appropriate like CheckSignedModules.
The first 2 functions in the vtable aren’t very translatable, so let’s look at sub_140012840.
In case this wasn’t obvious, this is a debugger check. It looks like it tries a few methods, including an exception check.
If you raise an exception with 0x40010006 (DBG_PRINTEXCEPTION_C) as the exception code, a debugger would capture that code. Therefore, if you encapsulate a RaiseException that passes DBG_PRINTEXCEPTION_C as its exception code in a try/except block, and the except block isn’t reached, then the program is being debugged.
The try/except isn’t visible from the pseudocode window, so the disassembly view might help.
The try/except block is here, you just need to look hard. If RaiseException is caught in the except block, then RIP is set to 14001288B, al is set to 1 (true), and the program is dictated as not being debugged. Otherwise, RIP is not changed and goes to 140012889, al is set to 0 (false), and the program is dictated as being debugged.
Let’s rename this function to CheckForDebuggers and bookmark the imports that it uses, we may try to deploy some IAT hooks later.
Let’s look at sub_140012A20 now.
It’s a doozy, with a bunch of GetModuleHandleA calls. The strings seem to be… weird.
If you explore those functions, you’ll find a bunch of weird code. To spoil what this is, this is string obfuscation. Trying to deobfuscate the strings in this function would be a huge pain as each string has its own unique deobfuscation function. Good luck with that.
GetModuleHandle returns a HANDLE to a loaded module, so it appears to be looking for a list of DLLs and returning true if they exist. It already checks for unsigned DLLs, so maybe there’s a blacklist.
There are a few unobfuscated strings, though.
It checks NTDLL for wine_get_version. This is a way to detect if Linux’s Wine is running. Wine is (not) an emulator, so this might be checking for emulators and similar.
There’s also a looped call to GetFileAttributesA != -1. This technique just checks for the existence of a file. It might be checking for the existence of certain files on your computer (but why). We’ll bookmark the function for now. Let’s call it CheckForWineAndStuff.
Next stop is sub_1400128A0.
It loops all of the running processes on the machine, and looks for them within an std container (sub_140010320 gives the hint here. Did you catch it in the last function?). It’s hard to tell what container it is, but we could bookmark unk_140063860 as that is the object that is referenced.
The pe.szExeFile appears to be moved to the main structure (a1 + 160). This field also appears to be a std::basic_string, which can be assumed to be a std::string. These are a bit hard to pick up if you don’t expect them. sub_140016C00 gives the clue here.
If the process name is in this std container, return false. Otherwise, return true. Maybe checking a process blacklist? It didn’t like that IDA was open, so we can assume this one :).
Let’s rename sub_1400128A0 to CheckProcesses.
We’re getting a lot of good information from these virtual functions. And this is just from static analysis. Let’s move on to sub_140012390.
This looks fun.
I’m not much of a betting man, but this appears to be a very important function.
It looks like this function calls all of the other virtual functions that we looked at earlier, which perform various anti-reversing checks. So, if we find a way to VTable hook this, we can possibly remove all of the checks.
Let’s rename sub_140012390 to CheckChecks.
There’s one more virtual table function to look at, sub_1400124D0.
(I’m running out of email space, so there won’t be any more screenshots).
This function looks like it spits out the error message when the program quits. From here we can see all of the types of protections it offers. We figured out most of them just from imports, minus the first one, “You are not allowed to modify the code”. I bet that one is sub_1400129F0. Might be an issue when we try to modify the program, but if we’re able to prevent the check from even being called, then we should be fine. We also now know that CheckForWineAndThings is a VM check.
Now, maybe we can try and trick the program with just static patching.
I’m doing this one the fly. I wrote the anti-tamper solution and have been writing this post while testing how good it is. I originally planned to have this be a 2-part post, but since I’ve just found a really easy way to break it, let’s do that instead and I’ll make the defenses stronger by next post so it’s appropriate to use cooler injection/hack methods. Sound good? Good.
Look back at CheckChecks. It takes one parameter, which is |this|. What if we convinced the vtable that CheckChecks was another function? We should try to overwrite CheckChecks’ entry in the vtable with a function that returns true (at least very often!) so that the program doesn’t quit on us.
We can try pointing it to CheckForWineAndStuff and just not use a VM, that would at least let us debug the process, hopefully. We could always just patch some “mov rax, 1; ret;” code into a check as well, but maybe the code modification check would catch that.
Anyways, let’s patch the vtable entry for CheckChecks with the address of CheckForWineAndStuff, which is (when endian’d) is 20 2A 01 40 01 00 00 00.
Now, we can debug and not get kicked out! Let’s throw a breakpoint at the start of CheckForWineAndStuff so we can trace back.
Looking at the stack view, we see that CheckForWineAndStuff (which was originally CheckChecks) is called by a function called StartAddress (IDA might have renamed this for us).
This is a perpetual while loop that calls CheckForWineAndStuff and sleeps for an undisclosed time period. We can check further back in the call stack but it won’t show us much. Maybe StartAddress is referenced elsewhere?
It is :D
If we look at this Xref, we see that StartAddress is used as the parameter in a CreateThread call. You know, threads can be suspended, right? ;)
This Xref also appears to be the constructor of Protector. This can be noted by the assignment of “Protector::`vftable'”
Anyways, by this point, you’ve essentially disabled this program’s protections. I’m completely out of email space, so I’ll just summarize some ways to bypass this anti-tamper solution.
Patch out the CheckChecks call from the vtable (which we did)
Suspend the Protector protect thread
Inject the program via any method and disable the code integrity check (as the program only checks for injection every 100ms)
Performing a manual map injection or injecting a file that doesn’t end in “.dll”
From there, deploy IAT hooks to prevent the process from exiting or performing various checks
OR, deploy vtable hooks to prevent check calls
Deploy a shellcode injection that patches out check calls
Patch in “mov rax 1; ret;” instructions into the check calls
And more, if you’re creative enough :)
That’s all for this post. It’s a lot (literally at limit), so if you made it this far, thanks for reading!
Have a great weekend.
Go!
-BowTiedCrawfish