Cheat Engine DBVM

Research
ETC

라온화이트햇 핵심연구팀 김재성

DBVM?


DBVM은 Cheat Engine 개발자 DarkByte가 개발한 VMM(Virtual Machine Monitor)입니다.

범용 게임 해킹도구로 유명한 Cheat Engine을 적극적으로 활용하시는 분들이 있다면 DBVM도 한번 쯤 들어봤거나 사용해보았을 것이라 생각합니다.

DBVM에서 사용하는 기술은 VMware, Hyper-V 등의 Hypervisor에서 사용하는 Intel CPU의 VT-x 하드웨어 가상화 기술과 동일합니다. 하지만 DBVM은 VT-x 기술을 다른 Hypervisor와는 다르게 기존에 작동하던 OS를 Guest로 올리고 Guest OS에서 발생하는 모든 인터럽트가 VMM인 DBVM에게 제어권으로 넘어가도록 하여 OS를 조작하는 용도로 활용하고 있습니다.

본문에서는 DBVM이 어떻게 작동하고 어떤식으로 활용될 수 있는지를 서술하였습니다.

DBVM 활성화와 일반적인 사용방법


Cheat Engine을 통해 DBVM을 활성화하는 방법과 일반적으로 사용되는 방법은 아래와 같습니다.

/assets/2020-10-01/2010sung.png

Cheat Engine 메인화면의 우측 상단 아이콘을 마우스 오른쪽 클릭하면 아래와 같이 Cheat Engine에 대한 정보가 표시됩니다.

/assets/2020-10-01/2010sung1.png

여기서 Intel VT-x가 활성화된 PC라면 Your system supports DBVM 문구가 보일 것입니다. AMD CPU의 SVM을 활성화해도 DBVM을 사용할 수 있지만 AMD에 한하여 일부 기능제한이 있습니다.

VMware 환경의 Windows에서는 VM옵션에서 VT-x를 에뮬레이트하는 기능을 활성화하여 DBVM을 테스트해볼 수 있습니다.

/assets/2020-10-01/2010sung2.png

Your system supports DBVM 문구를 마우스 오른쪽 버튼을 클릭하면 위 화면이 표시됩니다. CPU 버튼은 시스템의 논리적 코어 갯수만큼 표시되고 모든 버튼을 한번씩 클릭해야 DBVM이 활성화됩니다.

/assets/2020-10-01/2010sung3.png

위와 같은 화면이 보이면 DBVM이 정상적으로 활성화된 것입니다.

/assets/2020-10-01/2010sung4.png

DBVM을 활성화한 이후에는 Cheat Engine에서 사용할 수 있는 Debugger method가 추가됩니다.

보통은 Vectored Exception Handler를 이용한 Use VEH Debugger 옵션을 사용해도 Themida를 비롯한 Anti-Debugging이 걸린 프로그램들을 쉽게 우회할 수 있지만 등록된 예외 핸들러를 지우거나 CPU에 설정된 DRx 레지스터를 보고 디버거를 탐지하는 방법이 여전히 존재합니다.

하지만 DBVM은 이러한 흔적을 남기지 않고 프로세스를 디버깅할 수 있으며 Cheat Engine 사용자들은 대부분 이러한 목적으로 DBVM을 활용하고 있습니다.

또한 기술적인 특성 때문에 커널공간에 할당된 페이지도 제한적인 Breakpoint를 걸 수 있는데, 어떻게 흔적을 남기지 않고 이러한 디버깅이 가능한지에 대한 내용을 아래에서 다루겠습니다.

DBVM Debugging


/assets/2020-10-01/2010sung5.png

DBVM이 VMM으로 올라간 이후에는 Guest OS에서 특정한 명령어가 실행되거나 특정 조건에서 VM Exit 이벤트가 발생하게 됩니다. Exit가 발생할 수 있는 이유는 여러가지가 있는데 아래는 그 중 일부입니다.

...
3: INIT signal 발생
16: RDTSC instruction을 Guest OS에서 실행
18: VMCALL instruction을 Guest OS에서 실행
48. EPT Violation 발생
...

VM Exit가 발생하면 그 시점에서 DBVM에게 제어권이 전달되어 Guest를 제어하게 됩니다. DBVM Debugging은 VM Exit reason 중에서 EPT Violation을 이용하여 구현됩니다.

EPT는 Extended Page Table의 약자로, 인텔에서 구현한 Second Level Address Translation(SLAT) 기술입니다. Guest도 Host처럼 물리주소와 가상주소 변환을 위한 페이지 테이블 참조가 필요한데 주소 변환 과정을 Guest에서도 빠르게 처리할 수 있도록 하드웨어 레벨에서 SLAT를 지원하고 있습니다.

EPT Violation은 Guest OS에서 발생하는 Page fault를 말하며 Page fault가 발생했을 때 DBVM에서 해당 페이지 영역이 사용자가 Breakpoint를 걸어놓은 주소가 포함된 페이지일 경우 Guest의 CPU 레지스터 조작 등이 가능하도록 구현되어있습니다.

Breakpoint 목록은 DBVM에서 정의한 ChangeRegBPEntry, EPTWatchEntry 구조체 등으로 관리하고 있고 자세한 코드는 https://github.com/cheat-engine/cheat-engine/blob/master/dbvm/vmm/vmeventhandler.c에서 확인할 수 있습니다.

typedef struct
{
  int Active;
  PCloakedPageData cloakdata;
  QWORD PhysicalAddress;
  unsigned char originalbyte;
  CHANGEREGONBPINFO changereginfo;
} ChangeRegBPEntry, *PChangeRegBPEntry;

typedef struct
{
  QWORD PhysicalAddress;
  int Size;
  int Type; //0=write, 1=access,2=execute
  DWORD Options;
  int Active;
  int CopyInProgress; //if 1 events will be ignored
  PPageEventListDescriptor Log;
} EPTWatchEntry, *PEPTWatchEntry;

ChangeRegBPEntry, EPTWatchEntry의 크기는 필요할 경우 realloc되기 때문에 Breakpoint를 걸 수 있는 수의 제약은 없지만 그만큼 오버헤드가 발생할 수 있습니다.

구조체의 정보로 물리 주소(PhysicalAddress)를 받으므로 유저/커널공간에 대한 제약 없이 커널 메모리도 Breakpoint를 걸 수 있지만 일부 커널주소에서는 BSOD가 발생할 수 있습니다.

아래는 임의의 커널 공간을 할당하고 해당 주소로 코드실행하였을 때 DBVM에 의해 register가 바뀌는지 테스트하는 Auto Assemble 코드입니다.

//Kernel Tools -> Allocate nonpaged memory -> FFFF8086A6FF1000
FFFF8086A6FF1000:
mov rax, 123
mov [FFFF8086A6FF1100], rax
ret

{$lua}
paddr = dbk_getPhysicalAddress(FFFF8086A6FF100A)
dbvm_changeregonbp(paddr, {newRAX=456})
dbk_executeKernelMemory(FFFF8086A6FF1000)

0xFFFF8086A6FF1000 주소로 코드를 실행하면 0xFFFF8086A6FF1100의 값은 0x123이 되어야하지만 DBVM에 의해 rax가 0x456로 변경되어 0x456가 메모리에 쓰여집니다.

System Timestamp 조작


Guest OS에서 rdtsc(Read Time Stamp Counter) 명령어가 호출되면 VM Exit 이벤트가 발생하고 이를 이용하여 rdtsc의 반환값(Timestamp)을 조작할 수 있습니다. 실제로 DBVM에서도 Speed hack으로 구현되어있고 이를 쉽게 컨트롤 할 수 있도록 Cheat Engine 7.0 버전부터 Lua 함수로 제공하고있습니다.

Windows는 rdtsc에 의존하는 여러가지 Win32 API를 제공하고있는데 Timestamp가 조작된다면 대부분의 응용 프로그램 및 게임들에 영향을 미칠 것입니다.

int handle_rdtsc(pcpuinfo currentcpuinfo, VMRegisters *vmregisters)
{
  QWORD t;
  double s;
  QWORD lTSC=lowestTSC;
  QWORD realtime;

  if (lTSC<currentcpuinfo->lowestTSC)
    lTSC=currentcpuinfo->lowestTSC;

  realtime=_rdtsc();

  if (currentcpuinfo->lowestTSC==0)
    currentcpuinfo->lowestTSC=realtime;

  if (useSpeedhack)
    t=(realtime-speedhackInitialTime)*speedhackSpeed+speedhackInitialOffset;
  else
    t=realtime; //speedhack disabled ATM globalTSC+s;

  if (lTSC==0)
    lTSC=t;

  if (t<lTSC)
    lTSC=t; //overflow happened... (bp here)
  ...
}

Guest의 rdtsc 핸들러는 handle_rdtsc 함수에서 구현되어있고 useSpeedhack 플래그가 켜져있으면 지정해놓은 값으로 Timestamp의 속도를 조절합니다.

아래는 DBVM speedhack을 사용하는 예시입니다. (default speed=1.0) (Cheat Engine 7.1버전부터는 dbvm_enableTSCHook함수를 추가로 호출해야합니다.)

dbvm_enableTSCHook();
dbvm_speedhack_setSpeed(1.0);

DBVM과 Guest OS의 통신


Cheat Engine은 커널 드라이버와 DeviceIoControl API함수를 호출하여 통신하지만 DBVM은 Guest OS에서 vmcall instruction을 호출하여 DBVM에게 명령을 전달합니다.

그렇기 때문에 아래처럼 직접 DBVM과 통신하는 코드를 작성할 수 있습니다.

alloc(vmcode,100)
alloc(vmcallinfo,100)
alloc(result,8)

vmcallinfo:
dd #12 //structsize
dd fedcba98 //password2
dd 0 //command

vmcode:
sub rsp, 100
mov rax, vmcallinfo
mov rdx, 76543210 //password1
vmcall
mov [result], rax
add rsp, 100
ret

createthread(vmcode)

rax, rdx 레지스터에 password 및 command 정보를 넣어 vmcall을 호출하면 rax값으로 그 결과를 반환합니다. 위 코드는 DBVM의 버전을 확인하는 코드로 실행하면 0xce00000e를 반환합니다.

만약 DBVM이 활성화되어있지 않거나 password가 올바르지 않으면 예외가 발생합니다.

password1, 2는 기본값으로 각각 0x76543210, 0xfedcba98로 상수 정의되어있는데 dbvm을 재컴파일하거나 VMCALL_CHANGEPASSWORD command를 호출하여 동적으로 패스워드를 변경할 수 있습니다.

대부분의 Cheat Engine 사용자는 DBVM을 사용할 때 password를 변경하지 않고 사용하므로 이러한 특성을 이용해서 보안솔루션에서 DBVM 명령어를 직접 호출을 시도하여 DBVM을 탐지하는 방법도 존재합니다.

또한, password만 정확하면 낮은 권한의 코드에서도 OS보다 더 높은 권한으로 물리메모리를 쓰는 등 시스템을 완전히 제어할 수 있기 때문에 악성코드가 DBVM을 악용할 수 없도록 password를 변경하는 것이 좋습니다.

아래는 DBVM에서 사용 가능한 명령어 번호로, 전체 명령어 번호는 https://github.com/cheat-engine/cheat-engine/blob/master/dbvm/vmm/vmcall.h에서 확인할 수 있습니다.

#define VMCALL_GETVERSION 0
#define VMCALL_CHANGEPASSWORD 1
#define VMCALL_READ_PHYSICAL_MEMORY 3
#define VMCALL_WRITE_PHYSICAL_MEMORY 4
#define VMCALL_REDIRECTINT1 9
#define VMCALL_INT1REDIRECTED 10
#define VMCALL_CHANGESELECTORS 12
#define VMCALL_BLOCK_INTERRUPTS 13
#define VMCALL_RESTORE_INTERRUPTS 14

#define VMCALL_REGISTER_CR3_EDIT_CALLBACK 16
#define VMCALL_RETURN_FROM_CR3_EDIT_CALLBACK 17
#define VMCALL_GETCR0 18
#define VMCALL_GETCR3 19
#define VMCALL_GETCR4 20
#define VMCALL_RAISEPRIVILEGE 21
#define VMCALL_REDIRECTINT14 22
#define VMCALL_INT14REDIRECTED 23
...

DBVM Bootloader


DBVM은 Cheat Engine에서 직접 로드하는 방법도 있지만 DBVM Bootloader를 활용하여 OS보다 먼저 로드되게 할 수 있습니다. 이 경우에는 Windows 뿐만 아니라 Ubuntu 등의 다른 OS에서도 DBVM을 사용할 수 있습니다.

DBVM 최신 부트로더를 컴파일 하기위해 https://github.com/cheat-engine/cheat-engine/dbvm/을 clone하고 make install 합니다.

/assets/2020-10-01/2010sung6.png

컴파일이 정상적으로 이루어지면 vmdisk.img 파일이 생성됩니다.

/assets/2020-10-01/2010sung7.png

/assets/2020-10-01/2010sung8.png

이 이미지를 VMware에서 간단하게 테스트하려면 Processors에서 VT-x/EPT 체크박스를 활성화하고 Floppy disk로 컴파일한 이미지를 선택, 부팅하면됩니다.

/assets/2020-10-01/2010sung9.png

정상적으로 부팅을 시작하면 위와 같은 화면이 표시되고 0을 누르면 DBVM이 활성화한 뒤 OS가 부팅됩니다. https://github.com/cheat-engine/cheat-engine/issues/521에서 Cheat Engine 개발자가 답변한 방법대로 rEFInd 부트로더 매니저를 이용하면 EFI를 사용중인 시스템에서도 DBVM 부팅을 할 수 있습니다.

Reference


https://en.wikipedia.org/wiki/Second_Level_Address_Translation https://github.com/cheat-engine/cheat-engine https://cheatengine.org/aboutdbvm.php https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3c-part-3-manual.pdf https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce-1961.html