前言
針對一個已經學習了Linux 開發(fā),并開始在上嘗試的研究人員來說,這一過程可能要比想象的更加艱難。內核與Linux完全不同。盡管如此,但Linux內核要比更容易理解,原因在于其開源的特性,并且與相比,Linux只具有相當少的功能。另一方面,在過去幾年中進行了重大的改進,由于這一改進,新版本與老版本相比已經發(fā)生了許多變化。在本文中,我們將專注于對 10 x86進行分析,但其他舊版本可能與之相比沒有太多的不同。目前,已經有很多關于PEB LDR的博客文章,但我們還沒有看到有任何文章中展現(xiàn)了完整的邏輯,并闡述其本質原因的。大多數研究人員只是通過進行分析,并要求讀者具備一定程度的后端基礎。我撰寫這篇文章的主要原因,是希望從C語言轉到ASM,并希望我們能共同了解在ASM x86中進行開發(fā)時,后端的工作原理。
.data段
在我們開始處理部分之前,我建議首先應該理解內存是如何工作的,因為我們即將做的一切操作都是在內存中。如果我們已經了解像、LPSTR這樣的數據類型,那么無疑是一個好消息,因為我們必須要知道:
標準的C語言并不等同于 C編程
接下來,唯一需要重點掌握的,就是基本的 x86。默認情況下,除了系統(tǒng)調用或API調用之外,ASM在Linux或中是相同的。因此,了解寄存器的工作原理就顯得非常重要。
最重要的是,我們應該了解如何對二進制文件進行反匯編。我主要使用和 x86。我會同時使用這兩個工具進行調試,因為有一些我們不能在中完成的事情,在 x86中是可以的,反之亦然。因此已經加載dll找不到輸入點,我們將不斷切換使用這兩個工具。
.text段
在我們開始使用之前,理解其在較低級別的工作方式,這一點非常重要。我們首先將從一個非常簡單的例子開始,找到系統(tǒng)的當前主機名。我們來看看下面是使用C語言編寫的 API示例:
在上圖中,我創(chuàng)建了兩個變量,分別是和。這些將是提供給函數的參數。請記住,和有兩個相似的函數。W代表寬字符,而A代表ANSI CHAR字符串。我們將在整個博客系列中使用ANSI。下面是MSDN對函數的說明:
BOOL (LPSTR , nSize);
上面的代碼表示,接受LPSTR,表示長指針字符串,而則表示長指針雙字。一個字的大小是16位,因此DWORD在所有平臺上都是32位。現(xiàn)在,如果使用g++編譯上述程序,我們將看到如下內容:
現(xiàn)在在這里,在程序的最開始,有# ,這也就意味著庫將被引入到代碼中,它應該在這里動態(tài)鏈接默認依賴項。但是,我們不能對ASM進行相同的操作。在ASM的場景中,我們需要動態(tài)地找到函數所在的地址,在堆棧上加載參數,并調用具有函數指針的寄存器。我們要知道的一件重要事情是,的大多數功能,都是通過三個主要DLL訪問的:NTDLL.DLL、.DLL和.DLL。因此,無論任何時間執(zhí)行任何二進制文件,這些都是始終要加載的必要DLL。為了加載函數,我們必須找到這個函數所在的DLL,并在那里找到它的基址。接下來,我們在上嘗試加載任何x86二進制文件,看看能得到什么。我將加載我們編譯的上述exe文件,但實際上,我們可以加載任何隨機的32位可執(zhí)行文件,因為我們只會瀏覽上面提到的那些DLL。使用打開exe文件,并導航到Log部分,可以看到加載了這三個DLL,以及其特定的地址:
接下來,我們將導航到突出顯示的部分,可以看到加載的不同DLL的名稱。在這里,我們可以瀏覽DLL,并查看它們提供的所有功能。
現(xiàn)在,如果我們在搜索框中搜索函數,它將顯示.DLL加載該函數。此外,還將打印出函數所在的地址。在理論和實際測試中,這一點都能夠很好地展現(xiàn)。接下來,讓我們通過C編程然后通過ASM來完成,我們要執(zhí)行的步驟如下:
1. 使用函數 在內存中加載.dll;
2. 使用在.dll中找到函數的地址;
3. 將返回值類型轉換為接受2個參數的函數(因為接受2個參數);
4. 為及其創(chuàng)建緩沖區(qū)。
將作為函數指針來執(zhí)行。
訪問的MSDN頁面,可以發(fā)現(xiàn)它返回一個,這意味著它將一個句柄返回到一個被加載的模塊。因此,我們創(chuàng)建了一個變量。類似地,返回從DLL加載的函數的地址。我們需要將返回的地址類型轉換為函數,以使其能夠正常工作。為此,我們創(chuàng)建了一個,它基本上復制了函數的結構。在上圖中,我們加載庫.dll,并使用查找函數的基址,將地址存儲在中。最后,我們創(chuàng)建兩個變量和,并使用(*)作為函數指針,執(zhí)行存儲在中的地址,并為其提供所需的變量。上面的代碼中,還打印了函數的地址。我們嘗試對其進行編譯,看看結果如何:
不錯!地址與上面用調試時發(fā)現(xiàn)的地址一致。
我們接下來進入到有趣的部分。所有DLL及其函數的地址在重新啟動時都會發(fā)生變化,并且在每個其他系統(tǒng)中都會有所不同。這就是我們無法對ASM代碼中的任何地址進行硬編碼的原因。但是,主要問題仍然存在,那就是我們如何找到.dll自身的地址?
我在一開始說過,每個exe都加載了.dll、NTDLL.DLL和.dll。事實上,這些DLL是操作系統(tǒng)中非常重要的一部分,每次在執(zhí)行任何操作時,都會加載這些DLL。因此,這些DLL到內存中的加載順序總是相同的。然而,這可能因操作系統(tǒng)而異。這就意味著,在 XP與 10之間可能有所不同,但所有 10中的加載順序將保持不變。
所以,我們在繼續(xù)下一步之前,需要完成下面的工作:
1. 找到.dll的加載順序;
2. 找到.dll的地址;
3. 找到的地址;
4. 在棧上加載的參數;
5. 調用函數指針。
可能聽起來很容易?我們來實際嘗試一下。
查找.dll的地址并不簡單。當我們執(zhí)行任何exe時,在操作系統(tǒng)中首先創(chuàng)建的就是TEB(線程環(huán)境塊)和PEB(進程環(huán)境塊)。
我們的主要關注點在于PEB結構(稱為LDR),因為這是與進程相關的所有信息都被加載的地方。從流程參數到流程ID的所有內容都存儲在這個位置。在PEB中,有一個名為的結構,它包含三個關鍵部分。這些被稱為鏈接列表( Lists)。
1. t - 加載模塊(exe或dll)的順序;
2. ist - 模塊(exe或dll)存儲在內存中的順序;
3. - 在進程環(huán)境塊中初始化模塊(exe或dll)的順序。
在鏈表中加載模塊的順序是固定的。這意味著,我們如果能夠在上面的列表中找到.dll的順序,就可以搜索.dll的地址已經加載dll找不到輸入點,并繼續(xù)進行。現(xiàn)在,我們啟動 x86。如果各位還沒有安裝及其依賴項,你可以在SLAER上找到一篇關于的文章。一旦安裝之后,就可以像我們之前那樣打開任意的exe文件。
在中加載exe文件后,會顯示一些輸出。限制,我們將忽略輸出內容,并在下面的命令提示符中輸入.cls以清除屏幕并重新開始。現(xiàn)在,我們在命令提示符下輸入!peb,看看在這里能夠得到什么:
如大家所見,我們得到了LDR(PEB結構)的地址,即。這非常重要,因為我們要使用該地址來計算前進的地址。接下來,我們輸入命令dt nt!_TEB,以查找PEB結構的偏移量。
如我們所見,_PEB位于偏移量0x030的位置。以類似的方式,我們可以使用dt nt!_PEB查看_PEB結構的內容。
的偏移量為0x00c。接下來,我們嘗試查找結構中的內容。我們可以用類似的方式實現(xiàn)這一點:
dt nt!
在這里,我們可以看到t位于偏移量0x00c處,ist位于偏移量0x014處,位于偏移量0x01c處。此外,如果要查看每個列表所在的地址,可以使用我們此前找到的地址(LDR的地址)以及命令dt nt! 。這將向我們顯示鏈接列表的相應起始地址和結束地址,如下所示:
有一個地方,可能會被一些人誤解,就是上圖中展示出ist的類型為,但在MSDN上已經另有說明:
因此,MSDN聲明它是類型而不是類型。我們嘗試查看結構中加載的模塊,并指定該結構的起始地址為,以便可以看到加載的模塊的基址。需要注意的是,是此結構的地址,因此第一個條目將比此地址少8個字節(jié)。因此,我們的命令是:
dt nt!Y -8
第一個出現(xiàn)的是.exe。這就是我之前執(zhí)行的exe文件。此外,我們可以看到現(xiàn)在的地址是。偏移量0x018處的中包含的基址。現(xiàn)在,我們下一個加載的模塊必須距離有8個字節(jié),也就是-8。
dt nt!Y -8
所以,我們的第二個模塊是ntdll.dll,它的地址是,下一個模塊位于之后的8個字節(jié)。所以,我們的下一個命令是:
dt nt!Y -8
由此,就得到了第三個模塊.dll,其地址是,其偏移量時0x018。模塊加載的順序總是固定的,至少這適用于 10、 7、 8(包括8.1)。因此,當我們編寫ASM時,我們可以遍歷整個PEB LDR結構體,并找到.dll的地址,并將其加載到我們的中。以類似的方式,我們還可以找到.dll的地址,這是第四個模塊。
現(xiàn)在,我們總結一下需要進行的工作:
1. PEB位于距離文件段寄存器偏移量為0x030的位置;
2. LDR位于偏移量為PEB + 0x00C的位置;
3. ist位于偏移量LDR + 0x014的位置;
4. 第一個模塊入口是exe本身;
5. 第二個模塊入口是ntdll.dll;
6. 第三個模塊入口是.dll;
7. 第四個模塊入口是.dll。
我們現(xiàn)在最感興趣的,就是.dll。每次加載DLL時,地址都將存儲在的偏移量0x018的位置。我們鏈接列表的起始地址將存儲在的偏移量中,即0x008。因此,偏移量之間的關系將是 – = 0x018 – 0x008 = 0x10。因此,.dll的偏移量將是LDR + 0x10。更詳細的理解,可以在下圖中看到,這張圖是我從這里偷過來的。
現(xiàn)在,如果我們在ASM中做同樣的工作,將會是如下所示:
我們使用NASM來編譯,并在中加載它。大家可以從這里下載NASM。
實際上,一旦我們的最后一條指令被運行,就應該在EAX寄存器中加載.dll的地址。我們來看看它在中看起來是否相同。
如我們所見,在最后一條指令之后,加載到EAX中的地址與我們在下面使用lm命令在中看到的地址相同,都是,這是.dll的地址。
現(xiàn)在,我們已經有了.dll的地址,下一步就是使用找到的地址,并調用該函數。我們將在下一篇文章中重點討論這一問題,完善ASM代碼,實現(xiàn)獲取計算機名稱并將其打印在屏幕上,然后打印到部分。