日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

hook什么意思_这是什么骚代码!

發布時間:2023/12/19 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 hook什么意思_这是什么骚代码! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

神秘代碼

今天給大家看個有意思的東西!

不僅有意思,還能學到知識。

話題從兩行(準確的說是一行)神奇的代碼聊起:

//?main.c
#include?int?main[]?=?{?232,-1065134080,26643,12517440,4278206464,12802064,(int)printf?};

這是一段C++代碼,猜猜看編譯運行后,會輸出什么?

可能,你會問:這TM連main函數都沒有,能編譯成功?

還真能!

咱們分別在Windows平臺下的Visual Studio和Linux平臺下的 g++ 進行編譯,然后分別執行看看效果:

Windows下:

Linux下:

不僅能編譯成功,還能正常運行,在Windows上輸出了一個MZ,在Linux上輸出了一個ELF

熟悉PE文件格式的同學可能知道,MZ是PE文件開頭的標志,另外,ELF也是Linux上的可執行文件開頭的標志。

也就是說:上面這行代碼執行后,把所在可執行文件頭部的字符串給打印出來了!

反匯編真相

看到這里,你可能有兩個問題:

  • 為什么沒有main函數還能通過編譯?
  • 為什么會輸出這么一串信息?

對于第一個問題,相信大家應該也猜到了個八九不離十。雖然代碼中沒有main函數,但是有一個main數組啊!會不會跟它有關系?

是的沒錯,對于編譯器而言,函數也好,變量也好,最終都處理成了一個個的符號Symbol,而編譯器并沒有區分這個符號是來自一個函數還是一個數組。所以,我們用一個main數組,騙過了編譯器。

也就是說:編譯器把main數組當成了main函數,把main數組中的數據當成了main函數的函數體指令。

而要回答第二個問題,那就得看下這個main數組中的這一段奇怪的數字,到底是一段什么樣的代碼?

將main數組中的數值轉換成16進制看看,按照一個int變量占4個字節對齊:

再進一步,使用反匯編引擎看看這段16進制數據是什么指令?

接下來,咱們逐條分析這些指令。

call $+5

這是一條非常重要的指令,請記住:call指令是在執行函數調用,執行call指令的時候,會將下一條指令的地址壓入線程的棧頂,用于函數返回時取出找到回去的路,那下一條是誰?就是下面的pop eax這條指令,所以執行這個call指令時,會把下面那個pop eax指令的地址壓入棧頂。

再者,call后面的目標地址是$+5,也就是這條call指令地址+5個字節的地方,同樣是下面那條pop eax指令的地址,所以call的目標函數就是緊接著的下面pop eax指令開始的地方。

那這么費勁執行這個call $+5的意義何在?其實就是為了獲取當前這段代碼所在的內存空間地址,但是又沒有辦法直接讀取指令寄存器EIP的值,所以借助一個call,把這段代碼的地址壓入到堆棧中,隨后再取出來就能知道這段代碼被放置在內存中哪個地址在執行了。

這個手法,是黑客編寫shellcode的慣用伎倆。

pop ?eax

注意,執行到這里的時候,線程的棧頂存放的就是這條指令所在的位置,是上面那條call指令導致的結果。

接著,pop eax,將棧頂存放的這個地址取出來,放到eax寄存器中。現在eax中存放的就是當前指令的內存地址了。

add ?eax, 13h

上面費這么大勁拿到了這個地址有什么用呢?別急,看這條指令,給它加了13h,也就是十進制的19,回頭看看main數組那個十六進制字節表,加了19后,正好是main數組最后一個元素所在的位置——里面存放了printf函數的地址。

所以,截止到這里,前面這三條指令的目的就是為了能拿到printf函數的地址。

push 400000h??拿到printf函數以后,開始調用。這里給printf傳了一個參數:0x00400000,也就是要打印的字符串地址。

mov ?edi, 400000h??這里同樣是在給printf函數傳參,這里和上面那條,一個通過堆棧傳參,一個通過寄存器傳參數,是為了同時兼容Windows平臺和Linux x64平臺上的函數調用約定。

而之所以傳遞的字符串地址是0x00400000,是因為剛好,這個數字是兩個平臺上可執行文件加載的默認基地址。

Windows:

Linux:

(gdb)?x?/16c?0x00400000
0x400000:?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數組的最后一個格子的地址,這個格子里面存放的是printf函數的地址。

于是,通過一個指針調用call,來調用printf,完成打印輸出。

pop ?eax

函數調用完了,得進行堆棧平衡,前面傳參壓棧了,這里就得彈出來。

retn

注意這個retn指令,retn指令和call指令對應,call用于調用函數,將返回地址壓棧,而retn指令則將棧頂的數據彈出來作為返回地址,跳回去執行。

還記得嗎,現在這段代碼是處于被第一個call指令調用的上下文中的,正常情況下,執行retn是不是應該返回到call指令后面?那豈不是又回去pop eax走一遍亂了套了?但注意,現在棧頂的那個返回地址已經提前被pop出來了(第二行那個pop eax),那現在執行retn,取出來的棧頂數據又是什么呢?

這個數據就是線程執行到整個main函數最開始的時候,棧頂保留的調用main函數的調用者的返回地址。所以這個retn不是返回到第一個call后面,而是返回到了上一級調用main函數的的那個地方。

至于具體是誰在調用main函數,這就不是這篇文章的重點了,屬于Linux和Windows上各自的C/C++運行時庫CRT函數的范疇。

到這里,你應該就能明白,這個程序是如何運行起來的,以及,為什么會有那樣的輸出信息。

幾個注意事項

  • 首先,為了能夠順利通過編譯,在Linux上,需要使用 g++ 而不是gcc進行編譯,因為對main這個全局變量初始化時,C語言規定必須是常量,而不能是動態確定的(最后那個printf函數地址就是動態的),同時還得加上 -fpermissive 編譯選項。
  • 需要關閉模塊的隨機加載功能。現代操作系統為了抵抗安全攻擊,可執行文件的加載基地址都進行了隨機化,防止被猜測,而這段代碼能夠正常運行的前提是可執行文件加載基址是0x00400000。不能隨機化,所以需要通過編譯器來關閉。
  • 最后,根據前面的分析其實也知道了,其實程序把main數組中的數據當成了代碼在執行。在現代操作系統的安全性保護下,默認情況下是拒絕執行數據所在的內存頁面的,因為這些內存頁面只有讀寫權限,而沒有可執行權限,這一安全機制叫DEP/NX。所以為了正常運行,需要把這個關閉。對于g++,添加 -z execstack 編譯選項即可。
  • 總結

    其實這段代碼的思路并非我的原創,在國外有一個國際C語言混亂代碼大賽(IOCCC, The International Obfuscated C Code Contest)。這個比賽的特點就在于寫最騷的代碼,實現最奇葩的效果,其中就有這樣的獲獎案例。

    后來,國內一個大牛也原創了自己的版本,參考鏈接:

    https://blog.csdn.net/masefee/article/details/6606813

    不過,這個版本僅適用于Windows平臺,我在此基礎之上,又改了現在這個版本,同時支持Windows和Linux平臺。

    這段代碼本身沒有任何意義,不具備實用價值,但透過代碼去研究代碼和程序背后執行的底層原理,了解CPU如何調用函數、傳遞參數,跳轉,操作堆棧,這些才是這篇文章的意義所在。

    給大家留個思考題,下面這行代碼能正常運行起來嗎,運行起來又做了什么呢?

    int?main[]?=?{0xC3};

    歡迎評論區留言探討!

    往期推薦

    打錢!我的數據庫被黑客勒索了!

    黑客愛用的HOOK技術大揭秘!

    安全軟件群雄混戰史

    總結

    以上是生活随笔為你收集整理的hook什么意思_这是什么骚代码!的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。