挑戰4個任務:迅速上手Unicorn Engine

前言

在這篇教程中,您將通過解決實際問題來練習如何使用Unicorn Engine。一共有4個練習,其中我將會詳細講解第一個練習,而對於其他練習我們會提供提示和解決方案供大家閱讀。

FAQ:

1、什麼是Unicorn Engine?

Unicore Engine是一個模擬器,儘管並不太常見。通過該模擬器,您不用模仿整個程序或系統。這一模擬器不支持系統調用,必須先映射內存,並手動將數據寫入到內存中,然後才能從指定的地址開始模擬。

2、這篇文章中的內容可以用於什麼場景?

我們可以在不創建有害進程的前提下,從惡意軟體中調用一個特定的函數。此外還可以用於CTF比賽,用於基於漏洞注入的自動軟體測試,也可以用於能預測未來的gdb插件(例如實現進一步的跳轉),還可以用來模擬混淆的代碼。

3、要開始本教程的練習,我需要安裝什麼?

需要安裝Unicorn Engine,並連接Python。此外,還需要一個反彙編工具。

任務1

該任務來自hxp CTF 2017,名稱為斐波那契,地址為:ctftime.org/event/489

二進位文件可以在這裡下載:eternal.red/assets/file

當我們運行這個程序的時候,我們可以注意到,它會計算並列印我們的Flag,但這個過程非常緩慢,並且Flag的計算過程會隨著位元組的增多變得越來越慢。

該題的Flag為:hxp{F。

這就意味著,我們需要對程序進行優化,以在合理的時間內得到Flag。

在IDA Pro的幫助下,我們將代碼反編譯成像C語言一樣的偽代碼。儘管代碼最終並不一定能被正確地反編譯,但我們通過這一過程,可以對代碼的具體功能有一定的了解。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)n{n void *v3; // rbp@1n int v4; // ebx@1n signed __int64 v5; // r8@2n char v6; // r9@3n __int64 v7; // r8@3n char v8; // cl@3n __int64 v9; // r9@5n int a2a; // [sp+Ch] [bp-1Ch]@3nn v3 = &encrypted_flag;n v4 = 0;n setbuf(stdout, 0LL);n printf("The flag is: ", 0LL);n while ( 1 )n {n LODWORD(v5) = 0;n don {n a2a = 0;n fibonacci(v4 + v5, &a2a);n v8 = v7;n v5 = v7 + 1;n }n while ( v5 != 8 );n v4 += 8;n if ( (unsigned __int8)(a2a << v8) == v6 )n break;n v3 = (char *)v3 + 1;n _IO_putc((char)(v6 ^ ((_BYTE)a2a << v8)), stdout);n v9 = *((char *)v3 - 1);n }n _IO_putc(10, stdout);n return 0LL;n}nnunsigned int __fastcall fibonacci(int i, _DWORD *a2)n{n _DWORD *v2; // rbp@1n unsigned int v3; // er12@3n unsigned int result; // eax@3n unsigned int v5; // edx@3n unsigned int v6; // esi@3n unsigned int v7; // edx@4nn v2 = a2;n if ( i )n {n if ( i == 1 )n {n result = fibonacci(0, a2);n v5 = result - ((result >> 1) & 0x55555555);n v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333;n }n elsen {n v3 = fibonacci(i - 2, a2);n result = v3 + fibonacci(i - 1, a2);n v5 = result - ((result >> 1) & 0x55555555);n v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333;n }n v7 = v6 + (v5 & 0x33333333) + ((v6 + (v5 & 0x33333333)) >> 4);n *v2 ^= ((BYTE1(v7) & 0xF) + (v7 & 0xF) + (unsigned __int8)((((v7 >> 8) & 0xF0F0F) + (v7 & 0xF0F0F0F)) >> 16)) & 1;n }n elsen {n *a2 ^= 1u;n result = 1;n }n return result;n}n

下面是主函數的彙編代碼:

.text:0x4004E0 main proc near ; DATA XREF: start+1Don.text:0x4004E0n.text:0x4004E0 var_1C = dword ptr -1Chn.text:0x4004E0n.text:0x4004E0 push rbpn.text:0x4004E1 push rbxn.text:0x4004E2 xor esi, esi ; bufn.text:0x4004E4 mov ebp, offset unk_4007E1n.text:0x4004E9 xor ebx, ebxn.text:0x4004EB sub rsp, 18hn.text:0x4004EF mov rdi, cs:stdout ; streamn.text:0x4004F6 call _setbufn.text:0x4004FB mov edi, offset format ; "The flag is: "n.text:0x400500 xor eax, eaxn.text:0x400502 call _printfn.text:0x400507 mov r9d, 49hn.text:0x40050D nop dword ptr [rax]n.text:0x400510n.text:0x400510 loc_400510: ; CODE XREF: main+8Ajn.text:0x400510 xor r8d, r8dn.text:0x400513 jmp short loc_40051Bn.text:0x400513 ; ---------------------------------------------------------------------------n.text:0x400515 align 8n.text:0x400518n.text:0x400518 loc_400518: ; CODE XREF: main+67jn.text:0x400518 mov r9d, edin.text:0x40051Bn.text:0x40051B loc_40051B: ; CODE XREF: main+33jn.text:0x40051B lea edi, [rbx+r8]n.text:0x40051F lea rsi, [rsp+28h+var_1C]n.text:0x400524 mov [rsp+28h+var_1C], 0n.text:0x40052C call fibonaccin.text:0x400531 mov edi, [rsp+28h+var_1C]n.text:0x400535 mov ecx, r8dn.text:0x400538 add r8, 1n.text:0x40053C shl edi, cln.text:0x40053E mov eax, edin.text:0x400540 xor edi, r9dn.text:0x400543 cmp r8, 8n.text:0x400547 jnz short loc_400518n.text:0x400549 add ebx, 8n.text:0x40054C cmp al, r9bn.text:0x40054F mov rsi, cs:stdout ; fpn.text:0x400556 jz short loc_400570n.text:0x400558 movsx edi, dil ; cn.text:0x40055C add rbp, 1n.text:0x400560 call __IO_putcn.text:0x400565 movzx r9d, byte ptr [rbp-1]n.text:0x40056A jmp short loc_400510n.text:0x40056A ; ---------------------------------------------------------------------------n.text:0x40056C align 10hn.text:0x400570n.text:0x400570 loc_400570: ; CODE XREF: main+76jn.text:0x400570 mov edi, 0Ah ; cn.text:0x400575 call __IO_putcn.text:0x40057A add rsp, 18hn.text:0x40057E xor eax, eaxn.text:0x400580 pop rbxn.text:0x400581 pop rbpn.text:0x400582 retnn.text:0x400582 main endpn

fibonacci函數的彙編代碼如下:

.text:0x400670 fibonacci proc near ; CODE XREF: main+4Cpn.text:0x400670 ; fibonacci+19p ...n.text:0x400670 test edi, edin.text:0x400672 push r12n.text:0x400674 push rbpn.text:0x400675 mov rbp, rsin.text:0x400678 push rbxn.text:0x400679 jz short loc_4006F8n.text:0x40067B cmp edi, 1n.text:0x40067E mov ebx, edin.text:0x400680 jz loc_400710n.text:0x400686 lea edi, [rdi-2]n.text:0x400689 call fibonaccin.text:0x40068E lea edi, [rbx-1]n.text:0x400691 mov r12d, eaxn.text:0x400694 mov rsi, rbpn.text:0x400697 call fibonaccin.text:0x40069C add eax, r12dn.text:0x40069F mov edx, eaxn.text:0x4006A1 mov ebx, eaxn.text:0x4006A3 shr edx, 1n.text:0x4006A5 and edx, 55555555hn.text:0x4006AB sub ebx, edxn.text:0x4006AD mov ecx, ebxn.text:0x4006AF mov edx, ebxn.text:0x4006B1 shr ecx, 2n.text:0x4006B4 and ecx, 33333333hn.text:0x4006BA mov esi, ecxn.text:0x4006BCn.text:0x4006BC loc_4006BC: ; CODE XREF: fibonacci+C2jn.text:0x4006BC and edx, 33333333hn.text:0x4006C2 lea ecx, [rsi+rdx]n.text:0x4006C5 mov edx, ecxn.text:0x4006C7 shr edx, 4n.text:0x4006CA add edx, ecxn.text:0x4006CC mov esi, edxn.text:0x4006CE and edx, 0F0F0F0Fhn.text:0x4006D4 shr esi, 8n.text:0x4006D7 and esi, 0F0F0Fhn.text:0x4006DD lea ecx, [rsi+rdx]n.text:0x4006E0 mov edx, ecxn.text:0x4006E2 shr edx, 10hn.text:0x4006E5 add edx, ecxn.text:0x4006E7 and edx, 1n.text:0x4006EA xor [rbp+0], edxn.text:0x4006ED pop rbxn.text:0x4006EE pop rbpn.text:0x4006EF pop r12n.text:0x4006F1 retnn.text:0x4006F1 ; ---------------------------------------------------------------------------n.text:0x4006F2 align 8n.text:0x4006F8n.text:0x4006F8 loc_4006F8: ; CODE XREF: fibonacci+9jn.text:0x4006F8 mov edx, 1n.text:0x4006FD xor [rbp+0], edxn.text:0x400700 mov eax, 1n.text:0x400705 pop rbxn.text:0x400706 pop rbpn.text:0x400707 pop r12n.text:0x400709 retnn.text:0x400709 ; ---------------------------------------------------------------------------n.text:0x40070A align 10hn.text:0x400710n.text:0x400710 loc_400710: ; CODE XREF: fibonacci+10jn.text:0x400710 xor edi, edin.text:0x400712 call fibonaccin.text:0x400717 mov edx, eaxn.text:0x400719 mov edi, eaxn.text:0x40071B shr edx, 1n.text:0x40071D and edx, 55555555hn.text:0x400723 sub edi, edxn.text:0x400725 mov esi, edin.text:0x400727 mov edx, edin.text:0x400729 shr esi, 2n.text:0x40072C and esi, 33333333hn.text:0x400732 jmp short loc_4006BCn.text:0x400732 fibonacci endpn

解決這個問題的方式有很多種。例如,我們可以使用一種編程語言重新構建代碼,並對新構建的代碼進行優化。重建代碼的過程並不容易,並且有可能會產生問題或錯誤,而解決問題、修正錯誤的這個過程是非常煎熬的。但假如我們使用Unicorn Engine,就可以跳過重建代碼的過程,從而避免上面提到的問題。我們還可以通過其他幾種方法跳過重建代碼的過程,例如通過腳本調試,或者是使用Frida。

在優化之前,我們首先模擬正常的程序,一旦程序成功運行後,我們再在Unicorn Engine中對其進行優化。

第一部分:模擬程序

首先我們創建一個名為fibonacci.py的文件,並將二進位文件放在同一個文件夾下。

將下面的代碼添加到文件中:

from unicorn import *nfrom unicorn.x86_const import *n

其中,第一行載入主二進位程序以及基本的Unicorn Constant,第二行載入特定於x86和x86-64體系結構的Constant。

接下來,添加如下幾行:

import structnndef read(name):n with open(name) as f:n return f.read()nndef u32(data):n return struct.unpack("I", data)[0]nndef p32(num):n return struct.pack("I", num)n

在這裡,我們只添加了一些通常的功能,這些功能稍後會對我們有所幫助。

其中,read會返回整個文件的內容。u32需要一個4位元組的字元串,並將其轉換為一個整數,以低位元組序表示這個數據。p32正相反,它需要一個數字,並將其轉換為4位元組的字元串,以低位元組序表示。

如果你安裝了pwntools,那麼你就不需要創建這些函數,只需要通過pwn import *導入即可。

接下來,讓我們初始化我們Unicorn Engine的類,以適應x86-64架構:

mu = Uc (UC_ARCH_X86, UC_MODE_64)n

我們需要使用下面的參數來調用函數Uc:

1、主結構分支,其中的Constant以UCARCH開始;

2、進一步的架構規範,其中的Constant以UCMODE開始。

您可以在本文後面的參考內容中,找到架構Constant的完整列表。

正如我們之前所說的,要使用Unicorn Engine,我們需要手動初始化虛擬內存。對於這個二進位文件,我們需要在其中的某個位置編寫代碼,並分配一個棧。

二進位的基址是0x400000。我們的棧將從地址0x000000開始,大小為1024*1024。也許我們並不需要那麼大的空間,但創建大一些的空間也不會有任何不好的影響。

我們可以通過調用mem_map方法來映射內存。

添加如下行:

BASE = 0x400000nSTACK_ADDR = 0x0nSTACK_SIZE = 1024*1024nnmu.mem_map(BASE, 1024*1024)nmu.mem_map(STACK_ADDR, STACK_SIZE)n

現在,我們需要在基址載入二進位文件,就像載入器一樣。然後我們需要將RSP設置為指向棧的末尾。

mu.mem_write(BASE, read("./fibonacci"))nmu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 1)n

在開始模擬並運行代碼之前,我們首先需要知道開始地址在哪裡,並且要知道模擬器應該在哪裡停止。

我們可以開始模擬位於地址0x4004E0的代碼,這是main的第一個地址。結束位置可以選擇0x400575,這是putc("n")的位置,會在列印完整個Flag後被調用。如下所示:

.text:0x400570 mov edi, 0Ah ; cn.text:0x400575 call __IO_putcn

我們可以開始模擬:

mu.emu_start(0x00000000004004E0, 0x0000000000400575)n

現在,可以運行這個腳本:

a@x:~/Desktop/unicorn_engine_lessons$ python solve.py nTraceback (most recent call last):n File "solve.py", line 32, in <module>n mu.emu_start(0x00000000004004E0, 0x0000000000400575)n File "/usr/local/lib/python2.7/dist-packages/unicorn/unicorn.py", line 288, in emu_startn raise UcError(status)nunicorn.unicorn.UcError: Invalid memory read (UC_ERR_READ_UNMAPPED)n

在這時,我們發現出現了一些問題,但具體還不得而知。在mu.emu_start之前,我們可以添加:

def hook_code(mu, address, size, user_data): n print(>>> Tracing instruction at 0x%x, instruction size = 0x%x %(address, size)) nnmu.hook_add(UC_HOOK_CODE, hook_code)n

這段代碼添加了一個鉤子。我們定義了函數hook_code,在模擬每個指令前被調用。該函數需要以下參數:

1、Uc實例

2、指令的地址

3、指令的大小

4、用戶數據(我們可以在hook_add()的可選參數中傳遞這個值)

相關源代碼請參考solve1.py:eternal.red/assets/file

運行時,我們可以看到:

a@x:~/Desktop/unicorn_engine_lessons$ python solve.py n>>> Tracing instruction at 0x4004e0, instruction size = 0x1n>>> Tracing instruction at 0x4004e1, instruction size = 0x1n>>> Tracing instruction at 0x4004e2, instruction size = 0x2n>>> Tracing instruction at 0x4004e4, instruction size = 0x5n>>> Tracing instruction at 0x4004e9, instruction size = 0x2n>>> Tracing instruction at 0x4004eb, instruction size = 0x4n>>> Tracing instruction at 0x4004ef, instruction size = 0x7nTraceback (most recent call last):n File "solve.py", line 41, in <module>n mu.emu_start(0x00000000004004E0, 0x0000000000400575)n File "/usr/local/lib/python2.7/dist-packages/unicorn/unicorn.py", line 288, in emu_startn raise UcError(status)nunicorn.unicorn.UcError: Invalid memory read (UC_ERR_READ_UNMAPPED)n

這意味著,腳本在執行以下指令時失敗:

.text:0x4004EF mov rdi, cs:stdout ; streamn

該指令從地址0x601038讀取內存(可以在IDA Pro中看到)。這是.bss段,並不是由我們分配的。因此我們的解決方案是跳過所有有問題的指令。

下面有一條指令:

.text:0x4004F6 call _setbufn

我們並不能調用任何glibc函數,因為此前並沒有將glibc載入到虛擬內存中。事實上,我們並不需要調用這個函數,所以也可以跳過它。

下面是我們需要跳過的指令列表:

.text:0x4004EF mov rdi, cs:stdout ; streamn.text:0x4004F6 call _setbufn.text:0x400502 call _printfn.text:0x40054F mov rsi, cs:stdout ; fpn

我們可以通過將地址寫入下一條指令的RIP寄存器來跳過指令:

mu.reg_write(UC_X86_REG_RIP, address+size)n

hook_code現在應該是這樣的:

instructions_skip_list = [0x00000000004004EF, 0x00000000004004F6, 0x0000000000400502, 0x000000000040054F]nndef hook_code(mu, address, size, user_data): n print(>>> Tracing instruction at 0x%x, instruction size = 0x%x %(address, size))nn if address in instructions_skip_list:n mu.reg_write(UC_X86_REG_RIP, address+size)n

此外,我們還需要對逐位元組列印Flag的指令進行一些操作。

.text:0x400558 movsx edi, dil ; cn.text:0x40055C add rbp, 1n.text:0x400560 call __IO_putcn

__IO_putc需要一個位元組,以列印出第一個參數(即寄存器RDI)。

我們可以從寄存器RDI中讀取一個值並列印出來,同時跳過模擬這個指令。此時的hook_code函數如下所示:

instructions_skip_list = [0x00000000004004EF, 0x00000000004004F6, 0x0000000000400502, 0x000000000040054F]nndef hook_code(mu, address, size, user_data): n #print(>>> Tracing instruction at 0x%x, instruction size = 0x%x %(address, size))nn if address in instructions_skip_list:n mu.reg_write(UC_X86_REG_RIP, address+size)nn elif address == 0x400560: #that instruction writes a byte of the flagn c = mu.reg_read(UC_X86_REG_RDI)n print(chr(c))n mu.reg_write(UC_X86_REG_RIP, address+size)n

相關源代碼請參考solve2.py:eternal.red/assets/file

接下來,便可以運行,我們發現它確實可以正常工作,但速度還是很慢。

a@x:~/Desktop/unicorn_engine_lessons$ python solve.py nhnxn

第二部分:提速

接下來,讓我們考慮一下提速的方法。為什麼這個程序的運行速度如此之慢?

查看反編譯的代碼,我們可以看到main()多次調用了fibonacci(),並且fibonacci()是一個遞歸函數。

具體分析這個函數,我們看到它有兩個參數,並返回兩個值。第一個返回值通過RAX寄存器傳遞,而第二個返回值通過第二個參數傳遞。深入研究main()和fibonacci(),我們注意到其第二個參數只能取0或1的值。如果我們沒有發現,還可以運行gdb,並在fibonacci()函數的開始處設置一個斷點。

為了優化這個函數,我們可以使用動態編程的方法來記錄針對特定參數的返回值。由於第二個參數只可能是兩個值,所以我們只需要記錄2個MAX_OF_FIRST_ARGUMENT對。

當RIP指向fibonacci函數的開始時,我們可以獲得函數的參數。在函數結束時,需要得知函數的返回值。既然目前我們不清楚返回值,所以需要使用一個棧,來幫助我們在函數結束時獲得這兩個返回值。在fibonacci的入口,我們需要將參數推入棧,並在最後彈出。為了記錄其中的對(Pairs),我們可以使用字典。

如何檢查對(Pairs)的值?

在函數的開始處,可以檢查返回值是否被存儲在字典中,以用於這些參數。如果已經被存儲,我們可以返回該對。只需要將返回值寫入到引用和RAX中即可。此外,我們還將RIP設置為一些RET指令的地址來退出函數。由於這一指令被Hook住了,所以我們不能在fibonacci函數中跳轉到RET。如果該返回值不在字典中,我們將參數添加到棧中。在退出函數時,可以保存返回值。我們可以從棧結構中讀取參數和引用指針。

代碼如下所示:

FIBONACCI_ENTRY = 0x0000000000400670nFIBONACCI_END = [0x00000000004006F1, 0x0000000000400709]nnstack = [] # Stack for storing the argumentsnd = {} # Dictionary that holds return values for given function arguments nndef hook_code(mu, address, size, user_data): n #print(>>> Tracing instruction at 0x%x, instruction size = 0x%x %(address, size))nn if address in instructions_skip_list:n mu.reg_write(UC_X86_REG_RIP, address+size)nn elif address == 0x400560: # That instruction writes a byte of the flagn c = mu.reg_read(UC_X86_REG_RDI)n print(chr(c))n mu.reg_write(UC_X86_REG_RIP, address+size)nn elif address == FIBONACCI_ENTRY: # Are we at the beginning of fibonacci function?n arg0 = mu.reg_read(UC_X86_REG_RDI) # Read the first argument. Tt is passed via RDIn r_rsi = mu.reg_read(UC_X86_REG_RSI) # Read the second argument which is a referencen arg1 = u32(mu.mem_read(r_rsi, 4)) # Read the second argument from referencenn if (arg0,arg1) in d: # Check whether return values for this function are already saved.n (ret_rax, ret_ref) = d[(arg0,arg1)]n mu.reg_write(UC_X86_REG_RAX, ret_rax) # Set return value in RAX registern mu.mem_write(r_rsi, p32(ret_ref)) # Set retun value through referencen mu.reg_write(UC_X86_REG_RIP, 0x400582) # Set RIP to point at RET instruction. We want to return from fibonacci functionnn else:n stack.append((arg0,arg1,r_rsi)) # If return values are not saved for these arguments, add them to stack.nn elif address in FIBONACCI_END:n (arg0, arg1, r_rsi) = stack.pop() # We know arguments when exiting the functionnn ret_rax = mu.reg_read(UC_X86_REG_RAX) # Read the return value that is stored in RAXn ret_ref = u32(mu.mem_read(r_rsi,4)) # Read the return value that is passed referencen d[(arg0, arg1)]=(ret_rax, ret_ref) # Remember the return values for this argument pairn

完整腳本請參考solve3.py:eternal.red/assets/file

至此,我們已經成功地使用Unicorn Engine來優化程序。

接下來,我推薦大家完成下面三個任務的練習。針對每個任務,都有提示和解決方案,並且在解決任務的過程中,可以查看後文的參考內容。

我認為,其中一個很重要的問題就是要知道Constant的名稱。處理這個問題的最好方法是藉助IPython的自動補全(Tab Completion)來完成。在你安裝IPython之後,可以輸入from unicorn import UCARCH,並按TAB鍵,所有以這個前綴開頭的Constant都將被列印出來。

任務2

分析下列Shellcode:

shellcode = "xe8xffxffxffxffxc0x5dx6ax05x5bx29xddx83xc5x4ex89xe9x6ax02x03x0cx24x5bx31xd2x66xbax12x00x8bx39xc1xe7x10xc1xefx10x81xe9xfexffxffxffx8bx45x00xc1xe0x10xc1xe8x10x89xc3x09xfbx21xf8xf7xd0x21xd8x66x89x45x00x83xc5x02x4ax85xd2x0fx85xcfxffxffxffxecx37x75x5dx7ax05x28xedx24xedx24xedx0bx88x7fxebx50x98x38xf9x5cx96x2bx96x70xfexc6xffxc6xffx9fx32x1fx58x1ex00xd3x80"n

如你所見,該程序集被混淆了(命令disarm是pwntools的一個功能):

a@x:~/Desktop/unicorn_engine_lessons$ disasm e8ffffffffc05d6a055b29dd83c54e89e96a02030c245b31d266ba12008b39c1e710c1ef1081e9feffffff8b4500c1e010c1e81089c309fb21f8f7d021d86689450083c5024a85d20f85cfffffffec37755d7a0528ed24ed24ed0b887feb509838f95c962b9670fec6ffc6ff9f321f581e00d380n 0: e8 ff ff ff ff call 0x4n 5: c0 5d 6a 05 rcr BYTE PTR [ebp+0x6a], 0x5n 9: 5b pop ebxn a: 29 dd sub ebp, ebxn c: 83 c5 4e add ebp, 0x4en f: 89 e9 mov ecx, ebpn 11: 6a 02 push 0x2n 13: 03 0c 24 add ecx, DWORD PTR [esp]n 16: 5b pop ebxn 17: 31 d2 xor edx, edxn 19: 66 ba 12 00 mov dx, 0x12n 1d: 8b 39 mov edi, DWORD PTR [ecx]n 1f: c1 e7 10 shl edi, 0x10n 22: c1 ef 10 shr edi, 0x10n 25: 81 e9 fe ff ff ff sub ecx, 0xfffffffen 2b: 8b 45 00 mov eax, DWORD PTR [ebp+0x0]n 2e: c1 e0 10 shl eax, 0x10n 31: c1 e8 10 shr eax, 0x10n 34: 89 c3 mov ebx, eaxn 36: 09 fb or ebx, edin 38: 21 f8 and eax, edin 3a: f7 d0 not eaxn 3c: 21 d8 and eax, ebxn 3e: 66 89 45 00 mov WORD PTR [ebp+0x0], axn 42: 83 c5 02 add ebp, 0x2n 45: 4a dec edxn 46: 85 d2 test edx, edxn 48: 0f 85 cf ff ff ff jne 0x1dn 4e: ec in al, dxn 4f: 37 aaan 50: 75 5d jne 0xafn 52: 7a 05 jp 0x59n 54: 28 ed sub ch, chn 56: 24 ed and al, 0xedn 58: 24 ed and al, 0xedn 5a: 0b 88 7f eb 50 98 or ecx, DWORD PTR [eax-0x67af1481]n 60: 38 f9 cmp cl, bhn 62: 5c pop espn 63: 96 xchg esi, eaxn 64: 2b 96 70 fe c6 ff sub edx, DWORD PTR [esi-0x390190]n 6a: c6 (bad)n 6b: ff 9f 32 1f 58 1e call FWORD PTR [edi+0x1e581f32]n 71: 00 d3 add bl, dln 73: 80 .byte 0x80n

請注意,目前的架構是x86-32。系統調用的列表可以在這裡查看:syscalls.kernelgrok.com

提示

您可以Hook一個int 80h指令,它由cd 80表示。接下來,您可以讀取寄存器和內存。需要記住的是,Shellcode是一個可以在任何地址載入的代碼,絕大多數Shellcode都使用了棧。

解決方案

下面的代碼是通過幾個步驟創建而成的。通過UE錯誤信息,我們獲得了一些線索,並想到了最終的解決方案。

from unicorn import *nfrom unicorn.x86_const import *nnshellcode = "xe8xffxffxffxffxc0x5dx6ax05x5bx29xddx83xc5x4ex89xe9x6ax02x03x0cx24x5bx31xd2x66xbax12x00x8bx39xc1xe7x10xc1xefx10x81xe9xfexffxffxffx8bx45x00xc1xe0x10xc1xe8x10x89xc3x09xfbx21xf8xf7xd0x21xd8x66x89x45x00x83xc5x02x4ax85xd2x0fx85xcfxffxffxffxecx37x75x5dx7ax05x28xedx24xedx24xedx0bx88x7fxebx50x98x38xf9x5cx96x2bx96x70xfexc6xffxc6xffx9fx32x1fx58x1ex00xd3x80" nnnBASE = 0x400000nSTACK_ADDR = 0x0nSTACK_SIZE = 1024*1024nnmu = Uc (UC_ARCH_X86, UC_MODE_32)nnmu.mem_map(BASE, 1024*1024)nmu.mem_map(STACK_ADDR, STACK_SIZE)nnnmu.mem_write(BASE, shellcode)nmu.reg_write(UC_X86_REG_ESP, STACK_ADDR + STACK_SIZE/2)nndef syscall_num_to_name(num):n syscalls = {1: "sys_exit", 15: "sys_chmod"}n return syscalls[num]nndef hook_code(mu, address, size, user_data):n #print(>>> Tracing instruction at 0x%x, instruction size = 0x%x %(address, size)) nn machine_code = mu.mem_read(address, size)n if machine_code == "xcdx80":nn r_eax = mu.reg_read(UC_X86_REG_EAX)n r_ebx = mu.reg_read(UC_X86_REG_EBX)n r_ecx = mu.reg_read(UC_X86_REG_ECX)n r_edx = mu.reg_read(UC_X86_REG_EDX)n syscall_name = syscall_num_to_name(r_eax)nn print "--------------"n print "We intercepted system call: "+syscall_namenn if syscall_name == "sys_chmod":n s = mu.mem_read(r_ebx, 20).split("x00")[0]n print "arg0 = 0x%x -> %s" % (r_ebx, s)n print "arg1 = " + oct(r_ecx)n elif syscall_name == "sys_exit":n print "arg0 = " + hex(r_ebx)n exit()nn mu.reg_write(UC_X86_REG_EIP, address + size)nnmu.hook_add(UC_HOOK_CODE, hook_code)nnmu.emu_start(BASE, BASE-1)n

最終代碼如下:

a@x:~/Desktop/unicorn_engine_lessons$ python solve_task2.pyn--------------nWe intercepted system call: sys_chmodnarg0 = 0x400058 -> /etc/shadownarg1 = 0666Ln--------------nWe intercepted system call: sys_exitnarg0 = 0x400058Ln

任務3

下載二進位文件( eternal.red/assets/file ),該文件是用以下命令編譯的:

gcc function.c -m32 -o function

這個二進位代碼如下所示:

int strcmp(char *a, char *b)n{n //get lengthn int len = 0;n char *ptr = a;n while(*ptr)n {n ptr++;n len++;n }nn //comparestringsn for(int i=0; i<=len; i++)n {n if (a[i]!=b[i])n return 1;n }nn return 0;n}nn__attribute__((stdcall))nint super_function(int a, char *b)n{n if (a==5 && !strcmp(b, "batman"))n {n return 1;n }n return 0;n}nnint main()n{n super_function(1, "spiderman");n}n

任務是調用super_function,使其返回1。

其彙編代碼如下:

.text:0x8048464 super_function proc near ; CODE XREF: main+16pn.text:0x8048464n.text:0x8048464 arg_0 = dword ptr 8n.text:0x8048464 arg_4 = dword ptr 0Chn.text:0x8048464n.text:0x8048464 push ebpn.text:0x8048465 mov ebp, espn.text:0x8048467 call __x86_get_pc_thunk_axn.text:0x804846C add eax, 1B94hn.text:0x8048471 cmp [ebp+arg_0], 5n.text:0x8048475 jnz short loc_8048494n.text:0x8048477 lea eax, (aBatman - 804A000h)[eax] ; "batman"n.text:0x804847D push eaxn.text:0x804847E push [ebp+arg_4]n.text:0x8048481 call strcmpn.text:0x8048486 add esp, 8n.text:0x8048489 test eax, eaxn.text:0x804848B jnz short loc_8048494n.text:0x804848D mov eax, 1n.text:0x8048492 jmp short locret_8048499n.text:0x8048494 ; ---------------------------------------------------------------------------n.text:0x8048494n.text:0x8048494 loc_8048494: ; CODE XREF: super_function+11jn.text:0x8048494 ; super_function+27jn.text:0x8048494 mov eax, 0n.text:0x8048499n.text:0x8048499 locret_8048499: ; CODE XREF: super_function+2Ejn.text:0x8048499 leaven.text:0x804849A retn 8n.text:0x804849A super_function endpn

提示

根據stdcall調用約定,當模擬過程開始時,棧應該如下圖所示。我們看到在下圖中,RET只是返回地址(可以為任意值)。

解決方案

from unicorn import *nfrom unicorn.x86_const import *nimport structnnndef read(name):n with open(name) as f:n return f.read()nndef u32(data):n return struct.unpack("I", data)[0]nndef p32(num):n return struct.pack("I", num)nnmu = Uc (UC_ARCH_X86, UC_MODE_32)nnBASE = 0x08048000nSTACK_ADDR = 0x0nSTACK_SIZE = 1024*1024nnmu.mem_map(BASE, 1024*1024)nmu.mem_map(STACK_ADDR, STACK_SIZE)nnnmu.mem_write(BASE, read("./function"))nr_esp = STACK_ADDR + (STACK_SIZE/2) #ESP points to this address at function callnnSTRING_ADDR = 0x0nmu.mem_write(STRING_ADDR, "batmanx00") #write "batman" somewhere. We have choosen an address 0x0 which belongs to the stack.nnmu.reg_write(UC_X86_REG_ESP, r_esp) #set ESPnmu.mem_write(r_esp+4, p32(5)) #set the first argument. It is integer 5nmu.mem_write(r_esp+8, p32(STRING_ADDR)) #set the second argument. This is a pointer to the string "batman"nnnmu.emu_start(0x8048464, 0x804849A) #start emulation from the beginning of super_function, end at RET instructionnreturn_value = mu.reg_read(UC_X86_REG_EAX)nprint "The returned value is: %d" % return_valuenna@x:~/Desktop/unicorn_engine_lessons$ python solve_task3.py nThe returned value is: 1n

任務4

這個任務與任務1類似,但不同之處就在於這裡的架構不再是x86,而是低位元組序的ARM32。

a@x:~/Desktop/unicorn_engine_lessons$ file task4ntask4: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=3dbf508680ba3d023d3422025954311e1d8fb4a1, not strippedn

二進位文件下載地址為:eternal.red/assets/file

參考這篇資料可能會有所幫助:infocenter.arm.com/help

正確答案

2635833876

提示

1、函數的第一個參數在R0 (UC_ARM_REG_R0)中傳遞;

2、返回值也在R0中;

3、第二個參數在R1 (UC_ARM_REG_R1)中傳遞;

4、我們可以通過這種方式來得到ARM32架構下的Unicorn實例:mu = Uc (UC_ARCH_ARM, UC_MODE_LITTLE_ENDIAN)。

解決方案

from unicorn import *nfrom unicorn.arm_const import *nnnimport structnndef read(name):n with open(name) as f:n return f.read()nndef u32(data):n return struct.unpack("I", data)[0]nndef p32(num):n return struct.pack("I", num)nnnmu = Uc (UC_ARCH_ARM, UC_MODE_LITTLE_ENDIAN)nnnBASE = 0x10000nSTACK_ADDR = 0x300000nSTACK_SIZE = 1024*1024nnmu.mem_map(BASE, 1024*1024)nmu.mem_map(STACK_ADDR, STACK_SIZE)nnnmu.mem_write(BASE, read("./task4"))nmu.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE/2)nninstructions_skip_list = []nnCCC_ENTRY = 0x000104D0nCCC_END = 0x00010580nnstack = [] # Stack for storing the argumentsnd = {} # Dictionary that holds return values for given function arguments nndef hook_code(mu, address, size, user_data): n #print(>>> Tracing instruction at 0x%x, instruction size = 0x%x %(address, size))nn if address == CCC_ENTRY: # Are we at the beginning of ccc function?n arg0 = mu.reg_read(UC_ARM_REG_R0) # Read the first argument. it is passed by R0nn if arg0 in d: # Check whether return value for this function is already saved.n ret = d[arg0]n mu.reg_write(UC_ARM_REG_R0, ret) # Set return value in R0n mu.reg_write(UC_ARM_REG_PC, 0x105BC) # Set PC to point at "BX LR" instruction. We want to return from fibonacci functionnn else:n stack.append(arg0) # If return value is not saved for this argument, add it to stack.nn elif address == CCC_END:n arg0 = stack.pop() # We know arguments when exiting the functionnn ret = mu.reg_read(UC_ARM_REG_R0) # Read the return value (R0)n d[arg0] = ret # Remember the return value for this argumentnnmu.hook_add(UC_HOOK_CODE, hook_code)nnmu.emu_start(0x00010584, 0x000105A8)nnreturn_value = mu.reg_read(UC_ARM_REG_R1) # We end the emulation at printf("%dn", ccc(x)).nprint "The return value is %d" % return_valuen

參考內容

from unicorn import * —— 載入主Unicorn庫,它包含函數和基本Constant。

from unicorn.x86_const import * —— 載入特定於x86和x86-64架構的Constant。

Unicorn模塊中的所有Const如下:

UC_API_MAJOR UC_ERR_VERSION UC_MEM_READ UC_PROT_ALLnUC_API_MINOR UC_ERR_WRITE_PROT UC_MEM_READ_AFTER UC_PROT_EXECnUC_ARCH_ARM UC_ERR_WRITE_UNALIGNED UC_MEM_READ_PROT UC_PROT_NONEnUC_ARCH_ARM64 UC_ERR_WRITE_UNMAPPED UC_MEM_READ_UNMAPPED UC_PROT_READnUC_ARCH_M68K UC_HOOK_BLOCK UC_MEM_WRITE UC_PROT_WRITEnUC_ARCH_MAX UC_HOOK_CODE UC_MEM_WRITE_PROT UC_QUERY_MODEnUC_ARCH_MIPS UC_HOOK_INSN UC_MEM_WRITE_UNMAPPED UC_QUERY_PAGE_SIZEnUC_ARCH_PPC UC_HOOK_INTR UC_MILISECOND_SCALE UC_SECOND_SCALEnUC_ARCH_SPARC UC_HOOK_MEM_FETCH UC_MODE_16 UC_VERSION_EXTRAnUC_ARCH_X86 UC_HOOK_MEM_FETCH_INVALID UC_MODE_32 UC_VERSION_MAJORnUC_ERR_ARCH UC_HOOK_MEM_FETCH_PROT UC_MODE_64 UC_VERSION_MINORnUC_ERR_ARG UC_HOOK_MEM_FETCH_UNMAPPED UC_MODE_ARM UcnUC_ERR_EXCEPTION UC_HOOK_MEM_INVALID UC_MODE_BIG_ENDIAN UcErrornUC_ERR_FETCH_PROT UC_HOOK_MEM_PROT UC_MODE_LITTLE_ENDIAN arm64_constnUC_ERR_FETCH_UNALIGNED UC_HOOK_MEM_READ UC_MODE_MCLASS arm_constnUC_ERR_FETCH_UNMAPPED UC_HOOK_MEM_READ_AFTER UC_MODE_MICRO debugnUC_ERR_HANDLE UC_HOOK_MEM_READ_INVALID UC_MODE_MIPS3 m68k_constnUC_ERR_HOOK UC_HOOK_MEM_READ_PROT UC_MODE_MIPS32 mips_constnUC_ERR_HOOK_EXIST UC_HOOK_MEM_READ_UNMAPPED UC_MODE_MIPS32R6 sparc_constnUC_ERR_INSN_INVALID UC_HOOK_MEM_UNMAPPED UC_MODE_MIPS64 uc_arch_supportednUC_ERR_MAP UC_HOOK_MEM_VALID UC_MODE_PPC32 uc_versionnUC_ERR_MODE UC_HOOK_MEM_WRITE UC_MODE_PPC64 unicornnUC_ERR_NOMEM UC_HOOK_MEM_WRITE_INVALID UC_MODE_QPX unicorn_constnUC_ERR_OK UC_HOOK_MEM_WRITE_PROT UC_MODE_SPARC32 version_bindnUC_ERR_READ_PROT UC_HOOK_MEM_WRITE_UNMAPPED UC_MODE_SPARC64 x86_constnUC_ERR_READ_UNALIGNED UC_MEM_FETCH UC_MODE_THUMB nUC_ERR_READ_UNMAPPED UC_MEM_FETCH_PROT UC_MODE_V8 nUC_ERR_RESOURCE UC_MEM_FETCH_UNMAPPED UC_MODE_V9n

來自unicorn.x86_const的一些Constant示例:

UC_X86_REG_EAX UC_X86_REG_RIP UC_X86_REG_RAX mu = Uc(arch, mode) —— 獲得一個Uc類的實例,在這裡可以指定架構。

舉例來說:

mu = Uc(UC_ARCH_X86, UC_MODE_64) 獲得一個x86-64架構的Uc實例。

mu = Uc(UC_ARCH_X86, UC_MODE_32) 獲得一個x86-32架構的Uc實例。

mu.mem_map(ADDRESS, 4096) 映射一個內存區域。

mu.mem_write(ADDRESS, DATA) 將數據寫入內存。

tmp = mu.mem_read(ADDRESS, SIZE) 從內存中讀取數據。

mu.reg_write(UC_X86_REG_ECX, 0x0) 將寄存器重新賦值。

r_esp = mu.reg_read(UC_X86_REG_ESP) 讀取寄存器的值。

mu.emu_start(ADDRESS_START, ADDRESS_END) 開始模擬。

指令跟蹤:

def hook_code(mu, address, size, user_data): n print(>>> Tracing instruction at 0x%x, instruction size = 0x%x %(address, size)) nnmu.hook_add(UC_HOOK_CODE, hook_code)n

這段代碼添加了一個鉤子。我們定義了函數hook_code,在模擬每個指令之前調用,該函數需要以下參數:

1、Uc實例

2、指令的地址

3、指令的大小

4、用戶數據(我們可以在hook_add()的可選參數中傳遞這個值)

參考資料

  1. 關於Unicorn Engine的基本介紹:

    unicorn-engine.org/BHUS
  2. Oh look, there are bindings for many languages:

    github.com/unicorn-engi
  3. Unicorn Engine參考:

    hackmd.io/s/rJTUtGwuW
  4. 官方UE教程:

    unicorn-engine.org/docs

登錄安全客 - 有思想的安全新媒體www.anquanke.com/,或下載安全客APP來獲取更多最新資訊吧~


推薦閱讀:

黑客如何接管銀行的所有在線業務?
Linux 防火牆技術
刺風有道,吳翰清的雲端飛揚
脫褲和撞褲
【快訊】美國選民資料庫泄露,包含2億選民個人信息

TAG:互联网 | 网络安全 | 信息安全 |