一
研究背景
二
实现原理
在KPROCESS结构的偏移地址0x2c8处,包含一个名为InstrumentationCallback的域,Windows系统Vista以及之后的版本中,可以使用InstrumentationCallback域来指定回调函数的地址,每次函数从内核态返回用户态之后系统都会调用指定的回调函数。
至于原理大致就是以上阐述的,那么了解Windows的异常派遣机制后可以知道每次系统产生异常时会从内核返回到用户层,既然这样那么我们是不是可以通过回调在回用户层的时候先拦截派遣,调用我们的异常处理函数后再放过它回去执行原来的派遣呢?
实践出真理,那就行用来验证想法是否正确吧。
bool Exception::InstallException(pfnExceptionHandlerApi p_exception_api)
{
DWORD old;
//获取syscall函数地址
NtSetContextThread = (pfnNtSetContextThread)NtSetContextThreadProc;
::VirtualProtect((PVOID)((DWORD64)&NtSetContextThreadProc + 0x04), 4, PAGE_EXECUTE_READWRITE, &old);
*(DWORD*)((DWORD64)&NtSetContextThreadProc + 0x04) = (DWORD)GetSSDTIndexByName("NtSetContextThread");
::VirtualProtect((PVOID)((DWORD64)&NtSetContextThreadProc + 0x04), 4, old, NULL);
NtSuspendThread = (pfnNtSuspendThread)NtSuspendThreadProc;
::VirtualProtect((PVOID)((DWORD64)&NtSuspendThreadProc + 0x04), 4, PAGE_EXECUTE_READWRITE, &old);
*(DWORD*)((DWORD64)&NtSuspendThreadProc + 0x04) = (DWORD)GetSSDTIndexByName("NtSuspendThread");
::VirtualProtect((PVOID)((DWORD64)&NtSuspendThreadProc + 0x04), 4, old, NULL);
NtResumeThread = (pfnNtResumeThread)NtResumeThreadProc;
::VirtualProtect((PVOID)((DWORD64)&NtResumeThreadProc + 0x04), 4, PAGE_EXECUTE_READWRITE, &old);
*(DWORD*)((DWORD64)&NtResumeThreadProc + 0x04) = (DWORD)GetSSDTIndexByName("NtResumeThread");
::VirtualProtect((PVOID)((DWORD64)&NtResumeThreadProc + 0x04), 4, old, NULL);
NtContinue = (pfnNtContinue)NtContinueProc;
::VirtualProtect((PVOID)((DWORD64)&NtContinueProc + 0x04), 4, PAGE_EXECUTE_READWRITE, &old);
*(DWORD*)((DWORD64)&NtContinueProc + 0x04) = (DWORD)GetSSDTIndexByName("NtContinue");
::VirtualProtect((PVOID)((DWORD64)&NtContinueProc + 0x04), 4, old, NULL);
//保存函数指针
this->_self_exception_api = p_exception_api;
HMODULE ntdll = ::GetModuleHandleA("ntdll.dll");
if (ntdll == NULL)
ntdll = ::LoadLibraryA("ntdll.dll");
//获取hook的返回地址
sysret_address = (DWORD64)::GetProcAddress(ntdll, "KiUserExceptionDispatcher");
if (sysret_address == NULL)
sysret_address = (DWORD64)::GetProcAddress(ntdll, "KiUserExceptionDispatcher");
rtl_restore_context_offset = this->GetOffset(sysret_address, 0x70, 0x10);
if (rtl_restore_context_offset <= 0)
::MessageBoxA(::GetActiveWindow(), "未找到函数偏移", "Error", MB_OK);
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION info;
info.Version = 0;
info.Reserved = 0;
info.Callback = MyCallbackEntry;
ULONG status = NtSetInformationProcess(GetCurrentProcess(), 0x28, &info, sizeof(info));
if (status)
return false;
return true;
}
写一段回调的汇编:
MyCallbackEntry PROC
mov gs:[2E0H], rsp ;Win10 TEB InstrumentationCallbackPreviousSp (保存的线程参数地址)
mov gs:[2D8H], r10 ;Win10 TEB InstrumentationCallbackPreviousPc (syscall 的返回地址)
mov r10, rcx ;保存rcx
sub rsp, 4D0H ;Context结构大小
and rsp, -10H ;align rsp
mov rcx, rsp ;parameters are fun
call __imp_RtlCaptureContext ;保存线程Context上下文
sub rsp, 20H ;开辟栈空间
call MyCallbackRoutine ;调用我们的函数
int 3 ;不应该执行到这里
MyCallbackEntry ENDP
下面实现MyCallbackRoutine:
void MyCallbackRoutine(CONTEXT* context)
{
context->Rip = __readgsqword(0x02D8);//syscall 的返回地址
context->Rsp = __readgsqword(0x02E0);//context = rsp, ExceptionRecord = rsp + 0x4F0
context->Rcx = context->R10;
if (context->Rip == sysret_address)
if (exception->_self_exception_api((PEXCEPTION_RECORD)(context->Rsp + 0x4F0), (PCONTEXT)context->Rsp) == EXCEPTION_CONTINUE_EXECUTION)
context->Rip = rtl_restore_context_offset;
NtContinue(context, 0);
}
![记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收 记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/05/6-1716549167.jpeg)
到这里回调基本就跑起来了,接下来实现修改设置硬件断点(dr0-3)0-3。
实现设置硬断函数:
int Exception::SetHardWareBreakPoint(const wchar_t* main_modulename, DWORD64 dr7_statu, DWORD64 dr0, DWORD64 dr1, DWORD64 dr2, DWORD64 dr3)
{
this->_dr0 = dr0;
this->_dr1 = dr1;
this->_dr2 = dr2;
this->_dr3 = dr3;
//遍历线程 通过openthread获取到线程环境后设置硬件断点
HANDLE hTool32 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hTool32 != INVALID_HANDLE_VALUE)
{
THREADENTRY32 thread_entry32; //线程环境结构体
thread_entry32.dwSize = sizeof(THREADENTRY32);
HANDLE h_hook_thread = NULL;
MODULEINFO module_info = { 0 }; //模块信息
HANDLE target_modulehandle = GetModuleHandleW(main_modulename);
//从 ntdll.dll 中取出 ZwQueryInformationThread
(FARPROC&)ZwQueryInformationThread = ::GetProcAddress(GetModuleHandleA("ntdll"), "ZwQueryInformationThread");
if (target_modulehandle != 0)
{
//获取模块结束地址
GetModuleInformation(GetCurrentProcess(), (HMODULE)target_modulehandle, &module_info, sizeof(MODULEINFO));
__int64 target_modulehandle_endaddress = ((__int64)module_info.lpBaseOfDll + module_info.SizeOfImage);
//遍历线程
if (Thread32First(hTool32, &thread_entry32))
{
do
{
//如果线程父进程ID为当前进程ID
if (thread_entry32.th32OwnerProcessID == GetCurrentProcessId())
{
h_hook_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, thread_entry32.th32ThreadID);
// 获取线程入口地址
PVOID startaddr;//用来接收线程入口地址
ZwQueryInformationThread(h_hook_thread, (THREADINFOCLASS)ThreadQuerySetWin32StartAddress, &startaddr, sizeof(startaddr), NULL);
if (((__int64)startaddr >= (__int64)target_modulehandle) && ((__int64)startaddr <= target_modulehandle_endaddress))
{
//暂停线程
ULONG previous_count = NULL;
NtSuspendThread(h_hook_thread, &previous_count);
//设置硬件断点
CONTEXT thread_context = { CONTEXT_DEBUG_REGISTERS };
thread_context.ContextFlags = CONTEXT_ALL;
//得到指定线程的环境(上下文)
if (!GetThreadContext(h_hook_thread, &thread_context))
return 3;
thread_context.Dr0 = dr0;
thread_context.Dr1 = dr1;
thread_context.Dr2 = dr2;
thread_context.Dr3 = dr3;
thread_context.Dr7 = dr7_statu;
if (NtSetContextThread(h_hook_thread, &thread_context) != NULL)
return 4;
if (!GetThreadContext(h_hook_thread, &thread_context))
return 3;
//恢复线程
NtResumeThread(h_hook_thread, &previous_count);
}
CloseHandle(h_hook_thread);
}
} while (Thread32Next(hTool32, &thread_entry32));
}
CloseHandle(hTool32);
return true;
}
else
return 2;//模块句柄获取失败
}
return 0;
}
LONG WINAPI ExceptionHandler(PEXCEPTION_RECORD exception_record, PCONTEXT context)
{
//hardware breakpoint
if (exception_record->ExceptionCode == EXCEPTION_SINGLE_STEP)
{
if (exception_record->ExceptionAddress == (PVOID64)exception->_dr0)
{
//ACE-Base64.dll + 815844 - 48 89 47 08 - mov[rdi + 08], rax //Hook点
//ACE-Base64.dll + 815848 - FF 53 20 - call qword ptr[rbx + 20] //跳过执行
//ACE-Base64.dll + 81584B - 48 8B 1B - mov rbx, [rbx]
std::cout << "caller address: " << std::hex << *(DWORD64*)context->Rsi << std::endl;
std::cout << "callee address: " << std::hex << *(DWORD64*)(context->Rbx + 0x20) << std::endl;
context->Rip = exception->_dr0 + 0x07;
return EXCEPTION_CONTINUE_EXECUTION;
}
else if (exception_record->ExceptionAddress == (PVOID64)exception->_dr1)
{
return EXCEPTION_CONTINUE_EXECUTION;
}
else if (exception_record->ExceptionAddress == (PVOID64)exception->_dr2)
{
return EXCEPTION_CONTINUE_EXECUTION;
}
else if (exception_record->ExceptionAddress == (PVOID64)exception->_dr3)
{
return EXCEPTION_CONTINUE_EXECUTION;
}
else
{
context->Dr0 = exception->_dr0;
context->Dr1 = exception->_dr1;
context->Dr2 = exception->_dr2;
context->Dr3 = exception->_dr3;
return EXCEPTION_CONTINUE_SEARCH;
}
}
//software breakpoint
else if (exception_record->ExceptionCode == EXCEPTION_BREAKPOINT)
{
}
return EXCEPTION_CONTINUE_SEARCH;
}
最后附上函数调用:
exception = std::make_shared<Exception>();
exception->InstallException(ExceptionHandler);
DWORD64 ace_base_module = 0;
while (true)
{
ace_base_module = (DWORD64)::GetModuleHandleA("ACE-Base64.dll");
if (ace_base_module > 0x1000)
break;
}
auto value = exception->SetHardWareBreakPoint(L"crossfire.exe", 0x455, ace_base_module + 0x815844, 0x0, 0x0, 0x0);
printf("value:%dn", value);
三
效果展示
![记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收 记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/05/8-1716549167.jpeg)
四
总结
贴上项目地址:https://github.com/gn277/ExceptionByInstCallback.git
看雪ID:GN-顾念
https://bbs.kanxue.com/user-home-971710.htm
# 往期推荐
2、BFS Ekoparty 2022 Linux Kernel Exploitation Challenge
3、银狐样本分析
![记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收 记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/05/8-1716549169.gif)
球分享
![记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收 记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/05/10-1716549170.gif)
球点赞
![记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收 记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/05/1-1716549170.gif)
球在看
![记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收 记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收](https://ctfiot.oss-cn-beijing.aliyuncs.com/uploads/2024/05/1-1716549171.gif)
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):记录一次鹅厂反作弊绕过之利用回调完成异常派遣的提前接收