최근 malware analysis research를 하다보니...
이런 저런 공격방법들과 예제등을 볼 수 있었습니다.
신기했던 것들중 하나에 대해서 써보려 합니다.
일명, Red Pill.
- Matrix라는 영화에 나온 그 빨간약이 맞습니다. Red Pill이 하는 일을 소개하기에 앞서
그 배경부터 설명하는게 나을듯 싶군요.
- 일단, 요즘 malware analysis의 대부분을 VMware나 VirtualBox 같은 VMM
(Virtual Machine Manager/Monitor)위에서 하여서 보다 안전하고 확실해졌는데요...
- 그러다보니 이 사실을 모를리 없는 malware 제작자들도 머리를 썼습니다.
VMM안에서 실행중인지 아닌지 여부를 판단하여서 취하는 행동을 다르게 하는것이지요.
(즉, VM안에서는 착한척.. 밖으로 나오면 나쁜놈이 되는겁니다..)
이 Red Pill 이라는것은 이와같이 VMM 안에 있는지 아닌지 여부를 판단해주는 프로그램입니다.
2004년 11월에 invisiblethings.org 에 게재되었습니다.
소스코드는 정말 간단합니다..
/* VMM detector, based on SIDT trick
* written by joanna at invisiblethings.org
*
* should compile and run on any Intel based OS
*
* http://invisiblethings.org
*/
#include <stdio.h>
int main () {
unsigned char m[2+4], rpill[] = "\x0f\x01\x0d\x00\x00\x00\x00\xc3";
*((unsigned*)&rpill[3]) = (unsigned)m;
((void(*)())&rpill)();
printf ("idt base: %#x\n", *((unsigned*)&m[2]));
if (m[5]>0xd0) printf ("Inside Matrix!\n", m[5]);
else printf ("Not in Matrix.\n");
return 0;
}
하지만, Red Pill은 VMM Detection의 시작에 불과했습니다.
Red Pill에서는 SIDT (Store Interrupt Descriptor Table) 명령어만을 사용하지만, 향후 계속된 리서치에서는 LDT (Local Descriptor Table)을 이용한 디텍션 기법도 소개되었습니다 [paper].
Red Pill에 사용된 SIDT 기법의 원리를 간단히 설명하자면 다음과 같습니다:
SIDT 명령어는 IDTR(Interrupt Descriptor Table Register)의 정보를 저장하는데, IDTR 레지스터는 단 한개밖에 존재하지 않기 때문에, 두 개 이상의 OS (host OS + guest OS)가 동시에 돌아가기 위해서는 VMM이 guest OS의 IDTR을 host OS의 것과 겹치지 않도록 다른곳에 저장해야됩니다.
여기서 문제가 생기는 것인데, 이러한 separation 때문에 VMM 여부를 detect할 수 있게 됩니다.
VMWare에서의 relocated IDT의 주소는 0xffXXXXXX 이고, Virtual PC에서의 relocated IDT 주소는 0xe8XXXXXX 인데 반해, Native OS 에서는 Windows는 0x80FFFFFFF, Linux에서는 0xC0FFFFFF 이기 때문에..
IDT 주소를 받아온 다음 first byte을 0xd0과 비교해서, 크면 VM, 작거나 같으면 Real Machine으로 간주합니다.
이러한 SIDT 문제에 대해서 다룬 USENIX paper 도 읽어보시면 좋겠습니다.
이같은 VMM detection 방법들이 malware에 급격히 사용되면서, 정확한 분석이 어려워지자..
또다른 리서치 분야에서는 반대방향의 연구를 시작했습니다.
바로 Anti-detection-mechanism 인데요..
하지만 곧, 이마져도 bypass 하는 다양한 기법들이 소개되었습니다 [trapkit].
Trapkit에서 배포한 VMWare detection tool인 ScoopyNG 에서는 총 7가지의 테스트를 합니다.
이 중 한개라도 VMWare detected 라는 문구를 돌려받으면, 현재 VMM안속에서 프로세스가 실행중이라고
보시면 됩니다.
<ScoopyNG Source Code>
/* ScoopyNG - The VMware detection tool
* Version v1.0
*
* Tobias Klein, 2008
* www.trapkit.de
*/
#include <windows.h>
#include <excpt.h>
#include <stdio.h>
#define DEBUG 0
#define EndUserModeAddress (*(UINT_PTR*)0x7FFE02B4)
typedef LONG (NTAPI *NTSETLDTENTRIES)(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD);
unsigned long
get_idt_base (void)
{
unsigned char idtr[6];
unsigned long idt = 0;
_asm sidt idtr
idt = *((unsigned long *)&idtr[2]);
return (idt);
}
unsigned long
get_ldtr_base (void)
{
unsigned char ldtr[5] = "\xef\xbe\xad\xde";
unsigned long ldt = 0;
_asm sldt ldtr
ldt = *((unsigned long *)&ldtr[0]);
return (ldt);
}
unsigned long
get_gdt_base (void)
{
unsigned char gdtr[6];
unsigned long gdt = 0;
_asm sgdt gdtr
gdt = *((unsigned long *)&gdtr[2]);
return (gdt);
}
void
test1 (void)
{
unsigned int idt_base = 0;
idt_base = get_idt_base ();
printf ("[+] Test 1: IDT\n");
printf ("IDT base: 0x%x\n", idt_base);
if ((idt_base >> 24) == 0xff) {
printf ("Result : VMware detected\n\n");
return;
}
else {
printf ("Result : Native OS\n\n");
return;
}
}
void
test2 (void)
{
unsigned int ldt_base = 0;
ldt_base = get_ldtr_base ();
printf ("\n[+] Test 2: LDT\n");
printf ("LDT base: 0x%x\n", ldt_base);
if (ldt_base == 0xdead0000) {
printf ("Result : Native OS\n\n");
return;
}
else {
printf ("Result : VMware detected\n\n");
return;
}
}
void
test3 (void)
{
unsigned int gdt_base = 0;
gdt_base = get_gdt_base ();
printf ("\n[+] Test 3: GDT\n");
printf ("GDT base: 0x%x\n", gdt_base);
if ((gdt_base >> 24) == 0xff) {
printf ("Result : VMware detected\n\n");
return;
}
else {
printf ("Result : Native OS\n\n");
return;
}
}
// Alfredo Andr? Omella's (S21sec) STR technique
void
test4 (void)
{
unsigned char mem[4] = {0, 0, 0, 0};
__asm str mem;
printf ("\n[+] Test 4: STR\n");
printf ("STR base: 0x%02x%02x%02x%02x\n", mem[0], mem[1], mem[2], mem[3]);
if ((mem[0] == 0x00) && (mem[1] == 0x40))
printf ("Result : VMware detected\n\n");
else
printf ("Result : Native OS\n\n");
}
void
test5 (void)
{
unsigned int a, b;
__try {
__asm {
// save register values on the stack
push eax
push ebx
push ecx
push edx
// perform fingerprint
mov eax, 'VMXh' // VMware magic value (0x564D5868)
mov ecx, 0Ah // special version cmd (0x0a)
mov dx, 'VX' // special VMware I/O port (0x5658)
in eax, dx // special I/O cmd
mov a, ebx // data
mov b, ecx // data (eax gets also modified but will not be evaluated)
// restore register values from the stack
pop edx
pop ecx
pop ebx
pop eax
}
} __except (EXCEPTION_EXECUTE_HANDLER) {}
#if DEBUG == 1
printf ("\n [ a=%x ; b=%d ]\n\n", a, b);
#endif
printf ("\n[+] Test 5: VMware \"get version\" command\n");
if (a == 'VMXh') { // is the value equal to the VMware magic value?
printf ("Result : VMware detected\nVersion : ");
if (b == 1)
printf ("Express\n\n");
else if (b == 2)
printf ("ESX\n\n");
else if (b == 3)
printf ("GSX\n\n");
else if (b == 4)
printf ("Workstation\n\n");
else
printf ("unknown version\n\n");
}
else
printf ("Result : Native OS\n\n");
}
void
test6 (void)
{
unsigned int a = 0;
__try {
__asm {
// save register values on the stack
push eax
push ebx
push ecx
push edx
// perform fingerprint
mov eax, 'VMXh' // VMware magic value (0x564D5868)
mov ecx, 14h // get memory size command (0x14)
mov dx, 'VX' // special VMware I/O port (0x5658)
in eax, dx // special I/O cmd
mov a, eax // data
// restore register values from the stack
pop edx
pop ecx
pop ebx
pop eax
}
} __except (EXCEPTION_EXECUTE_HANDLER) {}
printf ("\n[+] Test 6: VMware \"get memory size\" command\n");
if (a > 0)
printf ("Result : VMware detected\n\n");
else
printf ("Result : Native OS\n\n");
}
int
test7_detect (LPEXCEPTION_POINTERS lpep)
{
printf ("\n[+] Test 7: VMware emulation mode\n");
if ((UINT_PTR)(lpep->ExceptionRecord->ExceptionAddress) > EndUserModeAddress)
printf ("Result : VMware detected (emulation mode detected)\n\n");
else
printf ("Result : Native OS or VMware without emulation mode\n"
" (enabled acceleration)\n\n");
return (EXCEPTION_EXECUTE_HANDLER);
}
void __declspec(naked)
test7_switchcs ()
{
__asm {
pop eax
push 0x000F
push eax
retf
}
}
// Derek Soeder's (eEye Digital Security) VMware emulation test
void
test7 (void)
{
NTSETLDTENTRIES ZwSetLdtEntries;
LDT_ENTRY csdesc;
ZwSetLdtEntries = (NTSETLDTENTRIES)GetProcAddress (GetModuleHandle ("ntdll.dll"), "ZwSetLdtEntries");
memset (&csdesc, 0, sizeof (csdesc));
csdesc.LimitLow = (WORD)(EndUserModeAddress >> 12);
csdesc.HighWord.Bytes.Flags1 = 0xFA;
csdesc.HighWord.Bytes.Flags2 = 0xC0 | ((EndUserModeAddress >> 28) & 0x0F);
ZwSetLdtEntries (0x000F, ((DWORD*)&csdesc)[0], ((DWORD*)&csdesc)[1], 0, 0, 0);
__try {
test7_switchcs();
__asm {
or eax, -1
jmp eax
}
}
__except (test7_detect (GetExceptionInformation())) { }
}
int
main (void)
{
printf ("\n\n####################################################\n");
printf (":: ScoopyNG - The VMware Detection Tool ::\n");
printf (":: Windows version v1.0 ::\n\n");
test1 ();
test2 ();
test3 ();
test4 ();
test5 ();
test6 ();
test7 ();
printf (":: tk, 2008 ::\n");
printf (":: [ www.trapkit.de ] ::\n");
printf ("####################################################\n\n");
return 0;
}
<ScoopyNG를 이용한 VMM Detection>
1. VMWare (windows xp sp3)에서 돌렸을때
2. Native OS (Windows 7 build 7201)에서 돌렸을때


rss
그렇죠?ㅎㅎ
참 신기한게 많아요 이세상엔..
똑똑한사람도..ㅠ
원리를 거의 이해하지는 못하겠지만 프로그램 자체가 식별할 수 없게 발전되어야 할듯
계속 리서치가 돌아가고 있긴 하지만 ㅋ
뭔가 혁신적인 모델이 나오기전엔..ㅋㅋ
끨끨..이거 이용해서 나중에 어플 하나 만들어봐야지 ㅋ