日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

【詭異的精簡C語言程序】main函數隱藏

系統 2391 0

哎,幾個月以來沒有寫博客了,時間太緊,精力又有限。今天正好有這個時間,打算寫一篇今天在網上討論的一個問題。

我想大家應該都聽過“國際C語言混亂代碼大賽( IOCCC , The International Obfuscated C Code Contest)”吧,今天無意間在網上討論到這個問題。我有意將main函數改變了一下,居然編譯通過了,于是想利用這個特性,寫一個“詭異”的代碼。(寫完之后發現,IOCCC居然也有類似的參賽獲獎作品,悲劇,早知道我也去參賽了。。。)

進入正題吧,代碼是這樣的:

將這兩句代碼copy到XXX.c里,用VC編譯,能順利通過,并執行,輸出結果為:

This program cannot be run in DOS mode.
$

或者換一種寫法:

輸出結果一致。

這么一來,為什么這兩段詭異的代碼能夠通過編譯并運行呢?可以總結為兩個疑問:

1. 為什么能通過編譯?

2. 為什么能輸出這么一句字符串?也沒用看到調用printf,更沒有看到該字符串。

我們一個一個解決,對于第一個疑問:

首先,這兩段代碼都必須使用.c文件進行編譯,在.cpp文件下是不能通過編譯的,也就是說必須用C編譯器編譯,C++編譯器不能通過。

其次,正因為是C編譯器,同時又是Visual studio環境,那么對于函數的參數個數,類型等的檢查不會很嚴格,對于入口函數main,編譯器在查找main函數符號并鏈接時,不會嚴格檢查。因此,在這個地方將main函數用數組形式表達也能順利鏈接。GCC下也是可以順利編譯通過的,只是要改一下代碼才能成功運行,本文就不再累述了,這個不是重點。

到此,第一個疑問就解決了。那么第二個疑問就相對復雜很多了,我們一步一步分析。

首先,main數組被鏈接為main函數,那么main數組內部的整數(int,4字節)就是main函數執行的代碼字節(機器碼),這里有點類似shellcode的原理。至于什么是機器碼,這里就不做解釋了,可以在我之前的博客里或者網絡上找到答案。既然是機器碼,那么這些整數就一定代表具體的執行邏輯,由于是直接寫的機器碼(整數),我們只能看反匯編代碼,我們以第一個例子為例:

00492000 E8 00 00 00 00 call _main+5 (492005h)
00492005 58 pop eax
00492006 83 C0 0F add eax,0Fh
00492009 68 4E 00 40 00 push 40004Eh
0049200E FF 10 call dword ptr [eax]
00492010 58 pop eax
00492011 C3 ret
00492012 00 00 add byte ptr [eax],al
00492014 E0 B0 loopne 00491FC6
00492016 42 inc edx
00492017 00 E0 add al,ah

在看main數組的內存:

0x00492000 e8 00 00 00 00 58 83 c0 0f 68 4e 00 40 00 ff 10 58 c3 00 00 e0 b0 42 00 ?....X??.hN.@...X?..??B .

0x00492018 e0 b0 42 00 —————————————————————————------ ??B..............HI.....

橫杠為省略部分內存,大家可以發現,第一排的24個字節(6個int)正是main數組里的6個整數,最后4個字節即為printf函數的首地址:0x0042b0e0(在你的平臺下通常不一樣)。那么我們看上面的反匯編代碼,前面的機器碼也是內存里的24個字節,我們來分析一下反匯編代碼:

頭兩句紅色的匯編代碼:

call 0x00492005

pop eax

這兩句的功能主要是為了取得EIP寄存器的值,當執行完pop eax這句代碼之后,eax的值為0x00492005,這樣便取得了EIP的地址。

至于這兩句代碼為什么能取得EIP的地址在之前的博文里也有相關的講解,我們知道call指令理解分為兩步操作,一是將call指令的下一條指令的代碼地址壓棧,二是進行跳轉。

這里call指令的下一句代碼是 pop eax ,它的代碼地址是0x00492005。call指令在壓入這個地址值到棧里之后,再跳轉到pop eax這句。此刻,pop eax就會將剛剛壓入的代碼地址(0x00492005)彈出到eax里,這樣eax里就獲得了pop eax的代碼地址。為什么要這么麻煩呢,是因為內斂匯編不支持mov eax,eip這類的操作,所以就借助call的特性來獲得EIP。

那么,為什么要獲取這個地址呢,目的是為了獲取后面printf函數的地址所存儲的位置(也就是相對于main數組首地址的偏移量,也就是main數組最后一個int的內存地址),也就是 0x00492014 。這個地址也就是前面獲取的EIP值 0x00492005 + 0x0f 。因此,上面綠色的那句匯編代碼 add eax, 0fh 就不用再解釋了吧,add之后,eax的值則為 0x00492014

到此,printf函數的地址值存放的位置也知道了,這時該考慮怎么能夠調用printf輸出前面那串字符了。乍一看,這串字符似乎很熟悉,對!的確很熟悉,這串字符正是PE文件頭里的信息,exe文件里有這么一個信息,那么我們去哪兒找到這串字符的內存地址然后傳入printf呢?

平時調試程序的時候應該能夠注意到exe通常默認都會從0x00400000這個內存地址開始加載,當然也有時候不是這個地址開始的,例如在win7和vista下,如果編譯器開啟了隨機基地址選項時,那么每次運行exe時,就會隨機一個基地址進行加載,這時就不一定是從0x00400000這個內存地址開始加載了。本文只針對從0x00400000這個地址加載的情況進行分析。

好了,我們來看看0x00400000這個內存地址下的內存情況:

0x00400000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00 b8 00 00 00 00 00 00 00 MZ?.............?.......
0x00400018 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @.......................
0x00400030 00 00 00 00 00 00 00 00 00 00 00 00 e8 00 00 00 0e 1f ba 0e 00 b4 09 cd ............?.....?..?.?
0x00400048 21 b8 01 4c cd 21 54 68 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f !?.L?! This program canno
0x00400060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 6d 6f 64 65 2e 0d 0d 0a t be run in DOS mode....
0x00400078 24 00
00 00 00 00 00 00 25 3c f5 d5 61 5d 9b 86 61 5d 9b 86 61 5d 9b 86 $.......%<??a]??a]??a]??

可以看出,上面紅色的部分從0x0040004e開始即為之前輸出的那串字符,一直到'$'字符后才結束,即到0x00400079才結束輸出。

分析到此,前面的反匯編里的黑色粗體一句的匯編就已經很明了了,它正是將0x0040004e這個地址傳遞給printf函數,讓其輸出這串字符。

之后的一句藍色的call代碼,即調用printf函數,前面已經將printf的地址值在main數組里的地址值存到了eax里,此刻只需要將eax下的地址值取出來,call過去就可以了,也就等價于:

call main[ 5] // 偽代碼

調用完printf輸出之后,pop eax則是為了平衡棧,因為printf是__cdecl調用約定,所以調用者需要平衡棧。pop eax就等價于add esp,4,這里為了節約幾個字節,pop eax只占一個字節。

好了,反匯編代碼就分析得差不多了。原理其實很簡單,至于第二種寫法與第一種只是printf函數的地址存放的位置不一樣。我們來看反匯編代碼:

00492000 E0 B0 loopne 00491FB2
00492002 42 inc edx
00492003 00 db 00h
00492004 E8 00 00 00 00 call _main+5 (492009h)
00492009 58 pop eax
0049200A 83 E8 09 sub eax,9
0049200D 68 4E 00 40 00 push 40004Eh
00492012 FF 10 call dword ptr [eax]
00492014 58 pop eax
00492015 C3 ret
00492016 00 00 add byte ptr [eax],al

綠色的一句代碼變成了sub,向前減9個字節,剛好是 0x00492000 ,也就是變量"______"的地址。printf函數的地址值就存在這里,所以需要減去9,也就是0x00492009 - 9。其他部分的代碼與前面的一致。("______"變量和main數組的地址在內存上是連續的)

詭異的代碼就如此的產生了,其實如果將main數組翻譯為內斂匯編的版本,如下:

這兩個版本不能運行,只能通過編譯。因為printf的函數地址存放的位置不能確定了,我們使用數組是可以確定的。另外在ret指令后,如果不是4的整數倍,那么寫成main數組的時候,就需要填充字節,在main數組里我填充為0。

以上兩個版本可能在某些時候不能運行成功,因為main數組處于數據段,數據段的內存可能沒有執行權限,因此會出錯。在實際中,可以修改內存權限。

綜述:

1. 本文的例子不具有實用價值,只作為研究之用,其目的在于了解函數調用模型的本質以及匯編層的框架和指令的利用。重在原理性的研究,拓展思維。

2. 個人認為,很多不具有實際實用價值的東西并非不值得研究,研究的目的不在于結果,在于過程,吸收有利的,拋棄無用的。

3. 對于本文的實例,原理性的東西如函數調用模型,在實際中有用處很多,很典型的例子就是通過dump文件進行錯誤查找和分析,這里的dump文件可能是自定義的dump格式。

好了,本文到此就告一段落,歡迎交流。

---如需轉載,請注明出處,謝謝支持----

【詭異的精簡C語言程序】main函數隱藏

【詭異的精簡C語言程序】main函數隱藏


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 昌邑市| 汕头市| 丹东市| 余干县| 凤台县| 宜章县| 大关县| 平阳县| 调兵山市| 富阳市| 平湖市| 海口市| 辰溪县| 历史| 濉溪县| 宁明县| 新余市| 武山县| 汶川县| 大安市| 九龙坡区| 和林格尔县| 宾阳县| 杂多县| 镇赉县| 东阳市| 武冈市| 雷州市| 云梦县| 小金县| 安化县| 高淳县| 穆棱市| 绥芬河市| 敦化市| 裕民县| 得荣县| 琼结县| 邵东县| 瓦房店市| 铁力市|