-
-
[分享]手动映射sys到内核
-
发表于: 5天前 680
-
前言
说到过签大家第一想到的都是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即可,这种方式较为隐蔽,后续也可继续在此基础上改进
赞赏
- [分享]混淆、加密、反沙箱概念介绍 594
- [分享]手动映射sys到内核 681
- [分享]Minifilter简易文件访问监控 568
- [分享]内核逆向开发阶段评测方法 598
- [分享]游戏网络封包解析可行性分析 774