1.驱动开发:运用VAD隐藏R3内存思路2.驱动开发:应用DeviceIoContro模板精讲3.驱动开发:取进程模块的函数地址4.驱动开发:内核读写内存多级偏移5.驱动开发:内核物理内存寻址读写6.驱动开发:内核远程线程实现DLL注入7.驱动开发:摘除InlineHook内核钩子8.驱动开发:内核中进程与句柄互转9.驱动开发:内核注册表增删改查10.驱动开发:基于事件同步的反向通信11.驱动开发:文件微过滤驱动入门12.驱动开发:内核RIP劫持实现DLL注入13.驱动开发:内核解锁与强删文件14.驱动开发:内核ShellCode线程注入15.驱动开发:内核LoadLibrary实现DLL注入16.驱动开发:内核遍历文件或目录17.驱动开发:内核文件读写系列函数18.驱动开发:内核封装WFP防火墙入门
19.驱动开发:PE导出函数与RVA转换
20.驱动开发:内核扫描SSDT挂钩状态21.驱动开发:内核实现SSDT挂钩与摘钩22.驱动开发:内核PE结构VA与FOA转换23.驱动开发:内核解析PE结构节表24.驱动开发:内核解析PE结构导出表25.驱动开发:内核读写内存浮点数26.驱动开发:内核解析内存四级页表27.驱动开发:内核实现进程汇编与反汇编28.驱动开发:通过应用堆实现多次通信29.驱动开发:内核远程堆分配与销毁30.驱动开发:通过MDL映射实现多次通信31.驱动开发:内核使用IO/DPC定时器32.驱动开发:探索DRIVER_OBJECT驱动对象33.驱动开发:配置Visual Studio驱动开发环境34.驱动开发:内核封装TDI网络通信接口35.驱动开发:内核封装WSK网络通信接口36.驱动开发:内核层InlineHook挂钩函数37.驱动开发:内核LDE64引擎计算汇编长度38.驱动开发:内核强制结束进程运行39.驱动开发:内核监控FileObject文件回调40.驱动开发:内核监控Register注册表回调41.驱动开发:内核运用LoadImage屏蔽驱动42.驱动开发:内核监视LoadImage映像回调43.驱动开发:内核无痕隐藏自身分析44.驱动开发:内核注册并监控对象回调45.驱动开发:内核监控进程与线程回调46.驱动开发:内核测试模式过DSE签名47.驱动开发:内核枚举进程与线程ObCall回调48.驱动开发:内核枚举Registry注册表回调49.驱动开发:内核枚举LoadImage映像回调50.驱动开发:内核枚举ShadowSSDT基址51.驱动开发:Win10枚举完整SSDT地址表52.驱动开发:Win10内核枚举SSDT表基址53.驱动开发:内核特征码扫描PE代码段54.驱动开发:内核枚举Minifilter微过滤驱动55.驱动开发:内核特征码搜索函数封装56.驱动开发:内核枚举驱动内线程(答疑篇)57.驱动开发:内核枚举PspCidTable句柄表58.驱动开发:内核枚举DpcTimer定时器59.驱动开发:如何枚举所有SSDT表地址60.驱动开发:内核枚举IoTimer定时器61.驱动开发:内核遍历进程VAD结构体62.驱动开发:内核中实现Dump进程转储63.驱动开发:内核R3与R0内存映射拷贝64.驱动开发:内核通过PEB得到进程参数65.驱动开发:内核取应用层模块基地址66.驱动开发:内核取ntoskrnl模块基地址67.驱动开发:判断自身是否加载成功68.驱动开发:应用DeviceIoContro开发模板69.驱动开发:通过Async反向与内核通信70.驱动开发:通过PIPE管道与内核层通信71.驱动通信:通过PIPE管道与内核层通信72.驱动开发:通过ReadFile与内核层通信73.驱动开发:内核字符串拷贝与比较74.驱动开发:内核字符串转换方法75.驱动开发:内核中的自旋锁结构76.驱动开发:内核CR3切换读写内存77.驱动开发:内核中的链表与结构体78.驱动开发:摘链DKOM进程隐藏79.驱动开发:WinDBG 枚举SSDT以及SSSDT地址80.驱动开发:实现驱动加载卸载工具81.驱动开发:断链隐藏驱动程序自身82.驱动开发:通过内存拷贝读写内存83.驱动开发:内核MDL读写进程内存84.驱动开发:内核监控进程与线程创建85.驱动开发:监控进程与线程对象操作86.驱动开发:WinDBG 常用调试命令总结87.驱动开发:通过SystemBuf与内核层通信88.驱动开发:对象回调监控文件访问89.驱动开发:内核中枚举进线程与模块90.驱动开发:DKOM 实现进程隐藏91.驱动开发:内核读取SSDT表基址92.驱动开发:驱动与应用的简单通信93.驱动开发:恢复SSDT内核钩子94.驱动开发:挂接SSDT内核钩子95.驱动开发:WinDBG 配置内核双机调试96.VS2013+WDK8.1 驱动开发环境配置97.驱动开发:派遣函数与设备对象在笔者上篇文章《驱动开发:内核扫描SSDT挂钩状态》
中简单介绍了如何扫描被挂钩的SSDT函数,并简单介绍了如何解析导出表,本章将继续延申PE导出表的解析,实现一系列灵活的解析如通过传入函数名解析出函数的RVA偏移,ID索引,Index下标等参数,并将其封装为可直接使用的函数,以在后期需要时可以被直接引用,同样为了节约篇幅本章中的LoadKernelFile()
内存映射函数如需要使用请去前一篇文章中自行摘取。
首先实现GetRvaFromModuleName()
函数,当用户传入参数后自动将函数名解析为对应的RVA偏移或Index下标索引值,该函数接收三个参数传递,分别是wzFileName
模块名,FunctionName
所在模块内的函数名,Flag
标志参数,函数输出ULONG64
类型的数据。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
// 从指定模块中得到特定函数的RVA或相对序号相对偏移
ULONG64 GetRvaFromModuleName(WCHAR *wzFileName, UCHAR *FunctionName, INT Flag)
{
// 加载内核模块
PVOID BaseAddress = LoadKernelFile(wzFileName);
// 取出导出表
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
// DLL内存数据转成DOS头结构
pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
// 取出PE头结构
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
// 判断PE头导出表是否为空
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
{
return 0;
}
// 取出导出表偏移
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 取出节头结构
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
// 遍历节结构进行地址运算
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 导出表地址
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数地址
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数名字
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数序号
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 分析导出表
ULONG uOffset;
LPSTR FunName;
ULONG uAddressOfNames;
ULONG TargetOff = 0;
for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
{
uAddressOfNames = *AddressOfNames;
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
// 如果找到则返回RVA
if (!_stricmp((const char *)FunctionName, FunName))
{
// 等于1则返回RVA
if (Flag == 1)
{
TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
// DbgPrint("索引 [ %p ] 函数名 [ %s ] 相对RVA [ %p ] \n", *AddressOfNameOrdinals, FunName, TargetOff);
return TargetOff;
}
// 返回索引
else if (Flag == 0)
{
return *AddressOfNameOrdinals;
}
}
}
// 结束后释放内存
ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
return 0;
}
调用该函数很容易,传入模块路径以及该模块内的函数名,解析出RVA地址或Index下标。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
// 函数分别传入 [模块路径,函数名,标志=1] 返回该导出函数的RVA
ULONG64 get_rva = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 1);
DbgPrint("NtReadFile RVA = %p \n", get_rva);
// 函数分别传入 [模块路径,函数名,标志=0] 返回该导出函数的ID下标
ULONG64 get_id = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 0);
DbgPrint("NtReadFile ID = %d \n", get_id);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
编译并运行程序,分别获取到ntoskrnl.exe
模块内NtReadFile
函数的RVA,Index索引,调用效果如下;
第二个函数GetModuleNameFromRVA()
则实现传入RVA或者函数Index序号,解析出函数名,具体实现方法与如上函数基本一致,仅仅只是在过滤时做了调整。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
// 根据传入的函数RVA或Index下标,获取该函数的函数名
PCHAR GetModuleNameFromRVA(WCHAR *wzFileName, ULONG64 uRVA, INT Flag)
{
// 加载内核模块
PVOID BaseAddress = LoadKernelFile(wzFileName);
// 取出导出表
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
// DLL内存数据转成DOS头结构
pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
// 取出PE头结构
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
// 判断PE头导出表是否为空
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
{
return 0;
}
// 取出导出表偏移
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 取出节头结构
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
// 遍历节结构进行地址运算
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 导出表地址
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数地址
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数名字
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数序号
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 分析导出表
ULONG uOffset;
LPSTR FunName;
ULONG uAddressOfNames;
ULONG TargetOff = 0;
for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
{
uAddressOfNames = *AddressOfNames;
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
// 等于1则通过RVA返回函数名
if (Flag == 1)
{
if (uRVA == TargetOff)
{
return FunName;
}
}
// 返回索引
else if (Flag == 0)
{
if (uRVA == *AddressOfNameOrdinals)
{
return FunName;
}
}
}
// 结束后释放内存
ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
return "None";
}
调用GetModuleNameFromRVA()
并传入相应的RVA偏移或Index下标。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
PCHAR function_name;
// 传入函数RVA得到函数名
function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 0x5e5220, 1);
DbgPrint("根据RVA得到函数名 = %s \n", function_name);
// 传入函数下标得到函数名
function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 1472, 0);
DbgPrint("根据Index得到函数名 = %s \n", function_name);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
编译并运行程序,调用后分别获取到RVA=0x5e5220
或Index=1472
的函数名;