Smoke Loader Malware – An Easy Guide 101
By SentinelOne
Published: 2019-11-21 · Archived: 2026-04-05 22:33:11 UTC
Working in infosec and supporting clients and SOCs has always exposed me to a huge number of alerts and
incidents. Some of these are more interesting than others. Recently we stumbled across a particular sample of
Smoke Loader malware. Smoke Loader has been in-the-wild since circa 2013 and is often used to distribute
additional malicious components or artifacts. While the sample is not new, it did prove to be a good opportunity to
revisit this threat and walk through some of the internals.
This alert was raised against a suspicious file, classified as a trojan, that was killed and quarantined. What raised
my curiosity was the number of detections over only a few hours, always from the same workstation, and only
from the same user.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 1 of 30
Knowing that the threat was killed without doing any harm, I decided to dig into it a bit more. Just looking at the
SentinelOne console, I was able to see :
The full path where the detection was made.
The associated risk level is High: this implies that it’s a positive detection.
File unique hash that can be tested for any public Indicators of Compromise (IoC).
In order to do a walk-through of malware reverse engineering steps, I downloaded the threat file and started the
analysis.
First Layer: A Packed VB Win32 Program
With the downloaded file in my pocket, I quickly fired up an isolated analysis machine equipped with the Flare
tools and started to investigate. At first glance, the sample appears to be a Visual Basic program leveraging Win32
APIs.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 2 of 30
Let’s see what else we can get from its headers. Looks like pretty standard information, confirming a Visual Basic
program due to its import table.
We can spot some rude and folkloristic words inside the binary strings, some of which make me think of a
regional dialect of southern Italy.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 3 of 30
With a bit of experience, we can safely assume that the file is packed with an external layer of Visual Basic that
tries to stop, or at least slow down, static analysis. But what about its runtime behavior?
Observing the sample during runtime, we can observe the process injection: this behaviour is common for VB
packers and luckily for us, is often trivial to defeat.
Defeating Visual Basic Packer
We won’t spend too much time on this: there are plenty of resources on how to unpack such packers and I highly
recommend the OALabs Youtube video tutorials. It’s necessary and enough to put a breakpoint at
CreateProcessInternalW API inside the debugger to stop the execution at the right time.
At 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
search the entire memory map for a clue: I decided to search for the “DOS” substring that can usually be found as
part of the “This program cannot be run in DOS mode” string within the PE.
We got plenty of results for the string, whose hex is 44 4F 53 .
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 4 of 30
However, we are particularly interested in just a few locations. Usually the executable is loaded at 0x00400000
address, so the result we had at 0x0040006C looks like our executable itself.
Things become particularly interesting at 0x002F0094 , which we can follow in the memory dump.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 5 of 30
A memory region with a PE file inside, mapped as Executable, Read, Write. This is definitely our injected file.
We can simply dump out this memory region to file, clean the junk before the MZ header and analyze its headers.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 6 of 30
It seems like a legitimate executable, but something is going on: no imports at all. This is interesting!
Second layer: Static Analysis
When we load this new executable in IDA Pro, this was the only chunk of code that was disassembled.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 7 of 30
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 8 of 30
Here we recognize that it’s a XOR loop that will decode, from address 0x00401567 , a blob of code with the size
of 0xCD bytes with a XOR key equal to 0xCB . At the end of the loop, the same starting address 0x00401567 is
pushed onto the stack and with the RET instruction the program flow will be branched over there.
Decoding the Buffer
With a little bit of IDA scripting, we can XOR the encrypted buffer and move forward in the analysis.
After de-xoring the buffer we are met with a mixture of anti-disassembly and anti-debug techniques. It is now
possible to map the purpose of the code blocks.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 9 of 30
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 10 of 30
Inside this code, we can observe plenty of tricks that try to fool the disassembly flow. A few examples:
Abusing CALL and RET instruction to mess up function boundaries. The CALL instruction will push the
return address onto the stack. The RET instruction will then pop off this address into the EIP register,
which effectively makes these two instructions useless. However, these few opcodes make IDA think that
the function ends there and that the next instruction is the end of another function.
Abusing branch instructions that do nothing: CALL
and at : POP . It’s the
easiest way to get an address inside the EIP register and so to control the program’s flow.
Abusing JMP instructions: simply putting a lot of JMP instructions that will jump back and forth only to
make the life of the analyst miserable.
Obfuscated with these techniques, the malware checks if it’s being debugged. The code that implements this check
is nothing complicated: it queries certain flags of the PEB in order to spot the debugger, IsDebuggerPresent .
mov eax, fs:[30h] ; Process Environment Block
cmp b [eax+2], 0 ; check BeingDebugged
jne being_debugged
As said, this code is heavily obfuscated with junk jumps and a lot of instructions with the only purpose of
increasing complexity of analysis. As an example, this little chunk of code is the final part of a dozen lines of code
used to put value 0x30 inside the EAX register with the purpose of locating the PEB.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 11 of 30
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 12 of 30
At the end of this function, we spot another XOR stub decoding routine that will decode another blob of code and,
after that, redirect the execution flow. Decoding will start at address 0x004014E8 , with a buffer size of 0x7F and
the same XOR key 0xCB .
As before, we can proceed in the static analysis, manually decoding this buffer with the same script.
But wait! Here we go again, another anti-debugging trick, NtGlobalFlag check:
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 13 of 30
mov eax, fs:[30h] ; Process Environment Block
mov al, [eax+68h] ; NtGlobalFlag
and al, 70h
cmp al, 70h
je being_debugged
This chunk of code checks if the process is attached to a debugger and, if it goes well, another XOR decoding stub
starts from address 0x00401000 , with buffer size 0x4E8 and XOR key 0xCB .
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 14 of 30
After decoding the new buffer, we need to face another anti-disassembly trick; namely, JMP instructions with a
constant value. This is the most common trick used by malware to fool static analysis. Basically, it creates jumps
into a new location plus one or a few bytes. It results in an erroneous interpretation of the opcode by the
disassembler. It’s trivial to defeat but time intensive.
IAT Resolution at Runtime
At address 0x00401000 there’s a simple call to another address 0x00401049 , where it starts to become
interesting as the malware appears to dynamically resolve its imports. As we noted before, the binary header
analysis showed no imports at all. With this code, from the PEB location found earlier, the malware finds the base
address of ntdll.dll .
But how is this happening? In all recent Windows versions, the GS register points to a data structure called the
Thread Environment Block (TEB). At offset 0x30 of the TEB, there’s another data structure, namely the Process
Environment Block (PEB) we saw earlier.
We can inspect these data structures with the help of Microsoft public symbols and WinDBG.
With the same tools we can inspect the PEB too:
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 15 of 30
With the third instruction, we are following the offset 0x0C , the _PEB_LDR_DATA structure. This structure is
fairly important because it contains a pointer, InInitializationOrderModuleList , to the head of a double-linked
list that contains the NTDLL loader data structures for the loaded modules.
Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. If we inspect this structure, we get
the DLLBase .
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 16 of 30
Looking at this inside the debugger helps to shed some light:
We got the base address of module ntdll.dll into EDX register, because this is the first module loaded into
every process in a Windows environment. We have added comments and renamed select functions to clear up
some of the observables.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 17 of 30
After the malware gets the ntdll.dll base address, it loops twice calling a function named
DecryptionFunction . This function receives as input a dword, which here is a hash. As we’re going to see, it
will walk the Export Address Table of the module searching for a particular function with the name matching to
the passed hash. With this first loop, the malware finds two functions: strstr and LdrGetDllHandle .
As an example, in this particular case, the DecryptionFunction is walking, as we explained before for
ntdll.dll , the module kernel32.dll , retrieving the address of VirtualAlloc put inside the EAX register as
return value.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 18 of 30
DecryptionFunction
After fully disassembling the function(s) we have the following:
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 19 of 30
The hashes of the resolved and imported functions appear as follows:
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 20 of 30
After using the debugger to step into the loops of the DecryptionFunction , we were able to find what functions
the malware uses next.
This part of the executable almost works the same way through libraries and functions. I highly suggest looking at
the disassembly line by line to understand the inner working of the Windows Internal Subsystem and API calls.
Another interesting trick to be even more stealthy is the use of stack strings to build calls to LoadLibraryA . The
secret here is that, by definition, the CALL instruction pushes the next address onto the stack as the return
address. But this address is an ASCII null terminated string that will be an argument for the next LoadLibraryA
call. Here you can see how it loads two libraries: advapi32 and user32 .
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 21 of 30
Immediately after resolving the imports, the malware sleeps for 10 seconds and then retrieves a filename via
GetModuleFileNameA .
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 22 of 30
Interestingly, the image above also shows how the code checks if its own name contains the string “sample” and if
so consequently terminates itself. You can see how the call to the strstr function is built and how the previous
push is given to check for the “sample” string.
It’s a simple anti-analysis technique that might easily catch you out. Protip: do not call your sample “sample”. 🙂
Next, the malware performs another check via GetVolumeInformationA , which is thoroughly documented in
MSDN. Let’s look inside this call to understand its purpose.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 23 of 30
From the above disassembly, we can see that it retrieves the volume serial number and checks if it’s equal to some
two serials. It then opens a registry key with RegOpenKeyExA , pushing one of the arguments with the same CALL
technique. It then obtains the value of the registry key, closes the handle, and converts the value to lowercase
before proceeding.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 24 of 30
It looks clear when you see it in the debugger.
With this string saved somewhere in memory, the code goes on to perform some other checks trying to find any
sign of running inside a virtual environment.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 25 of 30
As part of the anti-virtual machine checks, it initializes a 4 cycle loop; during this loop it performs a call to the
strstr function to search inside the retrieved registry value for any sign of the strings: “qemu”, “virtual”,
“vmware”, “xen”. If you notice in the previous debugger screenshot, I’m running the sample inside a VMWare
machine, so to continue I will need to patch the return value of strstr function calls to return zero.
Other checks are waiting:
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 26 of 30
As you can see, the malware tries to understand if it’s being debugged or executed inside a sandbox by trying to
get a handle to modules sbiedll and dbghelp. If it’s able to detect one of these two libraries, it terminates the
process and exits.
Finally, The Payload!
Having passed all sorts of anti-analysis and anti-debugging checks, we finally reach the payload! Now, the
malware begins to reveal its secrets in memory.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 27 of 30
We can clearly see it’s a PE file, but it’s scrambled somehow. This code will be decoded and managed in memory
with a complex routine.
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 28 of 30
Digging into this code will require more time and effort than the analyst will normally want to expend. Instead, we
can detonate the malware in our isolated environment and observe its execution. As we will see in the next post,
this will reveal that a new instance of svchost.exe is loaded into memory, which suggests some sort of process
injection. If you enjoyed this deep dive and would like to know when the next Going Deep post is available, just
subscribe to the SentinelOne blog newsletter!
IOCs
Sample Hash 07e81dfc0a01356fd96f5b75efe3d1b1bc86ade4
MITRE ATT&CK
Smoke Loader {S0226}
Virtualization/Sandbox Evasion {T1497}
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 29 of 30
Source: https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
Page 30 of 30
https://www.sentinelone.com/blog/going-deep-a-guide-to-reversing-smoke-loader-malware/
A memory region with a PE file inside, mapped as Executable, Read, Write. This is definitely our injected file.
We can simply dump out this memory region to file, clean the junk before the MZ header and analyze its headers.
Page 6 of 30
je being_debugged This chunk of code checks if the process is attached to a debugger and, if it goes well, another XOR decoding stub
starts from address 0x00401000 , with buffer size 0x4E8 and XOR key 0xCB .
Page 14 of 30