Проблемы защиты функций с switch/case [bcc64/clang]

Решил попробовать на 64 битах. 5 из 5 программ неудачно. Появляются ошибки:
Команда не поддерживается
Jump on a part of a command

Проблема с switch/case. Например, так:

// test.cpp
int main() {
	char a, b;
	switch (a) {
	case '#': b = 'a'; break;
	case '$': b = 'b'; break;
	case '%': b = 'c'; break;
	case '&': b = 'd'; break;
	case '+': b = 'e'; break;
	case ']': b = 'f'; break;
	case '^': b = 'g'; break;
	case '`': b = 'h'; break;
	case '{': b = 'i'; break;
	case '|': b = 'j'; break;
	case '}': b = 'k'; break;
	case '~': b = 'l'; break;
	case 'x': b = 'm'; break;
	case '@': b = 'n'; break;
	}
}

Embarcadero C++ 7.30 for Win64 Copyright (c) 2012-2017 Embarcadero Technologies, Inc.
test.cpp:
Turbo Incremental Link64 6.90 Copyright (c) 1997-2017 Embarcadero Technologies, Inc.

VMProtect Ultimate v 3.1.2 (build 830) Copyright 2003-2017 VMProtect Software
Registered to: -[-@gmail.com], Personal License

Загрузка test.exe… 100%
Загрузка [V] 00401190 main
[Ошибка] main.00401295: Комманда не поддерживается “db 62”

Присылайте тестовый пример (оригинал EXE+MAP+VMP файлы) на нашу почту

Файлы отправил на вашу почту.

32-битные после защиты также могут падать. Причина где-то здесь:

push    ebp
mov     ebp, esp
push    ebx
push    edi
push    esi
sub     esp, 1D0h
call    $+5
pop     eax
mov     [ebp+var_1B8], eax
....

movzx   eax, byte ptr [ebp+eax+var_184]
cmp     eax, 3
ja      loc_4896EC
mov     eax, ds:dword_489760[eax*4]
add     eax, [ebp+var_1B8]
jmp     eax

....
dword_489760    dd 541h, 5B9h, 0E9Fh, 0FCCh

Проблема в том, что в main находится 2 SWITCH-а с одной таблицей переходов на двоих, поэтому при парсинге самого первого SWITCH получились кривые ссылки на код.

Второй пример в виде теста пришлете?

Файлы отправил на вашу почту (test2.zip).

Проверяйте (3.1.2.928):
http://vmpsoft.com/files/VMProtectDemo.exe

Отправил на почту ещё вариант, после защиты продолжает падать (test2.zip).

Дак он у вас и без защиты падает :slight_smile:)

P.S. SWITCH распарсился без проблем.

Да, перепроверил, всё пока работает.

После защиты падает. Может страницы в стек не подгружает? Файлы теста выслал (test.zip).

#include <stdio.h>
#include <malloc.h>
char *bb;
int main(int argc, char *argv[]) {
   if (argc == 1) {
      const int size = 4096 * 8;
      char *buf = (char *)alloca(size);
      bb = buf;
      for (int i = size - 1; i >= 0; i--) {
         bb[i] = 0;
      }
      printf("ok\n");
   }
}

Зачем выделять такой объем памяти на стеке?

Это же тест. Нашел место и смог воспроизвести в тесте с большим трудом. В реальной программе падало очень редко.
Проблема при небольшом выделении памяти (хоть 16 байт), если рядом с границей страницы памяти стека, то будет падать.

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

  1. Шаг вправо:
#include <stdio.h>
#include <malloc.h>
char *bb;
int main(int argc, char *argv[]) {
   if (argc == 1) {
      const int size = 4096 * 8;
      char *buf = (char *)alloca(size);
      printf("alloca\n");
      bb = buf;
      for (int i = size - 1; i >= 0; i--) {
         bb[i] = 0;
      }
      printf("ok\n");
   }
}
  1. Шаг влево:
#include <stdio.h>
#include <malloc.h>
char *bb;
int main(int argc, char *argv[]) {
   if (argc == 1) {
      const int size = 4096 * 8;
      char *buf = (char *)alloca(size);
      printf("alloca\n");
      bb = buf;
      for (int i = 0; i < size; i++) {
         bb[i] = 0;
      }
      printf("ok\n");
   }
}

Вы обычно просите тест проблемы, я вам его предоставил. Мои предыдуще тесты так же абсурдны в использовании switch/case и вообще тупые с точки зрения програмиирования, уж простите, мы тут все такие ламеры. Могу сделать тест, где будет падать и при выделении одного байта. Я как раз понимаю, как работает подгрузка страниц в стек, а ваши приведёные “шаг в правло/влево” не имеют никакого отношения к проблеме, ибо бессмысленны. Суть в том, что после защиты вашим протектором, программа перестает работать, как работала в первоначальном виде. В этом случае хотелось бы знать ограничения вашего протектора при разработке программ. Ограничения в использовании приёмов программирования, ограниченная проддержка компиляторов?

Если указатель стека находится на начале страницы памяти, то при обращении по адресу с любым меньшим адресом (хоть -1 байт) протектор будет падать, потомучто там может не быть закомиченой страницы памяти, а Windows делает подгрузку это страницы автоматически. Надеюсь, вы понимаете этот момент и ваш протектор здесь отрабатывает корректно.

Хорошо. Давайте я вам объясню что у вас происходит. Для начала немного теории - операционная система не выделяет всю доступную память под стек, а выделяет только часть и на границе выделенной памяти создается страница с типом GUARD_PAGE. Размер страницы как водится равен 0x1000. Когда ваш стек наезжает на GUARD_PAGE в системе возникает исключение STATUS_GUARD_PAGE_VIOLATION, которое перехватывается системным обработчиком и он выделяется следующие 0x1000 байт и передвигает GUARD_PAGE ниже.
Теперь что происходит у вас - вы с помощью alloca “перескочили” GUARD_PAGE и оказались в памяти, доступ к которой запрещен от слова “совсем”. Т.е. при доступе к этой памяти вместо STATUS_GUARD_PAGE_VIOLATION возникает уже STATUS_ACCESS_VIOLATION, на которое системный обработчик начинает вызывать обработчики исключений в самой программе и при их отсутствии прибивает процесс целиком. В вашем коде доступ к buf идет в обратном порядке, т.е. вы в итоге проходите GUARD_PAGE и в результате прибегаете к памяти, к которой доступ уже есть. Поэтому я вам навскидку предложил 2 варианта, при котором ваш пример с минимальными изменениями будет точно также крешиться даже без всяких вмпротектов и прочего. О чем это говорит? Это говорит о том, что выделять память на стеке да еще в таких количествах это очень дурная практика, которая может привести к трудно обнаруживаемым багам, связанным исключительно с механизмом работы стека.
Теперь что касается вмпротекта - для своей работы виртуальная машина использует тоже самый стек, что и защищенная функция, поэтому обращение с области памяти за GUARD_PAGE происходит сразу же после изменении указателя на вершину стека:

push rbp
mov rbp, rsp
cmp ecx, 01
jnz 0000000000401267 ↓
mov rcx, rsp
add rcx, -00008000
mov rsp, rcx <--------------- изменение указателя на вершину стека
mov [0000000000416440], rcx <------------ при выполнении этой команды произойдет обращение к недоступной памяти, т.к. вершина стека находится в недоступной памяти
mov eax, 00007FFF
jmp 000000000040124B ↓
...

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

Я прекрасно понимаю, что тут происходит и об этом вам уже указал в самом начале. Но как раз с операционной системой проблем нет и программа работает прекрасно, подгрузка страниц происходит без проблем, это было сделано специально, если вы не поняли. В отличии от ваших нерабочих примеров, зачем вы их привели мне не понятно. Рабочая программа не выделяет большие блоки памяти. Было уже указано, что проблема не в количестве выделяемой памяти, а в работе у границы неподгруженой страницы. Если вмпротект использует стек программы, тогда всё объясняется. Здесь несовместимость clang с вмпротектом.
А ваши заключения по работе со стеком довольно забавны, учитывая, что эти “дурные практики” выделения памяти в ростущем стеке эффективно использутся уже более 30 лет на уровне компиляторов. Правильные рассуждения: Clang генерирует не правильный код выделения памяти в стеке. Отсутсвует подкачка страниц стека (probe stack) до изменения его указателя. Так что спишем проблему на clang.