Привет, в этом посте я буду распаковывать и анализировать инфостилер Vidar из моего выступления на BSides Islamicabad 2021. Образец стейджа поставляется в виде файла .xll с расширением файла надстройки Excel. Это позволяет сторонним приложениям добавлять дополнительные функции в Excel с помощью Excel-DNA, инструмента или библиотеки, которые используются для написания надстроек .NET Excel. В этом случае файл xll содержит вредоносный загрузчик dll, который дополнительно сбрасывает упакованный исполняемый файл Vidar infostealer на компьютер-жертву, исследование всей цепочки заражения выходит за рамки этого поста, однако я буду глубоко копать исполняемый файл (Packed Vidar) в части 1 статьи этого постав блоге, а финальный пайлод во второй части.
SHA256: 5cd0759c1e566b6e74ef3f29a49a34a08ded2dc44408fccd41b5a9845573a34c
Технический анализ
Я обычно начинаю распаковывать общие упаковщики/загрузчики вредоносного ПО, просматривая его сначала в основных инструментах статического анализа, затем открывая его в IDA и рассматривая с высоты различные разделы для переменных с возможными зашифрованными строками, ключами, импортами или другими глобальными переменными, содержащими важную информацию, проверяя, есть ли у него идентифицированные криптографические подписи, а затем начинаю его отладку. После загрузки в x64dbg я сначала ставлю точку останова на API-интерфейсы выделения памяти, такие как LocalAlloc, GlobalAlloc, VirtualAlloc и API защиты памяти: VirtualProtect, и нажимаю кнопку "Выполнить", чтобы увидеть, срабатывает ли какая-либо из точек останова. Если да, то его довольно просто распаковать и извлечь полезную нагрузку следующего этапа, в противном случае может потребоваться углубленный статический и динамический анализ. Давайте нажмем кнопку запуска, чтобы увидеть, куда это нас приведет дальше.
Извлечение шелл-кода
Итак, первая точка останова в этом случае — это VirtualProtect, вызываемый в области памяти стека размером 0x28A, чтобы предоставить ему защиту Execute Read Write (0x40), как ни странно, верно!
Первые несколько опкодов E9, 55, 8B в выгруженных данных в стеке соответствуют инструкциям jmp, push и mov соответственно, поэтому можно предположить, что это шелл-код, помещаемый в стек, а затем предоставляющий защиту от выполнения для последующего его выполнения. Если я нажму выполнить до кнопку возврата на VirtualProtect и проследить обратно в дизассемблер, я вижу шелл-код, хранящийся в виде строк стека прямо перед вызовом VirtualProtect, и список аргументов помещается, как показано на рисунке ниже.
Следующие несколько операторов готовятся к выполнению шелл-кода в стеке, получая дескриптор объекта контекста устройства (DC) и передавая этот дескриптор в GrayStringA для выполнения шелл-кода из стека (значение ptr в eax взято из рисунка 1)
Давайте теперь приступим к изучению шеллкода.
Отладка шелл-кода для извлечения конечной полезной нагрузки
Как только GrayStringA выполняется, он попадает в точку останова VirtualAlloc, установленную в отладчике, который вызывается для резервирования/фиксации размера памяти 0xAA3CE с MEM_COMMIT | MEM_RESERVE (0x3000) тип выделения памяти.
Происходит возврат управления из VirtualAlloc и переход еще раз из ret приводит нас к шеллкоду. Следующие несколько операторов после вызова VirtualAlloc передают указатель на вновь созданный буфер, размер буфера и дескриптор файла для текущего загруженного процесса в стеке для вызова ReadFile
Он считывает 0xAA3CE байт данных из образа родительского процесса в буфер, допустим, это buffer1.
Дальнейшее выполнение снова достигает точки останова VirtualAlloc, на этот раз выделяя 0x14F0 байт памяти. Теперь я поставлю точку останова записи в область памяти, зарезервированную/зафиксированную вторым вызовом API VirtualAlloc, чтобы увидеть, какие и как данные сбрасываются во второй буфер, buffer2. Повторное нажатие кнопки "Выполнить" прервет выполнение инструкции, показанной на рисунке ниже.
Этот цикл копирует 0x14F0 байтов данных из определенного смещения буфера1 в буфер2, следующие несколько операторов снова вызывают VirtualAlloc для выделения еще 0x350DE байтов памяти, скажем, буфер3, помещая возвращенный адрес буфера вместе со смещением из буфера1 в стек для копирования байтов 0x350DE данных из буфера1 в буфер3
Цикл на следующем рисунке расшифровывает данные, скопированные в буфер2, следующая инструкция push помещает указатель буфера3 в стек в качестве аргумента подпрограммы, вызываемой из адреса буфера2 в edx, которая должна обрабатывать содержимое буфера3
На рисунке ниже показано расшифрованное содержимое окончательного буфера2
Переход в edx запускает выполнение содержимого буфера 2, где, кажется, сначала помещаются строки стека для kernel32.dll, а затем извлекается дескриптор kernel32.dll путем анализа структуры PEB (Process Environment Block)
Полученный дескриптор kernel32.dll передается следующему вызову вместе с другим аргументом с постоянным значением FF7F721A, быстрый поиск этой константы в Google приводит к некоторым общедоступным ссылкам на песочницу, но неясно, о чем именно идет речь. Давайте углубимся в этот момент, обход этой подпрограммы 0x0A4E приводит к разрешенному адресу API GetModuleFileNameW из Kernel32.dll, хранящемуся в eax, что означает, что эта подпрограмма предназначена для разрешения хешированных API.
Аналогичным образом второй вызов преобразует хеш-значение 7F91A078 в ExitProcess API. Подпрограмма-оболочка 0x0A4E выполняет итерацию по экспорту библиотеки, а подпрограмма 0x097A вычисляет хэш для входного параметра имени экспорта. Похоже, что шеллкод использует специальный алгоритм для хеширования API, вычисленное хеш-значение перенастраивается обратно в eax, которое сравнивается с входным хэш-значением, хранящимся в [ebp-4]. Если оба хэш-значения равны, API разрешается, а его адрес хранится в еах.
Следующие несколько инструкций записывают некоторые ненужные данные в стек, после чего помещают указатель на buffer3 и общий размер содержимого buffer3 (0x350C0) в стек и выполняют подпрограмму 0x0BE9 для дешифрования - эта пользовательская схема дешифрования работает путем обработки каждого байта из буфера3 с использованием повторяющихся отрицаний, вычитания, сложения, sar, shl, not или и xor набор инструкций с жестко закодированными значениями на нескольких уровнях, промежуточный результат сохраняется в [ebp-1]
И окончательное значение перезаписывает соответствующее значение buffer3 по смещению [eax]
Как только содержимое буфера3 расшифровано, он продолжает разрешать другие важные API в следующей подпрограмме 0x0FB6.
Я написал простой POC-скрипт на Python для алгоритма хеширования, реализованного с помощью расшифрованного шеллкода, который можно найти здесь. https://github.com/0x00-0x7F/RE_tips_and_tricks/blob/master/vidar_packer/api_hash_strings.py
После того, как все необходимые API были разрешены, он переходит к созданию нового процесса
А затем окончательная полезная нагрузка вводится во вновь созданный процесс с использованием API SetThreadContext, структура CONTEXT для удаленного потока настраивается с помощью ContextFlag и требуемых буферов памяти, а API SetThreadContext вызывается с дескриптором текущего потока и структурой CONTEXT удаленного потока для внедрения кода.
Основной процесс завершается сразу после запуска этого процесса, теперь мы можем сделать дамп этого процесса, чтобы извлечь финальную полезную нагрузку.
Вот и все, что нужно для распаковки! Скоро увидимся в следующей статье, посвященном подробному анализу инфостилера Vidar.
Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://0x00-0x7f.github.io/A-Case-of-Vidar-Infostealer-Part-1-(-Unpacking-)/
SHA256: 5cd0759c1e566b6e74ef3f29a49a34a08ded2dc44408fccd41b5a9845573a34c
Технический анализ
Я обычно начинаю распаковывать общие упаковщики/загрузчики вредоносного ПО, просматривая его сначала в основных инструментах статического анализа, затем открывая его в IDA и рассматривая с высоты различные разделы для переменных с возможными зашифрованными строками, ключами, импортами или другими глобальными переменными, содержащими важную информацию, проверяя, есть ли у него идентифицированные криптографические подписи, а затем начинаю его отладку. После загрузки в x64dbg я сначала ставлю точку останова на API-интерфейсы выделения памяти, такие как LocalAlloc, GlobalAlloc, VirtualAlloc и API защиты памяти: VirtualProtect, и нажимаю кнопку "Выполнить", чтобы увидеть, срабатывает ли какая-либо из точек останова. Если да, то его довольно просто распаковать и извлечь полезную нагрузку следующего этапа, в противном случае может потребоваться углубленный статический и динамический анализ. Давайте нажмем кнопку запуска, чтобы увидеть, куда это нас приведет дальше.
Извлечение шелл-кода
Итак, первая точка останова в этом случае — это VirtualProtect, вызываемый в области памяти стека размером 0x28A, чтобы предоставить ему защиту Execute Read Write (0x40), как ни странно, верно!
Первые несколько опкодов E9, 55, 8B в выгруженных данных в стеке соответствуют инструкциям jmp, push и mov соответственно, поэтому можно предположить, что это шелл-код, помещаемый в стек, а затем предоставляющий защиту от выполнения для последующего его выполнения. Если я нажму выполнить до кнопку возврата на VirtualProtect и проследить обратно в дизассемблер, я вижу шелл-код, хранящийся в виде строк стека прямо перед вызовом VirtualProtect, и список аргументов помещается, как показано на рисунке ниже.
Следующие несколько операторов готовятся к выполнению шелл-кода в стеке, получая дескриптор объекта контекста устройства (DC) и передавая этот дескриптор в GrayStringA для выполнения шелл-кода из стека (значение ptr в eax взято из рисунка 1)
Давайте теперь приступим к изучению шеллкода.
Отладка шелл-кода для извлечения конечной полезной нагрузки
Как только GrayStringA выполняется, он попадает в точку останова VirtualAlloc, установленную в отладчике, который вызывается для резервирования/фиксации размера памяти 0xAA3CE с MEM_COMMIT | MEM_RESERVE (0x3000) тип выделения памяти.
Происходит возврат управления из VirtualAlloc и переход еще раз из ret приводит нас к шеллкоду. Следующие несколько операторов после вызова VirtualAlloc передают указатель на вновь созданный буфер, размер буфера и дескриптор файла для текущего загруженного процесса в стеке для вызова ReadFile
Он считывает 0xAA3CE байт данных из образа родительского процесса в буфер, допустим, это buffer1.
Дальнейшее выполнение снова достигает точки останова VirtualAlloc, на этот раз выделяя 0x14F0 байт памяти. Теперь я поставлю точку останова записи в область памяти, зарезервированную/зафиксированную вторым вызовом API VirtualAlloc, чтобы увидеть, какие и как данные сбрасываются во второй буфер, buffer2. Повторное нажатие кнопки "Выполнить" прервет выполнение инструкции, показанной на рисунке ниже.
Этот цикл копирует 0x14F0 байтов данных из определенного смещения буфера1 в буфер2, следующие несколько операторов снова вызывают VirtualAlloc для выделения еще 0x350DE байтов памяти, скажем, буфер3, помещая возвращенный адрес буфера вместе со смещением из буфера1 в стек для копирования байтов 0x350DE данных из буфера1 в буфер3
Цикл на следующем рисунке расшифровывает данные, скопированные в буфер2, следующая инструкция push помещает указатель буфера3 в стек в качестве аргумента подпрограммы, вызываемой из адреса буфера2 в edx, которая должна обрабатывать содержимое буфера3
На рисунке ниже показано расшифрованное содержимое окончательного буфера2
Переход в edx запускает выполнение содержимого буфера 2, где, кажется, сначала помещаются строки стека для kernel32.dll, а затем извлекается дескриптор kernel32.dll путем анализа структуры PEB (Process Environment Block)
Полученный дескриптор kernel32.dll передается следующему вызову вместе с другим аргументом с постоянным значением FF7F721A, быстрый поиск этой константы в Google приводит к некоторым общедоступным ссылкам на песочницу, но неясно, о чем именно идет речь. Давайте углубимся в этот момент, обход этой подпрограммы 0x0A4E приводит к разрешенному адресу API GetModuleFileNameW из Kernel32.dll, хранящемуся в eax, что означает, что эта подпрограмма предназначена для разрешения хешированных API.
Аналогичным образом второй вызов преобразует хеш-значение 7F91A078 в ExitProcess API. Подпрограмма-оболочка 0x0A4E выполняет итерацию по экспорту библиотеки, а подпрограмма 0x097A вычисляет хэш для входного параметра имени экспорта. Похоже, что шеллкод использует специальный алгоритм для хеширования API, вычисленное хеш-значение перенастраивается обратно в eax, которое сравнивается с входным хэш-значением, хранящимся в [ebp-4]. Если оба хэш-значения равны, API разрешается, а его адрес хранится в еах.
Следующие несколько инструкций записывают некоторые ненужные данные в стек, после чего помещают указатель на buffer3 и общий размер содержимого buffer3 (0x350C0) в стек и выполняют подпрограмму 0x0BE9 для дешифрования - эта пользовательская схема дешифрования работает путем обработки каждого байта из буфера3 с использованием повторяющихся отрицаний, вычитания, сложения, sar, shl, not или и xor набор инструкций с жестко закодированными значениями на нескольких уровнях, промежуточный результат сохраняется в [ebp-1]
И окончательное значение перезаписывает соответствующее значение buffer3 по смещению [eax]
Как только содержимое буфера3 расшифровано, он продолжает разрешать другие важные API в следующей подпрограмме 0x0FB6.
Я написал простой POC-скрипт на Python для алгоритма хеширования, реализованного с помощью расшифрованного шеллкода, который можно найти здесь. https://github.com/0x00-0x7F/RE_tips_and_tricks/blob/master/vidar_packer/api_hash_strings.py
После того, как все необходимые API были разрешены, он переходит к созданию нового процесса
А затем окончательная полезная нагрузка вводится во вновь созданный процесс с использованием API SetThreadContext, структура CONTEXT для удаленного потока настраивается с помощью ContextFlag и требуемых буферов памяти, а API SetThreadContext вызывается с дескриптором текущего потока и структурой CONTEXT удаленного потока для внедрения кода.
Основной процесс завершается сразу после запуска этого процесса, теперь мы можем сделать дамп этого процесса, чтобы извлечь финальную полезную нагрузку.
Вот и все, что нужно для распаковки! Скоро увидимся в следующей статье, посвященном подробному анализу инфостилера Vidar.
Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://0x00-0x7f.github.io/A-Case-of-Vidar-Infostealer-Part-1-(-Unpacking-)/