Pourquoi le code assembleur suivant est-il un outil anti-débogage?
l1: call l3 l2: ;some code l3: mov al, 0c3h mov edi, offset l3 or ecx, -1 rep stosb
Je sais que C3h est RETN
et je sais que stobs
écrit la valeur dans al
comme opcode en fonction du décalage dans edi
et cela est fait pour ecx
fois à cause de rep
.
Je suis également conscient du fait que les stobs
et stosw
fonctionneront s’ils ont été pré-récupérés sur l’architecture intel comme leur format original.
Si nous exécutons le programme en mode débogué, la pré-extraction n’est pas pertinente et l’étiquette l2 s’exécutera (car elle est en une seule étape), sinon, s’il n’y a pas de débogueur, il sera ping entre l1 et l3.
Lorsque le programme est débogué (c.-à-d. Une seule étape), la queue de prélecture est vidée à chaque étape (en cas d’interruption). Cependant, lorsqu’il est exécuté normalement, cela n’arrivera pas à rep stosb
. Les processeurs plus anciens ne l’ont pas purgé même en cas d’écriture en mémoire dans la zone mise en cache, afin de prendre en charge le code auto-modifiable qui a été modifié à l’exception des rep movs
et rep stosb
. (IIRC a finalement été corrigé dans les processeurs i7.)
C’est pourquoi, s’il y a un débogueur (simple étape), le code s’exécutera correctement et lorsque rep stosb
sera remplacé par ret
l2
sera exécuté. Lorsqu’il n’y a pas de débogueur, rep stosb
continuera, puisque ecx
est le plus grand possible, il finira par écrire quelque part qu’il n’est pas censé écrire et qu’une exception se produira.
Cette technique anti-débogage est décrite dans cet article .
La seule chose qu’un débogueur fait ici est un délai supplémentaire. Cela peut être la clé de la façon dont cela fonctionne. Le manuel d’Intel (et je suppose que AMD) indique explicitement que le code auto-modifiable n’est pas garanti “à fonctionner” à moins que le programme ne signale à la CPU que la ligne de cache contenant l’instruction modifiée a changé. Ceci permet de rendre la logique de lecture anticipée peu coûteuse; les concepteurs de puces ne veulent pas avoir de matériel qui teste continuellement que chaque octet d’une cachette d’instructions est toujours valide.
Donc, je suppose que ce qui se passe avec les débogueurs est l1 appels l3, qui stocke un retour après le repstosb, et le retour est exécuté en raison des longs retards induits par le débogueur en pas à pas, forçant le cache après avoir modifié l3.
Sans le débogueur, je devine l’instruction (non représentée) après l’exécution du stosb. Si c’était un saut vers “sans débogueur”, le succès du saut démontrerait qu’aucun débogueur pas à pas n’était utilisé.
Si je trouvais ce code dans une application, je refuserais de l’exécuter.