写这个插件其实主要是因为现在32位下各种内核钩子,用一些调试工具畏首畏尾的,一会挂钩这里,一会挂钩那里,遂干脆一了百了,直接写个内核重载插件,虽说是OD插件,也可以支持其他软件。此插件名字就叫ReloadKernel V1.0吧,贴切一点:
(支持2003,XP,WIN7)请避免和360,QQ管家,百度杀毒等一同使用,对于没有DebugPort清零的保护应该是可以秒的,比如HS(亲测),当然你要处理R3的反调试,对于有调试权限清零的自己恢复,端口清零的自己改下偏移也就OK了,不会改的话再不济用这个插件也可以读写内存吧!
,请别瞎想,本人写这个插件主要是为了学习交流,请大家不要用于非法用途,先来个图
首先内核重载插件我分成以下几个步骤完成
1, 获取当前系统下的内核文件
2, 按PE格式加载硬盘中的内核文件,修复重定位表,导入导出表
3, 挂钩KiFastCallEntry
4, 实现过滤框架
5, R3,R0通信
6, OD插件GUI与逻辑部分
1,因为在单核,多核处理器等不同情况下,微软会加载不同的内核文件,所以我们需要知道当前系统所加载的是哪一个内核文件,之后获取到它的全路径,并Copy到内存中,已便操作
下面这个函数是获取系统加载的内核模块基址,没什么好说的,原理就是ZwQuerySystemInformation的11号功能,查询内核模块
PVOID
GetKernelFileBase()
{
DWORD dwsize = 0;
PVOID pKernelBase = NULL;
PVOID pSysInfo = NULL;
__try
{
ZwQuerySystemInformation(SystemModuleInformation,NULL,0,&dwsize);
pSysInfo = ::ExAllocatePool(NonPagedPool,dwsize);
if(pSysInfo)
{
memset(pSysInfo,0,dwsize);
if(NT_SUCCESS(ZwQuerySystemInformation(SystemModuleInformation,pSysInfo,dwsize,&dwsize)))
{
PSYSTEM_MODULE_INFORMATION_ENTRY psmi = (PSYSTEM_MODULE_INFORMATION_ENTRY)((PBYTE)pSysInfo + 4);
pKernelBase = (PVOID)psmi->Base;
}
else
{
KdPrint(("GetKernelFileBase.ZwQuerySystemInformation error\n"));
}
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("GetKernelFileBase异常\n"));
}
if(pSysInfo)
{
ExFreePool(pSysInfo);
pSysInfo = NULL;
}
return pKernelBase ;
}
还需要一个获取内核模块的全路径函数
bool
GetKernelFileFullPath(
OUT PUNICODE_STRING pUnicodeStr
)
{
bool bResult = FALSE;
DWORD dwsize = 0;
PVOID pSysInfo = NULL;
_try
{
ZwQuerySystemInformation(SystemModuleInformation,NULL,0,&dwsize);
pSysInfo = ::ExAllocatePool(NonPagedPool,dwsize);
if(pSysInfo)
{
memset(pSysInfo, 0, dwsize);
if(NT_SUCCESS(ZwQuerySystemInformation(SystemModuleInformation,pSysInfo,dwsize,&dwsize)))
{
PSYSTEM_MODULE_INFORMATION_ENTRY psmi = (PSYSTEM_MODULE_INFORMATION_ENTRY)((char*)pSysInfo + 4);
char *szKernelModuleName = psmi->ImageName + psmi->PathLength;
KdPrint(("模块 %s\n",szKernelModuleName));
char szKernelModuleFullPath[255];
ANSI_STRING asModuleName;
//"\\Device\\HarddiskVolume1\\Windows\\System32\\"
char szSystemPath[] = "\\??\\c:\\Windows\\System32\\";
strcpy(szKernelModuleFullPath,(char*)szSystemPath);
strcat(szKernelModuleFullPath,szKernelModuleName);
::RtlInitAnsiString(&asModuleName,szKernelModuleFullPath);
::RtlAnsiStringToUnicodeString(pUnicodeStr,&asModuleName,TRUE);
bResult = TRUE;
}
else
{
KdPrint(("GetKernelFileFullPath.ZwQuerySystemInformation error"));
}
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("GetKernelFileFullPath.异常"));
}
if(pSysInfo)
{
ExFreePool(pSysInfo);
pSysInfo = NULL;
}
return bResult;
}
起先我是使用“\\Device\\HarddiskVolume1\\Windows\\System32\\” 来代替系统目录的,后来经过一些测试,发现在多系统环境下,系统盘符不一定就是HarddiskVolume1了,所以干脆直接就改成"\\??\\c:\\Windows\\System32\\";如果谁有特殊癖好,你就自己改好了
2,内核重载,重头戏就是这个重载,首先就是复制一下内核文件到内存,之后在内存中将它展开,处理导入导出函数,处理重定位,在重定位时我将所有的内核数据都重定位到了原内核中,这样就省得自己再初始化内核文件了,并且也会避免很多冲突,因为有些数据只能存在一份,当然这样做的坏处就是绕不过Object Hook,系统回调了,但是这种小CASE,枚举一下就干掉了,这不在本文讨论范围内,如果有需要我也可以再发个主题关于怎么处理Object Hook。
首先就是把文件弄到内存里,函数如下
PVOID
CreateImageFromFile(
IN PUNICODE_STRING ImageFile)
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
HANDLE FileHandle = NULL;
OBJECT_ATTRIBUTES ObjAttributes = {0};
IO_STATUS_BLOCK IoStatusBlock = {0};
InitializeObjectAttributes(
&ObjAttributes,
ImageFile,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = ZwCreateFile(
&FileHandle,
GENERIC_READ,
&ObjAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if(NT_SUCCESS(Status))
{
FILE_STANDARD_INFORMATION fsi = {0};
IO_STATUS_BLOCK iostatus = {0};
Status = ZwQueryInformationFile(
FileHandle,
&iostatus,
&fsi,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
if(NT_SUCCESS(Status))
{
PVOID pBuffer = ExAllocatePool(PagedPool,(LONG)fsi.EndOfFile.QuadPart);
if(pBuffer)
{
memset(pBuffer,0,(LONG)fsi.EndOfFile.QuadPart);
Status = ZwReadFile(FileHandle, NULL, NULL, NULL,&iostatus, pBuffer, (LONG)fsi.EndOfFile.QuadPart, NULL, NULL);
if(NT_SUCCESS(Status))
{
KdPrint(("Read %x bytes\n", iostatus.Information));
ZwClose(FileHandle);
return pBuffer;
}
else
{
KdPrint(("CreateImageFromFile.读取文件失败 %x",Status));
return 0;
}
}
else
{
KdPrint(("CreateImageFromFile.申请非分页内存失败"));
}
}
else
{
KdPrint(("CreateImageFromFile.获取文件尺寸失败"));
return 0;
}
}
else
{
KdPrint(("CreateImageFromFile.打开文件失败"));
return 0;
}
return 0;
}
先查询一下文件大小,之后申请那么大的内存,读进来,没什么好说的,之后就是重头戏了,通过内核中未展开的PE文件结构在内存中创建一个展开的PE结构,好绕口...
ULONG
CreatePeStructFromMem(
IN ULONG ulFileAddress,
IN ULONG ntKernelAddress)
{
__try
{
PIMAGE_DOS_HEADER pIMAGE_DOS_HEADER = (PIMAGE_DOS_HEADER)ulFileAddress;
PIMAGE_NT_HEADERS pIMAGE_NT_HEADERS = (PIMAGE_NT_HEADERS)(ulFileAddress + pIMAGE_DOS_HEADER->e_lfanew);
PIMAGE_FILE_HEADER pIMAGE_FILE_HEADER = &pIMAGE_NT_HEADERS->FileHeader;
PIMAGE_OPTIONAL_HEADER32 pIMAGE_OPTIONAL_HEADER = &pIMAGE_NT_HEADERS->OptionalHeader;
KdPrint(("DOS 头 %x \n",pIMAGE_DOS_HEADER));
KdPrint(("NT 头 %x \n",pIMAGE_NT_HEADERS));
KdPrint(("文件 头 %x \n",pIMAGE_FILE_HEADER));
KdPrint(("可选 头 %x \n",pIMAGE_OPTIONAL_HEADER));
if(!pIMAGE_NT_HEADERS || !pIMAGE_FILE_HEADER || !pIMAGE_OPTIONAL_HEADER) return 0;
ULONG pMy_ImageBase = (ULONG)ExAllocatePool(NonPagedPool,pIMAGE_OPTIONAL_HEADER->SizeOfImage);
if(pMy_ImageBase)
{
memset((PVOID)pMy_ImageBase,0,pIMAGE_OPTIONAL_HEADER->SizeOfImage);
//复制头加段表
RtlCopyMemory((PVOID)pMy_ImageBase,(PVOID)ulFileAddress,pIMAGE_OPTIONAL_HEADER->SizeOfHeaders );
//节表展开
PIMAGE_SECTION_HEADER pSectionHeadTmp = (PIMAGE_SECTION_HEADER)((ULONG)pIMAGE_OPTIONAL_HEADER + sizeof(IMAGE_OPTIONAL_HEADER32));
for(int i = 0;i < pIMAGE_NT_HEADERS->FileHeader.NumberOfSections;i ++)
{
RtlCopyMemory((PVOID)(pSectionHeadTmp->VirtualAddress + (ULONG)pMy_ImageBase),//计算VA
(PVOID)(pSectionHeadTmp->PointerToRawData + (ULONG)ulFileAddress),//文件偏移
pSectionHeadTmp->SizeOfRawData);//计算尺寸
pSectionHeadTmp ++;
}
//导入表处理
for(
PIMAGE_IMPORT_DESCRIPTOR pImportDescriptorTmp = (PIMAGE_IMPORT_DESCRIPTOR)(pIMAGE_OPTIONAL_HEADER->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + (ULONG)pMy_ImageBase);
!IsStructEmpty<IMAGE_IMPORT_DESCRIPTOR>(pImportDescriptorTmp);
pImportDescriptorTmp++
)
{
KdPrint(("导入模块 %s\n",(char*)(pImportDescriptorTmp->Name + (ULONG)pMy_ImageBase)));
PVOID pModule = KernelGetModuleBase(((char*)(pImportDescriptorTmp->Name + (ULONG)pMy_ImageBase)));
if(pModule)
{
for(
PIMAGE_THUNK_DATA pThunkData = (PIMAGE_THUNK_DATA)(pImportDescriptorTmp->FirstThunk + (ULONG)pMy_ImageBase);
!IsStructEmpty<IMAGE_THUNK_DATA>(pThunkData);
pThunkData++
)
{
PVOID pFunctionTmp =
pThunkData->u1.AddressOfData & IMAGE_ORDINAL_FLAG32 ?
KernelGetProcAddress(pModule,(PCHAR)(pThunkData->u1.AddressOfData & 0xffff))
:
KernelGetProcAddress(pModule,(PCHAR)((PIMAGE_IMPORT_BY_NAME)(pThunkData->u1.AddressOfData + (ULONG)pMy_ImageBase))->Name);
if(!pFunctionTmp)
{
if(pThunkData->u1.AddressOfData & IMAGE_ORDINAL_FLAG32)
{
KdPrint(("有函数没找到,序号 %d",(PCHAR)(pThunkData->u1.AddressOfData & 0xffff)));
}
else
{
KdPrint(("有函数没找到,名字%s",(PCHAR)((PIMAGE_IMPORT_BY_NAME)(pThunkData->u1.AddressOfData + (ULONG)pMy_ImageBase))->Name));
}
}
else
{
if(pThunkData->u1.AddressOfData & IMAGE_ORDINAL_FLAG32)
{
KdPrint(("已经修复函数,序号 %d",(PCHAR)(pThunkData->u1.AddressOfData & 0xffff)));
}
else
{
KdPrint(("已经修复函数,名字%s",(PCHAR)((PIMAGE_IMPORT_BY_NAME)(pThunkData->u1.AddressOfData + (ULONG)pMy_ImageBase))->Name));
}
pThunkData->u1.Function = (DWORD)pFunctionTmp;
}
}
}
else
{
KdPrint(("获取模块 %s失败\n",(char*)(pImportDescriptorTmp->Name + (ULONG)pMy_ImageBase)));
}
}
int nRelocNum = 0;
ULONG ulR = ntKernelAddress - pIMAGE_OPTIONAL_HEADER->ImageBase;
KdPrint(("重定位偏移 ulR = %x",ulR));
for(
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(pIMAGE_OPTIONAL_HEADER->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + (ULONG)pMy_ImageBase);
!IsStructEmpty<IMAGE_BASE_RELOCATION>(pLoc);
pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock)
)
{
nRelocNum ++;
for(PWORD pHandleAddress = (PWORD)((DWORD)pLoc + sizeof(IMAGE_BASE_RELOCATION));pHandleAddress < (PWORD)((DWORD)pLoc + pLoc->SizeOfBlock);pHandleAddress ++)
{
if((*pHandleAddress & 0xf000) == IMAGE_REL_BASED_HIGHLOW * 0X1000)//x86重定位标记
{
*(PDWORD((*pHandleAddress & 0xfff) + pLoc->VirtualAddress + (ULONG)pMy_ImageBase)) += ulR;
}
}
}
KdPrint(("重定位块 = %d个",nRelocNum));
return pMy_ImageBase;
}
else
{
KdPrint(("CreatePeStructFromMem.申请内存失败"));
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("CreatePeStructFromMem.执行异常"));
}
return 0;
}
首先是把节展开,展开之后定位所有的导入函数,有时候导入模块是没加载到内核的,这种情况并没有考虑,因为用处不大,你可以先激活那个模块的功能,之后在打开插件即可(比如串口通信)分别处理按名字导入的和按序号导入两种情况,导出表就不用处理了,为啥就不用我说了吧,之后处理重定位表,注意这里
int nRelocNum = 0;
ULONG ulR = ntKernelAddress - pIMAGE_OPTIONAL_HEADER->ImageBase;
KdPrint(("重定位偏移 ulR = %x",ulR));
for(
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(pIMAGE_OPTIONAL_HEADER->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + (ULONG)pMy_ImageBase);
!IsStructEmpty<IMAGE_BASE_RELOCATION>(pLoc);
pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock)
)
{
nRelocNum ++;
for(PWORD pHandleAddress = (PWORD)((DWORD)pLoc + sizeof(IMAGE_BASE_RELOCATION));pHandleAddress < (PWORD)((DWORD)pLoc + pLoc->SizeOfBlock);pHandleAddress ++)
{
if((*pHandleAddress & 0xf000) == IMAGE_REL_BASED_HIGHLOW * 0X1000)//x86重定位标记
{
*(PDWORD((*pHandleAddress & 0xfff) + pLoc->VirtualAddress + (ULONG)pMy_ImageBase)) += ulR;
}
}
}
把所有需要重定位的数据都重定向到了原内核,这样可以避免一些数据上的麻烦,不初始化就用会蓝屏。上面用了个小模块,因为PE结构太多,我没有通过PE信息中的一些个数信息来判断结构体的数量,而是通过判断是否以0结尾,感觉这样靠谱一点。所以写了个如下的小模板,判断各种结构是否为空结构
template <class T>
bool IsStructEmpty(
IN T *Mystruct)
{
bool bIsEmpty = true;
__try
{
for(PBYTE p = (PBYTE)Mystruct;p < (PBYTE)((DWORD)Mystruct + sizeof(T)) ;p++)
{
if((*p) != 0 )
{
bIsEmpty = false;
break;
}
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("IsStructEmpty Exception"));
}
return bIsEmpty;
}
3,这里就准备开始挂钩了,挂钩之前首先要获取KifastCallEntry地址,还是老办法,你们觉得土可以用360的方法。
ULONG GetAddressOfKiFastCallEntry()
{
ULONG dwAddress = 0;
BYTE vgdtr[8] = {0};
__asm
{
push eax
push ebx
push ecx
push edx
mov ecx, 0x174
rdmsr
mov ebx, eax //Selector offset
sgdt vgdtr
// mov edx, vgdtr
lea edx,vgdtr
add edx, 0x02
mov eax, [edx] //GDT base
add ebx, eax //Selector base
mov edx, ebx
add edx, 0x07
mov eax, [edx]
shl eax, 24;
mov edx, ebx
add edx, 0x02
mov ecx, [edx]
and ecx, 0x00FFFFFF
add eax, ecx //Address CodeSegment
mov ebx, eax
mov ecx, 0x176
rdmsr
add eax, ebx
mov dwAddress, eax
pop edx
pop ecx
pop ebx
pop eax
}
return dwAddress;
}
获取之后就要找个好地方挂钩子了,最后经过分析找了一个这样的位置,既有表的地址,也有函数的索引号,(这个地址貌似百度,QQ,360都在用)因为我们只处理SSDT的,SHADOW的就不管了,如图
下面是跳转函数的实现,因为我们要支持2003,XP,WIN7,所以就要分别应对,但是幸运的是发现2003和XP的特别像,就写了2个,省了点事。首先获取系统版本,分别处理
enum SystemEnum
{
k_WinXp = 0,
k_Win2003,
k_Win7,
k_UnKonwn
}systemEnum;
SystemEnum GetSystemInfo()
{
ULONG majorVersion, minorVersion;
PsGetVersion(&majorVersion, &minorVersion, NULL, NULL);
if (majorVersion == 5 && minorVersion == 2)
{
return k_Win2003;
}
else if (majorVersion == 5 && minorVersion == 1)
{
return k_WinXp;
}
else if (majorVersion == 6 && minorVersion == 1)
{
return k_Win7;
}
return k_UnKonwn;
}
获取挂钩的位置,函数如下
ULONG GetAddressOfKiFastCallEntry_Hook(ULONG KiFastCallEntry)
{
__try
{
for(ULONG i = KiFastCallEntry;i < KiFastCallEntry + 0x200;i++)
{
if(*(PBYTE)i == 0x2b)
{
if(*(PBYTE)(i+1) == 0xe1)
{
if(*(PBYTE)(i+2) == 0xc1)
{
if(*(PBYTE)(i+3) == 0xe9)
{
if(*(PBYTE)(i+4) == 0x02)
{
return i;
}
}
}
}
}
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("GetAddressOfKiFastCallEntry_Hook 发生错误"));
}
return 0;
}
该获取的都获取到了,下面就要开始HOOK了,没有用那种开关中断的方式,太氧化铜(CuO)了,用了个以前写的小函数专门HOOK的。
enum HookEnum
{
jmpHook = 0,
pushretHook,
callHook
}hookEnum;
首先就是判断挂钩地址是否有效,下个断言必须运行在低于DISPATCH_LEVEL级别,以免缺页中断蓝了,之后先读一下内存地址,缺页的赶紧到内存中来,(KiFastCallEntry这种高频函数肯定是在内存中的,但是为了良好HOOK习惯)之后在MmIsAddressValid一下判断内存地址到底有没有效,完毕之后用MDL描述一下要挂钩的地址,重新搞一份可以写的,之后提升级别防止被切换,当然多核下这样还是不安全的,不过毕竟我们这属于小产品不用考虑那么多,(其实腾讯,百度都没考虑,记忆犹新,打把LOL直接给我蓝屏)HOOK完收工
BOOL Hook(ULONG_PTR ulHookAddress,ULONG_PTR ulDispatchAddress,int nLength,HookEnum hookKind)
{
KIRQL oldIrql = 0;
PMDL mdl = NULL;
PVOID pWritableAddress = NULL;
ULONG ulTestForPage = 0;
//确定HOOK函数运行级别小于DISPATCH_LEVEL,以免缺页
ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL);
_try
{
_asm
{
mov eax,ulHookAddress
mov eax,dword ptr[eax]
mov ulTestForPage,eax
mov eax,ulDispatchAddress
mov eax,dword ptr[eax]
mov ulTestForPage,eax
}
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
if(!MmIsAddressValid((PVOID)ulHookAddress))
{
return false;
}
if(nLength > 20) return false;
mdl = IoAllocateMdl((PVOID)ulHookAddress, nLength, false, false, NULL);
if(mdl != NULL)
{
MmBuildMdlForNonPagedPool(mdl);
_try
{
MmProbeAndLockPages(mdl,KernelMode,IoWriteAccess);
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
IoFreeMdl(mdl);
return false;
}
pWritableAddress = MmMapLockedPages(mdl, KernelMode);
if (pWritableAddress != NULL)
{
BYTE bCode[20] = {0x90};
oldIrql = KeRaiseIrqlToDpcLevel();
__try
{
switch(hookKind)
{
case jmpHook:
{
*(BYTE *)bCode = 0xe9;
*(ULONG *)(bCode + 1) = ulDispatchAddress - ulHookAddress - 5;
RtlCopyMemory(pWritableAddress, bCode, nLength);
}break;
case pushretHook:
{
*(BYTE *)bCode = 0x68;
*(ULONG *)(bCode + 1) = ulDispatchAddress;
*(BYTE *)(bCode + 5) = 0xc3;
RtlCopyMemory(pWritableAddress, bCode, nLength);
}break;
case callHook:
{
*(BYTE *)bCode = 0xe8;
*(ULONG *)(bCode + 1) = ulDispatchAddress - ulHookAddress-5;
RtlCopyMemory(pWritableAddress, bCode, nLength);
}break;
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("Hook 发生错误"));
}
KeLowerIrql(oldIrql);
MmUnmapLockedPages(pWritableAddress, mdl);
}
MmUnlockPages(mdl);
IoFreeMdl(mdl);
}
return true;
}
4,过滤框架也没设计的多复杂,也不是啥大项目,判断一下要保护的PID,是的话就走新内核,不是的话就走老内核。默认可以保护5个PID,不够就自己加,我设定5个,第一个是给OD的,其余4个自定义,下面就是2003,XP,WIN7的过滤函数,先保存寄存器和标志位,之后开辟私有栈给自己的局部变量用个,当然用完就恢复成原来的,函数中判断一下函数索引号,大过系统默认的索引就不处理了,之后判断是否SSDT表,是的话判断当前进程环境,是要保护的PID就走新内核,华丽的一个push ret,这个全局变量在驱动初始化函数中进行初始化。
__declspec(naked)void Xp_2003_KiFastCallEntry_Dispatch()
{
DWORD dwCurPid;
DWORD dwCurServiceTableBase;
DWORD dwIndex;
bool bProtect ;
int i ;
_asm {
push ebp
mov ebp, esp
sub esp, __LOCAL_SIZE
pushfd
pushad
mov dwCurServiceTableBase, edi
mov dwIndex, eax
}
if (dwIndex < KeServiceDescriptorTable->NumberOfServices)
{
if (dwCurServiceTableBase == g_dwServiceTableBase)
{
dwCurPid = (DWORD)PsGetCurrentProcessId();
bProtect = FALSE;
for ( i = 0; i < 5; i++)
{
if (g_dwProtectPid[i] == dwCurPid)
{
bProtect = TRUE;
break;
}
}
if (bProtect)
{
_asm
{
popad
popfd
mov esp, ebp
pop ebp
mov edi, g_pMyServiceTable
mov ebx, dword ptr[edi + eax * 4]
sub esp, ecx
shr ecx, 2
push g_ulKiFastCallEntryRetAddr
ret
}
}
}
}
_asm
{
popad
popfd
mov esp, ebp
pop ebp
sub esp, ecx
shr ecx, 2
push g_ulKiFastCallEntryRetAddr
ret
}
}
__declspec(naked)void Win7_KiFastCallEntry_Dispatch()
{
DWORD dwCurPid;
DWORD dwCurServiceTableBase;
DWORD dwIndex;
bool bProtect ;
int i;
_asm {
push ebp
mov ebp, esp
sub esp, __LOCAL_SIZE
pushfd
pushad
mov dwCurServiceTableBase, edi
mov dwIndex, eax
}
if (dwIndex < KeServiceDescriptorTable->NumberOfServices)
{
if (dwCurServiceTableBase == g_dwServiceTableBase)
{
dwCurPid = (DWORD)PsGetCurrentProcessId();
bProtect = FALSE;
for ( i = 0; i < 5; i++)
{
if (g_dwProtectPid[i] == dwCurPid)
{
bProtect = TRUE;
break;
}
}
if (bProtect)
{
KdPrint(("保护中 %d", dwCurPid));
_asm
{
popad
popfd
mov esp, ebp
pop ebp
mov edi, g_pMyServiceTable
mov edx, dword ptr[edi + eax * 4]
sub esp, ecx
shr ecx, 2
push g_ulKiFastCallEntryRetAddr
ret
}
}
}
}
_asm
{
popad
popfd
mov esp, ebp
pop ebp
sub esp, ecx
shr ecx, 2
push g_ulKiFastCallEntryRetAddr
ret
}
}
5,这里就是通信了,主要是OD发给驱动我们保护谁谁谁,简单的一个
#define Protect_Code CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_IN_DIRECT,FILE_ANY_ACCESS)控制码
响应函数如下
NTSTATUS RELOADPROTECT_DispatchDeviceControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
int nInput = irpSp->Parameters.DeviceIoControl.InputBufferLength;;
switch (irpSp->Parameters.DeviceIoControl.IoControlCode)
{
case Protect_Code:
{
ProtectInfo *protectInfo = (ProtectInfo *)Irp->AssociatedIrp.SystemBuffer;
KdPrint(("[%d]:要保护的进程Pid = %d", protectInfo->nIndex, protectInfo->dwPid));
if (protectInfo->nIndex < 5)
{
g_dwProtectPid[protectInfo->nIndex] = protectInfo->dwPid;
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
}break;
default:
{
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Information = 0;
}
break;
}
status = Irp->IoStatus.Status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
驱动入口函数中就开始调用上面的一些代码了,重载完内核文件之后,获取到新内核中的SSDT表地址,之后根据系统版本开始挂钩
__try
{
systemEnum = GetSystemInfo();
KdPrint(("当前系统为 %d\n", systemEnum));
ULONG ulNtBaseAddress = (ULONG)GetKernelFileBase();//获得Ntoskernel.exe在内存中的基址
if(ulNtBaseAddress)
{
KdPrint(("内核地址 %x\n", ulNtBaseAddress));
UNICODE_STRING usModuleName;
if(GetKernelFileFullPath(&usModuleName))//获取内核文件全路径
{
//内核文件读入内存
PVOID pImageFile = CreateImageFromFile(&usModuleName);
if(pImageFile)
{
//创建PE结构
ULONG ulImagePe = CreatePeStructFromMem((ULONG)pImageFile,ulNtBaseAddress);
if(ulImagePe)
{
g_dwOffset = ulImagePe - ulNtBaseAddress;
g_dwServiceTableBase = *(PDWORD)KeServiceDescriptorTable;
g_dwMyServiceTableBase = g_dwServiceTableBase - ulNtBaseAddress + ulImagePe;
KdPrint(("偏移为 %x\n",g_dwOffset ));
KdPrint(("内核重载的地址为 %x\n",ulImagePe));
KdPrint(("KiFastCallEntry 地址为 %x\n",GetAddressOfKiFastCallEntry()));
g_ulKiFastCallEntryRetAddr = GetAddressOfKiFastCallEntry_Hook(GetAddressOfKiFastCallEntry()) + 5;
int nNumberOfServices = KeServiceDescriptorTable->NumberOfServices;
g_pMyServiceTable = ExAllocatePool(NonPagedPool, nNumberOfServices * 4);
for (int i = 0; i < nNumberOfServices; i++)
{
DWORD dwOrgFuncAddress = *(PDWORD)(g_dwMyServiceTableBase + i * 4);
*(PDWORD)((ULONG)g_pMyServiceTable + i * 4) = dwOrgFuncAddress - ulNtBaseAddress + ulImagePe;
KdPrint(("[%d] 地址为 %x 新函数地址%x\n", i, (ULONG)g_pMyServiceTable + i * 4, dwOrgFuncAddress - ulNtBaseAddress + ulImagePe));
}
KdPrint(("g_pMyServiceTable 地址为 %x\n", g_pMyServiceTable));
switch (systemEnum)
{
case k_WinXp:
Hook(g_ulKiFastCallEntryRetAddr - 5, (ULONG)Xp_2003_KiFastCallEntry_Dispatch,5, jmpHook);
break;
case k_Win2003:
Hook(g_ulKiFastCallEntryRetAddr - 5, (ULONG)Xp_2003_KiFastCallEntry_Dispatch,5, jmpHook);
break;
case k_Win7:
Hook(g_ulKiFastCallEntryRetAddr - 5, (ULONG)Win7_KiFastCallEntry_Dispatch,5, jmpHook);
break;
default:
DbgPrint("未知系统\n");
break;
}
}
else
{
KdPrint(("创建PE文件失败\n"));
}
ExFreePool(pImageFile);
}
else
{
KdPrint(("创建内存文件失败\n"));
}
}
else
{
KdPrint(("获取内核模块全路径失败\n"));
}
}
else
{
KdPrint(("获取内核模块地址失败\n"));
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("DriverEntry异常\n"));
}
6,这部分就跟驱动没啥关系了,一些R3下GUI的逻辑部分,首先有5个PID,状态为2种,保护和未保护状态,第一个按钮是保护OD自身进程,另外扩展4个可用于指定PID。如图
直接输入要保护的进程的PID即可
R3过程主要是先打开设备,如果打开了证明已经加载过了,直接通信,如果没加载过,就生成个随机名字0-9,之后加载(请原谅没卸载驱动,感觉没必要)
hDevice = CreateFile("\\\\.\\RELOADPROTECT_DeviceName",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
srand((unsigned)timeGetTime());
int RanCheckNum = rand() % 10 ;
wsprintf(szDriverName,"%d.sys",RanCheckNum);
char szPath[256] = {0};
GetCurrentDirectory(256,szPath);
char szFullPath[256] = {0};
ReleaseRes(szDriverName,IDR_SYS1,"SYS");
wsprintf(szFullPath,"%s\\%s",szPath,szDriverName);
if (LoadNTDriver(szDriverName,szFullPath))
{
DeleteFile(szDriverName);
}
else
{
AfxMessageBox("加载失败");
DeleteFile(szDriverName);
}
}
// TODO: 在此添加额外的初始化代码
hDevice = CreateFile("\\\\.\\RELOADPROTECT_DeviceName",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
CString csInfo;
csInfo.Format("设备打开失败 %d %.8x/n", GetLastError(), hDevice);
AfxMessageBox(csInfo);
}
memset(bProtect,FALSE,sizeof(bool) * 5);
保护的通信函数如下
bool CProtectDlg::ProtectProcess(DWORD dwPid,int nIndex)
{
ProtectInfo *protectInfoBuff=(ProtectInfo *)malloc(sizeof(ProtectInfo)) ;
ULONG dwWrite ;
protectInfoBuff->dwPid = dwPid;
protectInfoBuff->nIndex = nIndex;
return DeviceIoControl(hDevice, Protect_Code , protectInfoBuff, sizeof(ProtectInfo), NULL,NULL, &dwWrite, NULL);
}
之后把驱动编译一下,文件放在OD插件的工程中编译一下,把驱动文件以资源方式导入DLL中即可
OD插件的基本逻辑就不在这里赘述了,都是公开的,大家可以自己查查
测试部分我就不发了,和谐一点
好了,上面说了那么多都是虚的,高清有码
驱动代码
ReloadProtect.rar
插件代码
ReloadKernel.part1.rar
ReloadKernel.part2.rar
照顾下没IDE的朋友,插件也上传一份吧
ReloadKernel_Dll.rar
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法