<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 12.3 IDAPyEmu 我們的第一個例子就是在 IDA Pro 分析程序的時候,使用 PyEmu 仿真一次簡單的函數 調用。這次實驗的程序就是 addnum.exe,主要功能就是從命令行中接收兩個參數,然后相 加,再輸出結果,代碼使用 C++ 編寫,可從 [http://www.nostarch.com/ghpython.htm](http://www.nostarch.com/ghpython.htm) 下載。 ``` /*addnum.cpp*/ #include <stdlib.h> #include <stdio.h> #include <windows.h> int add_number( int num1, int num2 ) { int sum; sum = num1 + num2; return sum; } int main(int argc, char* argv[]) { int num1, num2; int return_value; if( argc < 2 ) { printf("You need to enter two numbers to add.\n"); printf("addnum.exe num1 num2\n"); return 0; } num1 = atoi(argv[1]); num2 = atoi(argv[2]); return_value = add_number( num1, num2 ); printf("Sum of %d + %d = %d",num1, num2, return_value ); return 0; } ``` 程序將命令行傳入的參數轉換成整數,然后調用add_number函數相加。我們將 add_number 函數作為我們的仿真對象,因為它夠簡單而且結果也很容易驗證,作為我們使 用 PyEmu 的起點是個不二選擇。 在深入 PyEmu 使用之前,讓我們看看 add_number 的反匯編代碼。 ``` var_4= dword ptr -4 # sum variable arg_0= dword ptr 8 # int num1 arg_4= dword ptr 0Ch # int num2 push ebp mov ebp, esp push ecx mov eax, [ebp+arg_0] add eax, [ebp+arg_4] mov [ebp+var_4], eax mov eax, [ebp+var_4] mov esp, ebp pop ebp retn ``` Listing 12-1: add_number 的反匯編代碼 var_4,arg_0,arg_4 分別是參數在棧中的位置,從 C++的反匯編代碼中可以清楚的看 出,整個函數的執行流程,和參數的調用關系。我們將使用 PyEmu 仿真整個函數,也就是 上面列出的匯編代碼,同時設置 arg_0 和 arg_4 為我們需要的任何數,最后 retn 返回的時候, 捕獲 EAX 的值,也就是函數的返回值。雖然仿真的函數似乎過于簡單,不過整個仿真過程 就是一切函數仿真的基礎,一通百通。 ### 12.3.1 函數仿真 開始腳本編寫,第一步確認 PyEmu 的路徑設置正確。 ``` #addnum_function_call.py import sys sys.path.append("C:\\PyEmu") sys.path.append("C:\\PyEmu\\lib") from PyEmu import * ``` 設置好庫路徑之后,就要開始函數仿真部分的編寫了。首先將我們逆向的程序的,代碼 塊和數據塊映射到仿真器中,以便仿真器仿真運行。因為我們會使用 IDAPython 加載這些塊,對相關函數不熟悉的同學,請翻到第十一章,認真閱讀。 ``` #addnum_function_call.py ... emu = IDAPyEmu() # Load the binary's code segment code_start = SegByName(".text") code_end = SegEnd( code_start ) while code_start <= code_end: emu.set_memory( code_start, GetOriginalByte(code_start), size=1 ) code_start += 1 print "[*] Finished loading code section into memory." # Load the binary's data segment data_start = SegByName(".data") data_end = SegEnd( data_start ) while data_start <= data_end: emu.set_memory( data_start, GetOriginalByte(data_start), size=1 ) data_start += 1 print "[*] Finished loading data section into memory." ``` 使用任何仿真器方法之前都必須實例化一個 IDAPyEmu 對象。接著將代碼塊和數據塊 加載進 PyEmu 的內存,名副其實的依葫蘆畫瓢喔。使用 IDAPython 的 SegByName()函數找 出塊首,SegEnd()找出塊尾。然后一個一個字節的將這些塊中的數據拷貝到 PyEmu 的內存 中。代碼和數據塊都加載完成后,就要設置棧參數了,這些參數可以任意設置,最后再安裝 一個 retn 指令處理函數。 ``` #addnum_function_call.py ... # Set EIP to start executing at the function head emu.set_register("EIP", 0x00401000) # Set up the ret handler emu.set_mnemonic_handler("ret", ret_handler) # Set the function parameters for the call emu.set_stack_argument(0x8, 0x00000001, name="arg_0") emu.set_stack_argument(0xc, 0x00000002, name="arg_4") # There are 10 instructions in this function emu.execute( steps = 10 ) print "[*] Finished function emulation run." ``` 首先將 EIP 指向到函數頭,0x00401000,PyEmu 仿真器將從這里開始執行指令。接著, 在函數的 retn 指令上設置 助記符(mnemonic)或者指令處理函數(set_instruction_handler)。第 三步,設置棧參數以供函數調用。在這里設置成 0x00000001 和 0x00000002。最后讓 PyEmu 執行完成整個函數 10 行代碼。完整的代碼如下。 ``` #addnum_function_call.py import sys sys.path.append("C:\\PyEmu") sys.path.append("C:\\PyEmu\\lib") from PyEmu import * def ret_handler(emu, address): num1 = emu.get_stack_argument("arg_0") num2 = emu.get_stack_argument("arg_4") sum = emu.get_register("EAX") print "[*] Function took: %d, %d and the result is %d." % (num1, n return True emu = IDAPyEmu() # Load the binary's code segment code_start = SegByName(".text") code_end = SegEnd( code_start ) while code_start <= code_end: emu.set_memory( code_start, GetOriginalByte(code_start), size=1 ) code_start += 1 print "[*] Finished loading code section into memory." # Load the binary's data segment data_start = SegByName(".data") data_end = SegEnd( data_start ) while data_start <= data_end: emu.set_memory( data_start, GetOriginalByte(data_start), size=1 ) data_start += 1 print "[*] Finished loading data section into memory." # Set EIP to start executing at the function head emu.set_register("EIP", 0x00401000) # Set up the ret handler emu.set_mnemonic_handler("ret", ret_handler) # Set the function parameters for the call emu.set_stack_argument(0x8, 0x00000001, name="arg_0") emu.set_stack_argument(0xc, 0x00000002, name="arg_4") # There are 10 instructions in this function emu.execute( steps = 10 ) print "[*] Finished function emulation run." ``` ret 指令處理函數簡單的設置成檢索出棧參數和 EAX 的值,最后再將它們打印出來。 用 IDA 加載 addnum.exe,然后將 PyEmu 腳本當作 IDAPython 文件調用。輸出結果將如下: ``` [*] Finished loading code section into memory. [*] Finished loading data section into memory. [*] Function took 1, 2 and the result is 3. [*] Finished function emulation run. ``` Listing 12-2: IDAPyEmu 仿真函數的輸出 很好很簡單!整個過程很成功,棧參數和返回值都從捕獲,說明函數仿真成功了。作為進一步的練習,各位可以加載不同的文件,隨機的選擇一個函數進行仿真,然后監視相關數 據的調用或者任何感興趣的東西。某一天,當你遇到一個上千行的函數的時候,相信這種方 法能幫你從無數的分支,循環還有可怕的指針中拯救出來,它們節省的不僅僅是事件,更是 你的信心。接下來讓我們用 PEPyEmu 庫解壓一個被壓縮文件。 ### 12.3.2 PEPyEmu PEPyEmu 類用于可執行文件的靜態分析(不需要 IDA Pro)。整個處理過程就是將磁盤 上的可執行文件映射到內存中,然后使用 pydasm 進行指令解碼。下面的試驗中,我們將通 過仿真器運行一個壓縮過的可執行文件,然后把解壓出來的原始文件轉存到硬盤上。這次使 用的壓縮軟件就是 UPX(Ultimate Packer for Executables),一款偉大的開源壓縮軟件,同時也 是使用最廣的壓縮軟件,用于最大程度的壓縮可執行文件,同樣也能被病毒軟件用來迷惑分 析者。在使用自定義 PyEmu 腳本( Cody Pierce 提供 )對程序進行解壓之前,讓我們看看壓縮 程序是怎么工作的。 ### 12.3.3 壓縮程序 壓縮程序由來已久。最早在我們使用 1.44 軟盤的時候,壓縮程序就用來盡可能的減少 程序大小(想當初我們的軟盤上可是有上千號文件),隨著事件的流逝,這項技術也漸漸成為 病毒開發中的一個主要部分,用來迷惑分析者。一個典型的壓縮程序會將目標程序的代碼段 和數據段進行壓縮,然后將入口點替換成解壓的代碼。當程序執行的時候,解壓代碼就會將 原始代碼加壓進內存,然后跳到原始入口點 OEP(original entry point ),開始正常運行程序。 在我們分析調試任何壓縮過的程序之前,也都必須解壓它們。這時候你會想到用調試器完成 這項任務(因為各種豐富的腳本),不過現在的病毒一般都繳入反調試代碼,用調試器進行 解壓變得越來越困難。那怎么辦呢?用仿真器。因為我們并沒有附加到正在執行的程序,而 是將壓縮過的代碼拷貝到仿真器中運行,然后等待它自動解壓完成,接著再把解壓出來的原 始程序,轉儲到硬盤上。以后就能夠正常的分析調試它們了。 這次我們選擇 UPX 壓縮 calc.exe。然后用 PyEmu 解壓它,最后 dump 出來。記得這種 方法同樣適用于別的壓縮程序,萬變不離其宗。 1. ### UPX UPX 是自由的,是開源的,是跨平臺的(Linux Windows....)。提供不同的壓縮級別,和 許多附加的選項,用于完成各種不同的壓縮任務。我們使用默認的壓縮方案,都讓你可隨意 的測試。 從 [http://upx.sourceforge.net](http://upx.sourceforge.net) 下載 UPX。 解壓到 C 盤,官方沒有提供圖形界面,所以我們必須從命令行操作。打開 CMD,改 變當前目錄到 C:\upx303w(也就是 UPX 解壓的目錄),輸入以下命令: ``` C:\upx303w>upx -o c:\calc_upx.exe C:\Windows\system32\calc.exe Ultimate Packer for eXecutables Copyright (C) 1996 - 2008 UPX 3.03w Markus Oberhumer, Laszlo Molnar & John Reiser Apr 27th 2008 File size Ratio Format Name -------------------- ------ ----------- ----------- 114688 -> 56832 49.55% win32/pe calc_upx.exe Packed 1 file. C:\upx303w> ``` 成功的壓縮了 Windows 的計算器,并且轉儲到了 C 盤下。 -o 為輸出標志,指定輸出文件名。接下來,終于到了 PEPyEmu 出馬了。 2. 使用 PEPyEmu 解壓 UPX UPX 壓縮可執行程序的方法很簡單明了:重寫程序的入口點,指向解壓代碼,同時添 加兩個而外的塊,UPX0 和 UPX1。使用 Immunity 加載壓縮程序,檢查內存布局(ALT-M),將會看到如下相似的輸出: ``` Address Size Owner Section Contains Access Initial Access 00100000 00001000 calc_upx PE Header R RWE 01001000 00019000 calc_upx UPX0 RWE RWE 0101A000 00007000 calc_upx UPX1 code RWE RWE 01021000 00007000 calc_upx .rsrc data,imports RW RWE resources ``` Listing 12-3: UPX 壓縮之后的程序的內存布局. UPX1 顯示為代碼塊,其中包含了主要的解壓代碼。代碼經過 UPX1 的解壓之后,就跳 出 UPX1 塊,到達真正的可執行代碼塊,開始執行程序。我們要做的就是讓仿真器運行解壓 代碼,同時不斷的檢測 EIP 和 JMP,當發現有 JMP 指令使得 EIP 的范圍超出 UPX1 段的時 候,說明將到跳轉到原始代碼段了。 接下來開始代碼的編寫,這次我們只使用獨立的 PEPyEmu 模塊。 ``` #upx_unpacker.py from ctypes import * # You must set your path to pyemu sys.path.append("C:\\PyEmu") sys.path.append("C:\\PyEmu\\lib") from PyEmu import PEPyEmu # Commandline arguments exename = sys.argv[1] outputfile = sys.argv[2] # Instantiate our emulator object emu = PEPyEmu() if exename: # Load the binary into PyEmu if not emu.load(exename): print "[!] Problem loading %s" % exename sys.exit(2) else: print "[!] Blank filename specified" sys.exit(3) # Set our library handlers emu.set_library_handler("LoadLibraryA", loadlibrary) emu.set_library_handler("GetProcAddress", getprocaddress) emu.set_library_handler("VirtualProtect", virtualprotect) # Set a breakpoint at the real entry point to dump binary emu.set_mnemonic_handler( "jmp", jmp_handler ) # Execute starting from the header entry point emu.execute( start=emu.entry_point ) ``` 第 一 步 將 壓 縮 文 件 加 載 進 PyEmu 。 第 二 部 , 在 LoadLibraryA, GetProcAddress, VirtualProtect 三個函數上設置庫處理函數。這些函數都將在解壓代碼中調用,這些操作必須 我們自己在仿真器中完成。第三步,在解壓程序執行完成準備跳到 OEP 的時候,我們將進 行相關的操作,這個任務就有 JMP 指令處理函數完成。最后告訴仿真器,從壓縮程序頭部 開始執行代碼。 ``` #upx_unpacker.py from ctypes import * # You must set your path to pyemu sys.path.append("C:\\PyEmu") sys.path.append("C:\\PyEmu\\lib") from PyEmu import PEPyEmu ''' HMODULE WINAPI LoadLibrary( in LPCTSTR lpFileName ); ''' def loadlibrary(name, address): # Retrieve the DLL name dllname = emu.get_memory_string(emu.get_memory(emu.get_register("ESP"))) # Make a real call to LoadLibrary and return the handle dllhandle = windll.kernel32.LoadLibraryA(dllname) emu.set_register("EAX", dllhandle) # Reset the stack and return from the handler return_address = emu.get_memory(emu.get_register("ESP")) emu.set_register("ESP", emu.get_register("ESP") + 8) emu.set_register("EIP", return_address) return True ''' FARPROC WINAPI GetProcAddress( in HMODULE hModule, in LPCSTR lpProcName ); ''' def getprocaddress(name, address): # Get both arguments, which are a handle and the procedure name handle = emu.get_memory(emu.get_register("ESP") + 4) proc_name = emu.get_memory(emu.get_register("ESP") + 8) # lpProcName can be a name or ordinal, if top word is null it's an ordinal # lpProcName 的高 16 位是 null 的時候,它就是序列號(也就是個地址),否者就是名字 if (proc_name >> 16): procname = emu.get_memory_string(emu.get_memory(emu.get_register("ESP") + 8)) else: procname = arg2 #這 arg2 不知道從何而來,應該是 procname = proc_name # Add the procedure to the emulator emu.os.add_library(handle, procname) import_address = emu.os.get_library_address(procname) # Return the import address emu.set_register("EAX", import_address) # Reset the stack and return from our handler return_address = emu.get_memory(emu.get_register("ESP")) emu.set_register("ESP", emu.get_register("ESP") + 8) #這里應該是 r("ESP") + 8,因為有兩個參數需要平衡 emu.set_register("EIP", return_address) return True ''' BOOL WINAPI VirtualProtect( in LPVOID lpAddress, in SIZE_T dwSize, in DWORD flNewProtect, out PDWORD lpflOldProtect ); ''' def virtualprotect(name, address): # Just return TRUE emu.set_register("EAX", 1) # Reset the stack and return from our handler return_address = emu.get_memory(emu.get_register("ESP")) emu.set_register("ESP", emu.get_register("ESP") + 16) emu.set_register("EIP", return_address) return True # When the unpacking routine is finished, handle the JMP to the OEP def jmp_handler(emu, mnemonic, eip, op1, op2, op3): # The UPX1 section if eip < emu.sections["UPX1"]["base"]: print "[*] We are jumping out of the unpacking routine." print "[*] OEP = 0x%08x" % eip # Dump the unpacked binary to disk dump_unpacked(emu) # We can stop emulating now emu.emulating = False return True ``` LoadLibrary 處理函數從棧中捕捉到調用的 DLL 的名字,然后使用 ctypes 庫函數進 行真正的 LoadLibraryA 調用,這個函數由 kernel32.dll 導出。調用成功返回后,將句柄傳遞 給 EAX 寄存器,重新調整仿真器棧,最后重處理函數返回。同樣, GetProcAddress 處理函 數 從 棧 中 接 收 兩 個 參 數(arg2) , 然 后 在 仿 真 器 中 進 行 真 實 的 調 用(emu.os.add_library 和 emu.os.get_library_address) , 這 個 函 數 也 由 kernel32.dll 導 出 ( 當 然 也 可 以 使 用 windll.kernel32.GetProcAddress) 。 之 后 把 地 址 存 儲 到 EAX , 調 整 棧 ( 這 里 原 作 者 使 用 emu.set_register("ESP", emu.get_register("ESP") + 8),不過由于是兩個參數,應該是+12),返 回。第三個 VirtualProtect 處理函數,只是簡單的返回一個 True 值,接著就是一樣的棧處理 和從函數中返回。之所以這樣做,是因為我們不需要真正的保護內存中的某個頁面;我們值 需要確保在仿真器中的 VirtualProtect 調用都返回真。最后的 JMP 指令處理函數做了一個簡 單的確認,看是否要跳出解壓代碼段,如果跳出,就調用 dump_unpacked 將代碼轉儲到硬 盤上。之后告訴仿真器停止工作,解壓工作完成了。 下面就是 dump_unpacked 代碼。 ``` #upx_unpacker.py ... def dump_unpacked(emu): global outputfile fh = open(outputfile, 'wb') print "[*] Dumping UPX0 Section" base = emu.sections["UPX0"]["base"] length = emu.sections["UPX0"]["vsize"] print "[*] Base: 0x%08x Vsize: %08x"% (base, length) for x in range(length): fh.write("%c" % emu.get_memory(base + x, 1)) print "[*] Dumping UPX1 Section" base = emu.sections["UPX1"]["base"] length = emu.sections["UPX1"]["vsize"] print "[*] Base: 0x%08x Vsize: %08x" % (base, length) for x in range(length): fh.write("%c" % emu.get_memory(base + x, 1)) print "[*] Finished." ``` 我們只需要簡單的將 UPX0 和 UPX1 兩個段的代碼寫入文件。一旦文件 dump 成功,就能夠想正常程序一樣分析調試它們了。在命令行中使用我們的解壓腳本看看: ``` C:\>C:\Python25\python.exe upx_unpacker.py C:\calc_upx.exe calc_clean.exe [*] We are jumping out of the unpacking routine. [*] OEP = 0x01012475 [*] Dumping UPX0 Section [*] Base: 0x01001000 Vsize: 00019000 [*] Dumping UPX1 Section [*] Base: 0x0101a000 Vsize: 00007000 [*] Finished. C:\> ``` Listing 12-4:upx_unpacker.py 的命令行輸出 現在我們有了一個和未加密的 calc.exe 一樣的 calc_clean.exe。大功告成,各位不妨測試 著寫寫不同殼的解壓代碼,相信不久之后你會學到更多。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看