Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/jonathansalwan/vmprotect-devirtualization
Static AnalysisDynamic Analysis (Sandboxing)Reverse EngineeringFuzzingBinary Analysis
GitHubjonathansalwan/vmprotect-devirtualization

VMProtect-devirtualization

VMProtect 소프트웨어 보호를 다루기. 기호 실행 및 LLVM을 사용한 순수 함수의 자동 난독화 해제.

저장소 보기

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
1.5k2084년 전Kitploit 검토 완료

VMProtect Devirtualization

VMProtect 3.x로 보호된 순수 함수를 가상화 해제하기 위한 실험적 동적 접근 방식

 

 

  • TL;DR
  • Introduction
  • The approach
    • Example 1: A simple bitwise operation protected
    • Example 2: A MBA operation protected
    • Example 3: More than one basic block
  • Conclusion and limitations
  • References

 

 

TL;DR

VMProtect로 보호된 순수 함수를 가상화 해제하는 동적 접근 방식에 대한 몇 가지 노트를 공유합니다. 이 접근 방식은 가상화된 함수에 오직 하나의 기본 블록만 포함된 경우(크기와 무관) 매우 좋은 결과를 보여주었습니다. 이는 바이너리가 산술 연산을 보호할 때 흔히 발생하는 시나리오입니다. 하지만 대상 함수에 둘 이상의 기본 블록이 포함되면 이 접근 방식은 약간 더 실험적입니다. 그럼에도 불구하고 우리는 2개의 기본 블록을 포함한 샘플에서 바이너리 코드를 성공적으로 가상화 해제하고 재구성했으며, 이는 동적으로 작은 함수를 완전히 가상화 해제하는 것이 가능함을 시사합니다.

Introduction

VMProtect은 비표준 아키텍처의 가상 머신을 통해 코드를 실행하여 보호하는 소프트웨어 보호 솔루션입니다. 이 보호 솔루션은 어셈블리 애호가에게 훌륭한 놀이터입니다 [0, 1, 2, 3, 4, 5, 6, 11]. 또한, 이미 이 보호를 공격하는 수많은 도구들이 존재합니다 [7, 8, 9, 12, 13]. 2016년에 우리는 Tigress 소프트웨어 보호 솔루션을 살펴보고 기호 실행과 LLVM을 사용하여 가상화를 무력화하는 데 성공했습니다. 이 접근 방식은 DIMVA 2018 [10]에서 발표되었으며, 이를 VMProtect에서 테스트해보고자 했습니다. 모든 바이너리에서 작동하는 마법 같은 솔루션은 없으며, 대상과 목표에 따라 항상 트레이드오프가 존재합니다. 이 작은 기여는 VMProtect에 의해 가상화된 순수 함수에 대한 동적 공격의 예를 제공하는 것을 목표로 합니다. 동적 공격의 주요 장점은 설계 상 자기 수정 코드, 키 및 피연산자 암호화 등 VMProtect의 일부 정적 보호를 무력화한다는 점입니다.

우리는 순수 함수를 유한한 경로를 가지며 부작용이 없는 함수로 간주합니다. 여러 개의 입력이 있을 수 있지만 출력은 하나뿐입니다. 아래는 순수 함수의 예입니다:```cpp int secret(int x, int y) { int r = x ^ y; return r; }

root@kitploit:~
# 접근 방식

우리는 난독화된 트레이스 T'(난독화된 코드 P'로부터)가 원래 코드 P의 명령어(원래 코드에서 T'에 해당하는 트레이스 T)와 가상 머신 VM의 명령어를 결합하여 T' = T + VM(T)와 같다는 핵심 통찰에 의존합니다. 이 두 명령어 하위 시퀀스 T와 VM(T)를 구별할 수 있다면, 트레이스 T'로부터 원래 프로그램 P의 한 경로를 재구성할 수 있습니다. 이 작업을 반복하여 가상화된 프로그램의 모든 경로를 포괄하면 원래 프로그램 P를 재구성할 수 있습니다. 실제 예제에서 원래 코드는 실행 가능한 경로의 유한한 개수를 가지며, 이는 지적 재산권 보호와 관련된 많은 상황에서 해당됩니다. 이를 위해 다음 단계를 수행합니다:

1. 가상화된 함수와 그 인자를 식별합니다.
2. 대상의 VMProtect 트레이스를 생성합니다.
3. VMP 트레이스를 재생하고 심볼릭 표현식을 구성하여 입력과 출력 간의 관계를 얻습니다.
4. 심볼릭 표현식에 최적화를 적용하여 VM의 명령어를 최대한 피합니다.
5. 심볼릭 표현을 LLVM-IR로 리프팅하여 대상의 새로운 보호되지 않은 버전을 구축합니다.

## 예제 1: 간단한 비트 연산

첫 번째 예제로 다음 함수를 살펴보겠습니다: 두 개의 입력을 받아 `x ^ y`를 반환하며, 이는 VMProtect로 보호됩니다.```cpp
int secret(int x, int y) {
  VMProtectBegin("secret");
  int r = x ^ y;
  VMProtectEnd();
  return r;
}

우리는 함수가 VMProtect를 사용하는 위치와 인수의 개수를 식별하는 것부터 시작합니다. 예를 들어 아래와 같은 상황이 있을 수 있습니다:

코드를 읽는 것만으로도 함수가 주소 0x4011c0에서 시작하고, 두 개의 32비트 인수(edi 및 esi)를 가지며, 0x4011ef에서 반환된다는 것을 알 수 있습니다. 이것이 우리에게 필요한 모든 리버스 엔지니어링입니다. 다음 부분은 자동으로 처리됩니다. 이제 이 가상화된 함수의 트레이스 실행을 생성해야 합니다. 이를 위해 Pintool을 사용합니다. 이 도구는 계측 범위를 나타내는 시작 및 종료 주소(예제의 경우 0x4011c0 및 0x4011ef)만 필요합니다. 모든 종류의 DBI나 에뮬레이터가 이 작업을 수행할 수 있습니다.``` $ ./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:~
결과는 [여기](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample2.vmp.trace)에서 확인할 수 있습니다. 트레이스 형식은 세 가지 종류의 연산을 사용합니다: `mr`, `r`, `i`.
`mr`는 명령어 `i`에 의해 수행된 메모리 읽기 접근이고, `r`은 CPU 레지스터입니다. 예를 들어:```
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

우리는 주소 0x7ffda459d718에서 8 바이트 상수 0x227db4f8를 로드하는 메모리 읽기가 있습니다.

명령어는 주소 0x89173e에서 실행되며, 8바이트 길이의 opcode는 488BB42490000000이고 이것은 mov rsi, qword ptr [rsp + 0x90]입니다.

실행 전 레지스터 상태는 다음과 같습니다:```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:~
VMP 추적이 생성되면 [attack_vmp.py](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/attack_vmp.py) 스크립트를 사용하여 이를 재생합니다. 이 스크립트는 [Triton](https://github.com/jonathansalwan/Triton)을 사용하여 추적의 경로 술어를 구축합니다. 기호 변수(함수의 입력)를 포함하는 모든 표현식은 기호로 유지되며, 입력과 관련 없는 표현식은 구체화됩니다. 즉, 기호 표현식에는 가상 머신(기계 자체는 사용자에 의존하지 않음)과 관련된 연산이 포함되지 않고 원래 프로그램과 관련된 연산만 포함됩니다.

예를 들어, 아래는 구체화의 예입니다. 왼쪽에는 기호 변수를 포함하지 않는 하위 표현식(`1 + 2` 및 `6 ^ 3`)이 포함된 AST가 있습니다. 따라서 이러한 분기는 구체화되어 상수 `3` 및 `5`로 대체되어 오른쪽의 AST가 됩니다. **이것이 코드를 역가상화하는 방법입니다.**

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

**수식 수준 역방향 슬라이싱에 대한 참고 사항**: 기호 실행에서 일반적인 것처럼 기호 표현은 먼저 경로를 따라 순방향으로 계산된 다음, 최종 결과나 따라간 경로에 영향을 미치지 않는 모든 논리 연산 및 정의가 기호 표현식에서 제거됩니다(수식 슬라이싱, 일명 수식 가지치기). 이는 프로그램 출력에서 역방향 슬라이싱 코드 분석과 동등한 작업을 수식에 수행합니다. 따라서 `secret` 함수의 반환 시점에 VMProtect의 명령어 없이 입력과 출력 간의 관계에 대한 표현식을 얻을 수 있습니다.

`./attack_vmp.py` 스크립트는 추적 파일과 기호 변수의 크기를 매개변수로 사용합니다. `edi`와 `esi`였으므로 각각 4바이트 길이입니다. 스크립트의 결과는 다음과 같습니다.```
$ ./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 ==============================

보시다시피, secret 함수가 반환하는 탈가상화된 표현식은 매우 간결하며 가상 머신의 명령어를 포함하지 않습니다.```smt (bvor (bvnot (bvor (bvnot (bvnot x)) (bvnot y) ) ) (bvnot (bvor (bvnot x) (bvnot (bvand (bvnot y) (bvnot y) ) ) ) ) )

root@kitploit:~
하지만 우리는 간단한 `XOR` 연산이었던 원래 표현식을 복구하지 못했습니다. `XOR`이 비트 연산으로 변환된 것 같습니다. 다행히도, 우리는 최근 Triton 프로젝트에 [합성기(synthesizer)](https://github.com/JonathanSalwan/Triton/issues/1074)와 [LLVM-IR](https://github.com/JonathanSalwan/Triton/issues/1078)로의 리프터(lifter)라는 새로운 기능을 출시했습니다. 따라서 우리는 표현식을 합성하여 `(bvxor x y)` 표현식을 얻을 수 있습니다. 이것은 좋은 성과이며, 이제 이 표현식을 LLVM-IR으로 리프팅한 후 새로운 탈가상화(devirtualized) 바이너리 코드를 컴파일하는 더 나아갈 수 있습니다.


## 예시 2: 보호된 MBA 연산

자, 이제 MBA 연산을 숨기려는 또 다른 예제를 살펴보겠습니다. 원본 소스 코드는 다음과 같습니다:```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;
}

첫 번째 예제와 마찬가지로, 이 함수의 시작과 끝을 식별하고 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:~
[VMP trace](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/HEAD/vmp_traces/sample3.vmp.trace)가 생성되면, `./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 ==============================

그 결과는 여러 이유로 꽤 흥미롭습니다. 첫째, 가상 머신의 명령어를 최대한 피하는 데 성공하여 실행된 명령어가 25085개에서 25개의 LLVM 명령어로 줄었습니다. 하지만 출력의 합성된 버전을 잘 얻지 못했습니다 (네, 알고 있습니다. 우리는 단순한 역가상화를 넘어서고 있습니다). 기호 표현식을 LLVM-IR로 리프팅하면 LLVM의 최적화 파이프라인을 완전히 활용할 수 있다는 장점이 있습니다. 다음과 같이 해보겠습니다:```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:~
LLVM 최적화를 사용하여 역가상화된 출력에서 노이즈를 제거하고 MBA를 해독할 수 있었습니다.
`XOR` 연산과 해당 상수(`%0 = xor i8 %SymVar_0, 92`) 및 `+ y`(`%6 = add nsw i32 %5, %1`)를 볼 수 있습니다.
그 사이의 명령어는 부호를 처리하는 데만 사용됩니다. 이 예제를 요약하면, `attack_vmp.py` 스크립트를 사용하여 `secret` 함수를 완전히 역가상화한 후 LLVM 최적화를 사용하여 MBA를 완전히 해독했습니다.

## Example 3: More than one basic block

`secret` 함수에 크기에 관계없이 하나의 기본 블록만 포함되어 있으면 매우 좋은 결과를 얻었습니다. 따라서 이 시점에서 하나의 경로를 역가상화할 수 있습니다. 전체 함수 동작을 재구성하려면 도달 가능한 경로를 연속적으로 역가상화해야 합니다. 이를 위해 사용자 종속 분기에 대한 경로 커버리지를 수행해야 합니다. 최종적으로 원래 함수의 여러 경로를 나타내는 경로 트리를 결과로 얻습니다. 경로 트리는 동일한 접두사를 가진 두 개의 트레이스 T1과 T2에서 T1의 조건 C와 T2의 not(C) 다음에 if-then-else 구성을 도입하여 얻습니다. 경로 트리가 구축되면 LLVM이 CFG를 생성하도록 할 수 있습니다.

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

Tigress 소프트웨어 보호 기능에서는 가상 점프가 실제 `jcc` 명령어로 구현되어 점프 조건을 빠르게 식별할 수 있었습니다. 그러나 VMProtect에서는 가상 점프가 포함될 때 상황이 더 복잡해집니다. VMProtect는 다른 가상 블록으로 점프하기 위해 `jcc` 명령어를 사용하지 않기 때문입니다. 사용자 종속 분기에 관련된 조건을 찾기 위해 동적 트레이스에 마커를 정의해야 했습니다. 마커가 정확하지는 않지만 샘플에서 작동했기 때문에 이 공격의 실험적인 부분입니다.

자, 다음 샘플을 고려해 보겠습니다:```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;
}

첫 번째 예제들과 마찬가지로 trace를 생성하고 분석해야 합니다.``` $./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:~
스크립트는 주소 `0x80d905`의 `AF` 플래그에서 잠재적인 심볼릭 점프가 발견될 수 있다고 알려줍니다. 또한 다른 경로를 취해야 하는 새로운 모델(심볼릭 실행 사용)을 제공합니다. 이 모델을 사용하여 두 번째 트레이스를 생성해 보겠습니다(모델을 살펴보면 소스 코드와 관련하여 올바른 것입니다).```
$ ./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

두 번째 추적이 생성되면, 우리는 그 두 추적을 attack_vmp.py 스크립트에 제공하여 그 스크립트가 병합하고 경로 트리를 생성할 수 있습니다. 조건이 위치한 곳과 어떤 플래그(0x80d905의 AF 플래그)에 대한 추가 옵션이 있습니다.``` $ ./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:~
이 단계에서 우리는 두 트레이스를 가상화 해제하고 이를 `if-then-else` 표현식으로 병합했습니다.
표현식을 LLVM-IR로 리프팅한 후, 우리는 가상 머신이 실행했던 수천 개의 명령어와 비교하여 단 480개의 LLVM 명령어만 있는 CFG를 얻습니다. 이는 이미 좋은 성과입니다.
하지만 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, secret 함수의 원래 동작을 복구했습니다!

결론 및 한계

이 접근법은 하나의 경로를 포함하는 함수에 대해 매우 좋은 결과를 보였지만, 이 방법의 주요 한계는 VMProtect가 가상 점프를 수행하는 방식 때문에 경로 수가 적은 프로그램을 주로 대상으로 한다는 점입니다. 경로 수가 너무 많으면 원래 코드의 일부가 손실되어 불완전한 복구가 발생할 수 있습니다. 여기서는 CFG의 구문적 경로가 아닌 실행 경로를 고려하고 있습니다. 해시 및 기타 암호화 함수는 종종 경로가 매우 적습니다. 타이밍 공격에 강한 구현의 경우 단 하나의 경로만 있는 경우도 있습니다.

또한 현재 구현은 사용자 종속 메모리 액세스가 없는 프로그램으로 제한됩니다. 이 제한은 DSE에서 메모리 액세스를 보다 기호적으로 처리하면 부분적으로 제거할 수 있습니다.

제한된 루프 및 비재귀 함수 호출은 처리되지만, 현재는 인라인 또는 언롤된 코드로 복구되어 역가상화된 코드의 크기가 잠재적으로 커질 수 있습니다. 이러한 고수준 추상화를 재구성하는 후처리 단계를 추가하는 것이 흥미로울 것입니다.

결론적으로, 저는 어떤 마법 같은 방법을 제공하려는 것이 아닙니다. 이는 VMProtect로 보호된 매우 특정한 사례에 대한 동적 공격에 관한 몇 가지 노트일 뿐입니다 =).

더 깊이 살펴보고 싶다면 다음 자료를 확인하세요:

  • 트레이스를 생성하는 Pintool
  • VMP 트레이스를 분석하는 스크립트
  • 샘플 소스 코드
  • 원본 및 보호된 바이너리
  • VMP 트레이스
  • 역가상화 결과

마지막으로, 교정 및 편집을 도와준 동료 @0vercl0k에게 특별히 감사드립니다 🚀

참고 자료```

[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:~
도구 다운로드