Первые шаги с Unicorn Engine

в 7:22, , рубрики: emulator, engine, unicorn, виртуализация, информационная безопасность, перевод, реверс-инжиниринг

При поиске "Unicorn Engine" на Хабре, я с удивлением обнаружил, что этот инструмент еще ни разу не попадал в статьи. Я попробую заполнить эту пустоту. Начнем, пожалуй, с азов, и посмотрим на пример использования эмулятора в реальной жизни. Для того, чтобы не изобретать велосипед, я решил просто перевести этот мануал. Перед началом скажу, что все мои комментарии или замечания будут выглядеть так.

Что такое Unicorn Engine?

Сами разработчики пишут о Двигатель Единорога Unicorn Engine так:

Unicorn — это легковесный, мультиплатформенный и мультиархитектурный эмулятор процессора.

Это не стандартный эмулятор. Он не эмулирует работу всей программы или целой ОС. Он не поддерживает системные команды (такие как открытие файла, вывод символа в консоль и т. д.). Вам прийдется самостоятельно заниматься разметкой памяти и загрузкой данных в нее, а дальше вы просто запускаете выполнение с какого-то конкретного адреса.

Так чем же он полезен?

  • При анализе вирусов, вы можете вызывать одиночные функции, не создавая вредоносного процесса.
  • Для решения CTF.
  • Для фаззинга.
  • Плагин для gdb для предсказывания будущего состояния, к примеру, будущих прыжков или значений регистров.
  • Эмуляции обфурсифицированного кода.

Что вам понадобится?

  • Установленный Unicorn Engine с привязкой к Python.
  • Дизассемблер.

Пример

Для примера возьмем задачу с hxp CTF 2017 под именем Fibonacci. Бинарник можно загрузить здесь.

Когда вы запускаете программу, она начинает выводить наш флаг в консоль, но очень медленно. Каждый следующий байт флага считается все медленнее и медленнее.

The flag is: hxp{F

Это означает, что для получения флага в разумные сроки, нам необходимо оптимизировать работу данного приложения.

С помощью IDA Pro (я лично использовал radare2 + Cutter) мы декомпилировали код в С-подобный псевдокод. Не смотря на то, что код не декомпилировался должным образом, мы все равно можем получить от него информацию о том, что происходит внутри.

Декомпилированный код

__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 и fibonacci функций:

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

На данном этапе у нас много возможностей решить эту задачу. К примеру, мы можем восстановить код с использованием одного из языков программирования и применить оптимизацию там, но процесс восстановления кода — это очень сложная задача, во время которой мы можем допустить ошибки. Ну а сравнивать потом код, чтобы найти ошибку, — вообще никуда не годится. Но, если мы используем Unicorn Engine, то мы можем пропустить этап реконструкции кода и избежать проблемы, описанной выше. Мы, конечно, можем избежать этих неприятностей с помощью frida или написанием скриптов для gdb, но речь здесь не о том.

Прежде чем начинать оптимизацию, мы запустим эмуляцию в Unicorn Engine без изменений программы. И уже только после успешного запуска, перейдем к оптимизации.

Шаг 1: Да прийдет виртуализация

Давайте создадим файл fibonacci.py и сохраним рядом с бинарником.

Начнем с импортирования необходимых библиотек:

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

Первая строка загружает основной бинарник и базовые Unicorn константы. Вторая строка загружает константы для двух архитектур 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)

Здесь мы объявили функции, которые нам понадобятся позже:

  • read просто возвращает контент файла,
  • u32 берет 4-байтную строку в LE кодировке и конвертирует в int,
  • p32 делает противоположное действие — он берет число и превращает в 4-байтную строку в LE кодировке.

Примечание: Если у вас есть установленный pwntools, то вам не нужно создавать эти функции, вам достаточно импортировать их:

from pwn import *

 И так, наконец-то приступим к инициализации нашего Unicorn Engine класса для архитектуры x86_64:

mu = Uc (UC_ARCH_X86, UC_MODE_64)

Тут мы вызываем функции Uc со следующими параметрами:

  • первый параметр — это основная архитектура. Константы начинаются с UC_ARCH_;
  • второй параметр — это спецификация архитектуры. Константы начинаются с UC_MODE_.

Вы можете найти все константы в Cheatsheet.

Как я писал выше, для использования Unicorn Engine, нам понадобиться инициализировать виртуальную память вручную. Для этого примера нам нужно где-то в памяти разместить код и стек.

Базовый адрес (Base addr) бинарника начинается c 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. Концом будет считаться вызов putc("n"), который находится по адресу 0x00400575, после выведения всего флага.

.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, которая вызывается эмулятором перед каждой командой. Она принимает такие параметры:

  • наш экземпляр Uc,
  • адрес инструкции,
  • размер инструкции,
  • данные пользователя (мы можем передать это значение с помощью необязательного аргумента в hook_add()).
    Теперь, если мы запустим скрипт, то должны увидеть такой вывод:

    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 = [0x004004ef,0x004004f6,0x00400502,0x0040054f]
    
    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)

    Нам также нужно что-то сделать с инструкциями, которые выводят флаг в консоль байт-за-байтом.

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

    __IO_putc принимает байты для вывода в качестве первого аргумента (это регистр RDI).

    Мы можем считывать данные непосредственно с регистра, выводить данные в консоль и пропускать этот набор инструкций. Обновленный hook_code представлен ниже:

    instructions_skip_list = [0x004004ef,0x004004f6,0x00400502,0x0040054f]
    
    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: # 
            c = mu.reg_read(UC_X86_REG_RDI)
            print(chr(c),end="")
            mu.reg_write(UC_X86_REG_RIP, address+size)

    Мы можем запустить и это все будет работать, но по прежнему медленно.

    Шаг 2: Увеличим скорость!

    Давайте подумаем про увеличение скорости работы. Почему эта программа такая медленная?

    Если мы посмотрим на декомпилированный код, то увидим, что main() вызывает fibonacci() несколько раз и fibonacci() — это рекурсивная функция. Посмотрим на эту функцию поближе, она принимает и возвращает два аргумента. Первое возвращаемое значение передается через RAX регистр, второе возвращается через ссылку, которую передали через второй аргумент функции. Если мы посмотрим глубже, на связь main() и fibonacci(), то мы увидим, что второй аргумент принимает только два возможных значения: 0 или 1. Если вы этого все еще не видите, то запустите gdb и поставьте точку остановки на начало функции fibonacci().

    Для оптимизации работы алгоритма, мы можем воспользоваться динамическим программированием для того, чтобы запомнить возвращаемые значение для входящих параметров. Подумайте сами, второй аргумент может принимать только два возможных значения, так что нам достаточно запомнить лишь $inline$2 * MAX_OF_FIRST_ARGUMENT$inline$ пар.

    Для тех, кто не понял

    fibonacci — это рекурсивная функция, которая вычисляет следующее значение как сумму двух предыдущих. На каждом шаге она заходит все глубже. Каждый раз, когда она начинает сначала, она проходит тот же путь, что и раньше, плюс одно новое значение.

    Пример:
    Допустим, глубина = 6, тогда: 1 1 2 3 5 8.
    А теперь глубина = 8, тогда: 1 1 2 3 5 8 13 21.

    Мы бы могли просто запомнить, что первые 6 членов это 1 1 2 3 5 8, и когда нас просят посчитать больше, чем мы запомнили, то мы берем то, что запомнили и считаем только то, чего не хватает.

    Как только RIP находится в начале fibonacci(), мы можем получить аргументы функции. Мы знаем, что функция возвращает результат, когда выходит из функции. Так как мы не можем оперировать двумя параметрами сразу, нам понадобится стек для возвращения параметров. Во время входа в fibonacci() нам нужно положить аргументы в стек, и забрать их при выходе. Для хранения посчитанных пар мы можем использовать словарь.

    Как обработать пару значений?

    • В самом начале функции мы можем проверить, нет ли этой пары в уже известных нам результатах:
      • если есть, то мы можем возвращать эту пару. Нам всего лишь нужно записать возвращаемые значения в RAX и по адресу ссылки, которая находится во втором аргументе. Также мы присваиваем RIP адрес выхода из функции. Мы не можем использовать RET в fibonacci(), так как эти вызовы находятся под хуком, так что мы возьмем какой-то RET из main();
      • если этих значений нет, то мы просто добавляем их в стек.
    • Перед выходом из функции мы можем сохранить возвращаемую пару. Мы знаем входящие аргументы, так как мы можем их прочитать с нашего стека.

    Этот код представлен здесь

    FIBONACCI_ENTRY = 0x00400670
    FIBONACCI_END = [ 0x004006f1, 0x00400709]
    
    instructions_skip_list = [0x004004ef,0x004004f6,0x00400502,0x0040054f]
    
    # Стек для хранения аргументов
    stack = []
    
    # Словарь, в котором мы храним известные пары
    d = {}
    
    def hook_code(mu, address, size, user_data):
    
        if address in instructions_skip_list:
            mu.reg_write(UC_X86_REG_RIP, address+size)
    
        # Эта инструкция выводит байт флага
        elif address == 0x400560:
            c = mu.reg_read(UC_X86_REG_RDI)
            print(chr(c),end="")
            mu.reg_write(UC_X86_REG_RIP, address+size)
    
        # Мы находимся в начале функции?
        elif address == FIBONACCI_ENTRY:
    
            # Считать первый аргумент из RDI
            arg0 = mu.reg_read(UC_X86_REG_RDI)
    
            # Считать второй аргумент (ссылка)
            r_rsi = mu.reg_read(UC_X86_REG_RSI)
    
            # Считать второй аргумент, перейдя по ссылке
            arg1 = u32(mu.mem_read(r_rsi, 4))
    
            # Проверить, сохраненная ли это пара?
            if (arg0,arg1) in d:
                (ret_rax, ret_ref) = d[(arg0,arg1)]
    
                # Поместить сохраненное значение в RAX
                mu.reg_write(UC_X86_REG_RAX, ret_rax)
    
                # Записать результат через ссылку
                mu.mem_write(r_rsi, p32(ret_ref))
    
                # Поменять RIP на RET инструкцию, так как мы хотим выйти из fibonacci
                mu.reg_write(UC_X86_REG_RIP, 0x400582)
            else:
    
                # Если аргументы не были ранее сохранены, то помещаем их в стек
                stack.append((arg0,arg1,r_rsi))
    
        elif address in FIBONACCI_END:
    
            # Получаем аргументы из стека
            (arg0, arg1, r_rsi) = stack.pop()
    
            # Считываем возвращаемое значение из RAX
            ret_rax = mu.reg_read(UC_X86_REG_RAX)
    
            # Считываем значение, которое было передано по ссылке
            ret_ref = u32(mu.mem_read(r_rsi,4))
    
            # Запоминаем эту пару в словарь
            d[(arg0, arg1)]=(ret_rax, ret_ref)
    

    Вот весь скрипт

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from __future__ import print_function
    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)
    
    FIBONACCI_ENTRY = 0x00400670
    FIBONACCI_END = [ 0x004006f1, 0x00400709]
    
    instructions_skip_list = [0x004004ef,0x004004f6,0x00400502,0x0040054f]
    
    # Стек для хранения аргументов
    stack = []
    
    # Словарь, в котором мы храним известные пары
    d = {}
    
    def hook_code(mu, address, size, user_data):
    
        if address in instructions_skip_list:
            mu.reg_write(UC_X86_REG_RIP, address+size)
    
        # Эта инструкция выводит байт флага
        elif address == 0x400560:
            c = mu.reg_read(UC_X86_REG_RDI)
            print(chr(c),end="")
            mu.reg_write(UC_X86_REG_RIP, address+size)
    
        # Мы находимся в начале функции?
        elif address == FIBONACCI_ENTRY:
    
            # Считать первый аргумент из RDI
            arg0 = mu.reg_read(UC_X86_REG_RDI)
    
            # Считать второй аргумент (ссылка)
            r_rsi = mu.reg_read(UC_X86_REG_RSI)
    
            # Считать второй аргумент, перейдя по ссылке
            arg1 = u32(mu.mem_read(r_rsi, 4))
    
            # Проверить, сохраненная ли это пара?
            if (arg0,arg1) in d:
                (ret_rax, ret_ref) = d[(arg0,arg1)]
    
                # Поместить сохраненное значение в RAX
                mu.reg_write(UC_X86_REG_RAX, ret_rax)
    
                # Записать результат через ссылку
                mu.mem_write(r_rsi, p32(ret_ref))
    
                # Поменять RIP на RET инструкцию. Мы хотим выйти из fibonacci
                mu.reg_write(UC_X86_REG_RIP, 0x400582)
            else:
    
                # Если аргументы не были ранее сохранены, то помещаем их в стек
                stack.append((arg0,arg1,r_rsi))
    
        elif address in FIBONACCI_END:
    
            # Получаем аргументы из стека
            (arg0, arg1, r_rsi) = stack.pop()
    
            # Считываем возвращаемое значение из RAX
            ret_rax = mu.reg_read(UC_X86_REG_RAX)
    
            # Считываем значение, которое было переданное через ссылку
            ret_ref = u32(mu.mem_read(r_rsi,4))
    
            # Запоминаем эту пару в словарь
            d[(arg0, arg1)]=(ret_rax, ret_ref)
    
    mu = Uc (UC_ARCH_X86, UC_MODE_64)
    
    BASE = 0x400000
    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("./fibonacci"))
    mu.reg_write(UC_X86_REG_RSP, STACK_ADDR + STACK_SIZE - 1)
    
    mu.hook_add(UC_HOOK_CODE, hook_code)
    
    mu.emu_start(0x004004e0, 0x00400575)
    print()
    

    Ура, мы наконец-то смогли оптимизировать приложение, используя Unicorn Engine. Хорошая работа!

    Заметка

    Теперь я решил дать вам небольшое домашнее задание.
    Здесь вы можете найти еще три задачи, каждая из которых имеет подсказку и полное решение. Вы можете подглядывать в Cheatsheet во время решение задач.

    Одна из самых назойливых проблем — это вспомнить имя нужной константы. С этим легко бороться, если использовать Tab-дополнения в IPython. Когда у вас есть установленный IPython, вы можете написать from unicorn import UC_ARCH_ нажать Tab и вам будет выведены все константы, которые начинаются так же.

Автор: loony_dev

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js