挑战4个任务:迅速上手Unicorn Engine

阅读量308033

|评论1

|

发布时间 : 2018-01-18 16:32:39

x
译文声明

本文是翻译文章,文章原作者 eternal,文章来源:eternal.red

原文地址:http://eternal.red/2018/unicorn-engine-tutorial/

译文仅供参考,具体内容表达以及含义原文为准。

前言

在这篇教程中,您将通过解决实际问题来练习如何使用Unicorn Engine。一共有4个练习,其中我将会详细讲解第一个练习,而对于其他练习我们会提供提示和解决方案供大家阅读。

FAQ:

1、什么是Unicorn Engine?

Unicore Engine是一个模拟器,尽管并不太常见。通过该模拟器,您不用模仿整个程序或系统。这一模拟器不支持系统调用,必须先映射内存,并手动将数据写入到内存中,然后才能从指定的地址开始模拟。

2、这篇文章中的内容可以用于什么场景?

我们可以在不创建有害进程的前提下,从恶意软件中调用一个特定的函数。此外还可以用于CTF比赛,用于基于漏洞注入的自动软件测试,也可以用于能预测未来的gdb插件(例如实现进一步的跳转),还可以用来仿真混淆的代码。

3、要开始本教程的练习,我需要安装什么?

需要安装Unicorn Engine,并连接Python。此外,还需要一个反汇编工具。

 

任务1

该任务来自hxp CTF 2017,名称为斐波那契,地址为:https://ctftime.org/event/489
二进制文件可以在这里下载:http://eternal.red/assets/files/2017/UE/fibonacci
当我们运行这个程序的时候,我们可以注意到,它会计算并打印我们的Flag,但这个过程非常缓慢,并且Flag的计算过程会随着字节的增多变得越来越慢。
该题的Flag为:hxp{F。
这就意味着,我们需要对程序进行优化,以在合理的时间内得到Flag。
在IDA Pro的帮助下,我们将代码反编译成像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

fibonacci函数的汇编代码如下:

.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。
在优化之前,我们首先模拟正常的程序,一旦程序成功运行后,我们再在Unicorn Engine中对其进行优化。

第一部分:模拟程序

首先我们创建一个名为fibonacci.py的文件,并将二进制文件放在同一个文件夹下。
将下面的代码添加到文件中:

from unicorn import *
from unicorn.x86_const import *

其中,第一行加载主二进制程序以及基本的Unicorn Constant,第二行加载特定于x86和x86-64体系结构的Constant。
接下来,添加如下几行:

import struct

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)

在这里,我们只添加了一些通常的功能,这些功能稍后会对我们有所帮助。
其中,read会返回整个文件的内容。u32需要一个4字节的字符串,并将其转换为一个整数,以低字节序表示这个数据。p32正相反,它需要一个数字,并将其转换为4字节的字符串,以低字节序表示。
如果你安装了pwntools,那么你就不需要创建这些函数,只需要通过pwn import *导入即可。
接下来,让我们初始化我们Unicorn Engine的类,以适应x86-64架构:

mu = Uc (UC_ARCH_X86, UC_MODE_64)

我们需要使用下面的参数来调用函数Uc:
1、主结构分支,其中的Constant以UCARCH开始;
2、进一步的架构规范,其中的Constant以UCMODE开始。
您可以在本文后面的参考内容中,找到架构Constant的完整列表。
正如我们之前所说的,要使用Unicorn Engine,我们需要手动初始化虚拟内存。对于这个二进制文件,我们需要在其中的某个位置编写代码,并分配一个栈。
二进制的基址是0x400000。我们的栈将从地址0x000000开始,大小为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)

在开始模拟并运行代码之前,我们首先需要知道开始地址在哪里,并且要知道模拟器应该在哪里停止。
我们可以开始模拟位于地址0x4004E0的代码,这是main的第一个地址。结束位置可以选择0x400575,这是putc("n")的位置,会在打印完整个Flag后被调用。如下所示:

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

我们可以开始模拟:

mu.emu_start(0x00000000004004E0, 0x0000000000400575)

现在,可以运行这个脚本:

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,在模拟每个指令前被调用。该函数需要以下参数:
1、Uc实例
2、指令的地址
3、指令的大小
4、用户数据(我们可以在hook_add()的可选参数中传递这个值)
相关源代码请参考solve1.py:http://eternal.red/assets/files/2017/UE/solve1.py
运行时,我们可以看到:

a@x:~/Desktop/unicorn_engine_lessons$ python solve.py 
>>> Tracing instruction at 0x4004e0, instruction size = 0x1
>>> Tracing instruction at 0x4004e1, instruction size = 0x1
>>> Tracing instruction at 0x4004e2, instruction size = 0x2
>>> Tracing instruction at 0x4004e4, instruction size = 0x5
>>> Tracing instruction at 0x4004e9, instruction size = 0x2
>>> Tracing instruction at 0x4004eb, instruction size = 0x4
>>> Tracing instruction at 0x4004ef, instruction size = 0x7
Traceback (most recent call last):
  File "solve.py", line 41, 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)

这意味着,脚本在执行以下指令时失败:

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

该指令从地址0x601038读取内存(可以在IDA Pro中看到)。这是.bss段,并不是由我们分配的。因此我们的解决方案是跳过所有有问题的指令。
下面有一条指令:

.text:0x4004F6                 call    _setbuf

我们并不能调用任何glibc函数,因为此前并没有将glibc加载到虚拟内存中。事实上,我们并不需要调用这个函数,所以也可以跳过它。
下面是我们需要跳过的指令列表:

.text:0x4004EF                 mov     rdi, cs:stdout  ; stream
.text:0x4004F6                 call    _setbuf
.text:0x400502                 call    _printf
.text:0x40054F                 mov     rsi, cs:stdout  ; fp

我们可以通过将地址写入下一条指令的RIP寄存器来跳过指令:

mu.reg_write(UC_X86_REG_RIP, address+size)

hook_code现在应该是这样的:

instructions_skip_list = [0x00000000004004EF, 0x00000000004004F6, 0x0000000000400502, 0x000000000040054F]

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

    if address in instructions_skip_list:
        mu.reg_write(UC_X86_REG_RIP, address+size)

此外,我们还需要对逐字节打印Flag的指令进行一些操作。

.text:0x400558                 movsx   edi, dil        ; c
.text:0x40055C                 add     rbp, 1
.text:0x400560                 call    __IO_putc

__IO_putc需要一个字节,以打印出第一个参数(即寄存器RDI)。
我们可以从寄存器RDI中读取一个值并打印出来,同时跳过模拟这个指令。此时的hook_code函数如下所示:

instructions_skip_list = [0x00000000004004EF, 0x00000000004004F6, 0x0000000000400502, 0x000000000040054F]

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

    if address in instructions_skip_list:
        mu.reg_write(UC_X86_REG_RIP, address+size)

    elif address == 0x400560: #that instruction writes a byte of the flag
        c = mu.reg_read(UC_X86_REG_RDI)
        print(chr(c))
        mu.reg_write(UC_X86_REG_RIP, address+size)

相关源代码请参考solve2.py:http://eternal.red/assets/files/2017/UE/solve2.py
接下来,便可以运行,我们发现它确实可以正常工作,但速度还是很慢。

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

第二部分:提速

接下来,让我们考虑一下提速的方法。为什么这个程序的运行速度如此之慢?
查看反编译的代码,我们可以看到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 = 0x0000000000400670
FIBONACCI_END = [0x00000000004006F1, 0x0000000000400709]

stack = []                                          # Stack for storing the arguments
d = {}                                              # Dictionary that holds return values for given function arguments 

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

    if address in instructions_skip_list:
        mu.reg_write(UC_X86_REG_RIP, address+size)

    elif address == 0x400560:                       # That instruction writes a byte of the flag
        c = mu.reg_read(UC_X86_REG_RDI)
        print(chr(c))
        mu.reg_write(UC_X86_REG_RIP, address+size)

    elif address == FIBONACCI_ENTRY:                # Are we at the beginning of fibonacci function?
        arg0 = mu.reg_read(UC_X86_REG_RDI)          # Read the first argument. Tt is passed via RDI
        r_rsi = mu.reg_read(UC_X86_REG_RSI)         # Read the second argument which is a reference
        arg1 = u32(mu.mem_read(r_rsi, 4))           # Read the second argument from reference

        if (arg0,arg1) in d:                        # Check whether return values for this function are already saved.
            (ret_rax, ret_ref) = d[(arg0,arg1)]
            mu.reg_write(UC_X86_REG_RAX, ret_rax)   # Set return value in RAX register
            mu.mem_write(r_rsi, p32(ret_ref))       # Set retun value through reference
            mu.reg_write(UC_X86_REG_RIP, 0x400582)  # Set RIP to point at RET instruction. We want to return from fibonacci function

        else:
            stack.append((arg0,arg1,r_rsi))         # If return values are not saved for these arguments, add them to stack.

    elif address in FIBONACCI_END:
        (arg0, arg1, r_rsi) = stack.pop()           # We know arguments when exiting the function

        ret_rax = mu.reg_read(UC_X86_REG_RAX)       # Read the return value that is stored in RAX
        ret_ref = u32(mu.mem_read(r_rsi,4))         # Read the return value that is passed reference
        d[(arg0, arg1)]=(ret_rax, ret_ref)          # Remember the return values for this argument pair

完整脚本请参考solve3.py:http://eternal.red/assets/files/2017/UE/solve3.py
至此,我们已经成功地使用Unicorn Engine来优化程序。
接下来,我推荐大家完成下面三个任务的练习。针对每个任务,都有提示和解决方案,并且在解决任务的过程中,可以查看后文的参考内容。
我认为,其中一个很重要的问题就是要知道Constant的名称。处理这个问题的最好方法是借助IPython的自动补全(Tab Completion)来完成。在你安装IPython之后,可以输入from unicorn import UCARCH,并按TAB键,所有以这个前缀开头的Constant都将被打印出来。

任务2

分析下列Shellcode:

shellcode = "xe8xffxffxffxffxc0x5dx6ax05x5bx29xddx83xc5x4ex89xe9x6ax02x03x0cx24x5bx31xd2x66xbax12x00x8bx39xc1xe7x10xc1xefx10x81xe9xfexffxffxffx8bx45x00xc1xe0x10xc1xe8x10x89xc3x09xfbx21xf8xf7xd0x21xd8x66x89x45x00x83xc5x02x4ax85xd2x0fx85xcfxffxffxffxecx37x75x5dx7ax05x28xedx24xedx24xedx0bx88x7fxebx50x98x38xf9x5cx96x2bx96x70xfexc6xffxc6xffx9fx32x1fx58x1ex00xd3x80"

如你所见,该程序集被混淆了(命令disarm是pwntools的一个功能):

a@x:~/Desktop/unicorn_engine_lessons$ disasm e8ffffffffc05d6a055b29dd83c54e89e96a02030c245b31d266ba12008b39c1e710c1ef1081e9feffffff8b4500c1e010c1e81089c309fb21f8f7d021d86689450083c5024a85d20f85cfffffffec37755d7a0528ed24ed24ed0b887feb509838f95c962b9670fec6ffc6ff9f321f581e00d380
   0:    e8 ff ff ff ff           call   0x4
   5:    c0 5d 6a 05              rcr    BYTE PTR [ebp+0x6a], 0x5
   9:    5b                       pop    ebx
   a:    29 dd                    sub    ebp, ebx
   c:    83 c5 4e                 add    ebp, 0x4e
   f:    89 e9                    mov    ecx, ebp
  11:    6a 02                    push   0x2
  13:    03 0c 24                 add    ecx, DWORD PTR [esp]
  16:    5b                       pop    ebx
  17:    31 d2                    xor    edx, edx
  19:    66 ba 12 00              mov    dx, 0x12
  1d:    8b 39                    mov    edi, DWORD PTR [ecx]
  1f:    c1 e7 10                 shl    edi, 0x10
  22:    c1 ef 10                 shr    edi, 0x10
  25:    81 e9 fe ff ff ff        sub    ecx, 0xfffffffe
  2b:    8b 45 00                 mov    eax, DWORD PTR [ebp+0x0]
  2e:    c1 e0 10                 shl    eax, 0x10
  31:    c1 e8 10                 shr    eax, 0x10
  34:    89 c3                    mov    ebx, eax
  36:    09 fb                    or     ebx, edi
  38:    21 f8                    and    eax, edi
  3a:    f7 d0                    not    eax
  3c:    21 d8                    and    eax, ebx
  3e:    66 89 45 00              mov    WORD PTR [ebp+0x0], ax
  42:    83 c5 02                 add    ebp, 0x2
  45:    4a                       dec    edx
  46:    85 d2                    test   edx, edx
  48:    0f 85 cf ff ff ff        jne    0x1d
  4e:    ec                       in     al, dx
  4f:    37                       aaa
  50:    75 5d                    jne    0xaf
  52:    7a 05                    jp     0x59
  54:    28 ed                    sub    ch, ch
  56:    24 ed                    and    al, 0xed
  58:    24 ed                    and    al, 0xed
  5a:    0b 88 7f eb 50 98        or     ecx, DWORD PTR [eax-0x67af1481]
  60:    38 f9                    cmp    cl, bh
  62:    5c                       pop    esp
  63:    96                       xchg   esi, eax
  64:    2b 96 70 fe c6 ff        sub    edx, DWORD PTR [esi-0x390190]
  6a:    c6                       (bad)
  6b:    ff 9f 32 1f 58 1e        call   FWORD PTR [edi+0x1e581f32]
  71:    00 d3                    add    bl, dl
  73:    80                       .byte 0x80

请注意,目前的架构是x86-32。系统调用的列表可以在这里查看:https://syscalls.kernelgrok.com/

提示

您可以Hook一个int 80h指令,它由cd 80表示。接下来,您可以读取寄存器和内存。需要记住的是,Shellcode是一个可以在任何地址加载的代码,绝大多数Shellcode都使用了栈。

解决方案

下面的代码是通过几个步骤创建而成的。通过UE错误信息,我们获得了一些线索,并想到了最终的解决方案。

from unicorn import *
from unicorn.x86_const import *

shellcode = "xe8xffxffxffxffxc0x5dx6ax05x5bx29xddx83xc5x4ex89xe9x6ax02x03x0cx24x5bx31xd2x66xbax12x00x8bx39xc1xe7x10xc1xefx10x81xe9xfexffxffxffx8bx45x00xc1xe0x10xc1xe8x10x89xc3x09xfbx21xf8xf7xd0x21xd8x66x89x45x00x83xc5x02x4ax85xd2x0fx85xcfxffxffxffxecx37x75x5dx7ax05x28xedx24xedx24xedx0bx88x7fxebx50x98x38xf9x5cx96x2bx96x70xfexc6xffxc6xffx9fx32x1fx58x1ex00xd3x80" 


BASE = 0x400000
STACK_ADDR = 0x0
STACK_SIZE = 1024*1024

mu = Uc (UC_ARCH_X86, UC_MODE_32)

mu.mem_map(BASE, 1024*1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)


mu.mem_write(BASE, shellcode)
mu.reg_write(UC_X86_REG_ESP, STACK_ADDR + STACK_SIZE/2)

def syscall_num_to_name(num):
    syscalls = {1: "sys_exit", 15: "sys_chmod"}
    return syscalls[num]

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

    machine_code = mu.mem_read(address, size)
    if machine_code == "xcdx80":

        r_eax = mu.reg_read(UC_X86_REG_EAX)
        r_ebx = mu.reg_read(UC_X86_REG_EBX)
        r_ecx = mu.reg_read(UC_X86_REG_ECX)
        r_edx = mu.reg_read(UC_X86_REG_EDX)
        syscall_name = syscall_num_to_name(r_eax)

        print "--------------"
        print "We intercepted system call: "+syscall_name

        if syscall_name == "sys_chmod":
            s = mu.mem_read(r_ebx, 20).split("x00")[0]
            print "arg0 = 0x%x -> %s" % (r_ebx, s)
            print "arg1 = " + oct(r_ecx)
        elif syscall_name == "sys_exit":
            print "arg0 = " + hex(r_ebx)
            exit()

        mu.reg_write(UC_X86_REG_EIP, address + size)

mu.hook_add(UC_HOOK_CODE, hook_code)

mu.emu_start(BASE, BASE-1)

最终代码如下:

a@x:~/Desktop/unicorn_engine_lessons$ python solve_task2.py
--------------
We intercepted system call: sys_chmod
arg0 = 0x400058 -> /etc/shadow
arg1 = 0666L
--------------
We intercepted system call: sys_exit
arg0 = 0x400058L

任务3

下载二进制文件( http://eternal.red/assets/files/2017/UE/function ),该文件是用以下命令编译的:
gcc function.c -m32 -o function
这个二进制代码如下所示:

int strcmp(char *a, char *b)
{
    //get length
    int len = 0;
    char *ptr = a;
    while(*ptr)
    {
        ptr++;
        len++;
    }

    //comparestrings
    for(int i=0; i<=len; i++)
    {
        if (a[i]!=b[i])
            return 1;
    }

    return 0;
}

__attribute__((stdcall))
int  super_function(int a, char *b)
{
    if (a==5 && !strcmp(b, "batman"))
    {
        return 1;
    }
    return 0;
}

int main()
{
    super_function(1, "spiderman");
}

任务是调用super_function,使其返回1。
其汇编代码如下:

.text:0x8048464 super_function  proc near               ; CODE XREF: main+16p
.text:0x8048464
.text:0x8048464 arg_0           = dword ptr  8
.text:0x8048464 arg_4           = dword ptr  0Ch
.text:0x8048464
.text:0x8048464                 push    ebp
.text:0x8048465                 mov     ebp, esp
.text:0x8048467                 call    __x86_get_pc_thunk_ax
.text:0x804846C                 add     eax, 1B94h
.text:0x8048471                 cmp     [ebp+arg_0], 5
.text:0x8048475                 jnz     short loc_8048494
.text:0x8048477                 lea     eax, (aBatman - 804A000h)[eax] ; "batman"
.text:0x804847D                 push    eax
.text:0x804847E                 push    [ebp+arg_4]
.text:0x8048481                 call    strcmp
.text:0x8048486                 add     esp, 8
.text:0x8048489                 test    eax, eax
.text:0x804848B                 jnz     short loc_8048494
.text:0x804848D                 mov     eax, 1
.text:0x8048492                 jmp     short locret_8048499
.text:0x8048494 ; ---------------------------------------------------------------------------
.text:0x8048494
.text:0x8048494 loc_8048494:                            ; CODE XREF: super_function+11j
.text:0x8048494                                         ; super_function+27j
.text:0x8048494                 mov     eax, 0
.text:0x8048499
.text:0x8048499 locret_8048499:                         ; CODE XREF: super_function+2Ej
.text:0x8048499                 leave
.text:0x804849A                 retn    8
.text:0x804849A super_function  endp

提示

根据stdcall调用约定,当模拟过程开始时,栈应该如下图所示。我们看到在下图中,RET只是返回地址(可以为任意值)。

解决方案

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


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)

mu = Uc (UC_ARCH_X86, UC_MODE_32)

BASE = 0x08048000
STACK_ADDR = 0x0
STACK_SIZE = 1024*1024

mu.mem_map(BASE, 1024*1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)


mu.mem_write(BASE, read("./function"))
r_esp = STACK_ADDR + (STACK_SIZE/2)     #ESP points to this address at function call

STRING_ADDR = 0x0
mu.mem_write(STRING_ADDR, "batmanx00") #write "batman" somewhere. We have choosen an address 0x0 which belongs to the stack.

mu.reg_write(UC_X86_REG_ESP, r_esp)     #set ESP
mu.mem_write(r_esp+4, p32(5))           #set the first argument. It is integer 5
mu.mem_write(r_esp+8, p32(STRING_ADDR)) #set the second argument. This is a pointer to the string "batman"


mu.emu_start(0x8048464, 0x804849A)      #start emulation from the beginning of super_function, end at RET instruction
return_value = mu.reg_read(UC_X86_REG_EAX)
print "The returned value is: %d" % return_value
a@x:~/Desktop/unicorn_engine_lessons$ python solve_task3.py 
The returned value is: 1

任务4

这个任务与任务1类似,但不同之处就在于这里的架构不再是x86,而是低字节序的ARM32。

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

二进制文件下载地址为:http://eternal.red/assets/files/2017/UE/task4
参考这篇资料可能会有所帮助:http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf

正确答案

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 *
from unicorn.arm_const import *


import struct

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)


mu = Uc (UC_ARCH_ARM, UC_MODE_LITTLE_ENDIAN)


BASE = 0x10000
STACK_ADDR = 0x300000
STACK_SIZE = 1024*1024

mu.mem_map(BASE, 1024*1024)
mu.mem_map(STACK_ADDR, STACK_SIZE)


mu.mem_write(BASE, read("./task4"))
mu.reg_write(UC_ARM_REG_SP, STACK_ADDR + STACK_SIZE/2)

instructions_skip_list = []

CCC_ENTRY = 0x000104D0
CCC_END = 0x00010580

stack = []                                          # Stack for storing the arguments
d = {}                                              # Dictionary that holds return values for given function arguments 

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

    if address == CCC_ENTRY:                        # Are we at the beginning of ccc function?
        arg0 = mu.reg_read(UC_ARM_REG_R0)           # Read the first argument. it is passed by R0

        if arg0 in d:                               # Check whether return value for this function is already saved.
            ret = d[arg0]
            mu.reg_write(UC_ARM_REG_R0, ret)        # Set return value in R0
            mu.reg_write(UC_ARM_REG_PC, 0x105BC)    # Set PC to point at "BX LR" instruction. We want to return from fibonacci function

        else:
            stack.append(arg0)                      # If return value is not saved for this argument, add it to stack.

    elif address == CCC_END:
        arg0 = stack.pop()                          # We know arguments when exiting the function

        ret = mu.reg_read(UC_ARM_REG_R0)            # Read the return value (R0)
        d[arg0] = ret                               # Remember the return value for this argument

mu.hook_add(UC_HOOK_CODE, hook_code)

mu.emu_start(0x00010584, 0x000105A8)

return_value = mu.reg_read(UC_ARM_REG_R1)           # We end the emulation at printf("%dn", ccc(x)).
print "The return value is %d" % return_value

参考内容

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_ALL
UC_API_MINOR                UC_ERR_WRITE_PROT           UC_MEM_READ_AFTER           UC_PROT_EXEC
UC_ARCH_ARM                 UC_ERR_WRITE_UNALIGNED      UC_MEM_READ_PROT            UC_PROT_NONE
UC_ARCH_ARM64               UC_ERR_WRITE_UNMAPPED       UC_MEM_READ_UNMAPPED        UC_PROT_READ
UC_ARCH_M68K                UC_HOOK_BLOCK               UC_MEM_WRITE                UC_PROT_WRITE
UC_ARCH_MAX                 UC_HOOK_CODE                UC_MEM_WRITE_PROT           UC_QUERY_MODE
UC_ARCH_MIPS                UC_HOOK_INSN                UC_MEM_WRITE_UNMAPPED       UC_QUERY_PAGE_SIZE
UC_ARCH_PPC                 UC_HOOK_INTR                UC_MILISECOND_SCALE         UC_SECOND_SCALE
UC_ARCH_SPARC               UC_HOOK_MEM_FETCH           UC_MODE_16                  UC_VERSION_EXTRA
UC_ARCH_X86                 UC_HOOK_MEM_FETCH_INVALID   UC_MODE_32                  UC_VERSION_MAJOR
UC_ERR_ARCH                 UC_HOOK_MEM_FETCH_PROT      UC_MODE_64                  UC_VERSION_MINOR
UC_ERR_ARG                  UC_HOOK_MEM_FETCH_UNMAPPED  UC_MODE_ARM                 Uc
UC_ERR_EXCEPTION            UC_HOOK_MEM_INVALID         UC_MODE_BIG_ENDIAN          UcError
UC_ERR_FETCH_PROT           UC_HOOK_MEM_PROT            UC_MODE_LITTLE_ENDIAN       arm64_const
UC_ERR_FETCH_UNALIGNED      UC_HOOK_MEM_READ            UC_MODE_MCLASS              arm_const
UC_ERR_FETCH_UNMAPPED       UC_HOOK_MEM_READ_AFTER      UC_MODE_MICRO               debug
UC_ERR_HANDLE               UC_HOOK_MEM_READ_INVALID    UC_MODE_MIPS3               m68k_const
UC_ERR_HOOK                 UC_HOOK_MEM_READ_PROT       UC_MODE_MIPS32              mips_const
UC_ERR_HOOK_EXIST           UC_HOOK_MEM_READ_UNMAPPED   UC_MODE_MIPS32R6            sparc_const
UC_ERR_INSN_INVALID         UC_HOOK_MEM_UNMAPPED        UC_MODE_MIPS64              uc_arch_supported
UC_ERR_MAP                  UC_HOOK_MEM_VALID           UC_MODE_PPC32               uc_version
UC_ERR_MODE                 UC_HOOK_MEM_WRITE           UC_MODE_PPC64               unicorn
UC_ERR_NOMEM                UC_HOOK_MEM_WRITE_INVALID   UC_MODE_QPX                 unicorn_const
UC_ERR_OK                   UC_HOOK_MEM_WRITE_PROT      UC_MODE_SPARC32             version_bind
UC_ERR_READ_PROT            UC_HOOK_MEM_WRITE_UNMAPPED  UC_MODE_SPARC64             x86_const
UC_ERR_READ_UNALIGNED       UC_MEM_FETCH                UC_MODE_THUMB               
UC_ERR_READ_UNMAPPED        UC_MEM_FETCH_PROT           UC_MODE_V8                  
UC_ERR_RESOURCE             UC_MEM_FETCH_UNMAPPED       UC_MODE_V9

来自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):  
    print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))  

mu.hook_add(UC_HOOK_CODE, hook_code)

这段代码添加了一个钩子。我们定义了函数hook_code,在模拟每个指令之前调用,该函数需要以下参数:
1、Uc实例
2、指令的地址
3、指令的大小
4、用户数据(我们可以在hook_add()的可选参数中传递这个值)

参考资料

  1. 关于Unicorn Engine的基本介绍:
    http://www.unicorn-engine.org/BHUSA2015-unicorn.pdf
  2. Oh look, there are bindings for many languages:
    https://github.com/unicorn-engine/unicorn/tree/master/bindings
  3. Unicorn Engine参考:
    https://hackmd.io/s/rJTUtGwuW#
  4. 官方UE教程:
    http://www.unicorn-engine.org/docs/tutorial.html

 

本文翻译自eternal.red 原文链接。如若转载请注明出处。
分享到:微信
+14赞
收藏
P!chu
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66