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 :

  1. A = ror(A,1) ^ LFSR_K.next()
  2. si LFSR_i.next() == END :
    • Sauter à l'adresse A
  3. exécution du corps de la boucle
  4. 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 :

  1. A = rol(address,n%32);
  2. K = seed_K; // (seed étant la valeur initiale de LFSR_K)
  3. for (i = 0; i < n; i++)
  4. {
  5. K = LFSR_K.next(K);
  6. A = ror(A ^ K,1);
  7. }
  8. 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 :

  1. i = IV
  2. i = LFSR_i.next(i)
  3. A1 = ror(A1,1) ^ LFSR_K1.next()
  4. si i == LFSR_i.next(END) : // condition jamais vérifiée
    • Sauter à l'adresse A1
  5. A2 = ror(A2,1) ^ LFSR_K2.next()
  6. si i == END : // vrai sortie de la boucle
    • Sauter à l'adresse A2
  7. A3 = ror(A3,1) ^ LFSR_K3.next()
  8. si i == LFSR_i.next(LFSR_i.next(END)) : // condition jamais vérifiée
    • Sauter à l'adresse A3
  9. exécution du corps de la boucle
  10. 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 ;)