進程和線程
看了張銀奎老師的書,記錄一下。有很多比較官方的概念我直接抄了下來,所以不要噴我抄書,愛所有反卷局的大寶貝們!
進程和線程
處于運行狀態的程序又稱為進程,每個進程運行在自己的空間中,空間相對獨立,受操作系統保護,在每個進程空間中,一般都會有一個或者多個線程在運行。
進程資源
在操作系統的規則中,資源一般都是針對于進程來分配的,必須要先有一個進程,才能對其分配資源。
在Windows操作系統中,每個進程都擁有如下的資源:
1、一個虛擬的地址空間,又被稱為進程空間
2、全局唯一的進程ID,簡稱PID ( Client ID )
3、一個可執行映像,也是該進程的可執行文件在內存中的表示
4、一個或者多個線程
5、一個位于內核空間中的名為EPROCESS的數據結構,用來記錄該進程的關鍵信息,包括進程的創建時間,映像文件名稱等等
6、一個位于內核空間中的對象句柄表,用來記錄和索引該進程所創建/打開的內核對象,操作系統根據該表格將用戶模式下的句柄翻譯為指向內核對象的指針
7、一個用于描述內存目錄表起始位置的基地址,簡稱頁目錄基地址 ( DirBase ),當CPU切換到該任務/進程的時候,會將該地址加載到頁表基地址寄存器CR3,這樣當前進程的虛擬地址才會被翻譯為正確的物理地址
8、一個位于用戶空間中的進程環境塊PEB
9、一個訪問令牌,用于表示該進程的用戶,安全組以及優先級

!process 0 0 執行該命令可以列出系統內所有的進程,第一個參數用來指定要顯示的進程ID ( 因為叫Client ID 所以這里就是Cid ),0代表了所有進程,第二個參數用來指定要顯示的進程屬性,0代表只顯示最基本的進程屬性。

可以在命令后加上程序文件的名字來過濾,這樣命令執行后,只顯示wermgr進程的屬性。
- 第一行代表了EPROCESS結構的地址
- 下面的三行代表了進程的關鍵的屬性
進程的SessionId代表了該進程所在的Windows會話 ( session ) 的ID號, 當有多個用戶同時登陸的時候,Windows會給每個登錄用戶建立一個會話,每個會話都有自己的WorkStation和Desktop,這樣大家就可以在不同的會話中共用一個Windows系統,對于XP來說,只有一個用戶登錄的時候,用戶啟動的程序和系統服務都運行在session 0,當切換到另一個用戶賬號的時候,系統會建立Session 1,以此類推。

測試了一下,確實奧!如果我們net usr add后進行Switch User的時候我們的SessionId會進行++, 從Windows Vista開始,只允許系統服務運行在Session 0,系統啟動后便會自動創建,當用戶登錄的時候會創建另一個會話 一般叫做 Session 1,所以用戶登錄到系統的時候一般會看至少兩個Csrss在運行,系統啟動早期創建的幾個特殊進程不屬于任何會話,所以他們的SessionId為空,比如system Registr 等等。
Cid為進程ID,進程ID是表示進程的一個整數,很多用戶態的函數用它作為標識進程的參數,在內核空間的代碼中,主要使用EPROCESS指針來標識進程。
Parent Cid是父進程的進程ID,既創建該進程的那個進程的進程ID。
DirBase 代表了該進程頂級頁表的位置,也就是CPU切換到該進程的時候,CR3寄存器的內容,也就是當時看火哥的那個講的說頁表目錄CR3寄存器內容指向的就是第一個表的位置,該寄存器是將虛擬地址轉換成物理地址必須要的參數數值,頂級的頁表叫頁目錄表,所以這個字段的名字叫做頁目錄基地址,DirBase字段的位定義與當前使用的分頁模式有關,在32位的分頁模式的時候,DirBase低12位總是0,高20位是該進程的頁目錄的頁幀編號 ( PFN ),比如DirBase是上面的0xA2C83000,那么PFN為0xA2C83,那么就會有一個小疑問PFN是什么?這里我們就先把他當作一個index,是一個數組,等到時候變強了我再來看!
在IA32 分頁模式下,CR3低12位會因為CR4的PCIDE位(17位)而不同,PCIDE( Process-Context Identifiers Enable )。

當PCIDE開啟為1的時候,CPU會緩存多個進程的頁表信息,低12位變成進程上下文的ID號,為了防止CPU熔斷和幽靈的漏洞。NT內核中引入了KVA影子的安全補丁,這個補丁會使用CPU的PCID功能,但是并不知道CPU Meltdown和Spectry是啥!得去查查。
CPU Meltdown和Spectry漏洞:
回來啦!這兩個漏洞比較類似,大概的意思就是:我問二木哥:我昨天啊~看見了你和一個女生在一起逛街,你們是不是?雖然我昨天壓根兒什么都沒看到,但是我說完,他要是愣了一會(愣神比較久),回了一句:我憑什么要告訴你?雖然LSP沒有承認,但是顯然我已經知道了答案。那么這次的漏洞也是同理,利用了CPU的兩個特殊的機制:其中Meltdown利用了CPU的亂序執行,Spectre利用了CPU的預測執行。
什么是CPU亂序執行和預測執行呢?
預測執行:在整個OS系統中,假設某個惡意軟件去問操作系統:剛才那小伙!登錄的密碼第一個數字是1嗎?操作系統回復:不關你的事,你沒有權限知道。雖然這個過程本身是沒有問題的,但是問題出在:操作系統答復了,但是他心里想了一遍這個答案,尤其是當對方問到一個正確的數字時,他回答的稍微猶豫了幾毫秒,這個事情被惡意軟件注意到了,就可以通過反復的提問,最后猜到了密碼是什么。
亂序執行:假設二木哥 有a b c三個老婆,其中a老婆在娘家沒有探訪的權限,但是b和c在自己家里隨時可以訪問,此時執行下面這個條件表達式!x= a?b:c 由于a老婆不能去,如果二木哥去了!就會被揍!但實際上,二木哥會在檢查a是否可訪問的同時,預先就往下執行了,等到結果回來,已經根據a的結果完成了b或者c訪問,反正不能虧!只是還沒有賦給x而已,那么經過加載的b或者c會放緩存里。雖然報錯了,但如果再次訪問就會比較快,于是再次訪問b和c,根據返回的時間快慢,就可以猜到a的內容!假設我想知道a有沒有買化妝品,如果執行b成功了,就可以知道a確實買了化妝品。
知道了上面的瞎扯淡原理后,我們來好好看看是為啥!
Meltdown
; rcx = kernel address; rbx = probe_arraymov al, byte [rcx]shl rax, 0xcmov rbx, qword [rbx + rax]
rcx存放了用戶層不可以訪問的內核地址,rbx存放了探測的數組。
當我們要訪問rcx的時候,就會觸發異常,該指令及之后的指令對寄存器的修改都會被丟棄,處理器重新回到能正常執行的指令中,但由于處理器采用亂序執行方式,在等待處理器完成該指令執行的同時(權限檢查結束之前),后面兩條指令已經被執行了(盡管最終會被丟棄)。
將指令3讀取到的數據乘以4096(4KB)也就是一頁,因為對可訪問的內存進行了訪問后會將內存頁放入到緩存中去,根據這個速度就可以進行判定,因為緩存的速度會很快。

那么作為攻擊者去對緩存進行測信道攻擊就會知道哪個內存頁訪問過了,從而推斷出被訪問的內核內存數據。
NT內核中也引入了叫做KVA的補丁,會開啟PCID的功能,查看一下KVA的模式為1,cr4的寄存器位17為1。

ObjectTable的含義為該進程的內核對象和句柄表格,Windows使用該表格將句柄翻譯成內核對象的指針。

HandleCount表示ObjectTable所含的表項的數量。
進程空間
介紹了32位的進程空間,64位的,就還是內核空間是共享的,但是要為系統中的所有進程服務,所以不允許被某個進程任意訪問和破壞,所以內核空間的特權級別高于用戶空間,又介紹了64位的,又說了怎么看EPROCESS結構 dt _EPROCESS xxxxxxxx 用 !process EPROCESS的結構地址 來顯示進程的關鍵信息,還介紹了PEB是進程環境塊,里面有進程大多數用戶模式的信息,.process xxxxxxx 設置當前進程后 dt _PEB xxxxxxx。
訪問模式
重點來了!
Windows定義了兩種訪問模式 ( access mode )
- 用戶模式(user mode, 用戶態)
- 內核模式( Kernel mode, 內核態)
應用程序的代碼運行在用戶模式之下,操作系統的代碼運行在內核模式之下,對于x86處理器來說并沒有任何寄存器來表明我們當前處于何種模式,優先級只是代碼或者數據所在的內存段或者頁的一個屬性。
雖然不可以直接訪問R0層的,但是用戶程序可以通過調用系統服務來間接的訪問內核空中的數據或間接調用執行內核空間代碼,當我們調用系統服務的時候,會從用戶模式切換到內核模式,調用結束的時候返回用戶模式,這就是模式切換。
那么在線程KTHREAD結構中,定義了UserTime和KernelTime的兩個字段,用來記錄線程在用戶模式和內核模式運行的時間(時鐘中斷次數為單位),模式切換通過軟中斷或者專門的fast system call指令實現。
使用INT 2E切換到內核模式:

我們可以很簡明的看到我們該調用首先呢轉到Kernel32的ReadFile函數,ReadFile函數對參數進行簡單的檢查后調用Ntdll中的NtReadFile函數。
kernel32:

Ntdll:

我是個好奇的人,所以直接去看了win2k3的源碼把這個KiSystemService給逆了。
_KiSystemService proc ENTER_SYSCALL kss_a, kss_t ; set up trap frame and save state ?FpoValue = 0 ;; (eax) = Service number; (edx) = Callers stack pointer; (esi) = Current thread address;; All other registers have been saved and are free.;; Check if the service number within valid range; _KiSystemServiceRepeat: mov edi, eax ; copy system service number ;edi = eax 等于了系統調用號 shr edi, SERVICE_TABLE_SHIFT ; isolate service table number and edi, SERVICE_TABLE_MASK ; ; 這里用來確定是去找系統服務表還是驅動的表 mov ecx, edi ; save service table number add edi, [esi]+ThServiceTable ; compute service descriptor address ; 指向系統服務表 mov ebx, eax ; save system service number ; ebx位系統調用號 and eax, SERVICE_NUMBER_MASK ; isolate service table offset ; eax為系統服務下標;; If the specified system service number is not within range, then attempt; to convert the thread to a GUI thread and retry the service dispatch.; cmp eax, [edi]+SdLimit ; check if valid service jae Kss_ErrorHandler ; if ae, try to convert to GUI thread ; 檢查系統調用的下標是否超過了表的大小 ;; If the service is a GUI service and the GDI user batch queue is not empty,; then call the appropriate service to flush the user batch.; cmp ecx, SERVICE_TABLE_TEST ; test if GUI service ; 跳轉GUI API服務 jne short Kss40 ; if ne, not GUI service mov ecx, PCR[PcTeb] ; get current thread TEB address ; ecx指向KPCR xor ebx, ebx ; get number of batched GDI calls KiSystemServiceAccessTeb: or ebx, [ecx]+TbGdiBatchCount ; may cause an inpage exception jz short Kss40 ; if z, no batched calls push edx ; save address of user arguments push eax ; save service number call [_KeGdiFlushUserBatch] ; flush GDI user batch pop eax ; restore service number pop edx ; restore address of user arguments ;; The arguments are passed on the stack. Therefore they always need to get; copied since additional space has been allocated on the stack for the; machine state frame. Note that we don't check for the zero argument case -; copy is always done regardless of the number of arguments because the; zero argument case is very rare.; Kss40: inc dword ptr PCR[PcPrcbData+PbSystemCalls] ; system calls ; 直接跳轉到這里進行++系統調用數 FPOFRAME ?FpoValue, 0 mov esi, edx ; (esi)->User arguments ; 3環的指針 mov ebx, [edi]+SdNumber ; get argument table address ; 系統服務表 xor ecx, ecx mov cl, byte ptr [ebx+eax] ; (ecx) = argument size mov edi, [edi]+SdBase ; get service table address mov ebx, [edi+eax*4] ; (ebx)-> service routine ; 指向的函數 sub esp, ecx ; allocate space for arguments shr ecx, 2 ; (ecx) = number of argument DWORDs mov edi, esp ; (es:edi)->location to receive 1st arg cmp esi, _MmUserProbeAddress ; check if user address jae kss80 ; if ae, then not user address KiSystemServiceCopyArguments: rep movsd ; copy the arguments to top of stack. ; Since we usually copy more than 3 ; arguments. rep movsd is faster than ; mov instructions. ; 復制參數 ;; Make actual call to system service;kssdoit: CAPSTARTX <_KiSystemService,ebx> call ebx ; call system service ; call 系統服務 CAPENDX <_KiSystemService> kss60:
兩張表一張是找內核函數,另一個是驅動函數,那么我們看的eax 里存一個值,系統調用號或者服務號,這個東西的低12位就是函數參數表和函數地址表的下標,而第13位如果是0,表示找系統服務表,如果是1,那么找驅動函數的的表。

總結來說就是KiSystemService會根據服務ID從SSDT中找調用的服務地址和參數描述,然后將參數從用戶態復制到線程的內核態,然后調用真正的函數執行操作,操作結束后返回給KiSystemService,該函數會把結果返回到用戶態,在進行系統調用的時候,CPU需要從內存中加載門描述符和段描述符才可以得到KiSystemServer的地址,然后進行權限檢查,檢查源代碼位置和目標代碼位置的代碼段的權限。
快速系統調用
系統調用是非常繁瑣的事情,那么肯定想要減少這些開銷的啦!
- 系統調用服務例程的地址放到寄存器中來避免讀IDT這樣的內存操作(??因為IDT 是INT xxx IDT表的東西),因為讀寄存器的速度比讀內存的速度要快很多
- 避免權限檢查,也就是使用特殊的指令讓CPU省去對系統服務調用來說根本不需要的權限檢查
奔騰 II 處理器引入的SYSENTER/SYSEXIT 指令正是按照這一思路設計的,AMD K7 引入的 SYSCALL/SYSRETURN指令 都是因為這個速度的目的來設計的,對比INT 2E來說,使用這些指令可以加快系統調用的速度,因此利用這些指令來進行系統調用的方式叫做快速系統調用。
因為Windows 2000 或者之前的Windows的操作系統不支持快速系統調用,他們只能使用INT 2E的方式來進行系統調用,這時候我們需要去小路哥哥那里白嫖一個windows XP回來!

這讓人興奮的界面,直接來了把掃雷。

穩了!繼續學習!
在Windows XP和Windows Server 2003或更高的版本在啟動的過程中會通過CPUID 指令檢測CPU是否支持快速系統調用指令(EDX寄存器的SEP標志位),EDX的11位代表了SEP標志位,來代表是否支持快速系統調用,如果系統不支持就還會使用INT 2E的方式。
進行快速系統調用需要做的準備:
在GDT表中建立4個段描述符,用來描述供SYSENTER指令進入內核模式時使用的代碼段(CS)和棧段(SS),以及SYSEXIT指令從內核模式返回用戶模式時使用的代碼段和棧段,在GDT中他們的排列方式應該嚴格按照上述的方式進行排列。
設置專門用于系統調用的MSR寄存器,SYSENTER_EIP_MSR用于指定新的程序指針,是SYSENTER指令要跳轉的目標例程地址。Windows系統會將其設置為KiFastCallEntry的地址,因為該例程是Windows內核中專門用于受理快速系統調用的,SYSENTER_CS_MSR用來指定新的代碼段,也就是KiFastCallEntry所在的代碼段,SYSENTER_ESP_MSR用于指定真的棧指針(ESP)。
| MSR Name | MSR Add | Useful |
| ---------------- | ------- | -------------------- |
| SYSENTER_CS_MSR | 174 | 目標代碼段的CS選擇子 |
| SYSENTER_ESP_MSR | 175 | 目標ESP |
| SYSENTER_EIP_MSR | 176 | 目標EIP |
該地址用于Kernel debug時,通過rdmsr/wrmsr指令來讀/寫這3個寄存器。

174的位置我們應該不陌生了,因為0環的時候切換寄存器看到的cs經常是08,那么我們看一下EIP的位置:

可以看到是nt!KiFastCallEntry的地址。
會將一小段名為SystemCallStub的代碼復制到SharedUserData的內存區,內存區會被映射到每個Win32的進程空間中,這樣每次進行系統調用的時候,NTDll.DLL中的stub函數就會調用這段SystemCallstub代碼,該代碼根據系統硬件的不同而不同,對于IA-32就會使用SYSENTER,對于AMD就會使用SYSCALL指令。
打開windbg開始,看一下ntdll中的ReadFile函數!這里碰到了點坑,就是符號老加載不上,如果想去看ntdll的東西,需要切換一下進程。

可以很清楚的看到 SharedUserData!SystemCallstub 的地址給了edx寄存器,Call的是edx中的地址。

將當前的esp的寄存器放入到edx寄存器當中 ( 個人認為這里應該是要去獲取r3層的參數所以到時候可以通過edx寄存器來取參數,因為esp需要修改 ),因為sysenter要調用KiFastCallEntry,所以去看看!
8053e540 b923000000 mov ecx,23h8053e545 6a30 push 30h8053e547 0fa1 pop fs8053e549 8ed9 mov ds,cx8053e54b 8ec1 mov es,cx
在 KPCR(Processor Cotnrol Region)區域的 +0x40 位置是 TSS 指針(指向一個 KTSS 結構),KPCR 結構的地址在0xffdff000:

0x80042000 _KTSS為TSS的指針,所以KTSS的結構為:

0級的Esp值,這指向一個 KTRAP_FRAME 結構 V86Es 成員。
8053e54d 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h]8053e553 8b6104 mov esp,dword ptr [ecx+4]8053e556 6a23 push 23h
這個!ffdff040就是KPCR TSS指針所指向的KTSS的指針,所以他將這個值給了ecx,再通過ecx+4獲得ESP0的地址,因為當前的esp所在的是KTRAP_FRAME的V86Es,所以push 23修改了V86Es上層成員HardwareSegSs。
KTRAP_FRAME 結構
在 KiFastCallEntry() 中將 context 信息保存在一個被稱為 KTRAP_FRAME 的結構里,因為我們已經獲取到了ESP0的地址,KTRAP_FRAME的基址為 Esp0 - 0x7c。

在 KiFastCallEntry() 中將 context 信息保存在一個被稱為 KTRAP_FRAME 的結構里,在前面我們看到 KTRAP_FRAME 結構的地址被賦予 esp 寄存器,因此:KTRAP_FRAME 結構就是 KiFastCallEntry() 函數的 stack 區域。

開始逆!
kd> u nt!Kifastcallentry L20nt!KiFastCallEntry:8053e540 b923000000 mov ecx,23h 8053e545 6a30 push 30h8053e547 0fa1 pop fs ;fs = 0x308053e549 8ed9 mov ds,cx ;ds = 0x238053e54b 8ec1 mov es,cx ;es = 0x23 都是段選擇子有關的修改相應的權限的8053e54d 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h] ;ecx = _KTSS ptr8053e553 8b6104 mov esp,dword ptr [ecx+4] ;esp = ESP0 -> KTRAP_FRAME的0x7c位置的V86Es8053e556 6a23 push 23h ;_KTRAP_FRAME.HardwareSegSs = 0x238053e558 52 push edx ;_KTRAP_FRAME.HardwareEsp = edx (edx為三環的esp)8053e559 9c pushfd ;_KTRAP_FRAME.EFlags = EFlags8053e55a 6a02 push 2 8053e55c 83c208 add edx,8 ;因為edx為三環的esp,所以這個步驟是要去取三環的參數8053e55f 9d popfd ;0環 EFlags = 0x28053e560 804c240102 or byte ptr [esp+1],2 ;EFlags的 IF位置18053e565 6a1b push 1Bh ;_KTRAP_FRAME.SegCs = 0x1B8053e567 ff350403dfff push dword ptr ds:[0FFDF0304h] ;_KTRAP_FRAME.Eip為 kd> u 7c92e4f4 ntdll!KiFastSystemCallRet8053e56d 6a00 push 0 ;_KTRAP_FRAME.ErrCode = 08053e56f 55 push ebp ;_KTRAP_FRAME.EBP = EBP8053e570 53 push ebx ;_KTRAP_FRAME.EBX = EBX8053e571 56 push esi ;_KTRAP_FRAME.ESI = ESI8053e572 57 push edi ;_KTRAP_FRAME.EDI = EDI8053e573 8b1d1cf0dfff mov ebx,dword ptr ds:[0FFDFF01Ch] ;ebx = _kpcr 0ffdff0008053e579 6a3b push 3Bh ;_KTRAP_FRAME.SegFs = 0x3B8053e57b 8bb324010000 mov esi,dword ptr [ebx+124h] ;esi = kpcr_kprcb_CurrentThread 8053e581 ff33 push dword ptr [ebx] ;_KTRAP_FRAME.ExceptionList = ExceptionList8053e583 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh ;ExceptionList = -18053e589 8b6e18 mov ebp,dword ptr [esi+18h] ;ebp = kpcr_kprcb_CurrentThread_InitialStack8053e58c 6a01 push 1 ;_KTRAP_FRAME.PreviousPreviousMode = 1 代表三環過來的8053e58e 83ec48 sub esp,48h ;esp = ntdll!_KTRAP_FRAME_ptr8053e591 81ed9c020000 sub ebp,29Ch ;8053e597 c6864001000001 mov byte ptr [esi+140h],1 ;kpcr_kprcb_CurrentThread_PreviousMode = 1 表示從3環調用來8053e59e 3bec cmp ebp,esp ;比較當前的esp和ebp的位置8053e5a0 759a jne nt!KiFastCallEntry2+0x47 (8053e53c) ;如果不相同則異常,正常都在_KTRAP_FRAME_ptr8053e5a2 83652c00 and dword ptr [ebp+2Ch],0 ;_KTRAP_FRAME.Dr7 = 08053e5a6 f6462cff test byte ptr [esi+2Ch],0FFh ;DebugActive8053e5aa 89ae34010000 mov dword ptr [esi+134h],ebp ;TrapFrame = ebp(_KTRAP_FRAME_ptr)8053e5b0 0f854afeffff jne nt!Dr_FastCallDrSave (8053e400) ;檢測是否被調試的狀態8053e5b6 8b5d60 mov ebx,dword ptr [ebp+60h] ;ebx = _KTRAP_FRAME.Ebp8053e5b9 8b7d68 mov edi,dword ptr [ebp+68h] ;edi = _KTRAP_FRAME.Eip8053e5bc 89550c mov dword ptr [ebp+0Ch],edx ;_KTRAP_FRAME.DbgArgPointer = edx 保存3環的參數指針8053e5bf c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h ;_KTRAP_FRAME.DbgArgMark = 0BADB0D00h8053e5c6 895d00 mov dword ptr [ebp],ebx ;_KTRAP_FRAME.DbgEbp = _KTRAP_FRAME.Ebp8053e5c9 897d04 mov dword ptr [ebp+4],edi ;_KTRAP_FRAME.DbgEip = _KTRAP_FRAME.Eip8053e5cc fb sti

結構THRED的E0偏移為ServiceTable
KeServiceDescriptorTableShadow

8053e5cd 8bf8 mov edi,eax ;系統服務例程號8053e5cf c1ef08 shr edi,8 ;右移八位8053e5d2 83e730 and edi,30h ; index8053e5d5 8bcf mov ecx,edi ; ecx = index8053e5d7 03bee0000000 add edi,dword ptr [esi+0E0h] ;esi+0E0h = ServiceTable_ptr 代表是去找index=1還是=0的結構的項8053e5dd 8bd8 mov ebx,eax ;系統服務例程號8053e5df 25ff0f0000 and eax,0FFFh ;取后24位8053e5e4 3b4708 cmp eax,dword ptr [edi+8] ;edi + 8的位置為MaxServiceNumber:最大的系統服務例程號8053e5e7 0f8345fdffff jae nt!KiBBTUnexpectedRange (8053e332)8053e5ed 83f910 cmp ecx,10h ;比較index是否為18053e5f0 751a jne nt!KiFastCallEntry+0xcc (8053e60c)8053e5f2 8b0d18f0dfff mov ecx,dword ptr ds:[0FFDFF018h]8053e5f8 33db xor ebx,ebx ;ebx = 08053e5fa 0b99700f0000 or ebx,dword ptr [ecx+0F70h] ;調試的時候發現這里都是08053e600 740a je nt!KiFastCallEntry+0xcc (8053e60c)8053e602 52 push edx8053e603 50 push eax8053e604 ff15e43f5580 call dword ptr [nt!KeGdiFlushUserBatch (80553fe4)]8053e60a 58 pop eax8053e60b 5a pop edx8053e60c ff0538f6dfff inc dword ptr ds:[0FFDFF638h] ;系統調用的次數+18053e612 8bf2 mov esi,edx ;三環的edx給了當前的esi8053e614 8b5f0c mov ebx,dword ptr [edi+0Ch] ;ebx指向了參數表的8053e617 33c9 xor ecx,ecx ;ecx = 08053e619 8a0c18 mov cl,byte ptr [eax+ebx] ;讀取參數的個數ArgumentSizeTable:提供每個例程所需要的參數大小,這個值將要用來從 caller 里復制多少個參數8053e61c 8b3f mov edi,dword ptr [edi] ;獲取ServiceRoutineTable(提供真正的系統服務例程的地址)的地址8053e61e 8b1c87 mov ebx,dword ptr [edi+eax*4] ;讀取服務例程地址8053e621 2be1 sub esp,ecx ;在當前棧上開辟空間容納參數8053e623 c1e902 shr ecx,2 ;ecx/4 獲取參數個數8053e626 8bfc mov edi,esp ;edi指向棧8053e628 3b35d4995580 cmp esi,dword ptr [nt!MmUserProbeAddress (805599d4)] ;看是否屬于用戶空間8053e62e 0f83a8010000 jae nt!KiSystemCallExit2+0x9f (8053e7dc)8053e634 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] ;復制參數到當前棧上8053e636 ffd3 call ebx ;調用最終的服務例程
表示在windbg里面跟了一邊 清晰多了!但是windbg太不好看了!決定換回windbg preview?
畫一手流程圖:


ecx用來指定SYSEXIT返回用戶模式時的目標地址,當使用INT 2E進行系統調用的時候,由于INT N的指令會i東將中斷發生時的CS和EIP寄存器壓入棧中,當中斷處理例程通過執行iretd返回時,iretd指令會讓棧中保存的cs和EIP返回合適的位置,但是因為SYSENTER指令不會向棧中壓入要返回的位置,所以sysexit指令需要通過其他機制知道需要返回的位置。

書上給了一個例子,來加深這個系統調用的一個理解,因為棧上記錄了函數相互調用時的參數和返回地址等信息,棧回溯是從棧上去找到這些信息,然后顯示出來的過程。

可以看到第一列是序號,第二列是每個函數的棧幀基地址,第三列是返回地址,第四列是執行的位置。
逆向調用
之前我們知道了用戶模式進入內核模式的兩種方式,通過這兩種形式,用戶模式的代碼可以調用位于內核模式的系統服務,如果我們想讓內核模式的代碼主動調用用戶模式代碼的時候,那么這種調用叫做逆向調用。
逆向調用的過程:
內核代碼使用KiCallUserMode發起調用,接下來的執行的過程和系統返回的KiSystemCallExit類似,只是進入用戶模式的時候執行的是NTDLL.dll中的KiUserCallbackDispatcher,而后該函數會調用內核希望調用的用戶態函數,用戶模式的工作完成后,執行返回動作的函數會執行INT 2B的指令,從而觸發0x2B的異常。

可以知道對應的異常的函數是nt!KiCallbackReturn。
實例分析
如果要切換.process 一定要加上/i /p 這樣cr3才會換回來,上下文才會過來。


可以看到整個過程有之前說到的nt!KiFastCallEntry,通過服務號去找對應的函數,執行完后將消息組織好去調用KeUserModeCallback函數,從而進行逆向調用,用戶模式狀態下的過程沒有體現出來,但是可以看到XyCallbackReturn函數用于返回內核模式,調用int 2B nt!KiCallbackReturn 返回到內核模式。

線程
線程是進程中的生命,一個進程內有一個或者多個線程,進程創建初期或者進程退出和銷毀的過程中,進程內可能沒有任何線程。
ETHREAD
NT內核使用ETHREAD結構來描述線程,在內核代碼中,大多時候使用ETHREAD結構的地址來索引線程,執行.thread命令,就可以顯示出當前線程的ETHREAD結構地址。

查看一下KTHREAD的結構:

包含了線程的各種的屬性,ETHREAD開頭的512字節是一個KTHREAD結構,也稱作為線程控制塊( TCB ),里面主要是供內核調度線程時使用的。

Header字段為Dispatcher_Header類型,是NT內核的線程調度器的別名,代表了分發CPU時間片的意思。
因為字段太多了,所以調試的時候謎一般不直接觀察,使用擴展命令!thread,從而用這個擴展命令來更好的顯示線程屬性。

右上角的RUNNING on processor 0代表了這個線程正運行在0號CPU上,來源于KTHREAD結構的state字段。

為枚舉類型KTHREAD_STATE,共有9個值從而代表了線程的狀態。

可以看到當前的線程,在Running。

NT內核給每個CPU定義了一個名為處理控制塊的PRCB的龐大結構,這個結構中有一個DispatcherReadyListHead的數組,包括了32個元素,代表了線程的32個優先級,每個元素是一個LIST_ENTRY的結構,從而起到鏈表頭的作用,用來掛接對應優先級的就緒線程。

各個線程狀態之間的切換關系:

上面所說的那個鏈表的事情找到了答案,我用的是xp的系統32位的,那時候的這個結構在TEB中的WaitListEntry中。

如果線程處于等待狀態下,那么!thread命令會顯示出等待的原因,在KTHREAD結構中有一個叫做WaitReason的字段。

從而記錄線程等待的原因,長度為1字節,是枚舉的類型,名為KWAIT_REASON,在公開的PDB符號文件中,包含了這個枚舉類型的定義。
TEB
NT內核定義了線程環境快TEB來描述線程的用戶空間的信息,包括了用戶態棧,異常處理,錯誤碼,線程局部存儲等等。
windbg調試應用程序的時候,可以使用 !teb 來顯示當前線程的teb 結構的位置,我用的是本機win10 掛載的 notepad。

NT內核會使用CPU的硬件機制來快速定位當前線程的TEB,所以內核在創建線程的時候,會分配專門的內存也來用作TEB,將地址記錄在KTHREAD中,所以TEB的地址總是按照頁對齊的。
同時也可以用dt _TEB的命令查看TEB的信息。

WOW進程
現在大多數的windows系統是64位的,運行在支持64位CPU的地方,但是為了兼容32位的應用程序,64位windows系統可以運行32位的應用程序,這樣運行在64位內核上的32位進程有一個專門的名字,叫做WoW64(Windows 32 on Windows 64)進程。
架構:

由于32位的代碼是不能與64位內核交互的,所以中間轉換層為了解決這個問題設計,轉接層本身為64位的模塊,他給32位的應用程序營造了一個32位的環境,主要負責指針長度的轉換和解決API兼容等問題。
工作過程
可以使用.effmach命令再兩種代碼間進行切換。
.effmach amd64切換到64位的模式下,

可以看到上面的wow64和wow64win都是轉接層的核心的模塊。

切換到32位下,可以看到再WoW的進程中,有兩個ntdll的模塊,一個是64位的,一個是32位的,WINDBG會在后加載進程的32位版本的模塊名加上基地址。
在WoW的進程中,有很多的東西是雙份的,每個進程有兩個PEB,每個線程有兩個TEB,有兩個棧。
WINDBG的wow64exts擴展模塊專門是為調試WoW進程從而設計的,info命令可以顯示WoW進程的雙份的資產。

Guest代表了32位的代碼 Native指向的為64位的代碼。
執行過程

可以看到需要call Wow64SystemServiceCall。

jmp Wow64Transition (KiFastSystemCall)

段選擇子33 指向的是64位的段,這個就是從32位兼容模式到64位模式的方法。
我去查看了0x33段選擇子 base是0 所以直接跳轉到了0x773A7009的位置,我這里做了一個測試拿win7做的。
現在去逆以下wow64cpu!CpupReturnFromSimulatedCode 這里我個人推薦!直接去調notepad可以一點點看這個過程。
之前的cs的狀態是23的狀態,因為經過跳轉的過程變成33。

可以看到Long進行了改變,interl手冊的段描述符中的高21位置是L位代表了是32位還是64位。

可以看到gdtr表中他們的不同。

可以看到當前的esp裝的返回地址,那么+4放下推的話就是call之前的參數
00000000`754f271e 67448b0424 mov r8d,dword ptr [esp] ;取出32位的返回地址放到r8d因為到時候需要返回的時候需要

00000000`754f2723 458985bc000000 mov dword ptr [r13+0BCh],r8d ;可以看到r13是64位環境的棧底,保存了32位的返回地址00000000`754f272a 4189a5c8000000 mov dword ptr [r13+0C8h],esp ;保存了32位的時候的棧幀

但是這里有個疑問!0x1480是個什么,直接去看一下TEB的結構!

發現叫做TlsSlots,但是TlsSlots是什么東東!待我去學習一下!
http://www.nynaeve.net/?p=181
是個線程槽了,這里存儲了64位線程的一些關鍵環境以及32位線程的關鍵數據。
00000000`754f2731 498ba42480140000 mov rsp,qword ptr [r12+1480h] ;r12為teb的地址,這里取出了64位的rsp00000000`754f2739 4983a4248014000000 and qword ptr [r12+1480h],0 ;清空緩存

edx存放的是參數的地址:
00000000`754f2742 448bda mov r11d,edx ;r11d存放的是參數的地址
可以看到r15是一個表rcx相當于跳轉的服務號。

00000000`754f2745 41ff24cf jmp qword ptr [r15+rcx*8] ;跳轉
跳轉后的環境:

syscall會跳到:

可以看出來WoW進程中的32版本的NTDLL.DLL 在執行系統調用的時候,會調用特殊的WoW64SystemServiceCall函數,切換到64位的WoW轉換層。
注冊表重定向
64位的windows操作系統會對WoW進程的注冊表訪問實施重定向,如果程序中訪問的路徑為HKEY_LOCAL_MACHINE\software,那么就會被重定向到HKEY_LOCAL_MACHINE\software\Wow6432Node,所以i使用注冊表編輯器的時候,如果要查看WoW進程的設置,那么也要去查看Wow6432Node表鍵下的,有一部分的表鍵是供兩類程序共享的,有一類則是重定向的。
test代碼:
#include #include int main(){ //找到系統的啟動項 char* Register = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; char* Myapp = "C:\\Users\\Administrator\\Desktop\\ctfmon.exe"; HKEY hKey; //打開注冊表啟動項 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, Register, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) { RegSetValueExA(hKey, "Mytest", 0, REG_SZ, (BYTE*)Myapp, strlen(Myapp)); //關閉注冊表 RegCloseKey(hKey); printf("succeed!"); } else { printf("Failed!"); return -1; } return 0;}

注冊表反射
考慮到COM組件,有32位的也有64位的,為了讓用戶再一個版本中所做的設置在另一個版本中也有效,所以windows實現了一種叫做注冊表反射的機制,對于某些COM組件有關的表鍵,來自一邊的修改會自動更新到另一邊。
創建進程
我們打開一個新的程序,windows會使用一套標準的流程來創建一個新進程:
1、在父進程的用戶空間中打開要執行的映像文件,確定其名稱,類型和系統對它的設置選項
2、進入父進程的內核空間,為新進程創建EPROCESS結構,進程地址空間,KPROCESS結構和PEB
3、創建初始線程,但是創建的時候指定了掛起標志,并不會立刻開始運行
4、通知子系統服務程序,對于windows程序,通知windows子系統服務進程CSRSS
5、初始線程開始在內核空間執行
6、通過APC的方式,在新進程自己的空間中執行初始化動作,主要是通過NTDLL.DLL中的加載器,加載進程所依賴的DLL文件
Windows Internals上面詳細的介紹了!但是我去翻了沒太看懂,有點蒙。 到時候寫Windows Internals筆記的時候 會好好研究!寫出來的東西讓更多人明白一下!
最小進程和Pico進程
對于正常的NT進程,NT內核會自動創建一些設施并且將他們映射到進程的用戶空間當中,如PEB,TEB等等,因為考慮到NTDLL.DLL的特殊,NT內核也會自動將NTDLL.DLL映射到普通進程用戶模式空間中,對于某些特殊的情況,這些的動作可能是多余而帶有副作用的,所以設計了最小進程和Pico進程。
這里,最小進程和Pico進程推薦自己看書奧!這里沒有好的dump文件比較難搞,日后有時間補上。