{
	"id": "132c596d-95be-4f66-88f7-6a64a190b534",
	"created_at": "2026-04-06T00:10:54.6927Z",
	"updated_at": "2026-04-10T03:20:00.632753Z",
	"deleted_at": null,
	"sha1_hash": "a4b89dcbdb8b4a38153db477f7c89ca1bca89f49",
	"title": "Smoke Loader Malware – An Easy Guide 101",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 14688550,
	"plain_text": "Smoke Loader Malware – An Easy Guide 101\r\nBy SentinelOne\r\nPublished: 2019-11-21 · Archived: 2026-04-05 22:33:11 UTC\r\nWorking in infosec and supporting clients and SOCs has always exposed me to a huge number of alerts and\r\nincidents. Some of these are more interesting than others. Recently we stumbled across a particular sample of\r\nSmoke Loader malware. Smoke Loader has been in-the-wild since circa 2013 and is often used to distribute\r\nadditional malicious components or artifacts. While the sample is not new, it did prove to be a good opportunity to\r\nrevisit this threat and walk through some of the internals. \r\nThis alert was raised against a suspicious file, classified as a trojan, that was killed and quarantined. What raised\r\nmy curiosity was the number of detections over only a few hours, always from the same workstation, and only\r\nfrom the same user.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 1 of 30\n\nKnowing that the threat was killed without doing any harm, I decided to dig into it a bit more. Just looking at the\r\nSentinelOne console, I was able to see :\r\nThe full path where the detection was made.\r\nThe associated risk level is High: this implies that it’s a positive detection.\r\nFile unique hash that can be tested for any public Indicators of Compromise (IoC).\r\nIn order to do a walk-through of malware reverse engineering steps, I downloaded the threat file and started the\r\nanalysis.\r\nFirst Layer: A Packed VB Win32 Program\r\nWith the downloaded file in my pocket, I quickly fired up an isolated analysis machine equipped with the Flare\r\ntools and started to investigate. At first glance, the sample appears to be a Visual Basic program leveraging Win32\r\nAPIs.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 2 of 30\n\nLet’s see what else we can get from its headers. Looks like pretty standard information, confirming a Visual Basic\r\nprogram due to its import table.\r\nWe can spot some rude and folkloristic words inside the binary strings, some of which make me think of a\r\nregional dialect of southern Italy.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 3 of 30\n\nWith a bit of experience, we can safely assume that the file is packed with an external layer of Visual Basic that\r\ntries to stop, or at least slow down, static analysis. But what about its runtime behavior?\r\nObserving the sample during runtime, we can observe the process injection: this behaviour is common for VB\r\npackers and luckily for us, is often trivial to defeat.\r\nDefeating Visual Basic Packer\r\nWe won’t spend too much time on this: there are plenty of resources on how to unpack such packers and I highly\r\nrecommend the OALabs Youtube video tutorials. It’s necessary and enough to put a breakpoint at\r\nCreateProcessInternalW API inside the debugger to stop the execution at the right time.\r\nAt this point, somewhere in memory, there is a PE file ready to be run. We only need to find it. To do so, we can\r\nsearch the entire memory map for a clue: I decided to search for the “DOS” substring that can usually be found as\r\npart of the “This program cannot be run in DOS mode” string within the PE.\r\nWe got plenty of results for the string, whose hex is 44 4F 53 .\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 4 of 30\n\nHowever, we are particularly interested in just a few locations. Usually the executable is loaded at 0x00400000\r\naddress, so the result we had at 0x0040006C looks like our executable itself.\r\nThings become particularly interesting at 0x002F0094 , which we can follow in the memory dump.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 5 of 30\n\nA memory region with a PE file inside, mapped as Executable, Read, Write. This is definitely our injected file. \r\nWe can simply dump out this memory region to file, clean the junk before the MZ header and analyze its headers.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 6 of 30\n\nIt seems like a legitimate executable, but something is going on: no imports at all. This is interesting!\r\nSecond layer: Static Analysis\r\nWhen we load this new executable in IDA Pro, this was the only chunk of code that was disassembled. \r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 7 of 30\n\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 8 of 30\n\nHere we recognize that it’s a XOR loop that will decode, from address 0x00401567 , a blob of code with the size\r\nof 0xCD bytes with a XOR key equal to 0xCB . At the end of the loop, the same starting address 0x00401567 is\r\npushed onto the stack and with the RET instruction the program flow will be branched over there.\r\nDecoding the Buffer\r\nWith a little bit of IDA scripting, we can XOR the encrypted buffer and move forward in the analysis.\r\nAfter de-xoring the buffer we are met with a mixture of anti-disassembly and anti-debug techniques. It is now\r\npossible to map the purpose of the code blocks.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 9 of 30\n\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 10 of 30\n\nInside this code, we can observe plenty of tricks that try to fool the disassembly flow. A few examples: \r\nAbusing CALL and RET instruction to mess up function boundaries. The CALL instruction will push the\r\nreturn address onto the stack. The RET instruction will then pop off this address into the EIP register,\r\nwhich effectively makes these two instructions useless. However, these few opcodes make IDA think that\r\nthe function ends there and that the next instruction is the end of another function.\r\nAbusing branch instructions that do nothing: CALL \u003caddress\u003e and at \u003caddress\u003e : POP \u003creg\u003e . It’s the\r\neasiest way to get an address inside the EIP register and so to control the program’s flow.\r\nAbusing JMP instructions: simply putting a lot of JMP instructions that will jump back and forth only to\r\nmake the life of the analyst miserable.\r\nObfuscated with these techniques, the malware checks if it’s being debugged. The code that implements this check\r\nis nothing complicated: it queries certain flags of the PEB in order to spot the debugger, IsDebuggerPresent .\r\nmov eax, fs:[30h] ; Process Environment Block\r\ncmp b [eax+2], 0 ; check BeingDebugged\r\njne being_debugged\r\nAs said, this code is heavily obfuscated with junk jumps and a lot of instructions with the only purpose of\r\nincreasing complexity of analysis. As an example, this little chunk of code is the final part of a dozen lines of code\r\nused to put value 0x30 inside the EAX register with the purpose of locating the PEB.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 11 of 30\n\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 12 of 30\n\nAt the end of this function, we spot another XOR stub decoding routine that will decode another blob of code and,\r\nafter that, redirect the execution flow. Decoding will start at address 0x004014E8 , with a buffer size of 0x7F and\r\nthe same XOR key 0xCB .\r\nAs before, we can proceed in the static analysis, manually decoding this buffer with the same script.\r\nBut wait! Here we go again, another anti-debugging trick, NtGlobalFlag check:\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 13 of 30\n\nmov eax, fs:[30h] ; Process Environment Block\r\nmov al, [eax+68h] ; NtGlobalFlag\r\nand al, 70h\r\ncmp al, 70h\r\nje being_debugged\r\nThis chunk of code checks if the process is attached to a debugger and, if it goes well, another XOR decoding stub\r\nstarts from address 0x00401000 , with buffer size 0x4E8 and XOR key 0xCB .\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 14 of 30\n\nAfter decoding the new buffer, we need to face another anti-disassembly trick; namely, JMP instructions with a\r\nconstant value. This is the most common trick used by malware to fool static analysis. Basically, it creates jumps\r\ninto a new location plus one or a few bytes. It results in an erroneous interpretation of the opcode by the\r\ndisassembler. It’s trivial to defeat but time intensive. \r\nIAT Resolution at Runtime\r\nAt address 0x00401000 there’s a simple call to another address 0x00401049 , where it starts to become\r\ninteresting as the malware appears to dynamically resolve its imports. As we noted before, the binary header\r\nanalysis showed no imports at all. With this code, from the PEB location found earlier, the malware finds the base\r\naddress of ntdll.dll .\r\nBut how is this happening? In all recent Windows versions, the GS register points to a data structure called the\r\nThread Environment Block (TEB). At offset 0x30 of the TEB, there’s another data structure, namely the Process\r\nEnvironment Block (PEB) we saw earlier. \r\nWe can inspect these data structures with the help of Microsoft public symbols and WinDBG.\r\nWith the same tools we can inspect the PEB too:\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 15 of 30\n\nWith the third instruction, we are following the offset 0x0C , the _PEB_LDR_DATA structure. This structure is\r\nfairly important because it contains a pointer, InInitializationOrderModuleList , to the head of a double-linked\r\nlist that contains the NTDLL loader data structures for the loaded modules. \r\nEach item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. If we inspect this structure, we get\r\nthe DLLBase .\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 16 of 30\n\nLooking at this inside the debugger helps to shed some light:\r\nWe got the base address of module ntdll.dll into EDX register, because this is the first module loaded into\r\nevery process in a Windows environment. We have added comments and renamed select functions to clear up\r\nsome of the observables.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 17 of 30\n\nAfter the malware gets the ntdll.dll base address, it loops twice calling a function named\r\nDecryptionFunction . This function receives as input a dword, which here is a hash.  As we’re going to see, it\r\nwill walk the Export Address Table of the module searching for a particular function with the name matching to\r\nthe passed hash. With this first loop, the malware finds two functions: strstr and LdrGetDllHandle .\r\nAs an example, in this particular case, the DecryptionFunction is walking, as we explained before for\r\nntdll.dll , the module kernel32.dll , retrieving the address of VirtualAlloc put inside the EAX register as\r\nreturn value.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 18 of 30\n\nDecryptionFunction\r\nAfter fully disassembling the function(s) we have the following: \r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 19 of 30\n\nThe hashes of the resolved and imported functions appear as follows: \r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 20 of 30\n\nAfter using the debugger to step into the loops of the DecryptionFunction , we were able to find what functions\r\nthe malware uses next.\r\nThis part of the executable almost works the same way through libraries and functions. I highly suggest looking at\r\nthe disassembly line by line to understand the inner working of the Windows Internal Subsystem and API calls.\r\nAnother interesting trick to be even more stealthy is the use of stack strings to build calls to LoadLibraryA . The\r\nsecret here is that, by definition, the CALL instruction pushes the next address onto the stack as the return\r\naddress. But this address is an ASCII null terminated string that will be an argument for the next LoadLibraryA\r\ncall. Here you can see how it loads two libraries: advapi32 and user32 .\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 21 of 30\n\nImmediately after resolving the imports, the malware sleeps for 10 seconds and then retrieves a filename via\r\nGetModuleFileNameA .\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 22 of 30\n\nInterestingly, the image above also shows how the code checks if its own name contains the string “sample” and if\r\nso consequently terminates itself. You can see how the call to the strstr function is built and how the previous\r\npush is given to check for the “sample” string.\r\nIt’s a simple anti-analysis technique that might easily catch you out. Protip: do not call your sample “sample”. 🙂\r\nNext, the malware performs another check via GetVolumeInformationA , which is thoroughly documented in\r\nMSDN. Let’s look inside this call to understand its purpose.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 23 of 30\n\nFrom the above disassembly, we can see that it retrieves the volume serial number and checks if it’s equal to some\r\ntwo serials. It then opens a registry key with RegOpenKeyExA , pushing one of the arguments with the same CALL\r\ntechnique. It then obtains the value of the registry key, closes the handle, and converts the value to lowercase\r\nbefore proceeding.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 24 of 30\n\nIt looks clear when you see it in the debugger.\r\nWith this string saved somewhere in memory, the code goes on to perform some other checks trying to find any\r\nsign of running inside a virtual environment. \r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 25 of 30\n\nAs part of the anti-virtual machine checks, it initializes a 4 cycle loop; during this loop it performs a call to the\r\nstrstr function to search inside the retrieved registry value for any sign of the strings: “qemu”, “virtual”,\r\n“vmware”, “xen”. If you notice in the previous debugger screenshot, I’m running the sample inside a VMWare\r\nmachine, so to continue I will need to patch the return value of strstr function calls to return zero.\r\nOther checks are waiting:\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 26 of 30\n\nAs you can see, the malware tries to understand if it’s being debugged or executed inside a sandbox by trying to\r\nget a handle to modules sbiedll and dbghelp. If it’s able to detect one of these two libraries, it terminates the\r\nprocess and exits. \r\nFinally, The Payload!\r\nHaving passed all sorts of anti-analysis and anti-debugging checks, we finally reach the payload! Now, the\r\nmalware begins to reveal its secrets in memory.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 27 of 30\n\nWe can clearly see it’s a PE file, but it’s scrambled somehow. This code will be decoded and managed in memory\r\nwith a complex routine.\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 28 of 30\n\nDigging into this code will require more time and effort than the analyst will normally want to expend. Instead, we\r\ncan detonate the malware in our isolated environment and observe its execution. As we will see in the next post,\r\nthis will reveal that a new instance of svchost.exe is loaded into memory, which suggests some sort of process\r\ninjection. If you enjoyed this deep dive and would like to know when the next Going Deep post is available, just\r\nsubscribe to the SentinelOne blog newsletter!\r\nIOCs\r\nSample Hash 07e81dfc0a01356fd96f5b75efe3d1b1bc86ade4\r\nMITRE ATT\u0026CK\r\nSmoke Loader {S0226}\r\nVirtualization/Sandbox Evasion {T1497}\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 29 of 30\n\nSource: https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nhttps://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/\r\nPage 30 of 30\n\n https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/      \nA memory region with a PE file inside, mapped as Executable, Read, Write. This is definitely our injected file.\nWe can simply dump out this memory region to file, clean the junk before the MZ header and analyze its headers.\n   Page 6 of 30    \n\nje being_debugged This chunk of code checks if the process is attached to a debugger and, if it goes well, another XOR decoding stub\nstarts from address 0x00401000 , with buffer size 0x4E8 and XOR key 0xCB .\n  Page 14 of 30",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"references": [
		"https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/"
	],
	"report_names": [
		"going-deep-a-guide-to-reversing-smoke-loader-malware"
	],
	"threat_actors": [],
	"ts_created_at": 1775434254,
	"ts_updated_at": 1775791200,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/a4b89dcbdb8b4a38153db477f7c89ca1bca89f49.pdf",
		"text": "https://archive.orkl.eu/a4b89dcbdb8b4a38153db477f7c89ca1bca89f49.txt",
		"img": "https://archive.orkl.eu/a4b89dcbdb8b4a38153db477f7c89ca1bca89f49.jpg"
	}
}