Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- ** VAC encrypted code dumper via WOW64
- ** OR: a good use for TIB syscall hooking
- **
- ** This is for use with the VAC DLL with the following checksums:
- ** CRC32: 768D96EF
- ** MD5 : 28AB71A9216C0697027218BBF0023E81
- **
- ** You should inject this *after* VAC has loaded. (ie, launch the game first and
- ** then inject this into Steam.exe)
- */
- #include <stdio.h>
- #include <windows.h>
- #include <conio.h>
- #include <Tlhelp32.h>
- // some declarations...
- void HookSyscall();
- /*********************** Return address tables ****************************
- These are return addresses that are searched for on the stack whenever
- VirtualProtect is called. If one is found, the stack is rewritten so the
- code returns into our dumper.
- *************************************************************************/
- // All of the addresses in vac_return_addresses will use this variable
- // to determine where the code flow is. It's just the VAC module's handle
- // pulled from a snapshot.
- static HMODULE vac_base = NULL;
- #define VAC_RETURN_ADDRESS_COUNT 14
- static UINT32 vac_return_addresses[ 14 ] = {
- 0x14A9,
- 0x1E31,
- 0x2171,
- 0x2497,
- 0x29B0,
- 0x3586,
- 0x4546,
- 0x585B,
- 0x6611,
- 0x6FFA,
- 0x7313,
- 0x7DA2,
- 0x89D3,
- 0xF5E0,
- };
- // This is the return address from VirtualProtect() in the decryption routine.
- static UINT32 vac_virtualprotect_ra = 0x28F8;
- static UINT32 vac_createthread_ra = 0x6E36;
- // These are set up whenever NtProtectVirtualMemory is called.
- static UINT32 vac_last_return_address = 0;
- static UINT32 vac_decrypt_address = 0;
- static UINT32 vac_decrypt_size = 0;
- /*****************************************************************************
- CreateHijacker allocates some code off in memory that will change fs:0xC0 to
- our custom syscall filter when we point stuff to it.
- ****************************************************************************/
- UINT8* CreateHijacker( UINT32 resumeAddress ) {
- // Get both the hook address and the current program counter
- UINT8* hijacker = new UINT8[25];
- UINT32 syscall_hook_address = (UINT32)HookSyscall;
- UINT32 cur_pc = resumeAddress;
- UINT32 cur_pc_in_hijacker = (UINT32) hijacker + 19;
- // Load some code into the hijacker. This is plain x86 bytecode and is a bit messy,
- // not to mention hilariously inefficient.
- hijacker[0] = 0x50; // push eax
- hijacker[1] = 0xA1; // mov eax, HookSyscall
- hijacker[2] = syscall_hook_address & 0xff;
- hijacker[3] = (syscall_hook_address & 0x0000ff00) >> 8;
- hijacker[4] = (syscall_hook_address & 0x00ff0000) >> 16;
- hijacker[5] = (syscall_hook_address & 0xff000000) >> 24;
- hijacker[6] = 0x64; // mov fs:0xC0, eax
- hijacker[7] = 0xa3;
- hijacker[8] = 0xc0;
- hijacker[9] = 0x00;
- hijacker[10] = 0x00;
- hijacker[11] = 0x00;
- hijacker[12] = 0x58; // pop eax
- hijacker[13] = 0xff; // jmp old_eip
- hijacker[14] = 0x25;
- hijacker[15] = cur_pc_in_hijacker & 0xff;
- hijacker[16] = (cur_pc_in_hijacker & 0xff00) >> 8;
- hijacker[17] = (cur_pc_in_hijacker & 0xff0000) >> 16;
- hijacker[18] = (cur_pc_in_hijacker & 0xff000000) >> 24;
- hijacker[19] = cur_pc & 0xff;
- hijacker[20] = (cur_pc & 0xff00) >> 8;
- hijacker[21] = (cur_pc & 0xff0000) >> 16;
- hijacker[22] = (cur_pc & 0xff000000) >> 24;
- return hijacker;
- }
- /*************************************************************************
- The code dumper itself. VAC doesn't run scans too often so I won't assume
- that there will be conflicts with multiple scans running at the same time.
- *************************************************************************/
- void __stdcall WriteDecryptedCode( UINT32 address, UINT32 size ) {
- FILE* dumpFile = NULL;
- char stringBuffer[ 1000 ];
- sprintf(stringBuffer, "VACDump_%p.bin", address);
- dumpFile = fopen(stringBuffer, "wb");
- if (dumpFile) {
- fwrite( (void*)address, size, 1, dumpFile);
- cprintf("Dumped encrypted code at %p OK!\n", address);
- } else {
- cprintf("Couldn't dump encrypted code at %p\n", address);
- }
- }
- void __declspec(naked) HookVacReturnAddress() {
- __asm {
- // Push all registers onto the stack so we don't end up corrupting them.
- pushad
- // Write the file using the arguments passed to NtProtectVirtualMemory earlier.
- mov eax, vac_decrypt_size
- push eax
- mov eax, vac_decrypt_address
- push eax
- call WriteDecryptedCode
- // Pull registers and return back to the freshly decrypted code.
- popad
- push vac_last_return_address
- retn
- };
- }
- /*************************************************************************
- Here's the TIB syscall hook. This is attached to every thread when this code
- is injected.
- Also included in this block:
- FuckStack - modifies stack on NtProtectVirtualMemory.
- HijackVirginThread - changes fs:0xC0 to our filter on new threads.
- *************************************************************************/
- // real syscall address
- static UINT32 real_syscall = 0;
- void __stdcall FuckStack() {
- UINT32* _sp;
- __asm mov _sp, esp;
- int found_arguments = 0;
- cprintf("NtProtectVirtualMemory was called. Looking for the cause...\n");
- for (int i = 0; i < 3000; i++) {
- // We check for return addresses after the VAC decrypter is called,
- // and rewrite them so they go into the dumper. However, if we don't have
- // arguments from VirtualProtect, we don't even bother with this so the code
- // dumper doesn't crash.
- for (int x = 0; x < VAC_RETURN_ADDRESS_COUNT; x++) {
- if ((found_arguments) && (_sp[i] == vac_return_addresses[x])) {
- vac_last_return_address = _sp[i];
- _sp[i] = (UINT32)HookVacReturnAddress;
- }
- }
- // What we also check for is a return from VirtualProtect so we can
- // grab the arguments passed to it.
- if (_sp[i] == vac_virtualprotect_ra) {
- vac_decrypt_address = _sp[i + 1];
- vac_decrypt_size = _sp[i + 2];
- found_arguments = 1;
- }
- }
- }
- // NewThreadHijacker: Code that executes before CreateThread() returns to VAC.
- void __declspec(naked) NewThreadHijacker() {
- HANDLE newThreadHandle;
- __asm pushad;
- // get the new thread handle
- __asm mov newThreadHandle, eax
- // STOP! IN THE NAME OF LOOOOOOOOOOOOOOOOOVE
- SuspendThread( newThreadHandle );
- CONTEXT ctx;
- GetThreadContext( newThreadHandle, &ctx );
- UINT8* hijacker;
- hijacker = CreateHijacker( ctx.Eip );
- ctx.Eip = (DWORD)hijacker;
- SetThreadContext( newThreadHandle, &ctx );
- ResumeThread( newThreadHandle );
- __asm {
- popad
- jmp vac_createthread_ra
- };
- }
- void __stdcall HijackVirginThread() {
- UINT32* _sp;
- __asm mov _sp, esp;
- cprintf("New thread was created. Sending our thread hijacker after it.\n");
- for (int i = 0; i < 3000; i++) {
- if ( _sp[i] == vac_createthread_ra ) {
- // Rewrite it to return into new code
- _sp[i] = (UINT32)NewThreadHijacker;
- }
- }
- }
- void __declspec(naked) HookSyscall() {
- __asm {
- cmp eax, 0x4D // NtProtectVirtualMemory on Windows 7
- jz do_stack_crawl
- cmp eax, 0x4B // NtCreateThread
- jz hijack_virgin_thread
- cmp eax, 0xA5 // NtCreateThreadEx
- jz hijack_virgin_thread
- continue_syscall:
- jmp real_syscall
- do_stack_crawl:
- pushad
- call FuckStack
- popad
- jmp continue_syscall
- hijack_virgin_thread:
- pushad
- call HijackVirginThread
- popad
- jmp continue_syscall
- };
- }
- /*************************************************************************
- And finally, the startup code!
- *************************************************************************/
- void StartDumper() {
- AllocConsole();
- cprintf("We're benign. Don't call us a hack.\n");
- cprintf("Looking for VAC... (you'll probably want to start your game now)\n");
- // Grab the original syscall pointer (it's the same for all threads)
- __asm {
- push eax
- mov eax, fs:0xC0
- mov real_syscall, eax
- pop eax
- };
- // First loop here goes and hunts down the VAC module.
- while (!vac_base) {
- MODULEENTRY32 module;
- // Take a fresh snapshot. If we can't take it, die.
- HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
- if (!snapshot) {
- cprintf("Snapshot couldn't be taken.\n");
- return;
- }
- module.dwSize = sizeof( MODULEENTRY32 );
- // Grab the first module
- BOOL moduleFound = Module32First( snapshot, &module );
- while ( moduleFound ) {
- if ( strstr(module.szModule, ".tmp") ) {
- cprintf("VAC found in module list as %s\n", module.szModule);
- // Grab VAC's hModule and rebase our pointers.
- vac_base = module.hModule;
- for (int i = 0; i < VAC_RETURN_ADDRESS_COUNT; i++)
- vac_return_addresses[i] += (UINT32)vac_base;
- vac_virtualprotect_ra += (UINT32)vac_base;
- vac_createthread_ra += (UINT32)vac_base;
- }
- moduleFound = Module32Next( snapshot, &module );
- }
- }
- // Here is the actual hook. We'll enumerate through threads and change fs:0xC0 so syscalls
- // redirect into our filter.
- cprintf("Attaching stack patcher to threads...\n");
- UINT32 ourProcess = GetCurrentProcessId();
- THREADENTRY32 thread;
- HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
- if (!snapshot) {
- cprintf("Snapshot failed. Can't attach patcher to threads.\n");
- return;
- }
- thread.dwSize = sizeof(THREADENTRY32);
- BOOL haveThread = Thread32First( snapshot, &thread );
- while ( haveThread ) {
- // If this thread isn't in our process we shouldn't even care about it.
- if ( thread.th32OwnerProcessID != ourProcess) {
- haveThread = Thread32Next( snapshot, &thread);
- continue;
- }
- // If the thread is this thread we'll just freeze like a dumbass.
- if (thread.th32ThreadID == GetCurrentThreadId()) {
- haveThread = Thread32Next( snapshot, &thread);
- continue;
- }
- // Open the thread and attempt to pause it. If we can't open it just
- // go on to the next thread.
- HANDLE hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, thread.th32ThreadID);
- if (!hThread) {
- cprintf("WARNING: Couldn't open thread %d.\n", thread.th32ThreadID);
- haveThread = Thread32Next( snapshot, &thread);
- }
- CONTEXT ctx;
- // Now pause the thread and grab its context. If we can't get the context, resume
- // it and move onto the next one.
- SuspendThread( hThread );
- BOOL gotCtx = GetThreadContext( hThread, &ctx );
- if (!gotCtx) {
- cprintf("Couldn't get thread context. Last Windows error %d\n", GetLastError());
- ResumeThread(hThread);
- haveThread = Thread32Next( snapshot, &thread);
- continue;
- }
- // Allocate the thread hijacker.
- UINT8* hijacker = CreateHijacker( ctx.Eip );
- // Change the program counter to go into the hijacker, then resume the thread.
- ctx.Eip = (DWORD)hijacker;
- if ( SetThreadContext( hThread, &ctx ) ) {
- cprintf("Assigned context OK!\n");
- } else cprintf("Couldn't assign context. Windows error %d\n",GetLastError());
- ResumeThread( hThread );
- // Wait 100 ms then free the hijacker.
- //Sleep(10);
- //free(hijacker);
- cprintf("Thread %d was hijacked!\n", thread.th32ThreadID);
- // Move on to the next thread.
- haveThread = Thread32Next( snapshot, &thread );
- }
- cprintf("Startup went OK. Go play and I'll dump shit (or maybe just crash)\n");
- }
- BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
- if (fdwReason == DLL_PROCESS_ATTACH) {
- CreateThread( NULL, 100000, (LPTHREAD_START_ROUTINE)StartDumper,NULL,NULL,NULL);
- }
- return TRUE;
- }
Advertisement
Add Comment
Please, Sign In to add comment