1

[翻译]Unicorn引擎教程

zplusplus 2018-1-27 18:09 1063

Unicorn引擎教程

在该篇教程中,你将通过实际操作来学习Unicorn引擎。接下来将会有4个练习,我会解决第一个。对于其他的我将提供提示和解决方案。(译注:原网页中的提示和解决方案内容通过按钮可以显示或者隐藏,翻译后的文章不具备此功能,所有内容将直接呈现出来)

 

FAST FAQ:

  • Unocorn引擎是什么?

    简单的来讲,一款模拟器。尽管不太常见,你不能用来模拟整个程序或者系统,同时它也不支持syscall。你只能通过手动的方式来映射内存以及数据写入,然后就可以从某个指定的地址开始执行模拟了。

  • 模拟器在什么时候是有用的?

    • 你可以执行一些恶意软件中你感兴趣的函数而不必创建整个进程
    • CTF比赛中也很常用
    • Fuzzing
    • GDB插件扩充,例如支持长跳转
    • 模拟混淆后的代码
  • 在这篇教程开始之前有什么需要准备的?

    • 安装带有Python支持的Unicorn引擎
    • 一个反汇编器

目录

  • 任务1
  • 一些笔记
  • 任务2
  • 任务3
  • 任务4
  • 备忘录
  • 参考

任务1

该任务是hxp CTF 2017上的一个叫做Fibonacci(译注:斐波那契)的例子。二进制文件可以从这里下载

 

当我们运行这个程序的时候,可以注意到这个程序计算和输出Flag非常的慢。Flag的下一个字节计算的越来越慢。

 

The Flag is: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;
}

接下来是main函数的反汇编代码

.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

有许多种方法来解决该任务。例如,可以通过某种编程语言重新构建代码,然后在该语言中应用优化。重建代码的过程并不容易,可能会引入一些BUG和错误。盯着代码找错误并不好笑。。通过Unicorn引擎来解决这个任务可以跳过重构代码的过程,避免上面提到的问题。当然也可以通过其他的几种方法来避免重写代码-例如gdb脚本或者使用Frida.

 

在开始优化之前,我们先来用Unicorn引擎模拟一个正常的程序,不进行优化,成功之后,再开始优化。

Part1:模拟一个程序

首先,创建一个名为fibonacci.py的脚本,然后将二进制文件放到同一目录下。

 

先向脚本中添加如下代码:

from unicorn import *
from unicorn.x86_const import *

第一行代码会加载主要的二进制模块和一些Unicorn中的一些基本常量。第二行加载了一些特定的x86和x64的常量。

 

接下来,加入以下代码:

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字节的string(译注:python2中的string等同于bytes array)转换为integer,以小端序表示这个数据。

 

p32u32相反,将一个数字转换为以小端序保存的4字节string.

 

如果你安装了pwntools,可以不创建这两个函数,直接from pwn import*就可以了。

 

接下来为x86-64架构初始化一下Unicorn引擎。

 

mu = Uc (UC_ARCH_X86, UC_MODE_64)

 

Uc函数需要一下参数:

  • 第一个参数:架构类型。这些常量以UC_ATCH_为前缀
  • 第二个参数:架构细节说明。这些常量以UC_MODE_为前缀

你可以在备忘录中找到这些常量。

 

正如我之前所写,使用Unicorn引擎之前需要手动初始化内存。针对这个二进制文件来说,我们需要将代码写入到某个地方,并且分配一些栈空间。

 

二进制文件的基址是0x400000。栈的话不妨从地址0x0开始,大小为1024*1024字节(译注:即1M)。或许并不需要这么大的栈空间,但是也没有多大的影响。

 

可以使用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)

可以开始模拟和执行代码了,但是需要先知道从哪开始从哪结束。

 

我们可以从地址0x00000000004004E0 开始执行代码,这是main函数开始的地方。结尾的话可以是0x0000000000400575。这是puts("\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)

oooooops,发生了一些我们不知道的错误呀。在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。我们自定的函数hook_code在执行每一条代码模拟的时候都将被调用。参数如下:

  • Uc实例句柄
  • 指令的地址
  • 执行的长度
  • 用户自定义数据(我们可以在hook_add的可选参数中传递这个值)

此时,我们的脚本应该是这样子的 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.

 

可以运行以下看一下,尽管它依然很慢。

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

Part2:提升速度!

考虑以下速度提升,为什么这个程序跑这么慢?

 

观察一下反编译代码,我们可以看到main函数调用了fibonacci函数若干次,并且fibonacci函数是个递归函数。

 

看看这个函数,我们可以看到它有2个参数,返回2个值。第一个返回值存放在RAX寄存器中,第二个是第二个参数的指针。深入研究一下mainfibonacci函数可以发现,第二个参数只能取值为0或者1。如果发现不了的话,可以运行gdb然后在fibonacci函数的起始地址下个断点观察。

 

为了优化这个函数,我们可以使用动态编程来记住所给参数的返回值。因为第二个参数只有两种取值,只需记住只记住2 * MAX_OF_FIRST_ARGUMENT对就足够了。

 

可以在RIP指向fibonacci函数开始的时候获取函数的参数,值的返回在函数退出的时候。因为不能同时获取这两个值,所以我们需要一个栈来帮助我们在函数退出的时候获取这些值-在fibonacci入口我们需要将参数入栈,最后的时候出栈。可以使用dict来记住这些值。

 

如何保存这些成对的值?

  • 在函数开始的时候,我们可以检查参数对应的值是否已经被dict记录
    • 如果是,直接返回这个key-value就行,只需将返回值写入到RAX中,同时设置RIPRET指令的值,退出这个函数。不能在fabonacci函数内直接跳转到RET,因为这条指令已经被HOOK了,所以我们跳转到main中的ret
    • 如果dict中没有出现参数和对应的值,将参数添加到dict中。
  • 当退出函数的时候,保存返回值。可以从我们的栈结构中读取参数和返回值。

代码如下:

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

 

欢呼吧!我们已经成功地使用Unicorn引擎来优化程序。非常棒!

一些笔记

现在,我强烈建议你做一些小作业。在下边你可以找到我之前说的三个任务,每一个都有提示和可行的解决方案。你可以在解决这些小任务的时候参考一下备忘录

 

我觉得其中之一的难题是知道一些感兴趣的常量的名字。最好的方法是使用IPython tab completion。当你安装IPyhton后,你可以输入from unicorn import UC_ARCH_然后使用TAB键,然后所有的以UC_ARCH_为前缀的常量都将被列举出来。

任务2

分析一下代码:

shellcode = "\xe8\xff\xff\xff\xff\xc0\x5d\x6a\x05\x5b\x29\xdd\x83\xc5\x4e\x89\xe9\x6a\x02\x03\x0c\x24\x5b\x31\xd2\x66\xba\x12\x00\x8b\x39\xc1\xe7\x10\xc1\xef\x10\x81\xe9\xfe\xff\xff\xff\x8b\x45\x00\xc1\xe0\x10\xc1\xe8\x10\x89\xc3\x09\xfb\x21\xf8\xf7\xd0\x21\xd8\x66\x89\x45\x00\x83\xc5\x02\x4a\x85\xd2\x0f\x85\xcf\xff\xff\xff\xec\x37\x75\x5d\x7a\x05\x28\xed\x24\xed\x24\xed\x0b\x88\x7f\xeb\x50\x98\x38\xf9\x5c\x96\x2b\x96\x70\xfe\xc6\xff\xc6\xff\x9f\x32\x1f\x58\x1e\x00\xd3\x80"

如你所见,代码被混淆了(命令disasmpwntools的一部分):

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架构。syscall的调用号所对应的功能可以在这里找到

 

提示

 

你可以HOOKint 80h指令,二进制代码是cd 80,然后读取寄存器和内存。请记住,shellcode是可以在任何地址被加载执行的代码,绝大多数的shellcode使用栈来执行。

 

解决方案

 

在本贴回复中

任务3

可以从这里下载这个二进制文件。该代码使用了以下的编译选项:

 

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仅仅是返回某个地址(可以是任何值)。

 

 

解决方案

 

在本贴回复中

任务4

这个任务和第一个很像。区别是目标架构不是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

可以在这里下载到二进制文件

 

ARM调用约定可能会帮助到你

 

正确答案:

2635833876

提示

  • 函数的第一个参数通过R0传递(UC_ARM_REG_R0)
  • 返回值同样在R0
  • 函数的第二个参数通过R1传递(UC_ARM_REG_R1)
  • 可以通过mu = Uc (UC_ARCH_ARM,UC_MODE_LITTLE_ENDIAN)来初始化Unicorn。

解决方案

 

在本贴回复中

备忘录

from unicorn import * - 加载Unicorn库。包含一些函数和基本的常量。

 

from unicorn.x86_const import* - 加载 X86 和X64架构相关的常量

 

所有unicorn模块中的常量

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中的常量

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架构的实例。
  • mu = Uc(UC_ARCH_X86,UC_MODE_32) - 获取X86-32架构的实例。

mu.mem_map(ADDRESS,4096) - 映射一片内存区域

 

mu.mem_write(ADDRESS,DATA) - 向内存中写入数据

 

tmp = mu.mem_read(ADDRESS,SIZE) - 从内存中读取数据

 

mu.reg_write(UC_X86_REG_ECX,0X0) - 设置ECX值。

 

r_esp = mu.reg_read(UC_X86_REG_ESP) - 读取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(向Unicorn引擎中),我们定义的函数会在执行每一条命令之前被执行。参数含义如下:

  • Uc实例
  • 指令的地址
  • 指令的长度
  • 用户定义数据(通过hook_add()函数传递)

参考:

原文链接:

 

http://eternal.red/2018/unicorn-engine-tutorial/

 

本文由看雪翻译小组zplusplus翻译

最新回复 (13)
1
zplusplus 2018-1-27 18:10
2

以下的代码并不是一次性写出来的,通过UC的错误消息,获得可能的线索然后来辅助实施最终的解决方案

TASK 2

from unicorn import *
from unicorn.x86_const import *

shellcode = "\xe8\xff\xff\xff\xff\xc0\x5d\x6a\x05\x5b\x29\xdd\x83\xc5\x4e\x89\xe9\x6a\x02\x03\x0c\x24\x5b\x31\xd2\x66\xba\x12\x00\x8b\x39\xc1\xe7\x10\xc1\xef\x10\x81\xe9\xfe\xff\xff\xff\x8b\x45\x00\xc1\xe0\x10\xc1\xe8\x10\x89\xc3\x09\xfb\x21\xf8\xf7\xd0\x21\xd8\x66\x89\x45\x00\x83\xc5\x02\x4a\x85\xd2\x0f\x85\xcf\xff\xff\xff\xec\x37\x75\x5d\x7a\x05\x28\xed\x24\xed\x24\xed\x0b\x88\x7f\xeb\x50\x98\x38\xf9\x5c\x96\x2b\x96\x70\xfe\xc6\xff\xc6\xff\x9f\x32\x1f\x58\x1e\x00\xd3\x80" 


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 == "\xcd\x80":

        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)
1
zplusplus 2018-1-27 18:11
3

TASK 3

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, "batman\x00") #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
1
zplusplus 2018-1-27 18:13
4

TASK 4

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("%d\n", ccc(x)).
print "The return value is %d" % return_value
芃杉 2018-1-27 18:49
5
mark
YZJClear 2018-1-29 22:36
6
M一下
Beta猫 2018-1-31 11:52
7
学习了
聖blue 2018-1-31 22:42
8
csshaw 2018-2-1 16:21
9
学习了
Loopher 2018-2-1 18:05
10
1
Lucaks 2018-2-3 02:13
11
mark
19
仙果 2018-2-5 13:38
12
代码着色看起来很是舒服
kcarc 6天前
13
学到了~  话说。。。dp不是翻译成“动态编程”吧,一般都翻译为“动态规划”
1
zplusplus 3天前
14
kcarc 学到了~ 话说。。。dp不是翻译成“动态编程”吧,一般都翻译为“动态规划”
谢谢,学习了
返回