<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          這特么也可以???

          共 3450字,需瀏覽 7分鐘

           ·

          2020-12-01 23:03

          神秘代碼

          今天給大家看個(gè)有意思的東西!

          不僅有意思,還能學(xué)到知識(shí)。

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

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

          這是一段C++代碼,猜猜看編譯運(yùn)行后,會(huì)輸出什么?

          可能,你會(huì)問:這TM連main函數(shù)都沒有,能編譯成功?

          還真能!

          咱們分別在Windows平臺(tái)下的Visual Studio和Linux平臺(tái)下的 g++ 進(jìn)行編譯,然后分別執(zhí)行看看效果:

          Windows下:

          Linux下:

          不僅能編譯成功,還能正常運(yùn)行,在Windows上輸出了一個(gè)MZ,在Linux上輸出了一個(gè)ELF。

          熟悉PE文件格式的同學(xué)可能知道,MZ是PE文件開頭的標(biāo)志,另外,ELF也是Linux上的可執(zhí)行文件開頭的標(biāo)志。

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

          反匯編真相

          看到這里,你可能有兩個(gè)問題:

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

          對(duì)于第一個(gè)問題,相信大家應(yīng)該也猜到了個(gè)八九不離十。雖然代碼中沒有main函數(shù),但是有一個(gè)main數(shù)組??!會(huì)不會(huì)跟它有關(guān)系?

          是的沒錯(cuò),對(duì)于編譯器而言,函數(shù)也好,變量也好,最終都處理成了一個(gè)個(gè)的符號(hào)Symbol,而編譯器并沒有區(qū)分這個(gè)符號(hào)是來自一個(gè)函數(shù)還是一個(gè)數(shù)組。所以,我們用一個(gè)main數(shù)組,騙過了編譯器。

          也就是說:編譯器把main數(shù)組當(dāng)成了main函數(shù),把main數(shù)組中的數(shù)據(jù)當(dāng)成了main函數(shù)的函數(shù)體指令。

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

          將main數(shù)組中的數(shù)值轉(zhuǎn)換成16進(jìn)制看看,按照一個(gè)int變量占4個(gè)字節(jié)對(duì)齊:

          再進(jìn)一步,使用反匯編引擎看看這段16進(jìn)制數(shù)據(jù)是什么指令?

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

          call $+5

          這是一條非常重要的指令,請(qǐng)記?。?strong style="font-weight: border;color: rgb(248,57,41);">call指令是在執(zhí)行函數(shù)調(diào)用,執(zhí)行call指令的時(shí)候,會(huì)將下一條指令的地址壓入線程的棧頂,用于函數(shù)返回時(shí)取出找到回去的路,那下一條是誰?就是下面的pop eax這條指令,所以執(zhí)行這個(gè)call指令時(shí),會(huì)把下面那個(gè)pop eax指令的地址壓入棧頂。

          再者,call后面的目標(biāo)地址是$+5,也就是這條call指令地址+5個(gè)字節(jié)的地方,同樣是下面那條pop eax指令的地址,所以call的目標(biāo)函數(shù)就是緊接著的下面pop eax指令開始的地方。

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

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

          pop ?eax

          注意,執(zhí)行到這里的時(shí)候,線程的棧頂存放的就是這條指令所在的位置,是上面那條call指令導(dǎo)致的結(jié)果。

          接著,pop eax,將棧頂存放的這個(gè)地址取出來,放到eax寄存器中?,F(xiàn)在eax中存放的就是當(dāng)前指令的內(nèi)存地址了。

          add ?eax, 13h

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

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

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

          mov ?edi, 400000h??這里同樣是在給printf函數(shù)傳參,這里和上面那條,一個(gè)通過堆棧傳參,一個(gè)通過寄存器傳參數(shù),是為了同時(shí)兼容Windows平臺(tái)和Linux x64平臺(tái)上的函數(shù)調(diào)用約定。

          而之所以傳遞的字符串地址是0x00400000,是因?yàn)閯偤?,這個(gè)數(shù)字是兩個(gè)平臺(tái)上可執(zhí)行文件加載的默認(rèn)基地址。

          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存儲(chǔ)的是main數(shù)組的最后一個(gè)格子的地址,這個(gè)格子里面存放的是printf函數(shù)的地址。

          于是,通過一個(gè)指針調(diào)用call,來調(diào)用printf,完成打印輸出。

          pop ?eax

          函數(shù)調(diào)用完了,得進(jìn)行堆棧平衡,前面?zhèn)鲄簵A?,這里就得彈出來。

          retn

          注意這個(gè)retn指令,retn指令和call指令對(duì)應(yīng),call用于調(diào)用函數(shù),將返回地址壓棧,而retn指令則將棧頂?shù)臄?shù)據(jù)彈出來作為返回地址,跳回去執(zhí)行。

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

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

          至于具體是誰在調(diào)用main函數(shù),這就不是這篇文章的重點(diǎn)了,屬于Linux和Windows上各自的C/C++運(yùn)行時(shí)庫CRT函數(shù)的范疇。

          到這里,你應(yīng)該就能明白,這個(gè)程序是如何運(yùn)行起來的,以及,為什么會(huì)有那樣的輸出信息。

          幾個(gè)注意事項(xiàng)

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

          總結(jié)

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

          后來,國內(nèi)一個(gè)大牛也原創(chuàng)了自己的版本,參考鏈接:

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

          不過,這個(gè)版本僅適用于Windows平臺(tái),我在此基礎(chǔ)之上,又改了現(xiàn)在這個(gè)版本,同時(shí)支持Windows和Linux平臺(tái)。

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

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

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

          歡迎評(píng)論區(qū)留言探討!

          往期推薦

          打錢!我的數(shù)據(jù)庫被黑客勒索了!

          黑客愛用的HOOK技術(shù)大揭秘!

          安全軟件群雄混戰(zhàn)史


          瀏覽 92
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧色一级黄色视频 | 国产九九九在线观看 | 免费在线观看黄片视频 | 小骚逼操死你 | 日本A在线看 |