今天在網上看到平底鍋blog上Josh Grunzweig發表了一系列關于利用IDAPython分析malware的教程。感覺內容非常不錯,于是翻譯成中文與大家一起分享。
Part1 和 Part2 的譯文地址:
http://drops.wooyun.org/papers/11849
Part3和Par4原文地址:
http://researchcenter.paloaltonetworks.com/2016/01/using-idapython-to-make-your-life-easier-part-3/
http://researchcenter.paloaltonetworks.com/2016/01/using-idapython-to-make-your-life-easier-part-4/
上一篇譯文中我們討論了如何利用Python來方便我們進行逆向,這一篇中我們將會介紹如何利用IDAPython來解決條件斷點問題。
當我們用IDA Pro進行調試的時候,我們總想斷點在某個滿足某些條件的特殊地址上。舉兩個例子,第一個是我們只想將斷點下在某個函數被傳遞了指定的參數的情況下;第二個是我們想把斷點下在某個特定的庫被加載到我的虛擬機的時候。今天我將會給大家介紹如何解決這些問題的方法。
研究員經常會對dll文件進行分析。在很多情況下,這些dll會被其他的可執行文件加載。一種解決方案是用IDA在選項中設置,確保IDA在每個庫被加載的時候都暫停下來。如圖所示:
然而這種方式非常低效,經常需要研究人員手動的暫停和繼續:先判斷最新加載的庫是不是想調試的那個,如果不是再繼續執行。如果能僅僅運行一條簡單的指令,然后坐等想要的文件被加載進來的話,會讓我們的調試工作更加愜意。
我們將用一個dd.dll的文件作為例子進行講解。這個文件在運行過程中被提取后,通常會被利用程序注入到另一個進程中。為了能夠在IDA中動態調試這個文件,我將會動態加載rundll32.exe這個保存在system32目錄下的執行文件,并且將dd.dll文件作為參數(如圖2所示)。在給定導出名的情況下,rundll32.exe文件允許用戶加載一個指定的dll文件。在這個樣本中,我對dll文件中的Setting函數很有興趣,于是我將如下參數傳遞給IDA:
下一步是當一個DLL文件被加載進來后,找到正確的地方設置斷點。為了做到這一點,我先在調試器設置中的”在庫被加載或卸載時暫停”的選項上打勾,然后我能夠看到有一個斷點被設置到了NtMapViewOfSection的后面一個指令。
接下來我在0x7C91ADFB處下了一個斷點。在我的代碼中,我使用add_bpt()
和enable_bpt()
去創建和激活這個斷點。
#!bash
'''
ntdll.dll:7C91ADF1 push 0FFFFFFFFh
ntdll.dll:7C91ADF3 push dword ptr [ebp-20h]
ntdll.dll:7C91ADF6 call near ptr ntdll_NtMapViewOfSection
ntdll.dll:7C91ADFB mov edi, eax
'''
address = 0x7C91ADFB # Just after NtMapViewOfSection
add_bpt(address, 0, BPT_SOFT)
enable_bpt(address, True)
這個時候,我們已經在0x7C91ADFB處下了斷點,并且調試器會在每個dll被加載的時候暫停。為了確保只有當”dd.dll”文件被加載的時候才暫停,我們必須創建一個條件斷點。條件斷點允許一個研究人員使用IDC或者Python代碼去判斷一個斷點是不是真的被觸發。如果返回值為真,那么斷點就會被觸發,否則將會被忽略。為了使用Python,我們先將Python設置為默認語言。然后,將我們的Python代碼保存在一個變量中,然后在SetBptCnd()中調用這個變量,這樣就能把我們的代碼變成斷點的條件判斷了。當條件被設置好后,我們讓調試器繼續運行,直到遇到一個暫停事件。偽代碼如下:
#!python
address = 0x7C91ADFB # Just after NtMapViewOfSection
RunPlugin("python", 3) # Python default programming
StartDebugger("","","");
dll = "dd.dll"
condition = """
for m in Modules():
if "%s".lower() in m.name.lower():
print "Breaking on", m.name.lower()
del_bpt(%d)
return True
return False
""" % (dll, address)
add_bpt(address, 0, BPT_SOFT)
enable_bpt(address, True)
SetBptCnd(address, condition)
continue_process()
GetDebuggerEvent(WFNE_SUSP, -1)
實際上用來使用的條件斷點的代碼如下:
#!python
for m in Modules():
if "dd.dll".lower() in m.name.lower():
print "Breaking on", m.name.lower()
del_bpt(0x7C91ADFB)
return True
return False
這個代碼會遍歷所有被加載進來的模塊,然后判斷”dd.dll”是不是被加載了進來,如果被加載了,一條調試信息將會被打印出來,并且會返回Ture并觸發斷點。當執行的時候,我們能看到如下輸出:
在這時候,我們可以設置我們期望的導出函數為”Setting”然后運行程序直到觸發斷點。為了能夠自動的完成這個任務,如下代碼將會被用到:
#!python
def get_names(base, size, desired_name):
current_address = base
while current_address <= base+size:
current_address = NextHead(current_address)
print hex(current_address)
if desired_name in Name(current_address):
return current_address
for m in Modules():
if 'dd.dll' in m.name.lower():
base = m.base
size = m.size
analyze_area(base, base+size)
setting = get_names(base, size, "Setting")
if setting:
add_bpt(setting, 0, BPT_SOFT)
enable_bpt(setting, True)
continue_process()
GetDebuggerEvent(WFNE_SUSP, -1)
這段代碼會遍歷所有加載的模塊來尋找”dd.dll”文件。當找到這個文件之后,我們會分析這個DLL文件并且遍歷代碼中的函數名。當我們找到”Setting”這個函數名后會在這個函數名對應的函數上設置一個斷點。隨后繼續我們繼續運行調試器,直到這個斷點被觸發。當我們執行的時候,發現調試器暫停在了我們期望的地址上:
盡管設置條件斷點看起來是個很小的技術,但的確能節省分析人員大量的時間。一小段代碼就能解決我們一遍一遍手動的工作,何樂而不為呢。
分析人員經常會遇到復雜度很高的代碼,并且在動態運行過程中這些代碼并不是顯而易見的。使用IDAPython,我們不光可以確定哪些指令被執行了,還可以確定這些指令被執行了多少次。
為了這篇文章,我寫了一個簡單的c程序。代碼如下:
#!cpp
#include "stdafx.h"
#include <stdlib.h>
#include <time.h>
int _tmain(int argc, _TCHAR* argv[])
{
char* start = "Running the program.";
printf("[+] %s\n", start);
char* loop_string = "Looping...";
srand (time(NULL));
int bool_value = rand() % 10 + 1;
printf("[+] %d selected.\n", bool_value);
if(bool_value > 5)
{
char* over_five = "Number over 5 selected. Looping 2 times.";
printf("[+] %s\n", over_five);
for(int x = 0; x < 2; x++)
printf("[+] %s\n", loop_string);
}
else
{
char* under_five = "Number under 5 selected. Looping 5 times.";
printf("[+] %s\n", under_five);
for(int x = 0; x < 5; x++)
printf("[+] %s\n", loop_string);
}
return 0;
}
當我們把這個文件載入IDA后,我們可以看到期望的循環和重定向的語句。如果我們不看底層代碼(源代碼)的話,通過反匯編,我們依然可以知道會發生什么。如圖所示:
但是如果我們想要知道哪一塊代碼會在運行時執行。我就需要IDAPython來完成這個挑戰了。
我們第一個解決的挑戰是能夠單步處理每一條指令。我們用下面的代碼完成這個任務(每一條執行的指令都會通過調試接口輸出):
#!python
RunTo(BeginEA())
event = GetDebuggerEvent(WFNE_SUSP, -1)
EnableTracing(TRACE_STEP, 1)
event = GetDebuggerEvent(WFNE_ANY|WFNE_CONT, -1)
while True:
event = GetDebuggerEvent(WFNE_ANY, -1)
addr = GetEventEa()
print "Debug: current address", hex(addr), "| debug event", hex(event)
if event <= 1: break
在上面的代碼中,我們先啟動調試器,然后利用Runto(BeginEA())執行到入口處的代碼。隨后的GetDebuggerEvent()函數調用將會在斷點觸發的時候進行等待。然后我們使用EnableTracing()函數啟動跟蹤。隨后的GetDebuggerEvent()將會讓調試器繼續單步執行。最后我們進入一個循環然后遍歷每個地址,直到我們收到進程結束的信號為止。輸出結果如下圖所示:
下一步就是取出和設置每一條執行的指令的地址的顏色。為了做到這一點,我們可以使用GetColor()和SetColor()函數。接下來的代碼可以得到目標行指令的顏色,然后判斷并設置這行指令的顏色。在這個例子中,我使用了四種不同深度的藍色。某行指令被執行的越多顏色就會越深(我鼓勵讀者自定義自己的偏好)。
#!python
def get_new_color(current_color):
colors = [0xffe699, 0xffcc33, 0xe6ac00, 0xb38600]
if current_color == 0xFFFFFF:
return colors[0]
if current_color in colors:
pos = colors.index(current_color)
if pos == len(colors)-1:
return colors[pos]
else:
return colors[pos+1]
return 0xFFFFFF
current_color = GetColor(addr, CIC_ITEM)
new_color = get_new_color(current_color)
SetColor(addr, CIC_ITEM, new_color)
執行上面的代碼會讓指定行的指令變藍,如果指定行的指令被執行了多次,會讓藍色變得更深。
我們可以使用接下的代碼去除掉之前在IDA Pro文件中的顏色。因為設置顏色為0xFFFFFF會讓顏色變成白色,并且去掉之前設置的所有顏色。
#!python
heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA()))
for i in heads:
SetColor(i, CIC_ITEM, 0xFFFFFF)
將所有的代碼放在一起我們可以得到如下結果:
#!python
heads = Heads(SegStart(ScreenEA()), SegEnd(ScreenEA()))
for i in heads:
SetColor(i, CIC_ITEM, 0xFFFFFF)
def get_new_color(current_color):
colors = [0xffe699, 0xffcc33, 0xe6ac00, 0xb38600]
if current_color == 0xFFFFFF:
return colors[0]
if current_color in colors:
pos = colors.index(current_color)
if pos == len(colors)-1:
return colors[pos]
else:
return colors[pos+1]
return 0xFFFFFF
RunTo(BeginEA())
event = GetDebuggerEvent(WFNE_SUSP, -1)
EnableTracing(TRACE_STEP, 1)
event = GetDebuggerEvent(WFNE_ANY|WFNE_CONT, -1)
while True:
event = GetDebuggerEvent(WFNE_ANY, -1)
addr = GetEventEa()
current_color = GetColor(addr, CIC_ITEM)
new_color = get_new_color(current_color)
SetColor(addr, CIC_ITEM, new_color)
if event <= 1: break
當我們在我們的程序中執行這段代碼的時候,我們能看到如下的變化:我們能看到所有被執行的匯編指令都被高亮了。就像下圖所示,指令執行的次數越多,顏色就會越深,這會讓我們很容易的理解程序執行的流程。
這篇blog介紹了如何利用IDAPython來給程序上色,這個技術能夠很好的幫助研究人員分析復雜的代碼并節省大量的時間。