✊Rot Front✊
-
Автор темы
- #1
Итак, сегодня речь будет затронута темида и ещё защиты.
Здесь анализировались следующие версии:
Repacke от
WinLicense является просто модифицированной версией темиды,но с добавлением лицензии и другими плюшками.
Здесь будет продемонстрирован код больше x64 code,нежели x32 т.к логика почти одинакова
+ из-за ограничений по количеству слов автору пришлось пихнуть примеры в pastebin(заранее извиняюсь за это и просто вставьте его в notepad++,если надо)
Затрагиваемые моменты:
Анализ распаковки
Нахождение импорта в темиде
Обход anti-debug,anti-vm,file/register or monitor tool's
Анализ шифрование импорта
Обходим CRC file
Обходим Runtime CRC
Анализ мутации кода
Слегка анализируем VM
Так же автор попытается показать мысли/логику во время анализу,чтобы не было магическое появления информации
Распаковка
EP начинается с секции .boot и выглядит следующим образом:
Я не вижу особого смысла анализировать т.к он просто расшифровывает секцию .themida и передаёт ей управление с jmp rax(просто смело пропускайте сие чудо).
в x32 аналогично
Теперь перейдём к анализу расшифровки нашей секции .text
Итак, давайте воспользуемся HWBP,чтобы не ломать голову откуда запись/чтение.
Устанавливаем HWBP на 1 байт секции.
Давайте просто начнём копать, откуда идёт запись.
Чтобы упростить анализ используем трассировку.
Нас встречает следующий код:
Если упростить код
(вы всегда можете удалить jmp т.к ими часто обузят протекторы и свернуть математические операции, но об этом будет сказано в мутации кода):
Из кода выше понятно следующее:
mov eax,dword ptr ds:[rsi] - чтение зашифрованный секции
add rsi,4 прибавление + sizeof(ULONG)
;decrypt
mov dword ptr ds:[rsi],eax - запись расшифрованный байт секции .text
Быстро и лениво деобфусцируя:
sub 50F7E667 -> add 3E0A24B7-> xor 4B1068D5
or sub 12EDC1B0 -> xor 4B1068D5
В упрощённом видел:
mov eax,dword ptr ds:[rsi];чтение
add rsi,4; + sizeof(ULONG)
sub eax,12EDC1B0;расшифровка 1
xor eax,4B1068D5; расшифровка 2
mov dword ptr ds:[rsi],eax;запись расшифрованных байт
Короче, первое действие - просто расшифровка с помощью sub & xor в данном случае
Вторая запись:
В упрощенном виде:
mov byte ptr ds:[rdi],al
add rsi,1
add rdi,1
sub ecx,1
mov al,byte ptr ds:[rsi]
mov byte ptr ds:[rdi],al
Но погодите-ка, ведь адрес в rsi вообще выделен в отдельном регионе?
Ну, протектор, видимо, решил использовать данный трюк для усложнения + потом я покажу, что он использует этот трюк в другом месте.
Отсюда растут ноги в запись в выделенную память.
Я не хочу пихать код т.к он очень большой, но он напоминает распаковку секции .themida т.к так же юзается
add dl, dl
Сам график(код в первоначальном виде т.е jmp не очищены и прочие приколы):
Нахождение OEP:
Здесь работает самый тупой способ т.е через PAGE_GUARD hook на .text секцию.
Проблема в том, что протектор должен не пихать jmp на свою секцию,а утянуть вызов в свою VM(как делает тот же VMProtect).
00007FF646C81480 - OEP
Оригинальный код:
00007FF646C8147A | E8 9F090000 | call <JMP.&_Exit> |
00007FF646C8147F | 90 | nop |
00007FF646C81480 <wi | 48:83EC 28 | sub rsp,28 |
00007FF646C81484 | E8 D7030000 | call winlicense_test.7FF646C81860 |
00007FF646C81489 | 48:83C4 28 | add rsp,28 |
00007FF646C8148D | E9 72FEFFFF | jmp winlicense_test.7FF646C81304 |
Виртуализированный OEP:
00007FF6717B147A | E8 9F090000 | call <JMP.&_Exit> |
00007FF6717B147F | 90 | nop |
00007FF6717B1480 | E9 3ED86700 | jmp winlicense_test_protected.7FF671E2ECC3 |;1 выполнение инструкции при PAGE_GUARD секции .text
00007FF6717B1485 | 50 | push rax |;перезаписанный код на мусор
00007FF6717B1486 | A2 82FBF50E6592495A | mov byte ptr ds:[5A4992650EF5FB82],al |;перезаписанный код на мусор
00007FF6717B148F | AA | stosb |;перезаписанный код на мусор
00007FF6717B1490 | DF62 CC | fbld st(0),tword ptr ds:[rdx-34] |;перезаписанный код на мусор
00007FF6717B1493 | CC | int3 |;code cave
00007FF6717B1494 | 40:53 | push rbx |
Хукаем WinApi,которые юзает темида
Итак,если вы попытаетесь трассировать темиду,то иногда ваш код застрянет на поиске импортов:
Нас интересует эта часть кода:
ret 0x18
или в x32 бинарнике
ret 0xC
Из моих опытов, всегда после этой инструкции идёт jmp,благодаря чему можно составить сигнатуру
x64 - C2 18 00 E9
x32 - C2 0C 00 E9
Это будет первая точка остановы с поиском импорта, которая сработает при поиске этого паттерна.
Давайте проверим мои слова:
и само выполнение кода:
Получаем следующие NtApi/Api:
В теории вы можете просто перехватить это добро, чтобы,например, обойти anti-debug,CRC file и т.д
Так же в x64 бинарнике юзается VEH(в дальнейшем он удаляется).
Почему же они его юзают?
Ответ не застав долго ждать:
Начнём с простого т.к обнаружение мониторов для реестра/файлов.
Из списка выше понятно, что юзается FindWindows и вот список:
Однако, это не весь список WinApi/NtApi. WTF????(попробуйте только пропатчить FindWindows и запустить программу под темидой с детектом monitor tools)
Ну, ответ довольно прост:GetProcAddress(особенно из-за получения его выше темидой).
Лично автор решил данную проблему так: начинать трассировку, скопировать адрес NtApi/WinApi и когда индекс трассировки будет >50k смотреть данный адрес(трассировка->поиск-> константа)
Я так сделал и рекомендую так делать т.к разработчики протектора могут начать получать адрес в другом месте,хотя вы можете потратить много времени на это.
В любом случае, у нас есть в списке импорта lstrcmpiA на этот WinApi,поэтому ставим bp и мы получим следующий список драйверов:
NtQuerySystemInformation with SystemModuleInformation
Так же создаётся поток, который будет вызывать FindWindowA(можно просто сделать прыжок на свой же адрес т.е EB FE,заморозить поток и т.д).
Anti-debug(x64/x32):
Anti-VM:
Обфускация импорта
При трассировке видим следящее:
Нас должно зацепить следующее:
1)Способ получения начала секции
через:
call$+5
pop rbp
sub rbp,31E3F
2)Последняя операция с расшифровкой импорта:
00007FF7FDDFD631 or rax,reg ; decrypt address
3)Запихивание переменной в стек через push и при этом с 2 регистрами:
00007FF7FDE07979 | FF3428 | push qword ptr ds:[rax+rbp]
Я не вижу смысла деобфусцировать т.к можно понять логику и скажу следущее:
1)Протектор должен прочитать откуда-то зашифрованное значение и расшифровать(в моём случае оно находится в секции .winlicense) -> можно поставить PAGE GUARD hook
Первое чтение идёт как раз из push qword ptr ds:[rax+rbp] ; push qword ptr ds:[offset+start_secthion] (offset - 255B4)
Это делается из-за того,чтобы не убивать скорость выполнение программы т.к под VM займёт куда больше операций,а отсюда и времени
2)((res_offset - 7C4591CA) ^ 7D0AE1FA) | FFFFFFFF
Здесь идут операции :sub,xor,and
3)
push reg
mov qword ptr ss:[rsp],decrypt_address
При анализе я всегда встречал 3 пункта с верху(так же jmp,похоже,всегда вставляется даже при виртуализированном коде т.е начинать сканирование нужно от jmp),поэтому можно сделать логику для расшифровки импортов.
в x32:
1)Встречаем так же push dword ptr ss:[ebp+eax]
2)В конце используется xor для расшифровки
3) Так же в упрощённом виде:
push reg
mov qword ptr ss:[esp],decrypt_address
Аналогично так же с возможностью с расшифровкой автоматический(поиск jmp + проверка команд и меньше ~150 операций перед ret), но только нужно проверять на xor eax,reg в x32
Обходим CRC file
Итак, немного пояснения, чтобы не было проблем в различии CRC.
CRC используется протекторами для обнаружения изменений в файле/в секции.
Основная идея CRC file - обнаружить практический любое изменение байт в файле,за исключением некоторых моментов в файле + где сохраняется значение.
CRC подвергается коллизии, но этот способ неудобен в данном случае для обхода т.к нужно анализировать сам алгоритм,а он может быть в VM.
Самый простой способ - найти где сохраняется итоговый результат CRC или подменить файл на оригинальный.
Themida использует следующий способ подсчёта:
Themida использует следующий способ подсчёта CRC файла:
GetModuleFileName
CreateFileW
GetFileSize
CreateFileMappingW
MapViewOfFile
UnmapViewOfFile
Можно просто подменять файл и это работает,но давайте избавимся от бесполезной работы.
В данном примере понадобится CTF exploler для создания новой секции и вставки оригинального файла в новую секцию.
File -> open -> secthion headers -> add secthion(file data) и кидаем оригинальный файл -> add secthion(empty space) -> у новой секции change secthion flag ->
Is executeble и ставим галочку т.к туда будем пихать наш код.
Можно написать dll без crt и скопировать код(единственно, что вам нужно будет сделать: сохранять почти все регистры и Rlag/Eflag) и передавать туда выполнение
(вы сможете хукнуть эти api раньше, например, чем будет вообще выполнена распаковка(изменение EP на свой код)).
В данном примере автор продемонстрирует патчинг vm-entry после вызова API.
Логика обхода(в данном случае была дефолтная VM т.е Tiger Rea):
1)Делаем подмену rax/eax после вызова MapViewOfFile и сохраняем оригинальное значение, если вызов вернул не NULL
2)Делаем подмену размер файла(это не обязательно, но будем пунктуальные в этом плане)
3)Возвращаем оригинальный выделенный адрес из MapViewOfFile в UnmapViewOfFile т.к иначе будет unmap нашей секции или по-простому: нам кирдык.
Ставим bp на MapViewOfFile,GetFileSize и проверяем вызывается ли vm-handler пару раз или только 1 раз.
В моём случае он вызывался только 1 раз.
Мы должны получить адрес выхода vm перед вызовом UnmapViewOfFile т.к это убьёт наш файл.
Ставим bp на обращение к последним байтам,чтобы не снимать ооооочень длинную трассировку и начинаем трассировку,пока не дойдём до вызова.
У автора много раз вызывалась vm-exit,поэтому нужно ставить условие на подмену rax/eax.
Будет прикреплён с файлом в качестве примера.
Краткий обзор файла и пример обхода crc file(будет прикреплены в качестве примера):
rva: header va : offset file
5E36E4 : 00000001405E36E4 : 5DE4E4 - filze size(vm-entry)
5CA074 : 00000001405CA074 : 5C4E74 - map file(vm-entry)
3A6E7 : 000000014003A6E7 : 354E7 - unmap file(vm-exit call api or jmp next code)
Браво! Вы обошли CRC file ничего особо не делая!
Можно другими способами, но я продемонстрировал, который пришёл самый первый в голову автора.
в x32 почти аналогично.
Обходим Runtime CRC
или
Я купил себе машину, думал, круче не найти,
Откатал на ней неделю, сдохла, мать её ити!
Я называю так CRC т.к проверка может быть выполнена только в runtime самого вашего кода(CHECK_CODE_INTEGRITY ).
На самом деле CRC секции подсчитано и оно просто вычисляется заново & сравняется с готовым CRC.
Так же CRC runtime проверяет только патч ER секции(.text),а не другие и тем более VM(я не видел,чтобы темида его проверяла и сама пять ERW)
Итак, начинаем с того, что мы пробуем пропатчить любой байт в .text секции и мы получаем обнаружение.
Попробуем изменить безобидный code cave(int3) и получаем обнаружение:
Посмотри откуда идёт чтение:
Вас должен сразу смутить следущий код:
00007FF6BFEC86DD | mov bl,0x7
Значение перезаписывается т.е оно сохраняется где-то сверху и как раз после чтения идёт запись в RW регион памяти!
Теперь ставим там bp на обращение и смотрим, откуда идёт чтение:
00007FF6C044CFB4 | add eax,dword ptr ds:[r11]
и
00007FF6C044CE4C | xor eax,dword ptr ds:[rdx]
Если посмотрим на график,то это просто мутированный код и нам повезло!
График:
В упрощённом виде:
loop_read:
00007FF6C044CFB4 | add eax,dword ptr ds:[r11]
00007FF6C03F9184 | rol eax,10
00007FF6C044CE2B | push rdx
00007FF6C044CE42 | add rdx,rsi
00007FF6C044CE4C | xor eax,dword ptr ds:[rdx]
00007FF6C044CE52 | pop rdx
00007FF6C044E2F3 | add rsi,4; +sizeof(ULONG)
00007FF6C044F16E | sub ecx,1
00007FF6C03C5C6D | test ecx,ecx
00007FF6C0403D1F | je winlicense_test_protected.stop_scan
00007FF6C0403D25 | jmp winlicense_test_protected.loop_read
stop_scan:
00007FF6C0433708 | 8945 E0 | mov dword ptr ss:[rbp-20],eax
00007FF6C044FE30 | FF75 E4 | push qword ptr ss:[rbp-1C]
00007FF6C044B12F | FF97 D9310600 | call qword ptr ds:[rdi+631D9]; прыжок в VM
Дальше нас особо код не интересует
*Часть кода пропущена и основная логика без этого понятна.
Итак,здесь просто происходит цикл, да ещё и не в VM! ЛЯПОТА!
Результат сохраняется в eax и давайте его попробуем пропатчить!
убираем все патчи -> получаем оригинальный результат CRC(eax - 00000000F84B2AA5) -> видим E9 00000000 | jmp winlicense_test_protected.7FF6C044F287(идёт после pop rax) и меняем на:
mov eax,0xF84B2AA5 (5 байт как раз)
Ура! Мы обошли CRC runtime!
В крайнем случае вы можете всегда найти все места откуда идёт чтение и постепенно пропатчит их/просто давать скопированный регион.
С x32 такая же история.
Анализ мутации кода
До мутации:
После:
и
Поэтапно будем разбирать:
1)Злоупотреблением jmp.
Основная цель - попытаться обмануть дизассемблер т.к если дизассемблировать линейно т.е с начал адреса секции,то многие операнды будут накладываться друг на друга и ломать анализ
2)Расшифровка в зашифрованном режиме:
00007FF7867EBB2B | 48:B8 AAB7EF7F00000000 | mov rax,7FF786170000
00007FF7867FD14C | 48:81EE 2749FF7E | sub rsi,7EFF4927
00007FF7867FD153 | 48:81EE CD8FFB17 | sub rsi,17FB8FCD
00007FF7867FD15A | 48:81C6 B7F1FC7F | add rsi,7FFCF1B7
00007FF7867FD161 | 48:81C6 C1A7F67F | add rsi,7FF6A7C1
00007FF7867FD168 | 48:81EE 41C1FF7B | sub rsi,7BFFC141
00007FF7867FD16F | 48:01C6 | add rsi,rax
00007FF7867FD172 | 48:81C6 41C1FF7B | add rsi,7BFFC141
00007FF7867FD179 | 48:81EE C1A7F67F | sub rsi,7FF6A7C1
00007FF7867FD180 | 48:81EE B7F1FC7F | sub rsi,7FFCF1B7
00007FF7867FD187 | 48:81C6 CD8FFB17 | add rsi,17FB8FCD
00007FF7867FD18E | 48:81C6 2749FF7E | add rsi,7EFF4927
Протектор использует это только к sub и add.
Суть и так ясна
3)Расшифровка констант,используя математические операции:
и
mov eax, 80008001- код,который была мутированная
5)mba
[rsp] - 0,rsi - 63B08FFAF8
00007FF7867C38DE | 48:333424 | xor rsi,qword ptr ss:[rsp] | | 00000063B08FFAE8: 0-> 0
00007FF7867C38E2 | 48:313424 | xor qword ptr ss:[rsp],rsi | | 00000063B08FFAE8: 0-> 63B08FFAF8
00007FF7867C38E6 | 48:333424 | xor rsi,qword ptr ss:[rsp] | rsi: 63B08FFAF8-> 0 | 00000063B08FFAE8: 63B08FFAF8-> 63B08FFAF8
Эта запись аналогична простому:xchg qword ptr ss:[rsp], rsi
6)Получить начальный адрес секции:
00007FF7861838AE | E8 00000000 | call winlicense_test_protected.7FF7861838B3;получаем адресс следующей инструкции(pop rbp)
00007FF7861838B3 | 5D | pop rbp
00007FF7861838B4 | 48:81ED B3B80000 | sub rbp,B8B3
00007FF7861838BB | C3 | ret
7)переход к новому адресу, используя ret
Нас интересует только это:
7FF786170000 - PE файла
7FF7861AA888 - ret address
Чтение самой секции:
00007FF7867EBB22 | FFB5 BEFD2F00 | push qword ptr ss:[rbp+2FFDBE] | rsp: DE5CD9FC20-> DE5CD9FC18 | 00007FF786477DBE: 7FF786170000-> 7FF786170000 000 |
00007FF7867EBB2A | 50 | push rax | rsp: DE5CD9FC10-> DE5CD9FC08 | 000000DE5CD9FC08: 0-> 1 |
00007FF7867EBB2B | 48:B8 AAB7EF7F00000000 | mov rax,7FEFB7AA | rax: 1-> 7FEFB7AA | |
00007FF7867EBB35 | 49:BE 191F2FFF00000000 | mov r14,FF2F1F19 | r14: 0-> FF2F1F19 | |
00007FF7867EBB3F | 49:29C6 | sub r14,rax | r14: FF2F1F19-> 7F3F676F | |
00007FF7867EBB42 | 58 | pop rax | rax: 7FEFB7AA-> 1 rsp: DE5CD9FC08-> DE5CD9FC10 | 000000DE5CD9FC08: 1-> 1 |
00007FF7867EBB43 | 4C:317424 08 | xor qword ptr ss:[rsp+8],r14 | | 000000DE5CD9FC18: 7FF786170000-> 7FF7F928676F |
00007FF7867EBB48 | 41:5E | pop r14 | rsp: DE5CD9FC10-> DE5CD9FC18 r14: 7F3F676F-> 0 | 000000DE5CD9FC10: 0-> 0 |
00007FF7867EBB4A | 48:8B0424 | mov rax,qword ptr ss:[rsp] | rax: 1-> 7FF7F928676F | 000000DE5CD9FC18: 7FF7F928676F-> 7FF7F928676F |
00007FF7867EBB4E | 48:83C4 08 | add rsp,8 | rsp: DE5CD9FC18-> DE5CD9FC20 | |
00007FF7867EBB52 | 48:35 6F673F7F | xor rax,7F3F676F | rax: 7FF7F928676F-> 7FF786170000 |
00007FF786803A1D | 48:C70424 A041BE3F | mov qword ptr ss:[rsp],3FBE41A0 | | 000000DE5CD9FC10: 0-> 3FBE41A0 |
00007FF786803A25 | 4C:8B3424 | mov r14,qword ptr ss:[rsp] | r14: 0-> 3FBE41A0 | 000000DE5CD9FC10: 3FBE41A0-> 3FBE41A0 |
00007FF786803A29 | 48:83C4 08 | add rsp,8 | rsp: DE5CD9FC10-> DE5CD9FC18 | |
00007FF786803A2D | 41:81F6 4B579B37 | xor r14d,379B574B | r14: 3FBE41A0-> 82516EB | |
00007FF786803A34 | 41:81EE 86ECFF6F | sub r14d,6FFFEC86 | r14: 82516EB-> 98252A65 | |
00007FF786803A3B | 41:83C6 01 | add r14d,1 | r14: 98252A65-> 98252A66 | |
00007FF786803A3F | 41:81F6 EE822698 | xor r14d,982682EE | r14: 98252A66-> 3A888 | |
00007FF786803A46 | 44:89F1 | mov ecx,r14d | rcx: 140000000-> 3A888 | |
00007FF786803A49 | 41:5E | pop r14 | rsp: DE5CD9FC18-> DE5CD9FC20 r14: 3A888-> 0 | 000000DE5CD9FC18: 0-> 0 |
00007FF786803A4B | E9 BD9E0000 | jmp winlicense_test_protected.7FF78680D | | |
00007FF78680D90D | 48:81C1 6AFAFB7F | add rcx,7FFBFA6A | rcx: 3A888-> 7FFFA2F2 | |
00007FF78680D914 | 48:81C1 C01D9B1B | add rcx,1B9B1DC0 | rcx: 7FFFA2F2-> 9B9AC0B2 | |
00007FF78680D91B | 48:81E9 528EEE6D | sub rcx,6DEE8E52 | rcx: 9B9AC0B2-> 2DAC3260 | |
00007FF78680D922 | 48:81E9 3DCADD3F | sub rcx,3FDDCA3D | rcx: 2DAC3260-> FFFFFFFFEDCE6823 | |
00007FF78680D929 | 48:01C1 | add rcx,rax | rcx: FFFFFFFFEDCE6823-> 7FF773E56823 | |
00007FF78680D92C | 48:81C1 3DCADD3F | add rcx,3FDDCA3D | rcx: 7FF773E56823-> 7FF7B3C33260 | |
00007FF78680D933 | 48:81C1 528EEE6D | add rcx,6DEE8E52 | rcx: 7FF7B3C33260-> 7FF821B1C0B2 | |
00007FF78680D93A | 48:81E9 C01D9B1B | sub rcx,1B9B1DC0 | rcx: 7FF821B1C0B2-> 7FF80616A2F2 | |
00007FF78680D941 | 48:81E9 6AFAFB7F | sub rcx,7FFBFA6A | rcx: 7FF80616A2F2-> 7FF7861AA888 |
Если упростить и взять только логику с ret,то:
;some code
call $+5
pop rax
sub rax,decrypt_offset_fix_start_secthion
push qword ptr ss:[rax+offset_themida.base_address];read secthion .winlicense and push
pop rax
add rax,decrypt_offset_next_code_ret
push rax;go next code
ret
Как видите,при мутации шифруются константы и используется сразу несколько математических операций,что,на мой взгляд, лучше той же мутации в VMP.
Это больше в качестве маленького и бесполезного бонуса т.к у автора мало времени на дальнейший анализ...
Слегка анализируем VM(Tiger Lite)
Если копать под jmp r9:
005F | 00007FF6D41429BD | E8 00000000 | call tiger_lite.7FF6D41429C2 | rsp: 72D32FFDC0-> 72D32FFDB8 | 00000072D32FFDB8: 6CF3AC79-> 7FF6D41429C2
00EC | 00007FF6D4142BD5 | 48:83E9 05 | sub rcx,5 | rcx: 7FF6D41429C2-> 7FF6D41429BD |
00ED | 00007FF6D4142BD9 | 48:81E9 BD290F00 | sub rcx,F29BD | rcx: 7FF6D41429BD-> 7FF6D4050000 |
rcx 7FF6D4050000 - base address
Темида получает адрес текущей секции через call $+5
2)Увеличить RSP:
1)
00A2 | 00007FF6D4142ABC | 48:89E0 | mov rax,rsp | rax: 0-> 72D32FFD70 |
00A3 | 00007FF6D4142ABF | 48:83C0 08 | add rax,8 | rax: 72D32FFD70-> 72D32FFD78 |
00A4 | 00007FF6D4142AC3 | 48:2D 08000000 | sub rax,8 | rax: 72D32FFD78-> 72D32FFD70 |
00A5 | 00007FF6D4142AC9 | 48:870424 | xchg qword ptr ss:[rsp],rax | rax: 72D32FFD70-> 0 | 00000072D32FFD70: 0-> 72D32FFD70
00A6 | 00007FF6D4142ACD | 48:8B2424 | mov rsp,qword ptr ss:[rsp]
2)
0205 | 00007FF6D41431BC | 41:55 | push r13 | rsp: 72D32FFD40-> 72D32FFD38 | 00000072D32FFD38: 0-> 0
0206 | 00007FF6D41431BE | 49:89E5 | mov r13,rsp | r13: 0-> 72D32FFD38 |
0207 | 00007FF6D41431C1 | 49:83C5 08 | add r13,8 | r13: 72D32FFD38-> 72D32FFD40 |
0208 | 00007FF6D41431C5 | 49:81C5 08000000 | add r13,8 | r13: 72D32FFD40-> 72D32FFD48 |
0209 | 00007FF6D41431CC | 4C:872C24 | xchg qword ptr ss:[rsp],r13 | r13: 72D32FFD48-> 0 | 00000072D32FFD38: 0-> 72D32FFD48
020A | 00007FF6D41431D0 | 48:8B2424 | mov rsp,qword ptr ss:[rsp] | rsp: 72D32FFD38-> 72D32FFD48 | 00000072D32FFD38: 72D32FFD48-> 72D32FFD48
3)Расшифровка таблицы переходов via VM handlers:
BCF6D + base_address
0242 | 00007FF6D41432CE | 89FE | mov esi,edi | rsi: 0-> 6BBFDC58 |
0244 | 00007FF6D41432D1 | 81CE CB19FF0D | or esi,DFF19CB | rsi: 6BBFDC58-> 6FFFDDDB |
0245 | 00007FF6D41432D7 | F7D6 | not esi | rsi: 6FFFDDDB-> 90002224 |
0246 | 00007FF6D41432D9 | 81C6 A7D2F33F | add esi,3FF3D2A7 | rsi: 90002224-> CFF3F4CB |
0247 | 00007FF6D41432DF | 81EE 774C0090 | sub esi,90004C77 | rsi: CFF3F4CB-> 3FF3A854 |
0248 | 00007FF6D41432E5 | 89F3 | mov ebx,esi | rbx: 0-> 3FF3A854 |
024B | 00007FF6D41432EF | 81C3 EA570CC0 | add ebx,C00C57EA | rbx: 3FF3A854-> 3E |
0005 | 00007FF6D4465815 | 48:BE DB00000000000000 | mov rsi,DB | rsi: 0-> DB |
0253 | 00007FF6D414347B | 48:8B1C24 | mov rbx,qword ptr ss:[rsp] | rbx: 3E-> DB
025B | 00007FF6D414349E | 48:C1E3 03 | shl rbx,3 | rbx: DB-> 6D8 |
0260 | 00007FF6D41434BA | 48:01D8 | add rax,rbx | rax: 7FF812ADF201-> 7FF812ADF8D9 |
0265 | 00007FF6D41434D5 | FF20 | jmp qword ptr ds:[rax] | | 00007FF6D410D645: 7FF6D40B98D5-> 7FF6D40B98D5
mov rax,6D8+ (BCF6D + base_address)
jmp [rax];handle execute
Основная цель выполнение vm-handler'ов и при этом данный способ требует от реверсера найти все vm-handler'ы и фиксить их,если мы распаковали программу:
Статья подошла к своему концу из-за нехватки времени.
Be robbers and ravagers as long as you cannot be rulers and owners, you men of knowledgel...
Здесь анализировались следующие версии:
Repacke от
Пожалуйста, авторизуйтесь для просмотра ссылки.
(Themida v 3.0.4.0)
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
WinLicense является просто модифицированной версией темиды,но с добавлением лицензии и другими плюшками.
Здесь будет продемонстрирован код больше x64 code,нежели x32 т.к логика почти одинакова
+ из-за ограничений по количеству слов автору пришлось пихнуть примеры в pastebin(заранее извиняюсь за это и просто вставьте его в notepad++,если надо)
Затрагиваемые моменты:
Анализ распаковки
Нахождение импорта в темиде
Обход anti-debug,anti-vm,file/register or monitor tool's
Анализ шифрование импорта
Обходим CRC file
Обходим Runtime CRC
Анализ мутации кода
Слегка анализируем VM
Так же автор попытается показать мысли/логику во время анализу,чтобы не было магическое появления информации
EP начинается с секции .boot и выглядит следующим образом:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Я не вижу особого смысла анализировать т.к он просто расшифровывает секцию .themida и передаёт ей управление с jmp rax(просто смело пропускайте сие чудо).
в x32 аналогично
Теперь перейдём к анализу расшифровки нашей секции .text
Итак, давайте воспользуемся HWBP,чтобы не ломать голову откуда запись/чтение.
Устанавливаем HWBP на 1 байт секции.
Давайте просто начнём копать, откуда идёт запись.
Чтобы упростить анализ используем трассировку.
Нас встречает следующий код:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Если упростить код
(вы всегда можете удалить jmp т.к ими часто обузят протекторы и свернуть математические операции, но об этом будет сказано в мутации кода):
Из кода выше понятно следующее:
mov eax,dword ptr ds:[rsi] - чтение зашифрованный секции
add rsi,4 прибавление + sizeof(ULONG)
;decrypt
mov dword ptr ds:[rsi],eax - запись расшифрованный байт секции .text
Быстро и лениво деобфусцируя:
sub 50F7E667 -> add 3E0A24B7-> xor 4B1068D5
or sub 12EDC1B0 -> xor 4B1068D5
В упрощённом видел:
mov eax,dword ptr ds:[rsi];чтение
add rsi,4; + sizeof(ULONG)
sub eax,12EDC1B0;расшифровка 1
xor eax,4B1068D5; расшифровка 2
mov dword ptr ds:[rsi],eax;запись расшифрованных байт
Короче, первое действие - просто расшифровка с помощью sub & xor в данном случае
Вторая запись:
Пожалуйста, авторизуйтесь для просмотра ссылки.
В упрощенном виде:
mov byte ptr ds:[rdi],al
add rsi,1
add rdi,1
sub ecx,1
mov al,byte ptr ds:[rsi]
mov byte ptr ds:[rdi],al
Но погодите-ка, ведь адрес в rsi вообще выделен в отдельном регионе?
Ну, протектор, видимо, решил использовать данный трюк для усложнения + потом я покажу, что он использует этот трюк в другом месте.
Отсюда растут ноги в запись в выделенную память.
Я не хочу пихать код т.к он очень большой, но он напоминает распаковку секции .themida т.к так же юзается
add dl, dl
Сам график(код в первоначальном виде т.е jmp не очищены и прочие приколы):
Нахождение OEP:
Здесь работает самый тупой способ т.е через PAGE_GUARD hook на .text секцию.
Проблема в том, что протектор должен не пихать jmp на свою секцию,а утянуть вызов в свою VM(как делает тот же VMProtect).
00007FF646C81480 - OEP
Оригинальный код:
00007FF646C8147A | E8 9F090000 | call <JMP.&_Exit> |
00007FF646C8147F | 90 | nop |
00007FF646C81480 <wi | 48:83EC 28 | sub rsp,28 |
00007FF646C81484 | E8 D7030000 | call winlicense_test.7FF646C81860 |
00007FF646C81489 | 48:83C4 28 | add rsp,28 |
00007FF646C8148D | E9 72FEFFFF | jmp winlicense_test.7FF646C81304 |
Виртуализированный OEP:
00007FF6717B147A | E8 9F090000 | call <JMP.&_Exit> |
00007FF6717B147F | 90 | nop |
00007FF6717B1480 | E9 3ED86700 | jmp winlicense_test_protected.7FF671E2ECC3 |;1 выполнение инструкции при PAGE_GUARD секции .text
00007FF6717B1485 | 50 | push rax |;перезаписанный код на мусор
00007FF6717B1486 | A2 82FBF50E6592495A | mov byte ptr ds:[5A4992650EF5FB82],al |;перезаписанный код на мусор
00007FF6717B148F | AA | stosb |;перезаписанный код на мусор
00007FF6717B1490 | DF62 CC | fbld st(0),tword ptr ds:[rdx-34] |;перезаписанный код на мусор
00007FF6717B1493 | CC | int3 |;code cave
00007FF6717B1494 | 40:53 | push rbx |
Хукаем WinApi,которые юзает темида
Итак,если вы попытаетесь трассировать темиду,то иногда ваш код застрянет на поиске импортов:
Нас интересует эта часть кода:
ret 0x18
или в x32 бинарнике
ret 0xC
Из моих опытов, всегда после этой инструкции идёт jmp,благодаря чему можно составить сигнатуру
x64 - C2 18 00 E9
x32 - C2 0C 00 E9
Это будет первая точка остановы с поиском импорта, которая сработает при поиске этого паттерна.
Давайте проверим мои слова:
и само выполнение кода:
Получаем следующие NtApi/Api:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Так же в x64 бинарнике юзается VEH(в дальнейшем он удаляется).
Почему же они его юзают?
Ответ не застав долго ждать:
Начнём с простого т.к обнаружение мониторов для реестра/файлов.
Из списка выше понятно, что юзается FindWindows и вот список:
x32:
FindWindows
lpClassName:
"OLLYDBG"
"GBDYLLO"
"pediy06"
"PROCMON_WINDOW_CLASS"
lpWindowName :
"File Monitor - Sysinternals:
"Process Monitor - Sysinternals:
x64:
FindWindows
lpClassName:
"PROCMON_WINDOW_CLASS"
"RegmonClass"
"18467-41"
"Registry Monitor - Sysinternals:
lpWindowName :
"Process Monitor - Sysinternals:
"File Monitor - Sysinternals:
FindWindows
lpClassName:
"OLLYDBG"
"GBDYLLO"
"pediy06"
"PROCMON_WINDOW_CLASS"
lpWindowName :
"File Monitor - Sysinternals:
Пожалуйста, авторизуйтесь для просмотра ссылки.
""Process Monitor - Sysinternals:
Пожалуйста, авторизуйтесь для просмотра ссылки.
"x64:
FindWindows
lpClassName:
"PROCMON_WINDOW_CLASS"
"RegmonClass"
"18467-41"
"Registry Monitor - Sysinternals:
Пожалуйста, авторизуйтесь для просмотра ссылки.
"lpWindowName :
"Process Monitor - Sysinternals:
Пожалуйста, авторизуйтесь для просмотра ссылки.
""File Monitor - Sysinternals:
Пожалуйста, авторизуйтесь для просмотра ссылки.
"Ну, ответ довольно прост:GetProcAddress(особенно из-за получения его выше темидой).
Лично автор решил данную проблему так: начинать трассировку, скопировать адрес NtApi/WinApi и когда индекс трассировки будет >50k смотреть данный адрес(трассировка->поиск-> константа)
Я так сделал и рекомендую так делать т.к разработчики протектора могут начать получать адрес в другом месте,хотя вы можете потратить много времени на это.
В любом случае, у нас есть в списке импорта lstrcmpiA на этот WinApi,поэтому ставим bp и мы получим следующий список драйверов:
NtQuerySystemInformation with SystemModuleInformation
CONST CHAR* bad_list_driver_monitor[] =
{
"FileMonitor.sys"
"Filem"
"REGMON"
"regsys"
"sysregm"
"PROCMON"
"Kernel Detective"
"CisUtMonitor"
"Revoflt"
};
{
"FileMonitor.sys"
"Filem"
"REGMON"
"regsys"
"sysregm"
"PROCMON"
"Kernel Detective"
"CisUtMonitor"
"Revoflt"
};
Так же создаётся поток, который будет вызывать FindWindowA(можно просто сделать прыжок на свой же адрес т.е EB FE,заморозить поток и т.д).
Anti-debug(x64/x32):
Loader:
NtOpenSection + NtMapViewOfSection Map ntdll file(KnownDlls)(берёт только NtQueryInformationProcess)
NtSetInformationThread with ThreadHideFromDebugger
CheckRemoteDebuggerPresent -> ProcessDebugPort
GetThreadContext -> NtGetThreadContext
NtQueryInformationProcess with ProcessDebugPort/ProcessDebugObjectHandle
GetForegroundWindow -> NtUserGetForegroundWindow
GetWindowText -> GetWindowThreadProcessId -> NtUserQueryWindow
NtQuerySystemInformation with SystemModuleInformation
CONST CHAR* bad_list_driver_anti_debug[] =
{
ntice.sys
iceext.sys
Syser.sys
HanOlly.sys
extrem.sys
FRDTSC.SYS
fengyue.sys
};
VirtualProtect -> NtProtectVirtualMemory -> IsBadWritePtr -> DbgUiRemoteBreakin goto(jmp) LdrShutdownProcess
VirtualProtect -> NtProtectVirtualMemory -> DbgUiRemoteBreakin -> ret
Анти-дебаг в лоадере аналогичен в SDK за исключением SystemModuleInformation.
NtOpenSection + NtMapViewOfSection Map ntdll file(KnownDlls)(берёт только NtQueryInformationProcess)
NtSetInformationThread with ThreadHideFromDebugger
CheckRemoteDebuggerPresent -> ProcessDebugPort
GetThreadContext -> NtGetThreadContext
NtQueryInformationProcess with ProcessDebugPort/ProcessDebugObjectHandle
GetForegroundWindow -> NtUserGetForegroundWindow
GetWindowText -> GetWindowThreadProcessId -> NtUserQueryWindow
NtQuerySystemInformation with SystemModuleInformation
CONST CHAR* bad_list_driver_anti_debug[] =
{
ntice.sys
iceext.sys
Syser.sys
HanOlly.sys
extrem.sys
FRDTSC.SYS
fengyue.sys
};
VirtualProtect -> NtProtectVirtualMemory -> IsBadWritePtr -> DbgUiRemoteBreakin goto(jmp) LdrShutdownProcess
VirtualProtect -> NtProtectVirtualMemory -> DbgUiRemoteBreakin -> ret
Анти-дебаг в лоадере аналогичен в SDK за исключением SystemModuleInformation.
Anti-VM:
x64
Компьютер\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000 -> DriverDesc
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System -> SystemBiosVersion\VideoBiosVersion
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\ACPI\DSDT\VBOX__
GetSystemFirmwareTable(NtQuerySystemInformation with SystemFirmwareTableInformation) + BMSR
x32
in eax,dx ; eax - (68584D56)hXMV, dx - XV
in eax,dx eax - (564D5868)VMXh,dx - VX
Мне было лень полностью анализировать т.к они юзают что-то ещё и достать быстро не получилось:
Компьютер\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000 -> DriverDesc
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System -> SystemBiosVersion\VideoBiosVersion
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\ACPI\DSDT\VBOX__
GetSystemFirmwareTable(NtQuerySystemInformation with SystemFirmwareTableInformation) + BMSR
И как-то ешё трюк, который мне лень анализировать из-за нехватки времени
anti-sandbox(x32/x64):
GetModuleHandleA:
cmdvrt32.dll
SbieDll.dll(Hi,VMProtect)
Компьютер\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000 -> DriverDesc
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System -> SystemBiosVersion\VideoBiosVersion
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\ACPI\DSDT\VBOX__
GetSystemFirmwareTable(NtQuerySystemInformation with SystemFirmwareTableInformation) + BMSR
x32
in eax,dx ; eax - (68584D56)hXMV, dx - XV
in eax,dx eax - (564D5868)VMXh,dx - VX
Мне было лень полностью анализировать т.к они юзают что-то ещё и достать быстро не получилось:
Компьютер\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000 -> DriverDesc
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System -> SystemBiosVersion\VideoBiosVersion
Компьютер\HKEY_LOCAL_MACHINE\HARDWARE\ACPI\DSDT\VBOX__
GetSystemFirmwareTable(NtQuerySystemInformation with SystemFirmwareTableInformation) + BMSR
И как-то ешё трюк, который мне лень анализировать из-за нехватки времени
anti-sandbox(x32/x64):
GetModuleHandleA:
cmdvrt32.dll
SbieDll.dll(Hi,VMProtect)
Обфускация импорта
При трассировке видим следящее:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Нас должно зацепить следующее:
1)Способ получения начала секции
через:
call$+5
pop rbp
sub rbp,31E3F
2)Последняя операция с расшифровкой импорта:
00007FF7FDDFD631 or rax,reg ; decrypt address
3)Запихивание переменной в стек через push и при этом с 2 регистрами:
00007FF7FDE07979 | FF3428 | push qword ptr ds:[rax+rbp]
Я не вижу смысла деобфусцировать т.к можно понять логику и скажу следущее:
1)Протектор должен прочитать откуда-то зашифрованное значение и расшифровать(в моём случае оно находится в секции .winlicense) -> можно поставить PAGE GUARD hook
Первое чтение идёт как раз из push qword ptr ds:[rax+rbp] ; push qword ptr ds:[offset+start_secthion] (offset - 255B4)
Это делается из-за того,чтобы не убивать скорость выполнение программы т.к под VM займёт куда больше операций,а отсюда и времени
2)((res_offset - 7C4591CA) ^ 7D0AE1FA) | FFFFFFFF
Здесь идут операции :sub,xor,and
3)
push reg
mov qword ptr ss:[rsp],decrypt_address
При анализе я всегда встречал 3 пункта с верху(так же jmp,похоже,всегда вставляется даже при виртуализированном коде т.е начинать сканирование нужно от jmp),поэтому можно сделать логику для расшифровки импортов.
в x32:
Пожалуйста, авторизуйтесь для просмотра ссылки.
1)Встречаем так же push dword ptr ss:[ebp+eax]
2)В конце используется xor для расшифровки
3) Так же в упрощённом виде:
push reg
mov qword ptr ss:[esp],decrypt_address
Аналогично так же с возможностью с расшифровкой автоматический(поиск jmp + проверка команд и меньше ~150 операций перед ret), но только нужно проверять на xor eax,reg в x32
Обходим CRC file
Итак, немного пояснения, чтобы не было проблем в различии CRC.
CRC используется протекторами для обнаружения изменений в файле/в секции.
Основная идея CRC file - обнаружить практический любое изменение байт в файле,за исключением некоторых моментов в файле + где сохраняется значение.
CRC подвергается коллизии, но этот способ неудобен в данном случае для обхода т.к нужно анализировать сам алгоритм,а он может быть в VM.
Самый простой способ - найти где сохраняется итоговый результат CRC или подменить файл на оригинальный.
Themida использует следующий способ подсчёта:
Themida использует следующий способ подсчёта CRC файла:
GetModuleFileName
CreateFileW
GetFileSize
CreateFileMappingW
MapViewOfFile
UnmapViewOfFile
Можно просто подменять файл и это работает,но давайте избавимся от бесполезной работы.
В данном примере понадобится CTF exploler для создания новой секции и вставки оригинального файла в новую секцию.
File -> open -> secthion headers -> add secthion(file data) и кидаем оригинальный файл -> add secthion(empty space) -> у новой секции change secthion flag ->
Is executeble и ставим галочку т.к туда будем пихать наш код.
Можно написать dll без crt и скопировать код(единственно, что вам нужно будет сделать: сохранять почти все регистры и Rlag/Eflag) и передавать туда выполнение
(вы сможете хукнуть эти api раньше, например, чем будет вообще выполнена распаковка(изменение EP на свой код)).
В данном примере автор продемонстрирует патчинг vm-entry после вызова API.
Логика обхода(в данном случае была дефолтная VM т.е Tiger Rea):
1)Делаем подмену rax/eax после вызова MapViewOfFile и сохраняем оригинальное значение, если вызов вернул не NULL
2)Делаем подмену размер файла(это не обязательно, но будем пунктуальные в этом плане)
3)Возвращаем оригинальный выделенный адрес из MapViewOfFile в UnmapViewOfFile т.к иначе будет unmap нашей секции или по-простому: нам кирдык.
Ставим bp на MapViewOfFile,GetFileSize и проверяем вызывается ли vm-handler пару раз или только 1 раз.
В моём случае он вызывался только 1 раз.
Мы должны получить адрес выхода vm перед вызовом UnmapViewOfFile т.к это убьёт наш файл.
Ставим bp на обращение к последним байтам,чтобы не снимать ооооочень длинную трассировку и начинаем трассировку,пока не дойдём до вызова.
У автора много раз вызывалась vm-exit,поэтому нужно ставить условие на подмену rax/eax.
Будет прикреплён с файлом в качестве примера.
Краткий обзор файла и пример обхода crc file(будет прикреплены в качестве примера):
rva: header va : offset file
5E36E4 : 00000001405E36E4 : 5DE4E4 - filze size(vm-entry)
5CA074 : 00000001405CA074 : 5C4E74 - map file(vm-entry)
3A6E7 : 000000014003A6E7 : 354E7 - unmap file(vm-exit call api or jmp next code)
call GetFileSize -> vm-emtry -> jmp patch ->
mov eax,0x674E00 ;размер полученный в оригинальном файле с помощью GetFileSize
pushfq ; восстанавливаем украденные байты
sub rsp,0x8
jmp winlicense_test_protected_2_11.7FF7966A36E9
MapViewOfFile -> vm-entry -> jmp patch ->
mov qword ptr ds:[0x7FF79673A120],rax ;сохраняем значение относительно rip + offset
lea rax,qword ptr ds:[0x7FF79673B000] ; получаем адрес оригинального файла в секции относительно rip
pushfq ; восстанавливаем украденные байты
push 0x6FF5AC8B
jmp winlicense_test_protected_2_11.7FF79668A079
Тут вообще должна быть проверка сначала:
cmp rax,0
но весь рофл в том,что темида всё-равно будет читать, если даже при неудачном NTSTATUS
pop rcx ; rcx - наш адрес, который мы подменили
push rdx
lea rdx,qword ptr ds:[0x7FF79673B000]; получаем адрес скопированной нашего файла
cmp rcx,rdx;наш подменённый адрес
jne winlicense_test_protected_2_11.7FF79673A051
mov rcx,qword ptr ds:[0x7FF79673A120] ; подменяем, иначе будет худо нам
pop rdx
pop rax ; просто восстанавливаем украденные байты
popfq
ret 0x0
mov eax,0x674E00 ;размер полученный в оригинальном файле с помощью GetFileSize
pushfq ; восстанавливаем украденные байты
sub rsp,0x8
jmp winlicense_test_protected_2_11.7FF7966A36E9
MapViewOfFile -> vm-entry -> jmp patch ->
mov qword ptr ds:[0x7FF79673A120],rax ;сохраняем значение относительно rip + offset
lea rax,qword ptr ds:[0x7FF79673B000] ; получаем адрес оригинального файла в секции относительно rip
pushfq ; восстанавливаем украденные байты
push 0x6FF5AC8B
jmp winlicense_test_protected_2_11.7FF79668A079
Тут вообще должна быть проверка сначала:
cmp rax,0
но весь рофл в том,что темида всё-равно будет читать, если даже при неудачном NTSTATUS
pop rcx ; rcx - наш адрес, который мы подменили
push rdx
lea rdx,qword ptr ds:[0x7FF79673B000]; получаем адрес скопированной нашего файла
cmp rcx,rdx;наш подменённый адрес
jne winlicense_test_protected_2_11.7FF79673A051
mov rcx,qword ptr ds:[0x7FF79673A120] ; подменяем, иначе будет худо нам
pop rdx
pop rax ; просто восстанавливаем украденные байты
popfq
ret 0x0
Браво! Вы обошли CRC file ничего особо не делая!
Можно другими способами, но я продемонстрировал, который пришёл самый первый в голову автора.
в x32 почти аналогично.
Обходим Runtime CRC
или
Я купил себе машину, думал, круче не найти,
Откатал на ней неделю, сдохла, мать её ити!
Я называю так CRC т.к проверка может быть выполнена только в runtime самого вашего кода(CHECK_CODE_INTEGRITY ).
На самом деле CRC секции подсчитано и оно просто вычисляется заново & сравняется с готовым CRC.
Так же CRC runtime проверяет только патч ER секции(.text),а не другие и тем более VM(я не видел,чтобы темида его проверяла и сама пять ERW)
Итак, начинаем с того, что мы пробуем пропатчить любой байт в .text секции и мы получаем обнаружение.
Попробуем изменить безобидный code cave(int3) и получаем обнаружение:
Посмотри откуда идёт чтение:
видим такую ситуацию:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Вас должен сразу смутить следущий код:
00007FF6BFEC86DD | mov bl,0x7
Значение перезаписывается т.е оно сохраняется где-то сверху и как раз после чтения идёт запись в RW регион памяти!
Теперь ставим там bp на обращение и смотрим, откуда идёт чтение:
00007FF6C044CFB4 | add eax,dword ptr ds:[r11]
и
00007FF6C044CE4C | xor eax,dword ptr ds:[rdx]
Если посмотрим на график,то это просто мутированный код и нам повезло!
График:
В упрощённом виде:
loop_read:
00007FF6C044CFB4 | add eax,dword ptr ds:[r11]
00007FF6C03F9184 | rol eax,10
00007FF6C044CE2B | push rdx
00007FF6C044CE42 | add rdx,rsi
00007FF6C044CE4C | xor eax,dword ptr ds:[rdx]
00007FF6C044CE52 | pop rdx
00007FF6C044E2F3 | add rsi,4; +sizeof(ULONG)
00007FF6C044F16E | sub ecx,1
00007FF6C03C5C6D | test ecx,ecx
00007FF6C0403D1F | je winlicense_test_protected.stop_scan
00007FF6C0403D25 | jmp winlicense_test_protected.loop_read
stop_scan:
00007FF6C0433708 | 8945 E0 | mov dword ptr ss:[rbp-20],eax
00007FF6C044FE30 | FF75 E4 | push qword ptr ss:[rbp-1C]
00007FF6C044B12F | FF97 D9310600 | call qword ptr ds:[rdi+631D9]; прыжок в VM
Дальше нас особо код не интересует
*Часть кода пропущена и основная логика без этого понятна.
Итак,здесь просто происходит цикл, да ещё и не в VM! ЛЯПОТА!
Результат сохраняется в eax и давайте его попробуем пропатчить!
убираем все патчи -> получаем оригинальный результат CRC(eax - 00000000F84B2AA5) -> видим E9 00000000 | jmp winlicense_test_protected.7FF6C044F287(идёт после pop rax) и меняем на:
mov eax,0xF84B2AA5 (5 байт как раз)
Ура! Мы обошли CRC runtime!
В крайнем случае вы можете всегда найти все места откуда идёт чтение и постепенно пропатчит их/просто давать скопированный регион.
С x32 такая же история.
Анализ мутации кода
До мутации:
После:
и
Поэтапно будем разбирать:
1)Злоупотреблением jmp.
00007FF786810E86 | add rsp,8
00007FF786810E8A | jmp winlicense_test_protected.7FF786803559
00007FF786803559 | push 77CC7B6D
00007FF78680355E | mov qword ptr ss:[rsp],rsi
00007FF786803562 | mov rsi,rsp
00007FF786803565 | add rsi,8
00007FF786803569 | sub rsi,8
Если смотреть просто дизассемблированный код,то видим следящее:
00007FF78680355D ja winlicense_test_protected.7FF7868035A7
00007FF78680355F mov dword ptr ss:[rsp],esi
00007FF786803562 mov rsi,rsp
00007FF786803565 add rsi,8
00007FF786803569 sub rsi,8
00007FF786810E8A | jmp winlicense_test_protected.7FF786803559
00007FF786803559 | push 77CC7B6D
00007FF78680355E | mov qword ptr ss:[rsp],rsi
00007FF786803562 | mov rsi,rsp
00007FF786803565 | add rsi,8
00007FF786803569 | sub rsi,8
Если смотреть просто дизассемблированный код,то видим следящее:
00007FF78680355D ja winlicense_test_protected.7FF7868035A7
00007FF78680355F mov dword ptr ss:[rsp],esi
00007FF786803562 mov rsi,rsp
00007FF786803565 add rsi,8
00007FF786803569 sub rsi,8
2)Расшифровка в зашифрованном режиме:
00007FF7867EBB2B | 48:B8 AAB7EF7F00000000 | mov rax,7FF786170000
00007FF7867FD14C | 48:81EE 2749FF7E | sub rsi,7EFF4927
00007FF7867FD153 | 48:81EE CD8FFB17 | sub rsi,17FB8FCD
00007FF7867FD15A | 48:81C6 B7F1FC7F | add rsi,7FFCF1B7
00007FF7867FD161 | 48:81C6 C1A7F67F | add rsi,7FF6A7C1
00007FF7867FD168 | 48:81EE 41C1FF7B | sub rsi,7BFFC141
00007FF7867FD16F | 48:01C6 | add rsi,rax
00007FF7867FD172 | 48:81C6 41C1FF7B | add rsi,7BFFC141
00007FF7867FD179 | 48:81EE C1A7F67F | sub rsi,7FF6A7C1
00007FF7867FD180 | 48:81EE B7F1FC7F | sub rsi,7FFCF1B7
00007FF7867FD187 | 48:81C6 CD8FFB17 | add rsi,17FB8FCD
00007FF7867FD18E | 48:81C6 2749FF7E | add rsi,7EFF4927
Протектор использует это только к sub и add.
Суть и так ясна
3)Расшифровка констант,используя математические операции:
;some code
;some code
rcx будет преобразован в 140000000
Пожалуйста, авторизуйтесь для просмотра ссылки.
;some code
rcx будет преобразован в 140000000
Пожалуйста, авторизуйтесь для просмотра ссылки.
mov eax, 80008001- код,который была мутированная
5)mba
[rsp] - 0,rsi - 63B08FFAF8
00007FF7867C38DE | 48:333424 | xor rsi,qword ptr ss:[rsp] | | 00000063B08FFAE8: 0-> 0
00007FF7867C38E2 | 48:313424 | xor qword ptr ss:[rsp],rsi | | 00000063B08FFAE8: 0-> 63B08FFAF8
00007FF7867C38E6 | 48:333424 | xor rsi,qword ptr ss:[rsp] | rsi: 63B08FFAF8-> 0 | 00000063B08FFAE8: 63B08FFAF8-> 63B08FFAF8
Эта запись аналогична простому:xchg qword ptr ss:[rsp], rsi
6)Получить начальный адрес секции:
00007FF7861838AE | E8 00000000 | call winlicense_test_protected.7FF7861838B3;получаем адресс следующей инструкции(pop rbp)
00007FF7861838B3 | 5D | pop rbp
00007FF7861838B4 | 48:81ED B3B80000 | sub rbp,B8B3
00007FF7861838BB | C3 | ret
7)переход к новому адресу, используя ret
Трассировка:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Нас интересует только это:
7FF786170000 - PE файла
7FF7861AA888 - ret address
Пожалуйста, авторизуйтесь для просмотра ссылки.
Чтение самой секции:
00007FF7867EBB22 | FFB5 BEFD2F00 | push qword ptr ss:[rbp+2FFDBE] | rsp: DE5CD9FC20-> DE5CD9FC18 | 00007FF786477DBE: 7FF786170000-> 7FF786170000 000 |
00007FF7867EBB2A | 50 | push rax | rsp: DE5CD9FC10-> DE5CD9FC08 | 000000DE5CD9FC08: 0-> 1 |
00007FF7867EBB2B | 48:B8 AAB7EF7F00000000 | mov rax,7FEFB7AA | rax: 1-> 7FEFB7AA | |
00007FF7867EBB35 | 49:BE 191F2FFF00000000 | mov r14,FF2F1F19 | r14: 0-> FF2F1F19 | |
00007FF7867EBB3F | 49:29C6 | sub r14,rax | r14: FF2F1F19-> 7F3F676F | |
00007FF7867EBB42 | 58 | pop rax | rax: 7FEFB7AA-> 1 rsp: DE5CD9FC08-> DE5CD9FC10 | 000000DE5CD9FC08: 1-> 1 |
00007FF7867EBB43 | 4C:317424 08 | xor qword ptr ss:[rsp+8],r14 | | 000000DE5CD9FC18: 7FF786170000-> 7FF7F928676F |
00007FF7867EBB48 | 41:5E | pop r14 | rsp: DE5CD9FC10-> DE5CD9FC18 r14: 7F3F676F-> 0 | 000000DE5CD9FC10: 0-> 0 |
00007FF7867EBB4A | 48:8B0424 | mov rax,qword ptr ss:[rsp] | rax: 1-> 7FF7F928676F | 000000DE5CD9FC18: 7FF7F928676F-> 7FF7F928676F |
00007FF7867EBB4E | 48:83C4 08 | add rsp,8 | rsp: DE5CD9FC18-> DE5CD9FC20 | |
00007FF7867EBB52 | 48:35 6F673F7F | xor rax,7F3F676F | rax: 7FF7F928676F-> 7FF786170000 |
00007FF786803A1D | 48:C70424 A041BE3F | mov qword ptr ss:[rsp],3FBE41A0 | | 000000DE5CD9FC10: 0-> 3FBE41A0 |
00007FF786803A25 | 4C:8B3424 | mov r14,qword ptr ss:[rsp] | r14: 0-> 3FBE41A0 | 000000DE5CD9FC10: 3FBE41A0-> 3FBE41A0 |
00007FF786803A29 | 48:83C4 08 | add rsp,8 | rsp: DE5CD9FC10-> DE5CD9FC18 | |
00007FF786803A2D | 41:81F6 4B579B37 | xor r14d,379B574B | r14: 3FBE41A0-> 82516EB | |
00007FF786803A34 | 41:81EE 86ECFF6F | sub r14d,6FFFEC86 | r14: 82516EB-> 98252A65 | |
00007FF786803A3B | 41:83C6 01 | add r14d,1 | r14: 98252A65-> 98252A66 | |
00007FF786803A3F | 41:81F6 EE822698 | xor r14d,982682EE | r14: 98252A66-> 3A888 | |
00007FF786803A46 | 44:89F1 | mov ecx,r14d | rcx: 140000000-> 3A888 | |
00007FF786803A49 | 41:5E | pop r14 | rsp: DE5CD9FC18-> DE5CD9FC20 r14: 3A888-> 0 | 000000DE5CD9FC18: 0-> 0 |
00007FF786803A4B | E9 BD9E0000 | jmp winlicense_test_protected.7FF78680D | | |
00007FF78680D90D | 48:81C1 6AFAFB7F | add rcx,7FFBFA6A | rcx: 3A888-> 7FFFA2F2 | |
00007FF78680D914 | 48:81C1 C01D9B1B | add rcx,1B9B1DC0 | rcx: 7FFFA2F2-> 9B9AC0B2 | |
00007FF78680D91B | 48:81E9 528EEE6D | sub rcx,6DEE8E52 | rcx: 9B9AC0B2-> 2DAC3260 | |
00007FF78680D922 | 48:81E9 3DCADD3F | sub rcx,3FDDCA3D | rcx: 2DAC3260-> FFFFFFFFEDCE6823 | |
00007FF78680D929 | 48:01C1 | add rcx,rax | rcx: FFFFFFFFEDCE6823-> 7FF773E56823 | |
00007FF78680D92C | 48:81C1 3DCADD3F | add rcx,3FDDCA3D | rcx: 7FF773E56823-> 7FF7B3C33260 | |
00007FF78680D933 | 48:81C1 528EEE6D | add rcx,6DEE8E52 | rcx: 7FF7B3C33260-> 7FF821B1C0B2 | |
00007FF78680D93A | 48:81E9 C01D9B1B | sub rcx,1B9B1DC0 | rcx: 7FF821B1C0B2-> 7FF80616A2F2 | |
00007FF78680D941 | 48:81E9 6AFAFB7F | sub rcx,7FFBFA6A | rcx: 7FF80616A2F2-> 7FF7861AA888 |
Если упростить и взять только логику с ret,то:
;some code
call $+5
pop rax
sub rax,decrypt_offset_fix_start_secthion
push qword ptr ss:[rax+offset_themida.base_address];read secthion .winlicense and push
pop rax
add rax,decrypt_offset_next_code_ret
push rax;go next code
ret
Как видите,при мутации шифруются константы и используется сразу несколько математических операций,что,на мой взгляд, лучше той же мутации в VMP.
Это больше в качестве маленького и бесполезного бонуса т.к у автора мало времени на дальнейший анализ...
Слегка анализируем VM(Tiger Lite)
Пожалуйста, авторизуйтесь для просмотра ссылки.
005F | 00007FF6D41429BD | E8 00000000 | call tiger_lite.7FF6D41429C2 | rsp: 72D32FFDC0-> 72D32FFDB8 | 00000072D32FFDB8: 6CF3AC79-> 7FF6D41429C2
00EC | 00007FF6D4142BD5 | 48:83E9 05 | sub rcx,5 | rcx: 7FF6D41429C2-> 7FF6D41429BD |
00ED | 00007FF6D4142BD9 | 48:81E9 BD290F00 | sub rcx,F29BD | rcx: 7FF6D41429BD-> 7FF6D4050000 |
rcx 7FF6D4050000 - base address
Темида получает адрес текущей секции через call $+5
2)Увеличить RSP:
1)
00A2 | 00007FF6D4142ABC | 48:89E0 | mov rax,rsp | rax: 0-> 72D32FFD70 |
00A3 | 00007FF6D4142ABF | 48:83C0 08 | add rax,8 | rax: 72D32FFD70-> 72D32FFD78 |
00A4 | 00007FF6D4142AC3 | 48:2D 08000000 | sub rax,8 | rax: 72D32FFD78-> 72D32FFD70 |
00A5 | 00007FF6D4142AC9 | 48:870424 | xchg qword ptr ss:[rsp],rax | rax: 72D32FFD70-> 0 | 00000072D32FFD70: 0-> 72D32FFD70
00A6 | 00007FF6D4142ACD | 48:8B2424 | mov rsp,qword ptr ss:[rsp]
2)
0205 | 00007FF6D41431BC | 41:55 | push r13 | rsp: 72D32FFD40-> 72D32FFD38 | 00000072D32FFD38: 0-> 0
0206 | 00007FF6D41431BE | 49:89E5 | mov r13,rsp | r13: 0-> 72D32FFD38 |
0207 | 00007FF6D41431C1 | 49:83C5 08 | add r13,8 | r13: 72D32FFD38-> 72D32FFD40 |
0208 | 00007FF6D41431C5 | 49:81C5 08000000 | add r13,8 | r13: 72D32FFD40-> 72D32FFD48 |
0209 | 00007FF6D41431CC | 4C:872C24 | xchg qword ptr ss:[rsp],r13 | r13: 72D32FFD48-> 0 | 00000072D32FFD38: 0-> 72D32FFD48
020A | 00007FF6D41431D0 | 48:8B2424 | mov rsp,qword ptr ss:[rsp] | rsp: 72D32FFD38-> 72D32FFD48 | 00000072D32FFD38: 72D32FFD48-> 72D32FFD48
3)Расшифровка таблицы переходов via VM handlers:
Пожалуйста, авторизуйтесь для просмотра ссылки.
BCF6D + base_address
0242 | 00007FF6D41432CE | 89FE | mov esi,edi | rsi: 0-> 6BBFDC58 |
0244 | 00007FF6D41432D1 | 81CE CB19FF0D | or esi,DFF19CB | rsi: 6BBFDC58-> 6FFFDDDB |
0245 | 00007FF6D41432D7 | F7D6 | not esi | rsi: 6FFFDDDB-> 90002224 |
0246 | 00007FF6D41432D9 | 81C6 A7D2F33F | add esi,3FF3D2A7 | rsi: 90002224-> CFF3F4CB |
0247 | 00007FF6D41432DF | 81EE 774C0090 | sub esi,90004C77 | rsi: CFF3F4CB-> 3FF3A854 |
0248 | 00007FF6D41432E5 | 89F3 | mov ebx,esi | rbx: 0-> 3FF3A854 |
024B | 00007FF6D41432EF | 81C3 EA570CC0 | add ebx,C00C57EA | rbx: 3FF3A854-> 3E |
0005 | 00007FF6D4465815 | 48:BE DB00000000000000 | mov rsi,DB | rsi: 0-> DB |
0253 | 00007FF6D414347B | 48:8B1C24 | mov rbx,qword ptr ss:[rsp] | rbx: 3E-> DB
025B | 00007FF6D414349E | 48:C1E3 03 | shl rbx,3 | rbx: DB-> 6D8 |
0260 | 00007FF6D41434BA | 48:01D8 | add rax,rbx | rax: 7FF812ADF201-> 7FF812ADF8D9 |
0265 | 00007FF6D41434D5 | FF20 | jmp qword ptr ds:[rax] | | 00007FF6D410D645: 7FF6D40B98D5-> 7FF6D40B98D5
mov rax,6D8+ (BCF6D + base_address)
jmp [rax];handle execute
Основная цель выполнение vm-handler'ов и при этом данный способ требует от реверсера найти все vm-handler'ы и фиксить их,если мы распаковали программу:
Статья подошла к своему концу из-за нехватки времени.
Be robbers and ravagers as long as you cannot be rulers and owners, you men of knowledgel...
Пожалуйста, авторизуйтесь для просмотра ссылки.
:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
Последнее редактирование: