Premiers pas avec Unicorn Engine

Lors de la recherche de "Unicorn Engine" sur Habr, j'ai été surpris de constater que cet outil n'a jamais été présenté dans les articles. Je vais essayer de combler ce vide. Commençons par les bases et regardons un exemple d'utilisation de l'émulateur dans la vie réelle. Afin de ne pas réinventer la roue, j'ai décidé de traduire simplement ce manuel. Avant de commencer, je dirai que tous mes commentaires ou commentaires ressembleront à ceci .


Qu'est-ce qu'un moteur Unicorn?


Les développeurs eux-mêmes écrivent sur Moteur Licorne Unicorn Engine comme ceci:


Unicorn est un émulateur de processeur léger, multi-plateforme et multi-architecture.

Ce n'est pas un émulateur standard. Il n'émule pas le fonctionnement de l'ensemble du programme ou de l'ensemble du système d'exploitation. Il ne prend pas en charge les commandes système (telles que l'ouverture d'un fichier, la sortie d'un caractère sur la console, etc.). Vous devrez faire le balisage de la mémoire et y charger vous-même les données, puis vous démarrez simplement l'exécution à partir d'une adresse spécifique.


Alors, comment est-ce utile?


  • Lors de l'analyse de virus, vous pouvez appeler des fonctions uniques sans créer de processus malveillant.
  • Pour résoudre CTF.
  • Pour le fuzzing .
  • Un plugin pour gdb pour prédire l'état futur, par exemple, les sauts futurs ou les valeurs de registre.
  • Émulation d'un code riche en fonctionnalités.

De quoi avez-vous besoin?


  • Moteur Unicorn installé avec liaison Python.
  • Démonteur

Exemple


Par exemple, prenez une tâche avec hxp CTF 2017 sous le nom Fibonacci . Le binaire peut être téléchargé ici .


Lorsque vous démarrez le programme, il commence à afficher notre drapeau dans la console, mais très lentement. Chaque octet de drapeau suivant est considéré de plus en plus lent.


The flag is: hxp{F 

Cela signifie que pour obtenir le drapeau dans un délai raisonnable, nous devons optimiser le fonctionnement de cette application.


En utilisant IDA Pro ( j'ai personnellement utilisé radare2 + Cutter ), nous avons décompilé le code en un pseudocode de type C. Bien que le code n'ait pas été décompilé correctement, nous pouvons toujours en obtenir des informations sur ce qui se passe à l'intérieur.


Code décompilé
 __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; } 

Voici le code assembleur des fonctions main et 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 

À ce stade, nous avons de nombreuses occasions de résoudre ce problème. Par exemple, nous pouvons restaurer le code en utilisant l'un des langages de programmation et y appliquer l'optimisation, mais le processus de récupération du code est une tâche très difficile, au cours de laquelle nous pouvons faire des erreurs. Eh bien, comparer le code pour trouver l'erreur est généralement sans valeur. Mais, si nous utilisons le moteur Unicorn, nous pouvons ignorer l'étape de reconstruction du code et éviter le problème décrit ci-dessus. Bien sûr, nous pouvons éviter ces problèmes en utilisant frida ou en écrivant des scripts pour gdb, mais ce n'est pas tout.


Avant de commencer l'optimisation, nous exécuterons l'émulation dans le moteur Unicorn sans changer le programme. Et seulement après un lancement réussi, passons à l'optimisation.


Étape 1: laissez venir la virtualisation


Créons le fichier fibonacci.py et enregistrons-le à côté du binaire.


Commençons par importer les bibliothèques requises:


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

La première ligne charge les constantes binaires et de base Unicorn principales. La deuxième ligne charge les constantes des deux architectures x86 et x86_64.


Ensuite, ajoutez quelques fonctions nécessaires:


 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) 

Ici, nous avons annoncé les fonctions dont nous aurons besoin plus tard:


  • read renvoie simplement le contenu du fichier,
  • u32 prend une chaîne de 4 octets dans le codage LE et convertit en int,
  • p32 fait le contraire - il prend un nombre et le transforme en une chaîne de 4 octets dans l'encodage LE.

Remarque: Si vous avez installé pwntools , vous n'avez pas besoin de créer ces fonctions, il vous suffit de les importer:


 from pwn import * 

Et enfin, commençons par initialiser notre classe Unicorn Engine pour l'architecture x86_64:


 mu = Uc (UC_ARCH_X86, UC_MODE_64) 

Ici, nous appelons les fonctions Uc avec les paramètres suivants:


  • Le premier paramètre est l'architecture principale. Les constantes commencent par UC_ARCH_ ;
  • le deuxième paramètre est la spécification de l'architecture. Les constantes commencent par UC_MODE_ .

Vous pouvez trouver toutes les constantes dans la feuille de triche .


Comme je l'ai écrit ci-dessus, pour utiliser le moteur Unicorn, nous devons initialiser la mémoire virtuelle manuellement. Pour cet exemple, nous devons placer le code et la pile quelque part en mémoire.


L'adresse de base (Base addr) du binaire commence à 0x400000. Mettons notre pile à 0x0 et allouons 1024 * 1024 de mémoire pour cela. Très probablement, nous n'avons pas besoin d'autant d'espace, mais cela ne fait toujours pas de mal.


Nous pouvons baliser la mémoire en appelant la méthode mem_map .


Ajoutez ces lignes:


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

Maintenant, nous devons charger le binaire dans son adresse principale de la même manière que le chargeur de démarrage. Après cela, nous devons définir RSP à la fin de la pile.


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

Nous pouvons maintenant démarrer l'émulation et exécuter le code, mais nous devons déterminer avec quelle adresse commencer à travailler et quand l'émulateur doit s'arrêter.


Prenez l'adresse de la première commande de main () , nous pouvons démarrer l'émulation à partir de 0x004004e0. La fin sera considérée comme un appel à putc ("\ n") , qui se trouve à 0x00400575, après avoir affiché le drapeau entier.


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

Nous pouvons commencer à émuler:


 mu.emu_start(0x004004e0,0x00400575) 

Exécutez maintenant le 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) 

Oups, quelque chose s'est mal passé, mais nous ne savons même pas quoi. Juste avant d'appeler mu.emu_start, nous pouvons ajouter:


 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) 

Ce code ajoute un crochet. Nous déclarons notre propre fonction hook_code , qui est appelée par l'émulateur avant chaque commande. Il prend les paramètres suivants:


  • notre copie de Uc ,
  • adresse d'instruction
  • instructions de taille
  • données utilisateur (nous pouvons passer cette valeur avec un argument facultatif à hook_add () ).
    Maintenant, si nous exécutons le script, nous devrions voir la sortie suivante:
     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) 

    À l'adresse où l'erreur s'est produite, nous pouvons comprendre que notre script ne peut pas traiter cette commande:

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

    Cette instruction lit les données de l'adresse 0x601038 (vous pouvez les voir dans IDA Pro). Il s'agit de la section .bss que nous n'avons pas balisée . Ma solution serait de simplement sauter toutes les instructions problématiques si cela n'affecte pas la logique du programme.
    Voici une autre instruction problématique:

     .text:0x4004F6 call _setbuf 

    Nous ne pouvons appeler aucune fonction avec glibc, car nous n'avons pas de glibc chargée en mémoire. Dans tous les cas, nous n'avons pas besoin de cette commande, nous pouvons donc également la sauter.
    Voici la liste complète des commandes à ignorer:

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

    Pour ignorer les commandes, nous devons réécrire RIP avec l'instruction suivante:

     mu.reg_write(UC_X86_REG_RIP, address+size) 

    Maintenant, hook_code devrait ressembler à ceci:



     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) 

    Nous devons également faire quelque chose avec des instructions qui affichent l'indicateur dans la console octet par octet.


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

    __IO_putc prend des octets pour la sortie comme premier argument (il s'agit du registre RDI ).


    Nous pouvons lire les données directement à partir du registre, sortir les données vers la console et ignorer cet ensemble d'instructions. Le hook_code mis à jour est présenté ci-dessous:


     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) 

    Nous pouvons courir et tout fonctionnera, mais toujours lentement.


    Étape 2: augmentez la vitesse!


    Pensons à augmenter la vitesse de travail. Pourquoi ce programme est-il si lent?


    Si nous regardons le code décompilé, nous verrons que main () appelle fibonacci () plusieurs fois et fibonacci () est une fonction récursive. Examinons de plus près cette fonction; elle prend et renvoie deux arguments. La première valeur de retour est transmise via le registre RAX , la seconde est renvoyée via le lien qui a été transmis via le deuxième argument à la fonction. Si nous regardons plus en profondeur la relation entre main () et fibonacci () , alors nous verrons que le deuxième argument ne prend que deux valeurs possibles: 0 ou 1. Si vous ne voyez toujours pas cela, exécutez gdb et mettez un point d'arrêt au début de la fonction fibonacci () .


    Pour optimiser le fonctionnement de l'algorithme, nous pouvons utiliser la programmation dynamique afin de mémoriser la valeur de retour des paramètres entrants. Pensez par vous-même, le deuxième argument ne peut prendre que deux valeurs possibles, donc tout ce que nous avons à faire est de nous souvenir $ inline $ 2 * MAX \ _OF \ _FIRST \ _ARGUMENT $ inline $ vapeur


    Pour ceux qui ne comprennent pas

    fibonacci est une fonction récursive qui calcule la valeur suivante comme la somme des deux précédentes. À chaque étape, elle va plus loin. Chaque fois qu'elle recommence, elle suit le même chemin qu'auparavant, plus un nouveau sens.


    Un exemple:
    Supposons que la profondeur = 6, puis: 1 1 2 3 5 8 .
    Et maintenant profondeur = 8, puis: 1 1 2 3 5 8 13 21.


    Nous pourrions simplement nous rappeler que les 6 premiers membres sont 1 1 2 3 5 8 , et quand ils nous demandent de compter plus que nous nous en souvenions, nous prenons ce dont nous nous souvenons et ne comptons que ce qui manque.


    Une fois que RIP est au début de fibonacci () , nous pouvons obtenir les arguments de la fonction. Nous savons qu'une fonction renvoie un résultat lorsqu'elle quitte une fonction. Comme nous ne pouvons pas fonctionner avec deux paramètres à la fois, nous avons besoin d'une pile pour renvoyer les paramètres. Lorsque nous entrons dans fibonacci (), nous devons mettre les arguments sur la pile et les récupérer à notre sortie. Pour stocker les paires comptées, nous pouvons utiliser un dictionnaire.


    Comment traiter une paire de valeurs?


    • Au tout début de la fonction, nous pouvons vérifier si cette paire figure dans les résultats que nous connaissons déjà:
      • s'il y en a, alors nous pouvons retourner cette paire. Nous avons juste besoin d'écrire les valeurs de retour en RAX et à l'adresse du lien, qui est dans le deuxième argument. Nous attribuons également une adresse RIP pour quitter la fonction. Nous ne pouvons pas utiliser RET dans fibonacci () , car ces appels sont accrochés, nous allons donc prendre un certain RET de main () ;
      • si ces valeurs ne le sont pas, nous les ajoutons simplement à la pile.
    • Avant de quitter la fonction, nous pouvons enregistrer la paire retournée. Nous connaissons les arguments d'entrée, car nous pouvons les lire à partir de notre pile.

    Ce code est présenté ici.
     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) 

    Voici le script entier
     #!/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() 

    Hourra, nous avons enfin pu optimiser l'application à l'aide du moteur Unicorn. Bon travail!


    Une note


    Maintenant, j'ai décidé de vous donner un peu de devoirs.
    Ici, vous pouvez trouver trois autres tâches, chacune ayant un indice et une solution complète. Vous pouvez jeter un œil à la feuille de triche tout en résolvant les problèmes.


    L'un des problèmes les plus ennuyeux est de se souvenir du nom de la constante souhaitée. C'est facile à gérer si vous utilisez des modules complémentaires Tab dans IPython . Lorsque vous avez installé IPython, vous pouvez écrire à partir de l'importation de licorne UC_ARCH_, appuyez sur Tab et vous verrez toutes les constantes qui commencent de la même manière.

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


All Articles