VMProtect 2 가상머신 난독화 기법
라온화이트햇 핵심연구팀 권재승
core-research-team.github.io
가상화: 보호하려는 코드를 자체 개발한 가상 머신(VM)용 바이트 코드로 변환
CPU가 직접 실행하던 코드 -> VM 명령어(바이트 코드, 스택 머신 기반 명령)로 변환 -> 실행 시 CPU가 아닌 VM이 코드를 실행( 난독화된 구간만 VM 인터프리터(가상머신)가 바이트코드 해석)
; (일반 코드: CPU가 직접 실행)
mov eax, 1
add eax, 2
; (난독화 코드 진입: VMProtect 인터프리터로 분기)
call VMProtect_Interpreter <- 가상 CPU Fetch-Decode-Execute
; 이 아래는 이미 VM 명령어로 변환된 코드
; 실제 동작은 VMProtect 인터프리터가 바이트코드 해석
; (난독화 구간 끝나고 다시 일반 코드로 복귀)
sub eax, 3
VMProtect 2는 실행가능한 프로그램의 명령어를 RISC 및 스택머신 기반 가상 명령어로 변환하는 가상 머신 기반 난독화(obfuscator) 프로그램
// 원본
mov eax, [var1]
add eax, [var2]
mov [result], eax
// RISC 기반 가상머신 명령어 (CISC -> RISC), 레지스터 연산이 중심이 됨.
LOAD R1, var1 ; var1 값을 R1에 로드
LOAD R2, var2 ; var2 값을 R2에 로드
ADD R3, R1, R2 ; R1, R2를 더해 R3에 저장
STORE result, R3 ; R3의 값을 result에 저장
위 두 코드를 보면 얼핏 비슷해 보인다. 하지만,
- 보호 도구가 임의로 설계한 커스텀 명령어 집합
- 표준 명령어 X -> add, ADD는 같아 보이지만 내부 동작, 파라미터 구조, 의미가 표준 CPU(x86, x64, ARM 등)와 다름
- CPU가 직접 실행하지 않고, 프로그램 내장 가상머신(VM)이 해석
- 명령어의 동작 방식이나 데이터 흐름 문서화X -> 분석자가 '명령어 사전'부터 만들어야 함.
- 명령어 집합/구조의 비공개성
- 빌드마다 바뀌거나, 랜덤하게 재배열/확장될 수 있음.
- 동일한 소스 코드라도, 보호할 때마다 완전히 다른 명령어 시퀀스를 생성
- 원본 코드 흐름/의미 은폐: 변수명, 연산 순서, 제어 흐름 등이 변형됨
위의 이유로 난독화로 분류
mov eax, [var1]
add eax, [var2]
mov [result], eax
// 스택머신 기반
PUSH var1 ; var1 값을 스택에 push
PUSH var2 ; var2 값을 스택에 push
ADD ; 스택 상단 두 값을 pop, 더해서 다시 push
POP result ; 결과를 pop해서 result에 저장
- 상당히 어려운 데이터 흐름 추적
- 일반 어셈은 레지스터나 변수로 직접 이동 -> 분석자의 추적 용이
- 스택 머신 -> 모든 데이터가 스택에 쌓임 -> 어떤 값이 언제, 어디서, 무엇을 위해 사용되는지 추적하기 어려워짐
- 명령어의 의미와 목적이 감춰짐
- 겉보기에는 매우 단순함
- 근데 데드 코드 삽입, 스택 깊이 변화, 가짜 연산 삽입 등 -> 실제 로직 파악 어렵게 함.
- 제어 흐름 분석 -> 스택 값을 기준으로 분기 -> '어디서 어떻게' 추적이 어려움 , flattening
- RISC 기반과 마찬가지로 명령어 집합 비공개/동적 변화
- 여러 난독화 트릭 추가 용이(스택 깊이 변화, 가짜 명령어, 복수 연산 내장 -> 디컴파일, 자동 분석 등이 어려워짐)
VMProtect 2 대표 기법
- 데드 스토어(Dead Store)
- 불투명 분기(Opaque Branch)
- 롤링 복호화(Rolling Decryption)
데드 스토어: 명령어들 사이에 실제 연산과 전혀 상관이 없는 코드(Write 명령)를 삽입 -> 저장한 값이 사용되지 않고 덮어쓰이거나, 사라짐
// 데드 스토어 제거 전
.vmp0:0000000140004149 66 D3 D7 rcl di, cl // 삭제
.vmp0:000000014000414C 58 pop rax
.vmp0:000000014000414D 66 41 0F A4 DB 01 shld r11w, bx, 1
.vmp0:0000000140004153 41 5B pop r11
.vmp0:0000000140004155 80 E6 CA and dh, 0CAh
.vmp0:0000000140004158 66 F7 D7 not di // 삭제
.vmp0:000000014000415B 5F pop rdi
// 데드 스토어 제거 후
.vmp0:000000014000414C 58 pop rax
.vmp0:000000014000414D 66 41 0F A4 DB 01 shld r11w, bx, 1
.vmp0:0000000140004153 41 5B pop r11
.vmp0:0000000140004155 80 E6 CA and dh, 0CAh
.vmp0:000000014000415B 5F pop rdi
불투명 분기: 조건에 관계없이 항상 같은 결과로 수렴하는 쓸모 없는 분기
void opaque_test(int a, int b)
{
int tmp;
int res = 0;
if(a > 10){
res = a*b;
} else {
tmp = a; // 데드 스토어
tmp = a*a; // 데드 스토어
tmp = tmp*b;
res = tmp/a; // 결국 res = a * b
}
printf("res : %d\n", res);
}
int main(){
opaque_test(11, 5);
opaque_test(5, 11);
return 0;
}
// 그냥 return a * b
롤링 암호화 키: 시간이 흐르거나 패킷이 오갈 때마다 암호화 키가 계속해서 바뀌는 방식
롤링 복호화: 가상 명령어를 롤링 암호화 키를 사용하여 암호화
- 가상 명령어 실행 시 옵코드와 명령어의 피연산자를 해독하는데 사용 -> 가상 명령어들의 순서가 바뀌게 되면 키가 손상되어 다음 가상 명령어를 해독 못하게 만듦(다음 명령어 복호화에 이전 명령어가 뭔지가 쓰임) -> 가상 명령어들이 항상 순서대로 실행되는 것 보장, 후킹 방지, 정적 분석 난이도 상승
-> 즉치 값에 대해 암복호화 -> opcode는 명령어 핸들러 테이블을 인덱싱하기 위한 값
롤링 복호화: 가상 명령어의 해독을 위해서는 총 5개 변환을 순차적으로 적용.
이변환은
XOR, NEG, NOT, AND, ROR, ROL, SHL, SHR, ADD, SUB, INC, DEC, BSWAP
위 13개 명령어 들로 구성. 롤링 암복호화에 사용되는 변환은 고정되지 않고 변함. 해독할 때마다 그에 맞는 변환 방법 필요.
1. 롤링 암호키 포함하여 변환
XOR, AND, ROR, ROL, ADD, SUB
롤링 암호키는 처음에 RBX에 있음.
decoded = EncryptedOperand {위 여섯가지 연산 중 하나} RBX -> 연산 후 복호화 값을 얻는 동시에, 이 연산의 역을 적용해서 키를 업데이트 해야함. 따라서 가역 연산인 위 6가지 중 하나를 사용
이유 : 위의 명령어 순서~ 라고 쓴거 처럼
VMProtect에서는 명령어 한 줄마다 "서로 다른 롤링 키 값"으로 피연산자(즉치값)가 암호화되어 있습니다.
즉, 각 명령어의 암호화된 즉치값은 그 명령어에 대응하는 롤링 키에 맞춰 암호화되어 있기 때문에,
해당 키를 정확히 사용하지 않으면 복호화가 실패
2~4. 해독해야할 피연산자가 들어있는 레지스터만 적용하여 변환 수행
5. 복호화한 즉치값(al)과 롤링 키 값(bl) 연산(1의 역연산) -> 여기서 수정된 bl 값은 다음 명령어의 피연산자 복호화에 사용
-> 위 다섯 단계는 VMP에서 그냥 정한 것. 그래서 명령어 순서가 중요
예시
---------- example1 ----------
add al, bl
inc al
neg al
ror al, 0x06
add bl, al
---------- example2 ----------
xor ax, bx
rol ax, 0x0E
xor ax, 0xA808
neg ax
xor bx, ax
---------- example3 ----------
sub al, bl
ror al, 2
not al
inc al
sub bl, al
연산대상 크기에 따라 레지스터 ax, al 등 씀.
'코드 난독화 도구' 카테고리의 다른 글
🛡️ VMProtect 완전 분석: 바이트코드, IR, 그리고 최적화까지 (1) | 2025.07.14 |
---|---|
ObfusTree + Tigress 난독화 및 역난독화 결과 분석 (0) | 2025.05.25 |
실험 및 Tigress와의 비교 (0) | 2025.05.19 |
Tigress 난독화 옵션 적용 및 평가지표 설계 (0) | 2025.05.12 |
Tigress.wtf 빌드 및 사용 (0) | 2025.05.07 |