Primeiros passos com o Unicorn Engine

Ao pesquisar "Unicorn Engine" no Habr, fiquei surpreso ao descobrir que essa ferramenta nunca foi destaque em artigos. Vou tentar preencher esse vazio. Vamos começar com o básico e ver um exemplo de uso do emulador na vida real. Para não reinventar a roda, decidi simplesmente traduzir este manual. Antes de começar, direi que todos os meus comentários ou comentários ficarão assim .


O que é um Unicorn Engine?


Os próprios desenvolvedores escrevem sobre Mecanismo unicórnio Unicorn Engine assim:


Unicorn é um emulador leve, multiplataforma e multi-arquitetura de processador.

Este não é um emulador padrão. Não emula a operação de todo o programa ou de todo o sistema operacional. Ele não suporta comandos do sistema (como abrir um arquivo, enviar um caractere para o console etc.). Você terá que fazer a marcação da memória e carregar os dados nela, e então você simplesmente inicia a execução a partir de um endereço específico.


Então, como isso é útil?


  • Ao analisar vírus, você pode chamar funções únicas sem criar um processo malicioso.
  • Para resolver o CTF.
  • Para fuzzing .
  • Um plugin para o gdb para prever o estado futuro, por exemplo, saltos futuros ou valores de registro.
  • Emulação de um código rico em recursos.

Do que você precisa?


  • Mecanismo Unicorn instalado com ligação Python.
  • Desmontador

Exemplo


Como exemplo, faça uma tarefa com hxp CTF 2017 com o nome Fibonacci . O binário pode ser baixado aqui .


Quando você inicia o programa, ele começa a exibir nossa bandeira no console, mas muito lentamente. Cada byte de flag subsequente é considerado cada vez mais lento.


The flag is: hxp{F 

Isso significa que, para obter a bandeira em um período de tempo razoável, precisamos otimizar a operação desse aplicativo.


Usando o IDA Pro ( eu pessoalmente usei o radare2 + Cutter ), descompilamos o código em um pseudocódigo do tipo C. Apesar do fato de o código não ter sido descompilado corretamente, ainda podemos obter informações sobre o que está acontecendo lá dentro.


Código descompilado
 __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; } 

Aqui está o código do assembler das funções principais e fibonacci :


principal
 .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 

Nesta fase, temos muitas oportunidades para resolver este problema. Por exemplo, podemos restaurar o código usando uma das linguagens de programação e aplicar a otimização, mas o processo de recuperação do código é uma tarefa muito difícil, durante a qual podemos cometer erros. Bem, comparar o código para encontrar o erro geralmente não vale nada. Porém, se usarmos o Unicorn Engine, podemos pular o estágio de reconstrução do código e evitar o problema descrito acima. Obviamente, podemos evitar esses problemas usando frida ou escrevendo scripts para o gdb, mas não é sobre isso.


Antes de iniciar a otimização, executaremos a emulação no Unicorn Engine sem alterar o programa. E somente após um lançamento bem-sucedido, vamos para a otimização.


Etapa 1: Deixe a virtualização chegar


Vamos criar o arquivo fibonacci.py e salvá-lo ao lado do binário.


Vamos começar importando as bibliotecas necessárias:


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

A primeira linha carrega as principais constantes binárias e básicas do unicórnio. A segunda linha carrega as constantes para as duas arquiteturas x86 e x86_64.


Em seguida, adicione algumas funções necessárias:


 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) 

Aqui anunciamos as funções que precisaremos posteriormente:


  • read simplesmente retorna o conteúdo do arquivo,
  • O u32 pega uma string de 4 bytes na codificação LE e converte em int,
  • O p32 faz o oposto - pega um número e o transforma em uma sequência de 4 bytes na codificação LE.

Nota: Se você instalou o pwntools , não precisará criar essas funções, basta importá-las:


 from pwn import * 

E então, finalmente, vamos começar a inicializar nossa classe Unicorn Engine para a arquitetura x86_64:


 mu = Uc (UC_ARCH_X86, UC_MODE_64) 

Aqui chamamos as funções Uc com os seguintes parâmetros:


  • O primeiro parâmetro é a arquitetura principal. As constantes começam com UC_ARCH_ ;
  • o segundo parâmetro é a especificação da arquitetura. As constantes começam com UC_MODE_ .

Você pode encontrar todas as constantes na folha de dicas .


Como escrevi acima, para usar o Unicorn Engine, precisamos inicializar a memória virtual manualmente. Neste exemplo, precisamos colocar o código e a pilha em algum lugar da memória.


O endereço base (endereço base) do binário começa em 0x400000. Vamos colocar nossa pilha em 0x0 e alocar 1024 * 1024 de memória para ela. Provavelmente, não precisamos de muito espaço, mas ainda não dói.


Podemos marcar a memória chamando o método mem_map .


Adicione estas linhas:


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

Agora precisamos carregar o binário em seu endereço principal da mesma maneira que o gerenciador de inicialização. Depois disso, precisamos definir o RSP no final da pilha.


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

Agora podemos iniciar a emulação e executar o código, mas precisamos descobrir com qual endereço começar o trabalho e quando o emulador deve parar.


Pegue o endereço do primeiro comando em main () , podemos iniciar a emulação de 0x004004e0. O final será considerado uma chamada para putc ("\ n") , localizada em 0x00400575, após a exibição do sinalizador inteiro.


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

Podemos começar a emular:


 mu.emu_start(0x004004e0,0x00400575) 

Agora execute o script:


 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) 

Ops, algo deu errado, mas nem sabemos o quê. Antes de chamar mu.emu_start, podemos adicionar:


 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) 

Este código adiciona um gancho. Declaramos nossa própria função hook_code , que é chamada pelo emulador antes de cada comando. São necessários os seguintes parâmetros:


  • nossa cópia do Uc ,
  • endereço de instrução
  • instruções de tamanho
  • dados do usuário (podemos passar esse valor com um argumento opcional para hook_add () ).
    Agora, se executarmos o script, veremos a seguinte saída:
     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) 

    No endereço em que ocorreu o erro, podemos entender que nosso script não pode processar este comando:

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

    Esta instrução lê dados do endereço 0x601038 (você pode vê-los no IDA Pro). Esta é a seção .bss que não marcamos. Minha solução seria simplesmente pular todas as instruções problemáticas se isso não afetar a lógica do programa.
    Abaixo está outra instrução problemática:

     .text:0x4004F6 call _setbuf 

    Não podemos chamar nenhuma função com o glibc, pois não temos o glibc carregado na memória. De qualquer forma, não precisamos deste comando, portanto também podemos ignorá-lo.
    Aqui está a lista completa de comandos a serem ignorados:

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

    Para pular comandos, precisamos reescrever o RIP com a seguinte instrução:

     mu.reg_write(UC_X86_REG_RIP, address+size) 

    Agora hook_code deve ser algo como isto:



     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) 

    Também precisamos fazer algo com instruções que exibam o sinalizador no console byte a byte.


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

    __IO_putc recebe bytes para saída como o primeiro argumento (este é o registro RDI ).


    Podemos ler dados diretamente do registro, enviar dados para o console e pular este conjunto de instruções. O hook_code atualizado é apresentado abaixo:


     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) 

    Podemos correr e tudo vai funcionar, mas ainda lentamente.


    Etapa 2: aumentar a velocidade!


    Vamos pensar em aumentar a velocidade do trabalho. Por que esse programa é tão lento?


    Se olharmos para o código descompilado, veremos que main () chama fibonacci () várias vezes e fibonacci () é uma função recursiva. Vamos dar uma olhada mais de perto nesta função, que pega e retorna dois argumentos. O primeiro valor de retorno é passado pelo registro RAX , o segundo é retornado pelo link que foi passado pelo segundo argumento para a função. Se examinarmos mais profundamente a relação entre main () e fibonacci () , veremos que o segundo argumento usa apenas dois valores possíveis: 0 ou 1. Se você ainda não vê isso, execute gdb e coloque um ponto de interrupção no início da função fibonacci () .


    Para otimizar a operação do algoritmo, podemos usar a programação dinâmica para lembrar o valor de retorno dos parâmetros recebidos. Pense por si mesmo, o segundo argumento pode ter apenas dois valores possíveis, então tudo o que precisamos fazer é lembrar $ inline $ 2 * MAX \ _OF \ _FIRST \ _ARGUMENT $ inline $ vapor


    Para quem não entende

    fibonacci é uma função recursiva que calcula o próximo valor como a soma dos dois anteriores. A cada passo ela se aprofunda. Cada vez que ela recomeça, segue o mesmo caminho de antes, mais um novo significado.


    Um exemplo:
    Suponha profundidade = 6, então: 1 1 2 3 5 8 .
    E agora profundidade = 8, então: 1 1 2 3 5 8 13 21.


    Podemos lembrar que os 6 primeiros membros são 1 1 2 3 5 8 e, quando pedem que conte mais do que lembrávamos, pegamos o que lembramos e contamos apenas o que está faltando.


    Quando o RIP estiver no início de fibonacci () , podemos obter os argumentos da função. Sabemos que uma função retorna um resultado quando sai de uma função. Como não podemos operar com dois parâmetros ao mesmo tempo, precisamos de uma pilha para retornar os parâmetros. Quando inserimos fibonacci (), precisamos colocar os argumentos na pilha e buscá-los quando sairmos. Para armazenar os pares contados, podemos usar um dicionário.


    Como processar um par de valores?


    • No início da função, podemos verificar se esse par está nos resultados que já sabemos:
      • se houver, podemos devolver este par. Só precisamos escrever os valores de retorno em RAX e no endereço do link, que está no segundo argumento. Também atribuímos um endereço RIP para sair da função. Não podemos usar o RET em fibonacci () , já que essas chamadas são conectadas, portanto, pegaremos um RET de main () ;
      • se esses valores não forem, simplesmente os adicionamos à pilha.
    • Antes de sair da função, podemos salvar o par retornado. Conhecemos os argumentos de entrada, pois podemos lê-los em nossa pilha.

    Este código é apresentado aqui.
     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) 

    Aqui está o script inteiro
     #!/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() 

    Hooray, finalmente conseguimos otimizar o aplicativo usando o Unicorn Engine. Bom trabalho!


    Uma nota


    Agora eu decidi fazer um pouco de lição de casa.
    Aqui você encontra mais três tarefas, cada uma com uma dica e uma solução completa. Você pode espiar a folha de dicas enquanto resolve problemas.


    Um dos problemas mais irritantes é lembrar o nome da constante desejada. É fácil lidar com isso se você usar complementos de tabulação no IPython . Quando você tiver o IPython instalado, poderá escrever na importação de unicórnio UC_ARCH_ e pressionar Tab e serão mostradas todas as constantes que começam da mesma maneira.

Source: https://habr.com/ru/post/pt431058/


All Articles