PE型病毒编写总结
關(guān)鍵詞(預(yù)備知識):?
PE文件?
PE文件頭?
PE文件Import?table(引入表)?
PE文件Section?table?(節(jié)表)?
PE文件入口地址?
RVA?地址?
Win32?宏匯編語言?
內(nèi)存映射文件?
病毒功能:?
自動搜索硬盤上的PE文件(包括exe和dll),在文件中插入病毒體,使該P(yáng)E文件在執(zhí)行時先執(zhí)行病毒體后執(zhí)行原有程序。?
病毒流程(原始文件):?
1.查找LoadLibrary和GetProcAddress函數(shù)的地址,將其地址保存在數(shù)據(jù)段兩個變量中。?
2.將數(shù)據(jù)段的那兩個保存函數(shù)地址的變量的地址保存在代碼段的存儲區(qū)中(以下病毒體開始)?
3.根據(jù)代碼段存儲區(qū)中保存的值調(diào)用LoadLibrary和GetProcAddress得到病毒所需要的函數(shù)的入口地址,并保存在代碼段存儲區(qū)中?
4.搜索硬盤上的所有文件?
5.檢查該文件是否是合法的PE文件,如果不是,轉(zhuǎn)1?
6.檢查該文件是否已經(jīng)被感染,如果是,轉(zhuǎn)1?
7.根據(jù)節(jié)表計算病毒體大小和該P(yáng)E文件代碼段的空隙,如果病毒體不可以完全插入空隙則轉(zhuǎn)1?
8.尋找PE文件的引入表,查找LoadLibrary和GetProcAddress函數(shù)如果沒有找到,轉(zhuǎn)1?
9.記錄這兩個函數(shù)的地址在PE文件裝載時被存放的位置?
10.更新代碼段存儲區(qū)中原來用來存放這兩個函數(shù)的地址的空間的地址的單元?
11.保存舊的入口地址?
12.根據(jù)代碼段的開始偏移和代碼段大小計算新的入口地址?
13.更新PE文件的入口地址?
14.修改代碼段的屬性成為可讀可寫可執(zhí)行?
15.寫入標(biāo)志(PUKE)?
16.計算病毒代碼寫入的偏移?
17.將病毒體寫入PE文件?
18.計算從病毒代碼跳回源代碼的偏移?
19.在病毒的末尾寫入跳轉(zhuǎn)指令?
20.轉(zhuǎn)1?
編寫過程中遇到的一些主要問題:?
數(shù)據(jù)的存儲?
一 般情況下一個程序中需要用到的數(shù)據(jù)(比如一些常量,字符串,緩沖區(qū))需要存放在數(shù)據(jù)段中。但是由于該病毒采用了插入技術(shù),不能夠修改源PE文件的段結(jié)構(gòu)和 數(shù)據(jù)區(qū),所以不方便保存自己的數(shù)據(jù)段。希望數(shù)據(jù)能夠緊湊的和代碼連接在一起,但是如果在代碼段中定義數(shù)據(jù),代碼段又是不可寫的,如果不加修改的使用,肯定 會出現(xiàn)Exception導(dǎo)致病毒體無法正常運(yùn)行下去。PE文件的每一個段都在PE的段表中占有一項數(shù)據(jù)結(jié)構(gòu),其中有一個元素規(guī)定了該段的一些屬性,比如 可讀,可寫,可執(zhí)行等等,經(jīng)過試驗,只要修改代碼段的屬性到可寫,我們就能夠像操作數(shù)據(jù)段一樣操作代碼段,即可以任意讀寫代碼段。(相關(guān)的知識請查閱PE 文件的格式的文檔),所以我們在感染每一個PE文件之后,為了確保植入的病毒體能夠正確的運(yùn)行,我們要修改其代碼段的屬性為可寫。該問題解決。?
動態(tài)獲取函數(shù)地址?
在病毒的執(zhí)行過程中,需要調(diào)用一些I/O的API來進(jìn)行對文件的操作,如果在編寫病毒源文件的時候直接使用函數(shù)名來調(diào)用,比如?
Invoke?CreateFile,?……?
或者?
Call?CreateFile?
那 么由病毒源文件編譯出來的二進(jìn)制文件使用的將是相對于病毒源可知性文件的相對地址和絕對地址,比如Call?CreateFile變成了 call?dword?ptr[4xxxxx],4xxxxx是用來存放該函數(shù)真實(shí)調(diào)用地址的地址的地址,在401xxx處是一條 jmp?dword?ptr[yyyyyy]的指令,yyyyy是存放該函數(shù)入口地址的地址,這條指令就直接跳轉(zhuǎn)到要調(diào)用的函數(shù)的入口處。但是植入其他的 文件之后,存放該函數(shù)入口地址的地址發(fā)生了改變,并且存放該函數(shù)真實(shí)調(diào)用地址的地址的地址也發(fā)生了改變,病毒體不能正確的調(diào)用到函數(shù),導(dǎo)致程序崩潰。解決 這個問題經(jīng)過了兩個階段。?
第一個階段,我設(shè)想在使用LoadLibrary和GetProcAddress兩個函數(shù)將所有需要的函數(shù)的地址全部 在程序中動態(tài)拿到并保存在代碼段的某一處,每一次掉用的時候都用call?dword?ptr[esi+xxx]來調(diào)用,其中esi指向病毒體頭,xxx 是存儲單元相對于病毒體頭的偏移。這種方法使用函數(shù)的絕對加載地址來調(diào)用函數(shù),避免了上述問題,并在win2kServer上測試通過。但是這種方法無法 動態(tài)得到LoadLibrary和GetProcAddress兩個函數(shù),也就是說這調(diào)用兩個函數(shù)動態(tài)獲得其它函數(shù)地址的這段代碼是不能寫入病毒體的,所 以產(chǎn)生的病毒代碼只能使用病毒第一次運(yùn)行時獲得的函數(shù)的地址,并在傳播中一直使用,但是windows的不同版本中,函數(shù)的加載地址是不同的,通過收集兩 個mm的機(jī)器上的函數(shù)加載地址(win2kPro)證實(shí)了我的擔(dān)心,所以使用這種方法編寫出來的病毒是無法在Win2kPro/Win98/Winxp或 者Win的其他版本上傳播的,除非他們在他們的平臺上運(yùn)行病毒的源可執(zhí)行文件。?
第二個階段,實(shí)際上從第一階段可以看出,我們現(xiàn)在所面臨的問題只 是怎么在程序中動態(tài)獲得LoadLibrary和GetProcAddress兩個函數(shù)的加載地址,呵呵,當(dāng)然必須使用LoadLibrary和 GetProcAddress兩個函數(shù)來獲得了,我們似乎陷入了一個死循環(huán)之中,但是病毒還是要寫的,跨平臺傳播的特性還是要有的否則就太沒有意思了。解 決這個問題的關(guān)鍵在于PE文件的另一個重要的數(shù)據(jù)結(jié)構(gòu)—引入表(import?table)引入表中記錄了該P(yáng)E文件所使用的動態(tài)連接庫的信息,當(dāng)然包括 kernel32.dll以及它里面的函數(shù)LoadLibrary和GetProcAddress(由于這兩個函數(shù)和這個動態(tài)庫的重要性,大部分的PE文 件都要用到它)。在病毒的源文件中我們可以使用函數(shù)名來調(diào)用LoadLibrary和GetProcAddress,用來獲得他們自身的地址,將其存放在 數(shù)據(jù)段(其實(shí)放在哪里都可以)的兩個單元A,B中在代碼段的某處C,D中存放數(shù)據(jù)段A,B的地址:?
mov?eax,?offset?A?
mov?C,?eax?
由于我們從引入表中只能拿到存放這兩個函數(shù)的地址的存儲單元的RVA地址,在感染的時候我們將用那個RVA地址填入C,D,然后使用?
mov?eax,?dword?ptr[C];eax=A的地址?
call?dword?ptr[eax];即調(diào)用eax所指的地址的里存放的地址?
這 樣我們就調(diào)到了LoadLibrary和GetProcAddress來動態(tài)獲取其他的函數(shù)加載地址。用LoadLibrary和 GetProcAddress獲取LoadLibrary和GetProcAddress函數(shù)的地址的那部分代碼是不放入病毒體的,而調(diào)用這兩個函數(shù)獲得 其它函數(shù)的地址是在病毒體內(nèi)的,所以為了保持病毒體的正確運(yùn)行,我們在編寫病毒源文件的時候要讓這一段代碼和病毒傳播后的行為一致,所以我們才把獲得地址 存在AB中再在CD中存AB的地址。如果這個文件沒有用到kernel32.dll或者沒有用到LoadLibrary和GetProcAddress這 兩個函數(shù)的話,我們就放過吧(呵呵,不要太貪),當(dāng)我們找到在PE加載的時候存放這兩個函數(shù)的絕對地址的存儲單元的地址(RVA)之后,我們就把這兩個存 儲單元的RVA地址存入C,D中,使用上面的那段代碼我們就可以得到或者加載任何我們想使用的API。?
動態(tài)定位各存儲單元?
在編寫源文 件的時候,我們用到了許多標(biāo)號來表示存儲單元,比如handle_來保存FindfirstA的返回值,還有很多用于跳轉(zhuǎn)的標(biāo)號比如start:?等等, 如果源文件使用了這些標(biāo)號來讀寫某個存儲單元的值,在編譯后對這些標(biāo)號的調(diào)用自動被轉(zhuǎn)換成為了絕對地址(也是RVA地址,但是是絕對的RVA?地址,和當(dāng) 前的地址無關(guān)),這樣,如何在別人的進(jìn)程空間里準(zhǔn)確定位自己的加載基址并使用這個基址來訪問自己的存儲單元就成了一個問題。但是這個問題很簡單:?
call?start?
……;一些數(shù)據(jù)定義,就是將數(shù)據(jù)段里的數(shù)據(jù)定義移到代碼段里的那一部分,執(zhí)行?
……;的時候直接跳過?
……?
start:?
pop?esi?
這樣esi中就有了我們的病毒的數(shù)據(jù)定義的第一個字節(jié)的地址,在使用的時候我們使用esi+偏移的方式來使用這些數(shù)據(jù)單元,保證了不論在何處都能夠準(zhǔn)確定位。?
其他問題?
解決了以上的問題之后,寫一個PE型病毒應(yīng)該是輕而易舉的事情了。但是還有一些問題解決了之后能夠大大增強(qiáng)病毒的活性,列舉如下:?
1.無法在除了代碼段的其他各段插入病毒體并運(yùn)行?
2.可以修改只讀文件的屬性?
3.病毒體太大
PE文件?
PE文件頭?
PE文件Import?table(引入表)?
PE文件Section?table?(節(jié)表)?
PE文件入口地址?
RVA?地址?
Win32?宏匯編語言?
內(nèi)存映射文件?
病毒功能:?
自動搜索硬盤上的PE文件(包括exe和dll),在文件中插入病毒體,使該P(yáng)E文件在執(zhí)行時先執(zhí)行病毒體后執(zhí)行原有程序。?
病毒流程(原始文件):?
1.查找LoadLibrary和GetProcAddress函數(shù)的地址,將其地址保存在數(shù)據(jù)段兩個變量中。?
2.將數(shù)據(jù)段的那兩個保存函數(shù)地址的變量的地址保存在代碼段的存儲區(qū)中(以下病毒體開始)?
3.根據(jù)代碼段存儲區(qū)中保存的值調(diào)用LoadLibrary和GetProcAddress得到病毒所需要的函數(shù)的入口地址,并保存在代碼段存儲區(qū)中?
4.搜索硬盤上的所有文件?
5.檢查該文件是否是合法的PE文件,如果不是,轉(zhuǎn)1?
6.檢查該文件是否已經(jīng)被感染,如果是,轉(zhuǎn)1?
7.根據(jù)節(jié)表計算病毒體大小和該P(yáng)E文件代碼段的空隙,如果病毒體不可以完全插入空隙則轉(zhuǎn)1?
8.尋找PE文件的引入表,查找LoadLibrary和GetProcAddress函數(shù)如果沒有找到,轉(zhuǎn)1?
9.記錄這兩個函數(shù)的地址在PE文件裝載時被存放的位置?
10.更新代碼段存儲區(qū)中原來用來存放這兩個函數(shù)的地址的空間的地址的單元?
11.保存舊的入口地址?
12.根據(jù)代碼段的開始偏移和代碼段大小計算新的入口地址?
13.更新PE文件的入口地址?
14.修改代碼段的屬性成為可讀可寫可執(zhí)行?
15.寫入標(biāo)志(PUKE)?
16.計算病毒代碼寫入的偏移?
17.將病毒體寫入PE文件?
18.計算從病毒代碼跳回源代碼的偏移?
19.在病毒的末尾寫入跳轉(zhuǎn)指令?
20.轉(zhuǎn)1?
編寫過程中遇到的一些主要問題:?
數(shù)據(jù)的存儲?
一 般情況下一個程序中需要用到的數(shù)據(jù)(比如一些常量,字符串,緩沖區(qū))需要存放在數(shù)據(jù)段中。但是由于該病毒采用了插入技術(shù),不能夠修改源PE文件的段結(jié)構(gòu)和 數(shù)據(jù)區(qū),所以不方便保存自己的數(shù)據(jù)段。希望數(shù)據(jù)能夠緊湊的和代碼連接在一起,但是如果在代碼段中定義數(shù)據(jù),代碼段又是不可寫的,如果不加修改的使用,肯定 會出現(xiàn)Exception導(dǎo)致病毒體無法正常運(yùn)行下去。PE文件的每一個段都在PE的段表中占有一項數(shù)據(jù)結(jié)構(gòu),其中有一個元素規(guī)定了該段的一些屬性,比如 可讀,可寫,可執(zhí)行等等,經(jīng)過試驗,只要修改代碼段的屬性到可寫,我們就能夠像操作數(shù)據(jù)段一樣操作代碼段,即可以任意讀寫代碼段。(相關(guān)的知識請查閱PE 文件的格式的文檔),所以我們在感染每一個PE文件之后,為了確保植入的病毒體能夠正確的運(yùn)行,我們要修改其代碼段的屬性為可寫。該問題解決。?
動態(tài)獲取函數(shù)地址?
在病毒的執(zhí)行過程中,需要調(diào)用一些I/O的API來進(jìn)行對文件的操作,如果在編寫病毒源文件的時候直接使用函數(shù)名來調(diào)用,比如?
Invoke?CreateFile,?……?
或者?
Call?CreateFile?
那 么由病毒源文件編譯出來的二進(jìn)制文件使用的將是相對于病毒源可知性文件的相對地址和絕對地址,比如Call?CreateFile變成了 call?dword?ptr[4xxxxx],4xxxxx是用來存放該函數(shù)真實(shí)調(diào)用地址的地址的地址,在401xxx處是一條 jmp?dword?ptr[yyyyyy]的指令,yyyyy是存放該函數(shù)入口地址的地址,這條指令就直接跳轉(zhuǎn)到要調(diào)用的函數(shù)的入口處。但是植入其他的 文件之后,存放該函數(shù)入口地址的地址發(fā)生了改變,并且存放該函數(shù)真實(shí)調(diào)用地址的地址的地址也發(fā)生了改變,病毒體不能正確的調(diào)用到函數(shù),導(dǎo)致程序崩潰。解決 這個問題經(jīng)過了兩個階段。?
第一個階段,我設(shè)想在使用LoadLibrary和GetProcAddress兩個函數(shù)將所有需要的函數(shù)的地址全部 在程序中動態(tài)拿到并保存在代碼段的某一處,每一次掉用的時候都用call?dword?ptr[esi+xxx]來調(diào)用,其中esi指向病毒體頭,xxx 是存儲單元相對于病毒體頭的偏移。這種方法使用函數(shù)的絕對加載地址來調(diào)用函數(shù),避免了上述問題,并在win2kServer上測試通過。但是這種方法無法 動態(tài)得到LoadLibrary和GetProcAddress兩個函數(shù),也就是說這調(diào)用兩個函數(shù)動態(tài)獲得其它函數(shù)地址的這段代碼是不能寫入病毒體的,所 以產(chǎn)生的病毒代碼只能使用病毒第一次運(yùn)行時獲得的函數(shù)的地址,并在傳播中一直使用,但是windows的不同版本中,函數(shù)的加載地址是不同的,通過收集兩 個mm的機(jī)器上的函數(shù)加載地址(win2kPro)證實(shí)了我的擔(dān)心,所以使用這種方法編寫出來的病毒是無法在Win2kPro/Win98/Winxp或 者Win的其他版本上傳播的,除非他們在他們的平臺上運(yùn)行病毒的源可執(zhí)行文件。?
第二個階段,實(shí)際上從第一階段可以看出,我們現(xiàn)在所面臨的問題只 是怎么在程序中動態(tài)獲得LoadLibrary和GetProcAddress兩個函數(shù)的加載地址,呵呵,當(dāng)然必須使用LoadLibrary和 GetProcAddress兩個函數(shù)來獲得了,我們似乎陷入了一個死循環(huán)之中,但是病毒還是要寫的,跨平臺傳播的特性還是要有的否則就太沒有意思了。解 決這個問題的關(guān)鍵在于PE文件的另一個重要的數(shù)據(jù)結(jié)構(gòu)—引入表(import?table)引入表中記錄了該P(yáng)E文件所使用的動態(tài)連接庫的信息,當(dāng)然包括 kernel32.dll以及它里面的函數(shù)LoadLibrary和GetProcAddress(由于這兩個函數(shù)和這個動態(tài)庫的重要性,大部分的PE文 件都要用到它)。在病毒的源文件中我們可以使用函數(shù)名來調(diào)用LoadLibrary和GetProcAddress,用來獲得他們自身的地址,將其存放在 數(shù)據(jù)段(其實(shí)放在哪里都可以)的兩個單元A,B中在代碼段的某處C,D中存放數(shù)據(jù)段A,B的地址:?
mov?eax,?offset?A?
mov?C,?eax?
由于我們從引入表中只能拿到存放這兩個函數(shù)的地址的存儲單元的RVA地址,在感染的時候我們將用那個RVA地址填入C,D,然后使用?
mov?eax,?dword?ptr[C];eax=A的地址?
call?dword?ptr[eax];即調(diào)用eax所指的地址的里存放的地址?
這 樣我們就調(diào)到了LoadLibrary和GetProcAddress來動態(tài)獲取其他的函數(shù)加載地址。用LoadLibrary和 GetProcAddress獲取LoadLibrary和GetProcAddress函數(shù)的地址的那部分代碼是不放入病毒體的,而調(diào)用這兩個函數(shù)獲得 其它函數(shù)的地址是在病毒體內(nèi)的,所以為了保持病毒體的正確運(yùn)行,我們在編寫病毒源文件的時候要讓這一段代碼和病毒傳播后的行為一致,所以我們才把獲得地址 存在AB中再在CD中存AB的地址。如果這個文件沒有用到kernel32.dll或者沒有用到LoadLibrary和GetProcAddress這 兩個函數(shù)的話,我們就放過吧(呵呵,不要太貪),當(dāng)我們找到在PE加載的時候存放這兩個函數(shù)的絕對地址的存儲單元的地址(RVA)之后,我們就把這兩個存 儲單元的RVA地址存入C,D中,使用上面的那段代碼我們就可以得到或者加載任何我們想使用的API。?
動態(tài)定位各存儲單元?
在編寫源文 件的時候,我們用到了許多標(biāo)號來表示存儲單元,比如handle_來保存FindfirstA的返回值,還有很多用于跳轉(zhuǎn)的標(biāo)號比如start:?等等, 如果源文件使用了這些標(biāo)號來讀寫某個存儲單元的值,在編譯后對這些標(biāo)號的調(diào)用自動被轉(zhuǎn)換成為了絕對地址(也是RVA地址,但是是絕對的RVA?地址,和當(dāng) 前的地址無關(guān)),這樣,如何在別人的進(jìn)程空間里準(zhǔn)確定位自己的加載基址并使用這個基址來訪問自己的存儲單元就成了一個問題。但是這個問題很簡單:?
call?start?
……;一些數(shù)據(jù)定義,就是將數(shù)據(jù)段里的數(shù)據(jù)定義移到代碼段里的那一部分,執(zhí)行?
……;的時候直接跳過?
……?
start:?
pop?esi?
這樣esi中就有了我們的病毒的數(shù)據(jù)定義的第一個字節(jié)的地址,在使用的時候我們使用esi+偏移的方式來使用這些數(shù)據(jù)單元,保證了不論在何處都能夠準(zhǔn)確定位。?
其他問題?
解決了以上的問題之后,寫一個PE型病毒應(yīng)該是輕而易舉的事情了。但是還有一些問題解決了之后能夠大大增強(qiáng)病毒的活性,列舉如下:?
1.無法在除了代碼段的其他各段插入病毒體并運(yùn)行?
2.可以修改只讀文件的屬性?
3.病毒體太大
轉(zhuǎn)載于:https://blog.51cto.com/jiayu/33259
總結(jié)
- 上一篇: SQL Tips:兼顾检索速度和精确性
- 下一篇: OSPF身份验证配置实例