3、 ThreadHideFromDebugger
这项技术用到了常常被用来设置线程优先级的API ntdll!NtSetInformationThread(),不过这个API也能够用来防止调试事件被发往调试器。
NtSetInformationThread()的参数列表如下。要实现这一功能,ThreadHideFromDebugger(0x11)被当作ThreadInformationClass参数传递,ThreadHandle通常设为当前线程的句柄(0xFFFFFFFE):
NTSTATUS NTAPI NtSetInformationThread(
HANDLE ThreadHandle,
THREAD_INFORMATION_CLASS ThreadInformaitonClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength
);
ThreadHideFromDebugger内部设置内核结构ETHREAD16的HideThreadFromDebugger成员。一旦这个成员设置以后,主要用来向调试器发送事件的内核函数_DbgkpSendApiMessage()将不再被调用。
示例
调用NtSetInformationThread()的一个典型示例:
push 0 ;InformationLength
push NULL ;ThreadInformation
push ThreadHideFromDebugger ;0x11
push 0xfffffffe ;GetCurrentThread()
call [NtSetInformationThread]
对策
可以在ntdll!NtSetInformationThread()里下断,断下来后,逆向分析人员可以操纵EIP防止API调用到达内核,这些都可以通过ollyscript来自动完成。另外,Olly Advanced插件也有补这个API的选项。补过之后一旦ThreadInformaitonClass参数为HideThreadFromDebugger,API将不再深入内核仅仅执行一个简单的返回。
【原创】inline hook SSDT 躲避 Themida 的ThreadHideFromDebugger (学习笔记2)
1 个附件
Themida保护的程序会采用ThreadHideFromDebugger来反调试,这个一旦设置,就一直起效,所以对于需要附加的程序就不太好办了,或者有的程序直接调用 int 2e来反调试, 虽然有插件比如 invisible可以躲过,但是需要附加进行调试的程序就不起作用了。
比如我以前写的一个反调试函数
void anti()
{
/////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
//NtSetInformationThread 功能设置调试端口为0
//
//
//
::GetCurrentThread();
_asm // xp系统
{
push 0 //InformationLength
push 0 //ThreadInformation
push 0x11 //11 就是ThreadHideFromDebugger
push eax //当前线程句柄
mov eax, 0xe5 //EAX NtSetInformationThread调用号
mov edx, esp //EDX 当前堆栈放
int 0x2e
add esp, 0x10
}
::GetCurrentThread();
_asm // 2000系统
{
push 0
push 0
push 0x11
push eax
mov eax, 0xc6
mov edx, esp
int 0x2e
add esp, 0x10
}
}
为了躲避上面的ANTI inline hook是个不错的方法
inline hook 分5步
第一步 申请一个全局变量 用来存放被破坏的指令
第二步 声明一个类型 用来强制转换
第三步 自己的函数
第四步 安装HOOK
第五部 卸载HOOK
其中起关键作用的是下面的代理函数
NTSTATUS __stdcall //第三步 自己的函数
MyNtSetInformationThread(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
)
{
if(ThreadInformationClass == 0x11)
{
DbgPrint("发现 ANTI DEBUG 行为");
DbgPrint("NtSetInformationThread 参数 %8x %8x %8x %8x ", ThreadHandle, ThreadInformationClass, ThreadInformationClass, ThreadInformationLength); // 打印出参数
ThreadInformationClass = (THREADINFOCLASS)0xff;
DbgPrint("参数被我改过后是 %8x %8x %8x %8x ", ThreadHandle, ThreadInformationClass, ThreadInformationClass, ThreadInformationLength);
return STATUS_SUCCESS; //直接返回成功 这样ANTI就失效了
}
//强制转换成函数的形式
return ((SYSNTSETINFORMATIIONTHREAD)OldNtSetInformationThread)( // 放行
ThreadHandle,
ThreadInformationClass,
ThreadInformation,
ThreadInformationLength
);
}
下面是全部的代码 附件中也包含完成的代码
SYS加载有 你只要调用下 anti这个函数 代理函数就会输出参数 并且 改变参数
void * OldNtSetInformationThread;; //第一步 申请一个全局变量 用来存放被破坏的指令
//NtSetInformationThread
typedef NTSTATUS (__stdcall *SYSNTSETINFORMATIIONTHREAD) //第二步 声明一个类型 用来强制转换
(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
);
NTSTATUS __stdcall //第三步 自己的函数
MyNtSetInformationThread(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
)
{
if(ThreadInformationClass == 0x11)
{
DbgPrint("发现 ANTI DEBUG 行为");
DbgPrint("NtSetInformationThread 参数 %8x %8x %8x %8x ", ThreadHandle, ThreadInformationClass, ThreadInformationClass, ThreadInformationLength); //证明自己存在
ThreadInformationClass = (THREADINFOCLASS)0xff;
DbgPrint("参数被我改过后是 %8x %8x %8x %8x ", ThreadHandle, ThreadInformationClass, ThreadInformationClass, ThreadInformationLength); //证明自己存在
return STATUS_SUCCESS; //直接返回成功
}
//强制转换成函数的形式
return ((SYSNTSETINFORMATIIONTHREAD)OldNtSetInformationThread)( // 放行
ThreadHandle,
ThreadInformationClass,
ThreadInformation,
ThreadInformationLength
);
}
ULONG AddrNtSetInformationThread;
VOID OnUnload(IN PDRIVER_OBJECT DriverObject);
// 驱动程序加载时调用DriverEntry例程
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
{
DbgPrint("Hook NtSetInformationThread DriverEntry");
pDriverObj->DriverUnload = OnUnload;
AddrNtSetInformationThread = *PULONG((ULONG)KeServiceDescriptorTable->ServiceTable + 0xe5 * 4);
AfxHookCode((void*)AddrNtSetInformationThread, (void*)MyNtSetInformationThread, (void**)&OldNtSetInformationThread, 10); //第四步 AFXHOOK
// if(驱动加载失败)
// 一定要 AfxUnHookCode 不然驱动加载失败 自己分配的内存代码也就失效了 系统经过这个SSDT也就蓝了
return STATUS_SUCCESS;
}
//////////////////////////////////////////////////////
VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
AfxUnHookCode((void*)AddrNtSetInformationThread, OldNtSetInformationThread, 10); //第五部 卸载 驱动卸载一定要还原HOOK 因为驱动卸载了,自己分配的内存也就失效了
DbgPrint("Unload Hook NtSetInformationThread");
}
下面是RING 0 HOOK 模块
#ifndef __AFXHOOKCODE_H__
#define __AFXHOOKCODE_H__
bool AfxHookCode(void* TargetProc, void* NewProc,void ** l_OldProc, int bytescopy = 5)
{
*l_OldProc = ExAllocatePool(NonPagedPool,(bytescopy+5)); // 执行被覆盖的指令 再跳到原来的代码上运行
memcpy(*l_OldProc, TargetProc, bytescopy); // 事先保存被破坏的指令
__asm{
cli
mov eax,cr0
and eax,not 10000h
mov cr0,eax
}
// 我的内存的代码执行完 跳到原来的 代码+破坏的代码的长度 上去
*((unsigned char*)(*l_OldProc) + bytescopy) = 0xe9;
// 我内存代码跳到原来代码上的偏移
*(unsigned int *)((unsigned char*)(*l_OldProc) +bytescopy + 1)=(unsigned int)(TargetProc) + bytescopy - ( (unsigned int)((*l_OldProc)) + 5 + bytescopy ) ;
//被HOOK的函数头改为jmp
*(unsigned char*)TargetProc =(unsigned char)0xe9;
//被HOOK的地方跳到我的新过程 接受过滤
*(unsigned int*)((unsigned int)TargetProc +1) = (unsigned int)NewProc - ( (unsigned int)TargetProc + 5);
__asm{
mov eax,cr0
or eax,10000h
mov cr0,eax
sti
}
return true;
}
bool AfxUnHookCode(void* TargetAddress, void * l_SavedCode, unsigned int len)
{
__asm{
cli
mov eax,cr0
and eax,not 10000h
mov cr0,eax
}
// 恢复HOOK
memcpy(TargetAddress, l_SavedCode, len);
__asm{
mov eax,cr0
or eax,10000h
mov cr0,eax
sti
}
return true;
}
#endif