buffer_overflow 第3章 筆記3 抓取kernel32.dll基址

本節:shellcode 自行加載需要的dll



在前面的例子中 列出兩種編譯器所加載的dll,可以發現kernel32.dll 跟ntdll.dll都有被載入。

策略:

kernel32.dll是系統重要得dll,假設他一定會被載入,kernel32.dll中有一個LoadLibraryA的函數,可以加載其他的dll,我們要利用LoadLibraryA來載入msvcrt.dll,找到msvcrt.dll之後,在找到msvcrt.dll裡面的函數 printf() 和 exit()完成打印Hello, world! 並 使用exit()離開程式。

1.先找到kernel32.dll的基址
2.找到kernel32.dll裡面的函數LoadLibraryA
3.利用函數LoadLibraryA載入msvcrt.dll
4.再從msvcrt.dll找到函數 printf() 和 exit()

我們需要shellcode幹上面這些事



小知識:



1.
程式在視窗作業系統下執行時,該程式底下的執行緒會被作業系統已一個資料結構TEB來表示。

2.
程式在視窗作業系統下執行時,作業系統也會保留一個特殊資料結構來代表這一程序,此資料結構稱為PEB。



3.
FS是一個區段暫存器,程式在執行時,FS會指向TEB這個結構。
但由於區段暫存器執行速度比較慢,所以 TEB結構 偏移0x00 剛好是NT_TIB結構,NT_TIB結構下偏移0x18 有個成員self 。


4.
我們可以用WinDbg來觀察windows的內部結構,不過在輸入指令時如果出現:
*************************************************************************
***                                                                   
***                                                                   
***    Your debugger is not using the correct symbols                 
***                                                                   
***    In order for this command to work properly, your symbol path   
***    must point to .pdb files that have full type information.     
***                                                                   
***    Certain .pdb files (such as the public OS symbols) do not      
***    contain the required information.  Contact the group that      
***    provided you with these symbols if you need this command to    
***    work.                                                          
***                                                                   
***    Type referenced: ntdll!_TEB                                    
***                                                                   
*************************************************************************
Symbol ntdll!_TEB not found.。

可以先在創建一個c:\symbols的資料夾
在輸入下面2行

.symfix+ c:\symbols
.reload







FS是一個區段暫存器,程式在執行時,FS會指向TEB這個結構。


先觀察TEB






NT_TIB結構下偏移0x18 有個成員self 。


self是一個指標,指向NT_TIB結構,用NT_TIB結構偏移0x18 找到self的位置,而self又指向NT_TIB,所以self內容為NT_TIB的位置,而NT_TIB結構剛好就是TEB結構偏移0x00的地方,所以FS:[0x18]= &(TEB)。

 成員self跟FS暫存器一樣也指向TEB結構









由於FS區段暫存器速度比較慢,所以通常用FS:[0x18] 找到self成員,再用暫存器EAX存取self的值,達到EAX指向TEB結構,如此可以節省時間。

!!!但也可以用FS,以下我們的 shellcode 會使用FS指向TEB結構





介紹TEB結構接下來要介紹的PEB結構:
因為我們要透過PEB結構先找到kenrel32.dll的基址

再回到TEB結構看一下: 0x30的成員ProcessEnvironmentBlock

是一個指標,指向PEB結構,我們可以透過FS:[0x30]來找到PEB結構










PEB結構如下:


結構成員太多,後面省略

關鍵在偏移0x0c的成員Ldr 他是一個指標,指向_PEB_LDR_DATA結構。





_PEB_LDR_DATA結構中, 關鍵是偏移0x01c的成員InInitializationOrderModuleList,他的類型是_LIST_ENTRY結構,這個成員包含了應用程式再啟動時 DLL初始化的順序資訊,一些固定的DLL通常會被優先初始化,例如kernel32.dll   ntdll.dll。












觀察WinDbg的輸出 跟windows SDK7.1 表頭檔winternl.h對_PEB_LDR_DATA結構的定義對比。


winternl.h對_PEB_LDR_DATA結構的定義:


typedef struct _PEB_LDR_DATA {
    BYTE Reserved1[8];                                                     //此行占8bytes  偏移0x00 -  0x07
    PVOID Reserved2[3];                                                   //此行占12bytes 偏移0x08-  0x13
    LIST_ENTRY InMemoryOrderModuleList;                // 此行偏移從0x14開始

} PEB_LDR_DATA, *PPEB_LDR_DATA;



發現到偏移量0x14 成員InMemoryOrderModuleList都一樣,但InInitializationOrderModuleList 和 EntryInProgress不見了,因為微軟很壞不想讓我們知道裡面的東西,正所謂,你不讓我看,裡面一定有鬼。






既然關鍵是成員InInitializationOrderModuleList,類型是_LIST_ENTRY結構,我們先觀察一下_LIST_ENTRY長啥鬼樣。






看起來有前也有後,而且前後也都指向_LIST_ENTRY結構,所以可能是雙向鏈結串列結構。



我們需要一個exe當作範例載入觀察這個結構。用前面的Hello.exe載入


看到Ldr的位址在00341ea0




接著觀看成員InInitializationOrderModuleList 偏移0x01c:

他是_LIST_ENTRY結構,看起來真的像雙鏈接串列。



------------------------------------------------------------------------------------
                  記憶體位置     指向下一元素             指向上一元素
                                           flink                         blink
                 
(元素1)          00341ebc         00341f58                003420e0      00000000 abababab
(元素2)          00341f58          00342020               00341ebc      7c920000 7c932c28
(元素3)          00342020         003420e0                00341f58      7c800000 7c80b63e
(元素4)          003420e0         00341ebc                00342020      77be0000 77bef2a1





可以看出一共有4個部分,第一部分是InInitializationOrderModuleList 本身,看起來是串列的頭
,內部不帶資料,後面那三個所帶的資料,可以發現7c920000 7c800000 77be0000,看起來很眼熟,使用WinDbg看一下Hello.exe使用了那些 dll 。




因此我們確定這些就是dll的基底,也知道InInitializationOrderModuleList 鏈接串列,每個元素偏移0x08的地方,都包含了一個DLL的基底。





但問題來了我們剛是靠WinDbg來知到DLL的名稱,例如我找到7c900000我也只知道他是一個dll基址,但不知道他是誰。

我們摸到了InInitializationOrderModuleList這個結構,但windows沒有公開這個結構,所以我們只能慢慢推測這裏面長啥樣。


回到_PEB_LDR_DATA結構中,一個成員InMemoryOrderModuleList長的跟InInitializationOrderModuleList很像,也都是_LIST_ENTRY結構。






很幸運的InMemoryOrderModuleList結構有被公開過可以在winternl.h找到:



typedef struct _LDR_DATA_TABLE_ENTRY {

    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID Reserved3[2];
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    } DUMMYUNIONNAME;
    ULONG TimeDateStamp;

} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;



其中的UNICODE_STRING結構也有被定義:

typedef struct _UNICODE_STRING {

    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;

} UNICODE_STRING;




偏移0x00 是成員Length ,0x02是成員MaximumLength,0x04是成員Buffer。
第一個成員代表名稱長度,第二個最大長度,第三個存放字串的記憶體位置。




.

既然知道InMemoryOrderModuleList有UNICODE_STRING結構,我們就來猜看看InInitializationOrderModuleList 有沒有,利用UNICODE_STRING第一個成員名稱長度來找。

------------------------------------------------------------------------------------
                  記憶體位置     指向下一元素             指向上一元素
                                           flink                         blink
                 
(元素1)          00341ebc         00341f58                003420e0      00000000 abababab
(元素2)          00341f58          00342020               00341ebc      7c920000 7c932c28
(元素3)          00342020         003420e0                00341f58      7c800000 7c80b63e
(元素4)          003420e0         00341ebc                00342020      77be0000 77bef2a1




既然要猜看看InInitializationOrderModuleList裏面有沒有UNICODE_STRING結構,那我們先試想一下 第2個元素有7c920000這個DLL的位址資料 那應該也會存放有一個DLL 名稱資料,就像InMemoryOrderModuleList裡的結構一樣。



UNICODE_STRING 成員 Length 代表 DLL名稱長度, 我們先前知道7c900000是ntdll.dll的基底
位置,所以我們來猜Length 裡面存放的長度是多少:

情況1:
只有檔名  "ntdll.dll" 所以 9個字元 但不知道會不會連字串結尾NULL字元也算進去,所以有可能是10個字元。

情況2:
檔名加路徑 "C:\Windows\system32\ntdll.dll" 所以29個字元 但不知道會不會連字串結尾NULL字元也算進去,所以有可能是30個字元。

注意:
因為是UNICODE字串所以每個字元占2bytes,所以有這4種組合  考慮到Length長度是unsigned short  (兩位元組)

                       9*2  = 18     =    0x00     0x12
                     10*2  = 20     =    0x00     0x14

                     29*2  = 58     =    0x00     0x3A

                     30*2  = 60     =    0x00     0x3C

考慮到windows 是litttle endian  (反向儲存) 所以有這4種組合
                                          
                                                0x12     0x00  

                                                0x14     0x00 

                                                0x3A    0x00 

                                                0x3C    0x00
                                             
                                             



從第二個元素的起始位置列出 40個位元組 ,看看在記憶體裏面,有沒有上面這4種組合
------------------------------------------------------------------------------------
                  記憶體位置     指向下一元素             指向上一元素
                                           flink                         blink
               
(元素1)          00341ebc         00341f58                003420e0      00000000 abababab
(元素2)          00341f58          00342020               00341ebc      7c920000 7c932c28
(元素3)          00342020         003420e0                00341f58      7c800000 7c80b63e
(元素4)          003420e0         00341ebc                00342020      77be0000 77bef2a1

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


WinDbg列出 00341f58 往後40個位元組記憶體儲存的數值:



在第二行最後4個位元組,發現有我們要的組合12 00 14 00,非常有可能是UNICODE_STRING結構裏面會存放的數據



typedef struct _UNICODE_STRING {

    USHORT Length;                             //存放名子長度
    USHORT MaximumLength;            //存放名子最大長度 (也就是最後加上NULL)
    PWSTR  Buffer;

} UNICODE_STRING;





可以看到00341f58+1C的位置開始數值12 00 14 00 把這個位置套到 UNCODE_STRING的結構看看。

推論正確:可以看得出來這個位置是一個UNCODE_STRING,即使沒公開,也被我們摸中了!!!

終於找到了!!!

                      ã€Œfound you meme」的圖片搜尋結果

接著把其他元素也找出來!!!

------------------------------------------------------------------------------------
                  記憶體位置     指向下一元素             指向上一元素
                                           flink                         blink
               
(元素1)          00341ebc         00341f58                003420e0      00000000 abababab
(元素2)          00341f58          00342020               00341ebc      7c920000 7c932c28
(元素3)          00342020         003420e0                00341f58      7c800000 7c80b63e
(元素4)          003420e0         00341ebc                00342020      77be0000 77bef2a1

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

剛剛找到元素2的UNICODE_STRING結構在(00341f58+1c)的位址,那摸元素3,4應該也一樣,


                                         ã€Œfeel good meme」的圖片搜尋結果

全部都中!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!



既然我們知道有記憶體中有 kernel32.基址,連dll名稱也有。
那我們可以用組合語言來寫個程序,讓他自動抓取InitializtionOrderModuleList位置,然後自動比對裡面的各個元素的資料。


typedef struct _UNICODE_STRING {

    USHORT Length;                             //存放名子長度
    USHORT MaximumLength;            //存放名子最大長度 (也就是最後加上NULL)
    PWSTR  Buffer;                               //指標,指向某個DLL名稱字串

} UNICODE_STRING;



比對可以有2種方法:

1.利用成員 Buffer是一個指標,指向函數名稱字串的起始位置。

例如:

我們要找的是kernel32.dll ,我們可以找到某個元素內的Buffer變數,找到字串開頭位置,在一個個字元判斷是否是名子為kernel32.dll。




2.利用成員 Buffer是一個指標,指向函數名稱字串的起始位,但我們只比對字串尾是否為NULL字元。

例如:

利用"kernel32.dll"為12字元,在UNICODE下 12*2=24字元,字串尾會加上NULL字元0,所以找到名稱字串指標之後,接著判斷第25個字元是否為0,一樣也可以找到。

敢用這種方法找是因為重要的系統DLL不多,很難找到名稱長度會剛好一樣的DLL

題外話:

--------------------------------------------------------------------------------------------------------------
或許可以利用成員 MaximumLength是一個數值,存放著名稱的最大長度。


例如:


利用"kernel32.dll"為12字元,在UNICODE下 12*2=24字元,字串尾會加上NULL字元0,所以找到名稱字串指標之後,接著是否為25,不知道行不行的通。




或許也可以利用成員 Length 是一個數值,存放著名稱的長度,比對長度,

不過可能名稱一樣長的DLL很多,所以行不通,還未測試過。

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


用組合語言來寫:以上的邏輯:

xor eax,eax                           //eax=0  大概是不想用EAX暫存器來指向TEB 所以把EAX變0

mov ebx,[fs:eax+0x30]        //ebx = &(_PEB)

mov ebx,[ebx,0x0c]             //ebx = PEB->Ldr

add ebx,0x1c                       //ebx = Ldr.InitializtionOrderModuleList


LOOP:
mov ebx,[ebx]                     //ebx = ebx-> Flink 跳過首先的鏈結串列 到下一個元素

mov  ecx,[ebx+0x08]          //ecx= 某個DLL 的基底

mov  edx,[ebx+0x20]         //edx = 該DLL名稱的指標

cmp  [edx+0x18],al            //比較字串第25個位元是否為0(NULL字元)

JNE LOOP                        // 不為0就跳LOOP




執行完上面的組語後,代表kernel32.dll的基址已經被我們定位到了!!!!



我們找到kernel32.dll的基底之後,下一步,我們需要在kernel32.dll裏面找到LoadLibraryA函數的位址,利用LoadLibraryA把其他的dll也給載進來。。。


下一篇,在kernel32.dll抓取LoadLibrary!!!!!!



                                          ã€Œnot yet meme」的圖片搜尋結果











留言

這個網誌中的熱門文章

buffer_overflow 第1章 筆記 介紹

Buffer_overflow 第2章 筆記1 函數堆疊的故事

buffer_overflow 第3章 筆記6 拼出我們第二個shellcode。