2015년 Samuel Chevet의 Inside VMProtect 발표를 분석한 내용이다.
https://webtv.univ-lille.fr/video/7566/inside-vmprotect
목차
1. Introduction
2. Internal
3. Analysis
4. VM Logic
5. Conclusion
Packer, Protector : 실행 파일의 섹션 내용을 암호화 하거나 압축하고, 이를 복호화 하거나 복원할 수 있는 코드를 추가하는 것
이 과정에서 다양한 안티 디버깅, 인티 VM 기법이 추가되지만 결국 오회할 수 있는 방법이 존재

원래 실행 파일에 있던 import table 항복들은 제거되고, API 호출을 위한 리디렉션 코드가 삽입된다.

남은 1 byte?
Fake push : 가짜 push 명령을 넣어 인자가 있는 것처럼 위장
Dead code : 의미 없는 명령어를 추가해 분석을 어렵게 함
비유하면 실행을 위한 새로운 CPU를 만드는 것과 같음

위 사진은 메시지박스를 띄우는 코드에 가상화를 적용했을 때의 예시이다. 당연히 가상화를 적용해도 원래의 동작을 수행한다. code를 덤프 하였을때 어떠한 코드인지 해석이 불가능 함을 볼 수 있다.
원래의 native code가 완전히 사라짐. 실행 중인 프로세스의 메모리를 덤프 하더라도 어떠한 동작을 하는지 이해할 수 없음
VMProtect가 사용하는 VM은 전부 RISC구조를 기반으로 한다.

스택 머신은 레지스터를 사용하지 않고 모든 산술 연산이 스택 위에서 이루어진다.

native code를 복원하고 싶다면, 이러한 스택 머신의 구조 자체를 제거하는 과정이 필요하다.
코드 가상화를 하기 전에 실행 중인 프로그램의 context를 먼저 저장해야 한다.
context는 가상 머신의 내부 구조체 안에 저장돼 있으며, 32/64bit에 따라 다르지만 보통 8/16개의 가상 레지스터에 이전 레지스터 값들을 저장한다.
VMProtect의 VM Context 안에 두 개의 저장 공간이 존재한다. Relocation Difference, ICE Layer가 저장이 된다.
산술 연산의 결과나 플래그 같은 것을 임시로 저장할 수 있는 temporal register가 존재한다.
VMProtect에서는 모든 VM context pointer를 EDI or RDI가 가리키도록 되어 있다.
RBP는 VM stack pinter로 사용이 되고,
RDI는 VM context structure로 사용 된다.
RBP가 RDI에 도달하게 되면, 전체 구조를 재조정하고 더 많은 공간을 확보한 뒤, 기존의 VM context와 VM stack을 복사해서 다시 저장한다.
이때 instruction pointer가 메모리를 읽는 방식은
이렇게 두 가지 방식으로 읽는다. 가상머신을 생성할 때, boolean 값을 랜덤으로 하여 읽는 방향을 정하는 것으로 추측
VMProtect의 bytecode는 암호화 되어 있을 수 있다. 코드 가상화의 시작 지점은 하나의 암호화 키에 의해 결정된다. 그렇기 때문에 10번째 명령어를 분석하고 싶어도 해당 키를 모른다면 복호화가 불가능하다.
이 암호화 키는 VM loop, opcode handler, operand에 의존한다.
해당 logic에서 키가 동적으로 갱신이 된다. 그렇기 때문에 임의의 위치에서 분석을 시작하는 것이 불가능 하다.
암호화 키는 EBX or RBX를 사용
논리 연산이나 산술 연산이 끝날 때마다 해당 핸들러는 연산 후에 CPU 플래그(EFLAGS/RFLAGS)를 push해서 VM stack에 저장한다.
이후 플래그들을 VM context에 저장하는 bytecode가 따라온다.
VM opcode는 쌍을 움직인다.
산술 연산 + 결과 플래그를 context에 저장하는 opcode
호스트에서 VM으로 진입할 때는 모든 호스트 레지스터들과 플래그들을 push한다.
VM block에 진입하게 되면 실행되는 로직이다.

여기서 SECURITY_CONSTANT는 가상화된 코드 블럭의 적합성 검증, 흐름 난독화, 암호화 키 파새 ㅇ등에 사용하는 임의값이다.
stack에 레지스터를 push하는 순서가 랜덤이기 때문에 VM context에 저장되는 레지스터의 순서는 랜덤이 되게 된다.

VM block이 끝나는 경우 2가지의 상황을 만나게 된다. 먼저 VM_EXIT을 만나는 경우는 VM이 종료된 경우로 모든 레지스터와 플래그를 pop하여 리턴한다.
만약 VM이 끝나지 않은 경우에는 SECURITY_CONSTANT를 암호화 하고 push한다. 그 다음 재배치 차이값을 push하고 다음 VM block으로 이동한다.
내부에서 사용하는 레지스터와 역할이다.
여기서는 이렇게 알려주었지만 VMProtect 버전 3을 보면 레지스터가 하는 역할을 랜덤으로 바꾸는 것을 확인할 수 있다. 참고용으로 보면 좋을 것 같다.
정적 분석도구들은 종종 버그가 많아 시간이 낭비되는 경우가 있다. 좋은 방법은 동적 분석을 먼저 하는 것이다.
이를 위해 intelligent code tracer를 만들어 분석을 하였다고 한다.

https://github.com/jjyg/metasm
발표자 Samuel Chevet이 만든 도구로 Ruby로 작성된 Open Source Framework다. 아래와 같은 기능을 한다.
각 명령어의 의미에 대한 설명이 포함되며, 중간언어를 사용하지 않고 직접 명령어의 의미를 표현한다.
하나의 opcode 핸들러 전체가 수행하는 의미를 분석 가능하다.
handler opcode 의미 분석을 하는 방법이다.
code_binding함수는 시작 주소와 종료 주소를 인자로 받는다. 이를 통해 코드 블록에 대하 자동으로 의미 분석을 한다.
먼저 레지스터나 VM stack에 간접적으로 접근하는 표현식들을 매핑한다.

이런식으로 opcode, vmkey, op_01처럼 레지스터와 스택의 특정 위치를 알기 쉽도록 매핑해 준다.
code binding을 수행하기 위해 VM의 start context를 설정하고 RSI/RBX를 초기화 한다.
현재 opcode에 해당하는 handler를 disassemble 하고, 그 명령어들의 의미를 계산한다.
상태 해석과 해석 결과를 바탕으로 계산해서, VM exit을 만날 때까지 루프를 계속 돌린다.
code binding의 종료 지점을 아래 방법을 사용하여 종료한다.
- Basic Block의 리스트를 추적 : 전체 가상 opcode가 실행할 basic block들을 모아서 추적하여 각 block의 의미를 분석한다.
- VM stack 최대값 기준으로 중지 : 가상 명령어들은 연산 결과를 VM stack에 저장한다. 특정 시점 이후에 VM stack이 더 이상 증가하지 않게 디고 이를 이용하여 종료지점을 파악한다.
- VM loop 복귀를 찾기 : 가상 명령어 실행이 끝나면 반드시 VM loop로 돌아가기 때문에 jmp, ret 명령어를 탐지하여 종료한다.
가상화된 코드는 코드를 직접 실행하지 않고 VM_ENTRY를 호출한다. 이때 첫 번째 인자로 bytecode pointer가 사용된다. 이 값에는 constant unfolding 이나 간단한 난독화가 적영돼 있을 수 있다.
앞서 얘기 했듯이 VM은 stack based language 이다.

코드를 보면 어떠한 읽는 것이 매우 어렵다. 해석이 용이를 위해 스택 머신의 특성을 제거하는 것이 좋다.
- push와 pop 명령을 단순 할당문으로 바꾸기
push 3 -> stack[sp++] = 3- AND, SUB, NOT 같은 opcode는 존재X
magic handler 내부에 NOR 게이트를 이용하여 NOT, AND, XOR 구현
VM 내부에는 포인터 하나와 크기 하나를 operand로 받아 체크섬 연산을 수행하는 로직이 존재한다. 이를 통해 파일의 무결성이나, VM 무결성을 확인한다.

하지만 쉽고 단순하여 우회가 가능하다고 한다.
가상 명령어 집합 목록을 완성하고 각 opcode handler의 의미를 모두 알아내면 VMProtect로 보호된 어떤 바이너리든지 disassemble이 가능하다.
항상 동일한 아키텍처(RISC + stack based)를 사용한다.
항상 암호화가 적용되지만 initial key는 유추 가능핟.
static disassembler를 만드는 것은 매우 어렵다. symbolic execution을 사용하거나 dynamic + static analysis을 해야 한다.