偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

針對常見混淆技術的反制措施

安全 應用安全
在這篇文章中,我們將仔細研究兩種常見的混淆技術,以了解它們是如何工作的,并弄清楚如何去混淆。

現(xiàn)代軟件經(jīng)常將混淆技術作為其反篡改策略的一部分,以防止黑客逆向分析軟件的關鍵組件。他們經(jīng)常使用多種混淆技術來抵御黑客的攻擊,這有點像滾雪球:隨著雪層的增多,軟件規(guī)模也隨之變大,使其逆向分析難度隨之提高。

在這篇文章中,我們將仔細研究兩種常見的混淆技術,以了解它們是如何工作的,并弄清楚如何去混淆。

概述

這里,我們將研究以下混淆技術:

基于IAT導入表的混淆技術

基于控制流的混淆技術

基于IAT導入表的混淆技術

在深入介紹基于IAT導入表的混淆方法之前,先讓我解釋一下導入表到底是什么。

什么是導入函數(shù)?

當進行逆向分析時,需要弄清楚的第一件事,就是它如何調(diào)用操作系統(tǒng)的函數(shù)。在我們的例子中,我們將重點關注Windows 10系統(tǒng),因為大多數(shù)視頻游戲只能在Windows系統(tǒng)上運行。無論如何,對于那些還不知道的人來說,Windows提供了一系列重要的動態(tài)鏈接庫(DLL)文件,幾乎每個Windows可執(zhí)行文件都會用到這些庫文件。這些DLL文件中保存了許多函數(shù),可以供Windows可執(zhí)行文件“導入”,使其可以加載和執(zhí)行給定DLL中的函數(shù)。

它們?yōu)楹稳绱酥匾?

例如,Ntdll.dll庫負責幾乎所有與內(nèi)存有關的功能,如打開一個進程的句柄(NtOpenProcess),分配一個內(nèi)存頁(NtVirtualAlloc,NtVirtualAllocEx),查詢內(nèi)存頁(NtVirtualQuery,NtVirtualQueryEx),等等。

另一個重要的DLL庫是ws2_32.dll,它通過以下函數(shù)處理各種網(wǎng)絡活動:

Socket
Connect / WSAConnect
Send / WSASend
SendTo / WSASendTo
Recv / WSARecv
RecvFrom / WSARecvFrom

現(xiàn)在讀者可能會問,知道這些有什么意義呢?好吧,如果您把一個二進制文件扔到像IDA這樣的反匯編器中(我通常會做的第一件事),就是檢查所有導入的函數(shù),以便對二進制文件的功能有一個大致的了解。例如,當ws2_32.dll存在于導入表中時,表明該二進制文件可能會連接到Internet。

現(xiàn)在,我們可能想要進行更深入的研究,并考察使用了哪些ws2_32.dll函數(shù)。如果我們使用Socket函數(shù)并找出它的調(diào)用位置,我們就可以檢查它的參數(shù),這樣,我們就可以通過搜索引擎查找相應的函數(shù)名,從而輕松地找出它所使用的協(xié)議和類型。

注意:IDA已自動向反匯編代碼中添加了注釋。

經(jīng)過混淆處理的導入表

無論如何,這些Windows函數(shù)能提供相當多的信息,因為它們是有據(jù)可查的函數(shù)。因此,攻擊者希望能夠把這些函數(shù)藏起來,以掩蓋正在發(fā)生的事情。

我們在反匯編器中看到的所有這些導入函數(shù)都是從導入地址表(IAT)加載的,該表在可執(zhí)行文件的PE頭文件中的某個地方被引用。一些惡意軟件/游戲通常試圖通過不直接指向DLL函數(shù)來隱藏這些導入地址。相反,他們可能會使用一個蹦床或迂回函數(shù)。

考察我們的示例

在這個例子中,我們使用的是一種蹦床式混淆技術,具體如下所示:

下面的地址0x7FF7D7F9B000引用了我們的函數(shù)0x19AA1040FE1,盡管看起來完全不是這么回事。您可能認為這是垃圾代碼,但仔細看看,您會發(fā)現(xiàn)并非如此。

請仔細查看前兩個指令:前面的指令是mov rax, FFFF8000056C10A1,后面的指令是jmp 19AA1040738,后面的都是垃圾指令。不管怎樣,讓我們跟隨跳轉(zhuǎn)指令,看看它會跳到哪里:

看,又是4個有效的指令,這次是一個異或指令和兩個加法指令,然后是另一個跳轉(zhuǎn)指令。讓我們把這個過程再重復幾遍...

最后,我們來到jmp rax指令!需要注意的是,所有的XOR、SUB和ADD指令都是在Rax寄存器上執(zhí)行的,這意味著它可能包含導入函數(shù)的實際指針。下面,讓我們算算看。

實際上,在經(jīng)過數(shù)學運算之后,我們得到了指向advapi32.regopenkeyexa的指針!

現(xiàn)在,我們所要做的就是重復幾百次運算,從而徹底消除針對IAT導入表的混淆處理。

基于IAT的自動去混淆處理

我想,沒有人喜歡用計算器手工重復上述過程,做一次已經(jīng)很煩了。從現(xiàn)在開始,我們將使用C#實現(xiàn)自動計算。正如您可能已經(jīng)看到的,我們只需要處理在同一個寄存器上執(zhí)行的ADD、SUB和XOR操作。原因是Rax被用作返回地址,而諸如Rcx、Rdx、R8、R9和其他寄存器對于被調(diào)用方來說是不安全的,并且可能與調(diào)用約定沖突。這意味著,我們甚至不需要使用反匯編器,因為我們可以很輕松地區(qū)分這些指令,這要歸功于涉及的寄存器和操作碼寥寥無幾。

到此為止,我們已經(jīng)詳細解釋了混淆處理技術。接下來,大家不妨以Unsnowman項目中的importfix.cs為例,來了解與去混淆處理相關的代碼。

基于控制流的混淆技術

在逆向分析二進制文件時,另一個有價值的信息來源是匯編指令本身。對于人類來說,它們可能難以理解,但對于像IDA這樣的反編譯器來說,我們只需按下F5鍵,IDA就會生成我們?nèi)祟惪梢岳斫獾膫未a。

混淆實際指令的一個簡單方法,是組合使用垃圾代碼和不透明分支(即該分支條件總是為不成立,也就是說,該分支用于也不會被執(zhí)行)。這意味著:把垃圾代碼放在一個分支指令之后。訣竅在于,我們可以使用條件轉(zhuǎn)移,但是,要確保條件永遠為真,這樣分支就會一直被執(zhí)行。反匯編器不知道的是,條件跳轉(zhuǎn)在運行時總是為真,這使得它相信條件跳轉(zhuǎn)的兩個分支都可以在運行時到達。

好吧,如果還不太明白的話,可以借助下面的截圖來加深理解。第一張截圖顯示的是落到另一條指令中的jbe。

注意:用紅色標記的字節(jié)是垃圾代碼。

現(xiàn)在仔細看看下面的第二張圖片,我在這里所做的只是NOP最后一條指令的兩個字節(jié),以便讓IDA顯示隱藏在and [rdx+24448B48h], bh指令后面的指令。

我們也可以用無條件跳轉(zhuǎn)來修補條件跳轉(zhuǎn),以確保IDA不會再次上當。

在我們繼續(xù)之前,我想展示最后一個例子,因為前面的例子太簡單了。當我們將這些實現(xiàn)混淆處理的跳轉(zhuǎn)鏈接起來時,事情就變得復雜起來,具體如下圖所示。

然而,這張圖只顯示了它在控制流方面造成的混亂,但想象一下,當IDA竭盡全力根據(jù)垃圾指令創(chuàng)建這張圖時,我的CPU是多么的痛苦。

現(xiàn)在,您可能想知道去混淆后的函數(shù)到底是什么樣子的,別急,請看下圖!

看到我在左邊畫的那個藍色小箭頭了嗎?右邊顯示的就是這部分內(nèi)容的放大版本。現(xiàn)在看一下右邊,在函數(shù)的一小部分中就有七個去混淆的跳轉(zhuǎn)。想象一下,以手動或半自動方式去混淆得需要多少時間。實際上,就算用IDA腳本手工完成這個過程,也花了我40分鐘……這還只是處理了一個函數(shù)。設想一下,為了找到真正要找的東西,還得需要處理多少其他的函數(shù)呢?!

基于控制流的自動去混淆技術

好了,現(xiàn)在我們已經(jīng)考察了基于控制流的去混淆原理,接下來,我們將對這個過程實現(xiàn)自動化。正如我之前提到的,我們曾經(jīng)用IDA腳本來修補無條件跳轉(zhuǎn)指令,并將垃圾指令替換為NOP指令。

然而,這個去混淆過程還是花了我40分鐘,因為識別不透明的分支非常費勁。那么,我們該如何解決這個問題呢?大家可能認為應該檢查每一個條件跳轉(zhuǎn)指令,并檢查它是否是不透明的,如果是的話,就用NOP替換它,然后重復上述過程,對吧?錯了!

讓我告訴你一個秘密,我們并不關心什么是不透明的,或諸如此類的事情。我真正關心的是,當我按下F5鍵時,IDA能否返回反編譯好的代碼——只要這些經(jīng)過混淆的跳轉(zhuǎn)指令導致垃圾指令與實際的匯編指令發(fā)生沖突,這種情況就不會發(fā)生。

但這是否意味著我們需要弄清楚一個條件跳轉(zhuǎn)是否是不透明的呢?不,我們只需檢查跳轉(zhuǎn)操作是否與現(xiàn)有的指令相沖突,如果是的話,就對這個指令進行相應的修改,就像我們第一個例子中看到的那樣。

DeFlow去混淆算法

現(xiàn)在,我們知道了如何解決這個問題,下面,我們開始深入研究本人想出的算法,以便對用這種混淆技術處理的內(nèi)容進行去混淆。

  var disasm = new Disassembler(buffer, address - base); // NOTE: base = BaseAddress + .text offset

foreach(var insn in disasm.Disassemble())
ulong target = 0;
ulong lastAddrStart
bool isJmp = true;

switch(insn.Mnemonic)
// Stop analysing when we encounter a invalid or return instruction while we have no lastTarget
case ud_mnemonic_code.Invalid:
case ud_mnemonic_code.Ret:
if(lastTarget == 0)
return newChunks; // Only accept when no lastTarget as we may be looking at junk code
break;
case ud_mnemonic_code.ConditionalJump: // all conditional jumps
if(lastTarget == 0)
target = calcTargetJump(insn); // Helper to extract jump location from instruction

if(!isInRange(target)) // Helper to see if target address is located in our Buffer
isJmp = false;
break;

// Check if instruction is bigger then 2, if so it wont be obfuscated but we
// do want to analyse the target location
if(insn.Length > 2)
isJmp = false;
newChunks.Add(target);
break;
else
isJmp = false; // Do not this conditional jump accept while we already
// have a target (might be looking at junk code)
break;
case ud_mnemonic_code.UnconditionalJump:
case ud_mnemonic_code.Call:
if(lastTarget == 0)
ulong newAddress = calcTargetJump(insn); // Helper to extract jump location from instruction

if(!isInRange(newAddress))
isJmp = false;
break;

// Add target and next instruction IF not JMP (CALL does return, JMP not)
if(insn.Mnemonic == ud_mnemonic_code.Call)
newChunks.Add(address + insn.PC);

// Add instruction target for further analyses
newChunks.Add(newAddress);
return newChunks;
break;


// quick mafs
ulong location = (address+insn.Offset);
stepsLeft = (int)(lastTarget - location); // Only valid if we have a lastTarget!

// Setup a new target if current instruction is conditional jump while there is no lastTarget
if(lastTarget == 0 && isJmp)
lastBranch = loction;
lastBranchSize = insn.Length;
lastTarget = target;
else if (stepsLeft <= 0 && lastTarget != 0)
// if stepsLeft isn't zero then our lastTarget is located slighlt above us,
// meaning that we are partly located inside the previous instruction and thus we are hidden (obfuscated)
if(stepsLeft != 0)
int count = lastTarget = lastBranch; // calculate how much bytes we are in the next instruction
if(count > 0)
// making sure we are a positive jump
int bufferOffset = lastBranch - base; // subtract base from out address so we can write to our local buffer

// NOP slide everything except our own instruction
if(int i = 0; i < count - lastBranchSize; i++)
buffer[bufferOffset + lastBranchSize + i] = isNegative ? 0x90 : 0xCC; // We use NOP for negative jumps
// and int3 for positive

if(!isNegative)
buffer[bufferOffset] = 0xEB; // Force unconditional Jump

// add next instruction for analyses and exit current analysis
newChunks.Add(lastTarget);
return newChunks;
else
// we are a negative jump, set 63th bit to indicate negative jump
lastTarget = |= 1 << 63;

// add target to analyser and exit current analysis
newChunks.Add(lastTarget);
return newChunks;
else
// stepsLeft was zero, meaning there is no collision
// add both target address and next instruction address so we can exit current analysis
newChunks.Add(lastBranch + lastBranchSize);
newChunks.Add(lastTarget);
return newChunks;

return newChunks;

注意:這里顯示是偽代碼,并且我知道它無法正常運行! (真的)

代碼很長,是吧?同時,它比基于IAT導入表的去混淆處理更難理解,因為我們使用了一個實際的反匯編庫來獲得每個指令的大小和助記符。使用反匯編器幾乎是必須的,因為我們還必須弄清楚一條指令是否與其他指令相沖突。

偽代碼中提供了大量的注釋,可以幫助大家理解其中的工作原理。

關于DeFlow算法的深入解釋

主函數(shù)在遞歸調(diào)用DeflowChunk進行線性反匯編時,會跟蹤已經(jīng)發(fā)現(xiàn)的塊。對新發(fā)現(xiàn)的塊的跟蹤是通過列表和循環(huán)完成的:由于在一個塊中可能執(zhí)行大量的分支指令,所以可能觸發(fā)StackOverflow。

DeflowChunk將首先檢查是否遇到給定的分支指令,如果是的話,則執(zhí)行以下操作之一:

  • Ret——如果沒有設置lastTarget,則停止
  • Invalid——如果沒有設置lastTarget,則停止
  • ConditionalJump——如果在我們的緩沖區(qū)范圍內(nèi),則計算目標地址并跟蹤
  • UnconditionalJump——如果在我們的緩沖區(qū)范圍內(nèi),則計算目標地址并保存以供進一步分析
  • Call——如果在我們的緩沖區(qū)范圍內(nèi),計算目標地址并保存以供進一步分析

如果我們沒有設置lastTarget,將檢查當前指令是否是在緩沖區(qū)范圍內(nèi)跳轉(zhuǎn)的ConditionalJump(isJmp標志),如果是的話將lastTarget設置為ConditionalJump的目標。

一旦我們獲得了符合條件的lastTarget,就用當前的指令指針減去lastTarget,從而計算出還需要反匯編多少字節(jié)(stepsLeft)。

在計算出stepsLeft之后,需要檢查該值是否等于零。如果該值大于零,我們將繼續(xù)線性反匯編。

當stepsLeft小于零時,表示匯編代碼與下一條指令發(fā)生了沖突。這很可能意味著負責設置lastTarget的最后一個ConditionalJump是一個不透明的條件,這意味著我們當前的塊很可能永遠不會被執(zhí)行,而是用來掩蓋后面的幾條合法的匯編指令。

我們可以通過將ConditionalJump的第一個字節(jié)修改為0xEB,使其成為UnconditionalJump,從而修復該問題。為了進一步掃清障礙,我們還修改了最后一個ConditionalJump和lastTarget之間的所有字節(jié)。

然后,對于在線性反匯編過程中發(fā)現(xiàn)的每個調(diào)用或條件跳轉(zhuǎn)操作,都進行相應的處理。

小結(jié)

不僅是惡意軟件,像視頻游戲這樣的合法軟件也傾向于使用這類混淆技術來盡可能多地隱藏有價值的信息,希望能防止軟件被逆向工程。然而,正如您所看到的,我們已經(jīng)成功地解開了這兩種技術的神秘面紗,并能夠揭示所有隱藏的信息。

無論如何,我們?nèi)匀豢梢缘贸鼋Y(jié)論:這些混淆技術極大地提高了逆向分析的難度,這是一個很好的方法,在一定程度上可以阻止軟件被逆向工程。最重要的是,Deflow算法本身需要幾分鐘/小時(取決于文件大小),就能消除混淆技術對二進制文件的完整控制流造成的影響。

本文翻譯自:https://ferib.dev/blog.php?l=post/Reversing_Common_Obfuscation_Techniques如若轉(zhuǎn)載,請注明原文地址

責任編輯:武曉燕 來源: 嘶吼網(wǎng)
相關推薦

2023-07-14 16:10:09

惡意軟件

2023-02-20 14:31:11

2018-05-04 10:53:14

2012-04-13 13:24:36

2021-07-01 18:54:04

無人機反制偵測

2021-01-12 09:39:17

算法互聯(lián)網(wǎng)技術

2010-10-14 11:37:24

無線LAN技術

2023-08-18 11:29:56

2009-06-16 16:17:35

2011-03-15 17:35:45

2022-09-07 14:22:57

勒索軟件企業(yè)

2017-07-14 16:28:21

2023-07-14 14:25:00

Python語言錯誤

2010-01-13 10:22:27

2009-12-28 14:04:44

ADO技術

2022-05-12 13:20:32

GoogleAndroid 應用訂閱

2020-05-18 09:50:17

華為禁令技術

2022-06-02 16:22:54

穩(wěn)定幣安全金融

2013-01-29 10:42:57

2011-07-27 18:36:16

點贊
收藏

51CTO技術棧公眾號