┌───────────────────────┐
▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
│ █ █ █ █ █ █ │
│ █ █ █ █ █▀▀▀▀ │
│ █ █ █ █ ▄ │
│ ▄▄▄▄▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
│ ▄ ▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
│ ▄▄▄▄▄ │
│ █ │
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()