原始程式大小為6kb,經過5次混淆後,大小變為66kb.
1.有分支跳轉改成無分支跳轉。
在逆向分析時主要是通過條件跳轉來確定函式的流程,根據跳轉的目標地址來確定這是一個分支還是一個迴圈。如果將所有條件跳轉改成無分支跳轉,那麼程式只有在動態執行時,才能確定流程。這大大提高了逆向分析的難度。另外,ida在進行還原偽c程式碼時,遇到無分支跳轉,會進行截斷,導致後面指令無法還原偽c程式碼。這對靠f5吃飯的人說就gameover了。因為此時已經控制了程式碼流程, 如果在後面加一些垃圾指令,會導致後面的程式碼全部識別錯亂,只有執行時才確定要跳到哪個位置。
另外對 jmp imm, call imm和call [imm](一般這種間接call都是api呼叫) 也改成無分支跳轉。
本來沒打算對這類指令進行處理,但是發現效果還挺好,乾脆就加上吧。因為jmp imm也可以作為判斷分支或者迴圈的依據,而call也是一個比較關鍵的指令,尤其是call api,在逆向分析時,通過觀察api的前後程式碼來確定程式碼到底在幹嘛。這樣一處理,你靜態分析時看不到任何條件跳轉,看不到任何call呼叫(除了call reg,當然了,靜態分析你也看不出來call reg)。
另外對條件跳轉和call這類指令的下一條指令加一些垃圾程式碼,會導致後面的整段指令識別錯誤。也會給別人留下個坑,比如一般人想步過這個函式呼叫,會在函式的下一條指令地址處下斷點,但這條指令實際上永遠不會執行。
實現思路:
1.將跳轉目標地址儲存到一個地址陣列表裡面,在建一個索引表記錄地址表的索引,在對索引進行隨機加密。執行時根據條件碼得到正確的索引表地址,在對索引進行解密,得到正確的目標地址偏移。
程式碼變形前:
程式碼變形後:
兩幅圖對比發現call memset 變成了一大串指令,最後以ret結尾。這一大串做了一個事情,去索引表找到地址表的索引,在根據這個索引去地址表找到正確偏移值,加上基地址,得到目標地址。同時會把返回地址壓棧,實際上返回地址是可以隨意更改的,我是按照程式碼塊進行處理的,隨意加一些垃圾指令, 把後面的程式碼塊往下移動一點就好了,這個自己改,另外索引加密這個我註釋掉了,去掉就可以。大家發現這裡先push了2個暫存器,在這裡偷了點懶,實際上最好應該是用反彙編引擎掃一下當前哪些暫存器是被佔用的,再從沒被佔用的暫存器中隨機分配2個暫存器。如果沒有可利用的暫存器,在這麼做。
call memset是一個立即數,再來看下scanf (call [imm])
程式碼變形前:
程式碼變形後:
再來看下f5後的程式碼:
程式碼變形前:
程式碼變形後:
從上圖可以發現變形後,ida根本就沒有識別完整個函式,只識別了一點點。ida遇到無條件跳轉會截斷當前函式。
對地址常量包括字串常量進行加密 :
在進行逆向分析時,很多人第一反應會去看看有沒有一些敏感字串,根據ida的交叉引用來確定目標地址。
另外一些地址值可能也比較關鍵,比如回撥函式。而這類常量賦值一般都是mov指令和push指令,對 mov imm,push imm這類指令的立即數做隨機加密,就可以起到一個保護的作用。
加密前:
加密後:
本文由看雪論壇 淡淡的熒光 原創 轉載請註明來自看雪社群