Osiris dropper found using process doppelgänging
Process Doppelgänging, a new technique of impersonating a process, was published last year at Black Hat conference. After some time, a ransomware named SynAck was discovered that adopted it for malicious purposes. However, this technique is still pretty rare in wild. So, it was an interesting surprise to notice it in a dropper of the Osiris banking Trojan (a new version of the infamous Kronos).
The authors of this dropper were skilled, and they added several other tricks to spice the whole thing up. In this post, we will have a closer look at the loader’s implementation.
- 5e6764534b3a1e4d3abacc4810b6985d – original sample (stage 1)
Osiris is loaded in three steps:
The dropper creates a new process and injects the content inside:
Interestingly, when we look into the modules loaded in the process space of the injector, we can see an additional copy of NTDLL:
This is a well-known technique that some malware authors use in order to evade monitoring applications and hide the API calls that they used.
When we examine closely what the functions are called from that additional NTDLL, we find more interesting details. It calls several APIs related to NTFS transactions. It was easy to guess that the technique of Process Doppelgänging, which relies on this mechanism, was applied here.
Loading additional NTDLL
NTDLL is a special, low-level DLL. Basically, it is just a wrapper around syscalls. It does not have any dependencies from other DLLs in the system. Thanks to this, it can be loaded conveniently, without the need to fill its import table.
Other system DLLs, such as Kernel32, rely heavily on functions exported from NTDLL. This is why many user-land monitoring tools hook and intercept the functions exported by NTDLL: to watch what functions are being called and check if the process does not display any suspicious activity.
Of course malware authors know about this, so sometimes, in order to fool this mechanism, they load their own, fresh and unhooked copy of NTDLL from the disk. There are several ways to implement this. Let’s have a look how the authors of the Osiris dropper did it.
Looking at the memory mapping, we see that the NTDLL is loaded as an image, just like other DLLs. However, it was not loaded by a typical
LoadLibrary function, nor even by its low-level version from NTDLL,
LdrLoadDll. Instead, the authors decided to load the file as a section, using following functions:
ntdll.NtCreateFile– to open the ntdll.dll file
ntdll.NtCreateSection– to create a section out of this file
ntdll.ZwMapViewOfSection– to map this section into the process address space
This was smart move because the DLL looks like it was loaded in a typical way, and yet, if we monitor the
LdrLoadDll function, we see nothing suspicious.
Implementation of Process Doppelgänging
In order to make their injection more stealthy, the authors took the original implementation of Process Doppelgänging a step further and used only low-level APIs. So, instead of calling the convenient wrappers from Kernel32, for most of the functions they called their equivalents from NTDLL. Moreover, they used the aforementioned custom copy of this DLL.
First, they created a new suspended process. This is the process into which the payload will be injected. In this particular case, the function was called from kernel32.dll:
Process Doppelgänging then starts from creating a new transaction, within which a new file is created. The original implementation used
CreateFileTransacted from Kernel32 for this purpose. But this is not the case here.
First, a function
ZwCreateTransaction from a NTDLL is called. Then, instead of
CreateFileTransacted, the authors open the transacted file by
RtlSetCurrentTransaction along with
ZwCreateFile (the created file is %TEMP%\Liebert.bmp). Then, the dropper writes the content of the new executable to the file—the second stage of the malware. Analogically,
ZwWriteFile is used.
We can see that the buffer that is being written contains the new PE file: the second stage payload. Typically, for the Process Doppelgänging technique, the file is visible only within the transaction and cannot be opened by other processes, such as AV scanners.
After the file inside the transaction is created, it will be used to create a buffer in special format, called a section. The function that can do it is available only via low-level API: ZwCreateSection/NtCreateSection.
After the section is created, the file that was created is no longer needed. The transaction gets rolled back (by
ZwRollbackTransaction), and the changes to the file are never saved on the disk.
Further, the created section will be used to load a PE file. After writing the payload into memory and setting the necessary patches, such as Entry Point redirection, the process is resumed:
Second stage loader
The next layer (8d58c731f61afe74e9f450cc1c7987be) is not the core yet, but the next stage of the loader. The way it loads the final payload is much simpler, yet still not trivial. The code of Osiris core is unpacked piece by piece and manually loaded along with its dependencies into a newly allocated memory area within the loader process.
After this self-injection, the loader jumps into the payload’s entry point:
The interesting thing is that the entry point of the application is different than the entry point saved in the header. So, if we dump the payload and try to run it interdependently, we will not get the same code executed. This is an interesting technique used to misguide researchers.
This is the entry point that was set in the headers is at RVA 0x26840:
The call leads to a function that makes the application go in an infinite sleep loop:
The real entry point, from which the execution of the malware should start, is at 0x25386, and it is known only to the loader.
Comparison with Kronos loader
A similar trick using a hidden entry point was used by the original Kronos (2a550956263a22991c34f076f3160b49).
In the case of Kronos, the final payload is injected into svchost. The execution is redirected to the core by patching the entry point in the svchost:
In this case, the entry point within the payload is at RVA 0x13B90, while the entry point saved in the payload’s header (d8425578fc2d84513f1f22d3d518e3c3) is at 0x15002.
The code at the real Kronos entry point displays similarities with the analogical point in Osiris. Yet, we can see they are not identical:
The implementation of Process Dopplegänging used in the first stage loader is clean and professional. The author used a relatively new technique and made the best out of it by composing it with other known tricks. The precision used here reminds us of the code used in the original Kronos. However, we can’t be sure if the first layer is written by the same author as the core bot. Malware distributors often use third-party crypters to pack their malware. The second stage is more tightly coupled with the payload, and here we can say with more confidence that this layer was prepared along with the core.