分享
 
 
 

Windows NT System-Call Hooking (Dr. Dobb's Journal)

王朝system·作者佚名  2006-04-28
窄屏简体版  字體: |||超大  

---------------------------------------------------------------------

Notes from "Windows NT System-Call Hooking" (Dr. Dobb's Journal, '97)

---------------------------------------------------------------------

Intro : kernel hooks (from "Rootkits : Subverting the Windows kernel")

- kernel mem : high virtual address memory region

- x86 : kernel mem resides in region of mem 0x8000000 and above

-- if use /3GB boot config, kernel mem starts at 0xC0000000

- processes can't access kernel mem

-- exception : when a process has debug privileges or

when a call gate has been installed

- rootkit : can access krenel memory IF implements a device driver

-- then rootkit operating in ring 0

Core components of NT : ntoskrnl (ntoskrnl.exe), win32k (win32k.sys)

- NTOSKRNL and WIN32K : are part of the kernel

- win32k : added to kernel in NT 4.0

-- implements graphical system services

-- much of win32 API's graphics engine moved to kernel to boost perf

-- that fxnality was previously implemented in user mode by gdi32.dll

and user32.dll

- NTDLL.DLL : also exists at user level, sits on top of NTOSKRNL

- win32 (API) : consists of kernel32.dll, gdi32.dll, user32.dll

- win32 (API) : sits on top of NTDLL.DLL, exists at user level

-- win32 subsystem

-- when an app makes a call to a win32 (api) function, usually the

called DLL will in the end call upon native NT services provided by

NTOSKRNL or WIN32K

- invoke NTOSKRNL via calling to NTDLL.DLL

- for example CreateFile(...) is a function exported by kernel32.dll

- but this function calls several kernel functions depending upon

the values of the flags passed to CreateFile(...)

- call to kernel to see whether desired file already exists

- call to kernel to create or open the file

- call to kernel to get the new/opened file's attributes

User-level subsystems : win32 (by which we mean the win32 api), POSIX, OS/2

NTDLL.DLL : exports NTOSKRNL to user-level subsystems

- populates EAX register with system call number of kernel function

- then executes system call trap (int 2Eh for x86 NT)

- provides a very thin wrapper on kernel services

- for example the ntdll.dll function ZwCreateFile(...) is invoked by CreateFile(...)

-- ZwCreateFile(...) disassembled

mov eax, 17h // system call # is 0x17 for NT (0x20 for 2k, 0x25 for XP)

lea edx, [esp+4] // make edx point to function params (user-mode stack)

// lea : loads edx with address of user args

int 2Eh // execute sys call trap for NT, 2k on x86

// (SYSENTER is used for XP,2003 on x86)

ret 2Ch

- ZwCreateFile(...) has alias NtCreateFile(...) - either can be used

-- this is true for all Zw* calls :

- for user-mode programs, either of these two can be used interchangeably

-- each refers to the same entry point in ntdll.dll

- for kernel-mode programs, these are linked against ntoskrnl.exe,

not against ntdll.dll; so the different forms (NtXxx and ZwXxx)

refer to different entry points

ZwXxx : contains copy of the code from ntdll.dll

- reenters the kernel, uses the system service dispatcher

- system service dispatcher sets previous mode to be kernel mode

- then when the actual code of the system service is exec'd,

much of the preamble is skipped

- b/c preamble code includes privilege checks etc which will

succeed regardless of ACLs on any object since previous mode

== kernel mode

NtXxx : results depend on the previous mode

- kernel mode code has little control over what previous mode is here

- if service has any params that are pointers and these pointers

point to automatic or static vars in the kernel mode program

then if previous mode is user mode then call will fail

- some fraction of native API calls are defined in <ntddk.h>

- and in general user code can access ntdll functions directly (though

the official documentation on these is sparse)

- Trapping to kernel-mode ( x86 ):

- there is no mode of x86 cpu called "kernel-mode"

- contrast : Motorola 68000 which has a flag in a status regiter that tells

the cpu if it is currently exec'ing in user-mode or supervisor-mode

- for x86 : the privilege level of the code segment that is currently

executing determinies the privilege level of the executing program

- the kernel finds the addy of the service (function) to handle the call

by looking at the executing thread's Thread Environment Block (TEB)

- this contains a thread's registers, priority level, a pointer to its

process, and a pointer to the thread's Service Table List

- The Service Table List contains a pointer to a table that contains the

addresses of all kernel services

-- so Service Table List is a data structure, contains :

- a pointer to the NTOSKRNL call table

- the # of NTOSKRNL syscalls

- a pointer to the NTOSKRNL arg table

- a pointer to the win32k call table

- the # of win32k syscalls

- a pointer to the win32k arg table

- the appropriate address is determined via returning the entry in that

table at the location specified by eax * 4 (each entry is 4 bytes long)

-- kernel's system call trap handler does this (& makes sure sys call #

is a valid one -- i.e. is within the range covered by this table)

- there is a parallel table which contains the size of each function's

arguments in bytes

- win32 (api) "system calls" have "system call numbers" that start at

0x1000 whereas kernel system call numbers start at 0

- kernel's system call trap handler then gets the address of the service

it must call

- then kernel's system call trap handler reads how many bytes are

required by this function's arguments -- this will be what that handler

pushes onto its stack from the caller's stack as it calls the service

- Some weirdness clarification : each thread could potentially point to a

unique Service Table List ... however all such lists actually point to

global (shared) service and argument-length tables

==> so if can change an entry in either the ntoskrnl or win32k

service tables (to make such point to a hook routine) : will cause

all threads to use these new <altered> addresses & thus our routines

++++++++++++++++++++++++++++

Actually hooking the calls :

++++++++++++++++++++++++++++

Unlike for the Win '9x kernel, NT doesn't provide a service-hooking function

So must write NT-version-dependent code to achieve this functionality

NT versions vary in two relevant parameters :

(1) the offset in the TEB where the Service Table List pointer lives

(2) the system call numbers that identify services

UPDATED knowledge : re (1)

- KeServiceDescriptorTable is a symbol exported by ntoskrnl.exe

- so can load ntoskrnl.exe into memory

- then search its export table for that symbol to get the table's offset address (RVA)

- to get the physical addy from the offset addy, get the kernel's base address

- then from that eventually get the System Service Dispatch Table (SSDT)

which is equivalent to what we call the NTOSKRNL Service Table above

and the System Service Parameter Table (SSPT) is equivalent to what we

call the NTOSKRNL Argument Table above (contains lengths of args to fxns).

+ anyway, code can be written to accomplish this regardless of the NT version

- which was not the case for original implementation which required

using a fixed offset to manually index into the TEB

- where that offset varied (and still does) by version

- as does the location of the KeServiceDescriptorTable

===================================

More in-depth & current description

===================================

"Windows NT Native API" : set of system services provided by the kernel to

both the user and the kernel

- the address for each function which is part of this native API can be

found in the SSDT; the length of the args for each such function can be

found in the SSPT

-- both tables are indexed by system call # times a constant, which for

the SSDT is 4 and for the SSPT is 1

- KeServiceDescriptorTable : exported by ntoskrnl.exe

-- contains a pointer to each of: SSDT, SSPT

-- is equivalent to the "Service Table List" from above

- to call a specific function, the system service dispatcher (previously

referred to as the "kernel's system call trap handler") -- which is

called KiSystemService -- takes value in EAX and multiplies it by four

to get the index into the SSDT and takes the value in EAX and uses that

to index into the SSPT

- KiSystemService *acts* when int 2eh or SYSENTER is executed

- an application can call KiSystemService directly

- once you get your code loaded as a (kernel) device driver (described elsewhere),

your code can change the SSDT to point to a function it provides

instead of into NTOSKRNL.exe or WIN32K.SYS

- when non-kernel code calls into the kernel, the request is processed by

KiSystemService which uses the altered SSDT

+++++++++++++++++++++++++++++++

Obtaining a pointer to the SSDT

+++++++++++++++++++++++++++++++

typedef struct ServiceDescriptorTable {

SDE ServiceDescriptor[4];

} SDT;

typedef struct ServiceDescriptorEntry {

PDWORD KiServiceTable;

PDWORD CounterBaseTable;

DWORD ServiceLimit;

PBYTE ArgumentTable;

} SDE;

ServiceDescriptor[0].KiServiceTable : contains pointer to SSDT of system

services implemented by ntoskrnl.exe

Now if the syscall # for NtWriteFile is 0xed (as it is for win 2k; it's

0xc8 for NT and 0x0112 for XP and 0x011c for win 2003 server), then the

DWORD value at KiServiceTable[0xed] is a function pointer to NtWriteFile

---------------------------------------------------

Determining the physical memory address of the SSDT

---------------------------------------------------

Recall that the KeServiceDescriptorTable has a KiServiceTable member,

which contains the address of the SSDT.

So first we'll look for the KeServiceDescriptorTable

But its address in mem varies across versions of the OS

But the KeServiceDescriptorTable is a symbol exported by ntoskrnl.exe

So strategy is to load ntoskrnl.exe into memory then search for that

symbol in ntoskrnl.exe's export table : this will give us the offset

address (RVA) of that symbol.

So to convert that to a physical memory address, we must first know the

kernel's (ntoskrnl.exe's) base address "in protected-mode virtual memory"

- call ZwQuerySystemInformation with SystemModuleInformation as 1st param

PhysMemAddyKeSvcDescrTbl = KernelVirtualBaseAddress + Offset (from above)

- 0x8000000

-- where that 0x80000000 comes from above

[x86 : kernel mem resides in region of mem 0x8000000 and above]

So then we map the physical memory page containing the

KeServiceDescriptorTable into the userland process's virtual memory

[see below]

- then we get the address of the SSDT via

KeServiceDescriptorTable[0].KiServiceTable

- then we have to convert that address to a physical memory address

PhysMemAddySSDT = VirtualMemAddyServiceTable - 0x80000000

==============================

Modifying SSDT from user space [Chew Keong Tan, SIG^2]

==============================

- write directly to kernel memory using \device\physicalmemory

- assumes program running with administrator privilege

(1) use NtOpenSection (exported by ntdll.dll) with access flags

SECTION_MAP_READ | SECTION_MAP_WRITE to get a handle to \device\physicalmemory

- will usually fail since administrator doesn't have

SECTION_MAP_WRITE privileges on \device\physicalmemory

(2) use NtOpenSection with access flags READ_CONTROL | WRITE_DAC to get

a handle to \device\physicalmemory

- allows a new DACL to be added to the \device\physicalmemory

object

(3) add a DACL to \device\physicalmemory granting SECTION_MAP_WRITE

access to the administrator account

(4) repeat step (1)

- so now user-space prog should have a handle to \device\physicalmemory

- to write to physical memory, must map the physical memory page into

its virtual address space

-- use NtMapViewOfSection

ntStatus = NtMapViewOfSection(

hPhysMem, // handle to \device\physicalmemory

(HANDLE)-1, //

virtualAddr, // OUT : virt mem where phys mem mapped to

0, //

*length, //

&viewBase, // IN/OUT : phys mem addy to map in

length, // IN/OUT : size of mapped phys mem

ViewShare, //

0, //

PAGE_READWRITE // map for read/write access

);

- after mapping the physical memory pages into its virtual memory space,

the user-space program can read and write to those pages like any other

allocated memory

--------------------

Overwriting the SSDT

--------------------

Recall that the SSDT lives in kernel memory

- this is why our hooker must be a kernel device driver

- then we're running in kernel mode and thus can modify the SSDT, theoretically

However, the SSDT may be read-only which decreases the effect of our

kernel-mode existence on ability to write to the SSDT; in this case we

have to do some funny stuff in order to be able to write to the SSDT

[NB: if attempt to write to read-only mem, get blue screen of death]

(1) modify CR0 register to bypass memory protections (pgs. 66-7,

"Rootkits: Subverting...")

- control register zero (cr0)

-- contains bits which control how the processor behaves

-- modify cr0 to disable memory-access protection in the kernel

- has write-protect bit : controls whether processor will allow writes

to memory pages marked as read-only

-- set to zero disables memory protection (hey, is this what we do

when we create a REG_DWORD value in

HKLM\System\CurrentControlSet\Control\SessionManager\Memory Management

called "EnforceWriteProtection" which has value 0?

- assembly code to achieve this

(2) using an MDL

- you can describe a region of memory with a Memory Descriptor List (MDL)

- MDL : contains start addy, owning process, # of bytes, and flags for

that memory region

// <ntddk.h>, <wdm.h>

typedef struct _MDL {

struct _MDL *Next;

CSHORT Size;

CSHORT MdlFlags;

struct _EPROCESS *Process; // owning process

PVOID MappedSystemVa;

PVOID StartVa;

ULONG ByteCount;

ULONG ByteOffset;

} MDL, *PMDL;

- so we'll want to change the flags in order to be able to write to the SSDT

***********************************************************************************

#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;

// obtain value for KeServiceDescriptorTable.ServiceTableBase and

// obtain value for KeServiceDescriptorTable.NumberOfServices

// save old sys call locations

// map the SSDT into userland

g_pmdlSystemCall = MmCreateMdl( NULL,

KeServiceDescriptorTable.ServiceTableBase,

KeServiceDescriptorTable.NumberOfServices );

// 1st arg : IN memory descriptor list

// 2nd arg : IN base (PVOID) --> think this becomes PVOID StartVa

// 3rd arg : IN length (SIZE_T) --> think this becomes CSHORT Size

// See also : IoAllocateMdl(...)

if ( !g_pmdlSystemCall )

return STATUS_UNSUCCESSFUL;

// updates g_pmdlSystemCall MDL;

// given the starting virtual address (ServiceTableBase) and its size,

// figure out the corresponding physical pages' address, ByteCount, etc.

MmBuildMdlForNonPagedPool( g_pmdlSystemCall );

// change permissions on the MDL : will allow you to write to this mem region

g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;

// locks desired pages (the MDL pages)

// maps the physical pages described by g_pmdlSystemCall

// using access mode == KernelMode

// returns : starting address of the mapped pages

MappedSystemCallTable = MmMapLockedPages( g_pmdlSystemCall, KernelMode );

// now change addresses in KeServiceDescriptorTable to what you want...

// MappedSystemCallTable : same address as original SSDT but now writable.

***********************************************************************************

Taking a step back : what did we just do?

(1) imposed a description onto a region of memory

- where that region contains our SSDT

(2) change a property of that description so that we could write to

that region

----------------

Actually hooking

----------------

Both of the following two #defines work because all Zw* functions

exported by NTOSKRNL.exe start with :

mov eax, ULONG // where ULONG is the index # of the syscall in th SSDT

--> so look at the second byte of any Zw* function in order to get its

syscall # (clever! see Hoglund/Butler)

// takes addy of a Zw* function and returns the index # of its Nt* in the SSDT

#define SYSTEMSERVICE( _func ) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR) _func + 1) ]

// takes addy of a Zw* function and returns its index in the SSDT

#define SYSCALL_INDEX( _Function ) *(PULONG)((PUCHAR) _Function + 1)

// take the address of a Zw* function, get its index, exchange the addy

// at that index in the SSDT with the addy of _Hook

// write the original Zw* function address to _Orig (used for restoration later)

#define HOOK_SYSCALL( _Function, _Hook, _Orig ) _Orig = (PVOID) InterlockedExchange( (PLONG) &MappedSystemCallTable[ SYSCALL_INDEX( _Function )], (LONG) _Hook )

//

// not sure why this takes _Hook as an argument?

//

#define UNHOOK_SYSCALL( _Func, _Hook, _Orig ) InterlockedExchange( (PLONG) &MappedSystemCallTable[ SYSCALL_INDEX( _Func ) ], (LONG) _Orig )

============

References :

============

(1) Windows NT System Call Hooking

http://www.ddj.com/documents/s=945/ddj9701e/

(2) Windows System Call Table (NT/2k/XP/2003)

http://www.metasploit.com/users/opcode/syscalls.html

(3) Windows NT/2000 Native API Reference (Gary Nebbett)

(4) How Do Windows NT System Calls REALLY Work? (John Gulbrandsen)

http://www.codeguru.com/Cpp/W-P/system/devicedriverdevelopment/article.php/c8035/

(5) Defeating Kernel Native-API hookers via SSDT Restoration

http://www.security.org.sg/code/SIG2_DefeatingNativeAPIHookers.pdf

(6) Rootkits: Subverting the Windows Kernel (Hoglund/Butler)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有