滥用合法驱动的艺术

本文首发于先知社区 https://xz.aliyun.com/news/92398 在红队行动的过程中越来越多的Windows机器开启了PPL并且安装了EDR,在用户态下直接对lsass进程的内存读取几乎变的不可能,我看完了世界上所有关于dump lsass的相关的技术,可以确定之前的技术在现代防御全开的机器下基本上都已经全部失效,本文将通过滥用合法驱动直接读取lsass的物理内存轻松绕过了PPL,顺便吊打所有EDR,完整的代码已经上传Github,为保证大部分读者阅读体验,快速补充相关的windows机制基础知识。 PPL的机制 PPL的全程是Protected Process Light(Windows进程保护等级),微软最初为了版权的控制设计的机制,现在拿来保护lsass进程不被用户态下的任意进程读取内存,即使你具备最高的管理员或者System权限依然无法读取被PPL保护起来的内存。 PPL的本质是进程在内核空间维护的数据,这个数据叫做_EPROCESS,里面有一个Protection,不同内部结构在_EPROCESS偏移不同,完整结构见文档,这个标志位是在进程创建开始的时候指定的,并且需要系统特定的签名证书才能成功设置 struct _EPROCESS { struct _KPROCESS Pcb; //0x0 struct _EX_PUSH_LOCK ProcessLock; //0x1c8 VOID* UniqueProcessId; //0x1d0 struct _LIST_ENTRY ActiveProcessLinks; //0x1d8 struct _EX_RUNDOWN_REF RundownProtect; //0x1e8 ...... struct _PS_PROTECTION Protection; //0x5fa //这个Protection就是我们控制的 这个数据的类型为_PS_PROTECTION,名字叫Level,Level保存着三个变量 //0x1 bytes (sizeof) struct _PS_PROTECTION { union { UCHAR Level; //0x0 struct { UCHAR Type:3; //0x0 UCHAR Audit:1; //0x0 UCHAR Signer:4; //0x0 }; }; }; 分别是Signer、Audit、Type,一个bytes里面有8bit,如下放置 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 Signer(4 bits) | Audit(1 bit) | Type(3 bits) 不同的PPL进程也有自己的访问控制,在同一Type下比较时,Signe数值通常越大,代表信任/保护等级越高,观察到windows defender的组件是的保护等级是Light (Antimalware),系统核心进程的保护等级是Light (Windows)或者Light (winTcb) 从高到低,意味着系统进程可以访问windows defender的进程,而windows defender在Light (Antimalware)等级,意味着即使defender被漏洞等其他原因exploit了也无法访问比自己更高的保护等级 常规的安全软件的核心PPL进程被C:\Windows\System32\services.exe托管,着意味着第三方的组件的父进程都将被services拉起,这是合理的,因为即使是带签名的杀毒本身也无法直接拉起PPL进程,services在系统启动的那一刻就已经运行,具备受信任的能力被所有的杀毒/EDR组件托管,EDR安装时候向服务发起请求,托管到服务中,系统服务代劳核心PPL保护进程的启动 请注意,PPL只是保护等级,windows ACL依然生效,意味着如果一个进程PPL等级是普通用户进程,它在访问system进程的时候依然需要通过windows权限校验,结果显而易见是失败的。 Windows的虚拟内存和物理内存 现代64位的windows系统,每个进程都拥有自己独立的虚拟内存空间,所以不同进程之间写入同一个虚拟地址不会互相干扰。 ...

July 6, 2026 · 7 min · Theme PaperMod

Windows下无法被杀死的进程和线程

前段时间我某次逆向一个程序,发现有一个处于无敌的进程组件用火绒剑都杀不死,进程一直悬挂在进程列表,这意味着内核ZwTerminateProcess都无法杀死这个进程,当时立刻吓我一跳,第一时间我判断能做到这种事情的只有rootkit和bookit技术,难道我这种老司机还能翻车被恶意软件感染了,当时立刻重装系统,并且启用了secureboot,不过现在我找到了答案,让我们一探究竟。 复现场景 notmyfault64.exe可以非常适合复现这个场景,读者可以这里下载, 运行即可获得一个无法杀死的进程: 点击huang,然后尝试使用火绒剑杀死这个进程: 这确实很反常识,内核居然有杀不死的进程: 我快速逆向发现这个工具的驱动使用了KeWaitForSingleObject,这意味着线程一直在内核模式等待一个无法发生的事件,非常有意思。 进一步测试 这玩意有没有潜力用于恶意软件开发呢?我编写了如下驱动代码测试: #include <ntddk.h> //#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access) #define IOCTL_Device_Usermode CTL_CODE(FILE_DEVICE_UNKNOWN, 0x008, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_Device_Usermode_APC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x009, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_Device_Kernelmode CTL_CODE(FILE_DEVICE_UNKNOWN, 0x010, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_Device_Kernelmode_APC CTL_CODE(FILE_DEVICE_UNKNOWN, 0x020, METHOD_BUFFERED, FILE_ANY_ACCESS) void unloadriver(PDRIVER_OBJECT driver_object) { UNREFERENCED_PARAMETER(driver_object); } NTSTATUS dispatch_function( _In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp ) { Irp->IoStatus.Status = STATUS_UNSUCCESSFUL; DbgPrint("Good to in the dispatch ruteam"); UNREFERENCED_PARAMETER(DeviceObject); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); KEVENT event = { 0 }; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_Device_Usermode: KeInitializeEvent(&event, SynchronizationEvent, 0); KeWaitForSingleObject(&event, UserRequest, UserMode, 0, nullptr); break; case IOCTL_Device_Usermode_APC: KeInitializeEvent(&event, SynchronizationEvent, 0); KeWaitForSingleObject(&event, UserRequest, UserMode, 1, nullptr); break; case IOCTL_Device_Kernelmode: KeInitializeEvent(&event, SynchronizationEvent, 0); KeWaitForSingleObject(&event, UserRequest, KernelMode, 0, nullptr); break; case IOCTL_Device_Kernelmode_APC: KeInitializeEvent(&event, SynchronizationEvent, 0); KeWaitForSingleObject(&event, UserRequest, KernelMode, 1, nullptr); break; default: DbgPrint("no macth any ioctl"); return -1; } IofCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS default_dispatch( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { UNREFERENCED_PARAMETER(DeviceObject); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT object , PUNICODE_STRING Regstring) { UNREFERENCED_PARAMETER(Regstring); UNICODE_STRING devname; RtlInitUnicodeString(&devname, L"\\Device\\killmyifyoucan"); PDEVICE_OBJECT deviceobject; IoCreateDevice(object, 0, &devname, FILE_DEVICE_UNKNOWN, 0, 1, &deviceobject); UNICODE_STRING symlink; RtlInitUnicodeString(&symlink,L"\\DosDevices\\killmeifyoucan"); IoCreateSymbolicLink(&symlink,&devname); object->DriverUnload = unloadriver; object->MajorFunction[IRP_MJ_CREATE] = default_dispatch; object->MajorFunction[IRP_MJ_CLOSE] = default_dispatch; object->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatch_function; return 0; } 客户端: ...

July 5, 2026 · 2 min · Theme PaperMod

SGN算法可能已经死了|The SGN algorithm is dead.

The SGN algorithm is dead. Yara hunting for the sgn encode , i success 100% hunting all the sgn in the last relase version . SGN算法是什么? SGN算法是一种变异处理shellcode算法,被渗透测试、红队、APT广泛的使用,具体这个地址了解: https://github.com/EgeBalci/sgn 截至目前2026年3月16日,目前Sgn的混淆算法依然可以到VT0: 挑战编写Yara识别SGN算法 Github悬赏的挑战,比特币换算大概有1000RMB,我没有比特币的钱包地址,不过我依然决定亲自挑战一下全世界安全研究员都没做到的事情,疑似? 这个规则前几个月,我肝了几周写完的,本来想整理出来复习一下,但是现在时间隔得有点久了,没写记录,完全忘记了之前具体是怎么样分析SGN算法的(哈哈哈,专家一样会遗忘,我又不是机器人),想了一下算了,不写分析了,重新分析又要浪费好几天时间,目前精力主要在windows内核开发和基础的学习,就直接分享出来吧。。。。。 具体规则 注意,下面这个规则必须最新的yara版本才能解析,我不会提供老版本yara的语法规则。 具体Github地址,已经设置MIT版权了,随便用:https://github.com/kaliworld/theyaraforsgn 记得给我star,谢谢 rule sgn_plain_x86 { meta: description = "高置信度的 SGN x86 明文解码器 stub" author = "EndlessParadox" confidence = "high" strings: $stub = { E8 00 00 00 00 5? B9 ?? ?? ?? ?? [0-4] B? ?? [0-4] 30 ?? [2-8] 02 ?? [1-8] E2 ?? } condition: filesize > 32 and $stub in (0..64) } rule sgn_plain_x64 { meta: description = "高置信度的 SGN x64 明文解码器 stub" author = "EndlessParadox" confidence = "high" strings: $stub = { 48 C7 C1 ?? ?? ?? ?? [0-12] ( 48 | 49 | 4C | 4D ) 8D ?? ?? ?? ?? ?? [0-8] ( 30 ?? | 40 30 ?? | 41 30 ?? | 44 30 ?? | 45 30 ?? ) [0-6] ( 02 ?? | 40 02 ?? | 41 02 ?? | 44 02 ?? | 45 02 ?? ) [0-6] E2 ?? } condition: filesize > 32 and $stub in (0..96) } rule sgn_schema_x86 { meta: description = "高置信度的 SGN x86 架构型解码器" author = "EndlessParadox" confidence = "high" strings: $call = { E8 ?? ?? ?? ?? } $jmp = { FF E? } $tail = { 5? [0-64] ( 81 ?? ?? ?? ?? ?? | 81 ?? ?? ?? ?? ?? ?? | F7 1? | F7 5? ?? | C1 0? ?? | C1 4? ?? ?? ) [0-48] ( 81 ?? ?? ?? ?? ?? | 81 ?? ?? ?? ?? ?? ?? | F7 1? | F7 5? ?? | C1 0? ?? | C1 4? ?? ?? ) [0-48] ( 81 ?? ?? ?? ?? ?? | 81 ?? ?? ?? ?? ?? ?? | F7 1? | F7 5? ?? | C1 0? ?? | C1 4? ?? ?? ) [0-64] FF E? } condition: filesize > 96 and $call in (0..64) and $jmp in (filesize-8..filesize) and $tail } rule sgn_schema_x64 { meta: description = "高置信度的 SGN x64 架构型解码器" author = "EndlessParadox" confidence = "high" strings: $call = { E8 ?? ?? ?? ?? } $jmp_lo = { FF E? } $jmp_hi = { 41 FF E? } $tail_lo = { 5? [0-64] ( 81 ?? ?? ?? ?? ?? | 81 ?? ?? ?? ?? ?? ?? | F7 1? | F7 5? ?? | C1 0? ?? | C1 4? ?? ?? ) [0-48] ( 81 ?? ?? ?? ?? ?? | 81 ?? ?? ?? ?? ?? ?? | F7 1? | F7 5? ?? | C1 0? ?? | C1 4? ?? ?? ) [0-48] ( 81 ?? ?? ?? ?? ?? | 81 ?? ?? ?? ?? ?? ?? | F7 1? | F7 5? ?? | C1 0? ?? | C1 4? ?? ?? ) [0-64] FF E? } $tail_hi = { 41 5? [0-64] ( 41 81 ?? ?? ?? ?? ?? | 41 81 ?? ?? ?? ?? ?? ?? | 41 F7 1? | 41 F7 5? ?? | 41 C1 0? ?? | 41 C1 4? ?? ?? ) [0-48] ( 41 81 ?? ?? ?? ?? ?? | 41 81 ?? ?? ?? ?? ?? ?? | 41 F7 1? | 41 F7 5? ?? | 41 C1 0? ?? | 41 C1 4? ?? ?? ) [0-48] ( 41 81 ?? ?? ?? ?? ?? | 41 81 ?? ?? ?? ?? ?? ?? | 41 F7 1? | 41 F7 5? ?? | 41 C1 0? ?? | 41 C1 4? ?? ?? ) [0-64] 41 FF E? } condition: filesize > 96 and $call in (0..64) and ( $jmp_lo in (filesize-8..filesize) or $jmp_hi in (filesize-8..filesize) ) and any of ($tail*) } rule sgn_high_confidence { meta: description = "高置信度的 SGN 编码器检测规则" author = "EndlessParadox" profile = "high-confidence-SGN" release = "1.0" condition: sgn_plain_x86 or sgn_plain_x64 or sgn_schema_x86 or sgn_schema_x64 } 识别效果 msf生成shellcode: ...

March 16, 2026 · 4 min · Theme PaperMod

最新的CVE申请经验分享和我的一些工作感想

看到互联网上的经验都比较老了,我就分享一下2026年最新的申请CVE过程和经验,后续还夹杂着我的一些工作感想。 自行CVE申请 申请的官网 : https://cveform.mitre.org/ :选中Repoort直接报告即可: Attack vector(s) 通常可以填Github的issue链接或者自己建一个在公共空间填写的POC 向作者申请 如果是挖掘的开源软件,也可以直接使用Github内置的报告和开发者报告,这个效率非常高,开发者披露后走的是github分配的: CVE申请的限制和审核加强 CVE拒绝非正式版本的申请,我之前申请的bete版的漏洞拒绝了申请,具体请看 This request is relation to the beta version of a product. We are unable to assign for beta versions as they are not publicly available. 同时,并非所有的安全问题都有CVE或者适合CVE:举两个非常典型的例子,我在Github的开源仓库看到了大量的docker.socket不正确挂载导致容器的逃逸 类似 Dpanel,这种问题虽然严重,实战效果也很强,依然不会分配编号;另一个例子是红队常用的 Winodows降级攻击 WindowsDowndate ,这类利用天然的设计来带的安全问题也不会被收录到CVE 不确定Ai模型的问题会不会收录,我没有挖掘过任何ai相关的风险漏洞,不建议申请这类CVE,我目前没看到任何有关ai模型本身出现的幻觉或者注入会给CVE的,当然如果是涉及ai产品就是另外一回事了 CVE 申请效率 4ra1n以前的博客指出自行申请的效率: 实际上在2026年并不是如此,我的经验基本上一个月或者两个月就通过一批CVE(陆续送报的),很大可能和我们申请CVE的数量和排队有关,我猜测可能和邮件和批量审批有关,总之MITRE效率相比以前提高了很多,具体请看彩蛋。 CVE公开 等待CVE的回信,回信完毕会在档案里面写编号,第二步是申请公开,下面填入你的编号: 注意Link to the advisory不能填写github issue,我之前用的github issue写了官方回信如下: 最好自己写一个博客,然后写好漏洞分析,之后表格申请公开之后,等待一段时间(几天的时间)会收到一封邮件,内容大概是是漏洞将会在几个小时公开: 等待一会就能看到官方之后官网公布(这里点名表扬一下bing搜索当天就收录了,谷歌搜索还是空的,谷歌的搜索真不如bing优化的好),刚公开的会显示 “This CVE record has been marked for NVD enrichment efforts.",还需要等待NVD进行完整的漏洞评价: ...

March 16, 2026 · 1 min · Theme PaperMod

渗透测试实战回忆录

本文首发于先知社区 https://xz.aliyun.com/news/91353 回忆我这几年的失误和技术误解,并且分享有趣的故事—-几次打崩系统的故事,并且从中总结失败的教训,回忆稍显繁琐。本人已经没有参加任何比赛性质的攻防演练,均直接服务于项目,如有雷同,纯属巧合,所有渗透行为均合法授权,请不要瞎溯源。 第一次崩溃 大概是刚毕业出来工作的时候,在那个时候我还是个新手菜鸟,摆弄着各种大佬写的工具,那个时候,我开始熟悉冰蝎和Cobaltstirke,我发现了一个震惊我的功能,冰蝎居然能”一键上线CS“,对于不懂jni、汇编、架构和系统的那个时候的我,我震惊的,本地搭建环境测试了还成功了,轻松吊打360\微软DF,一个webshell 怎么可能打入EXE在内部执行? 但是测试过程出了一个问题,在我的本机搭建的环境测试非常顺利,一键上线绕过了我们国内主流的杀毒,上线执行命令一切正常,而我朋友Arui的机器上测试,每次都会崩溃,我们开始争辩,但是你懂的,讨论不出什么结论,因为我们那个时候知识有限,我们当时不得不得出这个技术不稳定的错误结论,哈哈哈。 那么到底为什么? 我和周边的朋友都是web出身的,基本上搞二进制的凤毛麟角,同时懂web和二进制这两个就更少了,22年当初问了一圈都没人能回答这个问题,过了几年,我开始掌握二进制的知识,然后翻阅了更新日志,看到了下面一条。 https://github.com/rebeyond/Behinder/releases 内存马shellcode注入前增加了CPU架构判断 4.1版本之前内存马shellcode居然没有CPU架构判断,所以答案已经很明晓,强制给32位的java塞一个64位的dll,架构都不一样肯定崩溃,也就是我同事的环境,可能还在用32位的java+windows测试环境,这要是实战,我们只能得出对面关闭了网站 我们再看一下源代码,这个是之前的64位和32位的判定: https://github.com/MountCloud/BehinderClientSource/blob/d146e76ec90dbb5f1454dd77ec8e2368b4eadf6f/src/main/java/net/rebeyond/behinder/ui/controller/ReverseViewController.java#L181 选择随机保存dll到如下目录 String remoteUploadPath = "c:/windows/temp/" + Utils.getRandomString((new Random()).nextInt(10)) + ".log"; 判定架构进入分支 (String)this.basicInfoMap.get("arch")).toString().indexOf("64") >= 0 是64位直接打入JavaNative_x64.dll,否则打入JavaNative_x32.dll if os == 64: inject JavaNative_x64.dll and shellcode else: inject JavaNative_x32.dll and shellcode 这样写修复了x86-64的问题,但是很明显,全世界有大量不同的架构,比方说ARM架构和RISC-V,这样就会直接进入inject JavaNative_x32.dll and shellcode打崩JVM 深入解释原因 等等,刚刚的就是正确的答案吗,崩溃的含义是什么?刚刚我们并没有解释清楚哪里崩了,对吧?已经是3年前的事情了,如果有dumpcrash的文件,我瞄一眼就能定位,但现在我只能尝试推测还原,让我们把问题分解开来,其实就是三部分:windows的问题、java自己的问题、载入的dll的问题: 首先,先理解JNI的本质只是调用LoadLibrary,一个64位的java.exe PE调用LoadLibrary加载32位的DLL是不会导致崩溃的,你可以编译一个64位的PE验证这一观点,它只会返回一个错误code回来,这可不会带崩进程和后续代码的执行逻辑: // demo.cpp : This file contains the 'main' function. Program execution begins and ends there. // #include <iostream> #include <Windows.h> int main() { std::cout << "SysWOW64 is 32 bit PE!\n"; LoadLibraryA("C:\\Windows\\SysWOW64\\ntdll.dll"); std::cout << "the lasterror is " << GetLastError() << std::endl; std::cout << "It is ok the countine execute code\n"; } 执行结果如下: ...

January 30, 2026 · 7 min · Theme PaperMod