操屁眼的视频在线免费看,日本在线综合一区二区,久久在线观看免费视频,欧美日韩精品久久综

新聞資訊

    什么是“倒著跑”的程序?

    正常的程序都是正著跑的:CPU 里有一個程序計數器(PC),用來記錄當前正在執行的指令的地址。執行完 PC 處的指令后,CPU 會接著執行 PC+1 處的指令。

    而倒著跑的程序指的是:執行完 PC 處的指令后,程序回過頭去執行 PC-1 處的指令。如果你反匯編這個程序,你會看到程序中機器指令的序列也是完全倒過來的,比如程序看起來會先調用函數,后傳參。

    等等,這樣的程序是真實存在的嗎?

    是的,而且我真的寫了一個這樣的 x86-64 程序,還把它出成了一道 CTF 逆向題!

    你可以下載這道題的附件:

    然后反匯編看一下:

    IDA 反匯編的結果

    正著看確實狗屁不通,反過來理解卻合情合理。最主要的是,這個 Linux 程序真的能在 x86-64 CPU 上跑起來!你可以自己找臺 20.04 的服務器嘗試一下。

    這個程序真的能跑

    那么,這個程序是怎么實現的呢?

    緣起

    某天刷知乎的時候看到了這個回答:

    具體的各種爭奪手法差不多可以寫本書了,這里就舉一個有趣的加密手法:

    利用int3讓內存里的程序倒序執行。或者說倒序才是正確的打開方式。

    本來第n條指令執行完畢之后應該執行第n+1條指令,加密程序利用int3在執行完第n條指令之后,把命令指針改成n-1,從而實現倒序執行。

    靜態分析,內存里的代碼簡直狗屁不通。

    直接用二進制寫程序_程序2進制轉10進制_用python寫搶票程序

    動態跟蹤你就得去截int3。可你一篡改int3,程序就沒法倒序執行了,結果就更是狗屁不通了,從而達成防止破解的目的。

    我覺得很有意思,剛好當時在籌備下一屆 ,于是決定把這個思路出成一道逆向,雖然我自己完全不會逆向(遺憾)。

    但讓一個合法的 x86-64 程序倒序執行,并不是一個簡單的事情,我們需要考慮很多問題,包括:

    好在思考了一段時間,外加一通 STFW 之后,我還是找到了一種可行的實現方法。

    實現思路倒序執行

    如何倒序執行程序呢?

    首先,負責執行程序的是 CPU。而據我所知,世界上的絕大多數 CPU,都只會按照 PC 遞增方向執行代碼(控制轉移指令除外),同時也不提供更改代碼執行方向的配置選項。

    不過并不是說這個事是解決不了的,或者說,既然 CPU 本身沒辦法解決這個事,我們也許可以換種思路:

    讓 CPU 單步執行代碼。使用軟件接管單步執行的流程。在一次單步執行結束后,修改 PC,使其指向上一條指令而非下一條。繼續單步執行。

    好,之前的問題解決了,但我們又多出三個問題:

    如何讓 CPU 單步執行代碼?如何使用軟件接管單步執行的流程?如何修改 PC 使其指向上一條指令?

    我們的目標是寫出一個倒著跑的 x86-64 程序,而在 x86 上,CPU 恰好為我們提供了一種單步執行的機制:trap flag,確切的說是 flags// 寄存器的第 8 位。

    8086 的 flags 寄存器,第 8 位為 TF

    在設置 trap flag 之后,CPU 會進入單步模式,每執行一條指令觸發一次異常,之后進入異常處理程序。而運行于 CPU 之上的操作系統,會根據異常原因識別出這一行為,并且通知當前執行的進程發生了 trap。

    在 Linux(或者說 POSIX)中,這種通知機制由 機制實現。發生 trap 時,程序會收到 信號。如需自行處理該信號,程序可注冊對應的 。

    不過,一般情況下, 會由調試器觸發,比如你正在單步調試。此時程序內是沒有任何 處理這個信號的,調試器會使用 來處理被調試進程的 。

    所以,為了實現(至少是部分代碼的)倒序執行,我們可以在程序啟動后設置 的 ,然后設置 trap flag,之后在 中處理更新 PC 的邏輯。

    那么,如何在 中,將 PC 指向上一條指令呢?

    用python寫搶票程序_直接用二進制寫程序_程序2進制轉10進制

    仔細閱讀 Linux 手冊中和 相關的部分,例如 ,其中提到 具備三個參數,其中第三個參數 的描述如下:

    This is a to a , cast to void *.

    The to by this field that was saved on the user-space stack by the ; for , see (2). about the can be found in (3) and (7).

    , the doesn't make any use of the third .

    也就是說,我們可以借助 ,修改由內核保存的,和該進程相關的上下文(),其中自然包括了所有寄存器的值。在 x86-64 中,我們只需要修改 rip( )寄存器,即可控制程序的 PC,很簡單吧!

    然而,上帝這個 byd 在為你打開一扇窗之后,就必然會關閉一扇門。在 x86-64 下,我們還需要考慮另一個問題:作為古老的 CISC 架構,x86-64 的指令是變長的,我們沒辦法簡單地通過“將 rip 的值減去固定數值”的方式,讓程序倒序執行。更蛋疼的是,x86-64 的指令編碼是一種前綴碼,你只能通過從前往后掃描來確定指令的邊界,從后往前掃是不行的。

    x86 的變長指令編碼

    也許這個問題有很多種解決方式,比如往程序里嗯塞一個 x86-64 (如 iced),或者利用 CPU 來幫我們完成一部分譯碼的任務(其實我沒想好這樣要怎么搞)。但在權衡實現的復雜性之后,我還是決定用一種非常簡單粗暴的方式來解決這個問題:打表。

    當然,為了進一步簡化實現,縮小程序的體積,我們可以把這個表壓縮成位圖():也就是說,指定一串數據,其中的每一位對應程序代碼段的每一個字節。如果某個字節是某條指令的開頭,就將位圖里對應位設為 1,否則設為 0。這個位圖可以提前用其他方式生成,然后嵌入到程序中。

    在需要更新 rip 的時候,我們可以根據 rip 目前的值,找到位圖中的對應位。需要注意的是,在設置 trap flag 的情況下,CPU 執行完一條指令之后,會將 rip 指向下一條指令,然后再觸發異常,進入 。所以我們需要在位圖中找到 rip 對應指令之前第二條指令的開頭,并將 rip 設為對應的地址。

    處理控制流

    經過一通折騰,我們終于找到了一種可行的方法,將整個順序執行的指令流變成倒序執行。不過,現實世界的程序中,幾乎不可能只有順序執行這一種情況,還會出現分支、跳轉、函數調用/返回等各類控制流轉移的情況。怎么處理這類情況呢?

    其實我們剛剛提出的處理方式已經可以完全兼容控制流了,只需要做出一些小小的改動。考慮以下情況:

      inst1
      inst2
      jump label1
      inst3
    

    直接用二進制寫程序_用python寫搶票程序_程序2進制轉10進制

    inst4 label1: inst5 inst6

    如果我們把它簡單地連 label 一起倒過來:

      inst6
      inst5
    label1:
      inst4
      inst3
      jump label1
      inst2
      inst1

    然后按照我們剛剛的思路,從 inst1 開始倒序執行,你會發現:執行完 jump 之后,rip 會指向 inst4。接著進入 , 會將 rip 前移兩條指令,也就是指向 inst6,然后開始執行。

    但程序原本的執行流程應該是,遇到 jump 之后,接著從 inst5 開始執行。為了實現這樣的效果,我們只需要把 label 往后挪一個指令:

      inst6
      inst5
      inst4
    label1:
    

    程序2進制轉10進制_直接用二進制寫程序_用python寫搶票程序

    inst3 jump label1 inst2 inst1

    這樣,倒序執行時的執行流就和順序時完全一致了。這種處理方式對于分支,函數調用等等其他控制流指令也同理。不過需要注意以下的情況:

    func:
      inst1
      inst2
      inst3
      call func

    倒過來之后,我們需要往開頭(或者說結尾)加兩條別的指令,然后才能把 func label 挪下去:

      call func
      inst3
      inst2
      inst1
      nop
    func:
      nop

    當然如果沒出錯直接用二進制寫程序,這兩條指令永遠都不會被執行到,所以其實把它們換成任何指令都是可以的。

    直接用二進制寫程序_程序2進制轉10進制_用python寫搶票程序

    自動化

    首先,很顯然,人肉寫匯編的話,按照上面我們討論的思路手搓一個倒序執行的程序,是完全可行的。

    然而,作為一個能寫腳本就決不躬親的極致懶狗,讓我手搓還不如把我揚了——而且這樣一點也不酷。跑個自動化腳本,啪的一下,很快啊,你寫的源代碼直接就變成一個倒著執行的二進制了,這才符合我對計算機的想象,科技并帶著趣味。

    所以我是怎么實現自動化的呢?首先我決定用 C 來編寫這樣的程序——其實用什么語言無所謂了,不過 C 會省心很多。我將前文提到的若干內容封裝成了頭文件,包括:

    任何需要倒序執行的程序,只需 上述頭文件即可,非常省事。

    接下來是負責把程序生成的機器指令倒過來的部分,這部分我選擇用以下方法實現:

    首先 gcc -S 輸出 C 程序對應的匯編。然后寫個腳本,按照前文提到的邏輯把匯編倒過來,同時修改所有 label 的位置。指定使用修改過的鏈接器腳本鏈接這個匯編程序,以便 C 里判斷代碼段范圍的邏輯生效。

    最后,我們需要生成 ,然后將其嵌入程序。這里我偷個懶,用 掃一遍剛剛得到的二進制,然后用腳本簡單解析一下輸出,生成一個 。最后用 定位二進制內 符號的偏移量,把 寫到二進制文件即可。

    相關工具及用法

    好消息:我開源了剛剛提到的自動化腳本。

    如果你也像我一樣閑,希望搞一個能倒著跑的 x86-64 Linux 程序的話,可以直接參考這套實現:

    具體來說,你需要:

    下載 rev.h。寫一個普通的 C 語言程序。注意這個實現暫不支持多文件(當然你感興趣的話可以改改),所以你必須把代碼寫在一個文件里。在程序的開頭 rev.h。下載 .py,執行 ./.py 你寫的C文件 編譯程序。程序會保存在當前目錄,運行一下看看吧!

    說實話這個腳本寫的有點直接用二進制寫程序,里面依賴了很多 gcc、ld、 和 的特性。而且,我只測試了給 出題用的那個程序(以及其他簡單的例子),對此這個腳本是可以正常工作的,但我不保證對于其他的 C 程序,這個腳本還能生成一個能跑的 ELF。

    為了避免這個腳本在你的環境上崩潰,我還提供了一個 。你可以執行(請確保你的 CPU 是 x86-64 架構):

    docker build -f Dockerfile路徑 -t rev

    然后在生成的鏡像里跑這個腳本,或者測試腳本生成的程序。

    你可以用它做什么?

    玩。

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有