hook什么意思_这是什么骚代码!
神秘代碼
今天給大家看個有意思的東西!
不僅有意思,還能學(xué)到知識。
話題從兩行(準確的說是一行)神奇的代碼聊起:
//?main.c#include?int?main[]?=?{?232,-1065134080,26643,12517440,4278206464,12802064,(int)printf?};
這是一段C++代碼,猜猜看編譯運行后,會輸出什么?
可能,你會問:這TM連main函數(shù)都沒有,能編譯成功?
還真能!
咱們分別在Windows平臺下的Visual Studio和Linux平臺下的 g++ 進行編譯,然后分別執(zhí)行看看效果:
Windows下:
Linux下:
不僅能編譯成功,還能正常運行,在Windows上輸出了一個MZ,在Linux上輸出了一個ELF。
熟悉PE文件格式的同學(xué)可能知道,MZ是PE文件開頭的標志,另外,ELF也是Linux上的可執(zhí)行文件開頭的標志。
也就是說:上面這行代碼執(zhí)行后,把所在可執(zhí)行文件頭部的字符串給打印出來了!
反匯編真相
看到這里,你可能有兩個問題:
- 為什么沒有main函數(shù)還能通過編譯?
- 為什么會輸出這么一串信息?
對于第一個問題,相信大家應(yīng)該也猜到了個八九不離十。雖然代碼中沒有main函數(shù),但是有一個main數(shù)組啊!會不會跟它有關(guān)系?
是的沒錯,對于編譯器而言,函數(shù)也好,變量也好,最終都處理成了一個個的符號Symbol,而編譯器并沒有區(qū)分這個符號是來自一個函數(shù)還是一個數(shù)組。所以,我們用一個main數(shù)組,騙過了編譯器。
也就是說:編譯器把main數(shù)組當(dāng)成了main函數(shù),把main數(shù)組中的數(shù)據(jù)當(dāng)成了main函數(shù)的函數(shù)體指令。
而要回答第二個問題,那就得看下這個main數(shù)組中的這一段奇怪的數(shù)字,到底是一段什么樣的代碼?
將main數(shù)組中的數(shù)值轉(zhuǎn)換成16進制看看,按照一個int變量占4個字節(jié)對齊:
再進一步,使用反匯編引擎看看這段16進制數(shù)據(jù)是什么指令?
接下來,咱們逐條分析這些指令。
call $+5
這是一條非常重要的指令,請記住:call指令是在執(zhí)行函數(shù)調(diào)用,執(zhí)行call指令的時候,會將下一條指令的地址壓入線程的棧頂,用于函數(shù)返回時取出找到回去的路,那下一條是誰?就是下面的pop eax這條指令,所以執(zhí)行這個call指令時,會把下面那個pop eax指令的地址壓入棧頂。
再者,call后面的目標地址是$+5,也就是這條call指令地址+5個字節(jié)的地方,同樣是下面那條pop eax指令的地址,所以call的目標函數(shù)就是緊接著的下面pop eax指令開始的地方。
那這么費勁執(zhí)行這個call $+5的意義何在?其實就是為了獲取當(dāng)前這段代碼所在的內(nèi)存空間地址,但是又沒有辦法直接讀取指令寄存器EIP的值,所以借助一個call,把這段代碼的地址壓入到堆棧中,隨后再取出來就能知道這段代碼被放置在內(nèi)存中哪個地址在執(zhí)行了。
這個手法,是黑客編寫shellcode的慣用伎倆。
pop ?eax
注意,執(zhí)行到這里的時候,線程的棧頂存放的就是這條指令所在的位置,是上面那條call指令導(dǎo)致的結(jié)果。
接著,pop eax,將棧頂存放的這個地址取出來,放到eax寄存器中。現(xiàn)在eax中存放的就是當(dāng)前指令的內(nèi)存地址了。
add ?eax, 13h
上面費這么大勁拿到了這個地址有什么用呢?別急,看這條指令,給它加了13h,也就是十進制的19,回頭看看main數(shù)組那個十六進制字節(jié)表,加了19后,正好是main數(shù)組最后一個元素所在的位置——里面存放了printf函數(shù)的地址。
所以,截止到這里,前面這三條指令的目的就是為了能拿到printf函數(shù)的地址。
push 400000h??拿到printf函數(shù)以后,開始調(diào)用。這里給printf傳了一個參數(shù):0x00400000,也就是要打印的字符串地址。
mov ?edi, 400000h??這里同樣是在給printf函數(shù)傳參,這里和上面那條,一個通過堆棧傳參,一個通過寄存器傳參數(shù),是為了同時兼容Windows平臺和Linux x64平臺上的函數(shù)調(diào)用約定。
而之所以傳遞的字符串地址是0x00400000,是因為剛好,這個數(shù)字是兩個平臺上可執(zhí)行文件加載的默認基地址。
Windows:
Linux:
(gdb)?x?/16c?0x004000000x400000:?127?'\177'?69?'E'?76?'L'?70?'F'?2?'\002'?1?'\001'?1?'\001'?0?'\000'
0x400008:?0?'\000'?0?'\000'?0?'\000'?0?'\000'?0?'\000'?0?'\000'?0?'\000'?0?'\000'
call dword ptr [eax]
還記得前面eax存儲的是main數(shù)組的最后一個格子的地址,這個格子里面存放的是printf函數(shù)的地址。
于是,通過一個指針調(diào)用call,來調(diào)用printf,完成打印輸出。
pop ?eax
函數(shù)調(diào)用完了,得進行堆棧平衡,前面?zhèn)鲄簵A?#xff0c;這里就得彈出來。
retn
注意這個retn指令,retn指令和call指令對應(yīng),call用于調(diào)用函數(shù),將返回地址壓棧,而retn指令則將棧頂?shù)臄?shù)據(jù)彈出來作為返回地址,跳回去執(zhí)行。
還記得嗎,現(xiàn)在這段代碼是處于被第一個call指令調(diào)用的上下文中的,正常情況下,執(zhí)行retn是不是應(yīng)該返回到call指令后面?那豈不是又回去pop eax走一遍亂了套了?但注意,現(xiàn)在棧頂?shù)哪莻€返回地址已經(jīng)提前被pop出來了(第二行那個pop eax),那現(xiàn)在執(zhí)行retn,取出來的棧頂數(shù)據(jù)又是什么呢?
這個數(shù)據(jù)就是線程執(zhí)行到整個main函數(shù)最開始的時候,棧頂保留的調(diào)用main函數(shù)的調(diào)用者的返回地址。所以這個retn不是返回到第一個call后面,而是返回到了上一級調(diào)用main函數(shù)的的那個地方。
至于具體是誰在調(diào)用main函數(shù),這就不是這篇文章的重點了,屬于Linux和Windows上各自的C/C++運行時庫CRT函數(shù)的范疇。
到這里,你應(yīng)該就能明白,這個程序是如何運行起來的,以及,為什么會有那樣的輸出信息。
幾個注意事項
總結(jié)
其實這段代碼的思路并非我的原創(chuàng),在國外有一個國際C語言混亂代碼大賽(IOCCC, The International Obfuscated C Code Contest)。這個比賽的特點就在于寫最騷的代碼,實現(xiàn)最奇葩的效果,其中就有這樣的獲獎案例。
后來,國內(nèi)一個大牛也原創(chuàng)了自己的版本,參考鏈接:
https://blog.csdn.net/masefee/article/details/6606813
不過,這個版本僅適用于Windows平臺,我在此基礎(chǔ)之上,又改了現(xiàn)在這個版本,同時支持Windows和Linux平臺。
這段代碼本身沒有任何意義,不具備實用價值,但透過代碼去研究代碼和程序背后執(zhí)行的底層原理,了解CPU如何調(diào)用函數(shù)、傳遞參數(shù),跳轉(zhuǎn),操作堆棧,這些才是這篇文章的意義所在。
給大家留個思考題,下面這行代碼能正常運行起來嗎,運行起來又做了什么呢?
int?main[]?=?{0xC3};歡迎評論區(qū)留言探討!
往期推薦
打錢!我的數(shù)據(jù)庫被黑客勒索了!
黑客愛用的HOOK技術(shù)大揭秘!
安全軟件群雄混戰(zhàn)史
總結(jié)
以上是生活随笔為你收集整理的hook什么意思_这是什么骚代码!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进程控制常用的一些操作
- 下一篇: 阿里云官方网站免费套餐怎么抢