手動打造一個彈窗程序

在平時的分析當中,常常會碰到PE結構的文件,雖然 010 Editor 等工具會提供一個模板,把各個部分都詳細的標記出來,可是在調試的時候,常常會須要在 VS 等程序框中進行調試,因此,就須要對PE結構有必定的瞭解,纔可以快速定位到本身想要的地方。算法

爲了更好的瞭解PE結構中的每一位的做用,最好的辦法就是本身手寫一個PE文件,這樣對每個部分的理解,都會清晰不少。 shell

目錄數組

0x00 準備工做 編輯器

0x01 構造DOS頭函數

0x02 構造File頭工具

0x03 構造Optional頭編碼

0x04 構造節表spa

0x05 構造導入表操作系統

0x06 執行代碼命令行

0x00 準備工做

在開始以前,有一些細節是須要提早思考好的,這些細節對於整個PE結構來講是很是重要的。

由於只須要完成一個彈窗的效果,代碼量是很是少的,因此在程序的設計上,一個節表就徹底足夠了,同時,咱們但願保證文件儘量小,因此將文件對齊設置爲200,將內存對齊設置爲1000。

再加上頭部和對齊的考慮,文件就須要佔用400個字節了,到內存展開之後就佔用2000字節。

注:PE格式中,全部能夠被覆蓋掉,而不影響程序運行的位置,我都會用CC來填充,這些位置能夠寫入字節的shellcode等。

0x01 構造DOS頭

DOS頭部在編輯器中佔用了4行,其中的多數數據都是在16位的DOS環境下運行時所必備的,在如今看來,已是能夠佔用的內容,只有兩個參數是必須的:e_magic和e_lfanew。

e_magic

這個位是識別性的頭部(MZ),這個位置是會被做爲一個合法PE文件的檢測位。

e_lfanew

用來指向一個新的結構,這個也就是咱們如今來講,最重要的結構,全部的參數信息都是在這個結構中定義的。

在DOS頭部後面還有一個Stub數據區,是16位程序的殘留數據,是能夠去掉的,因此就直接將e_lfanew指向了0x40,在這個位置開始新的結構。

0x02 構造File頭

PE標識、File頭以及Optional頭統稱爲NT頭,這裏就不提NT的概念了,PE標識有4字節。

File頭有1.4行,有4個重要的參數。

Machine

這是一組宏,表示在什麼硬件下運行,必定要根據實際的狀況來進行更改,當前是Intel386,因此填0x014c。

NumberOfSections

描述節表的個數,在前面規劃的時候提到了,使用一個節表就足夠了。

SizeOfOptionalHeader

描述Optional頭的長度,在本身寫程序進行分析的時候,必定要注意這一點,原版的OD直接使用sizeof來得到了,忽視了這個值是變長的,咱們能夠本身來更改,達到反調試的目的,這裏填寫0xE0。

Characteristics

描述可執行文件的屬性,具體參照下面這張圖。

最終填寫以下

0x03 構造Optional頭

Optional頭涉及到的參數就比較多了,也是最重要的一個部分

Magic

類型識別,能夠來判斷究竟是32位仍是64位,再或者是其餘的,咱們使用32位的,爲0x10b

AddressOfEntryPoint

程序入口點,由於頭部對齊後爲200字節,因此程序就從文件的200位置開始寫,對應到內存中就是1000,因此填寫0x1000

ImageBase

建議裝載地址,這個地址並非必定能佔住的,若是沒有到這個位置的話,會根據重定位表來修正程序中的地址,由於要寫一個exe文件,通常默認是0x400000

SectionAlignment

內存對齊,填寫0x1000

FileAlignment

文件對齊,填寫0x200

MajorSubsystemVersion

這個版本號是不能進行修改的,目前系統通常都是NT4的,填寫0x4

SizeOfImage

內存中的文件大小,在前面規劃的時候也提到過了,這裏填0x2000

SizeOfHeaders

頭部的大小,這裏都是要考慮對齊後大小的,因此填0x200

CheckSum

校驗和,在3環程序中,是不會檢測這個位置的,在0環中才會進行校驗,可是計算這個值的算法是公開的,因此能夠本身計算並填寫,檢測的意義不大,咱們把這個位填0。

Subsystem

這個位置是程序運行在什麼狀況下,填3,是命令行下,2是圖形化界面下,1是內核文件中,根據實際狀況填寫。

DllCharacteristics

是不是基於WDM的驅動程序,填0就能夠了,若是是的話,填0x2000

SizeOfStackReserve

準備保留多大的棧空間,本身填寫,合理便可

SizeOfStackCommit

程序運行的時候,佔用多大的棧空間,本身填寫,合理便可

SizeOfHeapReserve

準備保留多大的堆空間,本身填寫,合理便可

SizeOfHeapCommit

程序運行的時候,佔用多大的堆空間,本身填寫,合理便可

NumberOfRvaAndSizes

數據目錄的長度,默認是16個,填寫0x10

緊接着後面就是數據目錄的描述了,一個描述佔用8個字節,4個字節的RVA,4個字節的長度,只有導出表和重定位表的長度是會被使用的,其餘的數據目錄的長度都是能夠覆蓋掉的,他們經過一個全零結構來判斷結尾。

最終填寫以下

0x04 構造節表

在數據目錄的描述結束之後就是節表描述了,一個節表的描述是兩行半

Name

節表名字的長度是固定的,並且是能夠隨便寫的,並非說.data就必定是數據段,必定不能經過名字來判斷其中的內容。

VirtualSize

這是一個共用體,通常咱們使用的都是VirtualSize位,節表在內存中的長度,有效字節的長度,這個是對齊前的長度,這裏能夠填0。

VirtualAddress

節表的開始位置在內存中的RVA,按照前面的設想,這裏應該填0x1000

SizeOfRawData

節表在文件中的長度,這個是對齊後的大小,按照前面的設想,這裏應該填0x200

PointerToRawData

節表的起始位置,按照前面的設想,這裏應該填0x200

Characteristics

節屬性,描述這個節是可讀的,可寫的仍是可執行的。

對於上面的那四個參數,能夠描述爲,從文件中起始地址爲PointerToRawData的地方,複製SizeOfRawData的數據,粘貼到內存中RVA爲VirtualAddress的位置,實際字節爲VirtualSize的地方。

最終填寫以下,還須要對齊

0x05 構造導入表

在完成了這些內容之後,就須要開始構造導入表了,由於咱們須要調用MessageBoxA函數來實現彈窗的功能。

導入表也是整個PE結構中最複雜的地方,佔用1.4行,在程序執行前和執行後,導入表的結構是不同的。

OriginalFirstThunk

導入名稱表,這裏是一個RVA,它指向了一個結構,說它是一個數組更爲合適,裏面存儲的也是一個RVA,指向了_IMAGE_THUNK_DATA結構,這個結構也是一個共用體,能夠填一個序號,也能夠填一個函數名稱,由於導出表有按名字導出和按序號導出兩種形式。

這裏咱們使用按名字導出的方式,這樣就又涉及到了一個結構_IMAGE_IMPORT_BY_NAME

在這個結構中,Hint屬於廢棄的狀態,因此只須要寫上函數的名字就能夠了,這個名字是一個字節的,由於不知道函數名字的長短,也就無法使用定長的方式,爲了不空間的浪費,因此它只記錄了名字的起始位置,經過00來判斷結尾

Name

動態連接庫的名稱,也是一個RAV,它指向了名字

FirstThunk

導入地址表,這裏也是一個RVA,同樣指向了一個數組,與導入名稱表是對應的,在運行前,與導入名稱表同樣,都指向了_IMAGE_THUNK_DATA結構,結構圖下

當程序執行之後,導入地址表就會按照名稱進行搜索,獲得函數的地址,而後把地址填入到對應的位置中,結構圖就變成了下面這個樣子

在執行的時候,也就是間接調用的函數地址表

咱們先把導入表的結構寫出來,地址的位置先用0來補充,這裏將導入表也到250的位置

文件偏移是250,對應的內存偏移是1050,因此在數據目錄的第二項,也就是導入表的位置,寫上導入表的RVA,長度是能夠隨便寫的

由於導入表是依靠一個全零結構來判斷結尾的,咱們須要給它留下足夠的空間,咱們將 dll 名稱寫到 2C0 的位置,最後的 .dll 是能夠不用寫的,操做系統不依靠後綴名來判斷

文件偏移2C0對應的內存偏移是10C0,寫到導入表中對應的位置

而後佈置函數名稱MessageBoxA,開頭的兩個字節是能夠隨意填寫的,咱們將它放到 2E0 的位置,最後以00來結尾

文件偏移2E0對應的內存偏移是10E0,這個先記住,等一下再進行填寫

而後是導入名稱表和導入地址表,在執行前,這兩個的內容是同樣的,都指向了函數名稱,也就是上面的10E0,由於這裏咱們只用一個函數,因此導入名稱表和導入地址表都只有一項,前面也說過了,它們至關因而一個數組,是須要一個00來結尾的,因此每個都須要佔用8個字節,恰好是一行,因此,咱們把導入名稱表放到2D0的位置,將導入地址表放到2D8的位置。

文件偏移2D0對應的內存偏移是10D0,文件偏移2D8對應的內存偏移是10D8,而後將它們填到導入表中對應的位置。

到這裏爲止,導入表的編寫也就完成了。

0x06 執行代碼

最後就是代碼的編寫了,咱們先設置一下彈窗的標題和內容,咱們將標題放到2F0的位置,對應的內存偏移是10F0,由於ImageBase是400000,因此咱們須要push的地址是4010F0,而後把彈窗的內容放到2F4的位置,對應的內存偏移是10F4,須要push的地址是4010F4

前面已經提到過了,在代碼執行的時候,咱們須要調用的函數地址在導入地址表中,因此須要調用的地址是4010D8

這樣,所須要調用的內容也就都有了,接下來就是硬編碼的事情了,若是對硬編碼不熟悉的話,咱們能夠經過在OD中寫彙編,而後把硬編碼扣下來

而後把這段代碼寫到200的位置

這樣就大功告成了,而後保存運行,看看效果

成功彈窗,也就完成了手寫PE結構的任務,雖然還有導出表,重定位表等都沒有涉及到,可是經過這樣的一次小的練習,也就對整個PE結構都有了更深入的瞭解了。

理解原理,方可變化

歡迎關注公衆號:信安本原(sec-source)

相關文章
相關標籤/搜索