前言
本人小白一枚,第一次发帖,写的不好求轻喷{:1_889:}
样本来源https://www.52pojie.cn/thread-2104234-1-1.html
样本概述
样本为Sality病毒变种,使用了强壳,并使用LOADPE工具对PE头进行了修改。
注册表操作
文件系统操作
操作包括:写入system.ini、在Temp下创建自身副本、在C盘根目录下创建自身副本与autorun.inf,并修改文件属性与注册表键值
字符串提取
通过对比提取的文件字符串和内存字符串可知,样本是一个自解密的程序。
查看内存字符串可以猜测
[]样本解压出一个带UPX壳段PE文件
[]样本存在网络通信
- 样本存在动态解析API与注入
脱壳
(这真是令人头大{:301_983:} )
壳段分析
DIE查壳,显示其DOS头经过了修改。
分析发现,DOS头被压缩,e_lfanew指向0xC
PEID查壳(当时DIE查完就硬着头皮上了)这个壳段具有 Argument 与Safeguard 的特征
载入IDA分析,start入口通过 push ebx的方式,实现流程控制。实际功能类似于jmp 0x401291。
.text:00401040 call $+5
.text:00401045
.text:00401045 loc_401045: ; DATA XREF: start+6↓o
.text:00401045 pop ebx
.text:00401046 add ebx, (offset loc_401291 - offset loc_401045)
.text:0040104C push ebx
.text:0040104D xor al, bh
.text:0040104F retn
将0x401291地址强制转换为代码。
可以看到,整个壳段存在很多用于混淆的无实际意义的操作码与流程控制。看静态是不可能看明白的,那么直接让程序跑起来。
.text:00401291 loc_401291: ; DATA XREF: start+6↑o
.text:00401291 jbe short loc_40129C
.text:00401293 mov ebp, ebx
.text:00401295 rep imul ebp, edi, 253EC06Dh
.text:0040129C
.text:0040129C loc_40129C: ; CODE XREF: .text:loc_401291↑j
.text:0040129C jno short loc_4012A7
.text:0040129E imul ecx, 13791B8Bh
.text:004012A4 mov ch, 31h ; '1'
.text:004012A7
.text:004012A7 loc_4012A7: ; CODE XREF: .text:loc_40129C↑j
.text:004012A7 cmp edi, 0FC83h
.text:004012AD jz short loc_4012B5
.text:004012AF lea esi, ds:5C3E3CE1h
.text:004012B5
.text:004012B5 loc_4012B5: ; CODE XREF: .text:004012AD↑j
.text:004012B5 mov dh, 0F4h
.text:004012B8 mov edi, ecx
.text:004012BA and esi, 0
.text:004012BD
.text:004012BD loc_4012BD: ; CODE XREF: .text:00401306↓j
.text:004012BD test ebx, ecx
.text:004012BF jb short loc_4012CA
.text:004012C1 mov ah, dh
.text:004012C3 sbb al, 6Fh ; 'o'
.text:004012C5 mov ebp, 79C9475Dh
回想到在字符串中看到UPX字段与DOS头特征,怀疑其自解压出了一个PE文件。尝试在内存中找线索,果真找到了一个完整的PE文件
在PE魔术头位置下硬件写入断点,重新载入恶意样本,程序成功断在了地址0x402030。这是一个小LOOP,以eax作为计数器,进行解密操作,每次解密8字节
00402030 <1. | 892E | mov dword ptr ds:[esi],ebp
....
00402086 | 3D C1000000 | cmp eax,C1 |
0040208B | 73 05 | jae 1.402092 |
0040208D | E9 87FAFFFF | jmp 1.401B19 |
经过0x1C次循环后,成功得到 00 00 00 00 4D 5A 90 00
单个qword解密循环外层有一个巨大的LOOP,用edi作为计数器,根据最后的ret猜测,这一整个LOOP完成了对真实程序的解密与写入。
0040210A | 81FF E2FE0000 | cmp edi,FEE2 |
00402110 | 0F8C D8F9FFFF | jl 1.401AEE |
00402116 | C3 | ret |
F4执行到ret,内存里的PE文件完成解密。
再单步运行,可以看到后续程序进入一个校验PE头的函数,随后调用 LoadLibrary 与 GetProcAddrss API,并创建一个名为purity_control_90833 的内存映射。
“壳在完成 0xFEE2 字节的解密后,并未立即跳转执行,而是通过命名内存映射(purity_control_90833)将载荷映射到进程空间。随后解析出 LoadLibrary 地址。这种手法(FileMapping + 动态 API 解析)是高级木马(如 Gh0st 变种或远控后门)常用的内存执行技术,旨在规避基于文件的静态查杀。”
内存Dump
壳将解密后的 PE 载荷通过内存映射对象(名为 purity_control_90833)进行映射,该映射对象即为恶意代码本体
使用工具从运行内存中dump出PE文件。用DIE查壳,发现其存在 UPX 2.02 ,Characteristics = 0x101E ,说明该样本可以被重定位,可能会有进程注入等操作
进行upx脱壳后,程序载入IDA,伪代码已经清晰可读
样本分析
对一些常用函数进行rename处理。样本中存在多处长sleep用于规避沙箱与周期性运行。
start入口
init初始化 -> sub_40E6F0
函数逻辑:
修改注册表项(sub_40DC56) --> 动态解析网络API(MPR、WININET.DLL) --> 修改注册表 --> 生成HOSTshort --> 创建文件内存映射(purity_control_90833)--> 生成随机驱动名
修改注册表
| 注册表路径 |
键值名称 |
写入数据 |
恶意目的 |
| HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced |
Hidden |
2 (REG_DWORD) |
显示隐藏文件和系统文件,干扰用户视觉,为隐藏副本文件提供掩护。 |
| HKLM\SOFTWARE\Microsoft\Security Center<br>以及<br>HKLM\SOFTWARE\Microsoft\Security Center\Svc |
循环写入以下7个值:<br>AntiVirusOverride<br>AntiVirusDisableNotify<br>FirewallDisableNotify<br>FirewallOverride<br>UpdatesDisableNotify<br>UacDisableNotify<br>AntiSpywareOverride |
空值或 0(禁用状态) |
屏蔽 Windows 安全中心各项监控(杀毒、防火墙、更新、UAC 等),防止系统向用户发出安全告警。 |
| HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings |
GlobalUserOffline |
0 (REG_DWORD) |
强制系统保持在线状态,确保与远程C2通信不被阻断。 |
| HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System |
EnableLUA |
0 (REG_DWORD) |
禁用 UAC(用户账户控制),实现静默提权,避免后续敏感操作弹窗。 |
| HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\Firewall\AuthorizedApplications\List |
当前恶意进程完整路径 |
%s:*:Enabled:ipsec |
将自身加入防火墙白名单,放行所有端口和IP,确保外联不被拦截。 |
| HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\Firewall\StandardProfile |
EnableFirewall |
0 (REG_DWORD) |
直接禁用 Windows 防火墙。 |
| 同上 |
DoNotAllowExceptions |
0 (REG_DWORD) |
关闭“不允许例外”策略,确保放行规则生效。 |
| 同上 |
DisableNotifications |
1 (REG_DWORD) |
禁用防火墙警告通知,阻止系统弹出网络异常提醒。 |
动态解析敏感网络API
hModule = LoadLibraryA(off_481494); // MPR
if ( hModule )
{
dword_4140BC = GetProcAddress(hModule, off_481664); // WNetEnumResourceA
dword_4140B4 = GetProcAddress(hModule, off_481668); // WNetOpenEnumA
dword_414010 = GetProcAddress(hModule, off_48166C); // WNetCloseEnum
}
LibraryA = LoadLibraryA(off_4816C8); // WININET.DLL
if ( LibraryA )
{
dword_41406C = GetProcAddress(LibraryA, off_4816D8);// InternetCloseHandle
dword_41400C = GetProcAddress(LibraryA, off_4816D0);// InternetReadFile
dword_4140A0 = GetProcAddress(LibraryA, off_4816D4);// InternetOpenUrlA
dword_4140A8 = GetProcAddress(LibraryA, off_4816CC);// InternetOpenA
}
生成hostshort
获取计算机名保存到 string,如果 string > 2 ,则生成 hostshort 用于后续远程连接
GetComputerNameA(String1, Data);
if ( lstrlenA(String1) > 2 )
{
v6 = String1[0];
hostshort = String1[lstrlenA(String1) - 1] * v6 + 2199;
}
生成内存文件映射
动态解析完成后,会创建内存映射文件对象,便于不同进程之间通过该名称共享内存映射文件对象。
hFileMappingObject = CreateFileMappingA(0xFFFFFFFF, 0, 4u, 0, 0x15400u, lpName);// lpname = "purity_control_90833"
生成特殊注册表键值
sub_401B0E(&dword_475C58);
根据获取到的用户名,进行计算。如果用户名长度小于 2 ,则在用户名尾部添加"monga_bonga"。根据获取到的用户名进行计算,得到新的 string,生成一个 Software\用户名\计算结果 组成的注册表键,并写入键值。作为后续调用的唯一标识符
生成随机数写入SYSTEM.INI
如果未能成功获得唯一标识符
if ( !lstrlenA(&::String) )
sub_4059DE(); // 计算随机数保存的SYSTEM.INI
则会调用 GetTickCount() 获取系统启动后的毫秒数并存储在 Target 中。
随后会尝试读取SYSTEM.INI中的指定字符串与键值。
如果没有则生成一个随机数,与指定键值一起格式化成字符串,写回SYSTEM.INI。用于后续读取和调用
生成随机文件名
从SYSTEM.INI读取DEVICEMB后面的值,如果非数字则重新一个随机数。这个值用于计算获得随机文件名。
将生成的随机文件名和路径拼接“C:\Windows\system32\drivers\<rand_Name>.sys”,保存到内存地址中并返回这个地址。
线程一:进程注入 -> sub_40D570
线程用于分配内存、动态加载api、进程注入
; byte_4856B8 (48字节 shellcode)
004856B8 E8 00 00 00 00 call $+5 ; 获取当前EIP
004856BD 5D pop ebp ; ebp = 当前地址
004856BE 81 ED 05 10 40 00 sub ebp, 401005h ; ebp = 基址
004856C4 8D 85 2F 10 40 00 lea eax, [ebp+40102Fh] ; eax = API哈希表地址
004856CA 50 push eax
004856CB 6A 00 push 0
004856CD 6A 00 push 0
004856CF FF 95 27 10 40 00 call [ebp+401027h] ; 调用某函数指针
004856D5 6A 00 push 0
004856D7 FF 95 2B 10 40 00 call [ebp+40102Bh] ; 调用某函数指针
004856DD EB F6 jmp $-8 ; 无限循环
004856DF 22 22 22 22 ; 自定义Hash1: 0x22222222 (LoadLibraryExA)
004856E3 33 33 33 33 ; 自定义Hash2: 0x33333333 (GetProcAddress)
004856E7 69 00 00 00 00 ; 自定义Hash3: 0x00000069 (其他API)
sub_40D2B0
1、通过 CreateToolhelp32Snapshot + Process32First / Process32Next遍历系统中所有进程(跳过进程 ID ≤ 10 的系统伪进程)。
2、构造唯一互斥体名,用"M_%d_"标记已注入
3、!LastError 为真,表明互斥体还不存在,该进程未被注入。则调用sub_40CC92执行CreateRemoteThread(ProcessHandle, 0, 0, lpBaseAddress, 0, 0, 0)
CharLowerA(String1); // 进程名转小写
wsprintfA(&String1[v1], "M_%d_", th32ProcessID); // 拼接成 "进程名M_PID_"
hMutex = CreateMutexA(0, 0, String1); // 尝试创建命名互斥体
LastError = GetLastError();
ReleaseMutex(hMutex);
CloseHandle(hMutex);
if ( !LastError ) // 如果 LastError == 0
sub_40CC92(pe.th32ProcessID, String1);
线程二:Rookit & SSDT Hook & 反杀软 -> sub_4053B2
删除注册表中安全模式配置与子键、动态加载API
sub_4043EC(off_481A08, HKEY_CURRENT_USER); // System\CurrentControlSet\Control\SafeBoot
sub_4043EC(off_481A08, HKEY_LOCAL_MACHINE); // System\CurrentControlSet\Control\SafeBoot
hModule = LoadLibraryA(lpLibFileName); // ADVAPI32.DLL
if ( hModule )
{
dword_4140A4 = GetProcAddress(hModule, off_481678);// CreateServiceA
...
sub_4047BF 打开了 \\driver\\ipfltdrv.sys 创建 IPFILTERDRIVER服务
lstrcatA(Buffer, off_4814BC); // "drivers\\"
lstrcatA(Buffer, off_4815D8); // "ipfltdrv.sys"
v1 = dword_414098(0, 0, 983103);
v2 = dword_4140A4(v1, off_4816AC[0], off_4816AC[0], 983551, 1, 3, 1, Buffer, 0, 0, 0, 0, 0);
if ( v2 )
dword_414090(v2);
OpenService(v1, off_4816AC[0]); // "IPFILTERDRIVER"
动态解析 NTDLL.DLL 调用 "NtQuerySystemInformation" 创建并启动名为 "amsint32"的驱动服务
LibraryA = LoadLibraryA(off_481624);// "NTDLL.DLL"
if ( LibraryA )
{
dword_414008 = GetProcAddress(LibraryA, off_481498);// "NtQuerySystemInformation"
if ( dword_414008 )
{
if ( !sub_404649() ) // "\\\\.\\amsint32"
{
sub_404595(); // 写入文件,参数为lpBuffer,dump分析后为驱动文件
sub_40470F(off_4814B0, FileName);// "amsint32"
}
驱动分析
1、在 \Device\ 和 \DosDevices\ 下暴露一个名为 amsint32 的设备接口。可以通过 CreateFile("\\.\amsint32") 打开该设备,并通过 DeviceIoControl 与驱动通信,发送 SSDT 表地址或接收控制指令。
2、所有主要 IRP 类型(创建、关闭、设备控制)都指向同一个处理函数 sub_104F0
3、sub_104F0通过接收的字节长度和首位四字节魔数来判断执行的逻辑
4、存在一个Str二维数组,在运行时解密
if ( Str[2] != 's' )
{
for ( i = 0; *(&Str)[i]; ++i )
{
for ( j = 0; (&Str)[i][j]; ++j )
(&Str)[i][j] ^= 0xF9u;
}
}
通过解密脚本得到结果
kaspersky, eset.com, f-secure., mcafee., symantec., etrust.com, trendmicro., sophos., virustotal., agnmitum, pandasoftware., bitdefender., spywareguide., windowsecurity., virusscan., ewido, spywareinfo., onlinescan, drweb., cureit., virusinfo., sality-remov, upload_virus
该黑名单覆盖了国际主流安全厂商、在线病毒扫描平台以及针对 Sality 病毒的专杀清除工具。
5、启用两个系统线程 StartRoutine 和 sub_102D0
StartRoutine :网络过滤。通过向 \Device\IPFILTERDRIVER 通讯,拦截 SMTP邮件通讯 与 杀软的网络流量,阻止杀软发送病毒告警、更新病毒库,阻止提交样本到 VirusTotal 等平台,阻止受害者下载 Sality 专杀工具
sub_102D0:SSDT 原始服务调用线程,接收 KiServiceTable 地址数组,根据当前计算机Windows内核版本计算SSDT偏移,主循环:接收 PID 并调用原始系统服务。通过“直调原始服务”绕过自身钩子,确保恶意软件能够正常打开其他进程句柄,以执行结束杀软进程或远程注入等操作
SSDT Hook
成功创建恶意驱动服务后,调用sub_404BD0实现SSDT Hook
1、调用 NtQuerySystemInformation 查询系统模块列表,找到内核文件 ntoskrnl.exe 在内存里的基地址和文件名
2、获取 ntoskrnl.exe的文件映射,保存到当前进程的内存,计算 KiServiceTable 的RVA
3、遍历 KiServiceTable ,计算出每个服务真正的内核地址,v10数组存放所有系统服务函数在内核空间的真实地址
4、把地址表发送给驱动 amsint32
hFile = CreateFileA(lpFileName, 0x40000000u, 0, 0, 3u, 0, 0);// '\\.\amsint32' ; 打开amsint32
if ( hFile == -1 )
return 0;
WriteFile(hFile, v10, 4 * v6, &NumberOfBytesWritten, 0);// 写入地址表
反杀软
通过 CreateToolhelp32Snapshot + Process32First / Process32Next遍历系统中所有进程,off_481A50数组存储安全软件关键词的偏移,通过循环find的方式,调用 sub_404F99 kill 进程
线程三:磁盘传播 -> sub_40E507
获取Windows文件保护属性
lstrcpyA(String1, off_48160C); // sfc
dword_414000 = 0;
hModule = LoadLibraryA(String1);
if ( hModule )
dword_414000 = GetProcAddress(hModule, off_481608);// SfcIsFileProtected
if ( !dword_414000 )
{
FreeLibrary(hModule);
lstrcatA(String1, off_4814C4); // _os
hModule = LoadLibraryA(String1);
if ( hModule )
dword_414000 = GetProcAddress(hModule, off_481608);// SfcIsFileProtected
}
创建自身副本与自启动 -> sub_40DD99
1、在 "C:\Users\用户名\AppData\Local\Temp\"下创建随机文件名.exe,写入自身副本
2、遍历磁盘,跳过光驱
3、通过校验 autorun.inf 的时间戳验证是否已经被感染
lstrcatA(&RootPathName, off_481630); // "autorun.inf" 即样本"autorun.inf"
hFile = CreateFileA(&RootPathName, 0x80000000, 1u, 0, 3u, 0x20u, 0);
if ( hFile == -1 )
goto LABEL_35; // 不存在则创建新的autorun
GetFileTime(hFile, &CreationTime, &LastAccessTime, &LastWriteTime);
if ( !FileTimeToSystemTime(&CreationTime, &SystemTime) || SystemTime.wSecond != SystemTime.wHour + 7 )
{ // 比较 second = hour + 7 ,满足则是已经感染,关闭线程
CloseHandle(hFile);
4、未感染,删除原来可能有的 autorun.inf
5、生成随机的文件名并选择扩展名为 ".exe" 或 ".pif",将文件名拼接硬编码中预设的参数,写入buffer
6、在每个盘的根目录下写入 autorun.inf,内容的为buffer,并验证。
7、在每个盘的根目录下创建随机生成的、autorun.inf配置中指向的可执行文件
感染最近开的可执行文件 -> sub_40CC39
遍历 HKEY_CURRENT_USER\Software\Microsoft\Windows\ShellNoRoam\MUICache(记录最近打开过的应用程序的资源文件位置) 和 HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\(指向当前用户的Shell配置信息下的键值,并感染该注册表中的可执行文件,同时用相同的循环find遍历可执行文件名称,避开安全软件。
sub_40CB2D(off_4814DC); // Software\Microsoft\Windows\ShellNoRoam\MUICache
sub_40CB2D(off_48149C); // Software\Classes\Local Settings\Software\Microsoft\Windows\Shell
如果检测到安全软件有关关键词,return 0
感染根目录下的可执行文件 -> sub_40CA87
用于遍历C盘目录下的 ".exe" 和 ".src" 文件。用相同的循环find的方式删除存在安全软件关键词的文件。
lstrcpyA(String1, off_481484); // "C:\\"
String1[0] = v1;
if ( GetDriveTypeA(String1) != 5 )
sub_40BADD(3, String1, 0, &FindFileData);
感染开机自启动文件
sub_40C5AE(HKEY_CURRENT_USER); // 键值Run
sub_40C5AE(HKEY_LOCAL_MACHINE);
线程四:远程控制驱动功能&下载并执行恶意载荷 -> sub_403FAA
存在两种操作模式:1、控制amsint32 ,2、下载执行恶意载荷
模式一通过RC4解密,密钥为"kukutrusted!.",模式二为RC4 ECB模式解密,密钥为 "GdiPlus.dll"
if ( LOBYTE(v3[1]) == 1 ) //模式一
{
sub_40C1EF(String1); // 网络通讯,解密后根据操作码进行驱动通讯,按照操作码传递魔数,再传入操作码 1 ,进行解密并尝试执行可执行文件
}
else if ( !LOBYTE(v3[1]) ) //模式二
{
nNumberOfBytesToRead = sub_40B888(String1, Buffer, 0, 500000);// 网络通讯
if ( nNumberOfBytesToRead )
sub_40BE89(Buffer, nNumberOfBytesToRead, 2);// 传入操作码 2 ,调用RC4 ECB解密,判断是否为PE文件,并执行
}
模式一
sub_404521:通过参数来操作与驱动的通讯
模式二
线程五:Temp目录下的文件感染与目录清除 -> sub_4057A0
内存映射与任务调度
在执行线程6前,程序创建了一段名为 "hh8geqpHJTkdns0" 的内存映射
通过注册特殊键的值,存储任务调度与配置,注册表键值生成方式与init中相同
线程六:句柄清理 -> sub_401189
循环遍历一个句柄数组,如果某个句柄处于 signaled 状态,就将该句柄关闭
while ( 1 )
{
for ( i = 0; i < 0x186A0; ++i )
{
if ( hHandle[i] )
{
if ( !WaitForSingleObject(hHandle[i], 0x64u) )
Add_Close_Handle(0, i, 1);
}
}
Sleep(0x4E20u);
线程七:开放UDP端口后门 -> sub_403911
绑定生成的端口,新建一个线程用于接收控制端的数据
线程八:远控交互 -> sub_403D9B
sub_403B41:通信记录与控制
sub_402CFA:创建内存映射,执行远程操作
总结
样本行为归纳
该样本为 Sality 病毒变种,具备完整的蠕虫传播、内核级隐藏(Rootkit)和远控后门功能。主要攻击链如下:
1、脱壳与自解压:通过 Safeguard 壳保护,内存中解密出 UPX 压缩的载荷。
2、防御规避:禁用 UAC、关闭防火墙、屏蔽安全中心通知、删除安全模式配置。
3、持久化:Run 键 + 服务(amsint32、IPFILTERDRIVER)+ SYSTEM.INI + 磁盘传播。
4、内核对抗(Rootkit):加载 amsint32.sys 驱动,实现 SSDT Hook,隐藏自身进程和文件。
5、双模式远控:用户态负责下载执行/指令交互,内核态负责隐身与自保
处置建议
1、杀死恶意进程
2、删除注册表项:HKLM...\SafeBoot、HKLM...\Security Center* 下的恶意覆盖值。
3、停止并删除服务:amsint32、IPFILTERDRIVER。
4、清除各盘符根目录下的 autorun.inf 及随机名 .exe/.pif 文件。
5、使用Sality专杀工具
后记
第一次发帖,磕磕绊绊也算是写完了(不知道啥要写啥要省略的,一鼓捣全写上了{:301_1008:} ),还是很开心的。花了好几天的时间,结合着AI分析完这个样本,受益匪浅,学到了很多新东西。又涨姿势了~~{:301_988:}
最后感谢各位师傅的过目,本人水平有限,如有问题欢迎在评论区指正!!!最后万分感谢!!!