I have, for a number of reasons, decided to discontinue this project. For one i havn't looked at any of this for months now. At this point i have no interest in finishing either the essay or tools i wrote to aid in the unpacking process. However, since a substantial amount of work has already been put into this project it would be a shame to completely discard what i came up with.
ACProtect is essentially an ASProtect rip-off with the same features, API and even help text (literally the same). Add to this, that it's a bad rip-off littered with both design and implementational bugs. The few strengths that it does have, data compression and code encryption using strong cryptography, can be added to projects by other much more professional means than using ACProtect.
The following are a couple of exerpts from my unfinished essay. They are from the first part of it, detailing mostly the anti-debugging techniques used. If you need additional information on the more gory parts of ACProtect, please ask questions. You might need to resize your browser's window to improve readability.
The virtual anatomy of ACProtect can be compared to that of an onion because it's layered in the same way. There's layer after layer, within containing the next after it, all the way until the centre of both objects.
There are roughly three different types of layers in ACProtect: the actively constructive layers, the anti-debugging layers and the layers that decrypt and transfer control between the two former types. From here on I'll be referring to the decrypting layers as \"bridging code\" because that's what they really are. Both the actively constructive layers and the anti-debugging layers are implemented as modules, with the bridging code there to help connect them. The modularity is especially noticeable with the anti-debugging modules since they are conditionally activated.
AD1: Uses an INT 1 instruction to throw an exception and detect SoftICE that way.
Segment registers have different values in different versions of Windows. In this snippet the code segment value is TESTed to see if the code is currently executing on an NT-flavoured Windows version. The rest of the module is skipped if it is not. Right after this it installs its own handler in the SEH chain and potentially generates an exception (by executing the INT 1 instruction.) The new term SEH stands for Structured Exception Handling and requires an essay in itself. We'll keep it simple and establish that the code is only dangerous if it is not traced. If you have SoftICE loaded its internal INT 1 handler will change the instruction pointer before passing the exception to ACProtect. Without SoftICE the exception will be reported occurring at the INT 1 instruction but when you have SoftICE loaded the instruction pointer is incremented to indicate an exception after the INT 1 instruction.
007649CE * MOV *AX, CS
007649D0 * TEST AL, 4 * * * * * * * * * * * * * // Windows NT ?
007649D2 * JNZ *ACProtec.00764A0C * * * * * * * // abort if not
007649D4 * NOP
007649D5 * NOP
007649D6 * NOP
007649D7 * NOP
007649D8 * CALL ACProtec.007649EB
007649DD * MOV *EBX, DWORD PTR SS:[ESP+C] * * * // SEH handler
007649E1 * ADD *DWORD PTR DS:[EBX+B8], 2 * * * *// EIP += 2
007649E8 * XOR *EAX, EAX
007649EA * RETN
007649EB * PUSH DWORD PTR FS:
007649F1 * MOV *DWORD PTR FS:, ESP * * * * * // install handler
007649F7 * XOR *EAX, EAX
007649F9 * INT *1
007649FB * INC *EAX
007649FC * INC *EAX
007649FD * OR * EAX, EAX
007649FF * JNZ *ACProtec.00764A06
00764A01 * NOP
00764A02 * NOP
00764A03 * NOP
00764A04 * NOP
00764A05 * POPAD * * * * * * * * * * * * * * * *// unmatched
00764A06 * XOR *EAX, EAX
00764A08 * POP *DWORD PTR FS:[EAX]
00764A0B * POP *EAX
00764A0C * ...
The easiest way to skip this module is to force the Windows NT check to fail. That will jump over the offending code and we'll be on our way to the next module.
AD2: Uses SEH to clear hardware breakpoints.
The INT3 throws a breakpoint exception when it's executed. If you have a debugger running it will catch the exception and pause on the INT3 instruction. A protection author would typically use this to their advantage because you know that a debugger is running if the exception is not passed to your own handler. However, this piece of code is worthless. Control would go straight to ACProtect's handler only if there was no debugger present, and then, what would be the point since all that the handler does is clear the debug registers? (It clears the debug address registers by updating the context structure that's being passed to it.)
00764C13 * CALL 00764C42
00764C18 * MOV *EAX, DWORD PTR SS:[ESP+4] * * * // SEH handler
00764C1C * MOV *ECX, DWORD PTR SS:[ESP+C]
00764C20 * INC *DWORD PTR DS:[ECX+B8] * * * * * // increment EIP
00764C26 * MOV *EAX, DWORD PTR DS:[EAX]
00764C28 * SUB *EAX, 80000003 * * * * * * * * * // exception code
00764C2D * JNZ *ACProtec.00764C41
00764C2F * NOP
00764C30 * NOP
00764C31 * NOP
00764C32 * NOP
00764C33 * XOR *EAX, EAX
00764C35 * MOV *DWORD PTR DS:[ECX+4], EAX * * * // DR0 = 0
00764C38 * MOV *DWORD PTR DS:[ECX+8], EAX * * * // DR1 = 0
00764C3B * MOV *DWORD PTR DS:[ECX+C], EAX * * * // DR2 = 0
00764C3E * MOV *DWORD PTR DS:[ECX+10], EAX * * *// DR3 = 0
00764C41 * RETN
00764C42 * XOR *EAX, EAX
00764C44 * PUSH DWORD PTR FS:[EAX]
00764C47 * MOV *DWORD PTR FS:[EAX], ESP * * * * // install handler
00764C4A * INT *3 * * * * * * * * * * * * * * * // throw exception
00764C4B * NOP
00764C4C * POP *DWORD PTR FS: * * * * * * * *// uninstall handler
00764C52 * ADD *ESP, 4
00764C55 * ...
The easiest way to skip this module is to not let it start execute at all. When you're at the call shown on the first line of the snippet, change the address of execution to the equivalence of 00764C55 shown here. Either way, just make sure you don't pass control to ACProtect's handler. If you find yourself on the IN3 instruction simply change the address of execution to point to the NOP right after it.
AD3: Detection of ring-3 debuggers by accessing PEB (Win2K) and TIB (Win9x).
We see two different techniques for detecting the presence of a ring-3 debugger. One is specific to Windows NT and the other is specific to Windows 9x, thereof the check and the branch at the beginning of the code. Trivia: the Windows NT technique is the same as used by the NT implementation of IsDebuggerPresent. All threads in the system have certain structures associated with them. These structures are used to keep track of the SEH list head, the TLS array pointer and debugging information among other things. The abbreviations PEB and TIB unfold to Process Environment Block and Thread Information Block, respectively. It's a rather big topic to cover so I'll leave it for you to research. The first 40 bytes of code (4 * 10) at the real entrypoint will be overwritten if the protection determines that you have a debugger active.
00764E5C * MOV *AX, CS
00764E5E * TEST AL, 4 * * * * * * * * * * * * * // Windows NT ?
00764E60 * JNZ *00764E7C * * * * * * * * * * * *// jump if not
00764E62 * NOP
00764E63 * NOP
00764E64 * NOP
00764E65 * NOP
00764E66 * MOV *EAX, DWORD PTR FS: * * * * *// pointer to PEB
00764E6B * MOVZX EAX, BYTE PTR DS:[EAX+2] * * * // BeingDebugged
00764E6F * OR * AL, AL
00764E71 * JNZ *00764E8E
00764E73 * NOP
00764E74 * NOP
00764E75 * NOP
00764E76 * NOP
00764E77 * JMP *00764EA1
00764E79 * NOP
00764E7A * NOP
00764E7B * NOP
00764E7C * MOV *EAX, DWORD PTR FS: * * * * *// DebugContext
00764E81 * OR * EAX, EAX
00764E83 * JNZ *00764E8E
00764E85 * NOP
00764E86 * NOP
00764E87 * NOP
00764E88 * NOP
00764E89 * JMP *00764EA1
00764E8B * NOP
00764E8C * NOP
00764E8D * NOP
00764E8E * MOV *EDI, DWORD PTR SS:[EBP+41F16D]
00764E94 * ADD *EDI, DWORD PTR SS:[EBP+40CF7B]
00764E9A * MOV *ECX, 0A
00764E9F * REP *STOS DWORD PTR ES:[EDI]
00764EA1 * ...
Our usual approach is still good. Skip it all together.
AD4: Verify that the parent process is one of the approved ones.
There's a lot more code in this module than in the other anti-debugging modules we've seen this far so I won't show it all. Instead, I'll try to explain what it does. *
First of all it makes sure that the CreateToolhelp32Snapshot API function was successfully resolved. The function is available in all Windows versions except versions of NT prior to Windows 2000 (version 5). If it was not resolved the module simply aborts execution. If, however, it was resolved, the module calls into bridging code to eventually connect with a sub-module. The sub-module is very careful to make sure that there are no breakpoints set on any of the functions it calls. We've already seen the code to do that. It's on page five if you need to refresh your memory.
The protected application retrieves its own process ID using GetCurrentProcessId and stores it away for a moment. It then uses the CreateToolhelp32Snapshot function and Process32First/Process32Next to cycle through all processes until it finds itself. Using the information it looked up, it is able to determine which process that started it. The same technique is then used to look up information about that process.
With the limited information comes the name of the file used to start the process. This would usually be \"Explorer.exe\" for the parent process. Unless, of course, you started the protected application through SoftICE's symbol loader (Loader32.exe) or using OllyDbg (OLLYDBG.EXE) or maybe using a completely different shell. The code around here is buggy and just generally stupid so I won't comment a whole lot on it. Basically, what it does is, it tries to hash the filename string and compare the resulting value to a table of pre-calculated hashes of \"approved\" parent names. As you can well imagine this is not a very smart thing to be doing to your customers. My choice of shell should not be dictated by anyone, obviously.
The proprietary algorithm used is this: (ESI points to the filename. Sometimes.)
The approved hashes are:
LODS * *DWORD PTR DS:[ESI]
ADD * * EAX, DWORD PTR DS:[ESI]
ADD * * EAX, DWORD PTR DS:[ESI+4]
MOV * * EBX, EAX
0xE3EDEFC2, 0xCAF9D7CE, 0xE8EDDAC5
The first one is \"EXPLORER.EXE\" (filename is uppercased before it's hashed). We may only speculate in what the rest of them are. I can think of a number of popular desktop replacements but it doesn't really matter. At one moment I was tempted to try brute-forcing the rest of the filenames but the algorithm doesn't exactly yield a unique hash for any given string. Rough tests show that about 0,0000015 percent of possible likely filenames will match one of the values.
The easiest way to bypass this module is to fool the first check we saw, the one that makes sure the CreateToolhelp32Snapshot API function is available. *
It's important to emphasise that none of these modules are very well integrated with the surrounding code. Once you see what patterns they follow and what rules they play by you'll also see the obvious ways to cheat them. Remember that the tail of any given module looks practically the same as that of the next one. You can determine a pattern at the byte-level that will lead you to other modules' tails. This knowledge will prove itself valuable when we examine the more advanced features of ACProtect.
AD5: Search the system for \"enemy processes\".
This module maintains a list of enemy processes that it doesn't want to find running in the system. It proceeds to generate a page fault if it finds any of these processes. The idea is pretty smart as the list is actually a merge of two lists. It contains both the default names of popular tools' executables and their main class names.
The same old check for CreateToolhelp32Snapshot can be seen at the start. A sub-module is called upon if the function is found to be present. Code in the sub-module performs an enumeration of processes in the system, and for all of them, the name of the file that started it is compared to each of the entries in the blacklist:
The list has been split up and re-arranged to give you a better overview. That last item seen is my own custom \"enemy process\" that I specified in one of the ACProtect tabs before I built the project. You might be interested in knowing that the filename is uppercased before the comparison. This immediately invalidates the \"OllyDbg\" entry in the blacklist! It also doesn't do a full string compare. No, it scans the filename from the left. There's only a minor difference but I thought I'd tell you.
EXESPY, * *WXR95, * * * * *REGMON,
REGMONEX, *RESSPY, * * * * REGSNAP,
MEMSPY, * *DEBUGVIEW, * * *PROCDUMP32,
FROGSICE, *MEMORYMONITOR, *OllyDbg,
FILE MONITOR, * *WINDOW DETECTIVE
MEMORY DOCTOR, * ADVANCED REGISTRY TRACER
MEMORY EDITOR, * SMU WINSPECTOR
MEMORY DUMPER, * NUMEGA SOFTICE LOADER
URSOFT W32DASM, *-=CHINA CRACKING GROUP=-
You're brought back to the super-module if all of your processes pass the test. Here's where the trick comes into play. The same list is checked against captions of top-level windows. It uses EnumWindows to find all the windows and GetWindowTextA with GetClassNameA to obtain information about them. As always, be careful about where you put your breakpoints.
AD6: Uses Get/SetThreadContext to clear hardware breakpoints.
You will see an attempt at updating the main thread's context to clear the debug address registers. It uses GetCurrentThread to obtain a pseudo-handle to the calling thread (the only thread in this case). It then uses GetThreadContext to store away its context where it can access and change it, after which it uses SetThreadContext to reload the thread with the updated context. Without revealing too much I think it's safe to say that the implementation is lacking something. We're off the hook this time as nothing whatsoever is changed in the context.
AD7: Detection of SoftICE by looking at changes to UnhandledExceptionFilter API.
If you have SoftICE loaded she'll have placed an INT 3 instruction at the start of UnhandledExceptionFilter. She does this in order to get first chance on all exceptions. This module looks at the first byte there and compares it to the INT 3 opcode. If it determines that you have SoftICE loaded it first installs an exception handler and generates an exception to enter into it. Once inside it continues to generate exceptions to make it harder for you to restore normal execution. Just skip past all of it.
AD8: Calls IsDebuggerPresent to catch your ring-3 debugger.
Yes, the name says it all. If it determines that you have a debugger present it starts scanning the code at the original entrypoint for calls and long jumps. When it finds one, it overwrites the target address and continues execution at the end of the module:
007650C2 * LODS BYTE PTR DS:[ESI]
007650C3 * CMP *AL, 0E8h * * * * * * * * * * * * // opcode CALL
007650C5 * JE * ACProtec.007650CF
007650C7 * NOP
007650C8 * NOP
007650C9 * NOP
007650CA * NOP
007650CB * CMP *AL, 0E9h * * * * * * * * * * * * // opcode JMP
007650CD * JNZ *ACProtec.007650C2
007650CF * MOV *DWORD PTR DS:[ESI], EAX
007650D1 * ...
AD9: Tries to find known tool drivers loaded in the system.
This is an extended version of the MeltICE trick. The trick was originally used to detect SoftICE by the presence of her active device drivers. But why stop at that? Here's the full list of driver names it looks for:
That about wraps our anti-debugging tour up.
SICE, * * * * * NTICE, * * * * *NTICE7871,
NTICED052, * * *TRWDEBUG, * * * TRW,
TRW2000, * * * *SUPERBPM, * * * ICEDUMP,
REGMON, * * * * FILEMON, * * * *REGVXD,
FILEVXD, * * * *VKEYPROD, * * * BW2K,
Anti-dumping? Import address table re-directing? ACProtect's API? Any questions you may have, just ask.