Computer Emergency Response Center

Matrisdən yayınma metodları - debugger və virtual maşınların aşkarlanması

"Matrix" filmində ki, səhnələrin birində Morpheus və Neo arasında belə bir dialoq keçirdi - "Səndə hamı kimi qul olaraq doğulmusan. Sən elə bir həbsxanadasan ki, ona toxunmaq, dadmaq və qoxlamaq mümkün deyil. Beyninin içində bir həbsxana var. Təəssüf ki, heç kimə Matrisin nə olduğunu söyləmək mümkün deyil. Bunu özün görməlisən. Bu sənin son şansındır. Bundan sonra geriyə dönüş olmayacaq. Mavi həbi içsən, bu hekayə bitəcək, yatağında oyanacaqsan və nəyə istəyirsən ona inanacaqsan. Qırmızı həbi qəbul etsən, möcüzələr ölkəsində qalacaqsan. Mən də sənə dovşan dəliyinin hara getdiyini göstərəcəyəm. Unutma... "sənə söz verdiyim tək şey həqiqətdir, başqa heç nə..."

Morpheus əslində Neo-nun virtual dünyada yaşadığını və ona reallığı göstərməyə hazır olduğunu deyirdi. Zərərvericilərdə virtual mühitdə olduqlarını aşkarlamaları üçün müxtəlif metodlardan istifadə edirlər. Yeganə fərq qırmızı həb əvəzinə kodlardan istifadə etmələridir.

Giriş

Bu məqalədə sizlərə zərərverici proqram təminat tərtibatçılarının üzərində tez-tez düşündüyü və araşdırma apardıqları mövzulardan biri olan matris aşkarlama və onlardan yayınma metodları haqqında bəhs edəcəyəm. İlk öncə matrisin nə olduğu sualına cavab verək.

Matris deyilən zaman zərərverici proqram təminatlarının analizi zamanı istifadə edilən köməkçi alət və ya proqram təminatları nəzərdə tutulur.

Bura

  1. Virtual maşınlar
  2. Debug alətləri

misal olaraq göstərə bilərik. Məhz bu tipli alətlərin dəstəyi ilə zərərli proqram analitikləri zərərli proqram təminatlarının analizinı həyata keçirirlər. Belə olan halda əlbətdə zərərvericilərin hədəf sistemə yoluxduqdan sonra fəaliyyətini davam etdirmələri və gizli qalmaları üçün attıqları ilk addımlardan biri matris içərisində olub olmadığını aşkar etməkdir. Bu məqalədə zərərvericilərin matrisi aşkarlamaq üçün hansı metodlardan istifadə edirlər bunlar haqqında praktiki məlumat veriləcəkdir. Məqalə 2 hissədən ibarətdir.

  1. Virtual maşınları aşkarlamaq üçün istifadə edilən metodlar
  2. Debug alətlərini aşkar etmək üçün istifadə edilən metodlardan

Məqalədə yalnız bu tipli əməliyyatlar zamanı istifadə edilən ən populyar metodlar haqqında məlumat veriləcək.

Virtual maşınların aşkarlanması

Virtual maşınlar və ya virtual mühitlər zərərvericilərin analizi zamanı geniş istifadə edilən proqram təminatlarıdır. Bu baxımdan zərərvericilər üçün ilk hədəflər arasında yer alırlar. Virtual maşınların əsas üstünlüyü əməliyyat sistemlərindən müstəqil olaraq onları təqlid edə bilmə qabiliyyətləridir. Maşın içərisində icra edilən hər hansı bir proqram təminatı əsas əməliyyat sistemi ilə əlaqəsi tam olaraq kəsilmiş və məruz qalınan dəyişikliklər yalnız virtual maşın içərisində fəaliyyət göstərən əməliyyat sisteminə təsir edir. Müxtəlif tipli virtualizasiya texnologiyaları olmasına baxmayaraq məqaləmizdə “Software Virtualization” adı verilən proqram təminatı səviyyəsində olan virtual maşınlar haqqında məlumat veriləcəkdir. Zərərverici analizi zamanı istifadə edilən 2 önəmli virtualizasiya proqram təminatı mövcuddur. Oracle VirtualBox və Vmware. Oxuduğunuz məqalədə test virtual mühiti üçün VirtualBox proqram təminatı istifadə ediləcəkdir.

Virtual maşının əlavənin quraşdırdığı proseslər üzərindən aşkarlanması

Proqram təminatı səviyyəsində işləyən virtual maşınların normal fəaliyyət göstərə bilməsi üçün istifadə edilən “Guest Tools” və ya “Guest Additions” adı verilən xüsusi proqram təminatı paketləri mövcuddur. Bu paketlərin önəmi virtual maşının performansının yüksəldilməsi, virtual maşın içərisində fəaliyyət göstərən sistem ilə əsas sistem arasında əlaqə və s. ibarətdir. Ətraflı: https://www.virtualbox.org/manual/ch04.html#guestadd-intro.
Bu paketlərin sistemə quraşdırılmasından sonra sistemdə virtual maşına aid izlər formalaşmağa başlayır. Bunlardan ilki sistemdə fəaliyyət göstərən proseslər arasında əlavələrə aid proseslərdir. Zərərverici məhz sistem prosesləri arasında sözü gedən əlavələrə aid proses adlarını axtararaq matris içərisində olub olmadığını aşkar edə bilir.

Prosesi aşkarlamaq üçün kiçik kod bloku:

HANDLE hProcessSnapshot = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe32 = {0};
char* vmp_name = "VBoxTray.exe";

hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnapshot == INVALID_HANDLE_VALUE)
{
    return -1;
}

pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hProcessSnapshot, &pe32))
{
    return -1;
}

do{
    
    if (strcmp(vmp_name, pe32.szExeFile) == 0)
    {
        printf("VirtualBox detected.");
        break;
    }
    
}while(Process32Next(hProcessSnapshot, &pe32));

Virtual maşının, əlavənin quraşdırdığı fayllar üzərindən aşkarlanması

Növbəti metod olaraq əlavənin sistemə quraşdırdığı faylları aşkarlayaraq matris içərisində olub olmadığını təyin etmək mümkündür. Bunun üçün istifadə edilən metodlardan biri Process Monitor tərzi alətlərin köməkliyi ilə əlavə virtual sistemə quraşdırılarkən fayl aktivliklərini izləmək  və sistemə hansı faylları quraşdırdığını təyin etməkdir. Misal olaraq VirtualBox əlavəsi sistemə quraşdırılarkən “Program Files” qovluğunda Oracle adında qovluq yaratması və içərisinə əlavənin fəaliyyəti üçün kritik faylları yükləməsidir. Zərərverici bu qovluq və ya faylların mövcüdluğunu yoxlayaraq matris içərisində olub olmadığını təyin edə bilər.

Bir digəri metod isə sistem sürücü fayllarının mövcud olduğu qovluq (“%windir%\system32\drivers”) içərisində əlavəya aid sürücü fayllarını axtarmaqdır.

>>> f = open("c:\\Windows\\system32\\drivers\\VBoxGuest.sys","rb")
>>> f.close()

Virtual maşının sistemdə fəaliyyət göstərən sürücülər (device drivers) üzərindən aşkarlanması

VirtualBox əlavəsi fəaliyyəti üçün sistem sürücülərindən istifadə edir. Zərərverici bu sürücülərin siyahısını alaraq bunlar üzərindən axtarış edərək virtual maşında olub olmadığını təyin edə bilər.

LPVOID lpImageBase[1024] = {0};
char lpFilename[1024] = {0};
DWORD nSize = 0;
char* vmd = "VBoxGuest.sys";

DWORD cb = sizeof(lpImageBase);
DWORD lpcbNeeded = 0;
DWORD i = 0;


if (!EnumDeviceDrivers(&lpImageBase[0], cb, &lpcbNeeded))
{
    return -1;
}

for(i=0; i < (lpcbNeeded / sizeof(lpImageBase[0])); i++)
{
    GetDeviceDriverBaseNameA(lpImageBase[i], lpFilename, sizeof(lpFilename) / sizeof(lpFilename[0]) );
    if (strcmp(lpFilename, vmd) == 0)
    {
        printf("VirtualBox detected!");
    }
}

Virtual maşının sistemdə fəaliyyət göstərən əlavənin servisi üzərindən aşkar edilməsi

VirtualBox əlavəsi sistemə bəzi servislər quraşdırır. Bunlardan biri “VboxService” adında servisdir.

Sistemdə fəaliyyət göstərən servislər arasında “VboxService” servisini aşkarlayan kiçik kod bloku:

SC_HANDLE hSCManager = NULL;
SC_HANDLE hService = NULL;
hSCManager = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
if (hSCManager == NULL)
{
    printf("sd");
    return -1;
}

hService = OpenServiceA(hSCManager, "VBoxService", SERVICE_QUERY_STATUS );
if (hService == NULL)
{
    return -1;
}

printf("VirtualBox detected!");

Virtual maşınların CPUID təlimatının (instruction) üzərindən aşkar edilməsi

CPUID intel prosesorları üçün identifikasiya və xüsusiyyətləri haqqında məlumat almaq üçün istifadə edilən təlimatdir.

Intel bu haqda: Returns processor identification and feature information to the EAX, EBX, ECX, and EDX registers, as determined by input entered in EAX (in some cases, ECX as well). Təlimat hansı məlumatları götürəcəyini (oxuyacağını) EAX registeri üzərindən öyrənir. CPU haqqında məlumat almaq üçün bizə 0H dəyəri lazımdır.

Misal olaraq VMWARE virtual maşınında bu təlimat icra edilərsə geriyə “VMwareVMware” dəyərini qaytarılır. Hal hazırda test olaraq istifadə etdiyim virtual maşında (VirtualBox) kiçik bir kod bloku ilə hansı məlumatların qaytarıldığına baxaq.

__asm{
    mov eax, 00000000h;
    cpuid;
    nop;
    nop;
    nop;
}

EBX = 756E6547  = uneG
ECX = 6C65746E = letn
EDX = 49656E69 = Ieni

WMI üzərindən virtual maşının aşkarlanması

Virtual maşın içərisində mövcud olan sürücülərə baxsanız burada virtual maşınlara aid bəzi kritik məlumatları görə bilərsiniz. Misal olaraq hal hazırda istifadə edilən VirtualBox maşınında:

Zərərverici WMI sorğu göndərərək matris içərisində olub olmadığını yoxlaya bilər.

BSTR resource = SysAllocString(L"ROOT\\CIMV2");
BSTR language = SysAllocString(L"WQL");
BSTR query    = SysAllocString(L"select * from Win32_DiskDrive");


hr = CoInitializeEx(0, COINIT_MULTITHREADED);
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

hr = CoCreateInstance(&CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, &IID_IWbemLocator, (LPVOID *) &locator);
hr = locator->lpVtbl->ConnectServer(locator, resource, NULL, NULL, NULL, 0, NULL, NULL, &services);


hr = services->lpVtbl->ExecQuery(services, language, query, WBEM_FLAG_BIDIRECTIONAL, NULL, &results);
IWbemClassObject *result = NULL;
ULONG returnedCount = 0;
while((hr = results->lpVtbl->Next(results, WBEM_INFINITE, 1, &result, &returnedCount)) == S_OK) {
		VARIANT name;
		VARIANT speed;
		hr = result->lpVtbl->Get(result, L"Model", 0, &name, 0, 0);
		}

Virtual maşının windows reyestr (registry) üzərindən aşkar edilməsi

Virtual maşın aləvəsinin virtual sistem üzərində buraxdığı növbəti izləri reyestr üzərində görmək mümkündür.

>>> import winreg
>>> hKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "Software\\Oracle\\VirtualBox Guest Additions", winreg.KEY_READ)

Aktiv pəncərələr üzərindən virtual maşının aşkarlanması

Sistemdə aktiv olan pəncərələr üzərindən virtual maşınları aşkar etmək mümkündür. Bu pəncərələrin siyahısı götürərək və ya spesifik pəncərə adını FindWindow funksiyasının dəstəyi ilə qarşı tərəf matris içərisində olub olmadığını test edə bilər. Misal olaraq VirtualBox aləvəsinin windows tray menyusunda VboxTrayToolWnd adında pəncərəsi mövcuddur.

>>> from ctypes import *
>>> if windll.user32.FindWindowW(None, "VBoxTrayToolWnd"):
...     print("VirtualBox detected!")
...
VirtualBox detected!

Virtual maşınların aşkarlanması haqqında bu qədər. Məqalənin başında qeyd edildiyi kimi yazıda bütün metodlar haqqında deyil, matrisi təyin etmək üçün geniş yayılan ümumi metodlar haqqında  məlumat verilmişdir. Daha ətraflı məlumat almaq üçün istinadlar bölməsinə müraciət edin.

Debuggerlərin aşkarlanması

Debuggerlər xüsusi proqram təminatlarıdırlar hansı ki, proqram təminatlarının testlərini aparmağa və onlarda mövcud olan boşluqları aşkarlamağa, müşahidə etməyə imkan yaradırlar. Debuggerlər istifadəçiyə dinamik olaraq kodları icra etməyə istənilən an icra fəaliyyətini dayandırmağa, real vaxt rejimində kodlar üzərində dəyişiklik etməyə icazə verirlər. Bununla yanaşı olaraq debugger zərərverici proqram təminatlarının analizi zamanıda geniş istifadə edilən alətlərdəndir. Ətraflı məlumat üçün: https://en.wikipedia.org/wiki/Debugger

IsDebuggerPresent funksiyası üzərindən debuggerin aşkarlanması

IsDebuggerPresent Windows applikasiya proqramlaşdırma interfeysi (bundan sonra win32) tərəfindən proqramın debug edilib edilmədiyini aşkarlamaq üçün təqdim edilən funksiyadır. 

if (IsDebuggerPresent())
{
	printf("Debugger detected!");
}

CheckRemoteDebuggerPresent funksiyası üzərindən debuggerin aşkarlanması

Demək olar ki, IsDebuggerPresent funksiyası ilə oxşar əməliyyatı yerinə yetirir. Əsas fərqi isə IsDebuggerPresent funksiyası yalnız cari prosesin debug edilib edilmədiyini yoxlayarkən CheckRemoteDebuggerPresent funksiyası fəaliyyət göstərən digər proseslərində debug edilib edilmədiyini sorğulayır.

hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, 66);
if (hProcess)
{
	CheckRemoteDebuggerPresent(hProcess, &BeingDebugged);
	if (BeingDebugged)
	{
		printf("Debugger detected!");
	}
}

EnumWindows və ya FindWindow funksiyası üzərindən debuggerin aşkarlanması

Virtual maşınların aşkarlanması bölməsində (1.7) aktiv pəncərələr üzərindən virtual maşınların aşkarlanması haqqında məlumat almışdınız. Eyni üsul ilə debugger alətlərini aşkarlamaq mümkündür.

>>> if windll.user32.FindWindowW(None, "OllyDbg"):
...     print("Debugger detected!")
...
Debugger detected!

Debuggerin birbaşa PEB (Process Environment Block) üzərindən aşkarlanması

IsDebuggerPresent windows applikasiya interfeysinin təqdim etdiyi bir funksiyadır. Lakin bu funksiya arxa fonda PEB üzərindən prosesin debug edilib edilmədiyini məlumatını götürür.

IsDebuggerPresent funksiyasını aşkarlamaq üçün bəzən zərərvericilər win32 üzərindən deyil birbaşa assembly təlimatları ilə PEB üzərindən debugeri aşkarlamağa çalışır.

NtQueryInformationProcess funksiyası üzərindən debuggerin aşkarlanması

Əgər CheckRemoteDebuggerPresent funksiyasını diqqət ilə analiz etsək bu funksiyanın arxa fonda NtQueryInformationProcess funksiyasını çağırdığını görə bilərik.

Nt* funksiyaları ntdll.dll kitabxanası içərisində mövcud olan funksiyalar toplusudur. Microsoft Nt* funksiyalarını kritik funksiyalar olaraq dəyərləndirir və digər funksiyalara nəzərən bunlar haqqında yarımçıq, bəziləri haqqında isə ümumiyətlə məlumat verilmir. Məsələn NtQueryInformationProcess funksiyası haqqında məlumat verilsə də bütün parametrlər haqqında məlumatlar yoxdur. Yuxarıda ki, şəkildə NtQueryInformationProcess funksiyasına göndərilən parametrlərdən birinin 0x7 olduğunu görürsünüz. Bu parametr (ProcessInformationClass) ProcessDebugPort olaraq göstərilmişdir. Bu parametr ilə çağrılan funkisya əgər prosesə bağlı debug port mövcud olarsa bu portu geri qaytarır.

Microsoft bu haqda:

ProcessDebugPort (0x7) - Retrieves a DWORD_PTR value that is the port number of the debugger for the process. A nonzero value indicates that the process is being run under the control of a ring 3 debugger. Use the CheckRemoteDebuggerPresent or IsDebuggerPresent function.

>>> from ctypes import *
>>>
>>> debugport = c_ulong()
>>> ret  =c_ulong()
>>> windll.ntdll.NtQueryInformationProcess(windll.kernel32.GetCurrentProcess(), 0x7, byref(debugport), 4, byref(ret))
0
>>> debugport
c_ulong(4294967295)

İstisna üzərindən debuggerin aşkarlanması

Windows əməliyyat sistemində debuggerləri aşkarlamağın bir başqa yolu isə istinalardır. İstisna Windows əməliyyat sistemində baş verən xüsusi hadisələrdir. Əməliyyat sistemi bu istinaları işləmək (handling) üçün Structured Exception Handle adı verilən mexanizmdən istifadə edir. Daha ətraflı: https://learn.microsoft.com/en-us/windows/win32/debug/about-structured-exception-handling

Microsoft C/C++ kompilyatoru __try, __except, __finally açar sözlərini ilə istifadəçilərə istinalar ilə işləmək üçün şərait yaradır. Bu başlıq altında yalnız __try__except istifadə ediləcəkdir. Konsept isə belədir. Problem yaratma potensialı olan  kodlar __try bloku içərisində yazılır və baş verəcək biləcək istina ilə bağlı işlər __except bloku içərisində həll edilir. INT 3 (0xCC) single byte breakpoint olaraq da bilinən, proqramın axışı zamanı istisna yaradan xüsusi təlimatdır. Proqram tərtibatçısı kod və ya debuggerin köməyi ilə məhz bu təlimat üzərindən proqramın fəaliyyətini lazım olan nöqtədə dayandıra bilər. Əgər proqram debugger tərəfindən debug edilərsə bu təlimatın köməyi ilə debuggeri aşkarlamaq mümkündür. Bunun üçün aşağıda ki, kod blokundan istifadə edilə bilər.

BOOL CheckDebugger(void)
{
    __try{
        __asm{
            int 3;
        }
    }__except(EXCEPTION_EXECUTE_HANDLER)
    {
        return FALSE;
    }
    
    return TRUE;
}

int main(void)
{
    
    if (CheckDebugger())
    {
        MessageBoxA(NULL, "Debugger detected!", "mrl.cert.gov.az", 64);
        return -1;
    }
    MessageBoxA(NULL, "Debugger not detected!", "mrl.cert.gov.az", 64);
    
    
    return 1;
}

Əgər proqram debugger tərəfindən işə salınıbsa bu istisna proqram (__except) tərəfindən deyil debugger tərəfindən except ediləcəkdir. Tam əksi baş versə __except bloku icra ediləcək və ekrana debugger aşkar edilmedi mesajı veriləcəkdir.

Debugger tərəfindən icra edildiyi halda:

NtSetInformationThread üzərindən debuggerdən yayınma

Sistemdə bir proqramın fəaliyyət göstərə bilməsi üçün mütləq şəkildə ən azı 1 axına (thread) sahib olması lazımdır. Yəni bir kodun icra olunmasını təmin edən şey axındır. Hər bir proqram əsas axın (main thread) adı verilən axına sahibdir. Bu baxımdan axınlar sistem üçün olduqca vacibdirlər. Bu başlıq altında debuggeri aşkarlamaq deyil, ondan yayınmağın yolu haqqında danışacam.

Qeyd etdiyim kimi proqram təminatının icra edilmısi üçün 1 axına ehtiyyacı var.

NtSetInformationThread funksiyasının köməyi ilə fəaliyyət göstərən axını debuggerdən gizlətmək imkanımız var. Belə olan halda debugger gizlədilən threadı izləmək imkanını qaçırır və kodları izləyə bilmir. Microsoft NtSetInformationThread funksiyası haqqında məlumat versədə axının gizlətmək üçün lazım olan parametr haqqında məlumat vermir.

https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntsetinformationthread

Aşağıda ki, kod bloku ilə axınımızı debuggerdən gizlədə bilərik.

#define ThreadHideFromDebugger 0x11

typedef NTSTATUS (WINAPI *pNtSetInformationProcess)(HANDLE ProcessHandle,
    ULONG  ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength);


int main(void)
{
    
    HMODULE hMod = NULL;
    pNtSetInformationProcess NtSetInformationThread = NULL;
    ULONG uRet = 0;
    
    
    hMod = GetModuleHandleA("ntdll.dll");
    
    NtSetInformationThread = (pNtSetInformationProcess)GetProcAddress(hMod, "NtSetInformationThread");
    
    NtSetInformationThread((HANDLE)-2, ThreadHideFromDebugger, NULL, 0, &uRet);
    
    return 1;
}

Burada sonuncu təlimatın olduğu adresə breakpoint set etməyimizə baxmayaraq debugger NtSetInformationThread funksiyasını çağırdıqdan sonra xəta baş verəcək və debugger işinə davam edə bilməyəcək. Əgər kod bloku başqa bir axın içərisində olarsa bu zaman debuggerin axın siyahısından həmin axın itəcəkdir.

Ana proses üzərindən debuggerin aşkarlanması

Tez-tez istifadə edilməsə belə debuggerlərin aşkarlanmasında istifadə edilən bir metoddur. Məntiq olduqca sadədir. Bir proqram təminatı işə salınarkən əgər her şey normaldırsa əksər hallarda ana prosesin (parent process) hansı proses olduğunu ayırd edə bilərsiniz. Məsələn mouse ilə proqram təminatını işə salarkən normalda ana proses əksər hallarda explorer.exe olur. Məslən notepad proqramını işə saldığınız zaman belə bir mənzərə ilə qarşılaşacaqsınız.

Əgər hazırladığınız proqramı test edərək hansı prosesin övlad prosesi (child process) olduğunu təyin edə bilsəniz proqramınız ən azından normal şəkildə işə salındığını aydınlaşdıra bilərsiniz. Bundan əlavə olaraq parent proses adlarını debugger proqram təminatlarının adları ilə qarşılaşdıra bilərsiniz. Məsələn Process32First (https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-process32first) və ya NtQueryInformationProcess ( PROCESS_BASIC_INFORMATION) ( https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess ) funksiyalarının dəstəyi ilə ana prosesin ID dəyərini alaraq buradan proses adını götürüb debugger adları ilə qarşılaşdıra bilərsiniz.

Son olaraq bu məqalədə göstərilən metodlar debugger və ya virtual maşınların aşkarlanması zamanı istifadə edilən bütün metodlar deyil. Məqalədə xüsusi olaraq aşkarlamaq üçün hansı yollar izlənilir bunlar haqqında məlumat verilmişdir. Daha ətraflı məlumat üçün aşağıdakı keçidlərə baxmağınız tövsiyyə edilir.

 

 

İstinadlar

[1] https://anti-reversing.com/Downloads/Anti-Reversing/The_Ultimate_Anti-Reversing_Reference.pdf
[2] https://anti-debug.checkpoint.com
[3] https://forums.virtualbox.org/viewtopic.php?f=2&t=93627
[4] https://en.wikipedia.org/wiki/CPUID

Press ESC to close