Unicorn Engineの最初のステップ

Habrで「Unicorn Engine」を検索したとき、このツールが記事で取り上げられたことがないことに驚きました。 この隙間を埋めようとします。 基本から始め、実際の生活でエミュレーターを使用する例を見てみましょう。 車輪を再発明しないために、私はこのマニュアルを単に翻訳することにしました。 開始する前に、すべてのコメントまたはコメントは次のようになります







Unicornエンジンとは何ですか?



開発者自身が書いています ユニコーンエンジン このようなユニコーンエンジン:







Unicornは、軽量でマルチプラットフォームでマルチアーキテクチャのプロセッサエミュレータです。

これは標準のエミュレータではありません。 プログラム全体またはOS全体の動作をエミュレートするわけではありません。 システムコマンド(ファイルを開く、コンソールに文字を出力するなど)はサポートしていません。 メモリのマークアップを行い、自分でメモリにデータをロードする必要があります。その後、特定のアドレスから実行を開始するだけです。







それでは、どのように役立ちますか?









何が必要ですか?











例として、 Fibonacciという名前のhxp CTF 2017のタスクを取り上げます。 バイナリはここからダウンロードできます







プログラムを起動すると、コンソールにフラグが表示されますが、非常にゆっくりです。 後続の各フラグバイトは、より低速であると見なされます。







The flag is: hxp{F
      
      





つまり、妥当な時間内にフラグを取得するには、このアプリケーションの動作を最適化する必要があります。







IDA Pro( 私は個人的にradare2 + Cutterを使用しました )を使用して 、コードをCのような擬似コードに逆コンパイルしました。 コードが適切に逆コンパイルされていなかったにもかかわらず、内部で何が起こっているかについての情報をそれから得ることができます。







逆コンパイルされたコード
 __int64 __fastcall main(__int64 a1, char **a2, char **a3) { void *v3; // rbp@1 int v4; // ebx@1 signed __int64 v5; // r8@2 char v6; // r9@3 __int64 v7; // r8@3 char v8; // cl@3 __int64 v9; // r9@5 int a2a; // [sp+Ch] [bp-1Ch]@3 v3 = &encrypted_flag; v4 = 0; setbuf(stdout, 0LL); printf("The flag is: ", 0LL); while ( 1 ) { LODWORD(v5) = 0; do { a2a = 0; fibonacci(v4 + v5, &a2a); v8 = v7; v5 = v7 + 1; } while ( v5 != 8 ); v4 += 8; if ( (unsigned __int8)(a2a << v8) == v6 ) break; v3 = (char *)v3 + 1; _IO_putc((char)(v6 ^ ((_BYTE)a2a << v8)), stdout); v9 = *((char *)v3 - 1); } _IO_putc(10, stdout); return 0LL; }
      
      





 unsigned int __fastcall fibonacci(int i, _DWORD *a2) { _DWORD *v2; // rbp@1 unsigned int v3; // er12@3 unsigned int result; // eax@3 unsigned int v5; // edx@3 unsigned int v6; // esi@3 unsigned int v7; // edx@4 v2 = a2; if ( i ) { if ( i == 1 ) { result = fibonacci(0, a2); v5 = result - ((result >> 1) & 0x55555555); v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333; } else { v3 = fibonacci(i - 2, a2); result = v3 + fibonacci(i - 1, a2); v5 = result - ((result >> 1) & 0x55555555); v6 = ((result - ((result >> 1) & 0x55555555)) >> 2) & 0x33333333; } v7 = v6 + (v5 & 0x33333333) + ((v6 + (v5 & 0x33333333)) >> 4); *v2 ^= ((BYTE1(v7) & 0xF) + (v7 & 0xF) + (unsigned __int8)((((v7 >> 8) & 0xF0F0F) + (v7 & 0xF0F0F0F)) >> 16)) & 1; } else { *a2 ^= 1u; result = 1; } return result; }
      
      





メイン関数とフィボナッチ関数のアセンブラーコードを次に示します。







メイン
 .text:0x4004E0 main proc near ; DATA XREF: start+1Do .text:0x4004E0 .text:0x4004E0 var_1C = dword ptr -1Ch .text:0x4004E0 .text:0x4004E0 push rbp .text:0x4004E1 push rbx .text:0x4004E2 xor esi, esi ; buf .text:0x4004E4 mov ebp, offset unk_4007E1 .text:0x4004E9 xor ebx, ebx .text:0x4004EB sub rsp, 18h .text:0x4004EF mov rdi, cs:stdout ; stream .text:0x4004F6 call _setbuf .text:0x4004FB mov edi, offset format ; "The flag is: " .text:0x400500 xor eax, eax .text:0x400502 call _printf .text:0x400507 mov r9d, 49h .text:0x40050D nop dword ptr [rax] .text:0x400510 .text:0x400510 loc_400510: ; CODE XREF: main+8Aj .text:0x400510 xor r8d, r8d .text:0x400513 jmp short loc_40051B .text:0x400513 ; --------------------------------------------------------------------------- .text:0x400515 align 8 .text:0x400518 .text:0x400518 loc_400518: ; CODE XREF: main+67j .text:0x400518 mov r9d, edi .text:0x40051B .text:0x40051B loc_40051B: ; CODE XREF: main+33j .text:0x40051B lea edi, [rbx+r8] .text:0x40051F lea rsi, [rsp+28h+var_1C] .text:0x400524 mov [rsp+28h+var_1C], 0 .text:0x40052C call fibonacci .text:0x400531 mov edi, [rsp+28h+var_1C] .text:0x400535 mov ecx, r8d .text:0x400538 add r8, 1 .text:0x40053C shl edi, cl .text:0x40053E mov eax, edi .text:0x400540 xor edi, r9d .text:0x400543 cmp r8, 8 .text:0x400547 jnz short loc_400518 .text:0x400549 add ebx, 8 .text:0x40054C cmp al, r9b .text:0x40054F mov rsi, cs:stdout ; fp .text:0x400556 jz short loc_400570 .text:0x400558 movsx edi, dil ; c .text:0x40055C add rbp, 1 .text:0x400560 call __IO_putc .text:0x400565 movzx r9d, byte ptr [rbp-1] .text:0x40056A jmp short loc_400510 .text:0x40056A ; --------------------------------------------------------------------------- .text:0x40056C align 10h .text:0x400570 .text:0x400570 loc_400570: ; CODE XREF: main+76j .text:0x400570 mov edi, 0Ah ; c .text:0x400575 call __IO_putc .text:0x40057A add rsp, 18h .text:0x40057E xor eax, eax .text:0x400580 pop rbx .text:0x400581 pop rbp .text:0x400582 retn .text:0x400582 main endp
      
      





フィボナッチ
 .text:0x400670 fibonacci proc near ; CODE XREF: main+4Cp .text:0x400670 ; fibonacci+19p ... .text:0x400670 test edi, edi .text:0x400672 push r12 .text:0x400674 push rbp .text:0x400675 mov rbp, rsi .text:0x400678 push rbx .text:0x400679 jz short loc_4006F8 .text:0x40067B cmp edi, 1 .text:0x40067E mov ebx, edi .text:0x400680 jz loc_400710 .text:0x400686 lea edi, [rdi-2] .text:0x400689 call fibonacci .text:0x40068E lea edi, [rbx-1] .text:0x400691 mov r12d, eax .text:0x400694 mov rsi, rbp .text:0x400697 call fibonacci .text:0x40069C add eax, r12d .text:0x40069F mov edx, eax .text:0x4006A1 mov ebx, eax .text:0x4006A3 shr edx, 1 .text:0x4006A5 and edx, 55555555h .text:0x4006AB sub ebx, edx .text:0x4006AD mov ecx, ebx .text:0x4006AF mov edx, ebx .text:0x4006B1 shr ecx, 2 .text:0x4006B4 and ecx, 33333333h .text:0x4006BA mov esi, ecx .text:0x4006BC .text:0x4006BC loc_4006BC: ; CODE XREF: fibonacci+C2j .text:0x4006BC and edx, 33333333h .text:0x4006C2 lea ecx, [rsi+rdx] .text:0x4006C5 mov edx, ecx .text:0x4006C7 shr edx, 4 .text:0x4006CA add edx, ecx .text:0x4006CC mov esi, edx .text:0x4006CE and edx, 0F0F0F0Fh .text:0x4006D4 shr esi, 8 .text:0x4006D7 and esi, 0F0F0Fh .text:0x4006DD lea ecx, [rsi+rdx] .text:0x4006E0 mov edx, ecx .text:0x4006E2 shr edx, 10h .text:0x4006E5 add edx, ecx .text:0x4006E7 and edx, 1 .text:0x4006EA xor [rbp+0], edx .text:0x4006ED pop rbx .text:0x4006EE pop rbp .text:0x4006EF pop r12 .text:0x4006F1 retn .text:0x4006F1 ; --------------------------------------------------------------------------- .text:0x4006F2 align 8 .text:0x4006F8 .text:0x4006F8 loc_4006F8: ; CODE XREF: fibonacci+9j .text:0x4006F8 mov edx, 1 .text:0x4006FD xor [rbp+0], edx .text:0x400700 mov eax, 1 .text:0x400705 pop rbx .text:0x400706 pop rbp .text:0x400707 pop r12 .text:0x400709 retn .text:0x400709 ; --------------------------------------------------------------------------- .text:0x40070A align 10h .text:0x400710 .text:0x400710 loc_400710: ; CODE XREF: fibonacci+10j .text:0x400710 xor edi, edi .text:0x400712 call fibonacci .text:0x400717 mov edx, eax .text:0x400719 mov edi, eax .text:0x40071B shr edx, 1 .text:0x40071D and edx, 55555555h .text:0x400723 sub edi, edx .text:0x400725 mov esi, edi .text:0x400727 mov edx, edi .text:0x400729 shr esi, 2 .text:0x40072C and esi, 33333333h .text:0x400732 jmp short loc_4006BC .text:0x400732 fibonacci endp
      
      





この段階では、この問題を解決する多くの機会があります。 たとえば、プログラミング言語のいずれかを使用してコードを復元し、そこに最適化を適用できますが、コードを復元するプロセスは非常に困難なタスクであり、その間ミスを犯す可能性があります。 それでは、コードを比較してエラーを見つけることは一般に価値がありません。 しかし、Unicorn Engineを使用すれば、コードの再構築の段階をスキップして、上記の問題を回避できます。 もちろん、fridaを使用するかgdbのスクリプトを記述することで、これらの問題を回避できますが、これはそれに関するものではありません。







最適化を開始する前に、プログラムを変更せずにUnicornエンジンでエミュレーションを実行します。 そして、正常に起動した後にのみ、最適化に移りましょう。







ステップ1:仮想化を実現する



fibonacci.pyファイルを作成して、バイナリの横に保存します。







必要なライブラリをインポートすることから始めましょう。







 from unicorn import * from unicorn.x86_const import * import struct
      
      





最初の行は、メインのバイナリおよび基本的なユニコーン定数をロードします。 2行目は、2つのx86およびx86_64アーキテクチャの定数をロードします。







次に、いくつかの必要な機能を追加します。







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





ここで、後で必要になる関数を発表しました。









注: pwntoolsをインストールしている場合、これらの関数を作成する必要はありません。インポートするだけです。







 from pwn import *
      
      





そして最後に、x86_64アーキテクチャ向けのUnicorn Engineクラスの初期化を始めましょう。







 mu = Uc (UC_ARCH_X86, UC_MODE_64)
      
      





ここでは、次のパラメーターを使用してUc関数を呼び出します。









チートシートですべての定数を見つけることができます。







上で書いたように、Unicorn Engineを使用するには、仮想メモリを手動で初期化する必要があります。 この例では、コードとスタックをメモリのどこかに配置する必要があります。







バイナリのベースアドレス(ベースアドレス)は0x400000から始まります。 スタックを0x0に置き、1024 * 1024のメモリを割り当てましょう。 ほとんどの場合、それほど多くのスペースは必要ありませんが、それでも痛みはありません。







mem_mapメソッドを呼び出すことで、メモリをマークアップできます。







次の行を追加します。







 BASE = 0x400000 STACK_ADDR = 0x0 STACK_SIZE = 1024*1024 mu.mem_map(BASE, 1024*1024) mu.mem_map(STACK_ADDR, STACK_SIZE)
      
      





次に、ブートローダーと同じ方法でバイナリをメインアドレスにロードする必要があります。 その後、 RSPをスタックの最後に設定する必要があります。







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





これでエミュレーションを開始してコードを実行できますが、どのアドレスで作業を開始し、いつエミュレータを停止する必要があるかを把握する必要があります。







main()から最初のコマンドのアドレスを取得し、0x004004e0からエミュレーションを開始できます。 最後は、フラグ全体を表示した後、0x00400575にあるputc( "\ n")の呼び出しになります。







 .text:0x400570 mov edi, 0Ah ; c .text:0x400575 call __IO_putc
      
      





エミュレートを開始できます:







 mu.emu_start(0x004004e0,0x00400575)
      
      





次に、スクリプトを実行します。







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





おっと、何かがおかしくなりましたが、何もわかりません。 mu.emu_startを呼び出す直前に、以下を追加できます。







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





このコードはフックを追加します。 各コマンドの前にエミュレーターによって呼び出される独自のhook_code関数を宣言します。 以下のパラメーターを受け入れます。










All Articles