{
	"id": "0904619d-fdbb-4026-94b2-59001d763dd5",
	"created_at": "2026-04-06T01:30:18.55281Z",
	"updated_at": "2026-04-10T03:20:37.239169Z",
	"deleted_at": null,
	"sha1_hash": "efb4b57f7095a51dbe77fc534c2a66acd82da3f3",
	"title": "PE Reflection: The King is Dead, Long Live the King",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 498258,
	"plain_text": "PE Reflection: The King is Dead, Long Live the King\r\nBy Chetan Nayak\r\nPublished: 2021-06-01 · Archived: 2026-04-06 00:11:16 UTC\r\nReflective DLL injection remains one of the most used techniques for post-exploitation and to get your code\r\nexecuted during initial access. The initial release of reflective DLLs by Stephen Fewer provided a great base for a\r\nlot of offensive devs to build their tools which can be executed in memory. Later came in PowerShell and C#\r\nreflection which use CLR DLLs to execute managed byte code in memory. C# and PowerShell reflection are both\r\nsubject to AMSI scan which perform string based detections on the byte code, which is not a lot different from\r\nyour usual Yara rule detection. Reflective DLLs however provide a different gateway which at a lower level\r\nallows you to customize how the payload gets executed in memory. Most EDRs in the past 3-4 years have\r\nupgraded their capabilities to detect the default process injection techniques which utilize Stephen Fewer’s\r\nreflective loader along with his Remote Process Execution technique using the CreateRemoteThread API.\r\nTo keep the detection false postivies to a minimum, most EDRs hook VirtualAlloc, VirtualALlocEx,\r\nWriteProcessMemory, CreateRemoteThread, QueueUserAPC, MapViewOfSection and a few more to hunt for\r\nconsecutive API calls and known malicious string scans in the RWX memory regions. But in the end, these are\r\nlegitimate windows APIs, and it becomes hard to categorize every such API call as malicious since it might lead to\r\na lot of false positives. Thus EDRs end up scanning the newly created Executable memory block in the remote\r\nprocess which has PAGE_EXECUTE_READWRITE permissions. Attackers realized this and started changing the\r\nmemory permission to PAGE_EXECUTE_READ for reflective DLLs and PAGE_EXECUTE for shellcode\r\ninjections. But this still leaves out a possibility of detection because of the new RWX artefact which get’s created\r\nby the loader after the injection.\r\nThe below image shows the default injection of Stephen Fewer with RWX modified to RX. You can see that even\r\nif you configure RX, the loader of Stephen Fewer still calls VitualAlloc with RWX, WriteProcessmemory after re-basing the PE and then calls the DllMain function as a function pointer.\r\nhttps://bruteratel.com/research/feature-update/2021/06/01/PE-Reflection-Long-Live-The-King/\r\nPage 1 of 4\n\nThe above logic can be verified from this line of Stephen fewer’s loader. This basically means that even if you\r\nallocate RX as the region for your initial loader code, your loader when executed, will rebase itself to a new region\r\nwith VirtualAlloc(RWX), load all the PE Sections and then call the DllMain entrypoint. Any EDR which hooks\r\nVirtualAlloc/VirtualAllocEx can scan the process memory for this RWX section, and it can quickly identify that\r\nthis is an injected DLL and quickly block it from it’s execution. Most payloads including the ones from Metasploit\r\nand other C2s do not provide any functionality for this section to be modified. Now, if you try to modify this part\r\nof the code and replace the RWX with first RW and then RX, then the dllmain execution will crash returning you\r\nan ACCESS_VIOLATION error. This is because several different sections of the PE, require different types of\r\npermissions. If you provide RWX to every PE section, it will work, but if you provide only RX, then it won’t\r\nwork because some PE sections require you to have the section as writable. If the section isn’t writable, the\r\nDllMain won’t be able to write any static variables to the required section or erase or reallocate new data in those\r\nparts of the section.\r\nHowever, those of you who have spent time reversing the DoublePulsar userland shellcode like me, would have\r\nnoticed that these payloads tend to reallocate the PE file a bit more than Stephen Fewer’s default reflective loader.\r\nSo, unlike Stephen’s loader which allocates the whole memory block to a single page of memory using\r\nVirtualAllocEx, we can simply distribute the sections of PE to different locations. Each of these sections will have\r\ndifferent permissions. So basically, before we copy the PE sections to the new rebased-address, we will validate\r\nthe IMAGE_SECTION_HEADER’s Characteristics attribute with the respective permissions using the ‘AND’\r\noperation which will check the binary bit if set or not, and then we will allocate every piece of the PE section to a\r\nnew page in memory. By doing this, every page will have its own permission and we will never require a full\r\nRWX region. We can split each section as follows.\r\nnumberOfSections = ((PIMAGE_NT_HEADERS)pOldNtHeader)-\u003eFileHeader.NumberOfSections;\r\npSectionHeader = ((ULONG_PTR) \u0026 ((PIMAGE_NT_HEADERS)pOldNtHeader)-\u003eOptionalHeader + ((PIMAGE_NT_HEADE\r\nwhile (numberOfSections--) {\r\n void* thisSectionVA = (void*) (dllNewBaseAddress + ((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eVirtu\r\n ULONG_PTR thisSectionVirtualSize = ((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eMisc.VirtualSize;\r\n DWORD ulPermissions = 0;\r\n if (((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eCharacteristics \u0026 IMAGE_SCN_MEM_WRITE) {\r\n ulPermissions = PAGE_WRITECOPY;\r\n }\r\n if (((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eCharacteristics \u0026 IMAGE_SCN_MEM_READ) {\r\n ulPermissions = PAGE_READONLY;\r\n }\r\n if ((((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eCharacteristics \u0026 IMAGE_SCN_MEM_WRITE) \u0026\u0026 (((PIMAGE\r\n ulPermissions = PAGE_READWRITE;\r\n }\r\n if (((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eCharacteristics \u0026 IMAGE_SCN_MEM_EXECUTE) {\r\n ulPermissions = PAGE_EXECUTE;\r\n }\r\n if ((((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eCharacteristics \u0026 IMAGE_SCN_MEM_EXECUTE) \u0026\u0026 (((PIMA\r\n ulPermissions = PAGE_EXECUTE_WRITECOPY;\r\n }\r\nhttps://bruteratel.com/research/feature-update/2021/06/01/PE-Reflection-Long-Live-The-King/\r\nPage 2 of 4\n\nif ((((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eCharacteristics \u0026 IMAGE_SCN_MEM_EXECUTE) \u0026\u0026 (((PIMA\r\n ulPermissions = PAGE_EXECUTE_READ;\r\n }\r\n if ((((PIMAGE_SECTION_HEADER)pSectionHeader)-\u003eCharacteristics \u0026 IMAGE_SCN_MEM_EXECUTE) \u0026\u0026 (((PIMA\r\n ulPermissions = PAGE_EXECUTE_READWRITE;\r\n }\r\n pVirtualProtect(thisSectionVA, thisSectionVirtualSize, ulPermissions, \u0026ulPermissions);\r\n pSectionHeader += sizeof(IMAGE_SECTION_HEADER);\r\n}\r\nThe below screenshot shows the newly rebased PE section which does not have any RWX regions anymore, and\r\nthe RX section only contains the executable code i.e. the .text section since all other remaining sections are\r\nallocated to other regions now.\r\nOne important note before we execute our main payload, is to cleanup any existing artefacts left from our\r\npreviously allocated (RX) region. This can be done using a simple struct containing the pointer to the start of our\r\ninitial RX region (thread) and the parameters passed to the thread and then forwarding it to Dllmain for cleanup\r\nusing VirtualFree. This can be done using the below code in DllMain. This basically erases the whole history of\r\nwho actually created the new rebased regions and executed DllMain.\r\n#include \"badger.h\"\r\nBOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)\r\n{\r\n BOOL bReturnValue = TRUE;\r\n switch (dwReason)\r\n {\r\n case DLL_PROCESS_ATTACH: {\r\n struct DLL_SWEEPER *dllSweeper = (struct DLL_SWEEPER*)lpReserved;\r\n CHAR* newlpParam = NULL;\r\nhttps://bruteratel.com/research/feature-update/2021/06/01/PE-Reflection-Long-Live-The-King/\r\nPage 3 of 4\n\ntask_crealloc(\u0026newlpParam, (CHAR*)dllSweeper-\u003elpParameter);\r\n VirtualFree((LPVOID)dllSweeper-\u003elpParameter, 0, MEM_RELEASE);\r\n VirtualFree((LPVOID)dllSweeper-\u003edllInitAddress, 0, MEM_RELEASE);\r\n badger_main(newlpParam);\r\n break;\r\n }\r\n case DLL_PROCESS_DETACH:\r\n case DLL_THREAD_ATTACH:\r\n case DLL_THREAD_DETACH:\r\n break;\r\n }\r\n return bReturnValue;\r\n}\r\nBrute Ratel will have this feature in the upcoming version 0.5. It not only relocates the whole PE section to a new\r\nregion with dedicated permissions, but also erases the whole PE, it’s arguments and it’s thread from memory\r\nwhich were created by it’s Parent process during the initial RX region execution. So, if any EDR or defender tries\r\nto find the injected PE in memory, they won’t find any threads created from external entity. Also, all the memory\r\nsections in the executable will look like garbage because the whole PE will be split into multiple parts allocated\r\ninto different places. And for those of you who don’t know, Brute Ratel’s payloads by default erased the DOS\r\nheader/PE header and NT header, whenever a new memory region was allocated since version 0.3.1.\r\nSource: https://bruteratel.com/research/feature-update/2021/06/01/PE-Reflection-Long-Live-The-King/\r\nhttps://bruteratel.com/research/feature-update/2021/06/01/PE-Reflection-Long-Live-The-King/\r\nPage 4 of 4",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://bruteratel.com/research/feature-update/2021/06/01/PE-Reflection-Long-Live-The-King/"
	],
	"report_names": [
		"PE-Reflection-Long-Live-The-King"
	],
	"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
		}
	],
	"ts_created_at": 1775439018,
	"ts_updated_at": 1775791237,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/efb4b57f7095a51dbe77fc534c2a66acd82da3f3.pdf",
		"text": "https://archive.orkl.eu/efb4b57f7095a51dbe77fc534c2a66acd82da3f3.txt",
		"img": "https://archive.orkl.eu/efb4b57f7095a51dbe77fc534c2a66acd82da3f3.jpg"
	}
}