Trao đổi với tôi

www.hdphim.info

6/11/09

[Rootkit] Căn bản về lkd

Căn bản về lkd

Author: Benina

A-Sử dụng Winddg:

Trong lkd (WinDbg) ta có thể debug 1 file thực thi hay có thể debug Kernel (tính năng này rất tuyệt). Để bắt đầu chúng ta bật WinDbg lên và :

-Vào menu File/Open Executable : Dùng để open debug một file exe

-Vào menu File/Kernel Debug.../Local Kernel: Dùng để debug NTOSKLN.

Trong WinXP support live debugger. Ta vào menu File - Kernel Debug – chọn Tab Local. Lúc đó nhìn vào hàng cuối cửa sổ có dòng command chờ lệnh là lkd>

Khái niệm cơ bản:

-Symbols là gì:

Như phần cài đặt WinDbg ta đã thấy, chúng ta cần cài symbols cho WinDbg. Vậy các symbols là gì?. Và tại sao lại phải cài đặt chúng?.

Theo từ điển tiếng việt symbol có nghĩa là biểu tượng, tượng trưng hay một số người dịch là ký hiệu. Còn về cuộc sống, symbol của đồng tiền đôla là $,..v..v..Vậy hiện tại trong đầu bạn đã có khái niệm về các symbols. Nhưng ko thể nói ra được vì do tiếng Việt chưa định nghĩa hòan chỉnh từ này. Để cho bạn hiểu rõ hơn, ở đây tôi đã search trên mạng khái niệm về symbol trong lĩnh vực viễn thông như sau:

Nguyên văn bởi nqbinhdi

Symbol không nên dịch là biểu trưng. Bản thân tôi khi dịch từ symbol cũng rất băn khoăn. Về bản chất, để có thể nâng cao hiệu quả sử dụng phổ (bandwidth efficiency), chức năng đầu tiên của điều chế số là ghép k bit thành một symbol, tức là thay vì sử dụng tín hiệu số cơ bản nhị phân để truyền tin (bit), chúng ta sử dụng tín hiệu số nhiều mức (M mức). Như vậy một tổ hợp k bit tạo nên một symbol - là một tín hiệu số có thể nhận một trong M = 2^k trạng thái hay giá trị có thể có và mang một lượng tin tức là k bit. Việc dịch symbol thành biểu trưng hay ký hiệu, hay dấu đều không lột tả được bản chất của vấn đề và theo kinh nghiệm của tôi, nó không giúp ích gì cho người mới học trong việc hiểu vấn đề cả. Trong tiếng Việt chúng ta từ trước không có khái niệm này.

Tôi rất muốn khuyến nghị mọi người sử dụng trực tiếp từ symbol như một từ lai, Việt hóa từ này như chúng ta đã từng sử dụng các từ xà phòng, đường ray, nhà ga, tăng-xê (hầm trú ẩn), ghi-đông mượn từ tiếng nước ngoài. Tại sao chúng ta đã sử dụng thành công các từ như bit hay chip mà lại không sử dụng từ symbol. Trước mắt, trong các tài liệu, để thể hiện việc mượn từ nước ngoài, chúng ta chỉ cần để từ symbol in nghiêng mà thôi. Đằng nào thì cũng chỉ những người làm kỹ thuật đọc đến những tài liệu này và vì vậy để nguyên từ như thế có khi còn dễ hiểu hơn là mỗi người dùng một từ Việt khác nhau mà gọi nó.

Nguyên văn bởi Tuan Duc

1 bit dữ liệu có giá trị 0 hoặc 1, để truyền giá trị 0 hoặc 1 đó thì người ta phải điều chế (bằng BPSK,QPSK, M-QAM... ) bit đó thành dạng symbol (ký tự).

Bản chất 1 symbols là 1 tín hiệu liện tục (hàm raised cosin, gaussian...). Trong OFDM sẽ gồm N sóng mang con liên tục, tại 1 khoãng thời gian thì trên mỗi sóng mang sẽ có 1 symbols, tức là có tổng cộng N symbols được truyền cùng lúc.

Bây giờ chúng ta bắt đầu tìm hiểu về symbols cho WinDbg của chúng ta. Hay nói rộng ra là symbols cho các debugger như SI chẳng hạn. Trong cuộc sống như trên tôi đã nói, con người sẽ hiểu ký hiệu $ tượng trưng cho đồng đôla. Vậy dấu $ là symbol của đồng đôla trong môi trường là thế giới con người. Do đó để một debugger hiểu một cái gì đó (một hàm hay một biến hệ thống chẳng hạn) thì ta phải đặt symbol cho nó trong một môi trường nhất định. Ở đây môi trường nhất định chính là chủng lọai và phiên bản của hệ điều hành. Vì vậy khi cài đặt symbols ta phải xem máy của mình là lọai Win gì, ví dụ WinXP SP1, WinXP SP2...Nếu ko có symbols thì debugger sẽ ko biết “nói chuyện” như thế nào khi nó giao tiếp với con người. Vì vậy chúng ta mới cần cài đặt symbols cho WinDbg. Tôi xin lấy ví dụ: Khi tôi “nói” với WinDbg rằng: Hảy cho tôi biết hàm ZwQuerySystemInformation nằm ở addr nào trong memory, lúc đó nó sẽ thông báo cho ta biết addr của hàm ấy thông qua symbols định sẳn. Có nghĩa là symbols sẽ qui định ứng với môi trường HĐH là WinXP SP2 chẳng hạn thì hàm ZwQuerySystemInformation sẽ nằm tại addr này. Tóm lại symbols bao hàm các định nghĩa ký hiệu tượng trưng cho hàm, biến, lọai dữ liệu hệ thống....

0/Lệnh .cls :

Xóa màn hình cửa sổ thông báo.

1/Lệnh u: unassemble Machine Code.

Lệnh này diassemble code từ addr1 đến addr2. Nếu ko có addr2 thì nó mặc định deassemble 8 dòng lệnh.

Ví dụ:

lkd> u 80502088 80502095

GetContextState failed, 0x80004001

nt!KiUnwaitThread+0x38:

80502088 7d08 jge nt!KiUnwaitThread+0x42 (80502092)

8050208a 03f9 add edi,ecx

8050208c 80b80302000002 cmp byte ptr [eax+203h],2

80502093 8bcf mov ecx,edi

lkd> u 80502088

nt!KiUnwaitThread+0x38:

80502088 7d08 jge nt!KiUnwaitThread+0x42 (80502092)

8050208a 03f9 add edi,ecx

8050208c 80b80302000002 cmp byte ptr [eax+203h],2

80502093 8bcf mov ecx,edi

80502095 7506 jne nt!KiUnwaitThread+0x4d (8050209d)

80502097 030dc4285680 add ecx,dword ptr [nt!PsPrioritySeperation (805628c4)]

8050209d 0fbedb movsx ebx,bl

805020a0 3bcb cmp ecx,ebx

2/Lệnh db, dw, dd: Dump memory Byte, Word, Dword

Dump bộ nhớ tại addr nào đó

Ví dụ:

lkd> dd 80503734

80503734 805a3054 805ef2d8 805f2b0e 805ef30a

80503744 805f2b48 805ef340 805f2b8c 805f2bd0

80503754 80613adc 8061481e 805ea67a 805ea2d2

80503764 805d330c 805d32bc 80614102 805b493a

80503774 8061371e 805a74de 805aef5e 805d4dd0

80503784 80500c00 80614810 80575900 80537bbc

80503794 8060cd26 805baeb4 805f3048 80621c18

805037a4 805f753a 805a3742 80621e6c 805a2ff4

lkd> dd 80503734 l 8

80503734 805a3054 805ef2d8 805f2b0e 805ef30a

80503744 805f2b48 805ef340 805f2b8c 805f2bd0

Ghi chú: “l 8” có nghĩa là dump 8 bytes

lkd> dd 80502588+0xAD*4 L 4

8050283c 012b8eb6 3c8b0000 55b5808d 14578580

lkd> dd KeServiceDescriptorTable L 4

8055b6e0 80503734 00000000 0000011c 80503ba8

lkd> dd 81bce838 + 0x88 L 4

81bce8c0 00000000 00000000 00000000 00000000

3/Lệnh x: examine symbols

Kiểm tra (hay khảo sát)các symbols. Liệt kê các symbolz của modulez

Có ba dạng:

- x *!* hiển thị tất cả các modulez có symbols brown được.

- x ! hiển thị symbolz của được lọc qua tham lệnh

Ví dụ:

lkd> x nt!Ke*

8057711e nt!IopFreeDCB =

80501346 nt!KiQuantumEnd =

8064305c nt!PiControlHaltDevice =

8066b6b6 nt!MiAllocateSpecialPool =

........................................................

8067af39 nt!SepAuditOptions =

8055993e nt!ErrorLogPortConnected =

80699b42 nt!PipNotifySetupDevices =

8061f4c8 nt!CcPfIsHostingApplication =

lkd> x nt!IopLoa*

8057f9fe nt!IopLoadDriver =

8067a068 nt!IopLoaderBlock =

80580134 nt!IopLoadUnloadDriver =

80580674 nt!IopLoadFileSystemDriver =

80576d12 nt!IopLoadDumpDriver =

- x . Chỉ sử dụng lệnh này khi module được cấp phát rồi.

4/Lệnh !object : Khảo sát một object nào đó

Các tham lệnh:

lkd> !object

Usage: !object [-p] | [[] | [

] | [0 ]]

Ví dụ:

lkd> !object \Device\PhysicalMemory

Object: e1002ce0 Type: (829c4438) Section

ObjectHeader: e1002cc8 (old version)

HandleCount: 0 PointerCount: 3

Directory Object: e1000320 Name: PhysicalMemory

5/Lệnh dt: Khảo sát một cấu trúc, biến

Lệnh này hiển thị thông tin về một biến, hay data type (lọai dữ liệu)

Tham lệnh:

dt

lkd> dt _OBJECT_HEADER e1002cc8

nt!_OBJECT_HEADER

+0x000 PointerCount : Int4B

+0x004 HandleCount : Int4B

+0x004 NextToFree : Ptr32 Void

+0x008 Type : Ptr32 _OBJECT_TYPE

+0x00c NameInfoOffset : UChar

+0x00d HandleInfoOffset : UChar

+0x00e QuotaInfoOffset : UChar

+0x00f Flags : UChar

+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION

+0x010 QuotaBlockCharged : Ptr32 Void

+0x014 SecurityDescriptor : Ptr32 Void

+0x018 Body : _QUAD

Ta có thể cho hiển thị thông tin cụ thể tại 1 addr cụ thể, điều này có ý nghĩa là tại addr đó nếu áp 1 lọai dữ liệu vào đó thì các thành phần của lọai dữ liệu đó có giá trị như thế nào. ví dụ:

lkd> dt _OBJECT_HEADER e1002cc8

nt!_OBJECT_HEADER

+0x000 PointerCount : 3

+0x004 HandleCount : 0

+0x004 NextToFree : (null)

+0x008 Type : 0x829c4438 _OBJECT_TYPE

+0x00c NameInfoOffset : 0x10 ''

+0x00d HandleInfoOffset : 0 ''

+0x00e QuotaInfoOffset : 0 ''

+0x00f Flags : 0x32 '2'

+0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION

+0x010 QuotaBlockCharged : 0x00000001

+0x014 SecurityDescriptor : 0xe100133f

+0x018 Body : _QUAD

lkd> dt _EPROCESS

nt!_EPROCESS

+0x000 Pcb : _KPROCESS

+0x06c ProcessLock : _EX_PUSH_LOCK

+0x070 CreateTime : _LARGE_INTEGER

+0x078 ExitTime : _LARGE_INTEGER

+0x080 RundownProtect : _EX_RUNDOWN_REF

+0x084 UniqueProcessId : Ptr32 Void

+0x088 ActiveProcessLinks : _LIST_ENTRY

.......

lkd> dt _LIST_ENTRY

nt!_LIST_ENTRY

+0x000 Flink : Ptr32 _LIST_ENTRY

+0x004 Blink : Ptr32 _LIST_ENTRY

lkd> dt nt!_ETHREAD

+0x000 Tcb : _KTHREAD

+0x1c0 CreateTime : _LARGE_INTEGER

+0x1c0 NestedFaultCount : Pos 0, 2 Bits

+0x1c0 ApcNeeded : Pos 2, 1 Bit

+0x1c8 ExitTime : _LARGE_INTEGER

+0x1c8 LpcReplyChain : _LIST_ENTRY

+0x1c8 KeyedWaitChain : _LIST_ENTRY

+0x1d0 ExitStatus : Int4B

+0x1d0 OfsChain : Ptr32 Void

+0x1d4 PostBlockList : _LIST_ENTRY

+0x1dc TerminationPort : Ptr32 _TERMINATION_PORT

+0x1dc ReaperLink : Ptr32 _ETHREAD

+0x1dc KeyedWaitValue : Ptr32 Void

.........

+0x250 LpcExitThreadCalled : Pos 1, 1 Bit

+0x250 AddressSpaceOwner : Pos 2, 1 Bit

+0x254 ForwardClusterOnly : UChar

+0x255 DisablePageFaultClustering : Uchar

6/Lệnh ln: Liệt kê symblos gần addr nhất (List Nearest Symbols)

Cú lệnh: ln addr

Ví dụ:

lkd> ln 8055b6e0

(8055b6e0) nt!KeServiceDescriptorTable | (8055b720) nt!KeLicensedProcessors

Exact matches:

nt!KeServiceDescriptorTable =

7/Lệnh lm: (List Loaded Modules)

Cú pháp: lm Options [a Address] [m Pattern | M Pattern]

Lệnh này liệt kê các modules đã load. các option đọc file help của WinDbg

Ví dụ:

lkd> lm

start end module name

804d7000 806e2000 nt (pdb symbols) C:\WINDOWS\Symbols\exe\ntkrpamp.pdb

Unloaded modules:

a9098000 a90c2000 kmixer.sys

a9098000 a90c2000 kmixer.sys

a9ac9000 a9af3000 kmixer.sys

a9ac9000 a9af3000 kmixer.sys

a9ac9000 a9af3000 kmixer.sys

a9b93000 a9bbd000 kmixer.sys

aa1dd000 aa1f1000 parport.sys

aa3d8000 aa402000 kmixer.sys

f8b71000 f8b72000 drmkaud.sys

aa42a000 aa44d000 aec.sys

aa5e2000 aa5ef000 DMusic.sys

aa742000 aa750000 swmidi.sys

f89e9000 f89eb000 splitter.sys

f8665000 f8675000 serial.sys

f87ad000 f87b2000 Cdaudio.SYS

f8217000 f821a000 Sfloppy.SYS

lkd> lm t n

start end module name

804d7000 806e2000 nt ntkrpamp.exe Wed Aug 04 12:58:37 2004 (41107B0D)

Unloaded modules:

a9098000 a90c2000 kmixer.sys

Timestamp: unavailable (00000000)

Checksum: 00000000

a9098000 a90c2000 kmixer.sys

Timestamp: unavailable (00000000)

Checksum: 00000000

.......

B-Các ví dụ:

Bài 1: Hàm Native API là gì? Trình tự các level của kernel.

Khái niệm:

Mọi ứng dụng Windows được phát triển dựa vào Win32 Application Programming Interface (API). Tuy nhiên, trên Windows NT/2000, lớp Win32 API layer chỉ là một giao diện mức (lớp) cao (high-level interface) cho một subsystem, mà nó được xây dựng trên một lớp API layer khác, được gọi là Native API.

Các file kernel là linh hồn của OS, theo trình tự từ lớp thấp đến lớp cao:

-HAL.dll là các thành phần core (nhân) truy xuất phần cứng.Hay là hardware layer mà kernel dùng đẻ truy xuất phần cứng.

-NTOSKRNL.exe là kernel của chính nó, là “bộ não” của OS hay còn gọi là bộ điều hành.

-NTDLL.dll thư viện kernel API, chứa các hàm Native API.(chỉ có ở win NT/2000)

-WIN32K.sys là thư viện graphics API. OS/2 và Unix ko hổ trợ file này. Là một subsystem trở thành một phần của kernel.

-CSRSS.exe Win32 subsystem Client.

-KERNEL.dll, USER32.dll, GDI32.dll là phần chính Win32 core subsystem APIs.

Ví dụ:

Khi bạn gọi hàm CreateProcess API (một hàm subsystem trong KERNEL32.dll),Windows tiến hành get các tham số và gọi một hàm có tên là NtCreateProcess. Hàm này là Native API NT để cài đặt một process và được chứa trong NTDLL.dll. Thực ra nó chưa cài đặt process. Mà NTDLL.dll chỉ làm những gì được gọi là “System Call” hay là một “Native System Service”. Kết quả cuối cùng trong một low-level code nằm trong NTOSKRNL.exe được gọi là ZwCreateProcess.

Bài 2: Tôi muốn tìm xem addr và assemble một hàm Native API có tên là ZwQuerySystemInformation.

Giải quyết vấn đề:

-Đầu tiên ta xem hàm ZwQuerySystemInformation ở addr nào, ta dùng lệnh

lkd> dd ZwQuerySystemInformation l 4

804ffe24 0000adb8 24548d00 086a9c04 04060ce8

Hàm này tại addr 804ffe24

-Assemble code hàm ZwQuerySystemInformation

lkd> u 804ffe24

nt!ZwQuerySystemInformation:

804ffe24 b8ad000000 mov eax,0ADh

804ffe29 8d542404 lea edx,[esp+4]

804ffe2d 9c pushfd

804ffe2e 6a08 push 8

804ffe30 e80c060400 call nt!KiSystemService (80540441)

804ffe35 c21000 ret 10h

Trong olly, ta tìm hàm này trong kernel32.dll như sau (xem như bạn biết làm trong Olly):

7C90E1AA > B8 AD000000 MOV EAX,0AD

7C90E1AF BA 0003FE7F MOV EDX,7FFE0300

7C90E1B4 FF12 CALL NEAR DWORD PTR DS:[EDX]

7C90E1B6 C2 1000 RETN 10

Trong lkd:

lkd> u 7C90E1AA

7c90e1aa b8ad000000 mov eax,0ADh

7c90e1af ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)

7c90e1b4 ff12 call dword ptr [edx]

7c90e1b6 c21000 ret 10h

7c90e1b9 90 nop

7c90e1ba 90 nop

7c90e1bb 90 nop

7c90e1bc 90 nop

Một chút lý thyết:

Xem code trên ta thấy 0ADh là ID của hàm Native API. Nó định vị thành phần trong bảng SERVICE_DESCRIPTOR_TABLE.

KeServiceDescriptorTable là một biến chứa addr của bảng SERVICE_DESCRIPTOR_TABLE.

Vậy hàm Native API có addr :

dword ptr [dword ptr [KeServiceDescriptorTable]+ID*4]

Bài 3: Nghiên cứu sự khác nhau giữa 2 lọai Native APIz là NtXxx và ZwXxx:

Các hàm Native APIz có 2 dạng tiếp đầu ngữ NtXxx và ZwXxx. Chúng được sử dụng trong 2 mode là user và kernel. Vậy có 4 cas xảy ra:

User Mode application calls NtReadFile

User Mode application calls ZwReadFile

Kernel Mode driver calls NtReadFile

Kernel Mode driver calls ZwReadFile

Ví dụ: NtReadFile và ZwReadFile

_1_Calling from user mode:

Trong biên dịch chương trình, như ta biết các ứng dụng user mode link với NTDLL.LIB.

Vì trong user mode ntddl.dll được load cùng file exe. Nên trong WinDbg ta vào menu File/Open Executalte mở một file exe nào đó để debug.

Sau đó ta dùng lệnh:

0:000> u ntdll!NtReadFile

ntdll!ZwReadFile:

7c90e27c b8b7000000 mov eax,0B7h

7c90e281 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)

7c90e286 ff12 call dword ptr [edx]

7c90e288 c22400 ret 24h

7c90e28b 90 nop

0:000> u ntdll!ZwReadFile

ntdll!ZwReadFile:

7c90e27c b8b7000000 mov eax,0B7h

7c90e281 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)

7c90e286 ff12 call dword ptr [edx]

7c90e288 c22400 ret 24h

7c90e28b 90 nop

_Nhận xét:

-Trong user mode, hai hàm này có code giống nhau và cùng một addr

-Bây giờ chúng ta xem SharedUserData!SystemCallStub (7ffe0300) là gì:

0:000> dd SharedUserData!SystemCallStub l 4

7ffe0300 7c90eb8b 7c90eb94 00000000 00000000

0:000> u 7c90eb8b

ntdll!KiFastSystemCall:

7c90eb8b 8bd4 mov edx,esp

7c90eb8d 0f34 sysenter

ntdll!KiFastSystemCallRet:

7c90eb8f 90 nop

7c90eb90 90 nop

7c90eb91 90 nop

7c90eb92 90 nop

7c90eb93 90 nop

ntdll!KiFastSystemCallRet:

7c90eb94 c3 ret

Vậy hàm này như sau: đầu tiên nó đặt 0B7h vào eax, sau đó nó lấy addr đỉnh stack (esp) đặt vào edx, sau đó gọi hàm sysenter. Hàm sysenter nhảy từ thread hiện hành vào kernel mode. Và thực thi routine trỏ đến bởi SYSENTER_EIP_MSR, nó là MSR 0x176

Trong WinDbg ta vào Local Kernel, dùng lệnh:

lkd> rdmsr 176

msr[176] = 00000000`80540510

lkd> ln 80540510

(80540510) nt!KiFastCallEntry | (8054061e) nt!KiServiceExit

Exact matches:

nt!KiFastCallEntry =

Tất cả code của KiFastCallEntry là xây dựng một trap frame (một trap frame giống như là nhật ký trạng thái tại 1 thời điểm của hệ thống) để khi chúng ta thóat kernel mode chúng ta sẽ quay lại code mà chúng ta đã rời đi trong user mode.

Thực chất KiFastCallEntry ko return mà nó nhảy ko đk đến KiSystemService. Code trong KiSystemService lấy số service và đặt vào trong eax trong dòng đầu tiên của call XxReadFile và tìm entry của nó trong bảng system service là KiServiceTable. Mỗi thành phần trong bảng này là con trỏ đến hàm Native APIz. Cũng được biết đến là "system service" routine. Trước khi gọi "system service" routine, system service dispatch code sẽ copy các tham số được truyền đến system service từ đỉnh của user stack vào trong kernel stack. Chúng ta đóan rằng do vậy tại sao con trỏ top của stack được save vào edx trước khi thực thi lệnh sysenter.

Kiểm tra lại lý thuyết trên:

lkd> dd nt!KiServiceTable+0xb7*4 l 4

80503a10 8057b21a 8057b784 805a4890 805b2c52

lkd> ln 8057b21a

(8057b21a) nt!NtReadFile | (8057b784) nt!NtReadFileScatter

Exact matches:

nt!NtReadFile =

Cuối cùng chúng ta show hàm nt!NtReadFile

lkd> u nt!NtReadFile

nt!NtReadFile:

8057b21a 6a58 push 58h

8057b21c 6898944d80 push offset nt!GUID_DOCK_INTERFACE+0x374 (804d9498)

8057b221 e84af9fbff call nt!_SEH_prolog (8053ab70)

8057b226 33f6 xor esi,esi

8057b228 8975e0 mov dword ptr [ebp-20h],esi

8057b22b 8975d0 mov dword ptr [ebp-30h],esi

8057b22e 8975a0 mov dword ptr [ebp-60h],esi

8057b231 8975a4 mov dword ptr [ebp-5Ch],esi

_2_Calling from kernel mode:

Tắt WinDbg sau đó bật lại. Ta vào menu File - Kernel Debug – chọn Tab Local

lkd> u nt!NtReadFile

nt!NtReadFile:

8057b21a 6a58 push 58h

8057b21c 6898944d80 push offset nt!GUID_DOCK_INTERFACE+0x374 (804d9498)

8057b221 e84af9fbff call nt!_SEH_prolog (8053ab70)

8057b226 33f6 xor esi,esi

Well, ở đây trông quen quen! Nó là hàm thực hiện hàm NtReadFile được called sau cùng trong User Mode (bởi vì nó ở đâu đó trong system service table trỏ đến). Bởi vậy, để ý rằng nếu chúng ta call NtReadFile từ một driver, thì chúng ta chỉ thực thi hàm này, bypassing (chỉnh hướng) bất cứ lọai system service dispatcher (bộ điều fối) thông thường nào of entry point.

Xem qua những gì mà tôi được thấy trước đó trong User Mode, ở đó hàm NtXxxx và ZwXxxx là đồng nhất, khi tôi disassemble nt!ZwReadFile có lẻ tôi mong đợi được thấy một cách chính xác những gì tôi đã xem được trong hàm nt!NtReadFile. Chúng ta hảy check:

lkd> u nt!ZwReadFile

nt!ZwReadFile:

804ffeec b8b7000000 mov eax,0B7h

804ffef1 8d542404 lea edx,[esp+4]

804ffef5 9c pushfd

804ffef6 6a08 push 8

804ffef8 e844050400 call nt!KiSystemService (80540441)

804ffefd c22400 ret 24h

Vậy là hàm ZwReadFile khác với hàm NtReadFile trong Kernel mode.

Chúng ta thấy ở trên trong hàm ZwReadFile, một chỉ thị quen thuộc lúc bắt đầu, move 0xb7 vào trong EAX. Rồi đặt một pointer [esp+4] đến các tham số mà chúng xuất hiện trên Kernel stack vào trong EDX, push các EFLAGS và một giá trị hằng số trên stack, và cuối cùng call KiSystemService!? Đó là hàm mà chúng ta bị kích họat gọi từ KiFastCallEntry khi chúng ta đã thực thi SYSENTER từ User Mode (xem lại phần calling from user mode).

Vì vậy tại sao chúng ta ko thực thi một SYSENTER ở đây? Duh! Bởi vì chúng ta thật sự đang ở trong Kernel Mode. Phần lớn những thứ quan trọng sẽ xuất hiện khi chúng ta đi qua route (tuyến lệnh) này là: chúng ta sẽ call native API từ Kernel Mode, thực thi trong Kernel Mode, và trong khi đi qua KiSystemService thì previous mode của chúng ta sẽ bị set đến Kernel Mode. Chú ý rằng điều này sẽ ko có nếu chúng ta call NtXxx từ Kernel Mode. Trong cas NtXxx, previous mode của chúng ta tồn tại ko thể chạm đến được là do chúng ta đi ngay đến hàm và bắt đầu thực thi nó.

Vậy, tóm lại luồng chỉ thị của một native API call từ Kernel Mode như sau:

Case A:

* Kernel Mode component calls NtXxx

* Đây là một lời gọi trực tiếp đến hàm mà chúng thi hành system service. Lời gọi hàm này ko thay đổi previous mode.

Case B:

* Kernel Mode component calls ZwXxxx

* Ở đây hướng đến một bước là đặt system service code (index value) vào trong EAX, và một pointer đến các đối số mà chúng thật sự đã được pushed vào trong (Kernel Mode) stack vào thanh ghi EDX.

* Rồi gọi hàm KiSystemService, which amongst doing other things, copies các tham số từ vị trí trỏ đến bởi EDX và lấy giá trị được chứa trong EAX trước đó và thực thi hàm định vị tại KiServiceTable[EAX].

* Hàm native API bây giờ thực thi (vẫn trong Kernel Mode) với previous mode set đến Kernel Mode. Điều này chỉ ra caller đến từ Kernel Mode.

Vậy, rõ ràng gọi NtXxx một cách trực tiếp ko có overhead (phần đầu), nhưng gọi ZwXxxx thay đổi previous mode. Vậy, điều đó nghĩa là sao? nghĩa là previous mode phải là thứ gì đó rất quan trọng.

_3_previous mode:

Khi một caller đến từ user mode, previous mode sẽ được set là user. Khi system service thực thi routine cần phải xác định mức độ tin cậy các tham số truyền cho hàm gọi, nó sẽ kiểm tra previous mode. Nếu previous mode là User thì bất kỳ tham số truyền nào trước khi sử dụng cũng phải thẩm tra. Còn nếu là Kernel thì nó sẽ tin tưởng tuyệt đối.

ZwXxx đựoc gọi thì previous mode được set là Kernel. Lúc đó các ham số truyền sẽ đuợc tin tưởng tuyệt đối ko cần thẩm tra.

NtXxx được gọi thì previous mode được set là User. Lúc đó các tham số truyền sẽ được thẩm tra trước khi sử dụng.

Tóm lại một ví dụ cần nhớ:

Cpro hỏi: hàm ZwTerminateProcess và hàm NtTerminateProcess khác nhau cái gì

sao 2 hàm này cách sử dụng sao giống nhau quá vậy,chúng lại nằm trong cùng 1 File dll,cho mình hỏi là chúng khác nhau cái gì

Snowflake trả lời:

Ở user mode thì 2 hàm này cùng một địa chỉ, còn ở kernel mode thì đó là 2 hàm khác nhau. Trong kenel mode khi gọi hàm ZwTerminateProcess thì hàm ZwTerminateProcess thông qua KiSystemService để gọi tới hàm NtTerminateProcess.