文章作者:zhuwg
信息来源:邪恶八进制信息安全团队(
www.eviloctal.com)
1。取得shadow ssdt真实地址
系统只提供了KeServiceDescriptorTable导出
KeServiceDescriptorTableShadow是个未导出结构
定义
复制内容到剪贴板
代码:
typedef struct _SYSTEM_SERVICE_TABLE
{
PNTPROC ServiceTable; // array of entry points
PDWORD CounterTable; // array of usage counters
DWORD ServiceLimit; // number of table entries
PBYTE ArgumentTable; // array of byte counts
}
SYSTEM_SERVICE_TABLE,
*PSYSTEM_SERVICE_TABLE,
**PPSYSTEM_SERVICE_TABLE;
//-----------------------------------------------------------------------------------------------------------
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
SYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe ( native api )
SYSTEM_SERVICE_TABLE win32k; // win32k.sys (gdi/user support)
SYSTEM_SERVICE_TABLE Table3; // not used
SYSTEM_SERVICE_TABLE Table4; // not used
}
SYSTEM_DESCRIPTOR_TABLE,
*PSYSTEM_DESCRIPTOR_TABLE,
**PPSYSTEM_DESCRIPTOR_TABLE;
其实 KeServiceDescriptorTableShadow包含4个子结构,其中第一个就是ntoskrnl.exe ( native api ),和KeServiceDescriptorTable指向一样 我们真正需要获得的是第二个win32k.sys (gdi/user support),第三个和第四个一般不使用
定位方法
1。硬编码
复制内容到剪贴板
代码:
//for xp
if(gKernelVersion==WINXP)
KeServiceDescriptorTableShadow=KeServiceDescriptorTable-0x40;
//for 2k
if(gKernelVersion==WIN2K)
KeServiceDescriptorTableShadow=KeServiceDescriptorTable+0xE0;
2。搜索KeAddSystemServiceTable
反汇编代码可以看出
复制内容到剪贴板
代码:
lkd> u KeAddSystemServiceTable l 40
nt!KeAddSystemServiceTable:
805ba589 8bff mov edi,edi
805ba58b 55 push ebp
805ba58c 8bec mov ebp,esp
805ba58e 837d1803 cmp dword ptr [ebp+18h],3
805ba592 774e ja nt!KeAddSystemServiceTable+0x6b (805ba5e2)
805ba594 8b4518 mov eax,dword ptr [ebp+18h]
805ba597 c1e004 shl eax,4
805ba59a 83b880a6558000 cmp dword ptr nt!KeServiceDescriptorTable (8055a680)[eax],0
805ba5a1 753f jne nt!KeAddSystemServiceTable+0x6b (805ba5e2)
805ba5a3 8d8840a65580 lea ecx,nt!KeServiceDescriptorTableShadow (8055a640)[eax]
805ba5a9 833900 cmp dword ptr [ecx],0
805ba5ac 7534 jne nt!KeAddSystemServiceTable+0x6b (805ba5e2)
805ba5ae 837d1801 cmp dword ptr [ebp+18h],1
805ba5b2 8b5508 mov edx,dword ptr [ebp+8]
805ba5b5 56 push esi
805ba5b6 8b7510 mov esi,dword ptr [ebp+10h]
805ba5b9 57 push edi
805ba5ba 8b7d14 mov edi,dword ptr [ebp+14h]
805ba5bd 8911 mov dword ptr [ecx],edx
805ba5bf 8b4d0c mov ecx,dword ptr [ebp+0Ch]
805ba5c2 898844a65580 mov dword ptr nt!KeServiceDescriptorTableShadow+0x4 (8055a644)[eax],ecx
805ba5c8 89b048a65580 mov dword ptr nt!KeServiceDescriptorTableShadow+0x8 (8055a648)[eax],esi
805ba5ce 89b84ca65580 mov dword ptr nt!KeServiceDescriptorTableShadow+0xc (8055a64c)[eax],edi
805ba5d4 0f855a3e0300 jne nt!KeAddSystemServiceTable+0x4d (805ee434)
805ba5da 5f pop edi
805ba5db b001 mov al,1
805ba5dd 5e pop esi
805ba5de 5d pop ebp
805ba5df c21400 ret 14h
805ba5e2 32c0 xor al,al
805ba5e4 ebf8 jmp nt!KeAddSystemServiceTable+0x6d (805ba5de)
805ba5e6 90 nop
805ba5e7 90 nop
805ba5e8 90 nop
805ba5e9 90 nop
805ba5ea 90 nop
搜索办法很简单
就是找到
805ba5a3 8d8840a65580 lea ecx,nt!KeServiceDescriptorTableShadow (8055a640)[eax]
的8d88 40a65580 lea ecx, nt!KeServiceDescriptorTableShadow
代码如下
复制内容到剪贴板
代码:
void GetKeServiceDescriptorTableShadow()
{
PUCHAR cPtr, pOpcode;
ULONG Length;
for (cPtr = (PUCHAR)KeAddSystemServiceTable;
cPtr < (PUCHAR)KeAddSystemServiceTable + PAGE_SIZE;
cPtr += Length)
{
if (!MmIsAddressValid(cPtr)) break;
Length = SizeOfCode(cPtr, &pOpcode);
if (!Length || (Length == 1 && *pOpcode == 0xC3)) break;
if (*(PUSHORT)pOpcode == 0x888D)
{
KeServiceDescriptorTableShadow = *(PVOID *)(pOpcode + 2);
break;
}
}
}
3。从KTHREAD.ServiceTable里面搜索
如果是GUI线程 那么就是指向ServiceDescriptorTableShadow
其实个人更加倾向这个办法 稳定安全可靠,上面那个代码也许会受到以后系统代码变化影响 但是这个肯定不会
复制内容到剪贴板
代码:
GetServiceDescriptorTableShadowAddress proc uses esi edi ebx
local dwThreadId:DWORD
xor ebx, ebx ; = NULL. Assume ServiceDescriptorTableShadow will be not found
mov eax, KeServiceDescriptorTable
mov esi, [eax]
; Find KTHREAD.ServiceTable field
; For non-GUI threads this field == KeServiceDescriptorTable
; and it points to ServiceDescriptorTable
; For GUI threads
; ServiceDescriptorTableShadow
invoke KeGetCurrentThread
mov edi, 200h-4
.while edi
.break .if dword ptr [eax][edi] == esi
dec edi
.endw
.if edi != 0
; edi = offset to ServiceTable field in KTHREAD structure
mov dwThreadId, 080h
.while dwThreadId < 400h
push eax ; reserve DWORD on stack
invoke PsLookupThreadByThreadId, dwThreadId, esp
pop ecx ; -> ETHREAD/KTHREAD
.if eax == STATUS_SUCCESS
push dword ptr [ecx][edi]
fastcall ObfDereferenceObject, ecx
pop eax
.if eax != esi
mov edx, MmSystemRangeStart
mov edx, [edx]
mov edx, [edx]
.if eax > edx ; some stupid error checking
mov ebx, eax
invoke DbgPrint, $CTA0("FindShadowTable: Found in thread with ID: %X\n"), dwThreadId
.break
.endif
.endif
.endif
add dwThreadId, 4
.endw
.endif
mov eax, ebx
ret
GetServiceDescriptorTableShadowAddress endp
4.mj0011所说的搜索有效内存地址的办法
复制内容到剪贴板
代码:
/*
define structure for the system service table
*/
struct SYS_SERVICE_TABLE {
void **ServiceTable;
unsigned long CounterTable;
unsigned long ServiceLimit;
void **ArgumentsTable;
};
SYSTEM_DESCRIPTOR_TABLE KeServiceDescriptorTableShadow;
/*
Define KeServiceDescriptorTable based on the SST structure
*/
extern struct SYS_SERVICE_TABLE *KeServiceDescriptorTable;
/*
Declare function GetServiceDescriptorShadowTableAddress()
*/
//struct SYS_SERVICE_TABLE * GetServiceDescriptorShadowTableAddress ();
/*
Declare the KeAddSystemServiceTable. This is just a
handle to the call function, it will be used by the function
above to obtain the correct address of the KeServiceDescriptorShadowTable
*/
__declspec(dllimport) KeAddSystemServiceTable (ULONG, ULONG, ULONG, ULONG, ULONG);
struct SYS_SERVICE_TABLE * GetServiceDescriptorShadowTableAddress ()
{
// First, obtain a pointer to KeAddSystemServiceTable
unsigned char *check = (unsigned char*)KeAddSystemServiceTable;
int i;
//Initialize an instance of System Service Table, will be used to
//obtain an address from KeAddSystemServiceTable
struct SYS_SERVICE_TABLE *rc=0;
// Make 100 attempts to match a valid address with that of KeServiceDescriptorTable
for (i=0; i<=99; i++) {
__try {
// try to obtain an address from KeAddSystemServiceTable
rc = *(struct SYS_SERVICE_TABLE**)check;
// if this address is NOT valid OR it itself is the address of
//KeServiceDescriptorTable OR its first entry is NOT equal
//to the first entry of KeServiceDescriptorTable
if (!MmIsAddressValid (rc) || (rc == KeServiceDescriptorTable)
|| (memcmp (rc, KeServiceDescriptorTable, sizeof (*rc)) != 0)) {
// Proceed with the next address
check++;
// don't forget to reset the old address
rc = 0;
}
} __except (EXCEPTION_EXECUTE_HANDLER) { rc = 0; }
// when the loop is completed, check if it produced a valid address
if (rc)
// because if it didn't, we failed to find the address of KeServiceDescriptorTableShadow
break;
}
// otherwise, there is a valid address! So return it!
return rc;
}
二。函数名定位
这个似乎没有多少好办法,解析pdb可以是可以 但是很麻烦
不过还好的是 同一个版本的系统 调用号一样
所以只需要3套 2k xp 2k3的调用号就可以完成hook
pdb办法
SymInitialize初始化
SymSetSearchPath “srv**symbols*
http://msdl.microsoft.com/”
SymLoadModule
SymGetSymFromName
老v曾经发出1个获取shadow地址和函数名称的工具 使用他可以很方便的获取
具体代码可以F5查看
以上读取pdb的方法测试使用的可以的
但是实际使用很麻烦
经过分析 我找到了1个比较好的定位办法
我们以SetWindowsHookExA为例子
复制内容到剪贴板
代码:
.text:77D311D1 ; HHOOK __stdcall SetWindowsHookExA(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId)
.text:77D311D1 public _SetWindowsHookExA@16
.text:77D311D1 _SetWindowsHookExA@16 proc near
.text:77D311D1
.text:77D311D1 idHook = dword ptr 8
.text:77D311D1 lpfn = dword ptr 0Ch
.text:77D311D1 hModule = dword ptr 10h
.text:77D311D1 dwThreadId = dword ptr 14h
.text:77D311D1
.text:77D311D1 mov edi, edi
.text:77D311D3 push ebp
.text:77D311D4 mov ebp, esp
.text:77D311D6 push 2 ; int
.text:77D311D8 push [ebp+dwThreadId] ; int
.text:77D311DB push [ebp+hModule] ; hModule
.text:77D311DE push [ebp+lpfn] ; int
.text:77D311E1 push [ebp+idHook] ; int
.text:77D311E4 call _SetWindowsHookExAW@20 ; SetWindowsHookExAW(x,x,x,x,x)
.text:77D311E9 pop ebp
.text:77D311EA retn 10h
.text:77D311EA _SetWindowsHookExA@16 endp
.text:77D2DCFD ; int __stdcall SetWindowsHookExAW(int, int, HMODULE hModule, int, int)
.text:77D2DCFD _SetWindowsHookExAW@20 proc near ; CODE XREF: SetWindowsHookExW(x,x,x,x)+13p
.text:77D2DCFD ; SetWindowsHookExA(x,x,x,x)+13p
.text:77D2DCFD
.text:77D2DCFD Filename = word ptr -20Ch
.text:77D2DCFD var_4 = dword ptr -4
.text:77D2DCFD arg_0 = dword ptr 8
.text:77D2DCFD arg_4 = dword ptr 0Ch
.text:77D2DCFD hModule = dword ptr 10h
.text:77D2DCFD arg_C = dword ptr 14h
.text:77D2DCFD arg_10 = dword ptr 18h
.text:77D2DCFD
.text:77D2DCFD mov edi, edi
.text:77D2DCFF push ebp
.text:77D2DD00 mov ebp, esp
.text:77D2DD02 sub esp, 20Ch
.text:77D2DD08 mov eax, ___security_cookie
.text:77D2DD0D push esi
.text:77D2DD0E mov esi, [ebp+hModule]
.text:77D2DD11 test esi, esi
.text:77D2DD13 push edi
.text:77D2DD14 mov edi, [ebp+arg_4]
.text:77D2DD17 mov [ebp+var_4], eax
.text:77D2DD1A jz short loc_77D2DD33
.text:77D2DD1C push 104h ; nSize
.text:77D2DD21 lea eax, [ebp+Filename]
.text:77D2DD27 push eax ; lpFilename
.text:77D2DD28 push esi ; hModule
.text:77D2DD29 call ds:__imp__GetModuleFileNameW@12 ; GetModuleFileNameW(x,x,x)
.text:77D2DD2F test eax, eax
.text:77D2DD31 jz short loc_77D2DD52
.text:77D2DD33
.text:77D2DD33 loc_77D2DD33: ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+1Dj
.text:77D2DD33 push [ebp+arg_10]
.text:77D2DD36 mov eax, esi
.text:77D2DD38 push edi
.text:77D2DD39 push [ebp+arg_0]
.text:77D2DD3C neg eax
.text:77D2DD3E push [ebp+arg_C]
.text:77D2DD41 sbb eax, eax
.text:77D2DD43 lea ecx, [ebp+Filename]
.text:77D2DD49 and eax, ecx
.text:77D2DD4B push eax
.text:77D2DD4C push esi
.text:77D2DD4D call __SetWindowsHookEx@24 ; _SetWindowsHookEx(x,x,x,x,x,x)
.text:77D2DD52
.text:77D2DD52 loc_77D2DD52: ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+34j
.text:77D2DD52 mov ecx, [ebp+var_4]
.text:77D2DD55 pop edi
.text:77D2DD56 pop esi
.text:77D2DD57 call @__security_check_cookie@4 ; __security_check_cookie(x)
.text:77D2DD5C leave
.text:77D2DD5D retn 14h
.text:77D2DD5D _SetWindowsHookExAW@20 endp
.text:77D2DD5D
.text:77D2DD65 ; __stdcall _SetWindowsHookEx(x, x, x, x, x, x)
.text:77D2DD65 __SetWindowsHookEx@24 proc near ; CODE XREF: SetWindowsHookExAW(x,x,x,x,x)+50p
.text:77D2DD65
.text:77D2DD65 var_10 = byte ptr -10h
.text:77D2DD65 var_8 = dword ptr -8
.text:77D2DD65 var_4 = dword ptr -4
.text:77D2DD65 arg_0 = dword ptr 8
.text:77D2DD65 arg_4 = dword ptr 0Ch
.text:77D2DD65 arg_8 = dword ptr 10h
.text:77D2DD65 arg_C = dword ptr 14h
.text:77D2DD65 arg_10 = dword ptr 18h
.text:77D2DD65 arg_14 = dword ptr 1Ch
.text:77D2DD65
.text:77D2DD65 mov edi, edi
.text:77D2DD67 push ebp
.text:77D2DD68 mov ebp, esp
.text:77D2DD6A sub esp, 10h
.text:77D2DD6D push [ebp+arg_4]
.text:77D2DD70 and [ebp+var_4], 0
.text:77D2DD74 lea eax, [ebp+var_10]
.text:77D2DD77 push eax
.text:77D2DD78 mov [ebp+var_8], eax
.text:77D2DD7B call ds:__imp__RtlInitUnicodeString@8 ; RtlInitUnicodeString(x,x)
.text:77D2DD81 push [ebp+arg_14]
.text:77D2DD84 push [ebp+arg_10]
.text:77D2DD87 push [ebp+arg_C]
.text:77D2DD8A push [ebp+arg_8]
.text:77D2DD8D push [ebp+var_8]
.text:77D2DD90 push [ebp+arg_0]
.text:77D2DD93 call _NtUserSetWindowsHookEx@24 ; NtUserSetWindowsHookEx(x,x,x,x,x,x)
.text:77D2DD98 leave
.text:77D2DD99 retn 18h
.text:77D2DD99 __SetWindowsHookEx@24 endp
.text:77D2DD99
.text:77D2DDA1 ; __stdcall NtUserSetWindowsHookEx(x, x, x, x, x, x)
.text:77D2DDA1 _NtUserSetWindowsHookEx@24 proc near ; CODE XREF: _SetWindowsHookEx(x,x,x,x,x,x)+2Ep
.text:77D2DDA1 mov eax, 1225h
.text:77D2DDA6 mov edx, 7FFE0300h
.text:77D2DDAB call dword ptr [edx]
.text:77D2DDAD retn 18h
.text:77D2DDAD _NtUserSetWindowsHookEx@24 endp
我们看这里 .text:77D2DDA1 mov eax, 1225h
里面有和ssdt一样的调用号 但是有个问题 NtUserSetWindowsHookEx在user32里面没有导出,我们需要多次搜索才行
复制内容到剪贴板
代码:
HHOOK STDCALL NtUserSetWindowsHookEx (HINSTANCE Mod, PUNICODE_STRING UnsafeModuleName, DWORD ThreadId, int HookId, HOOKPROC HookProc, BOOL Ansi)
这个就是原型 不过搜索起来确实是比较麻烦的,不知道还有无其他好办法了