Гайд VMProtect Black☭ut

✊Rot Front✊
Пользователь
Статус
Оффлайн
Регистрация
2 Июл 2020
Сообщения
131
Реакции[?]
256
Поинты[?]
84K
Вступление

Основная проблема обхода функций протекторов – ручной обход т,е обход без автоматизации. Это замедляед анализ и приходится повторять многие моменты. Автор решил из-за этого написать небольшую статью,чтобы напомнить, что некоторые моменты можно легко обойти + автора забавляет, что для обхода anti-debug для Rin-3 используют Ring-0 т.е пытаются задавить таракана зачем-то танком. Сегодня мы заглянем под капот VMP и напишем обход для SDK функций и функции лоадера VMP(anti-debug,anti-vm, list import,spoof syscall_id and e.t.c).

Анализу будет больше про x64 кода(больше 3.7.x-3.8.x, но большинство моментов аналогичны для более ранних версии(например, обход SDK CRC)) т.к аналогичные действия нужно будет сделать для x32 + не вижу смысла делать раздутый код. Будет показана только логика,а остальное можете посмотреть на Github т.к уже был один раз, когда впихнул много и получилась неудачная статья(из-за ограничений на количество символов).

Важный момент!Это больше является PoC,нежели готовым к вставки для обхода на мой взгляд, но я не буду спорить с читателем.



Глава 1 Ломаем SDK или Ice to meet you
1.1 Anti-debug или возвращение к технике Off Balance

1.1.png

Чем раздражителен VMP?

Во-первых, автор зачастую хитрит и использует WinApi/CRT оболочки, но об этом поговорим во 2 главе и из-за этого используется минимум NtApi/WinApi,а попытка построить anti-debug на manual syscall раздражает,но иногда вдохновляет + использование BugCheck функций для обнаружение хуков от anti-anti-debug tool может заставить вас потратить время.

Во-вторых, своей виртуализацией.

Не словом, а делом мы докажем эффективность отсутствия нужды анализа этого бреда. Это заставит докопаться до всего того, где отсутствует виртуализация + покажет разные способы к решению проблемы.

Автор уже затрагивал эту проблему в статье об обходе CRC протекторов.

VMP начиная ~ с версии 3.5.1 начинает использовать single step для обнаружения HWBP текущего потока:
VMP SDK anti-debug single step.png

Основная проблема в расшифровке ret_address т.к адрес к коду программы, после выполнения SDK функции:
VMP SDK anti-debug decrypt exit SDK.png

Благодаря этому, можно написать простой хук:

  1. Устанавливаем Veh хук
  2. Проверка ExceptionCode == STATUS_SINGLE_STEP
  3. Проверяем,что перед nop инструкция cpuid,rdtsc(на самом деле нужно проверить на адрес из какой секции вызвано,но это на усмотрения читателя)
  4. Сканируем стек, на ret_address к коду юзера
  5. Устанавливаем bp(0xCC) хук(скопируйте инструкцию) или можете заменить на свой хук(рекомендуется вызывать исключение т.е скопировать,а потом переписать ret_address на не валидный => хук обработки на уничтожение указателей[1] => не тригерим CRC
  6. Обработчик для исключения(у меня:ExceptionCode == STATUS_BREAKPOINT)
  7. Возвращаем в al = 0, прыгаем к следующему коду и победа?(сюрпри-сюприз,но это будет обходить anti-vm ещё т.к anti-debug использует только rdtsc, но будет уточнение чуть позже)

Тут есть огрех из-за того, что не хочу пихать Unicore Engine т.к Dll сильно раздувается, но рекомендую её впихнуть и написать проверку на ret_address т.к в x32 из-за особенности обработки исключения VMP excepthion смещение(ret_address) будет на > 0x4xx т.е будет найден не тот адрес и эмуляция решает проблему, но в пункте 2.2 показывается другой способ обхода этого чуда.
P.S В качестве примера используется Veh хук,но можно уйти на более ранний и менее заметный перехват в обработки исключения (напримре,Wow64PrepareForException).



1.2 CRC SDK
Аналогичная ситуация с обходом SDK anti-debug т.к автор использует NtQueryVirtualMemory и
в более ранних версиях VirtualQuery(добавьте хук,если понадобится).
Тут появляется неприятный момент т.к если функция будет просто вызываться из виртуализированного кода и это будет не из SDK VMP вызов,то мы вернём слегка иной статус,хотя для NtApi это не будет влиять, но напишем проверку для практики.

  1. Проверка откуда вызвалось(из нашего модуля,но лучше написать для секции VMP)
  2. ProcessHandle == NtCurrentProcess && MemoryInformationClass == MemoryBasicInformation
  3. Вот тут главный момент т.к у нас нет прямого доступа к rsp,но как бы и есть,но мы хотим просканировать строчку NtQueryVirtualMemory.
  4. Берём &MemoryInformationLength и отнимаем смещение 0x30 т.к 5 аргумент в x64 rsp + 0x28 и смещение текущего указателя на rsp.
  5. Если строка существует, то сканируем стек и ползучем ret_address и возвращаем al = 1.


Забавный момент: можно это сроку поменять на другой NtApi и VMP будет вызывать другой NtApi.

Изменение строки:


Вызов NtApi,который VMP не использует:


Ну и вишенка на торте!
Муха в котлете, заноза в заднице, ложка дёгтя в бочке дёгтя:

Протектор проверяет целостность кода до того,как это сделает SDK, даже если не включена проверка целостности в лоадере т.е неприятный подарок.
Но есть и приятный момент т.к это значение имеет 2 значения только т.е hash_detect_patch и hash_no_detect_patch т.е можете сделать наглый хук,если потребуется.
Довольно странное решение у автора,но пусть будет так. На самом деле эти значения есть в том же anti-debug, но у меня большие сомнения , что у вас их получится затригирить, если вы не творите полный бред.
Единственный вариант, который придумал – ручное нахождение из-за обращении(PG hook) RW памяти(обычно > PAGE_SIZE * 4) и это именно hash,который будет разный. Короче, лучше патчить после подсчёта CRC в лоадере, если вам лень делать лишние и бесполезные действия(или просто использовать HWBP).



1.3 Anti-vm SDK
На самом деле обход anti-debug обходит и anti-vm SDK,но есть пару проблем, которые автор скажет,но не будет решать т.к
  • проблема требует сканировать vm_cpuid и хукать их,но в том же VMWare решается проблема настройкой конфига,как и VirtualBox(речь про вызов cupid с eax = 1 и проверка 31 бита в ecx).
  • проблему требует подделки исправление для TF exception_info->ExceptionRecord->ExceptionAddress на адрес с опкодом nop для тогоже VirtualBox, но я им не пользуюсь + эта ошибка гипервизора и довольно странно, что её не исправляют.

Чтобы не было скучно,автор упомянет сканирование vm-строк,которые вызываются NtQuerySystemInformation с SystemFirmwareTableInformation (RSMB). План надёжный, как швейцарские часы т.е можно заставить возвращать, что их попросту нет:

  1. Сканирование инструкции mov rbx,qword ptr ss:[rsp+8](48 8B 5C 24)
  2. Дизассемблируем и проверяем,что это наша функция
  3. Находим mov al,1 и переписываем на mov al,0
  4. Победа?
P.S Обращение к буферу можно определить с помощью обнаружением буфера при вызове manual syscall NtQuerySystemInformation и установки HWBP



Глава 2 Ломаем лоадер


Вот тут по большей части у всех начинаются проблемы т.к довольно неприятно ковырять это творение.
Погнали постепенно разбираться что к чему!


2.1 Импорт


Недавно colby57 обновил и выпустил тулзу для фикса импорта на основе эмуляции. Правда,тут есть неприятная проблема, которую указал автор.
Импорт будет расшифрован импорт только в .text секции,а это значит что вирта будет вертеть данную тулзу на все 360 градусов,если обфусцированный вызов под ней. Мы пойдём слегка по иному пути,но сначала слова.
Тут автор затронет халтуру разработчика т.к давайте изучим обфускацию импортов у VMP:

3.0.9:
import_obf_3.0.9_and_3_5:
00 | 00007FF63F25109E | E8 DD5A1900              | call bin_64.0.9.vmp.7FF63F3E6B80
01 | 00007FF63F3E6B80 | 90                       | nop                         
02 | 00007FF63F3E6B81 | 41:0FBFCF                | movsx ecx,r15w             
03 | 00007FF63F3E6B85 | 41:8AC8                  | mov cl,r8b                 
04 | 00007FF63F3E6B88 | 41:0F47C8                | cmova ecx,r8d               
05 | 00007FF63F3E6B8C | 59                       | pop rcx                     
06 | 00007FF63F3E6B8D | E9 CF82F3FF              | jmp bin_64.0.9.vmp.7FF63F31EE61
07 | 00007FF63F31EE61 | 48:870C24                | xchg qword ptr ss:[rsp],rcx 
08 | 00007FF63F31EE65 | E9 D99D0D00              | jmp bin_64.0.9.vmp.7FF63F3F8C43
09 | 00007FF63F3F8C43 | 51                       | push rcx                   
0A | 00007FF63F3F8C44 | 0F9FC1                   | setg cl                     
0B | 00007FF63F3F8C47 | 48:8D0D 7D8FE5FF         | lea rcx,qword ptr ds:[7FF63F251BCB]
0C | 00007FF63F3F8C4E | E9 3DC8EDFF              | jmp bin_64.0.9.vmp.7FF63F2D5490
0D | 00007FF63F2D5490 | 48:8B89 96FA1B00         | mov rcx,qword ptr ds:[rcx+1BFA96]
0E | 00007FF63F2D5497 | E9 B06D0300              | jmp bin_64.0.9.vmp.7FF63F30C24C
0F | 00007FF63F30C24C | 48:8D89 D821AE71         | lea rcx,qword ptr ds:[rcx+71AE21D8]
10 | 00007FF63F30C253 | 48:870C24                | xchg qword ptr ss:[rsp],rcx 
11 | 00007FF63F30C257 | E9 24D7FFFF              | jmp bin_64.0.9.vmp.7FF63F309980
12 | 00007FF63F309980 | C3                       | ret

3.5
01 | 00007FF7A57710AC | E8 5FFFFFFF              | call bin_64_3.5.0.vmp.7FF7A5771010
02 | 00007FF7A5771010 | 48:894C24 08             | mov qword ptr ss:[rsp+8],rcx
03 | 00007FF7A5771015 | 48:895424 10             | mov qword ptr ss:[rsp+10],rdx
04 | 00007FF7A577101A | 4C:894424 18             | mov qword ptr ss:[rsp+18],r8
05 | 00007FF7A577101F | 4C:894C24 20             | mov qword ptr ss:[rsp+20],r9
06 | 00007FF7A5771024 | 53                       | push rbx                 
07 | 00007FF7A5771025 | 57                       | push rdi                 
08 | 00007FF7A5771026 | 48:83EC 38               | sub rsp,38               
09 | 00007FF7A577102A | B9 01000000              | mov ecx,1                 
0A | 00007FF7A577102F | 48:8D7C24 58             | lea rdi,qword ptr ss:[rsp+58]
0B | 00007FF7A5771034 | E8 72350500              | call bin_64_3.5.0.vmp.7FF7A57C45AB
0C | 00007FF7A57C45AB | 90                       | nop                       
0D | 00007FF7A57C45AC | 50                       | push rax                 
0E | 00007FF7A57C45AD | 41:0FB7C0                | movzx eax,r8w             
0F | 00007FF7A57C45B1 | 40:8AC6                  | mov al,sil               
10 | 00007FF7A57C45B4 | 98                       | cwde                     
11 | 00007FF7A57C45B5 | 48:8B4424 08             | mov rax,qword ptr ss:[rsp+8]
12 | 00007FF7A57C45BA | 48:8D40 01               | lea rax,qword ptr ds:[rax+1]
13 | 00007FF7A57C45BE | E9 C57C0500              | jmp bin_64_3.5.0.vmp.7FF7A581C288
14 | 00007FF7A581C288 | 48:894424 08             | mov qword ptr ss:[rsp+8],rax
15 | 00007FF7A581C28D | 48:8D05 EB4FF5FF         | lea rax,qword ptr ds:[7FF7A577127F
16 | 00007FF7A581C294 | E9 200BFEFF              | jmp bin_64_3.5.0.vmp.7FF7A57FCDB9
17 | 00007FF7A57FCDB9 | 48:8B80 AB060400         | mov rax,qword ptr ds:[rax+406AB]
18 | 00007FF7A57FCDC0 | E9 C2CA0300              | jmp bin_64_3.5.0.vmp.7FF7A5839887
19 | 00007FF7A5839887 | 48:8D80 690A0074         | lea rax,qword ptr ds:[rax+74000A69
1A | 00007FF7A583988E | E9 04B10600              | jmp bin_64_3.5.0.vmp.7FF7A58A4997
1B | 00007FF7A58A4997 | 48:870424                | xchg qword ptr ss:[rsp],rax
1C | 00007FF7A58A499B | E9 00E6FAFF              | jmp bin_64_3.5.0.vmp.7FF7A5852FA0
1D | 00007FF7A5852FA0 | C3                       | ret
3.8.1:
import_obf_3.8.1:
00 | 00007FF7B7811079 | E8 20470000              | call bin_64_3_8_1.vmp.7FF7B781579E
01 | 00007FF7B781579E | 50                       | push rax                       
02 | 00007FF7B781579F | 41:56                    | push r14                       
03 | 00007FF7B78157A1 | E8 72C10000              | call bin_64_3_8_1.vmp.7FF7B7821918
04 | 00007FF7B7821918 | 9C                       | pushfq                         
05 | 00007FF7B7821919 | 49:BE B28D379E99702E23   | mov r14,232E70999E378DB2       
06 | 00007FF7B7821923 | 57                       | push rdi                       
07 | 00007FF7B7821924 | 4D:8DB426 A3E322EA       | lea r14,qword ptr ds:[r14-15DD1C5D]
08 | 00007FF7B782192C | 48:BF 27442ED038039DBA   | mov rdi,BA9D0338D02E4427       
09 | 00007FF7B7821936 | 48:81FF 9B278BFA         | cmp rdi,FFFFFFFFFA8B279B       
0A | 00007FF7B782193D | 48:8B4424 28             | mov rax,qword ptr ss:[rsp+28] 
0B | 00007FF7B7821942 | E8 E9F51A00              | call bin_64_3_8_1.vmp.7FF7B79D0F30
0C | 00007FF7B79D0F30 | 41:50                    | push r8                       
0D | 00007FF7B79D0F32 | 4D:8BC6                  | mov r8,r14                     
0E | 00007FF7B79D0F35 | E8 E37E0000              | call bin_64_3_8_1.vmp.7FF7B79D8E1D
0F | 00007FF7B79D8E1D | 55                       | push rbp                       
10 | 00007FF7B79D8E1E | 49:8BEE                  | mov rbp,r14                   
11 | 00007FF7B79D8E21 | 48:8D40 01               | lea rax,qword ptr ds:[rax+1]   
12 | 00007FF7B79D8E25 | 41:0F9EC0                | setle r8b                     
13 | 00007FF7B79D8E29 | 6645:2BC6                | sub r8w,r14w                   
14 | 00007FF7B79D8E2D | 41:50                    | push r8                       
15 | 00007FF7B79D8E2F | E8 3174E4FF              | call bin_64_3_8_1.vmp.7FF7B7820265
16 | 00007FF7B7820265 | 66:C1E5 B8               | shl bp,B8                     
17 | 00007FF7B7820269 | E8 DD541A00              | call bin_64_3_8_1.vmp.7FF7B79C574B
18 | 00007FF7B79C574B | 4C:23C7                  | and r8,rdi                     
19 | 00007FF7B79C574E | 48:894424 60             | mov qword ptr ss:[rsp+60],rax 
1A | 00007FF7B79C5753 | 40:F6D5                  | not bpl                       
1B | 00007FF7B79C5756 | C1ED FC                  | shr ebp,FC                     
1C | 00007FF7B79C5759 | 66:C1E7 A1               | shl di,A1                     
1D | 00007FF7B79C575D | 48:81A4EC D0FFFFFF 3957B | and qword ptr ss:[rsp+rbp*8-30],79B7573
1E | 00007FF7B79C5769 | 41:55                    | push r13                       
1F | 00007FF7B79C576B | 41:50                    | push r8                       
20 | 00007FF7B79C576D | 48:814424 10 F4DFFFFF    | add qword ptr ss:[rsp+10],FFFFFFFFFFFFD
21 | 00007FF7B79C5776 | 56                       | push rsi                       
22 | 00007FF7B79C5777 | E8 08D00200              | call bin_64_3_8_1.vmp.7FF7B79F2784
23 | 00007FF7B79F2784 | 44:0B4424 11             | or r8d,dword ptr ss:[rsp+11]   
24 | 00007FF7B79F2789 | 0F8D 1F56E2FF            | jge bin_64_3_8_1.vmp.7FF7B7817DAE
25 | 00007FF7B79F278F | 48:BE 0351299631AC12E9   | mov rsi,E912AC3196295103       
26 | 00007FF7B79F2799 | 48:8B84AC 00000000       | mov rax,qword ptr ss:[rsp+rbp*4]
27 | 00007FF7B79F27A1 | 48:8B8468 F0FFFFFF       | mov rax,qword ptr ds:[rax+rbp*2-10]
28 | 00007FF7B79F27A9 | 0F82 9C100600            | jb bin_64_3_8_1.vmp.7FF7B7A5384B
29 | 00007FF7B79F27AF | 49:BD 9B961DD20F50BBD6   | mov r13,D6BB500FD21D969B       
2A | 00007FF7B79F27B9 | 6644:214424 10           | and word ptr ss:[rsp+10],r8w   
2B | 00007FF7B79F27BF | 856C24 30                | test dword ptr ss:[rsp+30],ebp 
2C | 00007FF7B79F27C3 | 4A:8D8400 D6E8229E       | lea rax,qword ptr ds:[rax+r8-61DD172A]
2D | 00007FF7B79F27CB | 48:0FCF                  | bswap rdi                       
2E | 00007FF7B79F27CE | 6644:8BC5                | mov r8w,bp                     
2F | 00007FF7B79F27D2 | E8 D999E3FF              | call bin_64_3_8_1.vmp.7FF7B782C1B0
30 | 00007FF7B782C1B0 | 0F86 757D1900            | jbe bin_64_3_8_1.vmp.7FF7B79C3F2B
31 | 00007FF7B782C1B6 | F65424 38                | not byte ptr ss:[rsp+38]       
32 | 00007FF7B782C1BA | 48:878424 80000000       | xchg qword ptr ss:[rsp+80],rax 
33 | 00007FF7B782C1C2 | 48:8B7C24 60             | mov rdi,qword ptr ss:[rsp+60] 
34 | 00007FF7B782C1C7 | 49:81C6 85A8A925         | add r14,25A9A885               
35 | 00007FF7B782C1CE | F7D6                     | not esi                       
36 | 00007FF7B782C1D0 | 66:F7DE                  | neg si                         
37 | 00007FF7B782C1D3 | 6641:C1EE 42             | shr r14w,42                   
38 | 00007FF7B782C1D8 | 4C:8BACAC 00000000       | mov r13,qword ptr ss:[rsp+rbp*4]
39 | 00007FF7B782C1E0 | 4C:037424 38             | add r14,qword ptr ss:[rsp+38] 
3A | 00007FF7B782C1E5 | C74424 38 A2A6BBED       | mov dword ptr ss:[rsp+38],EDBBA6A2
3B | 00007FF7B782C1ED | 41:C0E6 C7               | shl r14b,C7                   
3C | 00007FF7B782C1F1 | 814C24 38 B9A2BB03       | or dword ptr ss:[rsp+38],3BBA2B9
3D | 00007FF7B782C1F9 | 66:33F6                  | xor si,si                     
3E | 00007FF7B782C1FC | C1AC34 38002A96 E3       | shr dword ptr ss:[rsp+rsi-69D5FFC8],E3
3F | 00007FF7B782C204 | 4B:8DACF6 0AF4036F       | lea rbp,qword ptr ds:[r14+r14*8+6F03F40
40 | 00007FF7B782C20C | 0F87 08731A00            | ja bin_64_3_8_1.vmp.7FF7B79D351A
41 | 00007FF7B79D351A | 48:8B7424 10             | mov rsi,qword ptr ss:[rsp+10] 
42 | 00007FF7B79D351F | 49:F7D8                  | neg r8                         
43 | 00007FF7B79D3522 | 66:0FBE6C24 1D           | movsx bp,byte ptr ss:[rsp+1D] 
44 | 00007FF7B79D3528 | 45:84C6                  | test r14b,r8b                 
45 | 00007FF7B79D352B | 0FCD                     | bswap ebp                     
46 | 00007FF7B79D352D | 44:0F497424 3B           | cmovns r14d,dword ptr ss:[rsp+3B]
47 | 00007FF7B79D3533 | 0F9E846C 6E64FEFF        | setle byte ptr ss:[rsp+rbp*2-19B92]
48 | 00007FF7B79D353B | 49:85EE                  | test r14,rbp                   
49 | 00007FF7B79D353E | E8 28A20500              | call bin_64_3_8_1.vmp.7FF7B7A2D76B
4A | 00007FF7B7A2D76B | 48:8B6C24 48             | mov rbp,qword ptr ss:[rsp+48] 
4B | 00007FF7B7A2D770 | 0F85 A901F7FF            | jne bin_64_3_8_1.vmp.7FF7B799D91F
4C | 00007FF7B799D91F | 0F86 83E90000            | jbe bin_64_3_8_1.vmp.7FF7B79AC2A8
4D | 00007FF7B799D925 | 6644:854424 43           | test word ptr ss:[rsp+43],r8w 
4E | 00007FF7B799D92B | E8 FD000000              | call bin_64_3_8_1.vmp.7FF7B799DA2D
4F | 00007FF7B799DA2D | 41:0F91C0                | setno r8b                     
50 | 00007FF7B799DA31 | 4C:8BB424 88000000       | mov r14,qword ptr ss:[rsp+88] 
51 | 00007FF7B799DA39 | 814C24 28 84C59D60       | or dword ptr ss:[rsp+28],609DC584
52 | 00007FF7B799DA41 | 0F934424 28              | setae byte ptr ss:[rsp+28]     
53 | 00007FF7B799DA46 | 44:334424 4A             | xor r8d,dword ptr ss:[rsp+4A]   
56 | 00007FF7B799DA63 | 4E:8B8404 6A1D8A98       | mov r8,qword ptr ss:[rsp+r8-6775E296]
57 | 00007FF7B799DA6B | F75424 28                | not dword ptr ss:[rsp+28]     
58 | 00007FF7B799DA6F | E8 21570000              | call bin_64_3_8_1.vmp.7FF7B79A3195
59 | 00007FF7B79A3195 | 48:8D6424 48             | lea rsp,qword ptr ss:[rsp+48] 
5A | 00007FF7B79A319A | C2 1800                  | ret 18                         
5B | 00007FF7B79914B4 | FF7424 18                | push qword ptr ss:[rsp+18]     
5C | 00007FF7B79914B8 | 9D                       | popfq                         
5D | 00007FF7B79914B9 | 48:8D6424 30             | lea rsp,qword ptr ss:[rsp+30] 
5E | 00007FF7B79914BE | C3                       | ret
Не вижу смысл дальше показывать т.к мы можем заметить 2 вещи:

  1. Вызов всегда через call
  2. Всегда присутствует xchg
Забавно,что автор просто пытается ломать тулзы, а не обдумать следящую проблему:
Как же они её находят, если я накинул обфускации слегка и это КПД стремится к NULL?
Вопрос, конечно, риторический.

Итак,что же делать? Автор идёт старинным способом т.е пишем хук для получения списка импорта + пару идей для хука даже для текущей версии.

Поскольку это тригерит CRC от лоадера,то просто перепишите на HWBP хук,чтобы не париться,но меньше слова и больше дел!

~Включая до версии 3.8.1 можно было вообще кайфовать т.к можно было перехватывать адрес импорта и вернуть адрес на своей функции => $$$бесплатно$$$ хукать импорт из-за просто мутации оболочки получения адреса(я упоминал об этом в 1 чате).
Её можно легко найти, если поставить на PE ntdll PG хук обращение:





Однако, начиная с версии 3.8.2 из-за слива автор решил кинуть эту функцию под вирту,чем усложнил задачу,но всё-равно это не мешает.

Если проанализировать, то используется VMP_STR_CMP т.е сравнивается 2 строки и возвращается статус сравнения. Она только под мутацией, поэтому халява



План действий:

  1. Поиск по паттерну mov qword ptr ss:[rsp+8],rbx(48 89 5C 24 08)
  2. Дизассемблируем и проверяем,что это нужна функция
  3. Устанавливаем хук

Это не идеальное решение, но у нас есть список импорта.

Если говорить про правильное решение и эмуляцию,то:
Поиск lea reg64,qword ptr ds:[imm](48 8D) для версий ниже ~3.6 и call rip+offset(E8)

Собрать этот список в .vmp0 секции и можно наплевать на .text секцию т.к автор продолжает получать rip через call в обфусцированном вызове(не при переходе из .text в vmp0,а во время выхода обфусцированного чуда),поэтому он облегчает задачу.

Эмуляция и проверка, что указатель указывает на импорт(можно эмулировать до инструкции xchg) и проверка на ret,что мы на импорте. Но мне лень делать + я уже и без этого тащу 3 либы, поэтому это всё на усмотрение читателя,хотя логику сказал.

Так же можно добавить экспорт в свою библиотеку с тем же названием или хукать strcmp, хукнуть GetModuleHandleA и вернуть свою библиотеку,если там нас интересующий импорт. Это так, для шутки…


2.2 Драка за уничтожение syscall_id


Фирма «VMProtect» очень не хотела, чтобы реверсеры использовали для обхода anti-debug
Пожалуйста, авторизуйтесь для просмотра ссылки.
и плагины по типу
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
но открытие,что у VMP идёт смешная борьба за manual syscall, повлекло бы за собой совершенно забавное представление о ней.

Стоп. Тут нужно остановиться. Как большинство читателей знаю, VMP начал использовать ~ с версии
Пожалуйста, авторизуйтесь для просмотра ссылки.
. За этим забавно можно проследить в обновлениях
Пожалуйста, авторизуйтесь для просмотра ссылки.
.
В качестве примера, приведу дополнения код SharpOD,который патчит ProductVersion && FileVersion и берёт при загрузке ntdll(хук orig_NtMapViewOfSection) подделанные версии ProductVersion && FileVersion.
Простыми словами, все используют подделку OSBuildNumber в PEB, подделку в ресурсах ntdll ProductVersion, FileVersion. И чтобы жизнь малиной не казалось, VMP начал получать NtBuildNumber из PKUSER_SHARED_DATA и удивила всех подружек:



Это можно обойти и здесь будет показано,но с костылём, поскольку автор лентяй. Да и почему я должен быть честным в обходе, когда все хитрят?

Во-первых, потребуется подменить выше перечисленное( OSBuildNumber в PEB и ProductVersion/FileVersion),но и ещё подделать OSMajorVersion и до полного счастья OSMinorVersion для обхода NtBuildNumber.

Теперь происходит map ntdll.dll с помощью NtOpenSecthion и NtMapViewOfSection,а если это не получится то старый набор из NtOpenFile, NtCreateSection, NtMapViewOfSection. Если зафейлить и это,то автор кидает MessageBox через NtRaiseException и это вообще офигенно, поскольку так делать не надо, но разработчику виднее.

Вот на этом моменте автор застрял т.к пытался уничтожить, что только попалось и вот результат,что можо сделать при плохом настроении, будучи выпившим т.к обращение было только к PE мапнутой ntdll и секции .rdata.
Поэтому переходим к плану B, хотя это первоначальный способ обхода anti-debug в лоадере,но тут гарантировано будут уничтожены syscall_id. В чём шутка? Можно просто пройтись по стеку и уничтожить syscall_id,после unmap ntdll и как бы так делать нельзя. У всё работало, хотя это вообще варварский метод(но кто будет судить за такой метод честной игры?).

Просто повторите в пункте 1.2,но теперь просто уничтожайте syscall_id,но проблема в том,что в x64 они находится в верхушке стека т.к нужно вверх и вниз стека пройтись для полного счасть,как бы ни казалось странным.
Теперь можно пить медовуху у курить бамбук!



2.3 Ani-debug loader или Ivan...You are drunk


Что помогает реверсерам уже имевших с анализом VMP,обходить anti-debug и что VMP делает первым действием, как я упомянул в одной своей статье?

Верно!VMP проверяет всегда NtCurrentPeb()->BeingDebugged и тут просто устанавливаем HWBP hook на обращение.

Возвращаясь к прошлому пункту(2.2),первоначально использовался этот метод для уничтожения syscall_id NtQueryInformationProcess и NtSetInformationThread.

План действий аналогично такой же, но пишем 1 хук на сохранение HWBP и 3 хука(можно и для
Пожалуйста, авторизуйтесь для просмотра ссылки.

  1. NtContinue,если вызов из LdrInitializeThunk т.к если будет загружена DLL до RIP = EP,то будет вызвана данная функция => Dr регистры будут NULL
  2. NtQueryInformationProcess для ProcessDebugPort, ProcessDebugObjectHandle
  3. NtSetInformationThread для ThreadHideFromDebugger
  4. NtClose для правильной обработки STATUS_INVALID_HANDLE

Состояние anti-anti-debug tool на данный момент ~
Пожалуйста, авторизуйтесь для просмотра ссылки.
,если кому-то интересно.
Относительно легко и статья подходит постепенно к концу.
Тут должен быть обход CRC calc от лоадера.
Есть 2 типа CRC calc:критические о обычные т.е
1 вариант - делаем не валидный указатель и программа вызывает ошибку
2 вариант - кидаем MessageBox,если crc плохое
Я написал их поиск и подделку на чтение копии, но есть проблемы:
  1. Иногда происходит чтение байтов калькулятора не из CRC calc(vm-read_addr) => мы не может просто установить bp и отдавать выделенную память.
  2. Из-за пункта 1 можно только вручную добавить их для обхода + часть CRC calc не используется


3 Подарок от китайских друзей.

04.01.2024 на
Пожалуйста, авторизуйтесь для просмотра ссылки.
был опубликован специальный
Пожалуйста, авторизуйтесь для просмотра ссылки.
для отладочных исследований VMProtect v3.8.1.
Я попросил разработчика anti-anti-debug плагина
Пожалуйста, авторизуйтесь для просмотра ссылки.
отправить его, поэтому он идёт в качестве бонуса.
Он накрыт VMP и советую просто использовать под VM,но он работает и автор это проверял.
P.S спасибо
Пожалуйста, авторизуйтесь для просмотра ссылки.
за это.



4 Заключение

"Побеждённые сегодня будут победителями завтра, ибо поражение является для них наукой." Карл Либкнехт.
Проблема при публикации любой статьи по протекторам – постепенный фикс от разработчиков (рано или поздно, но вы придёте к такому выводу), поэтому многие не охотно делятся информацией или передают её между собой/в определённой группе.

Самый простой пример фиксов - догонялки с уничтожение build_windows в
Пожалуйста, авторизуйтесь для просмотра ссылки.
.
Надеюсь, статья и PoC поможет некоторым людям и ждём от Ивана VMP 4.0!

Пожалуйста, авторизуйтесь для просмотра ссылки.
:
Пожалуйста, авторизуйтесь для просмотра ссылки.

Сам
Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.

I Liked It...
P.S если в чём-то ошибся при анализе, то поправьте меня и спасибо за чтение статьи!
В версии 3.8.7 команда VMProtect догадалась исправить недочёты показанные в статье(я не анализировал всё)
 
Последнее редактирование:
c:\buildworker\csgo_rel_win64
Участник
Статус
Оффлайн
Регистрация
18 Окт 2022
Сообщения
587
Реакции[?]
209
Поинты[?]
137K
скорее всего этот парень и крякнул скит…
 
c:\buildworker\csgo_rel_win64
Участник
Статус
Оффлайн
Регистрация
18 Окт 2022
Сообщения
587
Реакции[?]
209
Поинты[?]
137K
Так это же китайская подделка с алиэкспресса?
хз сложно сказать, автор круто работает с перехватами функций, хуками и тд, а когда в системе будет висеть
Пожалуйста, авторизуйтесь для просмотра ссылки.
который использует функции по иерархии выше чем Nt(Zw) + защищает память процесса + мониторит всё это дело, вот там уже начнутся реальные проблемы.
 
Последнее редактирование:
Эксперт
Статус
Оффлайн
Регистрация
29 Мар 2021
Сообщения
1,500
Реакции[?]
560
Поинты[?]
104K
Читы на доту <3
Пользователь
Статус
Оффлайн
Регистрация
3 Май 2019
Сообщения
104
Реакции[?]
141
Поинты[?]
12K
Вступление

Основная проблема обхода функций протекторов – ручной обход т,е обход без автоматизации. Это замедляед анализ и приходится повторять многие моменты. Автор решил из-за этого написать небольшую статью,чтобы напомнить, что некоторые моменты можно легко обойти + автора забавляет, что для обхода anti-debug для Rin-3 используют Ring-0 т.е пытаются задавить таракана зачем-то танком. Сегодня мы заглянем под капот VMP и напишем обход для SDK функций и функции лоадера VMP(anti-debug,anti-vm, list import,spoof syscall_id and e.t.c).

Анализу будет больше про x64 кода(больше 3.7.x-3.8.x, но большинство моментов аналогичны для более ранних версии(например, обход SDK CRC)) т.к аналогичные действия нужно будет сделать для x32 + не вижу смысла делать раздутый код. Будет показана только логика,а остальное можете посмотреть на Github т.к уже был один раз, когда впихнул много и получилась неудачная статья(из-за ограничений на количество символов).

Важный момент!Это больше является PoC,нежели готовым к вставки для обхода на мой взгляд, но я не буду спорить с читателем.



Глава 1 Ломаем SDK или Ice to meet you
1.1 Anti-debug или возвращение к технике Off Balance

Посмотреть вложение 269535

Чем раздражителен VMP?

Во-первых, автор зачастую хитрит и использует WinApi/CRT оболочки, но об этом поговорим во 2 главе и из-за этого используется минимум NtApi/WinApi,а попытка построить anti-debug на manual syscall раздражает,но иногда вдохновляет + использование BugCheck функций для обнаружение хуков от anti-anti-debug tool может заставить вас потратить время.

Во-вторых, своей виртуализацией.

Не словом, а делом мы докажем эффективность отсутствия нужды анализа этого бреда. Это заставит докопаться до всего того, где отсутствует виртуализация + покажет разные способы к решению проблемы.

Автор уже затрагивал эту проблему в статье об обходе CRC протекторов.

VMP начиная ~ с версии 3.5.1 начинает использовать single step для обнаружения HWBP текущего потока:
Посмотреть вложение 269536

Основная проблема в расшифровке ret_address т.к адрес к коду программы, после выполнения SDK функции:
Посмотреть вложение 269537

Благодаря этому, можно написать простой хук:

  1. Устанавливаем Veh хук
  2. Проверка ExceptionCode == STATUS_SINGLE_STEP
  3. Проверяем,что перед nop инструкция cpuid,rdtsc(на самом деле нужно проверить на адрес из какой секции вызвано,но это на усмотрения читателя)
  4. Сканируем стек, на ret_address к коду юзера
  5. Устанавливаем bp(0xCC) хук(скопируйте инструкцию) или можете заменить на свой хук(рекомендуется вызывать исключение т.е скопировать,а потом переписать ret_address на не валидный => хук обработки на уничтожение указателей[1] => не тригерим CRC
  6. Обработчик для исключения(у меня:ExceptionCode == STATUS_BREAKPOINT)
  7. Возвращаем в al = 0, прыгаем к следующему коду и победа?(сюрпри-сюприз,но это будет обходить anti-vm ещё т.к anti-debug использует только rdtsc, но будет уточнение чуть позже)

Тут есть огрех из-за того, что не хочу пихать Unicore Engine т.к Dll сильно раздувается, но рекомендую её впихнуть и написать проверку на ret_address т.к в x32 из-за особенности обработки исключения VMP excepthion смещение(ret_address) будет на > 0x4xx т.е будет найден не тот адрес и эмуляция решает проблему, но в пункте 2.2 показывается другой способ обхода этого чуда.
P.S В качестве примера используется Veh хук,но можно уйти на более ранний и менее заметный перехват в обработки исключения (напримре,Wow64PrepareForException).



1.2 CRC SDK
Аналогичная ситуация с обходом SDK anti-debug т.к автор использует NtQueryVirtualMemory и
в более ранних версиях VirtualQuery(добавьте хук,если понадобится).
Тут появляется неприятный момент т.к если функция будет просто вызываться из виртуализированного кода и это будет не из SDK VMP вызов,то мы вернём слегка иной статус,хотя для NtApi это не будет влиять, но напишем проверку для практики.

  1. Проверка откуда вызвалось(из нашего модуля,но лучше написать для секции VMP)
  2. ProcessHandle == NtCurrentProcess && MemoryInformationClass == MemoryBasicInformation
  3. Вот тут главный момент т.к у нас нет прямого доступа к rsp,но как бы и есть,но мы хотим просканировать строчку NtQueryVirtualMemory.
  4. Берём &MemoryInformationLength и отнимаем смещение 0x30 т.к 5 аргумент в x64 rsp + 0x28 и смещение текущего указателя на rsp.
  5. Если строка существует, то сканируем стек и ползучем ret_address и возвращаем al = 1.


Забавный момент: можно это сроку поменять на другой NtApi и VMP будет вызывать другой NtApi.

Изменение строки:


Вызов NtApi,который VMP не использует:


Ну и вишенка на торте!
Муха в котлете, заноза в заднице, ложка дёгтя в бочке дёгтя:

Протектор проверяет целостность кода до того,как это сделает SDK, даже если не включена проверка целостности в лоадере т.е неприятный подарок.
Но есть и приятный момент т.к это значение имеет 2 значения только т.е hash_detect_patch и hash_no_detect_patch т.е можете сделать наглый хук,если потребуется.
Довольно странное решение у автора,но пусть будет так. На самом деле эти значения есть в том же anti-debug, но у меня большие сомнения , что у вас их получится затригирить, если вы не творите полный бред.
Единственный вариант, который придумал – ручное нахождение из-за обращении(PG hook) RW памяти(обычно > PAGE_SIZE * 4) и это именно hash,который будет разный. Короче, лучше патчить после подсчёта CRC в лоадере, если вам лень делать лишние и бесполезные действия(или просто использовать HWBP).



1.3 Anti-vm SDK
На самом деле обход anti-debug обходит и anti-vm SDK,но есть пару проблем, которые автор скажет,но не будет решать т.к
  • проблема требует сканировать vm_cpuid и хукать их,но в том же VMWare решается проблема настройкой конфига,как и VirtualBox(речь про вызов cupid с eax = 1 и проверка 31 бита в ecx).
  • проблему требует подделки исправление для TF exception_info->ExceptionRecord->ExceptionAddress на адрес с опкодом nop для тогоже VirtualBox, но я им не пользуюсь + эта ошибка гипервизора и довольно странно, что её не исправляют.

Чтобы не было скучно,автор упомянет сканирование vm-строк,которые вызываются NtQuerySystemInformation с SystemFirmwareTableInformation (RSMB). План надёжный, как швейцарские часы т.е можно заставить возвращать, что их попросту нет:

  1. Сканирование инструкции mov rbx,qword ptr ss:[rsp+8](48 8B 5C 24)
  2. Дизассемблируем и проверяем,что это наша функция
  3. Находим mov al,1 и переписываем на mov al,0
  4. Победа?
P.S Обращение к буферу можно определить с помощью обнаружением буфера при вызове manual syscall NtQuerySystemInformation и установки HWBP



Глава 2 Ломаем лоадер


Вот тут по большей части у всех начинаются проблемы т.к довольно неприятно ковырять это творение.
Погнали постепенно разбираться что к чему!


2.1 Импорт


Недавно colby57 обновил и выпустил тулзу для фикса импорта на основе эмуляции. Правда,тут есть неприятная проблема, которую указал автор.
Импорт будет расшифрован импорт только в .text секции,а это значит что вирта будет вертеть данную тулзу на все 360 градусов,если обфусцированный вызов под ней. Мы пойдём слегка по иному пути,но сначала слова.
Тут автор затронет халтуру разработчика т.к давайте изучим обфускацию импортов у VMP:

3.0.9:
import_obf_3.0.9_and_3_5:
00 | 00007FF63F25109E | E8 DD5A1900              | call bin_64.0.9.vmp.7FF63F3E6B80
01 | 00007FF63F3E6B80 | 90                       | nop                          
02 | 00007FF63F3E6B81 | 41:0FBFCF                | movsx ecx,r15w              
03 | 00007FF63F3E6B85 | 41:8AC8                  | mov cl,r8b                  
04 | 00007FF63F3E6B88 | 41:0F47C8                | cmova ecx,r8d                
05 | 00007FF63F3E6B8C | 59                       | pop rcx                      
06 | 00007FF63F3E6B8D | E9 CF82F3FF              | jmp bin_64.0.9.vmp.7FF63F31EE61
07 | 00007FF63F31EE61 | 48:870C24                | xchg qword ptr ss:[rsp],rcx  
08 | 00007FF63F31EE65 | E9 D99D0D00              | jmp bin_64.0.9.vmp.7FF63F3F8C43
09 | 00007FF63F3F8C43 | 51                       | push rcx                    
0A | 00007FF63F3F8C44 | 0F9FC1                   | setg cl                      
0B | 00007FF63F3F8C47 | 48:8D0D 7D8FE5FF         | lea rcx,qword ptr ds:[7FF63F251BCB]
0C | 00007FF63F3F8C4E | E9 3DC8EDFF              | jmp bin_64.0.9.vmp.7FF63F2D5490
0D | 00007FF63F2D5490 | 48:8B89 96FA1B00         | mov rcx,qword ptr ds:[rcx+1BFA96]
0E | 00007FF63F2D5497 | E9 B06D0300              | jmp bin_64.0.9.vmp.7FF63F30C24C
0F | 00007FF63F30C24C | 48:8D89 D821AE71         | lea rcx,qword ptr ds:[rcx+71AE21D8]
10 | 00007FF63F30C253 | 48:870C24                | xchg qword ptr ss:[rsp],rcx  
11 | 00007FF63F30C257 | E9 24D7FFFF              | jmp bin_64.0.9.vmp.7FF63F309980
12 | 00007FF63F309980 | C3                       | ret

3.5
01 | 00007FF7A57710AC | E8 5FFFFFFF              | call bin_64_3.5.0.vmp.7FF7A5771010
02 | 00007FF7A5771010 | 48:894C24 08             | mov qword ptr ss:[rsp+8],rcx
03 | 00007FF7A5771015 | 48:895424 10             | mov qword ptr ss:[rsp+10],rdx
04 | 00007FF7A577101A | 4C:894424 18             | mov qword ptr ss:[rsp+18],r8
05 | 00007FF7A577101F | 4C:894C24 20             | mov qword ptr ss:[rsp+20],r9
06 | 00007FF7A5771024 | 53                       | push rbx                  
07 | 00007FF7A5771025 | 57                       | push rdi                  
08 | 00007FF7A5771026 | 48:83EC 38               | sub rsp,38                
09 | 00007FF7A577102A | B9 01000000              | mov ecx,1                  
0A | 00007FF7A577102F | 48:8D7C24 58             | lea rdi,qword ptr ss:[rsp+58]
0B | 00007FF7A5771034 | E8 72350500              | call bin_64_3.5.0.vmp.7FF7A57C45AB
0C | 00007FF7A57C45AB | 90                       | nop                        
0D | 00007FF7A57C45AC | 50                       | push rax                  
0E | 00007FF7A57C45AD | 41:0FB7C0                | movzx eax,r8w              
0F | 00007FF7A57C45B1 | 40:8AC6                  | mov al,sil                
10 | 00007FF7A57C45B4 | 98                       | cwde                      
11 | 00007FF7A57C45B5 | 48:8B4424 08             | mov rax,qword ptr ss:[rsp+8]
12 | 00007FF7A57C45BA | 48:8D40 01               | lea rax,qword ptr ds:[rax+1]
13 | 00007FF7A57C45BE | E9 C57C0500              | jmp bin_64_3.5.0.vmp.7FF7A581C288
14 | 00007FF7A581C288 | 48:894424 08             | mov qword ptr ss:[rsp+8],rax
15 | 00007FF7A581C28D | 48:8D05 EB4FF5FF         | lea rax,qword ptr ds:[7FF7A577127F
16 | 00007FF7A581C294 | E9 200BFEFF              | jmp bin_64_3.5.0.vmp.7FF7A57FCDB9
17 | 00007FF7A57FCDB9 | 48:8B80 AB060400         | mov rax,qword ptr ds:[rax+406AB]
18 | 00007FF7A57FCDC0 | E9 C2CA0300              | jmp bin_64_3.5.0.vmp.7FF7A5839887
19 | 00007FF7A5839887 | 48:8D80 690A0074         | lea rax,qword ptr ds:[rax+74000A69
1A | 00007FF7A583988E | E9 04B10600              | jmp bin_64_3.5.0.vmp.7FF7A58A4997
1B | 00007FF7A58A4997 | 48:870424                | xchg qword ptr ss:[rsp],rax
1C | 00007FF7A58A499B | E9 00E6FAFF              | jmp bin_64_3.5.0.vmp.7FF7A5852FA0
1D | 00007FF7A5852FA0 | C3                       | ret
3.8.1:
import_obf_3.8.1:
00 | 00007FF7B7811079 | E8 20470000              | call bin_64_3_8_1.vmp.7FF7B781579E
01 | 00007FF7B781579E | 50                       | push rax                        
02 | 00007FF7B781579F | 41:56                    | push r14                        
03 | 00007FF7B78157A1 | E8 72C10000              | call bin_64_3_8_1.vmp.7FF7B7821918
04 | 00007FF7B7821918 | 9C                       | pushfq                          
05 | 00007FF7B7821919 | 49:BE B28D379E99702E23   | mov r14,232E70999E378DB2        
06 | 00007FF7B7821923 | 57                       | push rdi                        
07 | 00007FF7B7821924 | 4D:8DB426 A3E322EA       | lea r14,qword ptr ds:[r14-15DD1C5D]
08 | 00007FF7B782192C | 48:BF 27442ED038039DBA   | mov rdi,BA9D0338D02E4427        
09 | 00007FF7B7821936 | 48:81FF 9B278BFA         | cmp rdi,FFFFFFFFFA8B279B        
0A | 00007FF7B782193D | 48:8B4424 28             | mov rax,qword ptr ss:[rsp+28]  
0B | 00007FF7B7821942 | E8 E9F51A00              | call bin_64_3_8_1.vmp.7FF7B79D0F30
0C | 00007FF7B79D0F30 | 41:50                    | push r8                        
0D | 00007FF7B79D0F32 | 4D:8BC6                  | mov r8,r14                      
0E | 00007FF7B79D0F35 | E8 E37E0000              | call bin_64_3_8_1.vmp.7FF7B79D8E1D
0F | 00007FF7B79D8E1D | 55                       | push rbp                        
10 | 00007FF7B79D8E1E | 49:8BEE                  | mov rbp,r14                    
11 | 00007FF7B79D8E21 | 48:8D40 01               | lea rax,qword ptr ds:[rax+1]    
12 | 00007FF7B79D8E25 | 41:0F9EC0                | setle r8b                      
13 | 00007FF7B79D8E29 | 6645:2BC6                | sub r8w,r14w                    
14 | 00007FF7B79D8E2D | 41:50                    | push r8                        
15 | 00007FF7B79D8E2F | E8 3174E4FF              | call bin_64_3_8_1.vmp.7FF7B7820265
16 | 00007FF7B7820265 | 66:C1E5 B8               | shl bp,B8                      
17 | 00007FF7B7820269 | E8 DD541A00              | call bin_64_3_8_1.vmp.7FF7B79C574B
18 | 00007FF7B79C574B | 4C:23C7                  | and r8,rdi                      
19 | 00007FF7B79C574E | 48:894424 60             | mov qword ptr ss:[rsp+60],rax  
1A | 00007FF7B79C5753 | 40:F6D5                  | not bpl                        
1B | 00007FF7B79C5756 | C1ED FC                  | shr ebp,FC                      
1C | 00007FF7B79C5759 | 66:C1E7 A1               | shl di,A1                      
1D | 00007FF7B79C575D | 48:81A4EC D0FFFFFF 3957B | and qword ptr ss:[rsp+rbp*8-30],79B7573
1E | 00007FF7B79C5769 | 41:55                    | push r13                        
1F | 00007FF7B79C576B | 41:50                    | push r8                        
20 | 00007FF7B79C576D | 48:814424 10 F4DFFFFF    | add qword ptr ss:[rsp+10],FFFFFFFFFFFFD
21 | 00007FF7B79C5776 | 56                       | push rsi                        
22 | 00007FF7B79C5777 | E8 08D00200              | call bin_64_3_8_1.vmp.7FF7B79F2784
23 | 00007FF7B79F2784 | 44:0B4424 11             | or r8d,dword ptr ss:[rsp+11]    
24 | 00007FF7B79F2789 | 0F8D 1F56E2FF            | jge bin_64_3_8_1.vmp.7FF7B7817DAE
25 | 00007FF7B79F278F | 48:BE 0351299631AC12E9   | mov rsi,E912AC3196295103        
26 | 00007FF7B79F2799 | 48:8B84AC 00000000       | mov rax,qword ptr ss:[rsp+rbp*4]
27 | 00007FF7B79F27A1 | 48:8B8468 F0FFFFFF       | mov rax,qword ptr ds:[rax+rbp*2-10]
28 | 00007FF7B79F27A9 | 0F82 9C100600            | jb bin_64_3_8_1.vmp.7FF7B7A5384B
29 | 00007FF7B79F27AF | 49:BD 9B961DD20F50BBD6   | mov r13,D6BB500FD21D969B        
2A | 00007FF7B79F27B9 | 6644:214424 10           | and word ptr ss:[rsp+10],r8w    
2B | 00007FF7B79F27BF | 856C24 30                | test dword ptr ss:[rsp+30],ebp  
2C | 00007FF7B79F27C3 | 4A:8D8400 D6E8229E       | lea rax,qword ptr ds:[rax+r8-61DD172A]
2D | 00007FF7B79F27CB | 48:0FCF                  | bswap rdi                        
2E | 00007FF7B79F27CE | 6644:8BC5                | mov r8w,bp                      
2F | 00007FF7B79F27D2 | E8 D999E3FF              | call bin_64_3_8_1.vmp.7FF7B782C1B0
30 | 00007FF7B782C1B0 | 0F86 757D1900            | jbe bin_64_3_8_1.vmp.7FF7B79C3F2B
31 | 00007FF7B782C1B6 | F65424 38                | not byte ptr ss:[rsp+38]        
32 | 00007FF7B782C1BA | 48:878424 80000000       | xchg qword ptr ss:[rsp+80],rax  
33 | 00007FF7B782C1C2 | 48:8B7C24 60             | mov rdi,qword ptr ss:[rsp+60]  
34 | 00007FF7B782C1C7 | 49:81C6 85A8A925         | add r14,25A9A885                
35 | 00007FF7B782C1CE | F7D6                     | not esi                        
36 | 00007FF7B782C1D0 | 66:F7DE                  | neg si                          
37 | 00007FF7B782C1D3 | 6641:C1EE 42             | shr r14w,42                    
38 | 00007FF7B782C1D8 | 4C:8BACAC 00000000       | mov r13,qword ptr ss:[rsp+rbp*4]
39 | 00007FF7B782C1E0 | 4C:037424 38             | add r14,qword ptr ss:[rsp+38]  
3A | 00007FF7B782C1E5 | C74424 38 A2A6BBED       | mov dword ptr ss:[rsp+38],EDBBA6A2
3B | 00007FF7B782C1ED | 41:C0E6 C7               | shl r14b,C7                    
3C | 00007FF7B782C1F1 | 814C24 38 B9A2BB03       | or dword ptr ss:[rsp+38],3BBA2B9
3D | 00007FF7B782C1F9 | 66:33F6                  | xor si,si                      
3E | 00007FF7B782C1FC | C1AC34 38002A96 E3       | shr dword ptr ss:[rsp+rsi-69D5FFC8],E3
3F | 00007FF7B782C204 | 4B:8DACF6 0AF4036F       | lea rbp,qword ptr ds:[r14+r14*8+6F03F40
40 | 00007FF7B782C20C | 0F87 08731A00            | ja bin_64_3_8_1.vmp.7FF7B79D351A
41 | 00007FF7B79D351A | 48:8B7424 10             | mov rsi,qword ptr ss:[rsp+10]  
42 | 00007FF7B79D351F | 49:F7D8                  | neg r8                          
43 | 00007FF7B79D3522 | 66:0FBE6C24 1D           | movsx bp,byte ptr ss:[rsp+1D]  
44 | 00007FF7B79D3528 | 45:84C6                  | test r14b,r8b                  
45 | 00007FF7B79D352B | 0FCD                     | bswap ebp                      
46 | 00007FF7B79D352D | 44:0F497424 3B           | cmovns r14d,dword ptr ss:[rsp+3B]
47 | 00007FF7B79D3533 | 0F9E846C 6E64FEFF        | setle byte ptr ss:[rsp+rbp*2-19B92]
48 | 00007FF7B79D353B | 49:85EE                  | test r14,rbp                    
49 | 00007FF7B79D353E | E8 28A20500              | call bin_64_3_8_1.vmp.7FF7B7A2D76B
4A | 00007FF7B7A2D76B | 48:8B6C24 48             | mov rbp,qword ptr ss:[rsp+48]  
4B | 00007FF7B7A2D770 | 0F85 A901F7FF            | jne bin_64_3_8_1.vmp.7FF7B799D91F
4C | 00007FF7B799D91F | 0F86 83E90000            | jbe bin_64_3_8_1.vmp.7FF7B79AC2A8
4D | 00007FF7B799D925 | 6644:854424 43           | test word ptr ss:[rsp+43],r8w  
4E | 00007FF7B799D92B | E8 FD000000              | call bin_64_3_8_1.vmp.7FF7B799DA2D
4F | 00007FF7B799DA2D | 41:0F91C0                | setno r8b                      
50 | 00007FF7B799DA31 | 4C:8BB424 88000000       | mov r14,qword ptr ss:[rsp+88]  
51 | 00007FF7B799DA39 | 814C24 28 84C59D60       | or dword ptr ss:[rsp+28],609DC584
52 | 00007FF7B799DA41 | 0F934424 28              | setae byte ptr ss:[rsp+28]      
53 | 00007FF7B799DA46 | 44:334424 4A             | xor r8d,dword ptr ss:[rsp+4A]    
56 | 00007FF7B799DA63 | 4E:8B8404 6A1D8A98       | mov r8,qword ptr ss:[rsp+r8-6775E296]
57 | 00007FF7B799DA6B | F75424 28                | not dword ptr ss:[rsp+28]      
58 | 00007FF7B799DA6F | E8 21570000              | call bin_64_3_8_1.vmp.7FF7B79A3195
59 | 00007FF7B79A3195 | 48:8D6424 48             | lea rsp,qword ptr ss:[rsp+48]  
5A | 00007FF7B79A319A | C2 1800                  | ret 18                          
5B | 00007FF7B79914B4 | FF7424 18                | push qword ptr ss:[rsp+18]      
5C | 00007FF7B79914B8 | 9D                       | popfq                          
5D | 00007FF7B79914B9 | 48:8D6424 30             | lea rsp,qword ptr ss:[rsp+30]  
5E | 00007FF7B79914BE | C3                       | ret
Не вижу смысл дальше показывать т.к мы можем заметить 2 вещи:

  1. Вызов всегда через call
  2. Всегда присутствует xchg
Забавно,что автор просто пытается ломать тулзы, а не обдумать следящую проблему:
Как же они её находят, если я накинул обфускации слегка и это КПД стремится к NULL?
Вопрос, конечно, риторический.

Итак,что же делать? Автор идёт старинным способом т.е пишем хук для получения списка импорта + пару идей для хука даже для текущей версии.

Поскольку это тригерит CRC от лоадера,то просто перепишите на HWBP хук,чтобы не париться,но меньше слова и больше дел!

~Включая до версии 3.8.1 можно было вообще кайфовать т.к можно было перехватывать адрес импорта и вернуть адрес на своей функции => $$$бесплатно$$$ хукать импорт из-за просто мутации оболочки получения адреса(я упоминал об этом в 1 чате).
Её можно легко найти, если поставить на PE ntdll PG хук обращение:





Однако, начиная с версии 3.8.2 из-за слива автор решил кинуть эту функцию под вирту,чем усложнил задачу,но всё-равно это не мешает.

Если проанализировать, то используется VMP_STR_CMP т.е сравнивается 2 строки и возвращается статус сравнения. Она только под мутацией, поэтому халява



План действий:

  1. Поиск по паттерну mov qword ptr ss:[rsp+8],rbx(48 89 5C 24 08)
  2. Дизассемблируем и проверяем,что это нужна функция
  3. Устанавливаем хук

Это не идеальное решение, но у нас есть список импорта.

Если говорить про правильное решение и эмуляцию,то:
Поиск lea reg64,qword ptr ds:[imm](48 8D) для версий ниже ~3.6 и call rip+offset(E8)

Собрать этот список в .vmp0 секции и можно наплевать на .text секцию т.к автор продолжает получать rip через call в обфусцированном вызове(не при переходе из .text в vmp0,а во время выхода обфусцированного чуда),поэтому он облегчает задачу.

Эмуляция и проверка, что указатель указывает на импорт(можно эмулировать до инструкции xchg) и проверка на ret,что мы на импорте. Но мне лень делать + я уже и без этого тащу 3 либы, поэтому это всё на усмотрение читателя,хотя логику сказал.

Так же можно добавить экспорт в свою библиотеку с тем же названием или хукать strcmp, хукнуть GetModuleHandleA и вернуть свою библиотеку,если там нас интересующий импорт. Это так, для шутки…


2.2 Драка за уничтожение syscall_id


Фирма «VMProtect» очень не хотела, чтобы реверсеры использовали для обхода anti-debug
Пожалуйста, авторизуйтесь для просмотра ссылки.
и плагины по типу
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
но открытие,что у VMP идёт смешная борьба за manual syscall, повлекло бы за собой совершенно забавное представление о ней.

Стоп. Тут нужно остановиться. Как большинство читателей знаю, VMP начал использовать ~ с версии
Пожалуйста, авторизуйтесь для просмотра ссылки.
. За этим забавно можно проследить в обновлениях
Пожалуйста, авторизуйтесь для просмотра ссылки.
.
В качестве примера, приведу дополнения код SharpOD,который патчит ProductVersion && FileVersion и берёт при загрузке ntdll(хук orig_NtMapViewOfSection) подделанные версии ProductVersion && FileVersion.
Простыми словами, все используют подделку OSBuildNumber в PEB, подделку в ресурсах ntdll ProductVersion, FileVersion. И чтобы жизнь малиной не казалось, VMP начал получать NtBuildNumber из PKUSER_SHARED_DATA и удивила всех подружек:



Это можно обойти и здесь будет показано,но с костылём, поскольку автор лентяй. Да и почему я должен быть честным в обходе, когда все хитрят?

Во-первых, потребуется подменить выше перечисленное( OSBuildNumber в PEB и ProductVersion/FileVersion),но и ещё подделать OSMajorVersion и до полного счастья OSMinorVersion для обхода NtBuildNumber.

Теперь происходит map ntdll.dll с помощью NtOpenSecthion и NtMapViewOfSection,а если это не получится то старый набор из NtOpenFile, NtCreateSection, NtMapViewOfSection. Если зафейлить и это,то автор кидает MessageBox через NtRaiseException и это вообще офигенно, поскольку так делать не надо, но разработчику виднее.

Вот на этом моменте автор застрял т.к пытался уничтожить, что только попалось и вот результат,что можо сделать при плохом настроении, будучи выпившим т.к обращение было только к PE мапнутой ntdll и секции .rdata.
Поэтому переходим к плану B, хотя это первоначальный способ обхода anti-debug в лоадере,но тут гарантировано будут уничтожены syscall_id. В чём шутка? Можно просто пройтись по стеку и уничтожить syscall_id,после unmap ntdll и как бы так делать нельзя. У всё работало, хотя это вообще варварский метод(но кто будет судить за такой метод честной игры?).

Просто повторите в пункте 1.2,но теперь просто уничтожайте syscall_id,но проблема в том,что в x64 они находится в верхушке стека т.к нужно вверх и вниз стека пройтись для полного счасть,как бы ни казалось странным.
Теперь можно пить медовуху у курить бамбук!



2.3 Ani-debug loader или Ivan...You are drunk


Что помогает реверсерам уже имевших с анализом VMP,обходить anti-debug и что VMP делает первым действием, как я упомянул в одной своей статье?

Верно!VMP проверяет всегда NtCurrentPeb()->BeingDebugged и тут просто устанавливаем HWBP hook на обращение.

Возвращаясь к прошлому пункту(2.2),первоначально использовался этот метод для уничтожения syscall_id NtQueryInformationProcess и NtSetInformationThread.

План действий аналогично такой же, но пишем 1 хук на сохранение HWBP и 3 хука(можно и для
Пожалуйста, авторизуйтесь для просмотра ссылки.

  1. NtContinue,если вызов из LdrInitializeThunk т.к если будет загружена DLL до RIP = EP,то будет вызвана данная функция => Dr регистры будут NULL
  2. NtQueryInformationProcess для ProcessDebugPort, ProcessDebugObjectHandle
  3. NtSetInformationThread для ThreadHideFromDebugger
  4. NtClose для правильной обработки STATUS_INVALID_HANDLE

Состояние anti-anti-debug tool на данный момент ~
Пожалуйста, авторизуйтесь для просмотра ссылки.
,если кому-то интересно.
Относительно легко и статья подходит постепенно к концу.
Тут должен быть обход CRC calc от лоадера.
Есть 2 типа CRC calc:критические о обычные т.е
1 вариант - делаем не валидный указатель и программа вызывает ошибку
2 вариант - кидаем MessageBox,если crc плохое
Я написал их поиск и подделку на чтение копии, но есть проблемы:
  1. Иногда происходит чтение байтов калькулятора не из CRC calc(vm-read_addr) => мы не может просто установить bp и отдавать выделенную память.
  2. Из-за пункта 1 можно только вручную добавить их для обхода + часть CRC calc не используется


3 Подарок от китайских друзей.

04.01.2024 на
Пожалуйста, авторизуйтесь для просмотра ссылки.
был опубликован специальный
Пожалуйста, авторизуйтесь для просмотра ссылки.
для отладочных исследований VMProtect v3.8.1.
Я попросил разработчика anti-anti-debug плагина
Пожалуйста, авторизуйтесь для просмотра ссылки.
отправить его, поэтому он идёт в качестве бонуса.
Он накрыт VMP и советую просто использовать под VM,но он работает и автор это проверял.
P.S спасибо
Пожалуйста, авторизуйтесь для просмотра ссылки.
за это.



4 Заключение

"Побеждённые сегодня будут победителями завтра, ибо поражение является для них наукой." Карл Либкнехт.
Проблема при публикации любой статьи по протекторам – постепенный фикс от разработчиков (рано или поздно, но вы придёте к такому выводу), поэтому многие не охотно делятся информацией или передают её между собой/в определённой группе.

Самый простой пример фиксов - догонялки с уничтожение build_windows в
Пожалуйста, авторизуйтесь для просмотра ссылки.
.
Надеюсь, статья и PoC поможет некоторым людям и ждём от Ивана VMP 4.0!

Пожалуйста, авторизуйтесь для просмотра ссылки.
:
Пожалуйста, авторизуйтесь для просмотра ссылки.

Сам
Пожалуйста, авторизуйтесь для просмотра ссылки.

Пожалуйста, авторизуйтесь для просмотра ссылки.

I Liked It...
P.S если в чём-то ошибся при анализе, то поправьте меня и спасибо за чтение статьи!
Приходи к нам работать. Через несколько месяцев запускаем разработку легального продукта, где знания всех наших реверс-инженеров будут очень полезны.
 
Сверху Снизу