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