{
	"id": "85e41956-07f7-4c46-94a2-0642d115d0af",
	"created_at": "2026-04-06T00:06:25.266448Z",
	"updated_at": "2026-04-10T13:12:45.958848Z",
	"deleted_at": null,
	"sha1_hash": "5e4a2f41ff6b174d9375e1f9138ade339863421c",
	"title": "StealC Malware Analysis Part 1",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 2151734,
	"plain_text": "StealC Malware Analysis Part 1\r\nArchived: 2026-04-05 21:07:00 UTC\r\n01 - Introduction\r\nThis series of blog posts is aimed at a technical audience interested in reverse engineering and, more specifically,\r\nmalware analysis.\r\nIn this article, we'll take a look at the analysis of a malicious sample for Windows from the StealC family, from\r\nthe packed sample to the recovery of C2.\r\nWe'll automate our analysis steps with a view to integrating them into an automated pipeline for extracting\r\nindicators of compromise.\r\nIn the second article we'll retrieve C2 from the loader, get the third stage sample and unpack it.\r\nThe third article will focus on the last stage (StealC malware) and C2 recovery using static analysis.\r\nRequirements\r\nThe prerequisites for this articles are :\r\nKnowledge of reverse engineering on malicious programs\r\nBasic knowledge of x86 assembler\r\nKnowledge of C/Cpp is strongly recommended\r\nKnowledge of Python is required for automation\r\nKnowledge of a disassembler (Binary Ninja, IDA, Ghidra)\r\nExperience of opening malware in a disassembler\r\nExperience with a debugger (x64dbg/WinDBG...)\r\nMotivation and a few liters of coffee\r\n02 - Reminders and preparation of the analysis environment\r\nSome definitions\r\nIn order to follow this article, you need to be familiar with the vocabulary of malware analysis.\r\nA sandbox\r\nA sandbox is a solution for detonating (executing) a malicious program in a controlled environment. The purpose\r\nof this solution is to help you understand how a malicious program works. I invite you to find out for yourself\r\nabout this fascinating subject, as it can save you precious time when you're under time pressure.\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 1 of 30\n\nIt's worth noting that some malware programs have features that can detect whether they are running in this type\r\nof environment, so as not to execute the malicious payload and thwart detection.\r\nOpen-source (CapeSandbox, Drakfuf Sanbox...) and proprietary (AnyRun, JoeSandbox...) sandboxes are\r\navailable. Your choice of sandbox depends on the level of discretion you require when analyzing malware. In fact,\r\nsome sandboxes collect the samples you send them and make them available to a more or less restricted audience,\r\nas is the case with some free online sandboxes. If you need to maintain a high level of discretion, you should opt\r\nfor a sandbox that can be hosted on your premises, and disable all telemetry options or, even more radically, cut\r\noff access to the Internet.\r\nA command and control server\r\nA command and control server, also known as C2 or CnC (Command and Control), is a server belonging to a\r\nmalicious actor, enabling them to collect and even interact with an agent (malware) installed on an infected\r\nworkstation.\r\nThe C2 of a malicious program is very often requested from a malware analyst with the aim of adding it in\r\ndetection and network flow equipments (e.g. firewalls, SIEMs, etc.).\r\nThe C2 of a malicious program can, for example, be identified as a domain or an IP address.\r\nA packer\r\nA packer is generally the name given to a tool that compresses and potentially obfuscates an original program. A\r\npacker can be used legitimately by software publishers to protect intellectual property. Malicious programs are\r\nregularly packed to bypass static detection systems.\r\nA packer can incorporate obfuscation, anti-emulation, anti-VM and anti-debugging techniques. The packer studied\r\nin this blog post will contain only anti-emulation and obfuscation methods.\r\nThere are open-source packers and obfuscators available on Github, and other proprietary ones (e.g. Tigress,\r\nVMProtect).\r\nAn emulator (symbolic execution)\r\nIn the context of reverse engineering, an emulator simulates the behavior of a program or a sequence of\r\ninstructions by reproducing the execution context (CPU register, memory, etc.). Emulation does not deliver the\r\nsame performance as a virtual machine, because the program instructions are not executed directly by the\r\nprocessor, but simulated.\r\nAmong other things, emulation makes it possible to bypass specific anti-debug or anti-VM mechanisms that may\r\nbe implemented in programs. On the other hand, some programs use anti-emulation. Anti-emulation can be\r\ncharacterized by the presence of dead code (useless code) that requires a quite a few of execution time, e.g. a loop\r\nthat calls a useless function. A normal CPU would take a fraction of a second to execute, whereas an emulator can\r\ntake several minutes.\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 2 of 30\n\nEmulators are present in detection systems such as antivirus software. The latter analyzes the behavior of\r\nprograms by emulating them. If anti-emulation techniques are present in the program and are not thwarted by the\r\nemulator, the emulation may then terminate in timeout.\r\nSome open-source emulators allow symbolic execution of Windows PEs, such as MIASM, Qiling, Triton or\r\nSpeakEasy.\r\nAnalysis tools\r\nFor malware analysis, we strongly recommend using a virtual environment. You can use the hypervisor of your\r\nchoice (e.g. VMWare, Virtualbox, KVM). It can be useful to use different systems, as some malware only work on\r\nspecific systems. For example, you may have a Windows 7 machine and a Windows 10 machine. Once your\r\nmachine is installed, you can install analysis tools, a disassembler, a debugger, dynamic analysis tools, and a\r\ndevelopment and compilation environment.\r\nBelow is a non-exhaustive list of tools that may be useful for your analyses:\r\nSysinternal Suite (procmon, processexplorer, autoruns, tcpview...)\r\nDetect It Easy\r\nPEStudio\r\nDNSpy (if you come across .NET)\r\nPE-Bear\r\nFloss\r\nIsolate your machine from the network to prevent any connection to the outside world. Unless you're using a\r\nsystem such as inetsim, disable network interface completely.\r\nMake a snapshot of your virtual machine in a healthy state once all your analysis tools are installed and\r\nconfigured, so that you can return to a healthy state at any time.\r\nSet up a system enabling you to transfer files between your host and your virtual system. Be careful to restrict\r\naccess to an empty directory or one with no critical data (some ransomware encrypts file shares). If in doubt,\r\ndisable file sharing completely once you've transferred the malicious sample to the virtual machine.\r\nMake regular snapshots as you progress through the analyses, so that you can keep a record of your progress.\r\nA disassembler\r\nThere are several disassemblers on the market, but the one that seems to be the most widely used in professional\r\nenvironments today is IDA Pro. It costs a quite a few of money, especially if you need a decompiler for a specific\r\narchitecture. It has a built-in debugger and an API to automate your analysis, as well as a number of open-source\r\nplugins available on Github.\r\nIn this article we use Binary Ninja, which has the same features as the IDA Pro tool, but at a much more\r\naffordable price. Binary Ninja has an active community and responsive bug-fixing support. Its integrated plugin\r\nmanager lets you quickly install and configure plugins.\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 3 of 30\n\nIf you want to stay in the free world, there are disassemblers such as:\r\nRadare with its graphical interface iaito (or the fork Rizin with Cutter GUI)\r\nGhidra, a disassembler initially developed by the NSA using the Java language\r\nA debugger\r\nWhen you want to debug a program on Windows, you can use a debugger. Windows provides its own debugger,\r\nWinDBG.\r\nThere's also a relief for OllyDBG (for the oldest among you) called x64dbg. It has a large number of useful\r\nplugins for your reverse sessions. Some useful plugins for malware analysis:\r\nhttps://github.com/x64dbg/ScyllaHide\r\nhttps://github.com/buzzer-re/x64dbg-ASLR-Removal\r\nhttps://github.com/therealdreg/DbgChild\r\nInitial sample (Stage 1)\r\nOpen source research\r\nWhere to start\r\nThe aim of the open-source research phase is to gather the information you need for your analyses. When you're\r\nanalyzing a malicious sample, it's possible that some people have already studied the sample, or at least the\r\nmalware family. If you're dealing with a totally unknown sample, grab your knowledge, some tools, a few liters of\r\ncoffee and get started! The malicious actor who developed the malicious sample aims to discourage you, waste\r\nyour time and make you give up.\r\nTake the time to read blog articles on malicious sample analysis, attend or watch replays of conferences on the\r\nsubject (e.g. Botconf, Virus Bulletin), monitor social networks such as Twitter/X.\r\nAnother very useful platform for your malware research is Malpedia. It categorizes malware families by actor and\r\nprovides samples, Yara rules and links to external articles.\r\nSample recovery\r\nIn this article, we've chosen an arbitrarily selected sample for you. The sample was part of one of the last\r\nsubmissions on Malware Bazaar (malicious sample sharing platform) by user zbetcheckin. This sample has been\r\ncategorized as part of the StealC family. The program appears to be detected by 58 antivirus programs.\r\nA search in Malpedia tells us that StealC:\r\nis a Malware-as-a-Service\r\nhas existed since january 2023\r\nwas written in C\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 4 of 30\n\ncollects information on:\r\nweb browsers\r\ncryptographic wallets\r\ninstant message software and emails\r\ncommunicates with its C2 using HTTP POST requests\r\nTwo Yara rules are supplied: win_stealc_auto and win_stealc_w0 . These date from 2023. We can put them\r\naside for later, as they'll come in handy. Blog posts are also available - feel free to have a look if you want to\r\nunderstand some of the grey areas.\r\nHere's some information about the sample we'll be looking at in this article:\r\nType Data\r\nSHA256 c173cfcb0adfa3013a398638789bf4350601cce0e1c55a456d98311543062f82\r\nSHA1 87e5501bbc72be1d0b763acec9bf08c9db26a8d1\r\nMD5 51e5979460e5a9dc941c03bc76cc3855\r\nFile size 455'681 bytes\r\nFirst seen 2024-04-08 07:13:32 UTC (Malware Bazaar)\r\nMIME type application/x-dosexec\r\nimphash 9ee9346826f4cfd6b39a524a25cdc5de\r\nssdeep 12288:f9zyluCg7RvcQ7tZRsuPE16N0N9k9ptHMF:PCg7RvcKKnitHMF\r\nUser zbetcheckin indicates the origin of the malware in a comment on Malware Bazaar:\r\nComment on Malware Bazaar\r\nThe URL in the comment can be used to extract the following indicators of compromise:\r\nType Data\r\nIPv4 185.172.128[.]59\r\nFilename ISetup8.exe\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 5 of 30\n\nType Data\r\nURL hxxp://185.172[.]128.59/ISetup8.exe\r\nBy digging deeper into these indicators of compromise, it would be possible to retrieve more information on the\r\ninfrastructure of the malicious actor. For example, we could retrieve the AS (autonomous system) associated with\r\nthe IP address and pivot using this information. But this is beyond the scope of this article.\r\nWe can retrieve the malicious sample via open sources such as Malware Bazaar, VirusTotal (requires an\r\nappropriate license) or directly from the infrastructure of the malicious actor if the latter has not shut down its\r\nservice. If you plan to retrieve a sample from the infrastructure of a malicious actor, we strongly recommend that\r\nyou use at least one bounce to connect to it (e.g. Tor, VPN, even a proxy).\r\nThe files can be recovered here. Malicious samples are contained in password-protected ZIP archives. The\r\npassword used for these archives is the one most commonly used when exchanging malicious files: infected\r\nPacker identification\r\nIf you have not yet configured your machine, please refer to the chapter Reminders and preparation of the\r\nanalysis environment in this article. Now that you have the sample on your analysis system, make sure you have\r\nshut down the network.\r\nEntropy\r\nWith Binary Ninja\r\nIf you want to save time and you own Binary Ninja, you can open our .bndb file.\r\nIf we look at the binary data graph, we can see that a large part of the program contains data that cannot be\r\nunderstood by the disassembler:\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 6 of 30\n\nStage 1 - Pixel map\r\nYou can get an entropy score with the get_entropy method of the Binary Ninja API:\r\n\u003e\u003e\u003e for section in bv.sections:\r\n... sec = bv.get_section_by_name(section)\r\n... print(f\"Section '{section}': {bv.get_entropy(sec.start, sec.start)}\")\r\n...\r\nSection '.data': [0.446795254945755]\r\nSection '.extern': [0.0]\r\nSection '.rdata': [0.6443907618522644]\r\nSection '.rsrc': [0.5072780251502991]\r\nSection '.synthetic_builtins': [0.0]\r\nSection '.text': [0.839885950088501]\r\nWith Detect It Easy\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 7 of 30\n\nYou can also check the entropy of the binary with another tool such as Detect It Easy. With this tool, you can see\r\nthat the .data and .text sections have a rather high entropy level:\r\nDetect It Easy - Stage 1's Entropy\r\nWith Yara\r\nAnother less precise way of identifying whether a program is packed is to use Yara's math.entropy module.\r\nimport \"math\"\r\nrule PE_High_entropy\r\n{\r\n condition:\r\n uint16(0) == 0x5A4D and\r\n uint32(uint32(0x3C)) == 0x00004550 and\r\n math.entropy(0, filesize) \u003e 6.5\r\n}\r\nUnpack sample manually\r\nIf you're faced with this type of packer and you're in a hurry, it may be necessary to unpack it manually if no\r\nautomated solution exists.\r\nIt is common for packers to use memory allocation methods such as VirtualAlloc, then add execution permissions\r\nto this memory area with VirtualProtect. Once the memory area has been allocated, the packers retrieve the\r\nencrypted second stage. The second stage may, for example, be present in the program resources. The packer then\r\nuses decryption or decoding alrogithms to decrypt the second stage and execute it.\r\nOne way of extracting the packed program is to set write hardware breakpoints on memory areas freshly allocated\r\nby methods such as VirtualAlloc and then identify the decryption routine. Once the decryption loop has passed,\r\nyou can extract the memory area. Note that this type of case does not always work.\r\nThe pkr_ce1a packer uses the VirtualAlloc method, decrypts the second layer and adds a few modifications\r\nto it, then releases the memory area with VirtualFree .\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 8 of 30\n\nMemory allocation\r\nTo get started, set up your favorite debugger in a virtual machine isolated from the network and any file sharing.\r\nSet a breakpoint on the VirtualAlloc and VirtualFree methods of the Kernel32.dll library. Each packer\r\nhas its own way of extracting. Adapt the various steps to your technical environment.\r\nIf you're using Binary Ninja or a WinDBG base, you can set a breakpoint on specific methods once you've reached\r\nthe entry point of your program. The command is bu kernel32.dll!VirtualAlloc :\r\nBinary Ninja - Breakpoint in debugger console\r\nContinue program execution ( F9 ) until the breakpoint is triggered. Get the size of the VirtualAlloc argument.\r\nHere the argument sent to the function is pushed onto the stack and then stored in the eax register ( 0x6ae00 ) in\r\nthe VirtualAlloc function.\r\nBinary Ninja - Debugging view\r\nTo get the return address, continue to the function return ( CTRL + F9 in binary ninja). This will give us the\r\naddress of the memory area allocated by VirtualAlloc in 0x21a0000 . This address will not necessarily be the\r\nsame on your environment:\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 9 of 30\n\nBinary Ninja - Debugging view\r\nSave the starting address of the memory area, and its size for later usage. Continue executing the program until\r\nyou call the VirtualFree ( F9 ) method:\r\nBinary Ninja - VirtualFree Bp hit\r\nBefore extracting the PE, you can retrieve the first two bytes and make sure it's a Windows executable and has the\r\nMZ header:\r\nBinary Ninja - MZ header in memory region\r\nYou can then dump the memory area and save it on your filesystem. Below is the code you can adapt to the\r\nmemory address allocated by your system and the path where you wish to save the unpacked program:\r\nimport base64\r\npe_data = dbg.read_memory(0x21a0000, 0x6ae00).base64_encode()\r\nf = open(r\"C:\\Users\\user\\Desktop\\pe_unpacked.bin\", \"wb\")\r\nf.write(base64.b64decode(pe_data))\r\nf.close()\r\nOnce done, you can open it in a tool such as Detect It Easy to check its validity:\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 10 of 30\n\nDetect it Easy on unpacked sample\r\nBravo, you've just unpacked the first Stage! \\o/\r\nIdentifying elements in the packer\r\nCryptographic functions\r\nA packer can use cryptographic functions to hide/unhide code. You can use Yara or plug-ins built into your\r\ndisassembler to identify cryptographic constants.\r\nFor IDA, you can use Findcrypt-yara. With Binary Ninja, you can use CryptoScan:\r\nCryptoScan - TEA algorithm match\r\nA cryptographic constant from the TEA algorithm seems to be used in 0x4014c3 , so let's take a closer look. We\r\ncan start by renaming the variables and removing the dead code (garbage) present in this function:\r\n0040146e int32_t __fastcall xx_decode_shellcode_TEA(int32_t* memory_area)\r\n00401477 int32_t blob_length2_1 = blob_length2\r\n00401485 int32_t data_1 = *memory_area\r\n00401487 int32_t data_2 = memory_area[1]\r\n0040148a int32_t previous_data_1 = data_1\r\n00401493 if (blob_length2_1 == 0x594)\r\n004014a0 void lpFilename // Garbage\r\n004014a0 GetModuleFileNameW(hModule: nullptr, lpFilename: \u0026lpFilename, nSize: 0)\r\n004014a6 blob_length2_1 = blob_length2\r\n004014ac int32_t key1_1 = key1\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 11 of 30\n\n004014b4 int32_t key_1 = 0\r\n004014bb int32_t key2_1 = key2\r\n004014c0 int32_t delta = 0x9e3779b9 // Crypto constant identified by CryptoScan\r\n004014ca add_ecx_content_0xc6ef34e1(\u0026key_1) // Second crypto constant calculated\r\n004014cf key_1 += 0x23f\r\n004014d9 void lpszVolumeName\r\n004014d9 if (blob_length2_1 == 0x14)\r\n004014e6 // Garbage\r\n004014e6 FindNextVolumeA(hFindVolume: nullptr, lpszVolumeName: \u0026lpszVolumeName, cchBufferLength: 0)\r\n004014ec int32_t data_3 = key3\r\n004014f2 int32_t data_6 = key4\r\n004014f9 int32_t data = data_3\r\n004014ff int32_t loop_counter = 0x20\r\n00401643 int32_t loop_index\r\n00401643 do\r\n00401500 int32_t var_18_1 = 2\r\n00401507 char shift_amount_2 = 5\r\n00401521 int32_t temp_key = key_1\r\n00401527 int32_t temp_data_7 = data_4c1b48\r\n0040152c if (blob_length2 == 0xfa9) // Garbage\r\n0040152c temp_data_7 = 0xedeb2e40\r\n00401531 bool cond:2_1 = blob_length2 == 0x3eb\r\n0040153b data_4c1b48 = temp_data_7\r\n00401540 int32_t temp_data_8 = data_4c1aa0\r\n00401545 if (cond:2_1)\r\n00401545 temp_data_8 = 0\r\n0040154a data_4c1aa0 = temp_data_8\r\n0040155e int32_t temp_data_9 = ((data_1 \u003c\u003c 4) + data_3) ^ (temp_key + data_1)\r\n00401565 data_4c1b44 = 0xf4ea3dee\r\n00401579 int32_t shift_result = temp_data_9\r\n0040157f if (blob_length2 == 0x213) // Garbage\r\n00401586 CreateHardLinkW(lpFileName: nullptr, lpExistingFileName: nullptr, lpSecurityAttributes: nu\r\n0040158e IsValidCodePage(CodePage: 0)\r\n0040159a uint8_t* var_28\r\n0040159a GetCompressedFileSizeW(lpFileName: nullptr, lpFileSizeHigh: \u0026var_28)\r\n004015ad void lpBaseAddress\r\n004015ad WriteProcessMemory(hProcess: nullptr, lpBaseAddress: \u0026lpBaseAddress, lpBuffer: nullptr, nS\r\n004015b8 GetNumaProcessorNode(Processor: 0, NodeNumber: var_28)\r\n004015ca void lpBuffer\r\n004015ca void lpNumberOfEventsRead\r\n004015ca ReadConsoleInputA(hConsoleInput: nullptr, lpBuffer: \u0026lpBuffer, nLength: 0, lpNumberOfEvent\r\n004015d5 DeleteTimerQueueTimer(TimerQueue: nullptr, Timer: nullptr, CompletionEvent: nullptr)\r\n004015de data_2 -= ((previous_data_1 u\u003e\u003e 5) + data_6) ^ temp_data_9\r\n004015f8 int32_t xor_result_1 = (data_2 u\u003e\u003e shift_amount_2) + key2_1\r\n00401606 int32_t temp_data_11 = ((data_2 \u003c\u003c 4) + key1_1) ^ (key_1 + data_2)\r\n00401615 int32_t xor_result // Garbage\r\n00401615 if (blob_length2 != 0xb03)\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 12 of 30\n\n00401625 xor_result = xor_result_1\r\n00401615 else // Garbage\r\n00401617 GetConsoleAliasExesLengthA()\r\n00401620 xor_result = xor_result_1\r\n00401628 int32_t temp_data_12 = temp_data_11 ^ xor_result\r\n0040162c int32_t result_1 = temp_data_12\r\n00401634 data_1 = add(data_1, temp_data_12)\r\n00401636 previous_data_1 = data_1\r\n0040163c key_1 -= delta\r\n0040163f data_3 = data\r\n00401642 loop_index = loop_counter\r\n00401642 loop_counter -= 1\r\n00401643 while (loop_index != 1)\r\n0040164c *memory_area = data_1\r\n0040164f memory_area[1] = data_2\r\n00401657 return delta\r\nWe can see that the encryption keys are directly integrated into the function:\r\nTEA algorithm keys\r\nStarting from the following C code:\r\nvoid decrypt (uint32_t v[2], const uint32_t k[4]) {\r\n uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up; sum is (delta \u003c\u003c 5) \u0026 0xFFFFFFFF */\r\n uint32_t delta=0x9E3779B9; /* a key schedule constant */\r\n uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */\r\n for (i=0; i\u003c32; i++) { /* basic cycle start */\r\n v1 -= ((v0\u003c\u003c4) + k2) ^ (v0 + sum) ^ ((v0\u003e\u003e5) + k3);\r\n v0 -= ((v1\u003c\u003c4) + k0) ^ (v1 + sum) ^ ((v1\u003e\u003e5) + k1);\r\n sum -= delta;\r\n } /* end cycle */\r\n v[0]=v0; v[1]=v1;\r\n}\r\nIt can be transcribed into python and the encryption keys added:\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 13 of 30\n\nKEY = [0x1ed822c8, 0x1eda8d45, 0x6fd0bf39, 0x19ea64a5]\r\ndef tea_decipher(v):\r\n y = c_uint32(v[0])\r\n z = c_uint32(v[1])\r\n sum = c_uint32(0xc6ef3720)\r\n delta = 0x9e3779b9\r\n n = 32\r\n w = [0,0]\r\n while(n\u003e0):\r\n z.value -= ( y.value \u003c\u003c 4 ) + KEY[2] ^ y.value + sum.value ^ ( y.value \u003e\u003e 5 ) + KEY[3]\r\n y.value -= ( z.value \u003c\u003c 4 ) + KEY[0] ^ z.value + sum.value ^ ( z.value \u003e\u003e 5 ) + KEY[1]\r\n sum.value -= delta\r\n n -= 1\r\n w[0] = y.value\r\n w[1] = z.value\r\n return w\r\nThe function that calls our xx_decode_TEA function (renamed by us) seems to decode an entire buffer:\r\nBuffer decoding with TEA\r\nA search on a search engine allows us to put a name to our packer thanks to an article from Elemental X:\r\nGoogle search\r\nIdentify the shellcode call\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 14 of 30\n\nIf we go back to the Calltree of our xx_decode_shellcode_TEA decryption function, we can see that the function\r\nis called by xx_decode_shellcode , which decrypts a buffer, and then by a xxx_call_stage2 function in\r\n0x004016ca :\r\nBinary Ninja - Calltree\r\nIf you look at the code in the xxx_call_stage2 function, you'll see a lot of unnecessary code and anti-emulation.\r\nFor example, the code below shows a loop that performs 0x29156e rounds by calling the WinAPI method\r\nSelectObject. NULL values are sent to it, and the return of the function is not even checked. This type of call\r\nconsiderably slows down program execution in an emulator.\r\nAnti-Emulation loop\r\nWriting a Yara rule for the packer\r\nNow that we have a name for our pkr_ce1a packer, we can search the Internet for existing Yara rules.\r\nMalwarology has wrote a very good blog post on analyzing the packer.\r\nThey also provide two Yara rules, one that retrieves the size of a shellcode and the other that retrieves the\r\nshellcode address.\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 15 of 30\n\nstrings:\r\n $shellcode_size = { 00699AF974[4]96AACB4600 }\r\n $shellcode_addr = { 0094488D6A[4]F2160B6800 }\r\nWe can search for these sequences of bytes in our binary and rename the variables accordingly in our\r\ndisassembler. In this sample, the shellcode address is 0x41f0e8 and the size pointer is 0x41eef3 .\r\nIn addition to the two strings of the Malwarology rules, we're adding other parameters that we've identified from\r\nthe start to reduce the number of false positives:\r\nTEA cryptographic constants\r\nan entropy check to increase the likelihood of it being a packed program\r\nThis can give us the following rule:\r\nimport \"math\"\r\nimport \"pe\"\r\nrule Packer_pkr_ce1a_generic\r\n{\r\n meta:\r\n date = \"2024-04-25\"\r\n description = \"Detect pkr_ce1a packer\"\r\n sharing = \"TLP:CLEAR\"\r\n example = \"c173cfcb0adfa3013a398638789bf4350601cce0e1c55a456d98311543062f82\"\r\n packer = \"pkr_ce1a\"\r\n strings:\r\n // First stage shellcode size: 0x41ef1f\r\n $shellcode_size = { 00699AF974[4]96AACB4600 }\r\n // First stage shellcode addr: 0x41eef3\r\n $shellcode_addr = { 0094488D6A[4]F2160B6800 }\r\n // delta TEA algorithm : 0x004014c0\r\n $tea_const_delta = { B979379E }\r\n // sum TRA algorithm : Not found in this sample\r\n $tea_const_sum = { 2037EFC6 }\r\n /*\r\n // The tea sum is calculated; perhaps some samples don't have this implementation.\r\n 004014b1 8d4df8 lea ecx, [ebp-0x8 {key_1}]\r\n 00401462 8101e134efc6 add dword [ecx], 0xc6ef34e1\r\n 004014cf 8145f83f020000 add dword [ebp-0x8 {key_1}], 0x23f\r\n */\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 16 of 30\n\n$tea_sum_calculated1 = { 8101E134EFC6 }\r\n $tea_sum_calculated2 = { 8145F83F020000 }\r\n condition:\r\n uint16(0) == 0x5A4D and\r\n uint32(uint32(0x3C)) == 0x00004550 and\r\n $shellcode_size and $shellcode_addr and\r\n (1 of ($tea_const_*) or 2 of ($tea_sum_calculated*)) and\r\n math.entropy(0, filesize) \u003e 6.5\r\n}\r\nThis Yara rule can be improved by you to reduce the number of false positives or increase the match ratio. You can\r\nnow hunt (search for new samples) on platforms such as VirusTotal (if you have a license), MalwareBazaar,\r\nUnpacMe, or in your own database using a tool such as mquery (open-source and developed by CERT.pl).\r\nTo extract the shellcode from the binary, we can use our disassembler API to automate decryption and extraction.\r\nWe know the address of the shellcode, its size and the encryption algorithm.\r\nfrom binaryninja import *\r\nimport sys\r\nfrom ctypes import *\r\noutput_shellcode_path = \"/tmp/shellcode\"\r\nencrypted_blob_shellcode_address = 0x41f0e8\r\nencrypted_blob_shellcode_length = 0x37718\r\n#Crypto key found in Tea function @0x0040146e\r\nKEY = [0x1ed822c8, 0x1eda8d45, 0x6fd0bf39, 0x19ea64a5]\r\ndef tea_decipher(v):\r\n y = c_uint32(v[0])\r\n z = c_uint32(v[1])\r\n sum = c_uint32(0xc6ef3720)\r\n delta = 0x9e3779b9\r\n n = 32\r\n w = [0,0]\r\n while(n\u003e0):\r\n z.value -= ( y.value \u003c\u003c 4 ) + KEY[2] ^ y.value + sum.value ^ ( y.value \u003e\u003e 5 ) + KEY[3]\r\n y.value -= ( z.value \u003c\u003c 4 ) + KEY[0] ^ z.value + sum.value ^ ( z.value \u003e\u003e 5 ) + KEY[1]\r\n sum.value -= delta\r\n n -= 1\r\n w[0] = y.value\r\n w[1] = z.value\r\n return w\r\ndef bytes_to_int32_array(byte_data):\r\n num_int32 = len(byte_data) // 4\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 17 of 30\n\nint32_array = struct.unpack(\"\u003c{}I\".format(num_int32), byte_data)\r\n return int32_array\r\ndef int32_array_to_bytes(int32_array):\r\n byte_data = struct.pack(\"\u003c{}I\".format(len(int32_array)), *int32_array)\r\n return byte_data\r\ndef main():\r\n encrypted_blob_shellcode = bv.read(encrypted_blob_shellcode_address, encrypted_blob_shellcode_length)\r\n encrypted_blob_shellcode_int32_array = bytes_to_int32_array(encrypted_blob_shellcode)\r\n i = 0\r\n decrypted_shellcode_int32_array = []\r\n while i \u003c encrypted_blob_shellcode_length/4:\r\n memory_area = [\r\n encrypted_blob_shellcode_int32_array[i],\r\n encrypted_blob_shellcode_int32_array[i+1]\r\n ]\r\n memory_area = tea_decipher(memory_area)\r\n decrypted_shellcode_int32_array.append(memory_area[0])\r\n decrypted_shellcode_int32_array.append(memory_area[1])\r\n i+=2\r\n f = open(output_shellcode_path, \"wb+\")\r\n f.write(int32_array_to_bytes(decrypted_shellcode_int32_array))\r\n f.close()\r\n print(f\"Stage2 dumped : {output_shellcode_path}\")\r\n return\r\nmain()\r\nShellcode is then extracted, but this is not the originally packed program. This shellcode continues loading the\r\nvarious elements and calls up allocation and decryption methods. You can continue its analysis to go step by step\r\nthrough the decryption of the sample.\r\nShellcode analysis will not be covered in this article. We chose to take a more traditional approach to extraction,\r\neither using manual extraction or, to save time, automated extraction with an emulator.\r\nUnpack the sample automatically with MIASM\r\nWhen you encounter the same type of packer several times, or when you want to scale up your analyses, it may be\r\na good idea to automate your actions.\r\nYou can use sandboxes to extract samples. The drawback is that some packer programs or malware use anti-VM\r\ntechniques, and the execution (VM restoration and analysis time) can be more or less long and require a lot of\r\nmachine resources. Using a sandbox can be complicated, especially if you have a very large number of samples to\r\nextract.\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 18 of 30\n\nOne possible solution (which also has its drawbacks) is to use an emulation solution. In this article, we chose the\r\njitter of MIASM to emulate our sample. We chose MIASM because it meets our needs very well. But there are\r\nalso emulators with a sandbox, such as Qiling.\r\nPreparation of the environment\r\nWe will use the Jitter module of MIASM and its sandbox example here.\r\nTo run the basic script, you need to install the MIASM environment:\r\ngit clone https://github.com/cea-sec/miasm.git\r\npython3 -m venv ./venv/\r\nsource venv/bin/activate\r\ncd miasm\r\npython3 setup.py install\r\nYou can display script help with the --help option:\r\nHelp menu from MIASM jitter\r\nThroughout the development of the unpacker, you will be confronted with errors of various kinds, non-existent\r\nmemory zones, WinAPI methods not implemented in MIASM, etc... Example:\r\nMIASM jitter stacktrace example\r\nNew libraries\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 19 of 30\n\nYou should encounter a problem: the loading of new DLLs that are not present in your project by default. Below is\r\nthe error this generates, where msimg32.dll is missing:\r\n[INFO ]: kernel32_LoadLibrary(dllname=0x4192f8) ret addr: 0x401871\r\n[WARNING ]: Create dummy entry for 'msimg32.dll'\r\nWARNING: address 0x341EDC is not mapped in virtual memory:\r\nTraceback (most recent call last):\r\n File \"/home/user/Dev/test_stealc/sandbox_pe_x86_32.py\", line 143, in \u003cmodule\u003e\r\n sb.run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n super(Sandbox_Win_x86_32, self).run(addr)\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n self.jitter.continue_run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n return next(self.run_iterator)\r\n ^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n raise JitterException(exception_flag)\r\nmiasm.jitter.jitload.JitterException: A jitter exception occurred: DO_NOT_UPDATE_PC \u0026 ACCESS_VIOL (0x2004000)\r\nYou can create a win_dll folder, which must contain standard Windows libraries (DLL). Add missing DLLs you\r\nmay find on a healthy Windows system. Once you've added the directory, you should be able to continue running\r\nyour program.\r\nMIASM and Windows API\r\nBy default, the sandbox does not use system environment options, such as memory segments, Windows structures,\r\netc... You can add them by specifying them as arguments to the -o -i -s -y script. As you can see from the\r\nexecution below, we take this a step further by crashing a Windows function ( FlsAlloc from the kernel32.dll\r\nlibrary):\r\n$ python3 sandbox_pe_x86_32.py -o -i -s -y c173cfcb0adfa3013a398638789bf4350601cce0e1c55a456d98311543062f82.exe\r\ncannot find crypto, skipping\r\n[ERROR ]: Cannot open win_dll/kernel32.dll\r\n[ERROR ]: Cannot open win_dll/gdi32.dll\r\nc173cfcb0adfa3013a398638789bf4350601cce0e1c55a456d98311543062f82.exe\r\nkernel32.dll\r\ngdi32.dll\r\n[WARNING ]: Create dummy entry for 'kernel32.dll'\r\n[WARNING ]: Create dummy entry for 'gdi32.dll'\r\n[INFO ]: Add module 400000 'c173cfcb0adfa3013a398638789bf4350601cce0e1c55a456d98311543062f82.exe'\r\n[WARNING ]: Unknown module: omitted from link list ('kernel32.dll')\r\n[WARNING ]: Unknown module: omitted from link list ('gdi32.dll')\r\n[WARNING ]: No main pe, ldr data will be unconsistant [\u003cmiasm.loader.pe_init.PE object at 0x7f7020a680d0\u003e, None,\r\n[WARNING ]: No main pe, ldr data will be unconsistant\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 20 of 30\n\n[WARNING ]: No main pe, ldr data will be unconsistant\r\n[INFO ]: kernel32_GetSystemTimeAsFileTime(lpSystemTimeAsFileTime=0x13ffdc) ret addr: 0x409c40\r\n[INFO ]: kernel32_GetCurrentThreadId() ret addr: 0x409c4f\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x409c58\r\n[INFO ]: kernel32_QueryPerformanceCounter(lpPerformanceCount=0x13ffd4) ret addr: 0x409c65\r\n[INFO ]: kernel32_GetStartupInfo(ptr=0x13ff6c) ret addr: 0x405194\r\n[INFO ]: kernel32_GetProcessHeap() ret addr: 0x406d4e\r\n[INFO ]: kernel32_EncodePointer(0x0) ret addr: 0x403ff1\r\n[INFO ]: kernel32_EncodePointer(0x403b32) ret addr: 0x403b84\r\n[INFO ]: kernel32_GetModuleHandle(dllname=0x413ab8) ret addr: 0x405222\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413ad4) ret addr: 0x405232\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413ae0) ret addr: 0x405245\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413ae8) ret addr: 0x405258\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413af4) ret addr: 0x40526b\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413b00) ret addr: 0x40527e\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413b1c) ret addr: 0x405291\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413b2c) ret addr: 0x4052a4\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413b40) ret addr: 0x4052b7\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413b58) ret addr: 0x4052ca\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413b70) ret addr: 0x4052dd\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413b84) ret addr: 0x4052f0\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413ba4) ret addr: 0x405303\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413bbc) ret addr: 0x405316\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413bd4) ret addr: 0x405329\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413be8) ret addr: 0x40533c\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413bfc) ret addr: 0x40534f\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413c18) ret addr: 0x405362\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413c38) ret addr: 0x405375\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413c54) ret addr: 0x405388\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413c74) ret addr: 0x40539b\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413c88) ret addr: 0x4053ae\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413ca4) ret addr: 0x4053c1\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413cb8) ret addr: 0x4053d4\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413cc8) ret addr: 0x4053e7\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413cd8) ret addr: 0x4053fa\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413ce8) ret addr: 0x40540d\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413cf8) ret addr: 0x405420\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413d14) ret addr: 0x405433\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413d28) ret addr: 0x405446\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413d38) ret addr: 0x405459\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413d4c) ret addr: 0x40546c\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413d5c) ret addr: 0x40547f\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x71112000, fname=0x413d7c) ret addr: 0x405492\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457b90, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457ba8, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457bc0, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457bd8, dwSpinCount=0xfa0, Flags=0x0) ret a\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 21 of 30\n\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457bf0, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457c08, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457c20, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457c38, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457c50, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457c68, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457c80, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457c98, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457cb0, dwSpinCount=0xfa0, Flags=0x0) ret a\r\n[INFO ]: kernel32_InitializeCriticalSectionEx(lpCriticalSection=0x457cc8, dwSpinCount=0xfa0, Flags=0x0) ret a\r\nTraceback (most recent call last):\r\n File \"/home/user/Dev/test_stealc/sandbox_pe_x86_32.py\", line 16, in \u003cmodule\u003e\r\n sb.run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n super(Sandbox_Win_x86_32, self).run(addr)\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n self.jitter.continue_run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n return next(self.run_iterator)\r\n ^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n for res in self.breakpoints_handler.call_callbacks(self.pc, self):\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n res = c(*args)\r\n ^^^^^^^^\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n raise ValueError('unknown api', hex(jitter.pc), repr(fname))\r\nValueError: ('unknown api', '0x71112624', \"'kernel32_FlsAlloc'\")\r\nIf you don't want to specify the above-mentioned script options, you can force them into your script:\r\noptions = parser.parse_args()\r\noptions.usesegm = True\r\noptions.use_windows_structs = True\r\noptions.load_hdr = True\r\noptions.dependencies = True\r\nThe FLS methods have been implemented in MIASM, we will use them in our script:\r\nfrom miasm.os_dep.win_api_x86_32 import FLS\r\n[...]\r\nfls = FLS()\r\nkernel32_FlsAlloc = fls.kernel32_FlsAlloc\r\nkernel32_FlsSetValue = fls.kernel32_FlsSetValue\r\nkernel32_FlsGetValue = fls.kernel32_FlsGetValue\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 22 of 30\n\nFunctions not implemented\r\nAdding these lines allows us to go further in executing the program, until a similar error appears, indicating that\r\nthe GetEnvironmentStringsW method in kernel32.dll is not implemented:\r\nValueError: ('unknown api', '0x71112564', \"'kernel32_GetEnvironmentStringsW'\")\r\nNot all methods are implemented in MIASM, and sometimes you'll have to write them yourself in order to achieve\r\nyour goal in program execution.\r\nOne of MIASM's contributors (Commial) published on his GitHub a list of WinAPI methods with input\r\nparameters from several libraries. You can use this as a basis for implementing methods that are not present in\r\nMIASM.\r\nIn some cases, you don't necessarily have to simulate the actual behavior of a method. Some malware only checks\r\nthe return value of the function, so you can force the value to avoid triggering an error or being directed to an\r\nunwanted branch.\r\nAccording to MSDN, the GetEnvironmentStringsW method takes no arguments and returns a pointer to a string\r\nof Wild characters. We can implement the method as follows:\r\nfrom miasm.jitter.csts import *\r\nfrom miasm.os_dep.common import set_win_str_w\r\nfrom miasm.os_dep.win_api_x86_32 import FLS, winobjs\r\n[...]\r\nGetEnvironmentStringsW_addr = None\r\ndef kernel32_GetEnvironmentStringsW(jitter):\r\n \"\"\"\r\n LPWCH GetEnvironmentStringsW()\r\n \"\"\"\r\n global GetEnvironmentStringsW_addr\r\n # Get return address and function arguments\r\n ret_ad, args = jitter.func_args_stdcall([])\r\n #If GetEnvironmentStrings isn't allocated yet\r\n if not GetEnvironmentStringsW_addr:\r\n # Get next allocation address on heap\r\n alloc_addr = winobjs.heap.next_addr(50)\r\n # Allocate memory on heap\r\n winobjs.allocated_pages[alloc_addr] = (alloc_addr, 50)\r\n # Allocate memory page with READ/WRITE permission and write 50 bytes of nullbytes\r\n jitter.vm.add_memory_page(alloc_addr, PAGE_READ | PAGE_WRITE, b\"\\x00\"*50)\r\n # Put env variable in allocated memory\r\n set_win_str_w(jitter, alloc_addr, \"HelloWorld\")\r\n # Store allocated address for next call of GetEnviromentStringsW\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 23 of 30\n\nGetEnvironmentStringsW_addr = alloc_addr\r\n # Return address of allocated memory\r\n jitter.func_ret_stdcall(ret_ad, GetEnvironmentStringsW_addr)\r\n[...]\r\nThe next execution causes problems for the FreeEnvironmentStringsW method. As mentioned earlier, you can\r\nreturn a value expected by the program without having to actually implement the method:\r\ndef kernel32_FreeEnvironmentStringsW(jitter):\r\n ret_ad, args = jitter.func_args_stdcall([\"lpszEnvironmentBlock\"])\r\n jitter.func_ret_stdcall(ret_ad, 1)\r\n jitter.running = True\r\n return True\r\nContinue implementing the various methods on your own, as the logic remains very similar.\r\nAnti-emulation\r\nAt some point, you'll come to a repetitive call to the GetCurrentProcess and GetCurrentProcessId methods:\r\n[...]\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n[INFO ]: kernel32_GetCurrentProcess() ret addr: 0x401b76\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n[INFO ]: kernel32_GetCurrentProcess() ret addr: 0x401b76\r\n[... SAME LINES ...]\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n[INFO ]: kernel32_GetCurrentProcess() ret addr: 0x401b76\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n[INFO ]: kernel32_GetCurrentProcess() ret addr: 0x401b76\r\n[... SAME LINES ...]\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n[INFO ]: kernel32_GetCurrentProcess() ret addr: 0x401b76\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n[INFO ]: kernel32_GetCurrentProcess() ret addr: 0x401b76\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n[INFO ]: kernel32_GetCurrentProcess() ret addr: 0x401b76\r\n[INFO ]: kernel32_GetCurrentProcessId() ret addr: 0x401b70\r\n^C\r\nKeyboardInterrupt\r\nThe return addresses for these methods are 0x401b70 and 0x401b76 . Open the program in your disassembler\r\nand go to these addresses. You should find the following code:\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 24 of 30\n\nStage 1 - Anti-Emulation loops\r\nAs you can see, you've just encountered the loop in the first red frame. The GetCurrentProcessId and\r\nGetCurrentProcess methods are called a very large number of times (without the return values of these functions\r\nbeing checked) as long as the value of the ESI register is less than 0x4f672 .\r\nThis is an anti-emulation method. On a real environment, the execution of this loop is very fast, but when the\r\nenvironment is emulated, execution is much longer. Some security systems, such as antivirus software, stop their\r\nanalysis when they encounter this type of situation.\r\nYou have several options:\r\nWait until the end (beware, a second loop will follow)\r\nBypass this loop by adding a breakpoint in MIASM\r\nImplement an anti-anti-emulation bypass code\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 25 of 30\n\nFirst, we'll opt for the second method: bypass this loop in the hope that the packer doesn't evolve too much over\r\ntime and that the anti-emulation mechanisms are fixed (spoiler: the packer evolves, constants, WinAPI methods,\r\nloops required for unpacking or not...).\r\ndef jmp_0x00401bda(jitter):\r\n jitter.pc = 0x00401bda\r\n jitter.running = True\r\n return True\r\n[...]\r\nsb.jitter.add_breakpoint(0x401b68, jmp_0x00401bda) # anti-emu GetCurrentProcess \u0026 GetCurrentProcessId\r\nContinue execution and you'll encounter anti-emulation again. Here, the GetLastError method is called without\r\nchecking its return:\r\nAnti-Emulation loop - GetLastError\r\n[...]\r\ndef set_esi_0x674db(jitter):\r\n jitter.cpu.ESI = 0x674db\r\n jitter.running = True\r\n return True\r\n[...]\r\nsb.jitter.add_breakpoint(0x401829, set_esi_0x674db) # anti-emu GetLastError\r\nsb.jitter.add_breakpoint(0x40184a, set_esi_0x674db) # anti-emu GetLastError\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 26 of 30\n\nSEH\r\nThe next step you should encounter after implementing the non-existent Module32First method in MIASM is\r\naccess to a non-existent memory area ( 0x7FFFF000 ):\r\n[INFO ]: kernel32_CreateToolhelp32Snapshot(dwflags=0x8, th32processid=0x0) ret addr: 0x2000f7d1\r\n[INFO ]: kernel32_Module32First(hSnapshot=0xaaaa00, lpme=0x13d49c) ret addr: 0x2000f7f1\r\n[INFO ]: kernel32_VirtualAlloc(lpvoid=0x0, dwsize=0x6bc67, alloc_type=0x1000, flprotect=0x40) ret addr: 0x200\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13c438) ret addr: 0x20047a22\r\nWARNING: address 0x7FFFF000 is not mapped in virtual memory:\r\nTraceback (most recent call last):\r\n File \"/home/user/Dev/test_stealc/sandbox_pe_x86_32.py\", line 151, in \u003cmodule\u003e\r\n sb.run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n super(Sandbox_Win_x86_32, self).run(addr)\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n self.jitter.continue_run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n return next(self.run_iterator)\r\n ^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n raise JitterException(exception_flag)\r\nmiasm.jitter.jitload.JitterException: A jitter exception occurred: DO_NOT_UPDATE_PC \u0026 ACCESS_VIOL (0x2004000)\r\nTo understand in more detail what's happening at this point, you can display the new instruction blocks in MIASM\r\nusing the -b option. You can also debug instruction by instruction using the -z option. You can trigger these\r\nverbosity modes directly from your code with the following snippet:\r\njitter.set_trace_log(\r\n trace_instr=False, #Display each instructions\r\n trace_regs=False, # Display registers values\r\n trace_new_blocks=True #Display new instructions block\r\n)\r\nThis produces the following code:\r\nloc_2004704c\r\nCALL loc_20047a3f\r\n-\u003e c_next:loc_20047051\r\nloc_20047a3f\r\nPUSH EBP\r\nMOV EBP, ESP\r\nPUSH ECX\r\nMOV EAX, DWORD PTR FS:[0x0] ; Get TIB ptr\r\nMOV DWORD PTR [EBP + 0xFFFFFFFC], EAX ; Store TIB ptr in [EBP-0x4]\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 27 of 30\n\nMOV EAX, DWORD PTR [EBP + 0xFFFFFFFC] ; Get TIB TIB which was stored in stack\r\nJMP loc_20047a56\r\n-\u003e c_to:loc_20047a56\r\nloc_20047a56\r\nCMP DWORD PTR [EAX], 0xFFFFFFFF ; compare deref TIB ptr with 0xFFFFFFFF\r\nJNZ loc_20047a51\r\n-\u003e c_next:loc_20047a5b c_to:loc_20047a51\r\nloc_20047a51\r\nMOV EAX, DWORD PTR [EAX]\r\nMOV DWORD PTR [EBP + 0xFFFFFFFC], EAX\r\nCMP DWORD PTR [EAX], 0xFFFFFFFF\r\nJNZ loc_20047a51\r\n-\u003e c_next:loc_20047a5b c_to:loc_20047a51\r\nAs you can see, the packer code retrieves the TIB (Thread Information Block) pointer and compares its contents.\r\nThe address contained in FS:[0x0] is 0x7FFFF000 . We can allocate memory at this address and write\r\n0xFFFFFFFF to it to validate the comparison:\r\nsb.jitter.vm.add_memory_page(0x7FFFF000, PAGE_READ | PAGE_WRITE, b\"\\xff\"*4) # SEH\r\nYou should then arrive at the phase where the program exits with the atexit method of msvcr100.dll . Don't\r\nbother implementing it, as what we're interested in happens before that. As seen in the manual unpacking phase,\r\nwe have two VirtualAlloc and one VirtualFree :\r\n[INFO ]: kernel32_Module32First(hSnapshot=0xaaaa00, lpme=0x13d49c) ret addr: 0x2000f7f1\r\n[INFO ]: kernel32_VirtualAlloc(lpvoid=0x0, dwsize=0x6bc67, alloc_type=0x1000, flprotect=0x40) ret addr: 0x200\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13c438) ret addr: 0x20047a22\r\n[INFO ]: kernel32_LoadLibrary(dllname=0x13d3e4) ret addr: 0x20047099\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13d3e4) ret addr: 0x200470ce\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13d3e4) ret addr: 0x20047106\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13d3e4) ret addr: 0x20047134\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13d3e4) ret addr: 0x2004716c\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13d3e4) ret addr: 0x200471a8\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13d3e4) ret addr: 0x200471e0\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7c800000, fname=0x13d3e4) ret addr: 0x20047215\r\n[INFO ]: kernel32_SetErrorMode(uMode=0x400) ret addr: 0x20047e1c\r\n[INFO ]: kernel32_SetErrorMode(uMode=0x0) ret addr: 0x20047e21\r\n[INFO ]: kernel32_GetVersionEx(ptr_struct=0x13c3cc) ret addr: 0x20047dad\r\n[INFO ]: kernel32_VirtualAlloc(lpvoid=0x0, dwsize=0x6ae00, alloc_type=0x1000, flprotect=0x4) ret addr: 0x2004\r\n[INFO ]: kernel32_VirtualProtect(lpvoid=0x400000, dwsize=0x6e000, flnewprotect=0x40, lpfloldprotect=0x13d454)\r\n[WARNING ]: set page 400000 7\r\n[WARNING ]: set page 401000 7\r\n[WARNING ]: set page 412000 7\r\n[WARNING ]: create page 41b000 7\r\n[WARNING ]: create page 46e000 7\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 28 of 30\n\n[INFO ]: kernel32_VirtualFree(lpvoid=0x200b3000, dwsize=0x0, alloc_type=0x8000) ret addr: 0x20047446\r\n[INFO ]: kernel32_LoadLibrary(dllname=0x43851c) ret addr: 0x200474f6\r\n[...]\r\n[INFO ]: kernel32_LoadLibrary(dllname=0x13d3e4) ret addr: 0x2004789d\r\n[WARNING ]: Create dummy entry for 'msvcr100.dll'\r\n[INFO ]: kernel32_GetProcAddress(libbase=0x7111a000, fname=0x13d3e4) ret addr: 0x200478c7\r\nTraceback (most recent call last):\r\n File \"/home/user/Dev/test_stealc/sandbox_pe_x86_32.py\", line 168, in \u003cmodule\u003e\r\n sb.run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n super(Sandbox_Win_x86_32, self).run(addr)\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n self.jitter.continue_run()\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n return next(self.run_iterator)\r\n ^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n for res in self.breakpoints_handler.call_callbacks(self.pc, self):\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n res = c(*args)\r\n ^^^^^^^^\r\n File \"/home/user/Dev/test_stealc/venv/lib/python3.11/site-packages/miasm-0.1.5.dev47-py3.11-linux-x86_64.egg/m\r\n raise ValueError('unknown api', hex(jitter.pc), repr(fname))\r\nValueError: ('unknown api', '0x7111a004', \"'msvcr100_atexit'\")\r\nWe'll modify MIASM's VirtualFree method and register the memory zone starting with MZ like this:\r\nimport os\r\n[...]\r\ndef kernel32_VirtualFree(jitter):\r\n ret_ad, args = jitter.func_args_stdcall([\"lpvoid\", \"dwsize\", \"alloc_type\"])\r\n all_mem = jitter.vm.get_all_memory()\r\n for region in all_mem:\r\n if region != args.lpvoid:\r\n continue\r\n region_data = all_mem[region][\"data\"]\r\n if region_data[:2] == b\"MZ\":\r\n print(f\"PE unpacked : '{out_fname}' (Region : 0x{region:x})\")\r\n f = open(out_fname, \"wb\")\r\n f.write(region_data)\r\n f.close()\r\n exit()\r\n[...]\r\nbname, fname = os.path.split(options.filename)\r\nfname = os.path.join(bname, fname.replace('.', '_'))\r\nout_fname = fname + '_miasm_unpacked.bin'\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 29 of 30\n\nThis generates the following result after execution:\r\n[...]\r\n[INFO ]: kernel32_VirtualFree(lpvoid=0x200b3000, dwsize=0x0, alloc_type=0x8000) ret addr: 0x20047446\r\nPE unpacked : 'c173cfcb0adfa3013a398638789bf4350601cce0e1c55a456d98311543062f82_exe_miasm_unpacked.bin' (Region\r\nYou can compare the cryptographic fingerprint of the manually unpacked PE with the one you've just generated\r\nwith MIASM:\r\n$ sha256sum c173cfcb0adfa3013a398638789bf4350601cce0e1c55a456d98311543062f82_exe_miasm_unpacked.bin\r\n9874c7bd9d008c8a7105c8e402813204d5c3ddc3fb8d1aaddbb0e19d65062dfb\r\nCongratulations! You've just unpacked Stage 1 with MIASM! \\o/\r\nYou'll find the complete code for extraction with MIASM here.\r\nYou can go on to the next article to analyze this unpacked sample Loader analysis (Stage 2).\r\nSource: https://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nhttps://blog.lexfo.fr/StealC_malware_analysis_part1.html\r\nPage 30 of 30",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://blog.lexfo.fr/StealC_malware_analysis_part1.html"
	],
	"report_names": [
		"StealC_malware_analysis_part1.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775433985,
	"ts_updated_at": 1775826765,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/5e4a2f41ff6b174d9375e1f9138ade339863421c.pdf",
		"text": "https://archive.orkl.eu/5e4a2f41ff6b174d9375e1f9138ade339863421c.txt",
		"img": "https://archive.orkl.eu/5e4a2f41ff6b174d9375e1f9138ade339863421c.jpg"
	}
}