{
	"id": "92436154-d44d-453c-af98-d972ef75ec35",
	"created_at": "2026-04-06T00:11:43.676697Z",
	"updated_at": "2026-04-10T03:21:36.999691Z",
	"deleted_at": null,
	"sha1_hash": "4786302866c25e864d77bb221ad9d5076ab17818",
	"title": "Navigating a Maze of Intricacy – Malware Book Reports",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 10906028,
	"plain_text": "Navigating a Maze of Intricacy – Malware Book Reports\r\nBy muzi View all posts\r\nArchived: 2026-04-05 21:56:41 UTC\r\nGuLoader TL;DR\r\nGuLoader is a polymorphic shellcode loader packed full of anti-analysis and anti-vm techniques to evade\r\ndetection. The malware began as a Visual Basic (VB) 5/6 downloader, first identified in 2019. VB served as a\r\nwrapper for the core component implemented in shellcode until late last year. GuLoader began experimenting with\r\na variety of delivery methods including VBS and macro-enabled documents before introducing NSIS (Nullsoft\r\nScriptable Install System) in 2022. GuLoader and its delivery mechanisms are frequently updated by the authors\r\nto inhibit analysis and make detection more difficult. GuLoader typically delivers Remote Access Tools such as\r\nRemcos, but has been observed delivering numerous different malware families. Let’s attempt to navigate this\r\nmess of anti-analysis techniques together.\r\nSHA256: ee548086db277e0febd2797b582a734ac451a9cd050540d2a1fd08afa6232721\r\nNSIS Installer\r\nNSIS Primer\r\nA NSIS script is a regular text file with a special syntax. A script file contains Installer Attributes, Pages and\r\nSections and Functions. Each line is treated as a command. The following section provides essential knowledge\r\nrequired to analyze a NSIS script. For additional details, see the following link.\r\nInstaller Attributes\r\nInstaller attributes determine the behavior, look and feel of the installer. These attributes can change text shown\r\nduring installation, the number of installation types, etc. An example of an Installer Attribute is:\r\nAddBrandingImage left 100\r\nPages\r\nA non-silent installer has a set of wizard pages that let the user configure the installer. The Page command is used\r\nto set pages to be displayed. A typical set of pages looks like this:\r\nPage license\r\nPage components\r\nPage directory\r\nPage instfiles\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 1 of 33\n\nUninstPage uninstConfirm\r\nUninstPage instfiles\r\nSections\r\nInstallers commonly have multiple options available to the user during installation. For example, an installer may\r\nallow the user to install additional tools, plug-ins, examples and more. Each of these components has\r\ncorresponding code. If the user selects to install this component, then the installer will execute the respective code\r\nfor that component. In a script, that code is defined in sections.\r\nInstructions used in sections are different from instructions for installer attributes. They are executed at runtime on\r\nthe user’s computer and can extract files, read from and write to the registry, INI files or normal files, create\r\ndirectories, create shortcuts and more. See Instructions for more information. An example of a section looks like\r\nthis:\r\nSection \"My Program\"\r\n SetOutPath $INSTDIR\r\n File \"My Program.exe\"\r\n File \"Readme.txt\"\r\nSectionEnd\r\nFunctions\r\nFunctions, like Sections, contain script code. The difference between sections and functions is the way in which\r\nthey are called. There are two types of functions: user functions and callback functions.\r\nUser functions are called from Sections by the user or other functions using the Call instruction. User functions\r\nwill not execute unless you call them. After the code in the function has executed, the installer will continue\r\nexecuting the instructions that came after the Call instruction, unless installation has been aborted inside the\r\nfunction. User functions are useful if an installer contains a set of instructions that need to be executed in several\r\nlocations of the installer. Example user function:\r\nFunction Hello\r\n DetailPrint \"Hello world\"\r\nFunctionEnd\r\nCallback functions are called by the installer upon certain defined events, such as when the installer starts.\r\nCallbacks are optional. Example callback function:\r\nFunction .onInit\r\n MessageBox MB_YESNO \"This will install My Program. Do you wish to continue?\" IDYES gogogo\r\n Abort\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 2 of 33\n\ngogogo:\r\nFunctionEnd\r\nGuLoader NSIS Script\r\nNSIS Script\r\nThe NSIS install script is located in the .nsi file, which is bundled in the executable.\r\nFigure 1: Files extracted from NSIS using 7z\r\nThe script is relatively small, containing 6 user functions, 1 callback function and 1 unused section. Though the\r\nscript is small, the control flow of the script is intentionally convoluted, with junk commands sprinkled\r\nthroughout. The callback function .onMouseOverSection serves as the entry point. The key commands from the\r\nentry point function are:\r\n1. Store file path\r\n$INSTDIR\\Skrivefelt172\\Beskyttelsesprogram\\Udledningstilladelses169\\Gtteriernes.The in var\r\n$_45_\r\n2. Store the value 41000 in var $R1\r\n3. Store the value 1 in var $R7\r\n4. Store the value 2893 in var $1\r\n5. Store the file path $INSTDIR\\Arbejderklassernes.Ato in var $4\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 3 of 33\n\nFigure 2: Entry point callback function .onMouseOverSection\r\nThe callback function ends by calling func_36 . func_36 contains a loop that extracts individual characters\r\nfrom Arbejderklassernes.Ato to build command strings to load and execute GuLoader shellcode. Control flow\r\njumps between functions and labels, making analysis difficult to follow. The following commands are the key\r\ncomponents for the loop.\r\n1. Open $INSTDIR\\Skrivefelt172\\Beskyttelsesprogram\\Udledningstilladelses169\\Gtteriernes and store\r\nfile handle in var $_46_ .\r\n2. Push $_46_ onto the stack and Pop $R2 (store file handle in $R2 )\r\n3. Call func_0 to call FileSeek and move the file pointer to var $R1\r\n4. Read 1 byte from\r\n$INSTDIR\\Skrivefelt172\\Beskyttelsesprogram\\Udledningstilladelses169\\Gtteriernes at file pointer\r\nlocation and store in var $_42_\r\n5. Push $_42_ onto the stack and Pop $1 (store the byte read from the file in var $1\r\n6. Iterate index variable $R1 by 205 (file pointer + 205)\r\n7. Copy Z into var $R9\r\n8. Copy $1 (byte read from file) into var $ 0\r\n9. Loop condition: If $ 0 is a not a Z, write the it to the registry key HKCU Software\\Allos Setup and loop\r\nagain starting at label 37. If $0 is a Z, call func_23 to write the remaining value to the registry key\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 4 of 33\n\nHKCU Software\\Allos Setup and read the string stored in that key into var $R8 , then call func_62 to\r\nallocate memory using System::Alloc followed by System:Call to call the deobfuscated command that\r\nuses the Windows API to execute the GuLoader shellcode.\r\nFigure 3: Main execution loop that extracts commands from file to the registry and executes\r\nshellcode\r\nThe resulting deobfuscated commands use the Windows API to:\r\n1. Load the file $INSTDIR\\Arbejderklassernes.Ato\r\n2. Set the file pointer to offset 63101\r\n3. Allocate RWX memory of size 19456000\r\n4. Read shellcode starting from offset 63101 size 19456000 into the allocated RWX memory\r\n5. Execute GuLoader shellcode using EnumWindows callback function\r\nkernel32::CreateFileA(m r4 , i 0x80000000, i 0, p 0, i 4, i 0x80, i 0)i.r5\r\nkernel32::SetFilePointer(i r5, i 63101 , i 0,i 0)i.r3\r\nkernel32::VirtualAlloc(i 0,i 19456000, i 0x3000, i 0x40)p.r2\r\nkernel32::ReadFile(i r5, i r2, i 19456000,*i 0, i 0)i.r3\r\nuser32::EnumWindows(i r2 ,i 0)\r\nFigure 4: EnumWindows callback function executes shellcode\r\nGuLoader Shellcode\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 5 of 33\n\nPEB Parsing and API Hashing\r\nShellcode Decrypt and Execute\r\nThe first section of shellcode is responsible for decrypting the stage one shellcode. GuLoader calculates an XOR\r\nkey by performing arithmetic operations against a constant value, XOR decrypts the encrypted shellcode byte-by-byte, and transfers execution to the decrypted shellcode. The screenshots below show the XOR decrypt and the\r\ncall eax instruction that executes the decrypted stage one shellcode.\r\nFigure 5: XOR decrypt shellcode using key 0x3AD1577C\r\nFigure 6: Call decrypted GuLoader shellcode\r\nWalking the PEB and Resolving Windows APIs\r\nGuLoader does not have an Import Address Table (IAT) and therefore must manually resolve addresses of the\r\nfunctions it needs to execute. GuLoader walks the Process Environment Block (PEB) to locate base addresses of\r\nloaded modules and enumerates their export tables to find the desired Windows APIs . The PEB is always located\r\nat offset 0x30 (Win32) within the Thread Information Block (TIB).\r\ntypedef struct _PEB {\r\n BYTE Reserved1[2];\r\n BYTE BeingDebugged;\r\n BYTE Reserved2[1];\r\n PVOID Reserved3[2];\r\n PPEB_LDR_DATA Ldr;\r\n PRTL_USER_PROCESS_PARAMETERS ProcessParameters;\r\n PVOID Reserved4[3];\r\n PVOID AtlThunkSListPtr;\r\n PVOID Reserved5;\r\n ULONG Reserved6;\r\n PVOID Reserved7;\r\n ULONG Reserved8;\r\n ULONG AtlThunkSListPtr32;\r\n PVOID Reserved9[45];\r\n BYTE Reserved10[96];\r\n PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;\r\n BYTE Reserved11[128];\r\n PVOID Reserved12[1];\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 6 of 33\n\nULONG SessionId;\r\n} PEB, *PPEB;\r\nOnce a pointer to the PEB is acquired, the shellcode gets a pointer to the PEB_LDR_DATA structure, which is\r\nlocated at offset 0xC. Ldr contains an entry, InMemoryOrderModuleList , which is a doubly-linked list that\r\ncontains the loaded modules for the process.\r\nFigure 7: Walking the PEB\r\nEach item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure, including the DllBase address and\r\nthe DllName.\r\ntypedef struct _LDR_DATA_TABLE_ENTRY {\r\n PVOID Reserved1[2];\r\n LIST_ENTRY InMemoryOrderLinks;\r\n PVOID Reserved2[2];\r\n PVOID DllBase;\r\n PVOID EntryPoint;\r\n PVOID Reserved3;\r\n UNICODE_STRING FullDllName;\r\n BYTE Reserved4[8];\r\n PVOID Reserved5[3];\r\n union {\r\n ULONG CheckSum;\r\n PVOID Reserved6;\r\n };\r\n ULONG TimeDateStamp;\r\n} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 7 of 33\n\nFigure 8: Walking the PEB (continued)\r\nOnce the DLL has been identified, GuLoader iterates the exports of the DLL in search of the desired API.\r\nGuLoader uses DJB2 to hash the name of the API and compare it against the pre-computed hash value. This\r\nmethod reduces the number of strings visible in memory, increasing the difficulty of detection.\r\nFigure 9: Iterate DLL exports and calculate DJB2\r\nGuLoader leverages the DJB2 algorithm throughout the codebase to hash strings. The constant 5381 (0x1505)\r\nand instruction shl 5 are a clear indication of the use of the algorithm. Below is a representation of the\r\nalgorithm,\r\n unsigned long\r\n hash(unsigned char *str)\r\n {\r\n unsigned long hash = 5381;\r\n int c;\r\n while (c = *str++)\r\n hash = ((hash \u003c\u003c 5) + hash) + c; /* hash * 33 + c */\r\n return hash;\r\n }\r\nGuLoader employs an additional XOR as part of its DJB2 algorithm for additional obfuscation. This ensures that\r\nthe DJB2 hashes of specific APIs cannot be used for detection mechanisms such as Yara rules.\r\nFigure 10: Loop to calculate DJB2 hash\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 8 of 33\n\nVectored Exception Handler\r\nGuLoader registers a custom vectored exception handler (VEH), using RtlAddVectoredException , as a control\r\nflow obfuscation technique to hinder analysis in debuggers and disassemblers. A VEH is an extension to\r\nstructured exception handling that are not frame-based, therefore the VEH will be called for unhandled exceptions\r\nregardless of the location in a call frame. VEHs are called in the order they are added and can be designated to run\r\nfirst when registered via AddVectoredExceptionHandler .\r\nPVOID AddVectoredExceptionHandler(\r\n ULONG First,\r\n PVECTORED_EXCEPTION_HANDLER Handler\r\n);\r\nGuLoader calls RtlAddVectoredExceptionHandler with the First argument set to 1, which indicates the\r\nhandler should be the first handler to be called. The VEH is used to control execution by dynamically calculating\r\nthe address of EIP based on instructions following the address in which the exception occurred. GuLoader\r\nincorporates code throughout the shellcode that intentionally triggers the following three exceptions, causing the\r\nVEH code to execute.\r\n1. 0xC0000005 EXCEPTION_ACCESS_VIOLATION\r\n2. 0x80000004 EXCEPTION_SINGLE_STEP\r\n3. 0x80000003 EXCEPTION_BREAKPOINT\r\nEXCEPTION_ACCESS_VIOLATION\r\nGuLoader triggers access violation exceptions by performing mathematical operations on a constant stored in a\r\nregister, then uses this value to attempt to write data the the [invalid] memory address referenced by this constant.\r\nThis causes an access violation exception 0xC0000005 , triggering the VEH.\r\nFigure 11: Code to trigger access violation exception\r\nEXCEPTION_SINGLE_STEP\r\nSetting the Trap Flag is a well-known way to detect if a debugger is currently attached to a process. When the Trap\r\nFlag is set, a Single Step exception is raised. If a debugger is attached, it will handle the raised exception and\r\ncontinue execution. If a debugger is not attached, the exception will be handled by the exception handler, in this\r\ncase, the GuLoader VEH.\r\nThe code below is an example of the code blocks located throughout the GuLoader shellcode that cause a Single\r\nStep exception 0x80000004 . Constant obfuscation is used to conceal the value of 0x100, which is eventually\r\nstored in edx . pushfd is used to push the EFLAGS register to the top of the stack. Next, the value of the\r\nEFLAGS is calculated via or dword ptr ds:[edi] (0x206), edx (0x100) , resulting in the value 0x306 .\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 9 of 33\n\n0x306 is 1100000110 in binary, meaning the bit in position 8 (Trap Flag) is set. Finally, pushfd pops the\r\ndword on top of the stack into the EFLAGS register, setting the Trap Flag and triggering a Single Step exception\r\n(when the debugger is not attached).\r\nFigure 12: Code to trigger single step exception\r\nEXCEPTION_BREAKPOINT\r\nThe INT3 (0xCC) instruction is a single-byte instruction defined for use by debuggers to temporarily replace an\r\ninstruction in a running program in order to set a breakpoint. When an INT3 instruction is executed, a breakpoint\r\nexception 0x80000003 is triggered and the VEH is executed. If a debugger is attached, the exception is handled\r\nby the debugger, the VEH is not called, and program execution is paused. Instructions following the INT3\r\ninstructions are often invalid, causing exceptions and breaking execution in the debugger.\r\nFigure 13: int3 instruction to trigger breakpoint exception\r\nGuLoader VEH\r\nWhen an exception is thrown, the VEH receives an EXCEPTION_POINTER structure, which contains a pointer to the\r\nExceptionRecord and ContextRecord .\r\ntypedef struct _EXCEPTION_POINTERS {\r\n PEXCEPTION_RECORD ExceptionRecord;\r\n PCONTEXT ContextRecord;\r\n} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;\r\nThe ExceptionRecord contains a machine-independent description of the exception. The most important member\r\nfor the GuLoader VEH is the ExceptionCode , which is used to determine the code branch to execute in order to\r\ncalculate EIP and continue execution.\r\ntypedef struct _EXCEPTION_RECORD {\r\n DWORD ExceptionCode;\r\n DWORD ExceptionFlags;\r\n struct _EXCEPTION_RECORD *ExceptionRecord;\r\n PVOID ExceptionAddress;\r\n DWORD NumberParameters;\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 10 of 33\n\nULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];\r\n} EXCEPTION_RECORD;\r\nOnce the ExceptionCode is identified, the VEH accesses the ContextRecord to retrieve EIP, then calculates a\r\nnew EIP and continues execution using the following formula:\r\n1. Exception_Access_Violaton and Exception_Single_Step : eip = ((eip + 2) ^ 0xDB) + eip\r\n2. Exception_Breakpoint : eip = ((eip + 1) ^ 0xDB) + eip\r\nNote: The XOR value changes in each sample of GuLoader.\r\ntypedef struct _CONTEXT {\r\n DWORD64 P1Home;\r\n DWORD64 P2Home;\r\n DWORD64 P3Home;\r\n DWORD64 P4Home;\r\n DWORD64 P5Home;\r\n DWORD64 P6Home;\r\n DWORD ContextFlags;\r\n DWORD MxCsr;\r\n WORD SegCs;\r\n WORD SegDs;\r\n WORD SegEs;\r\n WORD SegFs;\r\n WORD SegGs;\r\n WORD SegSs;\r\n DWORD EFlags;\r\n DWORD64 Dr0;\r\n DWORD64 Dr1;\r\n DWORD64 Dr2;\r\n DWORD64 Dr3;\r\n DWORD64 Dr6;\r\n DWORD64 Dr7;\r\n DWORD64 Rax;\r\n DWORD64 Rcx;\r\n DWORD64 Rdx;\r\n DWORD64 Rbx;\r\n DWORD64 Rsp;\r\n DWORD64 Rbp;\r\n DWORD64 Rsi;\r\n DWORD64 Rdi;\r\n DWORD64 R8;\r\n DWORD64 R9;\r\n DWORD64 R10;\r\n DWORD64 R11;\r\n DWORD64 R12;\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 11 of 33\n\nDWORD64 R13;\r\n DWORD64 R14;\r\n DWORD64 R15;\r\n DWORD64 Rip;\r\n union {\r\n XMM_SAVE_AREA32 FltSave;\r\n NEON128 Q[16];\r\n ULONGLONG D[32];\r\n struct {\r\n M128A Header[2];\r\n M128A Legacy[8];\r\n M128A Xmm0;\r\n M128A Xmm1;\r\n M128A Xmm2;\r\n M128A Xmm3;\r\n M128A Xmm4;\r\n M128A Xmm5;\r\n M128A Xmm6;\r\n M128A Xmm7;\r\n M128A Xmm8;\r\n M128A Xmm9;\r\n M128A Xmm10;\r\n M128A Xmm11;\r\n M128A Xmm12;\r\n M128A Xmm13;\r\n M128A Xmm14;\r\n M128A Xmm15;\r\n } DUMMYSTRUCTNAME;\r\n DWORD S[32];\r\n } DUMMYUNIONNAME;\r\n M128A VectorRegister[26];\r\n DWORD64 VectorControl;\r\n DWORD64 DebugControl;\r\n DWORD64 LastBranchToRip;\r\n DWORD64 LastBranchFromRip;\r\n DWORD64 LastExceptionToRip;\r\n DWORD64 LastExceptionFromRip;\r\n} CONTEXT, *PCONTEXT;\r\nFigure 14: VEH Calculating EIP for Access Violation/Single Step Violation\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 12 of 33\n\nAnti-Analysis and Anti-Debug\r\nFigure 15: Your new favorite MessageBox indicating your debugger/VM has been detected and\r\nGuLoader is terminating\r\nSoftware Breakpoint Check\r\nGuLoader performs anti-analysis/debug checks prior to calling Windows APIs by checking for breakpoints at the\r\nstart of the function. When setting a software breakpoint on a function in a debugger, the debugger patches the\r\nfirst byte with a 0xCC , 0x3CD or 0xB0F , depending on the type of breakpoint selected, to trigger a software\r\ninterrupt. GuLoader checks the first byte of the function for these values in order to detect software breakpoints. If\r\ndetected, GuLoader jumps to code that crashes the process.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 13 of 33\n\nFigure 16: Check for software breakpoints\r\nScan Memory for Pre-Computed DJB2 Hashes of Strings\r\nGuLoader scans the entire memory area using ZwQueryVirtualMemory from 0x00010000 to 0x7FFFF000 for\r\nstrings indicating the malware is running in a virtualized environment or for various security tools.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 14 of 33\n\nFigure 17: ZwQueryVirtualMemory to scan entire memory area\r\nZwQueryVirtualmemory returns the MEMORY_BASIC_INFORMATION struct, which contains information including the\r\nBaseAddress as well as Protect , which describes current page protection.\r\ntypedef struct _MEMORY_BASIC_INFORMATION {\r\n PVOID BaseAddress;\r\n PVOID AllocationBase;\r\n ULONG AllocationProtect;\r\n USHORT PartitionId;\r\n SIZE_T RegionSize;\r\n ULONG State;\r\n ULONG Protect;\r\n ULONG Type;\r\n} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION;\r\nGuLoader access the State member, looking for memory pages with protection PAGE_EXECUTE ,\r\nPAGE_EXECUTE_READ , PAGE_EXECUTE_READWRITE , PAGE_WRITE , and PAGE_READWRITE (not pictured). GuLoader\r\nthen scans the identified memory pages for strings, hashes the string using DJB2 and compares the hash against\r\npre-computed hashes.\r\nFigure 18: Check memory page protection\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 15 of 33\n\nFigure 19: Check memory string hash against pre-computed DJB2 hashes\r\nQEMU Agent Detection\r\nGuLoader uses CreateFileA to check for of C:\\Program Files\\Qemu-ga\\qemu-ga.exe and C:\\Program\r\nFiles\\qga\\qga.exe to identify the QEMU emulator.\r\nFigure 20: Check for existence of Qemu\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 16 of 33\n\nFigure 21: Check for existence of Qemu continued\r\nDbgBreakPoint and DbgRemoteBreakin\r\nGuLoader gets the address of DbgBreakPoint and patches the first byte 0xCC (int3) with 0x90 (nop) ,\r\nmeaning breakpoints will no longer pause execution in the debugger.\r\nNote: Setting a breakpoint on this function inserts a CC at the beginning of the function, negating this anti-debug\r\ntechnique.\r\nFigure 22: DbgBreakPoint patched with 0x90\r\nDbgUiRemoteBreakin\r\nThe DbgUiRemoteBreakin API is used by the debugger to break in to a process. GuLoader patches this API to\r\nensure that the process cannot be attached to for debugging by replacing the beginning of the API with a call to\r\nExitProcess .\r\nFigure 23: Patched DbgUiRemoteBreakin API to call ExitProcess\r\nPatch ldrLoadDll\r\nGuLoader patches the initial bytes of LdrLoadDll, presumably to prevent hooks.\r\nFigure 24: Code to patch initial bytes of LdrLoadDll\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 17 of 33\n\nUnhooking API Calls\r\nAV and EDR products insert hooks into commonly used NTDLL API functions, allowing the security tool to\r\nmonitor API calls and arguments to monitor for malicious behavior. User mode hooks are generally inserted in the\r\nform of an unconditional jump, replacing the initial 0xB8 mov instruction with a jump 0xE9 to the handler.\r\nGuLoader identifies and removes these hooks by searching for byte patterns (\\xB8\\x00.{3}\\xB9) common of\r\nthose in NTDLL functions.\r\nFigure 25: Check for byte pattern indicating NTDLL call to syscall\r\nIf a hook is identified, GuLoader replaces the first 5 bytes to remove any hooks.\r\nFigure 26: Replace initial 5 bytes to original NTDLL\r\nGuLoader uses 0x33C9 , 0xC2 , and 0xE8 as anchor bytes in order to retrieve relative byte positions in order to\r\npatch.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 18 of 33\n\nFigure 27: Example of using byte anchor to retrieve relative byte positions\r\nGuLoader calls ZwProtectVirtualmemory to change the page permissions back to PAGE_EXECUTE_READ (0x20)\r\nonce it has finished replacing any hooks.\r\nFigure 28: Call ZwProtectVirtualMemory to set page permissions to PAGE_EXECUTE_READ 0x20\r\nEnumWindows\r\nGuLoader uses the Windows API EnumWindows to enumerate all top-level windows on the user’s screen to\r\nattempt to identify an analysis/sandbox environment. If the number of windows is less than 12, it calls\r\nTerminateProcess to terminate itself.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 19 of 33\n\nFigure 29: Bypassing EnumWindows check by setting number of top-level windows to 15\r\nNtSetInformationThread\r\nThe Windows API NtSetInformationThread is used to modify thread specific data for a provided thread.\r\n__kernel_entry NTSYSCALLAPI NTSTATUS NtSetInformationThread(\r\n [in] HANDLE ThreadHandle,\r\n [in] THREADINFOCLASS ThreadInformationClass,\r\n [in] PVOID ThreadInformation,\r\n [in] ULONG ThreadInformationLength\r\n);\r\nGuLoade calls NtSetInformationThread and passes 0x11 as the argument for ThreadInformationClass .\r\n0x11 corresponds to ThreadHideFromDebugger . This is a known anti-debug technique that causes the debugger\r\nto crash when a breakpoint is hit in the specified thread or when the debugger steps through instructions.\r\nFigure 30: GuLoader calls NtSetInformationThread passing ThreadHideFromDebugger\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 20 of 33\n\nEnumerate Device Drivers\r\nGuLoader uses EnumDeviceDriver and GetDeviceDriverBaseNameA from psapi.dll to enumerate system\r\ndriver names, searching for VM-related drivers. Similar to the methodology used to search for strings, GuLoader\r\nuses DJB2 to hash each driver name and compares it to a list of pre-computed hashes.\r\nFigure 31: EnumDeviceDrivers to enumerate drivers\r\nFigure 32: GetDriverBaseNameA returns vmmouse.sys\r\nEnumerate Installed Products\r\nGuLoader uses MsiGetProductInfoA and MsiEnumProductsA to enumerate installed software, hashes the name\r\nof the software, and compares them to a list of pre-computed hashes.\r\nFigure 33: GuLoader calling MsiEnumProductsA to enumerate installed software\r\nEnumerate System Services\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 21 of 33\n\nGuLoader enumerates system services using OpenSCManagerA and EnumServiceStatusA , hashes the service\r\nnames, and compares them to a list of pre-computed hashes.\r\nFigure 34: GuLoader calling OpenSCManagerA to enumerate system services\r\nNtQueryInformationProcess\r\nNtQueryInformationProcess is a Windows API that retrieves information about the specified process.\r\n__kernel_entry NTSTATUS NtQueryInformationProcess(\r\n [in] HANDLE ProcessHandle,\r\n [in] PROCESSINFOCLASS ProcessInformationClass,\r\n [out] PVOID ProcessInformation,\r\n [in] ULONG ProcessInformationLength,\r\n [out, optional] PULONG ReturnLength\r\n);\r\nAmong the process information available, the ProcessInformationClass provides the ProcessDebugPort (0x7) ,\r\nwhich provides the port number of the debugger for the process. A nonzero value indicates that the process is\r\nbeing run under the control of a ring 3 debugger. If a debugger is detected, the malware exits.\r\nFigure 35: Call to NtQueryInformationProcess querying ProcessDebugPort to identify a ring 3\r\ndebugger\r\nCPUID \u0026 RDTSC Sandwich\r\nGuLoader calls CPUID leaf 1 (eax == 1) and checks whether a hypervisor is present by checking bit 31 of\r\nregister ECX, indicating the malware is running in a virtual environment. This CPUID call is wrapped in rdtsc\r\ninstructions, which GuLoader uses to calculate the amount of time needed to execute the CPUID call. This is\r\nanother measure to detect a virtual environment, as a hypercall is required to execute the CPUID instruction\r\nwithin a virtual environment, therefore taking a longer amount of time to execute than on a virtualized system.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 22 of 33\n\nFigure 36: CPUID and RDTSC Sandwich\r\nString Decryption\r\nGuLoader decrypts all strings at runtime, making static analysis difficult. NtAllocateVirtualMemory is called to\r\nallocate a buffer to store the encrypted and decrypted data. Once the buffer is allocated, the length of the encrypted\r\nstring is written to the first word. Next, the encrypted data is written to the buffer, overwriting the length.\r\nGuLoader iterates through the ciphertext, XORing each byte by the key. The decrypted byte is then written back to\r\nthe buffer in place.\r\nFigure 37: String Decryption\r\nCode Injection\r\nGuLoader uses process hollowing in order to inject code into a suspended process, then resume execution inside\r\nthe new process. GuLoader has been observed using process hollowing injection into a number of different\r\nexecutables, as well as spawning a child process of itself to inject into. In the case of this sample, GuLoader\r\ninjected into a copy of itself using the following APIs.\r\nCreateProcessInternalW\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 23 of 33\n\nGuLoader first calls CreateProcessInternalW , passing its own path as an argument, as well as the creation flag\r\nof 0x4 (Suspended) . GuLoader uses a direct syscall rather than calling the API directly to avoid EDR/AV\r\ndetection.\r\nFigure 38: CreateProcessInternalW Suspended\r\nFigure 39: Suspended process created\r\nNtUnmapViewOfSection\r\nGuLoader uses NtUnmapViewOfSection to unmap the image at 0x400000 in the suspended process.\r\nFigure 40: NtUnmapViewOfSection unmapping the original image in the suspended process\r\nNtOpenFile\r\nGuLoader decrypts the path to C:\\Windows\\System32\\mshtml.dll and opens a file handle to it using\r\nNtOpenFile .\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 24 of 33\n\nFigure 41: NtOpenFile acquiring a file handle to C:\\Windows\\System32\\mshtml.dll\r\nNtCreateSection\r\nAfter opening a handle to mshtml.dll , GuLoader calls NtCreateSection using the file handle received from\r\nNtOpenFile in order to create a section object. A section object represents a section of memory that can be\r\nshared with other processes. A section object that is not backed by a file is suspicious, so GuLoader hardcodes a\r\nfile to create the section object to avoid potential detection.\r\nFigure 42: NtCreateSection creating a section object\r\nNtMapViewOfSection\r\nNext, GuLoader calls NtMapViewOfSection to map the mshtml.dll section that was just created using\r\nNtCreateSection into the virtual address space of the suspended process.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 25 of 33\n\nFigure 43: Mapping the section object created from mshtml.dll to memory\r\nFigure 44: Section successfully mapped to memory\r\nZwWriteVirtualMemory\r\nAfter the image is mapped in the suspended process, GuLoader writes its shellcode into the memory of the\r\nsuspended process. Note: The shellcode is not written into the mapped section. The payload will be mapped over it\r\nlater on.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 26 of 33\n\nFigure 45: GuLoader writing itself to the suspended process\r\nNtGetContextThread\r\nNext, GuLoader calls NtGetContextThread to retrieve a pointer to the Context structure of the thread in the\r\nsuspended process. This is the same context structure as discussed in the VEH section and contains processor-specific register data.\r\nFigure 46: GuLoader retrieving Context structure from suspended process\r\nZwSetContextThread\r\nGuLoader calculates an entry point for the suspended process and updates the EAX register\r\n( RtlUserThreadStart is EIP and will jump to the address in EAX). in the context structure retrieved with\r\nNtGetContextThread . If abnormal execution is detected, GuLoader will set a decoy entry point, breaking\r\nexecution in the new process.\r\nFigure 47: Accessing EIP in Context structure to update EIP\r\nOnce the entry point is calculated, GuLoader calls ZwSetContextThread to set the thread context in the\r\nsuspended process.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 27 of 33\n\nFigure 48: Setting Context structure in suspended process with updated registers/EIP\r\nNtResumeThread\r\nFinally, GuLoader calls NtResumeThread to resume execution of the suspended process, executing the injected\r\nGuLoader shellcode.\r\nFigure 49: Executing GuLoader shellcode in injected process\r\nPayload Download and Execution\r\nDecrypt C2\r\nGuLoader resumes execution in the new process, repeating all anti-analysis and anti-vm checks covered above.\r\nOnce completed, GuLoader decrypts the C2 in memory using the same decryption methodology mentioned above.\r\nFigure 50: Decrypted C2 String\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 28 of 33\n\nIf the fifth byte of the C2 is an ‘s’, GuLoader replaces the prefix to https:// . Otherwise, it replaces the prefix\r\nwith http:// .\r\nFigure 51: https:// prepended to C2\r\nDownload Payload\r\nGuLoader resolves the addresses of the following APIs InternetOpenA, InternetSetOptionA,\r\nInternetOpenUrlA, InternetReadFile, InternetCloseHandle in order to perform a GET request and download\r\nthe payload. GuLoader payloads are frequently hosted on Google Drive and other cloud storage and file-sharing\r\nsolutions. Payloads are generally long-lived as the payloads are XOR encrypted, making it difficult for providers\r\nto detect and remove them.\r\nFigure 52: Call to InternetOpenUrlA in process of downloading payload\r\nDecrypt Payload\r\nGuLoader’s decryption routine consists of three steps:\r\n1. Calculate XOR key\r\n2. Decrypt payload key\r\n3. Decrypt payload\r\nCalculate XOR Key\r\nWhen calculating the XOR key, GuLoader retrieves the first two bytes from the payload, which start at byte offset\r\n40. The two bytes are then XORed against the first two bytes of the encrypted key as well as a counter value. This\r\nprocess is repeated until the first two bytes of the payload are decrypted to 0x4D5A (MZ) . When the first two\r\nbytes of the payload are 0x4D5A , the value in the counter register is used as the decryption key for the payload\r\nkey. For this sample, the XOR key is 0x8BF7 .\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 29 of 33\n\nFigure 53: First part of routine to calculate XOR key\r\nFigure 54: XOR key loop condition checking for 4D5A, completing calculation of XOR key\r\nDecrypt Payload Key\r\nWith the XOR key now calculated, GuLoader moves to a routine to decrypt the payload key. The decryption\r\nroutine iterates through the downloaded payload 2 bytes at a time, XORing against the two byte XOR key. The\r\nlength of the payload key in this sample is 468 bytes.\r\nFigure 55: Using XOR key to decrypt 468 byte payload key\r\nFigure 56: Decrypted 468 byte payload key\r\nDecrypt Payload\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 30 of 33\n\nAfter the payload key is fully decrypted, GuLoader finally XOR decrypts the downloaded payload in place, byte-by-byte using the 468 byte payload key.\r\nFigure 57: Payload decryption routine\r\nFigure 58: Decrypted Remcos Payload\r\nExecute Payload\r\nZero Base Address 0x400000\r\nOnce the payload has been decrypted, GuLoader sets memory permissions at 0x400000 to RW and zeroes out\r\nthe image that is in place.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 31 of 33\n\nFigure 59: Zeroed out image base\r\nCopy Payload to Base Address\r\nNext, GuLoader copies the new payload to 0x400000 .\r\nFigure 60: Remcos payload written to 0x400000\r\nNtCreateSection\r\nGuLoader then calls NtCreateSection to create a section object so that it can map the image to memory.\r\nNtMapViewOfSection\r\nAfter the section is created, GuLoader maps the section into memory, then calls ZwProtectVirtualMemory to set\r\nmemory protection appropriately.\r\nFigure 61: Payload mapped to memory with permissions set\r\nNtCreateThreadEx\r\nFinally, GuLoader calls NtCreateThreadEx to execute the image that is now mapped at 0x400000 , executing the\r\npayload.\r\nBonus: Remcos Configuration Extraction\r\nThe payload downloaded by this sample of GuLoader is Remcos. Remcos is a commercial Remote Access Tool\r\nadvertised as legitimate software for surveillance and penetration testing, though it is frequently used in malware\r\ncampaigns.\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 32 of 33\n\nI previously wrote a Remcos configuration extractor for another project and it looks like the configuration storage\r\nhas not changed. The configuration extractor can be found on my GitHub.\r\n❯ python3 extract_config.py remcos_payload.bin\r\nRemcos Config Extractor - CRITICAL Extracting config from: remcos_payload.bin\r\nMalware Family: Remcos\r\nBotnet: RemoteHost\r\nC2s: ['194.59.218[.]165:2408']\r\nSource: https://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nhttps://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/\r\nPage 33 of 33",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://malwarebookreports.com/guloader-navigating-a-maze-of-intricacy/"
	],
	"report_names": [
		"guloader-navigating-a-maze-of-intricacy"
	],
	"threat_actors": [],
	"ts_created_at": 1775434303,
	"ts_updated_at": 1775791296,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/4786302866c25e864d77bb221ad9d5076ab17818.pdf",
		"text": "https://archive.orkl.eu/4786302866c25e864d77bb221ad9d5076ab17818.txt",
		"img": "https://archive.orkl.eu/4786302866c25e864d77bb221ad9d5076ab17818.jpg"
	}
}