LFSROFL
Par Baboon le mardi, février 9 2010, 13:57 - Lien permanent
Me revoilà avec mes titres de post foireux \o/
Suite à la lecture du post de corkami / Ange (je ne sais même pas si corkami est un pseudo ou un nom de blog ...) j'ai eu une idée pour junker les conditions de sortie des boucles.
Pour un état de l'art de ce qui existe allez donc voir le lien plus haut. Le but du junk de condition de sortie et de rendre la pose d'un breakpoint à la sortie d'une boucle plus difficile, empêchant donc le reverser de passer des milliers de layers en quelques secondes à l'aide d'un bête script.
L'idée dans les les techniques précédentes était de rendre la condition de fin obscure et / ou de cacher l'adresse de fin de boucle. Ce que je propose ici c'est de chiffrer cette adresse de façon à la rendre illisible tant que la condition de fin n'aura pas été atteinte.
Le principe est simple, au lieu d'utiliser un compteur que l'on décrémente et une adresse sur laquelle on saute, on utilise 2 PRNG en l'occurence dans mon poc des LFSR (d'où le supaire nom du post). Ces 2 LFSR serviront pour l'un de compteur, pour l'autre de clef de déchiffrement de l'adresse de fin de boucle.
(l'intérêt des LFSR c'est que s'ils sont bien construit ils sont cycliques et de période maximale - 1 : LFSR.next() décrira tout les nombres de 1 à 2^32-1 sans doublon si on l'exécute 2^32-1 fois avec une seed non nulle (il manque juste la valeur nulle pour qu'il décrive tout les nombres de 32bits))
Le principe est simple et l'exécution peut être décrite comme suis :
- A = ror(A,1) ^ LFSR_K.next()
- si LFSR_i.next() == END :
- Sauter à l'adresse A
- exécution du corps de la boucle
- retour à l'étape 1
Avec A l'adresse chiffrée, LFSR_K le LFSR utilisé comme clef et LFSR_i le LFSR utilisé comme index.
La valeur de END est calculée au moment de la compilation en itérant n fois le LFSR, n étant le nombre de fois que l'on souhaite exécuter la boucle + 1
La valeur initiale de A est calculée à l'aide de n et de LFSR_K grâce à l'algo suivant :
A = rol(address,n%32); K = seed_K; // (seed étant la valeur initiale de LFSR_K) for (i = 0; i < n; i++) { K = LFSR_K.next(K); A = ror(A ^ K,1); } A = rol(A,n%32);
Pourquoi avoir mis un ror en plus du xor dans l'algorithme de déchiffrement : la raison est simple, si on se borne au simple xor, l'inversion du bit de poid fort de A à son état initial entrainera une inversion de ce même bit une fois A déchiffré et donc on aboutira à une adresse invalide qui génèrera une exception, le cracker / reverser n'aura alors plus qu'à regarder la valeur d'eip, à faire un petit and 0x7FFFFFFF et il aura l'adresse de fin de boucle.
Vous allez maintenant me dire : "Ca pue ta technique, suffit de foutre un BP sur le goto A et s'fini ! n0ob !", heureusement non, il suffit de ne pas utiliser de branches conditionnelles et de remplacer le if (condition) {label1} else {label2} par un goto tcondition avec t = label1,label2 à ce moment là il n'y a plus de code spécifique au saut terminal.
"Bon ok, soit, mais si je fouts un conditionnal BP ?" : certes ca marcherai mais si le compteur de boucle est assez grand ca risque de prendre du temps, de plus il est possible de créer des fake sauts qui ne seront jamais pris, il suffit d'utiliser d'autres LFSR ou d'insérer des conditions où END correspond à un état du LFSR plus lointain que celui nécessaire pour sortir de la boucle :
- i = IV
- i = LFSR_i.next(i)
- A1 = ror(A1,1) ^ LFSR_K1.next()
- si i == LFSR_i.next(END) : // condition jamais vérifiée
- Sauter à l'adresse A1
- A2 = ror(A2,1) ^ LFSR_K2.next()
- si i == END : // vrai sortie de la boucle
- Sauter à l'adresse A2
- A3 = ror(A3,1) ^ LFSR_K3.next()
- si i == LFSR_i.next(LFSR_i.next(END)) : // condition jamais vérifiée
- Sauter à l'adresse A3
- exécution du corps de la boucle
- retour à l'étape 2
Dans l'exemple ci dessus LFSR_i.next(END) et LFSR_i.next(LFSR_i.next(END)) ne peuvent être atteint que lorsque i a déjà atteint la valeur END et donc que l'on est déjà sortit de la boucle, néanmoins pour le savoir il faut exécuter i = LFSR_i(i) tant que i != END pour le savoir et il n'est alors plus nécessaire de poser des BP conditionnels.
Il reste l'option de poser des BP conditionnels sur tous les test i == * mais il suffit d'ajouter x fake jumps pour ralentir x fois plus l'exécution du code avec des BP conditionnels.
La solution la plus élégante et la plus rapide serait est d'isoler les LFSR ainsi que les valeurs initiales, les valeurs de comparaison et les adresses chiffrée pour ensuite retrouver l'adresse de fin mais dans le cas d'un programme un tant soit peu junké cela peut vite devenir compliqué...
Pour finir voila un petit POC codé en python à l'arrache (qui génère une source GoAsm), amusez vous à tracer un peu dedans, c'est assez déroutant
J'ai écris ça un peu rapidement, si vous avez besoin d'éclaircissement : lisez la source et postez un commentaire
Commentaires
Très bonne idée, de détailler ta (ton?) POC!
pour mieux comprendre comment ça marche, ça aide de se faire une version plus légère, donc changer:
...
end = 1
...
endaddress = 0x00401030 + 0x3b * nbTests
...
...end + 1, end + 2)))
...
##random.shuffle(codes)
(Corkami est le nom du blog)
Merci pour la version courte
Un poc comme l'onomatopée, non ?
J'ai pas mal de mal avec le genre des mot anglais (même si bon "preuve" c'est bien féminin ...)
Je dois être le seul à dire indifféremment un ou une thread ...
(Sinon pourquoi corkami ?)
rassure toi, moi aussi, je dois me forcer pour blogger en fr a peu pres correctement
concernant corkami, aucune idée - si tu trouve un sens, hésite pas
ca veut dire 'filles' en polonais apparemment, sinon je n'ai pas particulièrement d'ami à Cork...