Domanda Risolto Reversing Creare protezione VM

JunkCoder

Moderatore
5 Giugno 2020
1,646
25
1,450
603
Ciao, e' da un po' che mi interesso di reversing, sia per quanto riguarda programmi compilati che interpretati. Quindi ho deciso di cimentarmi in un impresa non da poco, ho gia' creato qualche packer nativo, che seppur semplice ha qualche trick interessante, ora voglio sviluppare una protezione di tipo VM simile a quelle di Themida o VMProtect, non voglio realizzare un software professionale che richiederebbe un team intero, mi basta realizzarlo solo per software a 32 bit e con istruzioni asm comuni.

Veniamo alla domanda, siete a conoscenza di qualche software del genere opensource, oppure avete qualche dritta su una libreria che mi aiuta a disasseblare? O magari qualche paper sul funzionamento interno dei protettori professionali, ad esempio su come risolve gli indirizzi e la lunghezza delle varie procedure e come tratta le stringhe ed oggetti provenienti da .rdata o altre sezioni?

Grazie in anticipo
 
In merito a VMProtect, il PDF allegato penso possa darti qualche info, e magari anche spunti interessanti.

Taggo in particolare @nullptr e @St3ve che secondo me possono darti poi un quadro più completo in merito a quanto chiedi.
 

Allegati

  • Samuel Chevet - SecurityDay2015 - Inside VMProtect.pdf
    511.7 KB · Visualizzazioni: 24
  • Mi piace
Reazioni: JunkCoder
Figurati. ^^

Non volevo entrare nel merito del caso di specie per non fuorviarti magari, ma aggiungo una cosa.

Io ho una lib per la decodifica delle istruzioni, l'ho scritta per integrarla in un progetto "giocattolo".
Non saprei se può esserti utile per com'è impostata: dato un file offset (ma con un VA si comporterebbe allo stesso modo) decodifica le istruzioni nelle varie parti che la compongono (prefissi, opcode, imm, disp, sib byte,...) e tiene traccia della lunghezza (così da decodificare l'istruzione successiva). Architetture target x86 e x86_64.
Questo solo per la decodifica delle istruzioni (nel senso che non ha una logica all'interno).

Dovrei apportare qualche cambiamento (pulizia al codice in particolare), ma quando la renderò pubblica (ahimè non in tempi brevissimi temo), te la linko (magari può servirti anche solo per darti idee/spunti).
 
  • Mi piace
Reazioni: JunkCoder
Ultima modifica:
Figurati. ^^

Non volevo entrare nel merito del caso di specie per non fuorviarti magari, ma aggiungo una cosa.

Io ho una lib per la decodifica delle istruzioni, l'ho scritta per integrarla in un progetto "giocattolo".
Non saprei se può esserti utile per com'è impostata: dato un file offset (ma con un VA si comporterebbe allo stesso modo) decodifica le istruzioni nelle varie parti che la compongono (prefissi, opcode, imm, disp, sib byte,...) e tiene traccia della lunghezza (così da decodificare l'istruzione successiva). Architetture target x86 e x86_64.
Questo solo per la decodifica delle istruzioni (nel senso che non ha una logica all'interno).

Dovrei apportare qualche cambiamento (pulizia al codice in particolare), ma quando la renderò pubblica (ahimè non in tempi brevissimi temo), te la linko (magari può servirti anche solo per darti idee/spunti).

Ok grazie, nel frattempo ho trovato una libreria per disasseblare, Zydis, che potrebbe fare al caso mio dato che pensavo di farlo in C++.
Non mi e' chiaro su VMProtect del perche' sia difficile ricostruire gli opcode se deve tenere traccia dell'handler che immagino sia un semplice puntatore a funzione del tipo int64 generic_handler(int64 operand1, int64 operand2); internamente non mantiene le stesse istruzioni tra un sample e l'altro?

Mi spiego meglio, in quel pdf fa proprio l'esempio dell'handler NOR
Codice:
NOT QWORD_OP_1
NOT QWORD_OP_2
AND QWORD_OP_1, QWORD_OP_2
MOV QWORD_OP_2, QWORD_OP_1
MOV QWORD_OP_1, RFLAGS

Anche essendo gli opcode della vm random, non e' possibile capire immediatamente che si riferisce a quell'handler? Oppure hanno una sorta di mutation come quella su cui hai fatto la guida del codice metamorfo?
 
Nel passato ho utilizzato spesso Themida, non conosco bene VMProtect.

Quindi ho deciso di cimentarmi in un impresa non da poco, ho gia' creato qualche packer nativo, che seppur semplice ha qualche trick interessante, ora voglio sviluppare una protezione di tipo VM simile a quelle di Themida o VMProtect, non voglio realizzare un software professionale che richiederebbe un team intero, mi basta realizzarlo solo per software a 32 bit e con istruzioni asm comuni.
Per progettare una vera virtualizzazione devi trasformare le istruzioni x86 in bytecode, il codice verrà eseguito più lentamente e spesso non è semplice da ottimizzare. Il lavoro deve essere affettato nei minimi particolari: vuoi meccanizzare una procedura di virtualizzazione presumibilmente per intricare il reversing di un programma, quindi applicherai certe espedienti come l'anti-debug (e altre, tra cui alcune simili) di Themida (non saprei di VMProtect). Te lo consiglio perchè da quello che ho capito non vuoi fare un lavoro tanto consistente come quello di questi packer, ma se ne viene fuori qualcosa di scadente e mi lasci analizzare così facilmente l'intero set di istruzioni che il packer utilizza non ci vuole tanto a bypassare la virtualizzazione, un buon reverser vince a mani basse. A livello pratico, tradizionalmente avendo un programma packato (da packer come questi) unpacki la virtualizzazione a occhio e croce così:
  1. Si esamina la VM per capirne le computazioni, si crea una rappresentazione intermedia che comprende le semantiche del linguaggio non virtualizzato e quello virtualizzato per poi tradurle nelle istruzioni del linguaggio intermedio.
  2. È utile notare con quali istruzioni il packer esce dalla frazione di bytecode del programma, salta tra le istruzioni non virtualizzate e rientra nella prima.
  3. Si individuano gli entrypoint della VM, ci sono parti di codice troppo fragili per essere protette.
  4. Si disassembla il bytecode per poi convertirlo in codice intermediario, per poi possibilmente ottimizzare quest'ultimo.
  5. Si genera il codice x86, con gli eventuali ritocchi.

Mi spiego meglio, in quel pdf fa proprio l'esempio dell'handler NOR
Leggi bene:
"In fact all those operations are managed by one handler "NOR" logical gate"
"NOR" è il "logical gate", l'istruzione, o meglio una inferriata logica di quell'handler. Dovrebbe chiarirti un po' le idee.

Anche essendo gli opcode della vm random
Questi tipi di handler servono per formare l'astrazione del codice non virtualizzato, non sono random. C'è una logica dietro.

non e' possibile capire immediatamente che si riferisce a quell'handler? Oppure hanno una sorta di mutation come quella su cui hai fatto la guida del codice metamorfo?
VMProtect sembra non supportare la virtualizzazione di istruzioni tipo SIMD (AND, SUB, OR), e quell'handler attraverso l'istruzione nor (che non esiste in x86, e va tradotto successivamente in codice assembly in base ai costrutti per come è stata elaborata) fa capire come vengono eseguite. nor accetta 2 parametri, esempio: and ecx,eax è rappresentata come NOR(NOR(ecx, ecx) NOR(eax,eax)).
E no, non stiamo parlando di metamorfismo. Devi comunque studiare il codice prima.
 
  • Mi piace
Reazioni: JunkCoder
Ultima modifica:
"NOR" è il "logical gate", l'istruzione, o meglio una inferriata logica di quell'handler. Dovrebbe chiarirti un po' le idee.


Questi tipi di handler servono per formare l'astrazione del codice non virtualizzato, non sono random. C'è una logica dietro.


VMProtect sembra non supportare la virtualizzazione di istruzioni tipo SIMD (AND, SUB, OR), e quell'handler attraverso l'istruzione nor (che non esiste in x86, e va tradotto successivamente in codice assembly in base ai costrutti per come è stata elaborata) fa capire come vengono eseguite. nor accetta 2 parametri, esempio: and ecx,eax è rappresentata come NOR(NOR(ecx, ecx) NOR(eax,eax)).
E no, non stiamo parlando di metamorfismo. Devi comunque studiare il codice prima.
Grazie per i chiarimenti

Si intendevo dire che l'opcode e' random, non l'handler, nel senso che il valore dell'opcode di AND cambia da vm a vm ma poi andra' comunque ad usare NOR in quella modalita', quindi la mia domanda era: la loro implementazione di NOR puo' essere identificata immediatamente da un analisi statica anche su diversi eseguibili e da questo debuggando risalire a gli opcode che la usano? (NOR per fare un esempio, visto che era citato nel pdf, ma mi riferisco anche a gli altri handler, che magari avendo piu' istruzioni lasciano una signature piu' evidente)

Comunque ho trovato un'implementazione di interessante da cui ho preso degli spunti, lo linko qua cosi' se qualcuno e' interessato puo' dare un occhiata: rewolf-x86-virtualizer

Non e' che non voglia fare un lavoro "consistente" come quello degli altri packer, a livello di altre protezioni lo faro' sicuramente, ma intendevo proprio per la virtualizzazione essendo una mole di lavoro non indifferente, con grande attenzione dal dettaglio, non supportero' istruzioni un po' piu' "esotiche", che non vedi molto spesso e che lascero' fuori dalla VM.
 
In questi casi la prima cosa da fare è capire esattamente come funziona la VM in questi packer che hai menzionato.
Ovviamente non è facile perchè questi sono complessi.
Ma in generale, quello che devi fare è "inventarti" una tua architettura, e crearne un contesto completo (ad esempio decidere numero di registri ed allocarli, una lista di opcodes che consentano di eseguire quante più operazioni possibile, creare il vm stack ecc.).
A questo punto devi analizzare l'assembly originale e tradurlo in opcodes adatti alla tua macchina (questa è l'operazione più lunga e complessa).
Ora non ti resta che creare la macchina stessa, ovvero creare un parser dei tuoi opcodes, ognuno dei quali farà riferimento ad uno specifico handler.

Ci sono stati vari tutorials riguardo a questa parte ma personalmente non ne ho mai visti di completi e semplici da capire.
uno abbastanza semplice è qui: https://resources.infosecinstitute....ering-simple-virtual-machine-protection/#gref
 
  • Mi piace
Reazioni: JunkCoder
Si intendevo dire che l'opcode e' random, non l'handler, nel senso che il valore dell'opcode di AND cambia da vm a vm ma poi andra' comunque ad usare NOR in quella modalita'
Ribadisco: non c'è niente di random con quelle istruzioni, c'è una logica dietro. Themida però offre la generazione dinamica degli opcode nella VM, se intendi qualcosa del genere.

quindi la mia domanda era: la loro implementazione di NOR puo' essere identificata immediatamente da un analisi statica anche su diversi eseguibili e da questo debuggando risalire a gli opcode che la usano? (NOR per fare un esempio, visto che era citato nel pdf, ma mi riferisco anche a gli altri handler, che magari avendo piu' istruzioni lasciano una signature piu' evidente)
Ciò che realmente vuoi è devirtualizzare il programma con la scaletta che ho descritto sopra. Poi l'analisi statica è un conto, il debugging ne è un altro (è parte dell'analisi dinamica).
 
Ultima modifica:
In questi casi la prima cosa da fare è capire esattamente come funziona la VM in questi packer che hai menzionato.
Ovviamente non è facile perchè questi sono complessi.
Ma in generale, quello che devi fare è "inventarti" una tua architettura, e crearne un contesto completo (ad esempio decidere numero di registri ed allocarli, una lista di opcodes che consentano di eseguire quante più operazioni possibile, creare il vm stack ecc.).
A questo punto devi analizzare l'assembly originale e tradurlo in opcodes adatti alla tua macchina (questa è l'operazione più lunga e complessa).
Ora non ti resta che creare la macchina stessa, ovvero creare un parser dei tuoi opcodes, ognuno dei quali farà riferimento ad uno specifico handler.

Ci sono stati vari tutorials riguardo a questa parte ma personalmente non ne ho mai visti di completi e semplici da capire.
uno abbastanza semplice è qui: https://resources.infosecinstitute....ering-simple-virtual-machine-protection/#gref
Step chiari e concisi, infatti la parte che piu' mi preoccupa e' tradurre l'assembly originale nei miei opcodes, soprattutto per quanto riguarda i jump, dentro la VM, fuori ed alle API di windows (mi piacerebbe fare un handler apposito che non usa la IAT ed evita hook su LoadLibrary/GetProcAddress), grazie anche per il link.

Ribadisco: non c'è niente di random con quelle istruzioni, c'è una logica dietro. Themida però offre la generazione dinamica degli opcode nella VM, se intendi qualcosa del genere.


Ciò che realmente vuoi è devirtualizzare il programma con la scaletta che ho descritto sopra. Poi l'analisi statica è un conto, il debugging ne è un altro (è parte dell'analisi dinamica).

Si, e' quello che intendevo. Ovvio che hanno un senso e non siano random a runtime, dico proprio in fase di generazione della VM, che e' quello che impedisce di fare la decodifica automatica degli opcode su VM diverse una volta risolta la prima, un esempio pratico per capirci meglio: in una vm 0x05 e' AND, mentre in un altra AND e' 0xFA, quindi hanno un bytecode diverso a parita' di istruzioni originali, ma condividono lo stesso significato e lo stesso handler. Bene quindi avevo capito, abbiamo solo usato parole diverse, allora l'unica cosa che ostacola la devirtualizzazione del programma e' proprio attraverso ulteriori protezioni come quelle anti-debug.
 
A proposito dell'aspetto che ti preoccupa di più, ti ho allegato un PDF.
Tra le altre cose parla di MetaPHOR (sezione C); è vecchio ormai, tuttavia utilizzava uno pseudo instruction-set per rappresentare le istruzioni (x86). Estrapolo una piccola sezione (p. 8):

[...]
The opcode choices are motivated by the equivalent x86 opcode organization and by the sake of simplifying the manipulation of instructions and the coding of transformations. In particular, for the first type of opcodes, the opcode itself (for instance ADD) is encoded into bits 6..3, and the operand types into bits 2..0 and 7: bit 7 specifies whether the operands are 8 bits (for instance mov al, 12h) or 32 bits (for instance mov eax, 12h) whereas bits 2..0 specify the type of operands (Reg, Imm, etc.).
Finally, a pseudo-instruction is encoded in 16 bytes:

Codice:
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
OP *--------- operands --------* LM *- instr -*

OP contains the instruction opcode, on one byte. Then the operands are encoded (register index, memory address or immediate value) on the following 10 bytes. Then LM (“Label Mark”) is a flag on 1 byte telling whether this instruction is the target of a branching instruction: when this is the case, the instruction can neither be deleted nor compressed with instructions preceding it. The last 4 bytes contain a pointer which has miscellaneous significations along the execution: during the disassembly, it contains the address of the initial x86 instruction, during the permutation, it contains the instruction’s address inside the non-permutated code, etc.

Penso possa darti qualche idea su come e cosa fare (e cosa no).
 

Allegati

  • beaucamps07-metamorphism.pdf
    432.8 KB · Visualizzazioni: 12
A proposito dell'aspetto che ti preoccupa di più, ti ho allegato un PDF.
Tra le altre cose parla di MetaPHOR (sezione C); è vecchio ormai, tuttavia utilizzava uno pseudo instruction-set per rappresentare le istruzioni (x86). Estrapolo una piccola sezione (p. 8):



Penso possa darti qualche idea su come e cosa fare (e cosa no).

Grazie per lo spunto, avevo pensato piu' ad istruzioni a lunghezza dinamica per il mio bytecode, mentre metaphor se ho capito bene usava sempre 16 byte per istruzione

Grazie a tutti per avermi chiarito le idee e per tutti i link e risorse, credo di poter continuare da solo, se incappero' in qualche problema durante l'implementazione apriro' un topic ad hoc, intanto segno risolto
 
Ragazzi, erano anni che non venivo su questo forum e devo dire che a rileggere le vecchie discussioni ho sentito un po' di nostalgia di quel periodo! Sono passati 6 anni e molte cose sono cambiate ma vedere questi topic mi fa sorridere e ben sperare.
Quello di inforge è uno di quei forum che mi aveva stupito in positivo e continua tuttora a stupirmi, del resto qui dentro una parte di utenti è skillata. Questo era uno dei motivi che mi aveva spinto ad iscrivermi e a creare un Crackme in questa area.

Leggo con piacere questo topic proprio in un periodo in cui sto rompendo challenge di reversing e quello delle VM è un bellissimo tema. Purtroppo non mi sono mai cimentato a crearne una e non posso essere di aiuto. Recentemente ho risolto una challenge che adoperava una VM, che sicuramente è più semplice che scrivere un VM protector.

Questo post è solo per salutarvi tutti, è probabile che ricomincerò a frequentare questo forum. @EvOlUtIoN_OGM come stai? Sono felice di vederti ancora da queste parti! :)