VMP Virtualization 분석

안상준·2025년 7월 20일

VMProtect

목록 보기
2/6

Code

#include <stdio.h>
int main() {
    int a, b, c;
    printf("Enter two integers: ");
    scanf("%d %d", &a, &b);
    c = a ^ b;
    return 0;
}

코드는 간단한 xor연산을 하는 코드로 작성하였다.

VMP

Option


main 함수를 가상화 난독화 진행하였고

다른 옵션은 전부 해제하였다.

Static Analysis

Original


먼저 원본 코드를 IDA로 분석한 결과이다.
basic block을 보면 main 함수 1개인 것을 볼 수 있다.

Virtualization

Dispatcher


처음 코드의 시작 부분이다.

push    0F21FD100h
call    sub_4B0BD1

시작 부분을 보면 stack에 어떤 값을 push하고 call sub_4B0BD1을 실행하는 것을 볼 수 있다. 해당 위치로 이동해 보자

중요하다고 생각되는 부분은 push edi, pushf, push ecx, push ebp, ... 등 stack에 지속적으로 레지스터랑 flag 등을 push하고 있다.

그 다음 코드인데 esi에 위치한 값을 eax로 가져온다. 가상머신에서 esi는 instruction pointer를 가르킨다.

  • eax = opcode
  • base = 0x4B9E18
  • offset = opcode * 4 + base

그리하여 해당 부분은 opcode를 가져오고 핸들러의 위치를 구하는 코드로 생각된다.

ebx를 stack에 push하고 retn하게 된다. 즉, 위에서 구한 주소의 위치로 jmp한다.

Handler


정적 분석을 했기 때문에 handler의 정확한 주소 값을 몰라 base주소 부근을 찾아 보았다. 그 중 xor을 사용하는 코드를 발견 하였다. 코드를 보면 처음 보는 명령어들이 난무한다.

004B0906: mov     edi, [ebp+0]        ; VMContext로부터 어떤 값(레지스터)을 edi에 로드
004B090A: clc                         ; CF(캐리 플래그) 클리어
004B090B: mov     cl, [ebp+4]        ; VMContext의 다른 레지스터 값을 cl에 로드
004B090E: xor     bx, 0E814h         ; 잡음 연산 (무관할 수 있음)
004B0913: lea     ebp, [ebp-2]       ; VMContext를 살짝 이동 (잡음일 가능성 큼)
004B0919: bt      bx, 0A4h           ; bx의 특정 비트를 검사 (잡음)
004B091E: rcl     bx, cl             ; rotate with carry (잡음 가능성)
004B0921: xor     bx, di             ; bx ^= edi 하위 16비트
004B0924: shl     edi, cl            ; edi <<= cl
004B0926: movzx   bx, ch             ; ch zero-extend → bx (잡음)
004B092A: movzx   ebx, di            ; edi 하위 16비트를 ebx에 로드
004B092D: mov     bl, bl             ; NOP
004B092F: pushf                      ; 플래그 저장
004B0930: pop     ebx                ; 플래그 값을 ebx로 로드
004B0931: jmp     loc_4A0CE3         ; 아마도 dispatcher 루프 복귀

코드 해석을 봤을때, 정확히 원본 코드에서 사용한 xor의 handler인지는 잘 모르겠다. 하지만 동작을 보면 VMContext로 부터 어떤 값을 edi로 가져오고, xor, shl연산을 수행한다. 마지막에는 플래그 값을 push한다. 이러한 점을 봤을때, handler가 맞지 않을까 하는 생각을 하였다. 그리하여 handler가 맞는지를 확인하기 위하여 해당 위치부터 분기문을 계속 따라가 보았다.

.Fm$:004B0931                 jmp     loc_4A0CE3
	↓
.Fm$:004A0CE3                 mov     [ebp+4], edi
.Fm$:004A0CE6                 mov     [ebp+0], ebx
.Fm$:004A0CEA                 jmp     loc_4AD6FA
	↓
.Fm$:004AD6FA                 jmp     loc_4B4181
	↓
.Fm$:004B4181                 lea     edi, [esp+4+arg_78]
.Fm$:004B4188                 jmp     loc_4A8E30


loc_4B4181이 부분으로 복귀하였고 해당 위치는 처음에 살펴보았던 dispatcher의 앞 부분이다. 이 점을 보아 hander가 맞을 것이라 생각이 든다.
중간에 edi,ebx 값을 스택에 넣는 것을 볼 수 있다. 이 점은 hadler의 연산 결과를 저장하는 로직은 별도로 실행되는 것 같다.
코드르 보니 직접 loop에서 fetch-decode-execute가 실행되는 것이 아닌 hadler에서 복귀를 통해 loop로 동작하는 것을 알았다.

Noise Instructions

  • xch reg, reg
  • mov reg, reg
  • pushf/pop reg
  • lea reg, [reg+0]
  • clc/stc/cmc : 플래그 조작, 단독으론 영향 없음
  • nop
  • bt/btc/btr/bts/reg, val : 비트 검사/조작, 단독으로는 효과 없음
  • bswap reg : 바이트 순서 변경
  • sar/shr/rcl/rcr reg, val : 이동/회전 연산, 단독으로 의미 없음
  • movzx reg, reg : 상위 비트가 항상 0이면 무의미
  • test reg, val
  • cmp reg, reg
  • add/sub reg, 0

0개의 댓글