Threat Intelligence & APTs • Windows

Regin Malware: Architecture of a Nation-State Stage-1 Loader and Virtual Filesystems

Analyze a stealthy Regin Stage 1 kernel loader driver, uncovering embedded XOR configurations, kernel module resolution, and staging mechanisms.

Initial context:

▪Regin, a sophisticated APT platform.
▪The driver is the first stage, just a loader.
▪The location of the next stage is unique and encrypted within the driver's body.
▪There are dozens of such samples, need to decrypt them all.

Executive Summary

This report analyzes a suspected Regin Stage 1 kernel-mode loader, a highly stealthy component associated with long-term advanced persistent threat (APT) espionage operations. The sample is a small (11.4 KB), 32-bit native Windows driver compiled in 2007 using Microsoft Visual C++ tooling. Its lack of version metadata, minimal static imports, and deliberate obfuscation indicate a strong emphasis on stealth, stability, and long-term persistence.

The driver’s DriverEntry routine initializes structured exception handling, disables unloading, and decrypts an embedded configuration stored in the .data section using a custom XOR-based routine. This configuration includes a registry path under HKLM\SYSTEM\CurrentControlSet\Control\RestoreList, which is decrypted only once using a runtime control flag to limit execution artifacts. Additional data elements include preallocated buffers, benign-looking Windows directory strings, and registry value names used during early-stage environment preparation.

The malware dynamically resolves critical kernel components by locating loaded modules such as ntoskrnl.exe and hal.dll, and adapts its behavior based on the operating system version using NtBuildNumber. Access to kernel internals, including the System Service Descriptor Table (SSDT), enables OS-independent execution without relying on static imports. Overall, the findings confirm this sample functions as a first-stage Regin kernel loader, focused on secure initialization, environment validation, and preparation for subsequent payload stages, reflecting a level of sophistication consistent with highly targeted espionage activity.

Description

The analyzed sample is a small Win32 PE executable (11.41 KB) compiled for 32-bit Windows systems. The file was built using Microsoft Macro Assembler (MASM) and linked with Microsoft Visual C++ 7.1, suggesting deliberate low-level development rather than standard high-level application code. Multiple cryptographic hashes were generated to uniquely identify the sample. The executable exhibits characteristics consistent with a lightweight loader component, including its minimal size and lack of overt functionality. These traits align with the behavior expected from the first-stage loader of the Regin malware platform, which is designed to operate stealthily and facilitate the decryption and in-memory loading of subsequent stages.

File

SHA-256f1d903251db466d35533c28e3c032b7212aa43c8d64ddf8c5521b43031e69e1e
decrypted file SHA-256:
23115663c967069cb482604700be9f7ed6df4ea1297d453debad1119b0f85b2e

Findings

PE Metadata

File Identification and Basic Properties:

PE Headers

Entry Point Information

Sections

Section NameVirtual AddressVirtual Size (bytes)Raw Size (bytes)EntropyMD5Chi-Square (Chi²)
.text0x00000280 (640)8,9048,9286.52
e9285367382d63f5e65445b78a688b5e
49,665.19
.data0x00002560 (9,568)8248327.33
57db72556a0e508de756687d48351a93
1,608.62
INIT0x000028A0 (10,400)5866084.86
288e1b4ff839ac154cb20074ca668049
13,850.11
.reloc0x00002B00 (11,008)6446723.16
8a24633536aa0a73063171a85bac00b4
68,508.31

Compilation and Time Stamp Information

Description:

When we look at the parts of the program we see that it is put together in an organized way. The part that has the code is pretty mixed up which is what we usually see in code that someone wrote by hand or tried to make very small. This kind of code is often used in the parts of the program that load things. The part that stores data is more mixed up which makes us think that it might have secret or packed up information like settings or another hidden program. There is also a part that starts things up which is not typical and it means that the bad program has its own way of getting started. The part that helps the program move to places on the computer is pretty simple, which is what we expect to see. The program and the bad program are the thing in this case the malware. The malware has its lifecycle. The parts of the malware, like the.text section and the.data section and the INIT section and the.reloc section are all important to understand how the malware works. Overall, the section layout and entropy distribution support the assessment that this sample functions as a minimal first-stage loader designed for stealth and controlled execution rather than direct malicious activity.

Static Analysis

Imports /Resolved / Referenced Kernel APIs

Function NameFlagResolution TypeOrdinalAddress (RVA)Thunk AddressModule
wcslen-implicit-0x00002A2C0x00002A2Cntoskrnl.exe
wcscpy-implicit-0x000029F80x000029F8ntoskrnl.exe
strrchr-implicit-0x00002A4C0x00002A4Cntoskrnl.exe
strncpy-implicit-0x00002A840x00002A84ntoskrnl.exe
strncmp-implicit-0x00002A720x00002A72ntoskrnl.exe
strchr-implicit-0x00002A420x00002A42ntoskrnl.exe
atoi-implicit-0x00002A7C0x00002A7Cntoskrnl.exe
_strnicmp-implicit-0x00002A360x00002A36ntoskrnl.exe
_snprintf-implicit-0x00002A9E0x00002A9Entoskrnl.exe
ZwQueryValueKeyximplicit-0x000029660x00002966ntoskrnl.exe
ZwQuerySystemInformationximplicit-0x00002A560x00002A56ntoskrnl.exe
ZwQueryInformationFileximplicit-0x00002A120x00002A12ntoskrnl.exe
ZwOpenKey-implicit-0x000029780x00002978ntoskrnl.exe
ZwCreateFile-implicit-0x00002A020x00002A02ntoskrnl.exe
ZwClose-implicit-0x0000295C0x0000295Cntoskrnl.exe
RtlUnwind-implicit-0x000029C60x000029C6ntoskrnl.exe
RtlInitAnsiString-implicit-0x000029A40x000029A4ntoskrnl.exe
RtlFreeUnicodeString-implicit-0x000029440x00002944ntoskrnl.exe
RtlAnsiStringToUnicodeString-implicit-0x000029840x00002984ntoskrnl.exe
NtBuildNumber-implicit-0x00002A8E0x00002A8Entoskrnl.exe
KeServiceDescriptorTableximplicit-0x00002AAA0x00002AAAntoskrnl.exe
KeQueryPerformanceCounter-implicit-0x00002AC60x00002AC6HAL.dll
ExFreePool-implicit-0x000029D20x000029D2ntoskrnl.exe
ExAllocatePoolWithTag-implicit-0x000029E00x000029E0ntoskrnl.exe

Hex editor

Fig 1 - MZ header in the beginning
Figure: Fig 1 - MZ header in the beginning Click to zoom ↗

Fig 1 - MZ header in the beginning

IDA Analysis

IDA recognized the entry point as DriverEntry.

Fig 2 - Entry point
Figure: Fig 2 - Entry point Click to zoom ↗

Fig 2 - Entry point

DriverEntry function (mcd.h) - The DriverEntry miniport driver routine is called when the miniport driver is loaded.

Fig 3 - msdn DriverEntry
Figure: Fig 3 - msdn DriverEntry Click to zoom ↗

Fig 3 - msdn DriverEntry

▪One is a pointer to structure of type PDRIVER_OBJECT
▪Pointer to structure of type PUNICODE_STRING and it’s a registry path. that is unique for this driver.

Beginning of the code goes immediately goes into loop. XORing some bytes that IDA renamed as SourceString.

Fig 4 - xor SourceString
Figure: Fig 4 - xor SourceString Click to zoom ↗

Fig 4 - xor SourceString

Following SourceString Encrypted data followed by more encrypted data.

Fig 5 - Encrypted data conversion to Array
Figure: Fig 5 - Encrypted data conversion to Array Click to zoom ↗

Fig 5 - Encrypted data conversion to Array

Going into sub_106C0 next call is using another buffer.

.text:000106E0 push offset unk_12663 at this address

.data:00012663 unk_12663 db 0D3h ; DATA XREF: sub_106C0+20↑o

Fig 6 - address 0x106E0 calling buffer.
Figure: Fig 6 - address 0x106E0 calling buffer. Click to zoom ↗

Fig 6 - address 0x106E0 calling buffer.

Almost every important information in the code is using one or another buffer that looks like encrypted strings.

Decryption

decryption program template:

PYTHON
#!/usr/bin/python3

from decoder_core import *
from Crypto.Cipher import *

class Decoder(TrainingDecoder):
    def __init__(self):
        # MD5 hash of the correctly decrypted file
        super().__init__()

    def decode(self):
        # self.data is a bytearray with the contents of the file
    
        # 1. Set the proper file offset of the encrypted block
        encrypted_block_offset = REPLACE_WITH_THE_SOLUTION
        # 2. Set the proper length of the encrypted block
        encrypted_block_len = REPLACE_WITH_THE_SOLUTION
        # 3. Set the proper file offset of the decryption key
        key_offset = REPLACE_WITH_THE_SOLUTION
        # 4. Set the proper length of the decryption key
        key_len = REPLACE_WITH_THE_SOLUTION

        key = self.data[key_offset:key_offset+key_len]
        for i in range(0,encrypted_block_len):
            self.data[encrypted_block_offset+i] = ( self.data[encrypted_block_offset+i] ^ key[i%key_len] ^ i ) & 0xFF

        # Check if the results are correct
        self.check_results()

# Create the decryptor object. Automation happens in __init__()
Decoder()
Fig 7 - Counter loop
Figure: Fig 7 - Counter loop Click to zoom ↗

Fig 7 - Counter loop

The register ESI is used as the loop counter, initialized to zero and incremented on each iteration until it reaches the upper bound of 0x2EE, defining the size of the decrypted buffer.

encrypted_block_len = 0x2EE

Convert Virtual address to the file offset

IDA

Fig 8 - encrypted block offset
Figure: Fig 8 - encrypted block offset Click to zoom ↗

Fig 8 - encrypted block offset

encrypted_block_offset = 0x2569

The XOR key in this routine is a small rolling key stored at byte_1080A, combined with the loop counter (ESI).

Fig 9 - byte_1080A
Figure: Fig 9 - byte_1080A Click to zoom ↗

Fig 9 - byte_1080A

Fig 10 - key offset
Figure: Fig 10 - key offset Click to zoom ↗

Fig 10 - key offset

key_offset = 0x80A

Fig 11 - Converting 7 to binaryis 111b
Figure: Fig 11 - Converting 7 to binaryis 111b Click to zoom ↗

Fig 11 - Converting 7 to binaryis 111b

We’re going to have values starting from 0 and ending in 7, including 7.

So the length of the key is 8.

Fig 12 - Successfully decrypted
Figure: Fig 12 - Successfully decrypted Click to zoom ↗

Fig 12 - Successfully decrypted

File Differences

Fig 13 - First difference
Figure: Fig 13 - First difference Click to zoom ↗

Fig 13 - First difference

Fig 14 - Second difference
Figure: Fig 14 - Second difference Click to zoom ↗

Fig 14 - Second difference

Decrypted File Analysis

Basic Properties

Sections

Section NameVirtual AddressVirtual Size (bytes)Raw Size (bytes)EntropyMD5Chi-Square (Chi²)
.text0x00000280 (640)8,9048,9286.52
e9285367382d63f5e65445b78a688b5e
49,665.19
.data0x00002560 (9,568)8248321.36
22b3dd607e91c85ddef807a0441042f4
154,013.02
INIT0x000028A0 (10,400)5866084.86
288e1b4ff839ac154cb20074ca668049
13,850.11
.reloc0x00002B00 (11,008)6446723.16
8a24633536aa0a73063171a85bac00b4
68,508.31

MITRE ATT&CK Tactics and Techniques

Fig 15 - MITRE
Figure: Fig 15 - MITRE Click to zoom ↗

Fig 15 - MITRE

Sample Comparison – File Metadata and Hashes

Attribute06665b96e293b23acc80451abb413e5006665b96e293b23acc80451abb413e50.dec
MD5
06665b96e293b23acc80451abb413e50
f8d94c86b15b62d5d76b9846fad5ae59
SHA-1
9f0dc086875e6b06efe6bb3aadf049ce00f9e486
41a24d8697c06890cd0cf96ecb4ee30904ef5087
SHA-256
f1d903251db466d35533c28e3c032b7212aa43c8d64ddf8c5521b43031e69e1e
23115663c967069cb482604700be9f7ed6df4ea1297d453debad1119b0f85b2e
Vhash0140466d7e5519z16z17xz0140466d1e5519z16z17xz
Authentihash
139c80dde62a325f0ae2e2370b801e91caecef57dcf0ac2be7444e99341b9c92
8ccd332ab1c6585caafbb58fb31f03be285c8342ce32ff4a8df0120e50c72163
Imphash
a9c1041cccb87f4a7ba3b7048d4e8ad7
a9c1041cccb87f4a7ba3b7048d4e8ad7
Rich PE Header Hash
90c92121c7e7a342a64cd5609234ece1
90c92121c7e7a342a64cd5609234ece1
SSDEEP
192:za3N5H44rW3ias7dUFdELQcsafdPvlw/BZUGJdMr0KqOMwb:zkasBUFuVsafVvlw/BLMcOHb
192:za3N5H44rW3ias7dUFdELQcsafdPvlw/BZUGJdMr0KqOMGb:zkasBUFuVsafVvlw/BLMcO9b
TLSH
T1C5325D22B6C140F5DA59D8F2AD3A7B39247FAD21633FE5D283340D691D6A841A73708F
T158325C52B6C040F5DB59D8F29D277B3924BE9E21632BE6D387340D680D9A942A73708F
File TypeWin32 EXEWin32 EXE
MagicPE32 executable (Intel 80386, MS Windows)PE32 executable (Intel 80386, MS Windows)
Architecturex86 (32-bit)x86 (32-bit)
File Size11.41 KB (11,680 bytes)11.41 KB (11,680 bytes)
TrID IdentificationWin32 DLL (34.3%), Win32 EXE (23.4%), Windows Icons Library (10.7%), OS/2 EXE (10.5%), Generic Win/DOS EXE (10.4%)Same
Compiler / LinkerMASM 7.01.4035 / MS Linker 7.10.4035MASM 7.01.4035 / MS Linker 7.10.4035
MagikaPEBINPEBIN
PEiD PackerMicrosoft Visual C++ v7.1 EXEMicrosoft Visual C++ v7.1 EXE

Imported Kernel Functions

ModuleImported Function
ntoskrnl.exe_snprintf
ntoskrnl.exe_strnicmp
ntoskrnl.exeatoi
ntoskrnl.exeExAllocatePoolWithTag
ntoskrnl.exeExFreePool
ntoskrnl.exeKeServiceDescriptorTable
ntoskrnl.exeNtBuildNumber
ntoskrnl.exeRtlAnsiStringToUnicodeString
ntoskrnl.exeRtlFreeUnicodeString
ntoskrnl.exeRtlInitAnsiString
HAL.dllKeQueryPerformanceCounter

IDA Analysis of Decrypted file

Fig 16 - Loading decrypted binary in IDA
Figure: Fig 16 - Loading decrypted binary in IDA Click to zoom ↗

Fig 16 - Loading decrypted binary in IDA

Same Function DriverEntry , First block of data that was encrypted is now a readable registry location

Fig 17 - Registry location
Figure: Fig 17 - Registry location Click to zoom ↗

Fig 17 - Registry location

We have two folders name supposedly two folder’s name

Fig 18 - recognizable strings
Figure: Fig 18 - recognizable strings Click to zoom ↗

Fig 18 - recognizable strings

Both strings are used as an arguments to function

Fig 19 - arguments
Figure: Fig 19 - arguments Click to zoom ↗

Fig 19 - arguments

Several templates like temp, windows

Fig 20 - templates(temp,windows)
Figure: Fig 20 - templates(temp,windows) Click to zoom ↗

Fig 20 - templates(temp,windows)

This routine dynamically resolves kernel system call addresses by locating the base of ntoskrnl.exe and inspecting the System Service Descriptor Table. Execution is guarded to occur only once and includes explicit operating system version checks using NtBuildNumber, allowing the malware to adapt to different kernel layouts. By resolving system calls such as extended attribute handlers at runtime rather than importing them statically, the loader avoids detection and maintains compatibility across Windows versions. This behavior is characteristic of Regin’s highly defensive kernel-level architecture, where low-level system interaction is carefully abstracted and controlled.

Fig 21 - Function call
Figure: Fig 21 - Function call Click to zoom ↗

Fig 21 - Function call

Copied