IDAPython 讓你的生活更滋潤 – Part 3 and Part 4

wyzsk發表於2020-08-19
作者: 蒸米 · 2016/01/09 12:31

0x00 簡介


今天在網上看到平底鍋blog上Josh Grunzweig發表了一系列關於利用IDAPython分析malware的教程。感覺內容非常不錯,於是翻譯成中文與大家一起分享。

Part1 和 Part2 的譯文地址:
/papers/?id=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/

0x01 Part3 - 序


上一篇譯文中我們討論瞭如何利用Python來方便我們進行逆向,這一篇中我們將會介紹如何利用IDAPython來解決條件斷點問題。

當我們用IDA Pro進行除錯的時候,我們總想斷點在某個滿足某些條件的特殊地址上。舉兩個例子,第一個是我們只想將斷點下在某個函式被傳遞了指定的引數的情況下;第二個是我們想把斷點下在某個特定的庫被載入到我的虛擬機器的時候。今天我將會給大家介紹如何解決這些問題的方法。

0x02 Part3 – 背景介紹


研究員經常會對dll檔案進行分析。在很多情況下,這些dll會被其他的可執行檔案載入。一種解決方案是用IDA在選項中設定,確保IDA在每個庫被載入的時候都暫停下來。如圖所示:

p1

然而這種方式非常低效,經常需要研究人員手動的暫停和繼續:先判斷最新載入的庫是不是想除錯的那個,如果不是再繼續執行。如果能僅僅執行一條簡單的指令,然後坐等想要的檔案被載入進來的話,會讓我們的除錯工作更加愜意。

0x03 Part3 – 條件斷點


我們將用一個dd.dll的檔案作為例子進行講解。這個檔案在執行過程中被提取後,通常會被利用程式注入到另一個程式中。為了能夠在IDA中動態除錯這個檔案,我將會動態載入rundll32.exe這個儲存在system32目錄下的執行檔案,並且將dd.dll檔案作為引數(如圖2所示)。在給定匯出名的情況下,rundll32.exe檔案允許使用者載入一個指定的dll檔案。在這個樣本中,我對dll檔案中的Setting函式很有興趣,於是我將如下引數傳遞給IDA:

p2

下一步是當一個DLL檔案被載入進來後,找到正確的地方設定斷點。為了做到這一點,我先在偵錯程式設定中的”在庫被載入或解除安裝時暫停”的選項上打勾,然後我能夠看到有一個斷點被設定到了NtMapViewOfSection的後面一個指令。

p3

接下來我在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並觸發斷點。當執行的時候,我們能看到如下輸出:

p4

在這時候,我們可以設定我們期望的匯出函式為”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”這個函式名後會在這個函式名對應的函式上設定一個斷點。隨後繼續我們繼續執行偵錯程式,直到這個斷點被觸發。當我們執行的時候,發現偵錯程式暫停在了我們期望的地址上:

p5

0x04 Part3 – 總結


儘管設定條件斷點看起來是個很小的技術,但的確能節省分析人員大量的時間。一小段程式碼就能解決我們一遍一遍手動的工作,何樂而不為呢。

0x05 Part4 – 序


分析人員經常會遇到複雜度很高的程式碼,並且在動態執行過程中這些程式碼並不是顯而易見的。使用IDAPython,我們不光可以確定哪些指令被執行了,還可以確定這些指令被執行了多少次。

0x06 Part4 – 背景


為了這篇文章,我寫了一個簡單的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後,我們可以看到期望的迴圈和重定向的語句。如果我們不看底層程式碼(原始碼)的話,透過反彙編,我們依然可以知道會發生什麼。如圖所示:

p6

但是如果我們想要知道哪一塊程式碼會在執行時執行。我就需要IDAPython來完成這個挑戰了。

0x07 Part4 – 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()將會讓偵錯程式繼續單步執行。最後我們進入一個迴圈然後遍歷每個地址,直到我們收到程式結束的訊號為止。輸出結果如下圖所示:

p7

下一步就是取出和設定每一條執行的指令的地址的顏色。為了做到這一點,我們可以使用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)

執行上面的程式碼會讓指定行的指令變藍,如果指定行的指令被執行了多次,會讓藍色變得更深。

p8

我們可以使用接下的程式碼去除掉之前在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

當我們在我們的程式中執行這段程式碼的時候,我們能看到如下的變化:我們能看到所有被執行的彙編指令都被高亮了。就像下圖所示,指令執行的次數越多,顏色就會越深,這會讓我們很容易的理解程式執行的流程。

p9

0x08 Part4 – 總結


這篇blog介紹瞭如何利用IDAPython來給程式上色,這個技術能夠很好的幫助研究人員分析複雜的程式碼並節省大量的時間。

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章