{
	"id": "fcab77a0-8374-47ee-b91b-442d896953af",
	"created_at": "2026-04-06T00:18:01.320375Z",
	"updated_at": "2026-04-10T03:21:14.908041Z",
	"deleted_at": null,
	"sha1_hash": "f127787288227bb7fb1edd458fca48212600c557",
	"title": "Carving the IcedId - Part 3",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 2795456,
	"plain_text": "Carving the IcedId - Part 3\r\nBy techevo\r\nPublished: 2024-03-17 · Archived: 2026-04-05 16:47:47 UTC\r\nWelcome back to this series, analysing IcedId malware artefacts.\r\nThis is part 3 in the series, you can check out part 1 and part 2 to follow along from the beginning.\r\nThis post will focus on analysing a DLL file that was downloaded using a PowerShell script analysed in\r\npreviously in part 2.\r\nThe data for this case was published by @malware_traffic over at Malware Traffic Analysis1. You can download\r\nall the samples from this case from here.\r\nThis analysis has really stretched my learning regarding unpacking, it has by far been the most challenging and\r\nrewarding sample I’ve come across to date. If there are any errors that you spot, I’d really welcome the feedback\r\nto understand better how this sample works.\r\nIn order to make this walk through as accessible as possible, I will once again be storing artefacts and output in a\r\nGitHub repository here.\r\nThe GitHub repository contains the extracted shellcode as seen in the various commands for your own\r\nexperimentation, as well as the final payload.\r\nTL;DR\r\nThis post is fairly detailed and as a result quite long. A quick overview of how the sample executes is listed below\r\nto provide some quick insight. If you want a more guided tour of the execution and other interesting observations,\r\nskip this section.\r\n1. rundll32.exe executes a export on the dll.\r\n2. The DLL routine allocates some memory and copies and unpacks data into shellcode from the .reloc\r\nsection of the DLL.\r\n3. The unpacking consists of a 4 byte XOR as well as the supplied string on the command line, for various\r\nstages.\r\n4. The unpacked shellcode is patched with function addresses and creates some syscall stubs to avoid\r\nntdll.dll hooks.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 1 of 30\n\n5. The rundll32.exe process opens svchost.exe and injects a payload using shared mapped views of\r\nsections and NtQueueUserThread\r\n6. The svchost.exe process further unpacks a PE file which is then injected into memory at a fixed\r\nlocation.\r\n7. The injected payload is then executed.\r\n8. The final payload can be downloaded from the Bazaar or GitHub\r\nIn the previous post, a PowerShell script was used to download a DLL named r.dll from a compromised\r\nWordPress instance.\r\nPart of the script appended varying amounts of bytes to the file, ensuring the cryptographic hash changes with\r\neach download. You can find a copy of the DLL file on the Malware Bazaar, here The SHA1 hash for the copy we\r\nwill be looking at in this post is: 1c6e76af95f2a17b8e518965d62b3c9d7ecba6d5\r\nFor this explanation of the malware delivery, both static and dynamic analysis will be used in conjunction.\r\nFor static analysis I am using radare22 and for dynamic analysis x64dbg3 both are freely available.\r\nBinary File Triage\r\nFrom the Powershell script we know there must be an export named vcab , we can use a radare2 one-liner to\r\nshow the various exports.\r\n[Exports]\r\nnth paddr vaddr bind type size lib name demangled\r\n――――――――――――――――――――――――――――――――――――――――――――――――――――――――\r\n1 0x00000420 0x814e361020 GLOBAL FUNC 0 msys-edit-0.dll t_gcc_deregister_frame\r\n2 0x00000400 0x814e361000 GLOBAL FUNC 0 msys-edit-0.dll t_gcc_register_frame\r\n3 0x000151e0 0x814e375de0 GLOBAL FUNC 0 msys-edit-0.dll tel_fn_complete\r\n4 0x000192c0 0x814e379ec0 GLOBAL FUNC 0 msys-edit-0.dll trl_abort_internal\r\n5 0x00026338 0x814e38a138 GLOBAL FUNC 0 msys-edit-0.dll trl_print_completions_horizontally\r\n6 0x000192f0 0x814e379ef0 GLOBAL FUNC 0 msys-edit-0.dll trl_qsort_string_compare\r\n7 0x00016bf0 0x814e3777f0 GLOBAL FUNC 0 msys-edit-0.dll tdd_history\r\n8 0x000169a0 0x814e3775a0 GLOBAL FUNC 0 msys-edit-0.dll tppend_history\r\n9 0x00000880 0x814e361480 GLOBAL FUNC 0 msys-edit-0.dll t__next_word\r\n10 0x00000800 0x814e361400 GLOBAL FUNC 0 msys-edit-0.dll t__prev_word\r\n[ TRUNCATED ]\r\n152 0x000177a0 0x814e3783a0 GLOBAL FUNC 0 msys-edit-0.dll tistory_expand\r\n[ TRUNCATED ]\r\n430 0x00016fb0 0x814e377bb0 GLOBAL FUNC 0 msys-edit-0.dll there_history\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 2 of 30\n\n431 0x000177a0 0x814e3783a0 GLOBAL FUNC 0 msys-edit-0.dll vcab\r\nThe above output is truncated, however you can see there are 431 exports on this DLL. The final export listed is\r\nthe vcab export we already know about. You can find a full output of the command in the GitHub repository for\r\nthis blog posts, here.\r\nAs well as the export names, the virtual addresses are also quite interesting. Looking at the export\r\ntistory_expand , ordinal 152 , we can see it has the same virtual address as the vcab export.\r\nGiven the large amount of exports I believe this is likely a legitimate DLL file that has been modified with some\r\nadditional functionality. Searching for the DLL name msys-edit-0.dll also shows this is possibly related to the\r\nmsys2 project.\r\nSince we’ve looked at Exports, lets look at Imports, using the following command.\r\n[Imports]\r\nnth vaddr bind type lib name\r\n――――――――――――――――――――――――――――――――――――――――――――\r\n1 0x814e391860 NONE FUNC KERNEL32.dll GetModuleHandleA\r\nOne import is not a lot to go off for understanding the functionality. The lack of imports is also quite suspicious,\r\nand something that indicates this DLL should be investigated further.\r\nStatically analysing the DLL functions proved a little harder than expected. Forcing Ghidra to decompile the\r\nbytes was possible, but readability was not amazing.\r\nTo explore this sample further, I will be combining both static and dynamic analysis techniques.\r\nDebugger Setup\r\nFor the dynamic analysis parts of this you will require some working knowledge of x64dbg. Primarily around\r\nsetting breakpoints, although the commands are provided, just knowing what a breakpoint is and how to set it\r\nshould be enough. If something isn’t clear feel free to reach out and ask!\r\nAs well as the vcab entry point being supplied on the command line, a flag /k and string parameter were also\r\nprovided as shown below.\r\nrundll32 r.dll, vcab /k chokopai723\r\nTo look into the execution of the DLL I’ll be using x64dbg. It is possible to use the x64dbg DLL host binary,\r\nhowever for this analysis, debugging will be done with rundll32.exe executable in order to mimic the execution\r\nenvironment precisely.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 3 of 30\n\nOnce you have opened the binary C:\\Windows\\System32\\rundll32.exe with x64dbg change the command line to\r\ninclude the additional parameters as shown in Figure 1.\r\nFigure 1: x64dbg - Additional command line parameters.\r\nI find it helpful when analysing a new sample to setup breakpoints on DLL loads, which helpfully is a built in\r\nfeature.\r\nNavigating to Options and then Preferences you can enable the settings User DLL Load and System DLL Load .\r\nExecute until the r.dll is loaded and then issuing the following command in will set a breakpoint on the vcab\r\nentry point.\r\nbp r.vcab\r\nWe should also set some breakpoints for interesting API calls before starting, using the following commands.\r\nThese API’s specifically have been selected because VirtualAlloc is common in packed samples to aid in\r\nunpacking, and since the number of Imports was limited to a single Kernel32.dll library, there is a chance the\r\nsample will attempt to load more modules manually.\r\nbp VirtualAlloc\r\nbp LoadLibraryA\r\nCommand Line Validity Check\r\nThe first routine to highlight during this walk through is a check that the /k was supplied on the command line.\r\nSetting a breakpoint at 0x814e378887 and viewing the sample statically we can see the ASCII characters 0x6B\r\nand 0x2F being moved into a memory region, as shown in Figure 2.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 4 of 30\n\nFigure 2: radare2 - r.dll command line check routine.\r\nAn instruction at 0x0814E378AAB then copies these two bytes into the RDX register. The command line string is\r\nthen iterated over scanning for the /k flag being present. If its not then the execution flow exits.\r\nMemory Copy Routine\r\nThe next routine of interest is located at virtual address 0x0814E378B26 .\r\nThis routine is used throughout this portion of the loader to essentially move bytes from one location to another,\r\nmuch like the memcpy\r\n4\r\n function.\r\nThe function prototype for memcpy is shown below, and this is also used by the routine within the sample.\r\nIn x86_64 assembly the registers RCX , RDX and R8 are used to store the destination , source and count (size)\r\nparameters.\r\nvoid *memcpy(\r\n void *dest,\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 5 of 30\n\nconst void *src,\r\n size_t count\r\n);\r\nAlthough the function is located at 0x0814E378B26 , the primary loop that moves data between source and\r\ndestination can be seen at 0x814E378B71 . The disassembly for this routine is shown in Figure 3 below. The\r\nregister RDX is used as an index to then increment as it loops through the bytes being copied.\r\nFigure 3: radare2 - IcedId memcpy shellcode routine.\r\nSetting a breakpoint at 0x0814E378B26 will allow us to inspect the various bytes being moved around.\r\nbp 0x0814E378B26\r\nIf we allow execution until the memory copy routine breakpoint, we first see a call to copy the string\r\nchokopai723 from one area on the stack to another stack based memory location.\r\nFigure 4 shows the source address 0x0F340F0F44A , destination 0x0F340F0F5B0 and the number of bytes 0xB\r\nFigure 4: x64dbg - Memory copy routine register usage\r\nAllowing the execution to proceed, the debugger will break at a call to VirtualAlloc\r\n5\r\n. If we examine the\r\nsupplied parameters we can mock-up a call to VirtualAlloc with the following values.\r\nVirtualAlloc(NULL, 0xE27, 0x3000, 0x4);\r\nConverting some of the inputs to their constants5 \r\n6\r\n makes it a little easier to understand what is happening.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 6 of 30\n\nVirtualAlloc(NULL, 0xE27, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);\r\nHere we can see at least 0xE27 (3623) bytes of memory is being requested, to be committed and reserved, with\r\nthe page protection of Read and Write.\r\nThe value returned in the EAX register is going to be one to keep an eye on. This value is the address of an\r\nallocated region of memory. As this value changes from execution to execution I will refer to this as “memory\r\nregion 1” throughout this post.\r\nThis allocated region of memory is then populated using the malware’s implementation of memcpy already\r\ncovered ( 0x0814E378B26 ). The routine is called a total of 3 times, the total number of bytes copied matches the\r\nrequested region size of 0xE27 (3623) bytes.\r\nEach time, the source of the data is located in the .reloc section of the DLL.\r\nThe table below describes the source virtual address, the file physical offset, and number of bytes copied.\r\nSource Virtual Address File Offset Byte Count\r\n0x0814E3949E5 0x2B9E5 0x4A (74)\r\n0x0814E394A2F 0x2BA2F 0x18F (399)\r\n0x0814E394BBE 0x2BBBE 0xC4E (3150)\r\nTable 1: Virtual Address and file offset mappings\r\nThe file offset can be calculated using the source address seen in the debugger, minus the virtual address of the\r\nsection ( .reloc ). Then identifying the physical address of the section within the PE file using the headers, and\r\nadding the difference back.\r\nUsing x64dbg’s memory map tab you can save this memory region to a file, you can find a copy of the file\r\nrundll32_memory_region_1.bin in the Github repository here.\r\nEither using the offsets identified or by dumping the memory region, we can examine the data copied in more\r\ndetail. Data mysteriously copied into un-backed memory region has potential to be shellcode.\r\nWe can test this theory by attempting to disassemble the bytes in using this radare2 one-liner.\r\nFigure 5 shows the interpretation of the bytes as assembly. It appears to be junk as there is no obvious flow of\r\nexecution present.\r\n$ r2 -AA -c 'pd' rundll32_memory_region_1.bin\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 7 of 30\n\nFigure 5: radare2 - Disassembly view of allocated memory region #1\r\nIt’s a good idea at this point to set an Access breakpoint on the memory region to see if there are any routines that\r\nmay transform it in some way.\r\nExecuting the process again will break when the process attempts to access an address within the allocated region\r\nof memory.\r\nThe cause of this is an XOR operation at 0x0814E3784E8 as shown in Figure 6.\r\nFigure 6: x64dbg - XOR operation memory region #1\r\nThe screenshot in Figure 6 above and in Figure 7 below show this XOR taking place both from a dynamic and\r\nstatic perspective.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 8 of 30\n\nFigure 7: radare2 - XOR operation memory region #1\r\nThe AL register in this case is the lower 8 bytes of the EAX register.\r\nThe register pane on the right in Figure 7 shows this to contain the value 0xD6 .\r\nThe address the operation is being carried out on in this case is shows as ds:[rcx-1] which if we take a look at\r\nthe value in the RCX register should contain the address of the second byte within memory region 1, the -1\r\nthem refers to the first byte of our mystery data.\r\nIf we step through the next few operations hitting the XOR instruction we eventually see the same 4 bytes rotating\r\nthrough the AL register: 0xD6B20700\r\nThis raises an interesting question, where are these bytes coming from and can locate them within the DLL file?\r\nWe know from observing the routine, that the bytes used for the XOR key is being set in the EAX ( AL ) register.\r\nWithin the screen shot shown in Figure 7 you may notice the operation at 0x0814E3784F3 , also shown below.\r\nmovzx eax,byte ptr ds:[rax+rdi+2C]\r\nThis is the operation setting the value of the EAX / AL register prior to the XOR operation. If we follow the\r\naddress calculated at RAX + RDI + 2C in a dump we can see the 4 bytes at the address 0x0814E378BD4 or file\r\noffset 0x17FD4 , as shown in Figure 8.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 9 of 30\n\nFigure 8: hxd - hexadecimal dump of potential configuration block\r\nShown in the GREEN box, is the XOR key. Also within short proximity, shown in BLUE there are the sizes (in\r\nlittle endian7) of the data transferred into the first allocated memory region.\r\nLastly within the RED box, there is a NULL terminated string of init . This could be a useful marker for what\r\nmight turn out to be some kind of stored configuration.\r\nIf we allow the XOR routine to complete its rounds across the data, and repeat the steps from earlier to dump, and\r\nthen attempt to show the disassembly it now prints some pretty convincing shellcode.\r\nThe file rundll32_memory_region_1_xor.bin can also be found in the GitHub repository here\r\n$ r2 -AA -c 'pd' rundll32_memory_region_1_xor.bin\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 10 of 30\n\nFigure 9: radare2 - Shell code disassembly\r\nWe can validate that the XOR key is correct by applying it to the memory dump file we created previously and\r\ncomparing the output. Figure 10 shows the recipe required. You will notice the hexadecimal output matches the\r\ninstruction bytes in the disassembly above, in Figure 9.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 11 of 30\n\nFigure 10: CyberChef - XOR routine.\r\nIf we remember the call to VirtualAlloc previously, the region was requested with PAGE_READWRITE protection,\r\nrestricting the ability for execution. There are two possibilities for the shellcode now, the first is it will be executed\r\nin its current location or it will be copied somewhere else before executing.\r\nWherever the shellcode will be executed, the memory region will need its execute permission set. Just as\r\nVirtualAlloc was used to allocate the region, we can set a break point on VirtualProtect as shown below.\r\nSacrificial DLL Loading\r\nPressing on with the unpacking, there is a call to LoadLibraryA with the parameter to load the DLL dpx.dll\r\nfrom the default C:\\Windows\\System32 directory.\r\nLoading the dpx.dll library is followed by locating an exported function named dpx.DpxCheckJobExists .\r\nBased on my loose understanding of how the function is located, I believe this is chosen simply because it is the\r\nfirst function listed in the exports. This technique would allow the malware authors to potentially swap the\r\ndpx.dll for another fairly easily…\r\nThe address returned from for dpx.DpxCheckJobExists is then passed to VirtualProtect\r\n8\r\n, executed via a call\r\nr15 instruction at 0x0814E3786BE .\r\nThe arguments passed to VirtualProtect can be arranged as shown.\r\nThis function call will mark 0x15BB (5563) bytes as PAGE_READWRITE starting at the address of\r\ndpx.DpxCheckJobExists .\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 12 of 30\n\nVirtualProtect(dpx.CheckJobExists, 0x15BB, 0x4)\r\nThe original protection was PAGE_EXECUTE_READ , so the additional permission to allow writing is enough to know\r\nwe likely want to keep an eye on this region.\r\nMoving on, we hit a familiar breakpoint for the malware’s memcpy routine. This time, 0x15BB bytes are being\r\nmoved from the address 0x0814E39342A once again located in the .reloc section, to the address of\r\ndpx.DpxCheckJobExists . The file offset for this data is 0x2A42A .\r\nRather interestingly the bytes representing the amount of data transferred 0x15BB are located in the output of\r\nFigure 8 underneath the 0x4A byte.\r\nExtracting the 0x15BB bytes from the newly copied location, we can take a look and see what the original code\r\nfor dpx.DpxCheckJobExists has been replaced with.\r\n$ r2 -AA -c 'pd' rundll32_dpx_checkjobexists.bin\r\nFigure 11: radare2 - Dpx.CheckJobExists overwritten data\r\nIt doesn’t look shellcode, so likelihood is there will be an additional routine to de-obfuscate it.\r\nThrough setting some access breakpoints you will stumble elegantly upon yet another routine with an XOR\r\ninstruction located at 0x0814E3786E1 . This routine iterates over the dpx.DpxCheckJobExists location using the\r\nstring chokopai723 as a key for all 0x15BB bytes.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 13 of 30\n\nThe string chokopai732 was passed into the process via the command line flag /k .\r\nIf we take a look at the dpx.DpxCheckJobExists contents shown in Figure 12, once the XOR has been applied\r\nwe get something more resembling shellcode.\r\n$ r2 -AA -c 'pd' rundll32_dpx_checkjobexists_xor.bin\r\nFigure 12: radare2 - Dpx.DpxCheckJobExists shellcode\r\nThe sample then makes another call to VirtualProtect , restoring the page protection on\r\ndpx.DpxCheckJobExists back to PAGE_EXECUTE_READ .\r\nNow the code is executable again, the sample executes the newly laid out shellcode by call rsi operation at\r\n0x0814E378421 . This can be intercepted by setting a breakpoint on the dpx.DpxCheckJobExists symbol.\r\nExecuting the shellcode located at dpx.DpxCheckJobExists , it uses an internal routine labelled below as\r\nmw_resolve_api_hash_location to locate the procedure addresses for 3 API’s. The use of API hashes to resolve\r\nroutines is quite common in malware, as it makes it much harder to see what is being used.\r\nThe hash values are usually fairly static, although there a few different methods employed, “search engine-ing”\r\nthe hexadecimal values is the first step.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 14 of 30\n\nSpecial thanks to this GitHub project by hidd3ncod3s for supplying the hashes and corresponding API routines.\r\nFrom the following disassembly we can see 3 values being moved into ECX before the function\r\nmw_resolve_api_hash_location is used. The labels in the disassembly, show the methods being passed:\r\nNtCreateThreadEx ( 0x9a3c803e )\r\nRtlAllocateHeap ( 0x67cc0818 )\r\nRtlFreeHeap ( 0xd45a1e1f )\r\nFigure 13: radere2 - API hashes being resolved.\r\nOnce the API’s have been resolved, the routine RtlAllocateheap\r\n9\r\n is called using the call rbx instruction, and\r\n0x335B (13147) bytes are requested.\r\nFigure 14: x64dbg - RtlAllocate 0x335b Bytes\r\nOnce the region is allocated, the shellcode then accesses its own processes Process Envonrment Block aka the\r\nPEB, to retrieve the full command line given.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 15 of 30\n\nFigure 15: x64dbg - Command line copied from Process Environment Block\r\nProbably not surprisingly, this second shellcode also implements a memcpy routine, as shown in Figure 16.\r\nIt is first used to copy 0x1EAD (7853) bytes from 0x0814E39580C (file offset 0x2C80C within the .reloc\r\nsection) to a heap allocated region. Figure 8 above contains the value 0x1EAD within the configuration block at\r\noffset 0x17FD0 .\r\nFor future reference, the screen shot below shows the destination address in the RCX register as\r\n0x023D5D94A0B0 .\r\nFigure 16: radare2 - DPX.dll shellcode memory routine.\r\nExtracting the data that was just copied reveals not too much, and you might be able to spot a familiar pattern\r\noccurring.\r\nShellcode Patching\r\nMoving on to the next call of the memcpy routine, the sample copies 0xC4E (3150) bytes from the very first\r\nallocated memory region to the tail of the data written into the heap region previously described.\r\nThis second chunk of data being copied was originally transferred from 0x0814E394BBE (file offset 0x2BBBE )\r\ninto memory region 1, where is was then de-obfuscated.\r\nThe data copied into this heap region becomes very relevant later on. At this stage there is some missing\r\ninformation so don’t dump the memory region just yet. To clarify, the first chunk is obfuscated in some way, the\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 16 of 30\n\nsecond chunk is valid shellcode.\r\nThe next call the memcpy routine is used to copy a more 4 bytes containing the value 0x5B330000 into a location\r\nwithin the first allocated memory region. If we swap the endianness of 0x5B330000 we get 0x335B , matching\r\nthe size of a previously copied segment of shellcode… very interesting…\r\nNext, the shellcode’s routine for locating a procedure based on its hash is used to locate CreateThread . This\r\nlocation is then used to patch the shellcode that was written into the first region of allocated memory, using the\r\nmemcpy routine.\r\nFigure 17 shows the start of the memcpy routine with the shellcode to be patched in the lower pane. Currently, the\r\n8 bytes to be patched contains 0xA1A2A3A4A5\r\nFigure 17: x64dbg - Shell code patching routine, before patch.\r\nFigure 18 shows the shellcode after being patched, containing the address of CreateThread ready for it to be\r\ncopied into RAX and then called.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 17 of 30\n\nFigure 18: x64dbg - Shell code patching routine, after patch.\r\nThe same process of locating a function, and then patching shellcode is also carried out for additional functions.\r\nThe complete list of functions resolved and patched is:\r\nCreateThread\r\nLoadLibraryA\r\nReadProcessMemory\r\nVirtualProtect\r\nRtlAllocateHeap\r\nNtClose\r\nZwCreateThreadEx\r\nNext comes a routine that appears (at least to me), to parse the ntdll.dll module for the various syscall\r\noperations.\r\nContinuing the execution again we hit another call to the memcpy routine, this time copying 0xB (11) bytes from\r\na stack based address into a location within the first allocated memory region.\r\n4C 8B D1 B8 00 00 00 00 0F 05 C3\r\nAt first glance the purpose of the byte sequence is not obvious, it’s certainly not an address as previously\r\nobserved. If you continue to view the disassembler during the memcpy routine, you would have seen a patch\r\napplied to call a syscall directly.\r\nWe can quickly check the above hexadecimal opcodes using the CyberChef10 recipe to Disasemble X86 or use\r\nthe following rasm2 command.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 18 of 30\n\n$ rasm2 -a x86 -b 64 -d '4C 8B D1 B8 00 00 00 00 0F 05 C3'\r\nmov r10, rcx\r\nmov eax, 0\r\nsyscall\r\nret\r\nThis syscall related activity has a lot of similarities with what is described here over at www.ired.team\r\nOnce the syscalls stubs have been copied over, the function ZwAllocateVirtualMemory , is then used to request\r\n0x3841 (14401) bytes of memory with the protection constant PAGE_WRITECOPY , this region will be labelled and\r\nhence forth known as memory region 2.\r\nFigure 19 shows the call to ZwAllocateVirtualMemory being made. The registers RDX and R8 are being used\r\nto provide the address and protection flags. As can be seen in the display, RCX contains the location of memory,\r\nwhich contains the location in memory that is being altered….aka a pointer.\r\nThe address being altered here is stored in little-endian, and is 0x29E3E670000 as shown in the lower dump 2\r\npane.\r\nFigure 19: x64dbg - ZwProtectVirtualMemory from R13 register\r\nAfter building the syscall routines and patching the shellcode in memory region 1, more API’s are resolved.\r\nNtOpenProcess\r\nNtClose\r\nRtlFreeHeap\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 19 of 30\n\nThe malware went to a lot of trouble to generate the syscall stubs, it finally begins to use them starting with a call\r\nvia the RSI register.\r\nSetting an execution breakpoint on the region of memory containing the syscall stubs will allow you to step\r\nthrough the next procedure.\r\nFigure 20 shows the call via the RSI register, with a value of 0x5 being passed in on the RCX register. In the\r\ndisassembly view in the bottom pane, you can see the syscall ID being loaded into RAX , the value 0x36\r\nresolves to NtQuerySystemInformation\r\n11\r\nTaking a look at the documentation for NtQuerySystemInformation here provided by Geoff Chappell, the value\r\n0x5 is the constant for SystemProcessInformation . This is being used to generate a process listings, more\r\ndetails can be found here\r\nFigure 20: x64dbg - NtQuerySystemInformation native syscall\r\nOnce the PID for explorer.exe is located, it is passed to the NtOpenProcess syscall. Opening the\r\nrundll32.exe process in ProcessHacker we can see the handle to explorer.exe has been opened, as shown in\r\nFigure 21.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 20 of 30\n\nFigure 21: ProcessHacker - Handle to explorer process opened.\r\nThe handle on explorer.exe is then used by a call to NtOpenProcessToken . The returned handle for the token is\r\npassed to NtQueryInformationToken before being closed with NtClose .\r\nThe syscall NtSystemQueryInformation is then used as it was previously to generate a list of processes running\r\non the system.\r\nA series of calls to NtOpenProcess is then issued against all svchost.exe processes until one can be\r\nsuccessfully opened. As the process is running in a non-privileged context, calls to svchost.exe processes\r\nrunning as NT AUTHORITY\\SYSTEM are responded to with an access denied value in EAX as shown in Figure 22\r\nFigure 22: x64dbg - NtOpenProcess Access Denied.\r\nNote: The sihost.exe process is also attempted if the svchost.exe process list becomes exhausted.\r\nOnce a handle to an svchost.exe process is opened, the token information is harvested using\r\nNtOpenProcessToken and NtQueryInformationToken .\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 21 of 30\n\nTo determine if the target svchost.exe process is the correct architecture, NtQueryInformationProcess is used\r\nto check the ProcessWow64Information details.\r\nFor each thread on the svchost.exe process the following routines are called:\r\nNtOpenThread\r\nNtCreateEvent\r\nNtDuplicateObject\r\nNtQueueApcThread\r\nSetEvent\r\nOnce each thread has been setup, there is a call to NtQuerySystemTime .\r\nThe shellcode residing in memory region 1, is further patched with the value 0xB18 forming the first argument to\r\nReadProcessMemory as shown in Figure 23.\r\nFigure 23: x64dbg - Length value being patched in shellcode\r\nUsing the handle to svchost.exe , the rundll32.exe process makes a call to NtVirtualProtect targeting the\r\naddress of WinHelpW from user32.dll .\r\nLooking at the R9 register in Figure 24 you can see the value 0x40 , which corresponds to the memory\r\nprotection constant PAGE_EXECUTE_READWRITE .\r\nFigure 24: x64dbg - NtVirtualProtect WinHelpW\r\nPayload Transfer\r\nThe rundll32.exe process then calls NtCreateSection to create a section within the svchost.exe process.\r\nThis section is then mapped into view of the rundll32.exe process using NtMapViewOfSection .\r\nWith the section accessible to the rundll32.exe process, the memcpy implementation is called twice. The first\r\ntransfer copies 0x4A bytes, and the second transfers 0x18F bytes from the first memory region.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 22 of 30\n\nYou’ll notice the byte sizes align with the blocks of data transferred from the .reloc section into “memory\r\nregion 1”, which has been decoded and subsequently patched.\r\nThe original bytes from both WinHelpW (0x4A) and WinHelpA (0x18F) are copied into a location of memory,\r\npossibly for restoring later.\r\nOnce data has been written by the rundll32.exe process, NtUnMapviewofSection is called on the section.\r\nUsing the handle to the svchost.exe process, the section is mapped into memory using NtMapViewOfSection .\r\nNow comes a really interesting process, to avoid using heavily monitored API’s the rundll32.exe process such\r\nas WriteProcessMemory .\r\nThe rundll32.exe processes calls the NtQueueApcThread routine to schedule an execution of RtlCopyMemory\r\nwithin the svchost.exe process. The source parameter is the location of the mapped memory region of the\r\nshared section, the destination parameter contains the address of the WinHelpW routine within user32.dll .\r\nThus when the queued APC routine executes, the WinHelpW routine will be replaced with shellcode.\r\nThe setup for this can be seen in Figure 25 below.\r\nFigure 25: x64dbg - WinHelpW execution after NtDelayExecution\r\nThe same technique is then used to copy data from the mapped section, to overwrite the WinHelpA routine. The\r\nshellcode at WinHelpW is then scheduled to execute using the NtQueueApcThread routine as well as Sleep and\r\na call to NtDelayExecution .\r\nBoth the WinHelpW and WinHelpA locations have their memory protection restored back to PAGE_EXECUTE_READ\r\nusing NtVirtualProtectMemory , and the section becomes unmapped in the svchost.exe process with a call to\r\nNtUnMapviewofSection .\r\nExecution from this point will continue from within the perspective of the svchost.exe process.\r\nSetting a breakpoint on the WinHelpW routine, we can examine this further.\r\nExecuting WinHelpW Shellcode\r\n$ r2 -AA -c 'pdf' svchost_user32_injected.bin\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 23 of 30\n\nFigure 26: radare2 - svchost.exe User32.dll WinHelpW Shellcode\r\nCalls to OpenProcess on the rundll32.exe process. Then ReadProcessMemory from the rundll32.exe\r\nprocess, the heap allocated data previously described.\r\nFigure 27: x64dbg - ReadProcessMemory called from svchost.exe\r\nAs you can see from the screen shot in Figure 28, some of the data copied may contain a similar configuration\r\nblock identified with the init keyword. Further down into the bytes you may also spot the bytes 0xD6 , 0xB2 ,\r\n0x07 and 0x00 which was the XOR key used within the rundll32.exe unpacking staged.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 24 of 30\n\nFigure 28: x64dbg - svchost.exe init configuration block\r\nTaking a look at the shellcode that was placed at WinHelpA statically in Figure 29, we can see it contains the\r\nstring dpx.dll and will call LoadLibraryA to load it.\r\nIt then calls VirtualProtect on the routine DpxCheckJobExists to allow a byte copying routine to overwrite its\r\ncontents, replicating the behaviour from earlier in the unpacking routine.\r\n$ r2 -AA -c 's 0xe2; pd 40' svchost_user32_injected.bin\r\nFigure 29: radare2 - LoadLibraryA dpx.dll and overwrite DpxCheckJobExists\r\nIf you are viewing this dynamically then, you will observe 0xC4E (3150) bytes from the second chunk of data\r\ncopied from the rundll32.exe process into dpx.DpxCheckJobExists routine.\r\nA call to CreateThread is then issued with a base address of dpx.DpxCheckJobExists\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 25 of 30\n\nThe shellcode located at dpx.DpxCheckJobExists then kicks of a routine to XOR decode some of the remaining\r\ndata originally sourced from rundll32.exe .\r\nPayload Decrypting\r\nIn Figure 30 below we can see the static disassembly output of the XOR routine used.\r\n$ r2 -AA -c 's 0x57; pd 72' svchost_dpx_dpxcheckjobexists.bin\r\nFigure 30: radare2 - XOR Routine\r\nThis routine is used to reveal the FINAL PE file payload in its original memory buffer copied over from\r\nrundll32.exe , as shown in Figure 31 there is an MZ header and DOS stub visible.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 26 of 30\n\nFigure 31: x64dbg - Decoded DOS stub header\r\nAs well as the executable file, there also resides some configuration data that is used to allow shellcode to map the\r\nPE into the address space.\r\nValue 0x3400 taken from payload structure and passed to RtlAllocateHeap The PE file is the seemingly copied\r\ninto this allocated memory region.\r\nFigure 32: x64dbg - MZ header being copied into allocated Heap region\r\nPausing the debugger here, will allow you to extract the executable file before it gets mapped into memory.\r\nAs the shellcode within the dpx.DpxCheckJobExists area executes, it calls VirtualAlloc with a base region of\r\n0x0180000000 , a size of 0x3000 (12288) bytes and a page protection flag of 0x40\r\n( PAGE_EXECUTE_READWRITE ).\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 27 of 30\n\nFigure 33: x64dbg - VirtualAlloc hardcoded 0x0180000000\r\nOnce this very specific location of memory is allocated the PE file is mapped into execute, the process for this is\r\nwell documented elsewhere.\r\nOnce mapped, execution is started using a call to CreateThread using the 0x01800028D4 address as the entry\r\npoint.\r\nFigure 34: x64dbg - CreateThread hardcoded 0x0180000000\r\nUnpacked Payload\r\nNow we have jumped through the many hoops to unpack the final payload, we can validate the contents by\r\nloading it into PE-Bear12.\r\nAs you can see from Figure 35, the binary lists some imports from the WINHTTP.dll that look like might be\r\nworthy some additional analysis.\r\nYou can find a copy of the file svchost_icedid_unpacked.bin in the GitHub repository for this blog post here, or\r\non the malware Bazaar here.\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 28 of 30\n\nFigure 35: PE Bear - Unpacked icedid payload from svchost.exe\r\nFinal Words\r\nThat’s it for this blog post, its been quite in depth and low-level. If you want to understand anything covered, or\r\nmaybe not covered in this post feel free to reach out.\r\nI’m planning to do a part 4 taking a look into the extracted PE file so keep an eye out for that, and in the meantime\r\nkeep evolving.\r\n@techevo_\r\nReferences\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 29 of 30\n\nSource: https://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nhttps://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html\r\nPage 30 of 30",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://blog.techevo.uk/analysis/binary/2024/03/17/carving-the-icedid-part-3.html"
	],
	"report_names": [
		"carving-the-icedid-part-3.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434681,
	"ts_updated_at": 1775791274,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f127787288227bb7fb1edd458fca48212600c557.pdf",
		"text": "https://archive.orkl.eu/f127787288227bb7fb1edd458fca48212600c557.txt",
		"img": "https://archive.orkl.eu/f127787288227bb7fb1edd458fca48212600c557.jpg"
	}
}