这个可以通过遍历系统模块,找到第一个被加载的模块(NTOS),获得NTOS的路径,基地址,大小:
基本思路为:
1.ZwQuerySystemInformation查询到所有模块
2.获得NTOS的路径,基地址,大小
代码如下:
NTSTATUS GetNtosModuleInfo(WCHAR *pNtosPath,ULONG nSize,
PULONG_PTR pNtosModBase,
PULONG_PTR pNtosModSize
)
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
PRTL_PROCESS_MODULES psmi = NULL;
ULONG ulSize = 0;
ULONG ulIndex = 0;
// 转换
PCHAR pszNtosName = NULL;
ANSI_STRING Ansi_szNtosName;
UNICODE_STRING Unicode_szNtosName;
__try
{
do
{
ntStatus = ZwQuerySystemInformation(SystemModuleInformation, NULL, 0, &ulSize);
if (STATUS_INFO_LENGTH_MISMATCH != ntStatus)
{
break;
}
psmi = (PRTL_PROCESS_MODULES)ExAllocatePoolWithTag(NonPagedPool, ulSize, '0YGH');
if (NULL == psmi)
{
break;
}
ntStatus = ZwQuerySystemInformation(SystemModuleInformation,
psmi, ulSize, &ulSize);
if (STATUS_SUCCESS != ntStatus)
{
break;
}
//遍历打印:
//for (ulIndex = 0; ulIndex<psmi->NumberOfModules; ulIndex++)
//{
// KdPrint(("[ModInfo]-nIndex:%u--base:%p--size:%p--name:%s\n", ulIndex, psmi->Modules[ulIndex].ImageBase, psmi->Modules[ulIndex].ImageSize, psmi->Modules[ulIndex].FullPathName));
//}
if (pNtosPath)
{
memset(pNtosPath, 0, sizeof(WCHAR)*nSize);
wcscpy(pNtosPath, L"\\SystemRoot\\system32\\");
pszNtosName = psmi->Modules[0].FullPathName+psmi->Modules[0].OffsetToFileName;
RtlInitAnsiString(&Ansi_szNtosName, pszNtosName);
RtlAnsiStringToUnicodeString( &Unicode_szNtosName, &Ansi_szNtosName, TRUE);
wcsncpy(pNtosPath+wcslen(pNtosPath), Unicode_szNtosName.Buffer,Unicode_szNtosName.Length);
RtlFreeUnicodeString(&Unicode_szNtosName);
}
if (pNtosModBase
&&MmIsAddressValid(pNtosModBase))
{
*pNtosModBase = psmi->Modules[0].ImageBase;
}
if (pNtosModSize
&&MmIsAddressValid(pNtosModSize))
{
*pNtosModSize = psmi->Modules[0].ImageSize;
}
} while (FALSE);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
if (NULL != psmi)
{
ExFreePool(psmi);
psmi = NULL;
}
return ntStatus;
}
NTSTATUS ReadFileToBuf(WCHAR *pFilePath, PVOID *ppBuf, PULONG_PTR pulSize)
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
HANDLE hFile = NULL;
OBJECT_ATTRIBUTES obj;
UNICODE_STRING Unicode_FilePath;
IO_STATUS_BLOCK iosb = {0};
IO_STATUS_BLOCK io_readsb = {0};
FILE_STANDARD_INFORMATION fInfo = {0};
ULONG_PTR ulSize = 0;
PVOID pBuf = NULL;
do
{
RtlInitUnicodeString(&Unicode_FilePath, pFilePath);
InitializeObjectAttributes(&obj, &Unicode_FilePath, OBJ_CASE_INSENSITIVE, NULL, NULL);
ntStatus = ZwOpenFile(&hFile, SYNCHRONIZE, &obj, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (STATUS_SUCCESS != ntStatus)
{
break;
}
ntStatus = ZwQueryInformationFile(hFile,
&iosb,
&fInfo,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation
);
if (STATUS_SUCCESS != ntStatus)
{
break;
}
ulSize = fInfo.EndOfFile.LowPart;
pBuf = ExAllocatePoolWithTag(PagedPool, ulSize+1024, '0YHG');
if (NULL == pBuf)
{
break;
}
ntStatus = ZwReadFile(hFile,
NULL,
NULL,
NULL,
&io_readsb,
pBuf,
ulSize,
0,
NULL);
if (STATUS_SUCCESS != ntStatus)
{
break;
}
if (io_readsb.Information != ulSize)
{
ntStatus = STATUS_UNSUCCESSFUL;
break;
}
// 赋值
if (pulSize)
{
*pulSize = ulSize;
}
if (ppBuf)
{
*ppBuf = pBuf;
}
} while (FALSE);
if (hFile)
{
ZwClose(hFile);
hFile = NULL;
}
if (STATUS_SUCCESS != ntStatus)
{
if (pBuf)
{
ExFreePool(pBuf);
pBuf = NULL;
}
}
return ntStatus;
}
一般建议分配多一点,并映射MDL得到虚拟地址可读可写可执行
NTSTATUS MemoryMapMdl(PVOID pBase, ULONG ulSize, PMDL* ppMdl, PVOID* ppMapBase)
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
PMDL pMdl = NULL;
PVOID pMdlVA = NULL;
do
{
pMdl = IoAllocateMdl(pBase, ulSize, FALSE, FALSE, NULL);
if (NULL == pMdl)
{
break;
}
__try
{
// 锁住,避免被page out,直接会导致MmGetSystemAddressForMdl蓝屏
MmProbeAndLockPages(pMdl, KernelMode, IoModifyAccess);
//MmBuildMdlForNonPagedPool(pMdl);//如果是非分页内存,可以使用这个,使用这个就不需要MmUnmapLockedPages&MmUnlockPages了
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
IoFreeMdl(pMdl);
break;
}
pMdlVA = MmMapLockedPagesSpecifyCache(pMdl, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);
if (NULL == pMdlVA)
{
MmUnlockPages(pMdl);
IoFreeMdl(pMdl);
break;
}
ntStatus = MmProtectMdlSystemAddress(pMdl, PAGE_EXECUTE_READWRITE);
if (STATUS_SUCCESS != ntStatus)
{
MmUnmapLockedPages(pMdlVA, pMdl);
MmUnlockPages(pMdl);
IoFreeMdl(pMdl);
break;
}
// 赋值
if (ppMapBase && MmIsAddressValid(ppMapBase))
{
*ppMapBase = pMdlVA;
}
if (ppMdl && MmIsAddressValid(ppMdl))
{
*ppMdl = pMdl;
}
} while (FALSE);
return ntStatus;
}
对MDL映射出来的内存清0,再复制Header,复制section
NTSTATUS CopySections(PVOID pBase, PVOID pData, PIMAGE_NT_HEADERS pNtHdr)
{
ULONG ulIndex = 0;
PIMAGE_SECTION_HEADER pSectionHdr = IMAGE_FIRST_SECTION(pNtHdr);
for (ulIndex=0; ulIndex<pNtHdr->FileHeader.NumberOfSections; ulIndex++)
{
RtlCopyMemory((PUCHAR)pBase+pSectionHdr->VirtualAddress, (PUCHAR)pData+pSectionHdr->PointerToRawData, pSectionHdr->SizeOfRawData);
pSectionHdr++;
}
return STATUS_SUCCESS;
}
1.像查找NTOS一样查找到依赖的dll的基地址
2.根据函数名搜索依赖的dll的导出表,找到函数地址,填充
3.在校验NTOS时,注意原始NTOS的导入表为init的,所以不存在了,需查看IAT表(12序号)
NTSTATUS BuildNtosImportTable(PVOID pBase)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
PIMAGE_NT_HEADERS pNt = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImport = NULL;
PIMAGE_THUNK_DATA pOrigThunk = NULL;
PIMAGE_THUNK_DATA pFirstThunk = NULL;
PIMAGE_IMPORT_BY_NAME pName = NULL;
ULONG_PTR FunctionAddr = 0;
ULONG_PTR ModBase = 0;
if (NULL == pDos
|| IMAGE_DOS_SIGNATURE != pDos->e_magic)
{
return STATUS_INVALID_IMAGE_FORMAT;
}
pNt = (PIMAGE_NT_HEADERS)((PUCHAR)pBase+pDos->e_lfanew);
if (IMAGE_NT_SIGNATURE != pNt->Signature)
{
return STATUS_INVALID_IMAGE_FORMAT;
}
if (0 == pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size)
{
return STATUS_SUCCESS;
}
pImport = (PIMAGE_IMPORT_DESCRIPTOR)((PUCHAR)pBase+pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while (NULL !=pImport
&& MmIsAddressValid(pImport)
&&pImport->Name != 0)
{
// 获得模块基地址
if (STATUS_SUCCESS != GetModuleInfo((PCHAR)((PUCHAR)pBase+pImport->Name), &ModBase))
{
return STATUS_UNSUCCESSFUL;
}
KdPrint(("[BuildNtosImportTable]-ModName:%s, ModBase:%p\n",(PCHAR)((PUCHAR)pBase+pImport->Name), ModBase));
if (pImport->OriginalFirstThunk)
{
pOrigThunk = (PIMAGE_THUNK_DATA)((PUCHAR)pBase+pImport->OriginalFirstThunk);
pFirstThunk = (PIMAGE_THUNK_DATA)((PUCHAR)pBase+pImport->FirstThunk);
}
else
{
pOrigThunk = (PIMAGE_THUNK_DATA)((PUCHAR)pBase+pImport->FirstThunk);
pFirstThunk = (PIMAGE_THUNK_DATA)((PUCHAR)pBase+pImport->FirstThunk);
}
while (pOrigThunk->u1.Ordinal)
{
// 序号导入
if (IMAGE_SNAP_BY_ORDINAL(pOrigThunk->u1.Ordinal))
{
FunctionAddr = GetExportFunAddr(ModBase,FALSE, NULL, pOrigThunk->u1.Ordinal& ~IMAGE_ORDINAL_FLAG32);
}
else
{
pName = (PIMAGE_IMPORT_BY_NAME)((PUCHAR)pBase+pOrigThunk->u1.Ordinal);
FunctionAddr = GetExportFunAddr(ModBase, TRUE, pName->Name, 0);
KdPrint(("[BuildNtosImportTable]-Mod:%s, Fun:%s[%p]\r\n",(PCHAR)((PUCHAR)pBase+pImport->Name), pName->Name, FunctionAddr));
}
if (FunctionAddr)
{
pFirstThunk->u1.Function = FunctionAddr;
}
else
{
KdPrint(("[BuildNtosImportTable]-Function fail\n"));
}
pOrigThunk++;
pFirstThunk++;
}
pImport++;
}
return STATUS_SUCCESS;
}
其目的是使用同一份全局变量,所以重定位差值一定要是原始NTOS-0X400000(OptionalHeader.ImageBase)
NTSTATUS BuildNtosRelocTable(PVOID pBase,PVOID pSysBase)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
PIMAGE_NT_HEADERS pNt = NULL;
PIMAGE_BASE_RELOCATION pBaseReloc = NULL;
ULONG_PTR ulDiff = 0;
if (NULL == pDos
|| IMAGE_DOS_SIGNATURE != pDos->e_magic)
{
return STATUS_INVALID_IMAGE_FORMAT;
}
pNt = (PIMAGE_NT_HEADERS)((PUCHAR)pBase+pDos->e_lfanew);
if (IMAGE_NT_SIGNATURE != pNt->Signature)
{
return STATUS_INVALID_IMAGE_FORMAT;
}
if (0 == pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size)
{
return STATUS_SUCCESS;
}
pBaseReloc = (PIMAGE_BASE_RELOCATION)((PCHAR)pBase+pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
if (NULL == pBaseReloc)
{
return STATUS_SUCCESS;
}
ulDiff = (ULONG_PTR)pSysBase - (ULONG_PTR)pNt->OptionalHeader.ImageBase;
if (pBaseReloc->SizeOfBlock>sizeof(IMAGE_BASE_RELOCATION))
{
do
{
ULONG ulCount = (pBaseReloc->SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/sizeof(USHORT);
PUSHORT pItem = (PUSHORT)((PCHAR)pBaseReloc+sizeof(IMAGE_BASE_RELOCATION));
PULONG_PTR pRelocTarget = NULL;
ULONG ulIndex = 0;
for (ulIndex = 0; ulIndex<ulCount; ulIndex++)
{
//高四位类型:
// IMAGE_REL_BASED_ABSOLUTE:无具体含义,仅是让每个段4字节对齐
// IMAGE_REL_BASED_HIGHLOW:重定位指向的整个地址都要被修正,基本是这个情况
// IMAGE_REL_BASED_DIR64:出现在64位PE文件中,对指向的整个地址修正.
ULONG ulType = pItem[ulIndex]>>12;// 高4位
ULONG ulOffset = pItem[ulIndex] & 0xFFF;// 低12位
switch (ulType)
{
case IMAGE_REL_BASED_HIGHLOW:
case IMAGE_REL_BASED_DIR64:
{
pRelocTarget = (PULONG_PTR)((PCHAR)pBase+ulOffset+pBaseReloc->VirtualAddress);
*pRelocTarget += ulDiff;// 跳转到原始的全局变量
}
break;
default:
break;
}
}
pBaseReloc = (PIMAGE_BASE_RELOCATION)((PCHAR)pBaseReloc+pBaseReloc->SizeOfBlock);
} while (pBaseReloc->SizeOfBlock);//Vista的内核下,最后一个的VirtualAddress有可能不为0!
}
return STATUS_SUCCESS;
}
这时可以用.reload /i 来再加载一份NT的pdb,可以看到,新的NT的SSDT对象内容全为0,所以需要重建:
// 仅在X86下使用
void SetNewSSDT(PVOID pNewBase,PVOID pSysBase)
{
//禁止写保护,不然蓝屏
ULONG ulOffset = (ULONG)pNewBase - (ULONG)pSysBase;
ULONG ulIndex = 0;
DisablePageWriteProtection
g_pNewSSDT = (PKSERVICE_TABLE_DESCRIPTOR)((PCHAR)KeServiceDescriptorTable+ulOffset);
if (!MmIsAddressValid(g_pNewSSDT))
{
g_pNewSSDT = NULL;
return;
}
g_pNewSSDT->TableSize = KeServiceDescriptorTable->TableSize;
g_pNewSSDT->ServiceTableBase = (PULONG)((PCHAR)KeServiceDescriptorTable->ServiceTableBase+ulOffset);
g_pNewSSDT->ArgumentTable = (PUCHAR)((PCHAR)KeServiceDescriptorTable->ArgumentTable+ulOffset);
if (!MmIsAddressValid(g_pNewSSDT->ServiceTableBase)
||!MmIsAddressValid(g_pNewSSDT->ArgumentTable))
{
g_pNewSSDT = NULL;
return;
}
for (ulIndex=0; ulIndex<g_pNewSSDT->TableSize; ulIndex++)
{
// 函数偏移要变
g_pNewSSDT->ServiceTableBase[ulIndex] += ulOffset;
}
// 参数不变
memcpy(g_pNewSSDT->ArgumentTable, KeServiceDescriptorTable->ArgumentTable, KeServiceDescriptorTable->TableSize);
//恢复写保护
EnablePageWriteProtection
}
hook管家的函数,实现SSDT分发
ULONG __stdcall KiFastCallEntry_Filter(ULONG ulSyscallId,
ULONG ulSyscallAddr,
PULONG pulSyscallTable)
{
ULONG ulRet = 0;
pfn_KiFastCallEntry_Filter pfn = (pfn_KiFastCallEntry_Filter)QQHookZone;
//KdPrint(("[KiFastCallEntry_Filter]-ulSyscallId:%d,ulSyscallAddr:0x%08x,pulSyscallTable:0x%08x\n",ulSyscallId, ulSyscallAddr, pulSyscallTable));
ulRet = pfn(ulSyscallId, ulSyscallAddr, pulSyscallTable);
if (pulSyscallTable == KeServiceDescriptorTable->ServiceTableBase)
{
if (g_pNewSSDT)
{
if (!_strnicmp((char*)PsGetCurrentProcess()+0x174,"cheatengine-", 10))
{
ulRet = g_pNewSSDT->ServiceTableBase[ulSyscallId];
}
}
}
return ulRet;
}
注意:比如只针对CE,那么 CE必须先关闭,再释放驱动,不然极可能蓝屏掉(蓝屏在重载 的NTOS中)
以下代码在装了QQ管家+CE下,可以查看被保护的内存(如NP)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。