几乎所有专业红队APT都在广泛使用进程注入技术,一旦在内存中运行后,防守方使用杀毒进行查杀只会得到“您的设备没有任何威胁”,这一假象使得防守对象获得虚假的安全感,有经验的防守选择了重启系统清除所有内存中运行的木马,但是假如攻击方控制着重要的系统,客户选择暂停业务可能导致损失上百万乃至上千万,本篇文章将利用Yara技术狩猎内存中的恶意威胁,并分享如何手工清除恶意进程线程
Yara规则介绍
Yara的语法和C语言非常类似,大致可以分为三个部分,strings、condition、变量、关键字,并用ExampleRule包裹起来定义一条规则:
/*
这里是注释内容,放入说明
*/
rule ExampleRule // 这里也是一个注释
{
strings:
$my_text_string = "hacker"
$my_hex_string = { 41 B8 03 00 00 00 BA 02 00 00 00 B9 01 00 00 00}
condition:
$my_text_string or $my_hex_string
}
$符号后面跟上字符串,利用""包裹,也可以用{}以16进制包裹
$my_text_string = "hacker"
$my_hex_string = { 41 B8 03 00 00 00 BA 02 00 00 00 B9 01 00 00 00 }
这类16进制的bit还可以使用?来占位,用于匹配任意的字符
$hex_string = { E2 34 ?? C8 A? FB }
如果不确定16进制的长度可以使用中括号包裹
$hex_string = { F4 23 [4-6] 62 B4 } //代表最小4个、最大6个16进制的占位符
or是内置关键字,代表其中一个匹配到就算true
$my_text_string or $my_hex_string
比较方便的很多关键字提供了处理string的能力,只需要在后面追加上关键字就能自动处理前面的字符串
$text_string = "foobar" nocase //代表不区别大小写
$text_string = "foobar" base64 //代表base64处理
$text_string = "foobar" xor //代表xor处理
全部关键字如下,大部分关键字顾名思义,读者随时可以阅读文档学习更高级的语法,没必要完全记住,用到就顺手查一下就行
编写高效的Yara规则
对我个人而言,写Yara规则和写Xray的poc一样差不多,写Xray的poc建立在对漏洞的理解和http协议之上,而写Yara规则建立在对文件格式、内存加载以及一些Windows的理解,当然这里不需要深入理解,只需要稍微学习几天就能上手写出具备生产力的Yara规则,这也是为什么Yara被称作“快速制胜”的方法
我这边写了一个非常简单的cpp demo帮助理解:
// demo_foryara.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <Windows.h>
#include <iostream>
std::string name = "endlessparadox";
int for_test(int a, int b, int c) {
return a+b+c;
}
int main()
{
std::cout << "Hello Security service engineer!\n";
std::cout << "Address is " << &for_test << std::endl;
for_test(1, 2, 3);
std::cout << "Address is " << &name << std::endl;
int s = 100;
for (int i = 0; i < 10; i++) {
s = i + s ;
}
std::cout << "PID is " << GetCurrentProcessId() << std::endl;
getchar();
}
这里虽然只有寥寥几行代码,已经包含常量、自定义函数调用、循环、标准API调用,覆盖所有正常程序的行为,我们直接调试这个程序看看内存中的汇编指令:
源码中for_test(1, 2, 3)对应的汇编指令,这是经典的函数调用,参数依次移入寄存器,之后直接call函数地址
mov r8d, 3
mov edx, 2
mov ecx, 1
call for_test(address)
类似这种特殊的带常量的函数,编写yara也很简单,只需要拿到汇编对应的二进制机器码即可
$my_hex_string = { 41 B8 03 00 00 00 BA 02 00 00 00 B9 01 00 00 00 }
我们还缺少一个解析yara的工具,我这里用官方的工具,看起来工作的不错,成功的匹配了:
对于扫描磁盘的效果也是一样的,PE文件进入内存中本质是文件的内存展开、动态链接、重定位等等:
.\yara64.exe .\test.yara D:\codelearn\C++\demo_foryara\x64\Debug\demo_foryara.exe
对于独有的循环起来也很轻松,我们只需要取其中的一部分就能精确识别:
对于单个标准的API调用编写yara并不是一个好的选择,类似GetCurrentProcessId()只是一个call跳转,几乎可以肯定存在于每个正常的进程中,写出来的误报是非常高的
要查找独有字符串可以使用微软的sysinternals中strings,将匹配出全部字符串
.\strings64.exe D:\codelearn\C++\demo_foryara\x64\Debug\demo_foryara.exe
快速浏览我们发现,如果有很多正常的windows api和c函数字符,这些显然不利于我们打标,不过这里我这demo的包含的endlessparadox这个我的id很明显不是一个常见的字符:
运行一下,使用x64dbg去看看内存位置,可见在内存中也是能精准的匹配:
可以看到提取yara规则就是在分析文件的汇编指令,找出独有的二进制特征是一件非常耗费精力的过程,需要手工去提取分析,不过伟大的开源社区已经公开的很多优秀的yara规则,这里我推荐几个优秀的:
- https://github.com/Neo23x0/signature-base
- https://github.com/advanced-threat-research/Yara-Rules
- https://github.com/elastic/protections-artifacts/
稍微浏览这些项目,我们发现大量针对CS、MSF、Sliver等知名工具的打标,我这边下载protections-artifacts-main的Windows_Trojan_CobaltStrike.yar这个测试一下
yara64.exe Windows_Trojan_CobaltStrike.yar 9768
yara64.exe Windows_Trojan_CobaltStrike.yar beacon_x64.exe
效果不错,无论是在内存中还是在磁盘上都被精准的识别了,看看规则:
我们人工用imhex查找一下二进制,的确是非常精准:
修改现有工具加速狩猎
现在可以开始狩猎内存中的威胁了,不过我发现好像目前并没有公开可以批量扫描内存的工具,VT官方开发的默认只能扫描一个进程,完全不能作为生产力使用,而且debug信息也非常少,于是我花了一会修改了默认的Yara工具,使得能批量扫描所有内存中的进程,工具已经上传 Github :
核心改动的代码如下,我设置了pid作为标志位,当pid等于999999的时候扫描所有进程:
我在控制台这边注入了8776进程:
扫描所有进程
yara64.exe Windows_Trojan_CobaltStrike.yar 999999
不出一分钟,就扫描出了8776这个进程受到了恶意的CS感染
需要注意,红队可以获取System权限以阻止非System的进程打开自己,实战用system权限去扫描
手工清除恶意进程
我们看看这个注入的进程是什么,对于不重要的系统可以直接Kill掉:
这个pid无所谓,不过有些重要的进程是不可kill的,要是kill的话,windows会强制重启系统或者影响正常使用,这里列举几个重要的进程:
- System:pid固定为4,内部维护内核驱动的线程,维持着系统基本的运转
- System Idle Process : pid固定为0,计算机器的空闲时间
- Registry:注册表进程,维持大多数程序对注册表的访问
- Smss:会话管理进程,负责处理用户正常的会话交互
- Winint: 负责用户进入windows初始化过程,处理设置环境变量、密码验证
- Winlogon: 处理用户的注销和登录,kill掉会强制重新所有用户登录
- Explorer: 处理windows图形,kill掉会黑屏,不过也可以恢复
- Lsass:处理用户认证和管理密钥,kill掉会强制一分钟后重启系统
- svhost:系统大量存在,这是windows服务的主进程,维持了很多有用的windows服务,可以观察命令行参数,查看是否是重要的服务,最好不要随便kill
手工清除恶意线程
由于CS的休眠延迟机制,只有轮询的一瞬间有网络请求,理论上不可能人工去分析TCP连接。对于不完全理解注入工作的原理的师傅有点痛苦,到底那些是正常的线程?那些是异常的?
为了方便理解,我们直接注入看看效果,下面是注入前的进程线程:
这个是注入后的,多出来了很多,有一个地址很奇怪是0x0000000的那个:
我们点进出看看,这个线程的堆栈调用,从ntoskrnl.exe的特征这是典型的Syscall,12位置是sleepEx,这是CS的休眠调用,TID为8168的调用栈很明显不是正常的线程,有稍微使用过CS的就可以看出来这个是恶意CS的线程
我使用的是4.9的默认注入,shellcode的执行符合这种堆栈调用,只需要kill掉这个线程,我们的CS就看到掉线了(你的CS的线程堆栈可能和我的不太一样, CS的配置文件中可以控制是否使用Syscall)
kill掉这个线程,果然我们的beacon就掉线了:
刚刚演示的注入实际上的NT APC注入,CS自带了四中不同情况的注入,基于如下顺序,如果前面的APC注入如果失败就会进入下一个注入技术:
execute {
# The order is important! Each step will be attempted (if applicable) until successful
## self-injection
CreateThread "ntdll!RtlUserThreadStart+0x42";
CreateThread;
## Injection via suspened processes (SetThreadContext|NtQueueApcThread-s)
# OPSEC - when you use SetThreadContext; your thread will have a start address that reflects the original execution entry point of the temporary process.
# SetThreadContext;
NtQueueApcThread-s;
## Injection into existing processes
# OPSEC Uses RWX stub - Detected by Get-InjectedThread. Less detected by some defensive products.
#NtQueueApcThread;
# CreateRemotThread - Vanilla cross process injection technique. Doesn't cross session boundaries
# OPSEC - fires Sysmon Event 8
CreateRemoteThread;
# RtlCreateUserThread - Supports all architecture dependent corner cases (e.g., 32bit -> 64bit injection) AND injection across session boundaries
# OPSEC - fires Sysmon Event 8. Uses Meterpreter implementation and RWX stub - Detected by Get-InjectedThread
RtlCreateUserThread;
}
让我们看看其他效果注入的,我这里拉起一个自己编写的32位进程让前面的注入技术失败,到达CreateRemoteThread注入技术:
注入前:
注入后,可以看到又多了很多线程,其中一个起始地址的0x0的很奇怪:
我们点进去分析一下这个线程的堆栈,注意到15号了吗,调用了sleep休眠,16到21都是一些自加密内存的引用,这是CS自身加密的特征,CS在休眠的时候会把自己内存加密起来防止edr扫描内存,现在这些加密的本身也成了特征
现在我们验证刚刚的分析kill掉这个线程,果然控制台显示掉线了
你也许会好奇其他线程是什么,要不要管?实际上在结束掉核心的线程,之后其他的非正常的线程要不了多久就会自动退出,我们无需太过担忧这个事情,就像下图,一会后就基本退出完了
看起来无论使用什么注入技术,运行之后的行为取决于shellcode的工作原理,现在我们再看看其他C2的默认线程堆栈调用:
这是大名鼎鼎的metasploit注入后的shellcode堆栈调用:
因为我使用的是最新的msf,它现在也默认在使用syscall注入了,比较有意思的没想到process exploer居然没办法看到它的tcp连接,msf没有休眠机制默认应该可以看到tcp连接,连同Procmon也没法看到TCP连接
这是大名鼎鼎的sliver注入后的shellcode堆栈调用,是的,最新的默认的注入也是用了syscall:
默认堆栈,我想我们已经很熟悉了,这是session状态的:
它的TCP连接状态倒是能够看到:
上面的依然是理想状态,实际上即使锁定到了特定的进程,依然要分析大量的线程,我正常电脑的expoloer进程维持接近600个线程,完全手工去分析可疑的起始地址依旧是艰苦的工作
不过好消息是微软的process exploer有排序的功能,只需要点击Start Address就能只能把0x000的可疑线程拉到第一个,哈哈哈,虚拟机里面没多少,一个个看也很快
其他威胁狩猎场景
你应该注意到yara的强大之处在于它的灵活匹配,实际上yara完全可以狩猎其他类型的威胁,比方说在磁盘上的webshell或者恶意的驱动,甚至是日志中的漏洞利用的痕迹,如果拥有精准的yara规则甚至能精切到攻击者获取攻击者的每一步痕迹
狩猎磁盘上的威胁
高级权限维持:诸如隐藏极深的DLL代理用手工去排查几乎不可能,而Yara组合签名则能轻松识别
rule DLLProxying {
condition:
// Check for presence of DLL_PROCESS_ATTACH in DllMain function
uint16(0) == 0x6461 and (
// Check for the presence of LoadLibrary, which is used to load the legitimate DLL
uint32(2) == 0x6C6C6100 and uint32(6) == 0x6574726F and
// Check for the presence of GetProcAddress, which is used to retrieve the addresses of the functions in the legitimate DLL
uint32(10) == 0x72630067 and uint32(14) == 0x61647079 and uint32(18) == 0x61636F00 and uint32(22) == 0x0072696E and
// Check for the presence of a function that will be used to redirect function calls to the legitimate DLL
// This example uses a function named "ProxyFunction", but the function name can be anything
uint32(26) == 0x646E6900 and uint32(30) == 0x00667379
)
// Check for presence of dllexport attribute on the function that redirects calls to the legitimate DLL
// This example uses a function named "ProxyFunction", but the function name can be anything
and (pe.exports("ProxyFunction") or pe.exports("ProxyFunction@0"))
}
高级的Rootkit:高级的APT和红队非常喜欢使用有漏洞的驱动获得ring0权限来致盲edr,通过对存在漏洞的驱动进行打标,我们能轻松狩猎这种顶级的rootkit痕迹
漏洞利用的痕迹:不少web具备http日志,部分漏洞特征比较明显,也可以用Yara去匹配日志来识别,下面来自github编写的例子:
rule webshell_h4ntu_shell_powered_by_tsoi_ {
meta:
description = "Web Shell - file h4ntu shell [powered by tsoi].php"
author = "Florian Roth"
date = "2014/01/28"
score = 70
hash = "06ed0b2398f8096f1bebf092d0526137"
strings:
$s0 = " <TD><DIV STYLE=\"font-family: verdana; font-size: 10px;\"><b>Server Adress:</b"
$s3 = " <TD><DIV STYLE=\"font-family: verdana; font-size: 10px;\"><b>User Info:</b> ui"
$s4 = " <TD><DIV STYLE=\"font-family: verdana; font-size: 10px;\"><?= $info ?>: <?= "
$s5 = "<INPUT TYPE=\"text\" NAME=\"cmd\" value=\"<?php echo stripslashes(htmlentities($"
condition:
all of them
}
rule webshell_PHP_sql {
meta:
description = "Web Shell - file sql.php"
author = "Florian Roth"
date = "2014/01/28"
score = 70
hash = "2cf20a207695bbc2311a998d1d795c35"
strings:
$s0 = "$result=mysql_list_tables($db) or die (\"$h_error<b>\".mysql_error().\"</b>$f_"
$s4 = "print \"<a href=\\\"$_SERVER[PHP_SELF]?s=$s&login=$login&passwd=$passwd&"
condition:
all of them
}
总结
本篇主要介绍了yara的基本语法和使用场景,并分享了手工分析恶意线程的方法。从更高层次来看,攻防相当于双方的成本比拼,攻击者可以修改原有工具特征规避yara匹配,而防御方也能进一步编写独有的规则狩猎现有的恶意威胁,本篇文章希望减轻蓝队工作中排查的疼苦,然而yara总归只是特征检测,无法狩猎顶级APT使用的未知威胁,不过我想应付几次攻防演练已然足够。
参考资料:
https://yara.readthedocs.io/en/stable/writingrules.html#
https://github.com/chronicle/GCTI/
https://github.com/elastic/protections-artifacts/tree/main/yara/rules
https://nasbench.medium.com/windows-system-processes-an-overview-for-blue-teams-42fa7a617920
https://unprotect.it/technique/dll-proxying/
我的邮箱用的了cloudflare的邮件转发防止溯源,要交换友链的师傅麻烦发博客链接的时候也留个联系方式,我这边会主动加
最近我的博客上线了,欢迎各位大佬rss订阅,有大佬有兴趣也可以发邮件我邮箱hacker101@endlessparadox.com交换友链