Comment lire un exécutable binary par des instructions?

Existe-t-il un moyen de lire par programme un nombre d’instructions donné à partir d’un fichier exécutable binary sur une architecture x86?

Si j’avais le binary d’un programme simple, hello.c :

 #include  int main(){ printf("Hello world\n"); return 0; } 

Où, après compilation en utilisant gcc , la fonction main désassemblée ressemble à ceci:

 000000000000063a : 63a: 55 push %rbp 63b: 48 89 e5 mov %rsp,%rbp 63e: 48 8d 3d 9f 00 00 00 lea 0x9f(%rip),%rdi # 6e4  645: e8 c6 fe ff ff callq 510  64a: b8 00 00 00 00 mov $0x0,%eax 64f: 5d pop %rbp 650: c3 retq 651: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 658: 00 00 00 65b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) 

Y at-il un moyen facile en C de lire, par exemple, les trois premières instructions (signifiant les octets 55, 48, 89, e5, 48, 8d, 3d, 9f, 00, 00, 00 ) de main ? Il n’est pas garanti que la fonction ressemble à ceci – les premières instructions peuvent avoir tous les opcodes et toutes les tailles.

ceci affiche les 10 premiers octets de la fonction main en prenant l’adresse de la fonction et en la convertissant en un pointeur de caractère unsigned char , imprimé en hexadécimal.

Ce petit extrait ne compte pas les instructions. Pour cela, vous aurez besoin d’un tableau de taille d’instruction (pas très difficile, juste fastidieux à moins que vous trouviez le tableau déjà fait, quelle est la taille de chaque instruction asm? ) Pour pouvoir prédire la taille de chaque instruction à partir du premier octet.

(à moins bien sûr que le processeur que vous ciblez ait une taille d’instruction fixe, ce qui rend le problème sortingvial à résoudre)

Les débogueurs doivent également décoder les opérandes, mais dans certains cas comme étape ou trace, je suppose qu’ils ont une table très pratique pour calculer l’adresse de point d’interruption suivante.

 #include  int main(){ printf("Hello world\n"); const unsigned char *start = (const char *)&main; int i; for (i=0;i<10;i++) { printf("%x\n",start[i]); } return 0; } 

sortie:

 Hello world 55 89 e5 83 e4 f0 83 ec 20 e8 

semble correspondre au déassembly 🙂

 00401630 <_main>: 401630: 55 push %ebp 401631: 89 e5 mov %esp,%ebp 401633: 83 e4 f0 and $0xfffffff0,%esp 401636: 83 ec 20 sub $0x20,%esp 401639: e8 a2 01 00 00 call 4017e0 <___main> 
 .globl _start _start: bl main b . .globl main main: add r1,#1 add r2,#1 add r3,#1 add r4,#1 b main 

intentionnellement mauvaise architecture, l’architecture importe peu le format du fichier. cela a été construit dans un format de fichier elf, qui est très populaire, et est simplement un format de fichier, ce que j’ai compris de votre question, lire un fichier, pas modifier le binary pour lire le programme à partir de la mémoire.

il est très populaire et il existe des outils qui le font que vous semblez savoir utiliser.

 Disassembly of section .text: 00001000 <_start>: 1000: eb000000 bl 1008 
1004: eafffffe b 1004 <_start+0x4> 00001008
: 1008: e2811001 add r1, r1, #1 100c: e2822001 add r2, r2, #1 1010: e2833001 add r3, r3, #1 1014: e2844001 add r4, r4, #1 1018: eafffffa b 1008

si j’hexdump le fichier si

 00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 28 00 01 00 00 00 00 10 00 00 34 00 00 00 |..(.........4...| 00000020 c0 11 00 00 00 02 00 05 34 00 20 00 01 00 28 00 |........4. ...(.| 00000030 06 00 05 00 01 00 00 00 00 00 00 00 00 00 00 00 |................| 00000040 00 00 00 00 1c 10 00 00 1c 10 00 00 05 00 00 00 |................| 00000050 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001000 00 00 00 eb fe ff ff ea 01 10 81 e2 01 20 82 e2 |............. ..| 00001010 01 30 83 e2 01 40 84 e2 fa ff ff ea 41 11 00 00 |[email protected]...| 00001020 00 61 65 61 62 69 00 01 07 00 00 00 08 01 00 00 |.aeabi..........| 00001030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001040 00 00 00 00 00 10 00 00 00 00 00 00 03 00 01 00 |................| 00001050 00 00 00 00 00 00 00 00 00 00 00 00 03 00 02 00 |................| 00001060 01 00 00 00 00 00 00 00 00 00 00 00 04 00 f1 ff |................| 00001070 06 00 00 00 00 10 00 00 00 00 00 00 00 00 01 00 |................| 00001080 18 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................| 00001090 09 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................| 000010a0 17 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................| 000010b0 55 00 00 00 00 10 00 00 00 00 00 00 10 00 01 00 |U...............| 000010c0 23 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |#...............| 000010d0 2f 00 00 00 08 10 00 00 00 00 00 00 10 00 01 00 |/...............| 000010e0 34 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |4...............| 000010f0 3c 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |<...............| 00001100 43 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |C...............| 00001110 48 00 00 00 00 00 08 00 00 00 00 00 10 00 01 00 |H...............| 00001120 4f 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |O...............| 00001130 00 73 6f 2e 6f 00 24 61 00 5f 5f 62 73 73 5f 73 |.so.o.$a.__bss_s| 00001140 74 61 72 74 5f 5f 00 5f 5f 62 73 73 5f 65 6e 64 |tart__.__bss_end| 00001150 5f 5f 00 5f 5f 62 73 73 5f 73 74 61 72 74 00 6d |__.__bss_start.m| 00001160 61 69 6e 00 5f 5f 65 6e 64 5f 5f 00 5f 65 64 61 |ain.__end__._eda| 00001170 74 61 00 5f 65 6e 64 00 5f 73 74 61 63 6b 00 5f |ta._end._stack._| 00001180 5f 64 61 74 61 5f 73 74 61 72 74 00 00 2e 73 79 |_data_start...sy| 00001190 6d 74 61 62 00 2e 73 74 72 74 61 62 00 2e 73 68 |mtab..strtab..sh| 000011a0 73 74 72 74 61 62 00 2e 74 65 78 74 00 2e 41 52 |strtab..text..AR| 000011b0 4d 2e 61 74 74 72 69 62 75 74 65 73 00 00 00 00 |M.attributes....| 000011c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 000011e0 00 00 00 00 00 00 00 00 1b 00 00 00 01 00 00 00 |................| 000011f0 06 00 00 00 00 10 00 00 00 10 00 00 1c 00 00 00 |................| 00001200 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 |................| 00001210 21 00 00 00 03 00 00 70 00 00 00 00 00 00 00 00 |!......p........| 00001220 1c 10 00 00 12 00 00 00 00 00 00 00 00 00 00 00 |................| 00001230 01 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 |................| 00001240 00 00 00 00 00 00 00 00 30 10 00 00 00 01 00 00 |........0.......| 00001250 04 00 00 00 05 00 00 00 04 00 00 00 10 00 00 00 |................| 00001260 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................| 00001270 30 11 00 00 5c 00 00 00 00 00 00 00 00 00 00 00 |0...\...........| 00001280 01 00 00 00 00 00 00 00 11 00 00 00 03 00 00 00 |................| 00001290 00 00 00 00 00 00 00 00 8c 11 00 00 31 00 00 00 |............1...| 000012a0 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................| 000012b0 

pouvez google le format de fichier et trouver beaucoup d’informations sur wikipedia, avec un peu plus sur un des liens

informations d'en-tête utiles

 00 10 00 00 entrh 34 00 00 00 phoff c0 11 00 00 shoff 00 02 00 05 flags 34 00 ehsize 20 00 phentsize 01 00 phnum 28 00 shentsize 06 00 shnum 05 00shstrndx 

donc si je regarde le début des sections, il y en a un certain nombre

 0x11C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x11E8 1b 00 00 00 01 00 00 00 06 00 00 00 00 10 00 00 00 10 00 00 0x1210 21 00 00 00 03 00 00 70 00 00 00 00 00 00 00 00 1c 10 00 00 0x1238 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 30 10 00 00 0x1260 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 30 11 00 00 0x1288 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 8c 11 00 00 

0x1260 offset de type strtab 0x1130 qui est divisé en chaînes terminées par null jusqu'à ce que vous atteigniez un double null

 [0] 00 [1] 73 6f 2e 6f 00 so.o [2] 24 61 00 $a [3] 5f 5f 62 73 73 5f 73 74 61 72 74 5f 5f 00 __bss_start__ [4] 5f 5f 62 73 73 5f 65 6e 64 5f 5f 00 __bss_end__ [5] 5f 5f 62 73 73 5f 73 74 61 72 74 00 __bss_start [6] 6d 61 69 6e 00 main ... 

main est à l'adresse 0x115F dans le fichier qui est en décalage 0x2F dans le strtab.

0x1238 symtab commence à 0x1030, 0x10 ou 16 octets par entrée

 00001030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001040 00 00 00 00 00 10 00 00 00 00 00 00 03 00 01 00 |................| 00001050 00 00 00 00 00 00 00 00 00 00 00 00 03 00 02 00 |................| 00001060 01 00 00 00 00 00 00 00 00 00 00 00 04 00 f1 ff |................| 00001070 06 00 00 00 00 10 00 00 00 00 00 00 00 00 01 00 |................| 00001080 18 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................| 00001090 09 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................| 000010a0 17 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |................| 000010b0 55 00 00 00 00 10 00 00 00 00 00 00 10 00 01 00 |U...............| 000010c0 23 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |#...............| 000010d0 2f 00 00 00 08 10 00 00 00 00 00 00 10 00 01 00 |/...............| 000010e0 34 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |4...............| 000010f0 3c 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |<...............| 00001100 43 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |C...............| 00001110 48 00 00 00 00 00 08 00 00 00 00 00 10 00 01 00 |H...............| 00001120 4f 00 00 00 1c 10 01 00 00 00 00 00 10 00 01 00 |O...............| 

000010d0 2f 00 00 00 a le décalage 0x2f dans la table des symboles, il est donc principal; à partir de cette entrée, l'adresse 08 10 00 00 ou 0x1008 dans la mémoire du processeur, malheureusement, en raison des valeurs que j'ai choisies, il s'agit également du décalage de fichier, ne vous trompez pas.

cette section est de type 00000001

 0x11E8 1b 00 00 00 01 00 00 00 06 00 00 00 00 10 00 00 00 10 00 00 offset 0x1000 in the file 0x1C bytes 

voici le programme, le code machine.

 00001000 00 00 00 eb fe ff ff ea 01 10 81 e2 01 20 82 e2 00001010 01 30 83 e2 01 40 84 e2 fa ff ff ea 41 11 

Donc, à partir du décalage de mémoire 0x1008 qui est 8 octets après le point d’entrée (malheureusement, j’ai choisi une mauvaise adresse à utiliser), nous devons utiliser un décalage de 0x8 octets dans ces données.

 01 10 81 e2 01 20 82 e2 00001008 
: 1008: e2811001 add r1, r1, #1 100c: e2822001 add r2, r2, #1 1010: e2833001 add r3, r3, #1

tout cela est très dépendant du fichier, le cpu ne se soucie pas des étiquettes, principal signifie seulement quelque chose aux humains, pas le cpu.

Si je convertis l'elfe dans d'autres formats parfaitement exécutables:

fiche de motorola:

 S00A0000736F2E7372656338 S1131000000000EBFEFFFFEA011081E2012082E212 S10F1010013083E2014084E2FAFFFFEAB1 S9031000EC 

image binary brute

 hexdump -C so.bin 00000000 00 00 00 eb fe ff ff ea 01 10 81 e2 01 20 82 e2 |............. ..| 00000010 01 30 83 e2 01 40 84 e2 fa ff ff ea |.0...@......| 0000001c 

Les octets d’instructions d’intérêt sont bien sûr là, mais les informations de symbole ne sont pas. Cela dépend du format de fichier qui vous intéresse: 1) si vous pouvez trouver «principal», puis 2) imprimer les premiers octets à cette adresse.

Hmm, un peu dérangeant, mais si vous créez un lien pour 0x2000, gnu ld brûle de l'espace disque et place le décalage à 0x2000, mais choisissez 0x20000000 et consum plus d'espace disque, mais pas autant.

 000100d0 2f 00 00 00 08 00 00 20 00 00 00 00 10 00 01 00 

indique que le décalage de fichier est 0x010010 mais que l'adresse dans l'espace cible est 0x20000008

 00010010 01 30 83 e2 01 40 84 e2 fa ff ff ea 41 11 00 00 00010020 00 61 65 61 62 69 00 01 07 00 00 00 08 01 

Juste pour démontrer / appliquer le décalage de fichier et l'adresse de l'espace mémoire cible sont deux choses différentes.

c'est un très bon format pour ce que vous voulez faire

 arm-none-eabi-objcopy -O symbolsrec so.elf so.srec cat so.srec $$ so.srec $a $20000000 _bss_end__ $2001001c __bss_start__ $2001001c __bss_end__ $2001001c _start $20000000 __bss_start $2001001c main $20000008 __end__ $2001001c _edata $2001001c _end $2001001c _stack $80000 __data_start $2001001c $$ S0090000736F2E686578A1 S31520000000000000EBFEFFFFEA011081E2012082E200 S31120000010013083E2014084E2FAFFFFEA9F S70520000000DA