Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
Mergen — Désobfuscation par optimisation à l'aide de LLVM IR et de l'analyse de l'assembleur. | Kitploit
Outils/GitHubGitHub/nac-l/mergen
Analyse StatiqueAnalyse Dynamique (Sandboxing)Rétro-ingénierieAnalyse de BinairesExploitation de Binaires
GitHubnac-l/mergen

Mergen

Désobfuscation par optimisation à l'aide de LLVM IR et de l'analyse de l'assembleur.

Voir le dépôt
87894il y a 3 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Aperçu du projet :

Mergen est un outil conçu pour convertir du code assembleur en représentation intermédiaire LLVM (IR). Cet outil est conçu pour :

  • La désobfuscation ou la dévirtualisation de code binaire obfusqué
  • L'amélioration du processus de rétro-ingénierie, le rendant plus efficace et efficient, en particulier pour les systèmes logiciels complexes.

Guide pour construire et exécuter

Pour construire et exécuter le projet, consultez docs/BUILDING.md.

Porte de référence de réécriture

La porte construit des échantillons PE ciblés, exécute lifter et vérifie les sorties IR levées.

  • Documentation du workflow : docs/REWRITE_BASELINE.md
  • Porte en une commande : scripts\\rewrite\\run.cmd

Objectifs principaux :

  • Désobfuscation

  • Dévirtualisation

  • Optimisation

Comment cela fonctionne-t-il ?

Nous exécutons symboliquement (ou levons symboliquement) la cible, l'idée ici n'est pas de lever des instructions individuelles, mais de lever une fonction entière. Nous ne nous attendons pas à ce qu'une instruction ou un bloc de base se comporte de la même manière à chaque fois, nous les traitons plutôt comme s'ils pouvaient l'être et le sont à des fins différentes à chaque fois. Nous essayons de garder l'IR généré aussi simple et optimisable que possible. Nous avons également des besoins différents de ceux d'un compilateur habituel. Nous utilisons l'analyse pour évaluer le flux de contrôle. Nous ne pouvons pas dépendre de LLVM pour toute notre analyse, car ils sont créés pour des objectifs différents et pourraient être sous-optimaux pour notre cas d'utilisation.

image

Exemples

Voici l'exemple pratique pour illustrer comment Mergen résout les programmes virtualisés.

  1. VMProtect
  2. Branches/Jumptables
  3. Themida 3.1.6.0 LION64 (Red)

Exemple n°1 (VMProtect)

Voici notre programme cible

root@kitploit:~
struct test {
    int a;
    int b;
    int c;
};

int maths(test a, int b, int c) {
        return a.a  + b - c;
}

image

image

Paramètres VMProtect, tout est désactivé, nous virtualisons la fonction en réglage ultra. (Versions testées 3.4.0-3.6.0 3.8.1)

image

image

Ici, nous exécutons mergen. Le premier argument est le nom du fichier et le second argument est l'adresse de la fonction. Regardez comme c'est simple à exécuter. Et nous pouvons compiler la sortie pour l'explorer avec notre décompilateur préféré.

image

root@kitploit:~
; ModuleID = 'my_lifting_module'
source_filename = "my_lifting_module"

; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %0, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readonly %memory) local_unnamed_addr #0 {
entry:
  %stackmemory = alloca i128, i128 13758960, align 8
  %1 = trunc i64 %r8 to i32
  %2 = trunc i64 %rdx to i32
  %GEPLoadxd-5369456437- = getelementptr i8, ptr %memory, i64 %rcx
  %3 = load i32, ptr %GEPLoadxd-5369456437-, align 4
  %adc-temp-5370242400- = sub i32 %2, %1
  %realnot-5369532059- = add i32 %adc-temp-5370242400-, %3
  %stackmemory10243.sroa.55.1375304.insert.ext10255 = zext i32 %realnot-5369532059- to i64
  ret i64 %stackmemory10243.sroa.55.1375304.insert.ext10255
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }

Après compilation :

image

image

Vous remarquerez peut-être que les registres sont un peu décalés. Cela vient du fait que nous ne suivons pas les conventions d'appel ; si nous les suivions, la signature de la fonction ressemblerait à ceci :

root@kitploit:~
define i64 @main(i64 %rcx, i64 %rdx, i64 %rdx, i64 %r8, i64 %r9 ...)

Donc, nous ajustons simplement la signature de la fonction pour qu'elle paraisse normale. Si vous avez d'autres questions sur cette partie, je vous suggère de vous renseigner sur les conventions d'appel et l'ABI.

Exemple n°2 (Branches/Tableaux de sauts)

Donc, disons que nous avons ce code. Les machines virtuelles prendront le code ci-dessous et le transformeront en un saut indirect, ce qui est légèrement moins pratique pour le rétro-ingénieur.

root@kitploit:~
int maths(int a, int b, int c) {
    if (a > b)
        return a + b + c;
    else
        return a - b - c;
}
root@kitploit:~
next_handler = xxx;
if ( a-b > 0 )
  next_handler = yyy;
jump next_handler;

Nous essayons toujours d'analyser les valeurs et de les suivre. Cela nous permet de comprendre le flux de contrôle. Pour les branches de type table de sauts La sortie optimisée serait un simple

root@kitploit:~
define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readnone %TEB, ptr nocapture readnone %memory) local_unnamed_addr #0 {
fake_ret:
  %0 = lshr i64 %rcx, 62
  %common.ret.op = and i64 %0, 2
  ret i64 %common.ret.op
}

Sortie non optimisée. (DCE pour lisibilité)

root@kitploit:~
source_filename = "my_lifting_module"

define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr %TEB, ptr %memory) {
  %lsb = and i64 %rcx, 255
  %pf1 = mul i64 %lsb, 72340172838076673
  %pf2 = and i64 %pf1, -9205322385119247871
  %pf3 = urem i64 %pf2, 511
  %pf4 = and i64 %pf3, 1
  %pf5 = icmp eq i64 0, %pf4
  %0 = zext i1 %pf5 to i64
  %createrflag2 = shl i64 %0, 2
  %creatingrflag = or i64 2, %createrflag2
  %zeroflag = icmp eq i64 %rcx, 0
  %1 = zext i1 %zeroflag to i64
  %createrflag21 = shl i64 %1, 6
  %creatingrflag2 = or i64 %creatingrflag, %createrflag21
  %signflag = icmp slt i64 %rcx, 0
  %2 = zext i1 %signflag to i64
  %createrflag23 = shl i64 %2, 7
  %creatingrflag4 = or i64 %creatingrflag2, %createrflag23
  %GEPSTORE-5368713221- = getelementptr i8, ptr %memory, i64 1376032
  store i64 %creatingrflag4, ptr %GEPSTORE-5368713221-, align 4
  %realand-5368713229- = and i64 %creatingrflag4, 128
  %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7
  %3 = mul i64 %shr-lshr-5368713233-, 4
  %bvalue_indexvalue = add i64 5368713249, %3
  %4 = icmp eq i64 %bvalue_indexvalue, 5368713253
  %lolb- = select i1 %4, i64 5368713264, i64 5368713257
  %GEPSTORE-5368713248- = getelementptr i8, ptr %memory, i64 1376032
  store i64 %lolb-, ptr %GEPSTORE-5368713248-, align 4
  br i1 %4, label %real_ret, label %real_ret41

real_ret:                                         ; preds = %fake_ret
  %inc-5368713273- = add i64 %shr-lshr-5368713233-, 1
  ret i64 %inc-5368713273-

real_ret41:                                       ; preds = %fake_ret
  ret i64 %shr-lshr-5368713233-
}

Remarquez cette partie

root@kitploit:~
  %realand-5368713229- = and i64 %creatingrflag4, 128
  %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7

Nous obtenons les indicateurs, puis nous obtenons le 7e bit qui est le Sign Flag, puis nous utilisons le Sign Flag pour calculer une adresse. Grâce à l'analyse, nous déterminons que l'adresse peut être l'une des deux valeurs, 5368713257 ou 5368713264, puis nous transformons cela en une comparaison. Si l'adresse est 5368713257, prendre une branche, sinon l'autre. Ce faisant, il est également important de marquer la condition avec la valeur appropriée car plus tard, nous pourrions avoir besoin de calculer un autre saut avec exactement la même valeur.

Bien que nous résolvions les sauts indirects, les sauts avec plus de 2 emplacements possibles ne sont pas pris en charge. Cela est dû au fait que l'analyse correspondante n'est pas encore implémentée. Cela nous permet de résoudre les branches de style machine virtuelle, mais pose problème avec les tables de sauts réelles.

Exemple n°3 (Themida 3.1.6.0 LION64 (Red))

Notre programme cible :

image

Paramètres Themida (nous ne nous intéressons qu'aux machines virtuelles pour l'instant) :

image

image

image

Après virtualisation :

image

Exécution de Mergen :

image

Code de sortie : cliquez ici Alors, pourquoi notre résultat n'est-il pas aussi réussi que le levage d'un binaire protégé par VMP ?

Themida écrit activement sur la section .themida. Contrairement à la pile, nous ne pouvons pas ignorer ces écritures, car ces valeurs pourraient être lues par d'autres éléments plus tard.

Mais nous avons une solution temporaire à cela. Supprimer tous les stockages dans la section .themida. Comme notre programme n'écrit pas en mémoire, j'ai simplement commenté tous les stockages. Il nous reste donc ceci :

root@kitploit:~
source_filename = "my_lifting_module"

define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr writeonly %memory) local_unnamed_addr #0 {
  %trunc = trunc i64 %r8 to i32
  %trunc1 = trunc i64 %rdx to i32
  %trunc2 = trunc i64 %rcx to i32
  %realadd-5369771371- = add i32 %trunc1, %trunc2
  %realadd-5369582686- = add i32 %realadd-5369771371-, %trunc
  %trunc457139 = zext i32 %realadd-5369582686- to i64
  ret i64 %trunc457139
}

attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) }

Défis techniques

  • Boucles
  • Code auto-modifiant (en particulier avec modification conditionnelle)
  • Être dans un univers où les passes d'extraction (outlining) et de déroulage (unrolling) n'existent pas.

Prendre contact

Rejoignez notre Serveur Discord Mergen pour échanger des idées ou simplement discuter en général.

Télécharger l’outil