Разбираем Loki-Bot. Как устроены механизмы антиотладки банковского трояна

Исследовать малварь не только весело, но и крайне познавательно. Недавно в мои цепкие руки попал банковский троян LOKI-bot. Подобные вредоносы обычно пишут настоящие профессионалы, и потому банкеры зачастую содержат достаточно интересные хаки. Так случилось и в этот раз — трой сопротивляется отладке и пытается «спалить» нас довольно нетривиальными способами.

Для начала загрузим семпл в DiE и посмотрим, что он покажет.

LOKI в анализаторе DiE

Как видно на скриншоте, DiE отчего-то уверен, что наш семпл написан на Delphi, но это, разумеется, не так. Ты наверняка знаешь, что существуют специальные тулзы, которые умеют склеивать один файл с другим. Создатели LOKI-bot именно так и поступили (обрати внимание на размер секции ресурсов rsrc в файле относительно его общего размера). Оригинальный LOKI запустится после того, как отработает его «обертка».

Для того чтобы разобраться в механизме самозащиты этого бота, мы должны представлять себе, каким образом вообще малварь может сопротивляться отладке. Обычно так или иначе трой старается получить список процессов в системе, а после этого уже начинаются дальнейшие действия. Давай попробуем двигаться в эту сторону, чтобы изучить механизм самозащиты бота. Как известно, получить список процессов в Windows можно несколькими способами.

  • Перечислить процессы при помощи функции PSAPI EnumProcesses.
  • Создать снимок процессов при помощи функции CreateToolhelp32Snapshot.
  • Перечислить процессы при помощи ZwQuerySystemInformation.
  • Применить трюки со счетчиками производительности.
  • Использовать Windows Management Instrumentation (WMI).
  • Безусловно, самый распространенный метод получения процессов — при помощи функции CreateToolhelp32Snapshot, поэтому мы начнем с установки брейк-пойнта на эту функцию (используется отладчик x64dbg, версия для архитектуры x86).

    Итак, мы оказались в теле функции CreateToolhelp32Snapshot. Давай выполним ее и сделаем еще один шаг, чтобы вернуться по ret. В итоге мы попадаем в такой код:

    01DC40D8 | 53                       | push ebx
    01DC40D9 | 6A 02                    | push 2
    01DC40DB | FF56 0C                  | call dword ptr ds:[esi+C]
    01DC40DE | 8BD8                     | mov ebx,eax   <---------- мы стоим здесь
    01DC40E0 | 83FB FF                  | cmp ebx,FFFFFFFF
    01DC40E3 | 0F85 634C0000            | jne 1DC8D4C
    01DC40E9 | 33C0                     | xor eax,eax
    01DC40EB | E9 6AE5FFFF              | jmp 1DC265A
    

    Мы стоим на 01DC40DE, но ты ведь помнишь, что мы только что вернулись из CreateToolhelp32Snapshot? Стало быть, call, который выше по коду, и есть вызов CreateToolhelp32Snapshot. Вспоминаем прототип CreateToolhelp32Snapshot:

    HANDLE CreateToolhelp32Snapshot(
        DWORD dwFlags,
        DWORD th32ProcessID
    );
    

    Как видим, передаются два аргумента, один из которых — push 2, что говорит о передаче параметра TH32CS_SNAPPROCESS. Он заставляет CreateToolhelp32Snapshot сделать снимок всех процессов. Все указывает на вызов CreateToolhelp32Snapshot, но этот call не похож на стандартный вызов WinAPI в коде. Идем в ds:[esi+C] и смотрим, что там есть.

    Вид «сырой» памяти в ds:[esi+C]

    Видим какой-то код, похожий на мусор, среди которого прослеживаются имена функций WinAPI. Давай представим весь код в виде DWORD’ов.

    Имена функций, получаемых динамически

    Перед нами список функций, которые LOKI получает динамически. По этому списку мы можем судить о том, как работает банкер. В дальнейшем вызовы используемых WinAPI будут выполняться подобными call’ами. Итак, со списком WinAPI разобрались, теперь вернемся в код. Чтобы ты понимал его структуру, приведу листинг части функции поиска процессов (обрати внимание на адреса!):

    01DC40DB | FF56 0C                  | call dword ptr ds:[esi+C]       | CreateToolhelp32Snapshot 
    01DC40DE | 8BD8                     | mov ebx,eax                     |
    01DC40E0 | 83FB FF                  | cmp ebx,FFFFFFFF                |
    01DC40E3 | 0F85 634C0000            | jne 1DC8D4C                     |
    01DC40E9 | 33C0                     | xor eax,eax                     |
    01DC40EB | E9 6AE5FFFF              | jmp 1DC265A                     |
    
    00228D4C | 8D85 D4FDFFFF            | lea eax,dword ptr ss:[ebp-22C]  |
    00228D52 | 50                       | push eax                        |
    00228D53 | E9 53ABFFFF              | jmp 2238AB                      |
    
    002238AB | 53                       | push ebx                        |
    002238AC | 89BD D4FDFFFF            | mov dword ptr ss:[ebp-22C],edi  |
    002238B2 | FF56 10                  | call dword ptr ds:[esi+10]      | Process32FirstW
    002238B5 | E9 97EDFFFF              | jmp 222651                      |
    

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

    007C5D83 | 8D8D F8FDFFFF            | lea ecx,dword ptr ss:[ebp-208]          | Имя процесса
    007C5D89 | E8 4AD8FFFF              | call <str_len>                          | Считаем число символов имен процессов
    007C5D8E | 83F8 40                  | cmp eax,40                              | Сравниваем размер имени процесса с числом 40h (64)
    007C5D91 | 0F82 08000000            | jb 7C5D9F                               | Все хорошо, продолжаем корректное выполнение трояна
    007C5D97 | 6A 00                    | push 0                                  | Все плохо, завершаемся
    007C5D99 | FF96 AC000000            | call dword ptr ds:[esi+AC]              | ExitProcess
    007C5D9F | E9 E1D1FFFF              | jmp 7C2F85                              |
    

    Здесь считается размер имени каждого процесса и сравнивается с числом 40h, или 64 в десятеричной системе. Если процесс с таким длинным именем найден, то выполняется выход. В чем тут был замысел?

    Все дело в том, что обычно исследователи в вирусных лабораториях дают имена файлам семплов вредоносных программ в виде их хешей SHA-256, которые как раз имеют длину 64 символа. Если такой файл запустить, LOKI поймет это по числу символов процесса и выполнит выход. Или не поймет? Думаю, что внимательный читатель уже догадался: дело в том, что получаемое при помощи функции CreateToolhelp32Snapshot имя процесса также содержит его расширение, которое добавляет еще четыре символа к имени — .exe. Очевидно, разработчик забыл об этом, поэтому его замысел не сработает. Хотя идея достаточно оригинальна. Вывести из строя эту защиту банкера можно, проследив, чтобы в системе не было процессов с именами из 64 символов, включая расширение.

    Источник: xakep.ru

    Ответить

    Ваш адрес email не будет опубликован. Обязательные поля помечены *