Question:
Qu'est-ce que l'obscurcissement des "instructions qui se chevauchent"?
perror
2013-04-03 13:11:29 UTC
view on stackexchange narkive permalink

J'ai analysé certains binaires dans x86 / x86-64 en utilisant quelques astuces d'obfuscation. L'une s'appelait instructions qui se chevauchent . Quelqu'un peut-il expliquer comment fonctionne cette obfuscation et comment la contourner?

Je me suis demandé quel était le terme approprié pour cela. J'ai entendu la «scission d'instruction» utilisée assez fréquemment.
Je rencontre plusieurs manières de parler de cette technique. Mais, il est vrai qu'aucun n'a été largement adopté par la communauté. "_Overlaping Instructions_" était le plus utilisé que je puisse trouver depuis maintenant. Mais, je pourrais avoir lu seulement une petite partie de la documentation existante à ce sujet.
Quatre réponses:
#1
+28
Remko
2013-04-03 13:25:43 UTC
view on stackexchange narkive permalink

L'article Analyse statique des exécutables x86 explique assez bien les instructions qui se chevauchent. L'exemple suivant en est tiré (page 28):

  0000: B8 00 03 C1 BB mov eax, 0xBBC103000005: B9 00 00 00 05 mov ecx, 0x05000000000A: 03 C1 add eax, ecx000C : EB F4 jmp $ -10000E: 03 C3 add eax, ebx0010: C3 ret  

En regardant le code, on ne voit pas quelle sera la valeur de eax à l'instruction de retour ( ou que l'instruction de retour est jamais atteinte, d'ailleurs). Cela est dû au saut de 000C à 0002, une adresse qui n'est pas explicitement présente dans la liste (jmp $ -10 indique un saut relatif par rapport à la valeur actuelle du compteur de programme, qui est 0xC, et 0xC10 = 2). Ce saut transfère le contrôle au troisième octet de l'instruction de déplacement de cinq octets à l'adresse 0000. L'exécution de la séquence d'octets commençant à l'adresse 0002 déplie un tout nouveau flux d'instructions:

  0000: B8 00 03 C1 BB mov eax, 0xBBC103000005: B9 00 00 00 05 mov ecx, 0x05000000000A: 03 C1 ajouter eax, ecx000C: EB F4 jmp $ -100002: 03 C1 ajouter eax, ecx0004: BB B9 00 00 00 mov ebx, 0xB90009: 05 03 C1 EB F4 add eax, 0xF4EBC103000E: 03 C3 add eax, ebx0010: C3 ret  

Il serait intéressant de savoir si / comment Ida Pro et surtout le plugin Hex Rays gèrent cela. Peut-être que @IgorSkochinsky peut commenter cela ...

#2
+16
Ange
2013-04-08 19:47:54 UTC
view on stackexchange narkive permalink

On l'appelle aussi l'astuce du «saut au milieu».

explication

règles d'exécution

  • la plupart des instructions prennent plus d'un octet à encoder
    • ils peuvent prendre jusqu'à 15 octets sur les processeurs modernes
  • l'exécution peut commencer à n'importe quelle position tant que les autorisations sont valides

donc tout octet suivant le premier d'une instruction peut être réutilisé pour démarrer une autre instruction.

abuser des désassembleurs

  • les désassembleurs simples démarrent le instruction suivante juste après la fin de la dernière.

donc ces désassembleurs (qui ne suivent pas le flux) masqueront l'instruction qui se trouve au milieu d'un visible .

exemples

triviaux

  00: EB 01 jmp 302: 68 c3 90 90 90 push 0x909090c3  

s'exécutera effectivement comme

  00: EB 01 jmp 303: C3 retn ...  

car le premier jmp saute le premier octet 68 (qui encode une poussée immédiate) de l'instruction suivante.

chevauchements multiples

de cet exemple, 69 84 définit une instruction imul qui peut prendre jusqu'à 11 octets. Ainsi, vous pouvez insérer plusieurs lignes d'instructions dans ses 'faux' opérandes.

  00: EB02 jmp 402: 69846A40682C104000EB02 imul eax, [edx + ebp * 2 + 0102C6840], 0x002EB00400D: ... .  

sera en fait exécuté comme

  00: EB02 jmp 404: 6A40 push 04006: 682C104000 push 0x40102C0B: EB02 jmp 0xF0F: ...  

instruction se chevauchant

L'instruction saute dans le 2ème octet d'elle-même:

  00: EBFF jmp 102: C0C300 rol bl, 0  

sera en fait exécuté comme

  00: EBFF jmp 101: FFC0 inc eax03: C3 retn  

différents modes CPU

cette obfuscation peut être étendue au saut vers le même EIP mais dans un mode CPU différent:

  • Les processeurs 64b prennent toujours en charge l'instruction 32b
  • Le mode 64b utilise 0x33 pour cs
  • certaines instructions sont disponible uniquement dans un mode particulier:
    • arpl en mode 32b
    • movsxd en mode 64b

pour pouvoir accéder au même EIP mais avec un CS différent, et obtenir des instructions différentes.

Dans cet exemple, ce code est d'abord exécuté en mode 32b:

  00: 63D8 arpl ax, bx02: 48 dec eax03: 01C0 add eax, eax05: CB retf  

puis ré-exécuté en mode 64 bits comme:

  00: 63D8 movsxd rbx, eax02: 4801C0 add rax, rax05: CB retf  

Dans ce cas, les instructions se chevauchent, non pas à cause d'un EIP différent, mais parce que le CPU est temporairement passé du mode 32b à 64b.

Est-il possible de passer du mode 32 au 64 bits en tant que programme s'exécutant dans l'espace utilisateur sur tous les principaux systèmes d'exploitation?
@Dougall oui. Sous Windows, cela se fait par `X86SwitchTo64BitMode ()` (ou manuellement avec un appel / saut éloigné avec le sélecteur de segment 33). Cependant, je suis tout à fait certain que cela est spécifique à l'implémentation de Windows WOW64 et non applicable dans un autre système d'exploitation.
@Ange, pouvez-vous mettre à jour le lien https://code.google.com/p/corkami/source/browse/trunk/src/CoST/CoST.asm?r=1593#2247? Merci!
#3
+8
joxeankoret
2013-04-03 14:19:39 UTC
view on stackexchange narkive permalink

Presque toutes les instructions multi-octets peuvent être utilisées comme instruction de chevauchement dans x86 / x86_64. La raison est très simple: les jeux d'instructions x86 et x86_64 sont CISC. Ce qui signifie, entre autres, que les instructions n'ont pas de longueur fixe. Ainsi, comme les instructions sont de longueur variable, en écrivant soigneusement ce code machine, chaque instruction est susceptible de masquer les instructions qui se chevauchent.

Par exemple, étant donné le code suivant:

  [ 0x00408210: 0x00a31e10] > b0x000050f5 (01) 56 PUSH ESI 0x000050f6 (04) 8b742408 MOV ESI, [ESP + 0x8] 0x000050fa (01) 57 PUSH EDI 0x000050fb (03) c1e603 SHL ESI, 0x40000 MOV ESI (065000be) ESI + 0x40a058] 0x00005104 (01) 57 PUSH EDI 0x00005105 (06) ff15f4804000 CALL 0x004080f4; 1 KERNEL32.dll! GetModuleHandleA0x0000510b (02) 85c0 TEST EAX, EAX 0x0000510d (02) 750b JNZ 0x0000511a; 2 

Supposons que quelque part après la dernière instruction, il y ait un saut au milieu d'une instruction dans le code affiché comme, par exemple, vers le 2ème octet du MOV ESI ... instruction:

  [0x000050f7: 0x00405cf7] > c0x000050f7 (02) 7424 JZ 0x0000511d; 1 0x000050f7 ------------------------------------------------ ---------------------- 0x000050f9 (03) 0857c1 OU [EDI-0x3f], DL 0x000050fc (02) e603 OUT 0x3, AL  

Il s'avère que cette instruction se transforme en JZ. Ce qui est valable. Saut au 3e octet ...

  [0x000050f7: 0x00405cf7] > s +1 [0x000050f8: 0x00405cf8] > c0x000050f8 (02) 2408 ET AL, 0x8 0x000050fa (01) 57 PUSH E 0x000050fb (03) c1e603 SHL ESI, 0x3 0x000050fe (06) 8bbe58a04000 MOV EDI, [ESI + 0x40a058]  

Saut au 2ème octet de l'instruction CALL:

  [0x000050f5: 0x00405cf5] > s 0x5106 [0x00005106: 0x00405d06] > c0x00005106 (05) 15f4804000 ADC EAX, 0x4080f '\ x8e \ x91'0x0000510b (02) 85c0 TEST EAX, EAX 0x0000510d (02) 750b JNZ 0x0000511a; 1 

Comme vous pouvez le voir, pratiquement toutes les instructions multi-octets sont susceptibles d'être utilisées comme des instructions qui se chevauchent.

Cette astuce anti-inversion est assez souvent utilisée avec prédicats opaques afin de f ** k le diagramme de flux.

Alors, vous voulez dire qu'il n'y a aucun moyen de construire une telle liste? Un autre point qui me surprend beaucoup à propos des opcodes x86 / x86-64, est sa capacité à se resynchroniser après un certain temps avec le flux d'instructions d'origine. Cette propriété aide également beaucoup à faire se chevaucher les instructions. Cependant, je ne sais pas pourquoi la resynchronisation fonctionne si bien.
#4
+3
Dougall
2013-04-08 20:16:04 UTC
view on stackexchange narkive permalink

Comme les instructions x86 peuvent être de n'importe quelle longueur et n'ont pas besoin d'être alignées, la valeur immédiate d'une instruction peut être une autre instruction. Par exemple:

  00000000 0531C0EB01 add eax, 0x1ebc03100000005 055090EB01 add eax, 0x1eb90500000000A 05B010EB01 add eax, 0x1eb10b00000000F EBF0 jmp short 0x1  

Cela fait exactement ce qu'il dit , jusqu'au saut. Lorsqu'il saute, la valeur immédiate ajoutée à eax devient une instruction, donc le code ressemble à:

  00000000 05 db 500000001 31C0 xor ax, ax xor ax, ax00000003 EB01 jmp short 0x600000005 05 db 500000006 50 poussoir ax00000007 90 nop00000008 EB01 jmp court 0xb0000000A 05 db 50000000B B010 mov al, 0x10 mov al, 0x10 ....  

Les instructions qui sont réellement significatives sont affichées à droite - colonne de la main. Dans cet exemple, les instructions de saut courts sont utilisées pour ignorer la partie add eax de l'instruction ( 05 ). Il convient de noter que cela pourrait être fait plus efficacement en utilisant un octet unique pour manger les 05 , comme 3C05 qui est cmp al, 0x5 , et serait inoffensif dans le code qui ne se soucie pas des indicateurs.

Dans le modèle ci-dessus, vous remplacez facilement tous les 05 par 90 (nop) pour afficher le démontage correct. Cela peut être rendu plus délicat en utilisant les 05 comme valeurs immédiates du code caché (dont dépend l'exécution). En réalité, la personne qui obscurcirait le code n'utiliserait probablement pas add eax encore et encore, et pourrait changer l'ordre d'exécution pour le rendre plus compliqué à tracer.

J'ai préparé un échantillon en utilisant le modèle ci-dessus. Il s'agit d'un fichier Linux ELF 32 bits en base64. L'effet du code caché est l'exécution de execve ("// usr / bin / python", 0, 0) . Je vous suggère de ne pas exécuter de binaires aléatoires à partir de réponses SE. Vous pouvez cependant l'utiliser pour tester vos désassembleurs. IDA, Hopper et objdump tous échouent lamentablement au premier coup d'œil, même si je suppose que vous pouvez obtenir l'IDA pour le faire correctement en quelque sorte.

  f0VMRgEBAQAAAAAAAAAAAAIAAwABAAAAYIAECDQAAAAoAQAAAAAAADQAIAABACgAAwACAAEAAAAAAAAAAIAECACABAgUAQAAFAEAAAUAAAAAEAAAAAAAAAAAAAAAAAAABTHA6wEFUJDrAQWwEOsBBffg6wEF9 + DrAQWJw + sBBbRu6wEFsG / rAQX34 + sBBbRo6wEFsHTrAQVQkOsBBbR56wEFsHDrAQX34 + sBBbQv6wEFsG7rAQVQkOsBBbRp6wEFsGLrAQX34 + sBBbQv6wEFsHLrAQVQkOsBBbRz6wEFsHXrAQX34 + sBBbQv6wEFsC / rAQVQkOsBBTHJ6wEF9 + HrAQWJ4 + sBBbAL6wEFzYDrAelN //// AC5zaHN0cnRhYgAudGV4dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAAAGAAAAYIAECGAAAAC0AAAAAAAAAAAAAAAQAAAAAAAAAAEAAAADAAAAAAAAAAAAAAAUAQAAEQAAAAAAAAAAAAAAAQAAAAAAAAA =  


Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 3.0 sous laquelle il est distribué.
Loading...