记一次持续化隐藏服务的程序分析

本文最后更新于 2025年5月26日 上午

  1. 当计算机重启后,这个程序如何确保它继续运行(达到持久化驻留) ?

调用了CreatService函数,创建了一个服务,并设置为开机自启动。

分析过程:

==StartServiceCtrlDispatcherA== 函数将服务进程的主线程连接到服务控制管理器,这会导致线程成为调用进程的服务控制调度程序线程。

它的参数是一个结构体类型的指针

image-20250525144531072

这里传入的第一个参数是一个字符串的指针,定义了服务名为MalService,第二个参数,sub_401040是服务要运行的函数的地址。

进入执行的函数,发现有创建服务

image-20250525145520958

函数原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SC_HANDLE CreateServiceA(
SC_HANDLE hSCManager, // SCM 句柄,来自之前 OpenSCManagerA 的返回值
LPCSTR lpServiceName, // 服务名称(内部唯一标识),这里是字符串Malservice
LPCSTR lpDisplayName, // 显示名称(用户可见),这里是字符串Malservice
DWORD dwDesiredAccess, // 服务访问权限,这里传入了2,代表完全控制权限,允许对服务进行所有操作
DWORD dwServiceType, // 服务类型。10h代表运行在独立的进程中
DWORD dwStartType, // 启动类型,参数为2,代表系统启动时自动运行该服务,实现持久化
DWORD dwErrorControl, // 错误控制级别,参数为0,服务启动失败时,系统忽略错误(不记录日志,避免引起注意)
LPCSTR lpBinaryPathName, // 可执行文件路径
LPCSTR lpLoadOrderGroup, // 加载顺序组,0,无加载顺序组(服务启动顺序由系统默认管理)
LPDWORD lpdwTagId, // 标签标识符,0,不需要标签标识符(通常用于驱动程序,此处为 NULL)
LPCSTR lpDependencies, // 依赖服务,0代表独立运行
LPCSTR lpServiceStartName, // 运行账户,0代表使用最高权限账户
LPCSTR lpPassword // 账户密码,最高权限账户无需密码,为NULL
);
  1. 为什么这个程序会使用一个互斥量?

在函数的开始,使用互斥量,判断该服务是否存在;通过互斥体确保程序唯一运行实例,防止多个服务实例同时运行,避免资源冲突、数据竞争等问题导致服务崩溃。

image-20250525150401580

  1. 可以用来检测这个程序的基于主机特征是什么?

硬编码的字符串,如HGL345(互斥量的名称)、MalService(运行的服务名称)

  1. 检测这个恶意代码的基于网络特征是什么?;这个程序的目的是什么

这里使用Internet Explorer 8.0访问了http://www.malwareanalysisbook.com

image-20250525151043466

这个进程的目的:通过无限循环持续发起HTTP请求,用于对目标URL进行拒绝服务攻击(DoS)或资源耗尽攻击。

该服务函数创建了20个线程,每个线程都无限循环发起http请求且连接不终止,且每次都强制从服务器加载,不使用缓存。

image-20250525151415269

  1. 这个程序的目的是什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
memset(&SystemTime.wMonth, 0, 14);
SystemTime.wYear = 2100;
SystemTimeToFileTime(&SystemTime, &FileTime);
WaitableTimerA = CreateWaitableTimerA(0, 0, 0);
SetWaitableTimer(WaitableTimerA, (const LARGE_INTEGER *)&FileTime, 0, 0, 0, 0);
if ( !WaitForSingleObject(WaitableTimerA, 0xFFFFFFFF) ) # 0xffffffff无限等待该时间触发器
{
v2 = 20;
do
{
CreateThread(0, 0, StartAddress, 0, 0, 0);
--v2;
}
while ( v2 );
}
Sleep(0xFFFFFFFF);

先把SystemTime结构体初始化,将里面的年、月、星期、日、时、分、秒、毫秒都置为0

然后将年份设置为2100年,再将这个事件转化为文件时间,创建一个定时器,触发时间为文件时间,当时间到达时,触发该触发器。

触发后会执行后续的创建线程与dos攻击操作,最后无限休眠,阻止进程正常退出。

  1. 修改使得绕过时间触发机制运行StartAddress

WaitForSingleObject函数当接收到信号时,返回0;其他的信号80h,102h,0xffffffff分别代表互斥体被遗弃、等待超时、函数调用失败。将接收到信号,因此,将无限等待的时间改为0,此时接收到非0信号,将jnz改为jz

image-20250525210447890

这么做发现无法实现既定逻辑,经调试发现,eax的返回值为0,信号触发了,这不符合逻辑,往调试发现Filetime的值为0,往上调试去看filetotime函数的执行,发现SystemTimeToFileTime执行完之后,Filetime仍然为0

image-20250525215934333

image-20250525215958103

查询后得知,由于月份被置为0,这是一个无效的日期,导致返回了0

调试发现edx异或前就是0,直接+1,使得日期有效。

image-20250525221324115

发现还是无效,查看修改后的伪c代码,month被设置为高位的edx

image-20250525223143338

而汇编是这么显示的,需要将month对应的位置设置为1 ,改为[esp+6]

image-20250525223317684

image-20250525224126340

成功设置为1,但是函数返回还是为0

image-20250525225223391

自行编译了一个符合且能运行的,调试发现正常。

image-20250525225858302

重新patch,将其他部分nop掉

image-20250525230147603

还是不行,仿照下方,直接word字节赋值1

image-20250525230647812

还是不行,猜测edx为1导致的异常,将inc edx nop掉,还是不行。。

直接只把wMonth设置为1,还是不行,因为字节太长了,把下面的lea ecx影响到了,无法正常赋值。

image-20250525231908106

将赋值nop掉,调整参数的赋值,腾出空间给wMonth赋值

image-20250525233130845

还是不行,调试发现是周(0-6)这里是22,天(1-该月天数)这里是c000,不对.

image-20250525234332451

重新修改后,把参数设置成功

image-20250526000848012

  1. 修改internet explorer版本以及攻击的网址

虚拟机的版本为6.0,改为6.0,地址改为baidu

image-20250525153020496

ida找到二者对应的虚拟地址分别为0x0405050 、0x0405074,去010editor中修改方便一点,可以直接在右侧输入字符串来修改,不用自行转化ascii码

image-20250525153648151

由于直接将网址的长度变短了,导致Internet字符串的寻址错误,只有后半段了,这里用的是立即数寻址,在上侧补充上该有的0

image-20250525154206950

用0重新补充,使得5074的位置时字符串的开头I

image-20250525154350817

这次解析正常

image-20250525154507607

成功

image-20250526000822200


记一次持续化隐藏服务的程序分析
https://xyyr-c.github.io/2025/05/25/记一次持续化隐藏服务的程序分析/
作者
xyyr
发布于
2025年5月25日
许可协议