0x01 背景
4月份的时候分析了个银狐样本,该样本使用少见的4阶段加载,同时终阶载荷也在持久化和隐藏上做了比较复杂的相关操作,并且利用了一些比较少见的技术用以绕过一些edr,比较有意思,详细记录下相关分析过程。
如下是分析过来,笔者梳理其相关流程逻辑图:
1-4阶段加载图:
最终载荷释放流程图:
相关情况
样本:
1
2
3
4
5
name:vulkan-1.dll
md5:0ab0310cddb632990d9c1d3438723c2f
name:人员名单列表.exe
md5:5f7285e8f0b664d943e77d7b3280bcb3
通过im拉群传播使用:
0x02 样本分析:
dll文件不是pe格式文件;疑似是加密payload;
一、一阶段:
运行exe文件,exe读取vulkan-1.dll文件,然后解密还原:
内存:
解密:
解密后:
可以看到还原出来一阶段payload 是一个pe格式的文件,dump分析;
是一个反射dll‘加载,头部做了引导:
跳转到加载函数:
初始化函数0x1b42c1
,获取到getproceaddress+loadlibrary函数;
修复重定位表和导入表以及tls,最后跳转oep,dllmain函数;
利用www.baidu.com
测试网络联通性;
二、二阶段:
然后再从内存中动态解密出二阶段payload:一个dll文件,自实现了一套loadlibrary逻辑(区节拉升、修复导入表),将该dll加载到开辟的指定地址0x180000000
;并直接call对应dll的入口entry方法;
dllmain出来,返回之后,又调用0x1800001890(run)方法;
dump二阶段的dll文件静态分析。
dllmain里面,利用IsProcessFeaturePresent检测虚拟机、isdebuggerpresent、设定异常处理来反调试;
run方法:获取shellcode所在宿主进程名称判断是否为winlogon.exe
。
1、当前进程不是winlogon.exe,且没有管理员权限
如果当前进程不是winlogon.exe,判断当前用户是否是管理员组的用户:(shellcode中的代码都做了混淆,相关调用地址以及相关参数都是通过计算动态还原)
不是管理员组用户逻辑,通过bypassuac方式提权;
生成一个c:\windows\system32\winver.exe
,通过rpc(appinfo)方式调试方式启动winver.exe进程;
大致代码逻辑其实就是和uacme里面一致(应该就是直接使用),ucmDebugObjectMethod (https://github.com/hfiref0x/UACME/blob/master/Source/Akagi/methods/tyranid.c#L462);总而言之就是利用相同父进程的子进程共享调试句柄的方式获取到的taskmgr.exe(uac白进程,windows的uac机制内置了一些白名单,详情可以参考笔者之前的文章:一文搞懂windows UAC机制逻辑及提权原理)这个高权限进程的句柄,然后父进程伪造(taskmgr.exe)重启自己;从而实现提权;
2、当前进程不是winlogon.exe但是有管理员权限的
call 0x1800015b0
函数,然后退出;
将一段疑似shellcode复制到开辟的空间:
将一段疑似配置文件复制到开辟的指定空间:
然后通过CreateToolHelpo32Snapshot
获取进程列表,遍历进程,寻找winlogon.exe进程:
获取当前进程token,并调整token权限添加SeDebugPrivilege(想要注入winlogon系统进程所必须的权限)
利用RtlAdjustPrivilege
开启特殊权限的;
利用提升的权限句柄,打开winlogon进程,拿到winlogon的句柄;复制相关句柄;
然后就是利用拿到的句柄开展进程注入相关了。
在winlogon中远程开辟可读可写可执行的空间,使用writeprocessmemeory远程进程写入shellcode到的指定地址
再开辟一个可读可写空间,并写入相关内容。
再远程开辟一个可读可写可执行的空间,并写入一串疑似shellcode:
然后拿到如下一堆LPC相关函数地址,
调用ALPC(Advanced Local Procedure Call)相关接口,(通过创建伪造的 TP_ALPC
结构、将其写入目标进程内存,并利用 ALPC + IOCP + WorkerFactory
机制(windows 线程池管理相关机制)让目标进程调用该结构内指定的 shellcode,从而实现远程代码执行(RCE))。
最后注入了winlogon进程,注意这里没有直接使用createtremotethread,而是通过backhat2023上提到的PoolParty技术(如上文提到的,该项目利用 Windows 线程池的进程注入技术),简单说这个技术就是利用用户层下windows对进程中的线程管理机制来实现进程注入的,相比createremotethread和的apc注入,这里通过伪装为系统的线程池管理技术中的相关接口实现的注入,从而实现对一些edr的绕过;(但是还是要用viratualAlloc和WriteProcessMemory来操作受害进程)
此样本中使用的是该项目中Variant为5的Insert TP_ALPC work item to the target process’s thread pool 实现的注入技术,核心代码如下。
三、三阶段
注入的三阶段payload(shellcode) dump下来分析,相关内容如下:
其getprocaddr_byhash算法如下:
相关逻辑大致就是,拿到几个api之后,通过异或解密(sub_214
)一段内存;
然后调用函数0x214实现loadlibrary的效果,将解密出来的内存自加载:
四、四阶段:
解密前后对比,可以看到解密后是一个dll文件(四阶段载荷);
最后call到加载的dll里面的run方法里面;
dump四阶段载荷如下, 后续开展分析。
3、当前进程是winlogon.exe
如果是winlogon,运行的是0x180001010。
申请0xa88的空间,放意思加密内容,然后异或0x3a解密,拿到相关信息,一些配置内容:
解密后,可以看到这里又一些关键信息,如IP(应该是c2)以及一些文件名相关内容。同时从这里我们也可以看出攻击者使用的这个是一套成体系且较为成熟的远控。
这里有一个小细节,最后八位是单独解密的,刚开始以为是有什么骚操作,但是后面看了下解密的时候密钥还是异或0x3a,和之前解密是一样的,所以这里就是单纯因为8字节不够16,所以没办法和之前一样16字节16字节的解。
再申请一个0x6df98大小的空间,将三阶段dll(加载到0x180000000)中0x8574c偏移位置的内容写入到这里。
写入相关内容(之后会被作为参数传入三阶段payload,并再三阶段payload中对指定偏移进程解密加载,所以这里其实是加密的四阶段payload),。
持久化
戒指利用上面拿到的配置中的几个路径进行拼接,拿到新路径,并创建相关文件。
在C:\Program Files\Windows Mail
创建了三个文件,arphaDump64.exe
、arphaDump64.dll
、arphaDump64.bin
,文件内容就是提前放到三阶段载荷dll里面的用于持久化的白加黑权限维持的内容。下图是0x180001010函数的结构图:
copy过来一段shellcod,通过createthread执行:
该shellcode和上面分析的(当前进程不是winlogon但是又管理员权限)三阶段载荷一样。
这里注意一个细节,调用createthread的时候运行shellcode,还传入了一个参数,是之前写入的一段内容(上文提到的加密的四阶段payload):
拿到几个api之后,通过异或解密传入的参数对应偏移处的内存(和上面一致),然后调用run方法;
接着我们来分析,最终阶段的载荷(不管是winlogon进程还是具有管理员权限的其他进程,最后都加载运行这个终结载荷)
4、四阶段(最终载荷)载荷分析
run方法实现如下。
隐藏
sub_18003E0F0()
中实现ntdll重加载代码段,实现unhook,桡过edr监测:
sub_18003Dff0()
利用com接口添加防火墙策略,禁止外联:
利用com接口创建防火墙策略:
E2B3C97F-6AE1-41AC-817A-F6F92166D7DD
98325047-C671-4174-8D81-DEFCD3F03186
上面第一个是规则对象的uuid;
第二个是INetFwPolicy2 Interface的uuid;
然后拿到当前进程id,拿到进程名称判断是否是如下几个选项,做不同操作:
1
2
3
4
5
6
7
Inject Test
taskmgr.exe
perfmon.exe
winlogon.exe
svchost.exe
dllhost.exe
如果都不是,就判断是否为管理员权限,不是管理员权限就去运行:0x180046db0(利用白名单以及相同父进程下的子进程共享调试对象的机制bug来实现uac提权)
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
memset(String1, 0, 0x208ui64);
v8 = GetCurrentProcessId();
sub_18003D4E0(v8, String1); // getnamebyprocid
if ( !lstrcmpiW(::String1, L"Inject Test") )
((void (__fastcall *)(_QWORD *))*v3)(v3);
if ( !lstrcmpiW(String1, L"taskmgr.exe") )
{
sub_180023020();
ExitThread(0);
}
if ( !lstrcmpiW(String1, L"perfmon.exe") )
{
v9 = GetCurrentProcess();
TerminateProcess(v9, 0);
}
if ( !lstrcmpiW(String1, L"winlogon.exe") )
{
*(_OWORD *)&SystemInfo.dwOemId = 0i64;
*(_OWORD *)&SystemInfo.lpMaximumApplicationAddress = 0i64;
*(_OWORD *)&SystemInfo.dwNumberOfProcessors = 0i64;
GetNativeSystemInfo(&SystemInfo);
if ( SystemInfo.wProcessorArchitecture != 9 && SystemInfo.wProcessorArchitecture != 6 )
{
sub_180022E00();
ExitThread(0);
}
sub_180022EB0();
ExitThread(0);
}
if ( !lstrcmpiW(String1, L"svchost.exe") )
{
v10 = sub_18003DBD0();
if ( v10 != GetCurrentProcessId() )
{
while ( 1 )
{
do
{
v16 = CreateThread(0i64, 0i64, sub_180022560, v3, 0, 0i64);
v17 = v16;
}
while ( !v16 );
WaitForSingleObject(v16, 0xFFFFFFFF);
CloseHandle(v17);
}
}
v11 = sub_18003D5F0(word_18006B55C);
if ( v11 )
{
v12 = OpenProcess(0x1FFFFFu, 0, v11);
v13 = v12;
if ( v12 )
{
TerminateProcess(v12, 0);
CloseHandle(v13);
}
}
do
{
while ( 1 )
{
v14 = (void *)sub_180022E00();
v15 = v14;
if ( v14 )
break;
Sleep(0x3E8u);
}
WaitForSingleObject(v14, 0xFFFFFFFF);
LODWORD(ExitCode) = 0;
result = GetExitCodeProcess(v15, (LPDWORD)&ExitCode);
}
while ( (_DWORD)ExitCode != 123 );
return result;
}
if ( pNumArgs != 1 )
{
result = lstrcmpiW(L"/Processid:{F8284233-48F4-4680-ADDD-F8284233}", v2[1]);
if ( !result )
goto LABEL_50;
if ( pNumArgs <= 1 )
return result;
if ( !lstrcmpiW(L"/Processid:{F8284233-48F4-4680-ADDD-F8284233}", v2[1]) )
LABEL_50:
sub_1800226D0();
*(_OWORD *)&SystemInfo.dwOemId = 0i64;
*(_OWORD *)&SystemInfo.lpMaximumApplicationAddress = 0i64;
*(_OWORD *)&SystemInfo.dwNumberOfProcessors = 0i64;
GetNativeSystemInfo(&SystemInfo);
if ( SystemInfo.wProcessorArchitecture != 9 && SystemInfo.wProcessorArchitecture != 6 )// 不是64位
{
sub_180022E00();
ExitProcess(0);
}
return sub_180022EB0();
}
memset(Filename, 0, 0x208ui64);
GetModuleFileNameW(0i64, Filename, 0x104u);
if ( wcsstr(Filename, word_18006B494) )
{
*(_OWORD *)&SystemInfo.dwOemId = 0i64;
*(_OWORD *)&SystemInfo.lpMaximumApplicationAddress = 0i64;
*(_OWORD *)&SystemInfo.dwNumberOfProcessors = 0i64;
GetNativeSystemInfo(&SystemInfo);
if ( SystemInfo.wProcessorArchitecture != 9 && SystemInfo.wProcessorArchitecture != 6 )
{
sub_180022E00();
ExitProcess(0);
}
return sub_180022EB0();
}
memset(CommandLine, 0, 0x208ui64);
GetModuleFileNameW(0i64, CommandLine, 0x104u);
if ( IsUserAnAdmin() )
{
sub_180031660(word_18006B494);
sub_1800317C0(&byte_18006AC30, CommandLine);
ppv = 0i64;
ExitCode = 0i64;
sub_180023360(&ppv);
if ( ppv )
{
sub_1800236B0(ppv, &ExitCode);
if ( ExitCode )
{
memset(v31, 0, 0x208ui64);
wsprintfW(v31, L"%s\\%s", word_18006B494, word_18006B55C);
if ( (unsigned int)sub_180031A30(&psz, word_18006B368, v31, dwCreationFlags, (__int64)ExitCode, (__int64)ppv) )
{
v18 = ExitCode;
if ( ExitCode )
{
v19 = SysAllocString(&psz);
v20 = SysAllocString(&Address);
VariantInit(&pvarg);
v21 = *v18;
pvarg.llVal = (LONGLONG)v20;
pvarg.vt = 8;
v35 = 0i64;
if ( (*(int (__fastcall **)(__int64 *, BSTR, __int64 **))(v21 + 104))(v18, v19, &v35) >= 0 )
{
if ( v35 )
{
v22 = *v35;
*(_OWORD *)&SystemInfo.dwOemId = *(_OWORD *)&pvarg.vt;
SystemInfo.lpMaximumApplicationAddress = pvarg.pRecInfo;
(*(void (__fastcall **)(__int64 *, struct _SYSTEM_INFO *, _QWORD))(v22 + 96))(v35, &SystemInfo, 0i64);
(*(void (__fastcall **)(__int64 *))(*v35 + 16))(v35);
}
}
}
}
}
}
}
else
{
sub_180046DB0(CommandLine);
}
v23 = GetCurrentProcess();
return TerminateProcess(v23, 0)
那么这个病毒的核心逻辑就是在这了;不同运行实体,其对应的操作都不同,代表的不同的阶段;
这里不妨思考下,病毒为什么要判断自己是在哪个进程里面,从上面流程分析下来,进程不应该只有两种可能吗?一是在winlogon,一个是在非winlogon但是又管理员权限的进程中。而这里的判断似乎还有一堆其他的,比如:Inject Test、taskmgr.exe、perfmon.exe、svchost.exe、dllhost.exe。
简单揣测,大致离不开以下的原因:
- 1、这段最终载荷肯定是有机会在上面提到的进程中运行的,那么下面功能代码中可能会出现对相关进程的注入。
- 2、攻击者比较擅长使用上面几个进程来实现killchain里面的 持久化、隐藏、命令控制 三个阶段的相关操作,所以这里对最终载荷注入相关进程做了准备。说到底其实还是第一点。
这里我们继续往下跟踪,不难看出,攻击者这里主要是要注入winlogon,所以往下跟的时候我们从宿主进程为winlogon来看。
五、winlogon宿主
如果宿主进程是winlogon,先判断系统位数,然后调用不同函数。
x64系统
x64系统下,调用sub_180022eb0
,结构如下:
其中sub_18003dbd0
为遍历现有服务,找到svchost 计划任务服务对应的pid。
如下是动调的时候其拿到计划任务服务对应进程的pid。
找到计划任务的pid之后,如果存在则调用sub_180030EB0
,实现如下,先拿到sedebug权限;
然后疑似构建了一个链表,存储一些后续用于执行的函数,这些函数大致的功能是远程进程注入以及提权、降权的进程创建相关,最后返回该链表头指针。
动态调试的时候,拿到对应返回的地址,相对偏移和上面静态分析也都对的上。
相关函数:
常规注入
进程镂空的注入:
权限控制运行进程:
等一些用于操作进程的函数。
所以这里其实就是找到计划任务对应的进程(svchost 的schedule组)pid,并注入,注入方式使用的是第一种常规方法,拿到sedebug权限后,通过VirtualAllloc和WritePorcessMemeory远程开辟空间和写入相关内容,然后通过createRemoteThread注入。
注入的shellcode以及对应参数和上面winlogon进程中通过createthread方式运行三阶段payload完全一致(下面没有直接调用CreateRemoteThread而是直接调用用户层更为底层的ZwCreateThreadEx,效果一样,因为CreateRemoteThread底层也是调用ZWCreateThreadEx,但需要注意ZwCreateThreadEx并不是一个公开的函数,msdn文档没有相关记录,但是可以利用导出名称再ntdll中找到其导出地址):
这里我们直接把计划任务对应进程里面的对应shellcode区域dump下来:
如下两个是dump下来内存,可以看到最后跳转云彩的内容其实就是我们上面分析的三阶段payload。
如果没有找到计划任务对应的进程pid,则直接调用下面的sub_180022e00
,其实现如下:使用进程镂空方式创建一个svchost.exe -k netsvcs
进程。(镂空后运行的东西我们就不再追进去分析了,应该也是三阶段payload)
最后,再次校验当前进程是否为winlogon,如果不是就终止进程。
x32系统
如果不是64位的系统,那么就注入不了计划任务进程了,因为样本释放的时候是64位的(一般来说64位进程注入不了32位的进程),所以这里直接创建svchost.exe -k netsvcs
进程。
简单总结
分析到这,简单总结下:
如果是winlogon宿主进程运行了四阶段载荷,那么就会遍历进程拿到计划任务服务对应的进程pid,或者是重新创建一个commandline 为svchost -k netsvcs
(注意正常的svchost没有这种启动参数,最后要加组的)来实现进程镂空加载三阶段payload,进而加载四阶段载荷。
所以下面我们就需要带入的宿主进程为计划任务进程或者是自建的svchost进程来分析。
重新回到四阶段载荷,来到进程为svchost的条件
六、svchost 宿主
先获取计划任务访问的进程pid,然后判断当前进程是否是计划任务对应进程:
如果当前进程是计划任务服务对应的进程:
那么这里就先干掉用于权限维持的样本进程。
接着,进程镂空创建子进程:c:"\\windows\\system32\\svchost.exe - k netsvcs
,如下图。
如果当前进程不是计划任务服务对应的进程
其实这里的情况就是对应的上面 计划任务服务对应进程被注入之后,其通过进程镂空创建的子进程c:\\windows\system32\\svchost.exe -k netsvcs
,如果发现这个svchhost是子进程起来的,那么这里就创建新线程运行指定偏移函数0x22560
,如下图。
相对偏移为0x22560
的函数实现如下。有三个创建新线程执行的地方。
第一个创建,运行的载荷是0x286c0
偏移的函数,参数是对应函数数组。
其实现如下。0x286c0,里面有两个逻辑,第一个大致逻辑就是遍历远程会话,如果有,挨个判断是否是当前session,如果不是就调用0x27f10
函数,传入参数就是获取的非本进程用户的sessionid和之前的参数。第二个逻辑是创建线程运行偏移为0x2b380
的函数。
0x27f10
的实现如下。先解密还原出来一个String:
如下对应的string为:c:\\windows\\system32\\dllhost.exe /Processid:{F8284233-48F4-4680-ADDD-F8284233}
然后获取相关方法,并调用第四个方法(v9[3]
)执行。
接着下面还有创建通信管道的相关操作,这里因该是要跨session进程间管道通信使用。
回来,这里我们深入v9【3】也就是第四个方法去看下,对应的偏移是:0x30af0
,其通过对应用户session权限token,调用CreateProcessAsUserW以挂起方法(第七个参数有0x400)创建了某个进程,commandline就是上面解密还原出来的string,如下图。
所以这里其实就是创建了一个dllhost进程,参数为:/Processid:{F8284233-48F4-4680-ADDD-F8284233}
接着调用相对偏移为0x30330
的函数。
其实现如下,可以看出,其实就是对刚刚创建的进程进行了注入,修改eip之后,再唤起进程。
这里注入到新宿主的内容和注入到之前的宿主也是一样的。
接着回到上面函数,第二个线程运行的函数是0x2b380
的函数,其是实现如下。大致就是创建一个窗口,让后通过WTSRegisterSessionNotification,监听所有会话session的操作的通知,比如注销、登录之类的,然后调用自定义的处理函数,该函数偏移为0x2b320
判断拿到session登录消息的时候,调用自定义处理,处理函数和上面创建dllhost的一样。
接着回到相对偏移为0x22560
函数,下面第二个创建线程,如果是64位的系统,调用的函数是startAddress
,其实实现如下。
遍历进程,找到taskmgr.exe,然后远程进程注入。
第三个创建线程,调用的是0x22340
偏移处的函数。
0x22340
实现如下。大致逻辑就是找到遍历找到perfmon.exe进程,然后远程进程注入(和taskmgr.exe一致)。
最后我们需要注意,0x22560
函数的最后还调用了一个方法,是传入的参数转指针执行。如下图。
这个参数的由来我们简单看下由来,如下图。我们可以看到这个参数其实是前面某个函数获取到的一个方法数组,没有偏移就是数组的第一个元素,所以就是前面函数的第一个方法:相对偏移为0x25960
偏移的函数。
这个函数就是我们要找的核心回连c2的函数。我们来看下。
首先该函数在和之前持久化exe的同目录下尝试打开并读取c:\\Program Files\\Windows Mail\\install.cfg
文件
获取该文件大小,如果大小是0xa88
(前文提到过的某段载荷也是0xa88,好像是配置文件那个,这里可能也是兼容某种情况下的配置文件通过文件方式方式的使用),继续往下。
如果读取到了会调用一个解密的循环。
接着while-ture
,利用如下函数干掉休眠和屏保(省电模式之类的):
1
2
3
4
SetThreadExecutionState(0x80000003):
参数 0x80000003 = ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED
让系统保持唤醒状态,防止系统进入睡眠或关闭显示器。
SystemParametersInfoW (0xf\0x11)用于禁用屏保和低电量模式
然后读取之前解密还原的配置文件的相关内容,做比较,如下是对回连c2的协议做比较,可以看到配置文件中写的是tcp协议(同时也说明,这个远控回连是支持TCP、UDP、HTTP三种协议)
然后拿到一堆远程方法注入的那个数组,并且从配置文件中拿到端口:
然后调用v17[7],动态调试的时候,可以看到其实就是0x30dd0
偏移的函数,传入的参数1是一个16进制数组,参数2是端口。
这个函数里面,把端口转成字符类型,并向指定设备{"\\\.\\{F8284233-48F4-4680-ADDD-F828}
发送对应控制码以及端口号。
接着,调用相关函数,创建并且添加名为.NET Framework NGEN v4.0.30318
(伪装为 .net升级)的开机自启的计划任务,启动上面winlogon释放的用于持久化的白加黑程序:
创建计划任务:
计划任务信息:
1
2
name:.NET Framework NGEN v4.0.30318
md5:2088095e52420b7dcb3ced2e2eda2280
回连c2(18.143.121.97):
c2是亚马逊云主机
设置参数以及相关配置socket,如:规定传输数组大小以及keep-alive等。
那么整体来看,当前进程不是计划任务服务对应的进程,且进程是svchost.exe的时候。其会创建一个dllhost 指定参数的进程,并且执行相关感染逻辑,去注入其他会话并且监听相关session的操作通知,以及创建各session之间的管道通信,以及回连c2。
简单总结
当宿主位svchost.exe起来的进程的时候,其分两种情况,一个是计划任务服务对应的进程,一个是被感染的计划任务服务进程通过进程镂空创建的svchost.exe -k netsvcs
进程。
计划任务服务对应的进程:
做两件事:一个是干掉用于权限维持(开机自启的arphdump64进程),第二个是通过进程镂空创建svchost.exe -k netsvcs
进程。
svchost.exe -k netsvcs
进程:
也是做三件事,一个是遍历远程连接用户会话,利用相关用户session创建dllhost进程并进行注入用于权限维持,其中创建管道通信(同时创建了一个监听窗口,监听session登录的情况,有新用户session登录就执行注入感染逻辑);一个是遍历进程拿到taskmgr.exe(任务管理器)和perfmon.exe(windows的性能监控工具)进程的pid,然后开展注入。三是回连c2。
所以接下来我们继续分析4阶段载荷的时候可以关注下在、
1、进程为dllhost,且参数为/Processid:{F8284233-48F4-4680-ADDD-F8284233}
2、taskmgr.exe以及perfmon.exe宿主运行的时候产生的操作。
七、dllhost为宿主进程
四阶段判断中没有直接匹配dllhost的,但是有一个这样的逻辑,先判断commandline拆分下来的参数个数,如果有参数(参数数>=1),且参数为Processid:{F8284233-48F4-4680-ADDD-F8284233}
,这显然就是我们要找的dllhost进程。
满足条件调用偏移为0x226d0
函数,然后判断系统位数,执行注入计划任务进程以及创建svchost那套。
这里我们主要来看到0x226d0
h函数,这个函数中其实就是核心的远控功能实现以及相关远控逻辑了。如下。如下,显示获取到一对可以拷贝的是实现函数,然后拿到当前经常的会话session,然后调用核心函数0x37820
去获取远控的功能函数地址:
最后创建进程运行0x224d0
,参数为上面获取到的远控功能函数地址列表。
我们先回到上面获取远控相关功能的关键函数0x37820
,其实现如下。拿到一个数组,里面都是一些功能实现函数,最后返回该数组。
简单看下有对应一些功能函数的实现。
如下,是获取父进程pid。
如下是创建或者使用管道通信相关。
如下是监控鼠标相关信息:
分析功能函数地址获取的函数,其实现如下。创建了一个0x1000的空间,专门存储相关函数地址,然后给其赋值:
八、taskmgr、perfmon宿主
这个两个宿主的逻辑比较简单,perfmon宿主的话,直接干掉自己即干掉windows的性能监测工具;如果是taskmgr宿主的话调用相对偏移为0x23020
的函数,如下图。
0x23020
函数实现如下。应该是拿到taskmgr的句柄,然后通过句柄拿到某个地址,最后做了个劫持,将指定空间的内容/函数,劫持到0x23100
h函数。
0x23100
函数实现如下。大致就是遍历进程找到调用是否是符合条件的dllhost进程。
九、四阶段载荷总结
附一张图:各宿主进程之间的先后关系。释放逻辑图。
0x03 银狐技术更新
从分析上看相对去年,银狐使用了一些新的技术。
1、之前银狐一般都会使用ddr(Dead Drop Resolvers)相关技术,借助一些合法域名网站来分发自己多阶段加载要使用的payload(比如利用阿里云、腾讯云存储桶)。用以绕过安全软件的流量检测。 2、远程进程注入没有使用传统的几大类远程进程注入技术,而是使用了2023年blackhat上提到的PoolParty利用线程池机制的注入技术。
一、bypassUAC提权
利用rpc(aiclaunchadminprocess)方式启动两个进程,一个是winver,taskmgr,启动winver是以调试模式启动,启动之后,获取到调试句柄,分离调试器,然后终止winver.exe;接着以高权限起taskmgr,因为taskmgr是白名单,所以这里可以绕过uac直接启动,起的时候传入刚刚拿到的调试句柄,然后利用(两个线程回共享调试进程的原理)该调试句柄拿到taskmgr的句柄,最后利用taskmgr的句柄启动恶意进程自己,从而实现提权;
关键点:
1、rpc(AicLaunchAdminProcess)创建低权限调试进程winver.exe
2、利用NtQueryInfomationProcess 查询地权限进程,获取到调试句柄
3、(NtRemoveProcessDebug)断开调试器和调试进程,
4、rpc(AicLaunchAdminProcess)调试权限创建高权限进程taskmgr.exe(调试对象在创建地权限进程的时候已经初始化,所以这里直接分配到新进程)
5、检索初始调试事件,返回完整访问权限的taskmgr(uac白名单)高权限句柄,
3、利用ZWDuplicateObject复制taskmgr的进程句柄
4、利用CreateProcessAsUser来实现的父进程伪造
二、PoolParty进程注入技术
这个技术后续笔者会专门写一个主题文章,从技术实现原理到技术检出思路即落地完成相关闭环。
参考:https://github.com/SafeBreach-Labs/PoolParty
0x04 总结
从专业角度看,银狐背后的团伙技术水平的确有限,这一点从其相关shellcode载荷实现上、对抗分析调试的能力水平上是可以看出来的(又或者是其本身并不在乎相关人员对其进行分析,其和传统的黑灰产一样只关注对抗端口的软件以及edr,从而保证其相关载荷正常释放并运行即可,毕竟基本上只要样本被捕获,短时间内都会被相关公司后端支持能力组织进行刨析的清清楚楚,所以干脆放弃抵抗。)。但是其总能在一段时间后及时更新(有自己的技术免杀团队,但团队规模应该比较小),并合理的运用相关开源技术来绕过相关的edr和流量侧的产品。
笔者认为,在面对具备专门免杀团队的威胁组织的时候,端侧杀软、edr、hids、ids相关产品本身的固化的检出能力是非常低的,因为相关样本在其“出厂”的时候,就已经经过了专门的测试。笔者比较看好基于行为日志(edr、ids相关产品)来构建异常行为的发现。拿上面25年4月银狐举例,如果我们拿进程访问的行为异常场景举例,该场景下,1、主要是去发现一些不常见进程对高权限进程的访问情况。2、主要关注高权限进程的进程创建情况。
1、 taskmgr 一般情况下本身是不应该去创建其他相关进程的。
2、winlogon一般情况下本身不应该在C:\Program Files
目录下创建相关文件,它的职责是用户登录会话相关的功能。
3、winlogon 一般情况下不应该访问svchost进程。
这样我们就可以在银狐提权的时候发现它。不管它之后使用了ucame里面何种技术,又或者是其运用了何种新注入技术或者提权技术,我们都有发现其的可能。