┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ Désinfecteur de PT_NOTE en Python │ █ │ ~ manizzle └───────────────────█ ──┘ [ Traduit en français par @MorpheusH3x)from the ret2school team ] Bonjour à tous. Pour commencer, je ne suis pas un AVer. Les antivirus sont nuls, ils comportent des bogues et sont généralement sujets à l'exploitation. N'hésitez pas à faire fuir l'enfer de Lief et Capstone. Je suis sûr qu'ils ont des bogues. Maintenant sur la façon de désinfecter... La technique d'injection de PT_NOTE est assez propre et permet de remplir un emplacement mémoire tout fait avec de bonnes choses. Mais avec toutes les techniques d'infection, il y a généralement toujours une technique de désinfection. C'est la nature de la vie. J'aime à penser que la facilité avec laquelle on désinfecte est un moyen de mesurer la qualité d'une technique d'infection. Plus il y a de constantes dans une technique de désinfection, plus elle a de chances d'être brisée plus rapidement. Le jeu du chat et de la souris est sans fin, et c'est le seul moyen de développer des virus de plus en plus virus de plus en plus insidieux. Continuez à jouer le jeu avec vous-même et votre virus sera la chose de la folie et de l'émerveillement. Dans cette désinfection, nous utilisons le fait que la plupart des virus essaieront de charger le segment PT_NOTE le plus loin possible au cas où le binaire serait volumineux. Et qu'ils essaieraient de s'assurer que les goodies/informations ne soient pas mappés et causer des problèmes de chargement avec le binaire, car il faut être discret, non ? Nous utilisons Kmeans pour commencer et regrouper les segments PT_LOAD ensemble et nous utilisons l'inertie des clusters par rapport à leurs centroïdes comme un moyen de mesurer l'efficacité de Kmeans. Habituellement, pour les infections, il y a 1 PT_NOTE qui est infecté mais peut-être que sblip vous le dira plus tard, parfois il y en a 2 :) if (math.log(cluster_1.inertia_)/math.log(cluster_2.inertia_)) < INERTIA_RATIO: Une fois que nous avons trouvé quels segments semblent être cartographiés un peu plus loin que d'habitude (bien que si vous Si vous vouliez mapper le PT_NOTE entre les PT_LOADS valides et re-baser toute l'image, je veux dire qui ferait une telle chose ? une telle chose ?), nous pouvons commencer à fouiller dans son code. Habituellement, ces virus vont continuer à s'agiter, infecter d'autres fichiers, mais à un moment donné, , vous savez, pour éviter toute suspicion. On peut supposer que le saut vers le point d'entrée original se produit à la fin du segment PT_NOTE infecté, nous le recherchons donc. Parfois le jmp est direct, parfois il est dérivé. Nous suivons simplement la cible du jmp jusqu'à un point où il ajoute l' (original entry point, point d'entrée original) à la base qu'il a calculée auparavant (si vous voulez être vraiment fantaisiste, vous pouvez toujours utiliser une chaîne use-def, mais bien sûr les virii peuvent devenir encore plus fantaisistes et vous forcer à résoudre votre chaînes de fonctions croisées OH MY !) add {target}, CONST Remets-le dans ton PHDR et tu es de retour dans l'action. Plus de chance la prochaine fois, mon ami ! ################################################################## #!/usr/bin/env python3 from capstone import * from collections import Counter import lief import math import numpy as np from sklearn.cluster import KMeans import sys # ne sois pas un suceur d'anti-re, mec SUCKER_PUNCH = 3 # testé sur quelques grands et petits binaires # la plupart des binaires normaux se situent dans la fourchette de 1.0-quelque-chose # même les gros binaires de plusieurs mégaoctets. Je suis sûr que nous pouvons trouver quelque chose # qui le casse cependant INERTIA_RATIO = 1.1 def find_anomalous_load_segment(segment_ranges): segment_array = np.array(segment_ranges) cluster_2 = KMeans(n_clusters=2, random_state=0).fit(segment_array) cluster_1 = KMeans(n_clusters=1, random_state=0).fit(segment_array) if (math.log(cluster_1.inertia_)/math.log(cluster_2.inertia_)) < INERTIA_RATIO: print("No anomaly detected") return None cluster_counts = {v:k for k,v in Counter(cluster_2.labels_.tolist()).items()} if 1 not in cluster_counts: print("No singular cluster found") return None return segment_array[np.where(cluster_2.labels_ == cluster_counts[1])[0]][0] def find_oep(segment_bytes, segment_start): # nous supportons x64-64 pour l'instant mais cela peut être facilement porté vers # d'autres architectures si nécessaire. Ce serait cool d'utiliser une IR # ici pour qu'il soit multiplateforme. md = Cs(CS_ARCH_X86, CS_MODE_64) md.skipdata = True oep = None last_jump = None early_bail = 0 for r in [instr for instr in md.disasm(segment_bytes, segment_start)][::-1]: if last_jump: # si nous voyons une instruction de la forme # add {target}, CONST # nous sommes probablement en train d'ajouter l'OEP à l'adresse de base # Nous pouvons rendre cela plus générique en construisant une # une vraie chaîne use-def ici et en résolvant la valeur réelle de # rax ici. Il faudrait trouver des fonctions comme get_rip # qui sont utilisées pour faciliter les jmps vers le code relatif. if last_jump + ", " in r.op_str and "add" == r.mnemonic.strip(): try: oep = int(r.op_str.split(",")[1].strip(), 16) break except Exception as e: # continuer, mais il est peu probable que nous le trouvions maintenant # Continuez à essayer un peu plus, mais pas trop. # vous ne voulez pas vous faire avoir par des anti-re early_bail += 1 if early_bail == SUCKER_PUNCH: break continue if not last_jump and r.mnemonic.strip() == "jmp": target = r.op_str.strip() # Essayez de voir si le jmp se produit directement # puis prendre cette valeur comme l'OEP try: oep = int(target, 16) break except Exception as e: # Si non, il s'agit probablement d'un saut de registre. oep = None last_jump = target return oep def main(): l = lief.parse(sys.argv[1]) load_segs = [ [ll.virtual_address, ll.virtual_address + ll.virtual_size] for ll in l.segments if ll.type == lief.ELF.SEGMENT_TYPES.LOAD ] anomalous_segment_start, anomalous_segment_end = find_anomalous_load_segment(load_segs) segment_bytes = l.get_content_from_virtual_address(anomalous_segment_start, anomalous_segment_end) real_oep = find_oep(bytes(segment_bytes), anomalous_segment_start) print("found OEP: ", hex(real_oep)) l.header.entrypoint = real_oep l.write(sys.argv[1] + ".cleaned") if __name__ == "__main__": main()