首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
7
7
[分享]手动映射sys到内核
发表于: 2026-3-17 12:26
3255
[分享]手动映射sys到内核
mb_binusgki
2026-3-17 12:26
3255
# 前言 说到过签大家第一想到的都是BYOVD(Bring Your Own Vulnerable Driver),原理是: 攻击者加载一个 合法签名但存在漏洞的驱动,然后利用该驱动:读写任意内核内存、修改内核数据结构、关闭安全机制、手动映射未签名驱动 然而BYOVD一般分为两步,第一步是找到IoControl有漏洞的驱动并解析它,第二就是利用该漏洞驱动提供的读写内存的能力将没有签名的驱动弄进系统 这里又分为两步:第一种解决方案为Driver Signature Enforcement (DSE)绕过(禁用驱动签名),直接修改CI.dll的g_CiOptions = 0 这样就禁用了驱动签名任何驱动都能加载,就可以走正常的加载驱动流程 第二种方式就是手动映射驱动(Manual Map)类似 DLL Manual Map 流程为:禁用PatchGuard / DSE、解析PE、分配内核内存、复制sections、修复Relocation、修复ImportTable、调用DriverEntry等手动的加载sys,这种方法的优点是根本不走官方流程,自然也不会在PsLoadedModuleList留痕,比起第一种方式更加隐蔽,灵活性也更强 今天笔者就来实现如何手动在内核映射sys,至于找漏洞驱动并解析本节并不涉及 以下流程和DLL Manual Map几乎一模一样,开始: # 解析DOS头 这里的buffer就是sys或者shellcode ``` PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buffer; if (dos->e_magic != IMAGE_DOS_SIGNATURE) return STATUS_INVALID_IMAGE_FORMAT; ``` # 判断是否为pe程序 ``` PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)((PUCHAR)buffer + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) return STATUS_INVALID_IMAGE_FORMAT; ``` # 申请并初始化空间 ``` SIZE_T imageSize = nt->OptionalHeader.SizeOfImage; PVOID imageBase = ExAllocatePoolWithTag(NonPagedPool, imageSize, 'pmam'); if (!imageBase) return STATUS_INSUFFICIENT_RESOURCES; RtlZeroMemory(imageBase, imageSize); ``` # 复制 PE Header ``` RtlCopyMemory( imageBase, buffer, nt->OptionalHeader.SizeOfHeaders ); ``` # 复制section ``` PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt); for (UINT32 i = 0; i < nt->FileHeader.NumberOfSections; i++) { PVOID dest = (PUCHAR)imageBase + section[i].VirtualAddress; PVOID src = (PUCHAR)buffer + section[i].PointerToRawData; SIZE_T size = section[i].SizeOfRawData; if (size) { RtlCopyMemory(dest, src, size); } } ``` # 修复重定位 ``` BOOLEAN FixRelocation(PVOID newBase, PIMAGE_NT_HEADERS64 nt) { ULONGLONG delta = (ULONGLONG)newBase - nt->OptionalHeader.ImageBase; if (delta == 0) return TRUE; IMAGE_DATA_DIRECTORY relocDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; if (!relocDir.Size) return TRUE; PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)((PUCHAR)newBase + relocDir.VirtualAddress); while (reloc->VirtualAddress) { UINT32 count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT); PUSHORT list = (PUSHORT)((PUCHAR)reloc + sizeof(IMAGE_BASE_RELOCATION)); for (UINT32 i = 0; i < count; i++) { USHORT type = list[i] >> 12; USHORT offset = list[i] & 0xFFF; if (type == IMAGE_REL_BASED_DIR64) { PULONGLONG addr = (PULONGLONG)((PUCHAR)newBase + reloc->VirtualAddress + offset); *addr += delta; } } reloc = (PIMAGE_BASE_RELOCATION)((PUCHAR)reloc + reloc->SizeOfBlock); } return TRUE; } ``` # 修复 ImportTable ``` BOOLEAN FixImportTable(PVOID imageBase, PIMAGE_NT_HEADERS64 nt) { IMAGE_DATA_DIRECTORY importDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (!importDir.Size) return TRUE; PIMAGE_IMPORT_DESCRIPTOR import = (PIMAGE_IMPORT_DESCRIPTOR)((PUCHAR)imageBase + importDir.VirtualAddress); while (import->Name) { PCSTR moduleName = (PCSTR)((PUCHAR)imageBase + import->Name); //PVOID moduleBase = NtoskrnlBase; PVOID moduleBase = GetKernelModuleBaseByName(moduleName); if (!moduleBase) return FALSE; PIMAGE_THUNK_DATA64 thunk = (PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk); //PIMAGE_THUNK_DATA64 origThunk = // (PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk); PIMAGE_THUNK_DATA64 origThunk; if (import->OriginalFirstThunk) { origThunk = (PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk); } else { origThunk = (PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk); } while (origThunk->u1.AddressOfData) { PIMAGE_IMPORT_BY_NAME importName = (PIMAGE_IMPORT_BY_NAME)((PUCHAR)imageBase + origThunk->u1.AddressOfData); PVOID fn = GetKernelExport(moduleBase, importName->Name); if (!fn) return FALSE; thunk->u1.Function = (ULONGLONG)fn; origThunk++; thunk++; } import++; } return TRUE; } ``` # 执行entry 这里的driver和reg_path笔者没加因为是手动映射,如果要传参可以用 ``` DriverEntryFn entry = (DriverEntryFn)((PUCHAR)imageBase + entryRVA); NTSTATUS status = entry(); ``` # 定义被加载驱动 这里也是非常简单,只打印一个日志以证明是否运行成功 另外说下这种手动映射的方式其实就是shellcode的方式,只不过我们可以上驱动模板更方便开发,所以并不需要卸载函数,因为没走系统的注册流程 ``` //驱动入口 NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) { DbgBreakPoint(); //驱动程序入口 PZY_PRINT("驱动启动"); return STATUS_SUCCESS; } ``` # 测试   这里其实就是被调用驱动的DbgBreakPoint  走下去的call ffffbd0f`3f914030就是打印日志,也是成功看到驱动启动的日志  运行完后也是正常返回,这里因为逻辑简单笔者是直接进行了调用,但是逻辑复杂的情况下一定要上线程,否则可能会卡死  # 总结 经过测试发现:手动映射驱动的逻辑较为复杂,如果做BYOVD最好是先通过漏洞驱动关闭g_CiOptions,然后正常用注入驱动器注入要注入的驱动,注入完成后再关闭g_CiOptions即可,这种方式较为隐蔽,后续也可继续在此基础上改进
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-3-17 13:07 被mb_binusgki编辑 ,原因: 补充内容
#调试逆向
#系统底层
#问题讨论
#其他内容
收藏
・
7
点赞
・
7
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
jjjo
这个讨论对我很有帮助,谢谢!
2026-7-22 15:01
harx
期待更多优质内容的分享,论坛有你更精彩!
2026-5-19 14:10
白色风信子
非常支持你的观点!
2026-4-26 22:09
Rabbit0w0
感谢你的贡献,论坛因你而更加精彩!
2026-3-22 22:29
马来
为你点赞!
2026-3-18 10:38
新气象
非常支持你的观点!
2026-3-18 09:05
我的小拇指啊
为你点赞!
2026-3-17 15:47
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
harx
雪 币:
28
活跃值:
(30)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
2
粉丝
0
关注
私信
harx
2
楼
为什么你们这么厉害
2026-5-19 14:10
0
wx_垃圾债券之王
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
2
回帖
13
粉丝
1
关注
私信
wx_垃圾债券之王
3
楼
用RtlInsertInvertedFunctionTable处理下SEH你会发现 加了vmp的驱动也支持加载了,不过要立马remove掉 因为PsInvertedFunctionTable 受保护
2026-7-11 21:51
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mb_binusgki
29
发帖
6
回帖
0
RANK
关注
私信
他的文章
[分享]注册表回调内核通信
379
[分享]简单编译器实现
1869
[分享]Lexer简单词法解析器入门
1233
[分享]vm解释器入门2
1288
[分享]vm解释器入门
1587
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部