Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Mergen — Deofuscazione tramite ottimizzazione con utilizzo di LLVM IR e parsing dell'assembly. | Kitploit
Strumenti/GitHubGitHub/nac-l/mergen
Analisi StaticaAnalisi Dinamica (Sandboxing)Reverse EngineeringAnalisi di BinariBinary Exploitation
GitHubnac-l/mergen

Mergen

Deofuscazione tramite ottimizzazione con utilizzo di LLVM IR e parsing dell'assembly.

Vedi Repository
878943 mesi faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Panoramica del progetto:

Mergen è uno strumento progettato per convertire codice Assembly in Intermediate Representation (IR) di LLVM. Questo strumento è pensato per:

  • La deoffuscazione o devirtualizzazione di codice binario offuscato
  • Il miglioramento del processo di reverse engineering, rendendolo più efficiente ed efficace, soprattutto per sistemi software complessi.

Guida alla compilazione ed esecuzione

Per compilare ed eseguire il progetto, dai un'occhiata a docs/BUILDING.md.

Gate baseline per la riscrittura

Il lavoro di riscrittura deve mantenere verde il gate di regressione baseline. Il gate compila campioni PE mirati, esegue lifter e verifica gli output IR generati dal lifting.

  • Documentazione del flusso di lavoro: docs/REWRITE_BASELINE.md
  • Gate con un solo comando: scripts\\rewrite\\run.cmd

Obiettivi principali:

  • Deoffuscazione

  • Devirtualizzazione

  • Ottimizzazione

Come funziona?

Eseguiamo simbolicamente (o solleviamo simbolicamente) il target: l'idea qui non è il lifting delle singole istruzioni, ma il lifting di un'intera funzione. Non ci aspettiamo che una singola istruzione o un singolo blocco base si comportino allo stesso modo ogni volta; li trattiamo invece come se potessero essere, e di fatto sono, usati per scopi diversi ogni volta. Cerchiamo di mantenere l'IR generato il più semplice e ottimizzabile possibile. Abbiamo anche esigenze diverse rispetto a un compilatore tradizionale. Usiamo l'analisi per valutare il flusso di controllo. Non possiamo dipendere da LLVM per tutta la nostra analisi, perché è creato per obiettivi diversi e potrebbe non essere ottimale per il nostro caso d'uso.

image

Esempi

Questo è l'esempio pratico che illustra come Mergen affronta i programmi virtualizzati.

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

Esempio #1 (VMProtect)

Questo è il nostro programma target

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

Impostazioni di VMProtect: tutto è disattivato, virtualizziamo la funzione con l'impostazione ultra. (Versioni testate: 3.4.0-3.6.0, 3.8.1)

image

image

Qui eseguiamo Mergen. Il primo argomento è il nome del file e il secondo è l'indirizzo della funzione. Guarda com'è semplice eseguirlo. Possiamo inoltre compilare l'output per poterlo esplorare con il nostro decompilatore preferito.

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) }

Dopo la compilazione:

image

image

Ora potresti notare che i registri sono leggermente diversi. Questo perché non seguiamo le convenzioni di chiamata; se le seguissimo, la firma della funzione sarebbe così:

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

Quindi, ci limitiamo ad adattare la firma della funzione affinché appaia normale. Se hai altre domande su questa parte, ti suggerisco di approfondire le convenzioni di chiamata e l'ABI.

Esempio #2 (Branches/Jumptables)

Quindi, supponiamo di avere questo codice. Le VM prenderanno il codice sottostante e lo trasformeranno in un salto indiretto, il che è leggermente più scomodo per chi fa reverse engineering.

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;

Cerchiamo di analizzare sempre i valori e di tenerne traccia. Questo ci permette di comprendere il flusso di controllo. Per i branch simili a jump table L'output ottimizzato sarebbe un semplice

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
}

Output non ottimizzato. (DCE'd per leggibilità)

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-
}

Nota questa parte

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

Otteniamo i flag, poi estraiamo il 7° bit, che è il Sign Flag (flag di segno), quindi usiamo il Sign Flag per calcolare un indirizzo. Tramite l'analisi, determiniamo che l'indirizzo può essere uno di due valori, 5368713257 o 5368713264, e lo trasformiamo in un confronto. Se l'indirizzo è 5368713257, si prende un ramo; se è l'altro, si prende l'altro. Quando si fa questo, è importante anche contrassegnare la condizione con un valore appropriato, perché più tardi potremmo dover calcolare un altro salto con lo stesso identico valore.

Anche se risolviamo i salti indiretti, i salti con più di 2 possibili destinazioni non sono supportati. Questo perché l'analisi per essi non è ancora implementata. Questo ci permette di risolvere i branch in stile VM, ma abbiamo problemi con le jump table reali.

Esempio #3 (Themida 3.1.6.0 LION64 (Red))

Il nostro programma target:

image

Impostazioni di Themida (per ora ci interessano solo le VM):

image

image

image

Dopo la VM:

image

Eseguendo Mergen:

image

Codice di output: clicca qui Quindi, perché il nostro risultato non ha lo stesso successo del lifting di un binario protetto da VMP?

Themida scrive attivamente nella sezione .themida. A differenza dello stack, non possiamo ignorare queste scritture, perché quei valori potrebbero essere letti da altro in seguito.

Ma abbiamo una soluzione temporanea. Rimuovere tutte le store nella sezione .themida. Dato che il nostro programma non scrive in memoria, ho semplicemente commentato tutte le store. Ora rimane questo:

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) }

Sfide tecniche

  • Loop
  • Codice auto-modificante ( specialmente con modifiche condizionali )
  • Trovarsi in un universo in cui le pass "outlining" e "unrolling" non esistono.

Contatti

Unisciti al nostro Server Discord di Mergen per scambiare idee o semplicemente chiacchierare.

Scarica lo strumento