
Giocando con la protezione software VMProtect. Deoffuscazione automatica di funzioni pure utilizzando esecuzione simbolica e LLVM.
Un approccio dinamico sperimentale per devirtualizzare funzioni pure protette da VMProtect 3.x
Condivido alcune note su un approccio dinamico per devirtualizzare funzioni pure protette da VMProtect. Questo approccio ha mostrato risultati molto buoni se la funzione virtualizzata contiene un solo blocco di base (indipendentemente dalla sua dimensione). Si tratta di uno scenario comune quando i binari proteggono operazioni aritmetiche. Tuttavia, questo approccio è un po' più sperimentale quando la funzione target contiene più di un blocco di base. Cionondimeno, siamo riusciti a devirtualizzare e ricostruire il codice binario da campioni che contengono 2 blocchi di base, il che suggerisce che è possibile devirtualizzare completamente piccole funzioni in modo dinamico.
VMProtect è una protezione software che protegge il codice eseguendolo attraverso una macchina virtuale con architettura non standard. Questa protezione è un ottimo campo di gioco per gli amanti dell'assembly [0, 1, 2, 3, 4, 5, 6, 11]. Inoltre, esistono già numerosi strumenti che attaccano questa protezione [7, 8, 9, 12, 13]. Nel 2016 abbiamo dato un'occhiata alla soluzione di protezione software Tigress e siamo riusciti a sconfiggere la sua virtualizzazione utilizzando l'esecuzione simbolica e LLVM. Questo approccio è stato presentato a DIMVA 2018 [10] e volevo testarlo su VMProtect. Nota che non esiste una soluzione magica che funzioni su tutti i binari, ci sono sempre compromessi a seconda del target e dei tuoi obiettivi. Questo modesto contributo mira a fornire un esempio di attacco dinamico contro funzioni pure che sono virtualizzate da VMProtect. Il principale vantaggio di un attacco dinamico è che sconfigge per progettazione alcune protezioni statiche di VMProtect come il codice auto-modificante, la crittografia delle chiavi e degli operandi, ecc.
Consideriamo una funzione pura una funzione con un numero finito di percorsi e che non ha effetti collaterali. Possono esserci diversi input ma un solo output. Di seguito è riportato un esempio di funzione pura:```cpp int secret(int x, int y) { int r = x ^ y; return r; }
# L'approccio
Ci basiamo sull'intuizione chiave che una traccia offuscata T' (dal codice offuscato P') combina istruzioni originali del codice originale P (la traccia T corrispondente a T' nel codice originale) e istruzioni della macchina virtuale VM tali che T' = T + VM(T). Se siamo in grado di distinguere tra queste due sottosequenze di istruzioni T e VM(T), possiamo quindi ricostruire un percorso del programma originale P a partire da una traccia T'. Ripetendo questa operazione per coprire tutti i percorsi del programma virtualizzato, saremo in grado di ricostruire il programma originale P. Nel nostro esempio pratico, il codice originale ha un numero finito di percorsi eseguibili, cosa che accade in molte situazioni che coinvolgono la protezione della proprietà intellettuale. Per fare ciò, procediamo con i seguenti passaggi:
1. Identificare la funzione virtualizzata e i suoi argomenti
2. Generare una traccia VMProtect del target
3. Rieseguire la traccia VMP e costruire espressioni simboliche per ottenere la relazione tra input e output
4. Applicare ottimizzazioni sulle espressioni simboliche per evitare il più possibile le istruzioni della VM
5. Elevare la nostra rappresentazione simbolica a LLVM-IR per costruire una nuova versione non protetta del target
## Esempio 1: Una semplice operazione bit a bit
Prendiamo come primo esempio la seguente funzione: prende due input e restituisce `x ^ y` che è protetta da VMProtect.
```c
int xor(int x, int y) {
return x ^ y;
}
``````cpp
int secret(int x, int y) {
VMProtectBegin("secret");
int r = x ^ y;
VMProtectEnd();
return r;
}
Iniziamo identificando dove le funzioni utilizzano VMProtect e quanti argomenti hanno. Per il nostro esempio, potremmo avere qualcosa come il seguente:
Solo dalla lettura del codice sappiamo che la funzione inizia all'indirizzo 0x4011c0, ha due argomenti a 32 bit (edi e esi) e termina a 0x4011ef. Questo è tutto il reverse-engineering di cui abbiamo bisogno. Le parti successive saranno automatiche. Ora dobbiamo generare una traccia di esecuzione di questa funzione virtualizzata. Per farlo, utilizziamo un Pintool. Necessita solo di un indirizzo start e uno end (per il nostro esempio, 0x4011c0 e 0x4011ef) che rappresentano l'intervallo di strumentazione. Si noti che qualsiasi tipo di DBI o emulatore potrebbe svolgere questo compito.```
$ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198895 -- ./vmp_binaries/binaries/sample2.vmp.bin 1 2 &> ./vmp_traces/sample2.vmp.trace
Puoi vedere il risultato [qui](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample2.vmp.trace). Il formato delle tracce utilizza tre tipi di operazioni: `mr`, `r` e `i`.
`mr` è un accesso in lettura alla memoria eseguito dall'istruzione `i`, e `r` sono i registri della CPU. Per esempio:```
mr:0x7ffda459d718:8:0x227db4f8
r:0x40200a:0x0:0x7ffda459f571:0x2:0x40200a:0x0:0x0:0x7ffda459d688:0x0:0x0:0x7feee9b80ac0:0x7feee9b8000f:0xad1c3e:0x0:0x0:0x0
i:0x89173e:8:488BB42490000000
Abbiamo una lettura di memoria che carica una costante di 8 byte 0x227db4f8 dall'indirizzo 0x7ffda459d718.
L'istruzione viene eseguita all'indirizzo 0x89173e e il suo opcode lungo 8 byte è 488BB42490000000 che è un
mov rsi, qword ptr [rsp + 0x90].
Lo stato dei registri prima dell'esecuzione è il seguente:
Una volta generata la traccia VMP, la riproduciamo utilizzando lo script [attack_vmp.py](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/attack_vmp.py). Questo script usa
[Triton](https://github.com/jonathansalwan/Triton) per costruire il predicato di percorso della traccia. Nota che tutte le espressioni che
coinvolgono variabili simboliche (input della funzione) vengono mantenute simboliche, mentre tutte le espressioni non correlate agli input vengono concretizzate. In
altre parole, le nostre espressioni simboliche non contengono alcuna operazione relativa alla macchina virtuale (il meccanismo
stesso non dipende dall'utente) ma solo operazioni relative al programma originale.
Ad esempio, di seguito è riportato un esempio di concretizzazione. A sinistra abbiamo un AST che contiene sottoespressioni che non coinvolgono
variabili simboliche (`1 + 2` e `6 ^ 3`). Quindi questi rami vengono concretizzati e sostituiti con costanti `3` e `5`, portando
all'AST a destra. **È così che devirtualizziamo il codice.**
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/8096/857ed50cfe9cb2347f816ece1d8dc4c13174c971dcb65ebc5621be14269a5ca8.png">
</p>
**Nota sul backward slicing a livello di formula**: Come è comune nell'esecuzione simbolica, la rappresentazione simbolica viene prima
calcolata in avanti lungo il percorso, quindi tutte le operazioni logiche e le definizioni che non influenzano né il risultato
finale né il percorso seguito vengono rimosse dall'espressione simbolica (slicing della formula, noto anche come pruning della formula). Questo si rivela
equivalente, sulla formula, a un'analisi del codice di backward slicing a partire dall'output del programma. Quindi, al ritorno della
funzione `secret`, abbiamo un'espressione della relazione tra gli input e l'output senza le istruzioni di VMProtect.
Lo script `./attack_vmp.py` accetta come parametri il file di traccia e la dimensione delle variabili simboliche. Ricorda, erano `edi` e
`esi`, quindi sono lunghi 4 byte. Il risultato dello script è il seguente:```
$ ./attack_vmp.py --trace1 ./vmp_traces/sample2.vmp.trace --symsize 4
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] Instruction executed: 12462
[+] Emulation done
[+] Return value: 0x3
[+] Devirt expr: (bvor (bvnot (bvor (bvnot (bvnot x)) (bvnot y))) (bvnot (bvor (bvnot x) (bvnot (bvand (bvnot y) (bvnot y))))))
[+] Synth expr: (bvxor x y)
[+] LLVM IR ==============================
; ModuleID = 'tritonModule'
source_filename = "tritonModule"
define i32 @__triton(i32 %SymVar_0, i32 %SymVar_1) {
entry:
%0 = xor i32 %SymVar_0, %SymVar_1
ret i32 %0
}
[+] EOF LLVM IR ==============================
Come possiamo vedere, l'espressione devirtualizzata restituita dalla funzione secret è abbastanza concisa e non contiene istruzioni dalla macchina virtuale.```smt
(bvor
(bvnot (bvor
(bvnot (bvnot x))
(bvnot y)
)
)
(bvnot (bvor
(bvnot x)
(bvnot (bvand
(bvnot y)
(bvnot y)
)
)
)
)
)
Tuttavia, non siamo riusciti a recuperare l'espressione originale che era una semplice operazione `XOR`. Sembra che lo `XOR` sia stato
tradotto in operazioni bitwise. Fortunatamente, abbiamo recentemente rilasciato nuove funzionalità nel progetto Triton, ovvero
un [synthesizer](https://github.com/JonathanSalwan/Triton/issues/1074) e un lifter verso
[LLVM-IR](https://github.com/JonathanSalwan/Triton/issues/1078). Possiamo quindi sintetizzare l'espressione, ottenendo l'espressione
`(bvxor x y)`. È un buon successo e ora possiamo andare oltre, sollevando questa espressione in LLVM-IR e poi compilare un nuovo codice binario
devirtualizzato.
## Esempio 2: Un'operazione MBA protetta
Ok, ora diamo un'occhiata a un altro esempio che tenta di nascondere un'operazione MBA. Il codice sorgente originale è il seguente:```cpp
// This function is an MBA that computes: (x ^ 92) + y
// We will protect this MBA with VMProtect and see if we can recover "(x ^ 92) + y"
char secret(char x, char y) {
VMProtectBegin("secret");
int a = 229 * x + 247;
int b = 237 * a + 214 + ((38 * a + 85) & 254);
int c = (b + ((-(2 * b) + 255) & 254)) * 3 + 77;
int d = ((86 * c + 36) & 70) * 75 + 231 * c + 118;
int e = ((58 * d + 175) & 244) + 99 * d + 46;
int f = (e & 148);
int g = (f - (e & 255) + f) * 103 + 13;
int r = (237 * (45 * g + (174 * g | 34) * 229 + 194 - 247) & 255) + y;
VMProtectEnd();
return r;
}
Come con il primo esempio, dobbiamo identificare dove questa funzione inizia e finisce e generare una traccia VMP.``` $ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198857 -end 4199140 -- ./vmp_binaries/binaries/sample3.vmp.bin 1 2 &> ./vmp_traces/sample3.vmp.trace
Una volta generata la [traccia VMP](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample3.vmp.trace), esegui lo script `./attack_vmp.py`.```
$ ./attack_vmp.py --trace1 ./vmp_traces/sample3.vmp.trace --symsize 1
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] A potential symbolic jump found on CF flag: 0x821dac: popfq - Model: {0: x:32 = 0xa3, 1: y:32 = 0xff}
[+] A potential symbolic jump found on CF flag: 0x87f437: popfq - Model: {0: x:32 = 0xa3, 1: y:32 = 0xff}
[+] Instruction executed: 25085
[+] Emulation done
[+] Return value: 0x5f
[+] Devirt expr: In: (bvadd (bvadd (bvshl (bvadd (_ bv1 32) (bvnot (bvlshr (concat (_ bv0 8) (_ bv0 8) ((_ extract 15 8) ...
[+] Synth expr: In: (bvadd (bvadd (bvshl (bvadd (_ bv1 32) (bvnot (bvlshr (concat (_ bv0 8) (_ bv0 8) ((_ extract 15 8) ...
[+] LLVM IR ==============================
; ModuleID = 'tritonModule'
source_filename = "tritonModule"
define i32 @__triton(i8 %SymVar_0, i8 %SymVar_1) {
entry:
%0 = xor i8 %SymVar_0, 92
%1 = and i8 %SymVar_0, 0
%2 = zext i8 %1 to i32
%3 = or i32 0, %2
%4 = shl i32 %3, 8
%5 = zext i8 %0 to i32
%6 = or i32 %4, %5
%7 = and i8 %SymVar_1, 0
%8 = zext i8 %7 to i32
%9 = or i32 0, %8
%10 = shl i32 %9, 8
%11 = zext i8 %SymVar_1 to i32
%12 = or i32 %10, %11
%13 = zext i8 %7 to i32
%14 = or i32 0, %13
%15 = shl i32 %14, 8
%16 = zext i8 %SymVar_1 to i32
%17 = or i32 %15, %16
%18 = lshr i32 %17, 7
%19 = xor i32 %18, -1
%20 = add i32 1, %19
%21 = shl i32 %20, 8
%22 = add i32 %21, %12
%23 = add i32 %22, %6
ret i32 %23
}
[+] EOF LLVM IR ==============================
Il risultato è piuttosto interessante per diversi motivi. Innanzitutto, siamo riusciti con successo a evitare il più possibile istruzioni dalla macchina virtuale, passando da 25085 istruzioni eseguite a 25 istruzioni LLVM. Tuttavia, non siamo riusciti a ottenere una buona versione sintetizzata dell'output (sì, lo so, stiamo andando oltre la semplice devirtualizzazione). Il vantaggio di sollevare le nostre espressioni simboliche in LLVM-IR è che possiamo beneficiare appieno della pipeline di ottimizzazione di LLVM. Facciamo questo:```llvm $ opt -S -O3 ./devirt/sample3.ll ; ModuleID = 'devirt/sample3.ll' source_filename = "tritonModule"
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn define i32 @__triton(i8 %SymVar_0, i8 %SymVar_1) local_unnamed_addr #0 { entry: %0 = xor i8 %SymVar_0, 92 %1 = zext i8 %0 to i32 %2 = zext i8 %SymVar_1 to i32 %3 = shl nuw nsw i32 %2, 1 %4 = and i32 %3, 256 %5 = add nuw nsw i32 %1, %2 %6 = sub nsw i32 %5, %4 ret i32 %6 }
Utilizzando le ottimizzazioni LLVM siamo riusciti a rimuovere il rumore dall'output devirtualizzato e quindi a rompere l'MBA.
Possiamo vedere l'operazione `XOR` con la sua costante (`%0 = xor i8 %SymVar_0, 92`) e `+ y` (`%6 = add nsw i32 %5, %1`).
Le istruzioni intermedie gestiscono solo il segno. Per riassumere questo esempio, abbiamo completamente devirtualizzato la funzione
`secret` usando lo script `attack_vmp.py` e poi abbiamo completamente rotto l'MBA usando le ottimizzazioni LLVM.
## Esempio 3: Più di un blocco di base
Abbiamo ottenuto risultati molto buoni se la funzione `secret` contiene solo un blocco di base, indipendentemente dalle sue dimensioni. Quindi a questo punto
siamo in grado di devirtualizzare un percorso. Per ricostruire il comportamento dell'intera funzione, dobbiamo devirtualizzare successivamente
i percorsi raggiungibili. Per farlo, dobbiamo eseguire una copertura dei percorsi sui rami dipendenti dall'utente. Alla fine, otteniamo come risultato
un albero dei percorsi che rappresenta i diversi percorsi della funzione originale. L'albero dei percorsi si ottiene introducendo
costruzioni if-then-else da due tracce T1 e T2 con lo stesso prefisso seguite da una condizione C in T1 e un not(C)
in T2. Una volta costruito un albero dei percorsi, possiamo lasciare che LLVM generi un CFG.
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/8096/fc5d1bf9a1544b9b44ef164247f3fc38d04bf45d7c2d7a3c0b21ea3d6636cd39.png">
</p>
Con la protezione software Tigress, i salti virtuali erano implementati con vere istruzioni `jcc` che ci hanno permesso
di identificare rapidamente la condizione di salto. Tuttavia, le cose diventano più complesse quando i salti virtuali sono coinvolti
con VMProtect, poiché non utilizza istruzioni `jcc` per saltare a un altro blocco virtuale. Abbiamo dovuto definire
dei marcatori su una traccia dinamica per individuare la condizione coinvolta in un ramo dipendente dall'utente. Questa è la parte sperimentale
di questo attacco, poiché i marcatori non sono molto precisi, ma hanno funzionato per i nostri campioni.
Ok, consideriamo il seguente esempio:```cpp
int secret(int x, int y) {
VMProtectBegin("secret");
int r = 0;
if (x + y == 1001)
r = x + 1;
else
r = y - 1;
VMProtectEnd();
return r;
}
Come con i primi esempi, dobbiamo generare e analizzare la traccia.``` $./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198928 -- ./vmp_binaries/binaries/sample5.vmp.bin 1 2 &> ./vmp_traces/sample5.vmp.trace.1
$ ./attack_vmp.py --trace1 ./vmp_traces/sample5.vmp.trace.1 --symsize 4 [+] Replaying the VMP trace [+] Symbolize inputs [+] A potential symbolic jump found of AF flag: 0x80d905: cmp r11b, dl - Model: {0: x:32 = 0x0, 1: y:32 = 0x3e9} [+] Instruction executed: 16164 [+] Emulation done [+] Return value: 0x4 [+] Devirt expr: (bvnot (bvadd (bvand (bvnot y) (bvnot y)) (_ bv1 32))) [+] Synth expr: (bvadd y (_ bv4294967295 32))
[+] LLVM IR ==============================
; ModuleID = 'tritonModule' source_filename = "tritonModule"
define i32 @__triton(i32 %SymVar_1) { entry: %0 = add i32 %SymVar_1, -1 ret i32 %0 }
[+] EOF LLVM IR ==============================
Lo script ci informa che potrebbe esserci un potenziale salto simbolico trovato sul flag `AF` all'indirizzo `0x80d905`. Fornisce inoltre un nuovo modello (usando l'esecuzione simbolica) che dovrebbe prendere l'altro percorso. Quindi generiamo una seconda traccia usando questo modello (se diamo un'occhiata al modello, è corretto rispetto al nostro codice sorgente).```
$ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198928 -- ./vmp_binaries/binaries/sample5.vmp.bin 0 1001 &> ./vmp_traces/sample5.vmp.trace.2
Una volta generata la seconda traccia, dobbiamo fornire queste due tracce allo script attack_vmp.py in modo che possa unirle e creare un albero dei percorsi. Abbiamo opzioni aggiuntive per definire dove si trova la condizione e su quale flag (flag AF a 0x80d905).```
$ ./attack_vmp.py --trace1 ./vmp_traces/sample5.vmp.trace.1 --symsize 4 --trace2 ././vmp_traces/sample5.vmp.trace.2 --vbraddr 0x80d905 --vbrflag af
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] A potential symbolic jump found of AF flag: 0x80d905: cmp r11b, dl - Model: {0: x:32 = 0x0, 1: y:32 = 0x3e9}
[+] Instruction executed: 16164
[+] Emulation done
[+] A second trace has been provided
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] Instruction executed: 15758
[+] Emulation done
[+] Merging expressions from trace1 and trace2
[+] Return value: 0x3e9
[+] Devirt expr: In: (ite (= (ite (= (_ bv16 8) (bvand (_ bv16 8) (bvxor (bvsub (_ bv80 8) ((_ extract 7 0) (bvadd (bvlsh ...
[+] Synth expr: In: (ite (= (ite (= (_ bv16 8) (bvand (_ bv16 8) (bvxor (bvsub (_ bv80 8) ((_ extract 7 0) (bvadd (bvlsh ...
[+] LLVM IR ==============================
; ModuleID = 'tritonModule' source_filename = "tritonModule"
define i32 @__triton(i32 %SymVar_0, i32 %SymVar_1) { entry: %0 = add i32 %SymVar_1, -1 %1 = add i32 %SymVar_0, 1 %2 = add i32 %SymVar_1, %SymVar_0 %3 = xor i32 %2, -1 %4 = xor i32 %2, -1 %5 = and i32 %4, %3 %6 = xor i32 %5, 1001 %7 = add i32 %5, 1001 %8 = xor i32 %5, 1001 %9 = xor i32 %8, %7 %10 = and i32 %9, %6 [... skip ...] %469 = add i64 %468, 140737488347280 %470 = trunc i64 %469 to i8 %471 = xor i8 80, %470 %472 = sub i8 80, %470 %473 = xor i8 %472, %471 %474 = and i8 16, %473 %475 = icmp eq i8 16, %474 %476 = select i1 %475, i1 true, i1 false %477 = icmp eq i1 %476, false %478 = select i1 %477, i32 %1, i32 %0 ret i32 %478 }
[+] EOF LLVM IR ==============================
A questo punto abbiamo devirtualizzato le due tracce e le abbiamo unite in espressioni `if-then-else`.
Dopo aver sollevato l'espressione in LLVM-IR otteniamo un CFG con solo 480 istruzioni LLVM, il che è
già un buon guadagno rispetto alle migliaia di istruzioni eseguite dalla macchina virtuale.
Ma possiamo fare meglio se utilizziamo le ottimizzazioni di LLVM:```llvm
$ opt -S -O3 ./devirt/sample5.ll
; ModuleID = './devirt/sample5.ll'
source_filename = "tritonModule"
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn
define i32 @__triton(i32 %SymVar_0, i32 %SymVar_1) local_unnamed_addr #0 {
entry:
%0 = add i32 %SymVar_0, 1
%1 = add i32 %SymVar_1, -1
%2 = add i32 %SymVar_1, %SymVar_0
%.not = icmp eq i32 %2, 1001
%3 = select i1 %.not, i32 %0, i32 %1
ret i32 %3
}
attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn }
Woot, abbiamo recuperato il comportamento originale della funzione secret!
Sebbene l'approccio abbia mostrato ottimi risultati per funzioni che contengono un solo percorso, la principale limitazione del metodo è che è principalmente orientato a programmi con un numero ridotto di percorsi a causa del modo in cui VMProtect esegue i salti virtuali. In caso di un numero troppo elevato di percorsi, parti del codice originale potrebbero andare perse, portando a un recupero incompleto. Nota che stiamo considerando i percorsi eseguibili piuttosto che i percorsi sintattici nel CFG. Le funzioni hash e altre funzioni crittografiche hanno spesso pochissimi percorsi - solo un percorso nel caso di implementazioni resistenti agli attacchi temporali.
Inoltre, la nostra implementazione attuale è limitata a programmi senza alcun accesso alla memoria dipendente dall'utente. Questa limitazione può essere parzialmente rimossa utilizzando una gestione più simbolica degli accessi alla memoria nel DSE.
Nota anche che mentre i loop limitati e le chiamate a funzioni non ricorsive sono gestiti, attualmente vengono recuperati come codice inline o srotolato, causando un potenziale aumento delle dimensioni del codice devirtualizzato. Sarebbe interessante avere un passaggio di post-elaborazione che tenti di ricostruire queste astrazioni di alto livello.
Per concludere, nota che non intendo fornire alcun tipo di metodo magico, queste sono solo alcune note su un attacco dinamico contro casi molto specifici protetti da VMProtect =).
Se vuoi dare un'occhiata più approfondita, dai un'occhiata a queste risorse:
Last but not least, un ringraziamento speciale al mio amico @0vercl0k per la correzione di bozze e le modifiche 🚀
[00] https://www.usenix.org/legacy/event/woot09/tech/full_papers/rolles.pdf [01] https://secret.club/2021/09/08/vmprotect-llvm-lifting-1.html [02] https://secret.club/2021/09/08/vmprotect-llvm-lifting-2.html [03] https://secret.club/2021/09/08/vmprotect-llvm-lifting-3.html [04] https://back.engineering/17/05/2021/ [05] https://back.engineering/21/06/2021/ [06] https://www.mitchellzakocs.com/blog/vmprotect3 [07] https://github.com/can1357/NoVmp [08] https://github.com/archercreat/vmpfix [09] https://github.com/void-stack/VMUnprotect [10] https://github.com/JonathanSalwan/Triton/blob/master/publications/DIMVA2018-slide-deobfuscation-salwan-bardin-potet.pdf [11] https://whereisr0da.github.io/blog/posts/2021-02-16-vmp-3/ [12] https://github.com/pgarba/UniTaint [13] https://github.com/mrexodia/VMProtectTest
| Registro | Valore | Commento |
|---|
rax | 0x8911b3 | |
rbx | 0 | |
rcx | 0x7f7b399ffac0 | |
rdx | 0x2b | |
rsi | 0x7f7b399ffac4 | |
rdi | 0x8911b3 | |
rbp | 0x7ffda459e6c0 | |
rsp | 0x7ffda459d690 | |
r8 | 0x7f7b399ffac0 | |
r9 | 0x7ffda459d720 | |
r10 | 0x8de | |
r11 | 0x206 | |
r12 | 0x55bb5b5a8380 | |
r13 | 0x7ffda459eb50 | |
r14 | 0 | |
r15 | 0 | |
| (1) RAX = 0x40200a (9) R8 = 0 | ||
| (2) RBX = 0 (10) R9 = 0 | ||
| (3) RCX = 0x7ffda459f571 (11) R10 = 0x7feee9b80ac0 | ||
| (4) RDX = 0x2 (12) R11 = 0x7feee9b8000f | ||
| (5) RDI = 0x40200a (13) R12 = 0xad1c3e | ||
| (6) RSI = 0 (14) R13 = 0 | ||
| (7) RBP = 0 (15) R14 = 0 | ||
| (8) RSP = 0x7ffda459d688 (16) R15 = 0 |