Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
VMProtect-devirtualization — Brincando com a proteção de software VMProtect. Desofuscação automática de funções puras usando execução simbólica e LLVM. | Kitploit
Ferramentas/GitHubGitHub/jonathansalwan/vmprotect-devirtualization
Análise EstáticaAnálise Dinâmica (Sandboxing)Engenharia ReversaFuzzingAnálise de Binários
GitHubjonathansalwan/vmprotect-devirtualization

VMProtect-devirtualization

Brincando com a proteção de software VMProtect. Desofuscação automática de funções puras usando execução simbólica e LLVM.

Ver Repositório

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
1.5k208há 4 anosRevisado pelo Kitploit

Desvirtualização do VMProtect

Uma abordagem dinâmica experimental para desvirtualizar funções puras protegidas por VMProtect 3.x

 

 

  • Resumo
  • Introdução
  • A abordagem
    • Exemplo 1: Uma operação bitwise simples protegida
    • Exemplo 2: Uma operação MBA protegida
    • Exemplo 3: Mais de um bloco básico
  • Conclusão e limitações
  • Referências

 

 

TL;DR

Estou compartilhando algumas notas sobre uma abordagem dinâmica para desvirtualizar funções puras protegidas pelo VMProtect. Essa abordagem mostrou resultados muito bons se a função virtualizada contém apenas um bloco básico (independentemente de seu tamanho). Este é um cenário comum quando binários protegem operações aritméticas. No entanto, esta abordagem é um pouco mais experimental quando a função alvo contém mais de um bloco básico. Ainda assim, conseguimos desvirtualizar e reconstruir o código binário de amostras que contêm 2 blocos básicos, o que sugere que é possível desvirtualizar completamente pequenas funções de forma dinâmica.

Introduction

VMProtect é uma proteção de software que protege o código executando-o em uma máquina virtual com arquitetura não padrão. Essa proteção é um ótimo playground para os amantes de assembly [0, 1, 2, 3, 4, 5, 6, 11]. Além disso, já existem inúmeras ferramentas que atacam essa proteção [7, 8, 9, 12, 13]. Em 2016, analisamos a solução de proteção de software Tigress e conseguimos derrotar sua virtualização usando execução simbólica e LLVM. Essa abordagem foi apresentada no DIMVA 2018 [10] e quis testá-la no VMProtect. Observe que não existe uma solução mágica que funcione em todos os binários, sempre há compensações dependendo do alvo e dos seus objetivos. Esta modesta contribuição visa fornecer um exemplo de um ataque dinâmico contra funções puras que são virtualizadas pelo VMProtect. A principal vantagem de um ataque dinâmico é que ele derrota por design algumas proteções estáticas do VMProtect, como código automodificável, criptografia de chaves e operandos etc.

Consideramos uma função pura como uma função com um número finito de caminhos e que não possui efeitos colaterais. Pode haver várias entradas, mas apenas uma saída. Abaixo está um exemplo de uma função pura:```cpp int secret(int x, int y) { int r = x ^ y; return r; }

root@kitploit:~
# A abordagem

Baseamo-nos na intuição chave de que um trace ofuscado T' (do código ofuscado P') combina instruções
originais do código original P (o trace T correspondente a T' no código original) e
instruções da máquina virtual VM de modo que T' = T + VM(T). Se formos capazes de distinguir entre
essas duas subsequências de instruções T e VM(T), então seremos capazes de reconstruir um caminho do
programa original P a partir de um trace T'. Repetindo esta operação para cobrir todos os caminhos do
programa virtualizado, seremos capazes de reconstruir o programa original P. No nosso exemplo prático, o código
original tem um número finito de caminhos executáveis, o que é o caso em muitas situações envolvendo proteção
de propriedade intelectual. Para isso, procedemos com os seguintes passos:

1. Identificar a função virtualizada e seus argumentos
2. Gerar um trace VMProtect do alvo
3. Reproduzir o trace VMP e construir expressões simbólicas para obter a relação entre entradas e saída
4. Aplicar otimizações nas expressões simbólicas para evitar o máximo possível instruções da VM
5. Elevar nossa representação simbólica para LLVM-IR para construir uma nova versão desprotegida do alvo

## Exemplo 1: Uma operação bitwise simples

Vamos tomar como primeiro exemplo a seguinte função: ela recebe duas entradas e retorna `x ^ y` que é protegida por VMProtect.```cpp
int secret(int x, int y) {
  VMProtectBegin("secret");
  int r = x ^ y;
  VMProtectEnd();
  return r;
}

Começamos identificando onde as funções estão usando VMProtect e quantos argumentos elas têm. Para nosso exemplo, podemos ter algo como abaixo:

Apenas lendo o código, sabemos que a função começa no endereço 0x4011c0, tem dois argumentos de 32 bits (edi e esi) e retorna em 0x4011ef. Isso é tudo o que precisamos em engenharia reversa. As próximas partes serão automáticas. Agora temos que gerar uma execução de rastreamento desta função virtualizada. Para isso, usamos um Pintool. Ele precisa apenas de um endereço start e um end (para nosso exemplo, 0x4011c0 e 0x4011ef) que representam o intervalo da instrumentação. Note que qualquer tipo de DBI ou emulador poderia fazer esse trabalho.``` $ ./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

root@kitploit:~
Você pode ver o resultado [here](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample2.vmp.trace). O formato de rastreamento usa três tipos de operações: `mr`, `r` e `i`. `mr` é um acesso de leitura à memória feito pela instrução `i`, e `r` são os registradores da CPU. Por exemplo:```
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

Temos uma leitura de memória que carrega uma constante de 8 bytes 0x227db4f8 do endereço 0x7ffda459d718. A instrução é executada no endereço 0x89173e e seu opcode de 8 bytes é 488BB42490000000, que é um mov rsi, qword ptr [rsp + 0x90]. O estado dos registradores antes da execução é o seguinte:```python (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

root@kitploit:~
Uma vez que o trace VMP foi gerado, nós o reproduzimos usando o script [attack_vmp.py](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/attack_vmp.py). Este script utiliza o [Triton](https://github.com/jonathansalwan/Triton) para construir o predicado de caminho do trace. Note que todas as expressões que envolvem variáveis simbólicas (entradas da função) são mantidas simbólicas, enquanto todas as expressões não relacionadas às entradas são concretizadas. Em outras palavras, nossas expressões simbólicas não contêm nenhuma operação relacionada à máquina virtual (a própria maquinaria não depende do usuário), mas apenas operações relacionadas ao programa original.

Por exemplo, abaixo está um exemplo de concretização. À esquerda temos uma AST que contém subexpressões que não envolvem variáveis simbólicas (`1 + 2` e `6 ^ 3`). Então, esses ramos são concretizados e substituídos por constantes `3` e `5`, o que resulta na AST à direita. **É assim que desvirtualizamos o código.**

<p align="center">
  <img src="https://assets.kitploit.com/production/public/readmes/8096/857ed50cfe9cb2347f816ece1d8dc4c13174c971dcb65ebc5621be14269a5ca8.png">
</p>

**Uma observação sobre o slicing reverso no nível da fórmula**: Como é comum na execução simbólica, a representação simbólica é primeiro computada de forma direta ao longo do caminho; em seguida, todas as operações lógicas e definições que não afetam nem o resultado final nem o caminho seguido são removidas da expressão simbólica (slicing da fórmula, também conhecido como poda da fórmula). Isso equivale a realizar, na fórmula, o equivalente a uma análise de código de slicing reverso a partir da saída do programa. Assim, no retorno da função `secret`, temos uma expressão da relação entre as entradas e a saída sem as instruções do VMProtect.

O script `./attack_vmp.py` recebe como parâmetros o arquivo de trace e o tamanho das variáveis simbólicas. Lembre-se de que eram `edi` e `esi`, portanto têm 4 bytes cada. O resultado do script é o seguinte:```
$ ./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 ==============================

Como podemos ver, a expressão desvirtualizada retornada pela função secret é bastante concisa e não contém instruções da máquina virtual.```smt (bvor (bvnot (bvor (bvnot (bvnot x)) (bvnot y) ) ) (bvnot (bvor (bvnot x) (bvnot (bvand (bvnot y) (bvnot y) ) ) ) ) )

root@kitploit:~
No entanto, não conseguimos recuperar a expressão original que era uma simples operação `XOR`. Parece que o `XOR` foi traduzido para operações bit a bit. Felizmente, recentemente lançamos novos recursos no projeto Triton que são um [sintetizador](https://github.com/JonathanSalwan/Triton/issues/1074) e um elevador para [LLVM-IR](https://github.com/JonathanSalwan/Triton/issues/1078). Assim, podemos sintetizar a expressão, o que nos dá a expressão `(bvxor x y)`. É uma boa vitória e agora podemos ir mais longe, elevando esta expressão para LLVM-IR e depois compilar um novo código binário desvirtualizado.

## Exemplo 2: Uma operação MBA protegida

Ok, agora vamos dar uma olhada em outro exemplo que tenta ocultar uma operação MBA. O código fonte original é o seguinte:```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;
}

Assim como no primeiro exemplo, temos que identificar onde esta função começa e termina e gerar um trace 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

root@kitploit:~
Assim que o [VMP trace](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample3.vmp.trace) for gerado, vamos executar o 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 ==============================

O resultado é bastante interessante por várias razões. Primeiro, conseguimos evitar com sucesso o máximo possível de instruções da máquina virtual, pois passamos de 25085 instruções executadas para 25 instruções LLVM. No entanto, não conseguimos obter uma versão sintetizada adequada da saída (sim, eu sei, estamos indo além de apenas fazer desvirtualização). A vantagem de elevar nossas expressões simbólicas para LLVM-IR é que podemos nos beneficiar totalmente do pipeline de otimização do LLVM. Vamos fazer isso:```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 }

root@kitploit:~
Usando otimizações LLVM, conseguimos remover o ruído da nossa saída desvirtualizada e, assim, quebrar a MBA. Podemos ver a operação `XOR` com sua constante (`%0 = xor i8 %SymVar_0, 92`) e o `+ y` (`%6 = add nsw i32 %5, %1`). As instruções intermediárias estão apenas lidando com o sinal. Para resumir este exemplo, desvirtualizamos completamente a função `secret` usando o script `attack_vmp.py` e então quebramos totalmente a MBA usando otimizações LLVM.

## Exemplo 3: Mais de um bloco básico

Obtivemos resultados muito bons se a função `secret` contiver apenas um bloco básico, independentemente de seu tamanho. Portanto, neste ponto, somos capazes de desvirtualizar um caminho. Para reconstruir o comportamento completo da função, temos que desvirtualizar sucessivamente os caminhos alcançáveis. Para isso, temos que realizar uma cobertura de caminho em ramificações dependentes do usuário. Ao final, obtemos como resultado uma árvore de caminhos que representa os diferentes caminhos da função original. A árvore de caminhos é obtida introduzindo uma construção if-then-else a partir de dois traços T1 e T2 com o mesmo prefixo, seguidos por uma condição C em T1 e um not(C) em T2. Uma vez que a árvore de caminhos é construída, podemos deixar o LLVM gerar um CFG.

<p align="center">
  <img src="https://assets.kitploit.com/production/public/readmes/8096/fc5d1bf9a1544b9b44ef164247f3fc38d04bf45d7c2d7a3c0b21ea3d6636cd39.png">
</p>

Com a proteção de software Tigress, os saltos virtuais foram implementados com instruções reais `jcc`, o que nos permitiu identificar rapidamente a condição de salto. No entanto, as coisas ficam mais complexas quando saltos virtuais estão envolvidos com o VMProtect, pois ele não usa instruções `jcc` para saltar para outro bloco virtual. Tivemos que definir marcadores em um traço dinâmico para identificar a condição envolvida em uma ramificação dependente do usuário. Esta é a parte experimental deste ataque, pois os marcadores não são muito precisos, mas funcionaram para nossas amostras.

Ok, vamos considerar o seguinte exemplo:```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;
}

Assim como nos primeiros exemplos, temos que gerar e analisar o rastro.``` $./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 ==============================

root@kitploit:~
O script nos diz que pode haver um salto simbólico potencial encontrado na flag `AF` no endereço `0x80d905`. Ele também fornece um novo modelo (usando execução simbólica) que deve seguir o outro caminho. Então, vamos gerar um segundo rastreamento usando este modelo (se você der uma olhada no modelo, ele está correto em relação ao nosso código-fonte).```
$ ./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

Assim que o segundo trace é gerado, temos que fornecer esses dois traces para o script attack_vmp.py para que ele possa mesclá-los e criar uma árvore de caminhos. Temos opções extras para definir onde a condição está localizada e em qual flag (flag AF em 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 ==============================

root@kitploit:~
Neste passo, desvirtualizamos os dois traces e os mesclamos em expressões `if-then-else`.
Após elevar a expressão para LLVM-IR, obtemos um CFG com apenas 480 instruções LLVM, o que já é um bom ganho comparado aos milhares de instruções executadas pela máquina virtual.
Mas podemos fazer melhor se usarmos otimizações 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, recuperamos o comportamento original da função secret!

Conclusão e limitações

Embora a abordagem tenha mostrado resultados muito bons para funções que contêm um caminho, a principal limitação do método é que ele é voltado principalmente para programas com um pequeno número de caminhos devido à forma como o VMProtect realiza saltos virtuais. No caso de um número muito grande de caminhos, partes do código original podem ser perdidas, resultando em uma recuperação incompleta. Observe que estamos considerando caminhos executáveis, e não caminhos sintáticos no CFG. Funções de hash e outras funções criptográficas geralmente têm muito poucos caminhos - apenas um caminho no caso de implementações resistentes a ataques de temporização.

Além disso, nossa implementação atual está limitada a programas sem qualquer acesso à memória dependente do usuário. Essa limitação pode ser parcialmente removida usando um tratamento mais simbólico dos acessos à memória no DSE.

Observe também que, embora loops limitados e chamadas de função não recursivas sejam tratados, eles são atualmente recuperados como código inline ou desenrolado, causando um possível aumento no tamanho do código desvirtualizado. Seria interessante ter uma etapa de pós-processamento tentando reconstruir essas abstrações de alto nível.

Para concluir, observe que não pretendo fornecer nenhum tipo de método mágico, estas são apenas algumas observações sobre um ataque dinâmico contra casos muito específicos protegidos pelo VMProtect =).

Se você quiser dar uma olhada mais aprofundada, confira estes recursos:

  • O Pintool para gerar trace
  • Script para analisar um trace do VMP
  • Código fonte das amostras
  • Binários originais e protegidos
  • Traces do VMP
  • Resultados desvirtualizados

Por último, mas não menos importante, agradecimentos especiais ao meu amigo @0vercl0k pela revisão e edições 🚀

Referências```

[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

root@kitploit:~
Baixar ferramenta