Сказ о старом добром Visual Basic и его славных победах

Сказ о старом добром Visual Basic и его славных победах
Мы в JSOC CERT ежедневно сталкиваемся с событиями из разных песочниц, которые функционируют в составе AntiAPT-решений наших заказчиков и пропускают через себя тысячи файлов из web- и почтового трафика. Стоит отметить, что современные Sandbox-системы в своем развитии ушли намного дальше, чем простой перехват системных вызовов в Kernel Mode и API-функций в User Mode. Все чаще в них используются собственный гипервизор, система эмуляции пользовательской активности, динамическая инструментация, хэширование и кластеризация по участкам кода, анализ покрытия кода и т.д. Такое разнообразие технологий создает иллюзию, что если какой-то файл не отработал в песочнице и не показал свое «истинное лицо», то это наверняка APT или инновационная технология обнаружения виртуального окружения, о котором ИБ-сообществу еще не известно. Но…


Так как мы не знаем внутренних особенностей работы коммерческих песочниц, в некоторых случаях мы делаем double-check – вручную анализируем сэмплы, прошедшие проверку. За последнее время мы несколько раз столкнулись с тем, что некоторые коммерческие песочницы (по объективным причинам мы не можем сказать, какие) при динамическом анализе не детектировали определённые вредоносные файлы и, если статический анализатор тоже молчал, файл и вовсе пропускался.

Проверку в песочнице удалось обойти таким известным семействам вредоносного ПО, как Pony, Loki и Hawkeye. Объединяло их лишь одно — они были накрыты упаковщиком, написанным на Visual Basic.

Учитывая, что эти семейства ВПО уже давно не являются чем-то новым, «положительный» вердикт песочниц весьма удручает. Поэтому мы решили описать общий принцип работы этого упаковщика и наблюдения, сделанные нами на протяжении некоторого времени.
Общая схема работы упаковщика условно разделена на 4 стадии и изображена на схеме ниже.



Точка входа вредоносного файла выглядит типично для приложений на Visual Basic:



Мы встречали различные варианты данного упаковщика, и код VB Wrapper часто менялся, но выполняемая задача оставалась той же: передать управление коду Stage 1. В более ранних образцах передача управления производилась посредством API-функций класса Enum* (например, EnumWindows, EnumCalendarInfo и т.д), у которых в качестве параметра указывался адрес Stage 1 кода. В последнее время наблюдаем, что управление передается напрямую.

1 этап


Управление получает код Stage 1. Данный код не зашифрован, но обфусцирован. Методы обфускации варьируются от сэмпла к сэмплу, но общий алгоритм работы не меняется:
1) Цикл с множеством (в том числе и мусорных) инструкций, который вырабатывает необходимый для расшифровки кода Stage 2 ключ. Особенность этого фрагмента кода заключается в том, что там отсутствуют Sleep-функции, но за счет большого количества итераций его выполнение занимает в среднем 1-2 минуты.
2) Расшифровка (обычный XOR) и передача управления коду Stage 2.

На скриншоте ниже приведены примеры используемых методов обфускации:



2 этап


Основной задачей кода в Stage 2 является проверка окружения и реализация методов антиотладки. Некоторые участки кода зашифрованы (расшифровываются перед выполнением, а после – зашифровываются обратно все тем же алгоритмом XOR) для затруднения детектирования по сигнатурам. После расшифровки видны характерные признаки, по которым код Stage 2 можно узнать при ручном анализе.



Список проверок довольно большой и отличается в разных версиях упаковщика, поэтому мы приведем несколько методов, которые встречались во всех версиях, со скриншотами, а в конце перечислим весь список в таблице.

1) GetTickCount + Sleep


Берется текущая метка времени, вызывается Sleep на 2 секунды, после чего сразу же берется еще одна метка времени.
После этого проверяется разница между метками (на самом ли деле прошло 2 секунды).



2) SetErrorMode


Проверяется правильность работы API-вызова SetErrorMode. Функция вызывается два раза подряд с параметрами 0x800 и 0x0, после чего результат второго вызова проверяется: он должен быть равен 0x800.



3) SetLastError


Сначала вызывается SetLastError с параметром 0x5, после чего проверяется, что значение Last error code в TEB правильно установилось (то есть, равно 0x5).



4) Проверка движения курсора


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



5) DbgBreakPoint и DbgUiRemoteBreakin


Данные функции модифицируются, чтобы помешать отладчику подсоединиться к процессу.


Техника


Комментарий


GetTickCount + Sleep


Проверка временных меток


SetErrorMode


Проверка правильной работы функции


SetLastError


Проверка правильной работы функции


GetCursorPos


Проверка движения курсора


DbgBreakPoint


Модификация функции для запрета присоединения отладчика


DbgUiRemoteBreakin


Модификация функции для запрета присоединения отладчика


Hook Deletion


Восстанавливаются первые 5 байт функций в ntdll.dll на случай, если там стоят хуки


NtSetInformationThread


Параметр 0x11 (ThreadHideFromDebugger)


GetThreadContext + check DR


Проверяются отладочные регистры DR0-DR3, DR6, DR7


Check breakpoints


Проверяются инструкции INT3 (0xCC), int 3 (0xCD 0x03) и ud2 (0x0F 0x0B) в начале некоторых функций


cpuid (EAX=0x0)


Проверяются регистры EAX, ECX, EDX


cpuid (EAX=0x40000000)


Проверяются регистры EAX, ECX, EDX


cpuid (EAX=0x1)


Проверяется 31-й бит ECX


PEB (BeingDebugged)


Проверяется значение 0x1


PEB (NtGlobalFlag)


Проверяется значение 0x70


NtQueryInformationProcess


Вызывается с флагами ProcessDebugPort (0x7), ProcessDebugFlags (0x1F), ProcessDebugObjectHandle (0x1E)


Process Name Check


Проверяются строки «sample», «sandbox», «virus», «malware», «self.»



В случае выполнения всех техник этапа 2 происходит проверка командной строки на соответствие специальному формату. Если проверка не проходит, то выполняются следующие действия:
1) Вызывается функция CreateProcess с флагом CREATE_SUSPENDED для перезапуска текущего процесса. При этом командная строка имеет необходимый формат.
2) С помощью функций GetContextThread и SetContextThread точка входа изменяется на новую, которая находится в коде Stage 1.
3) Повторяются этапы 1 и 2 (включая длительный цикл и все проверки). В этот раз проверка командной строки проходит успешно и процесс переходит к следующему этапу.

3 этап


На этом этапе расшифровывается тело основного вируса и выполняется техника Process Hollowing на текущий процесс, после чего управление передается на точку входа основного вируса.

Lesson Learned


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

Несмотря на то, что современные песочницы в большинстве своем позиционируются как часть систем защиты от APT-атак, наши наблюдения говорят о том, что даже широко известные сообществу вредоносные семейства проникают внутрь инфраструктуры с завидным постоянством. Так как нет гарантий, что сэмпл, который обошел песочницу, не будет иметь в арсенале пару техник по обходу антивируса, полагаться только на эту связку защитных решений нельзя. В таких случаях обеспечить своевременное реагирование и минимизацию потенциального ущерба может правильно выстроенный процесс мониторинга, включающий в себя события информационной безопасности с конечных хостов.
Alt text