Деобфускация и трассировка флагов процессора При обфускации примитивов ВМ могут вставляться в код примитива "лишние" условные переходы (JCC). Работает это следующим образом - производятся некие математические или логические преобразования константы, после чего стоит условный переход, понятно, что он будет работать только в одном направлении (jcc или direct) в зависимости от значения константы и операции перед переходом. Чтобы правильно выполнить статическую трассировку нужно сделать анализ флагов перед переходом. Вопрос в том, как правильно сэмулировать значение разных флагов? На входе задачи имеем: - опкод инструкции, изменяющей флаги (add, sub, neg, and и т.д.) - значение константы до инструкции (V1) - значение константы после инструкции (V2) На выходе нужно получить значения всех флагов процессора. Рассмотрим эмуляцию всех флагов: 1. Флаг Z (ноль). Здесь всё просто: если V2 == 0, то Z = 1 2. Флаг S (знак) Тоже просто: если старший бит V2 == 1, то S = 1 3. Флаг P (паритет) Достаточно просто: если число единиц в младшем байте V2 четно, то P = 1 4. Флаг C (перенос) - если инструкция устанавливает или сбрасывает этот флаг, то всё просто, C = флаг инструкции - если инструкция изменяет этот флаг, то анализ V1, V2 и инструкции даст требуемый результат, например, для add, если V2 < V1, то С = 1 т.е. если есть перенос или заем, то С = 1 5. Флаг O (переполнение) - если инструкция устанавливает или сбрасывает этот флаг, то всё просто, О = флаг инструкции - для inc, если V2 == 0, то О = 1 - для add, если V2 < V1, то О = 1 т.е. для для операций "верхнего" переполнения достаточно просто, если размер значения V2 > размера V1, то О = 1 а как быть с операциями "нижнего" переполнения (вычитающими), одного лишь анализа заёма недостаточно, ведь команда neg не установит флаг переполнения? Прошу высказать, если я в чем то ошибаюсь или неточен, и как сделать полный анализ флага переполнения.
Vam С чего бы это neg не выставляла OF? Выставляет. Правда, знаковое переполнение там возникает в единственном случае: когда аргумент равен минимально возможному значению для данной разрядности. Например, mov eax,80000000h neg eax выставит OF в единицу.
Vam Да... забыл досказать. Непохоже, чтобы Вам это было неизвестно... Но на всякий случай: OF равен xor'у значений переноса (заёма) из предстаршего в старший (из старшего в предстарший) разряд и из старшего (в старший) разряд. Соответственно, OF выставляется тогда и только тогда, когда меняется знаковый бит аргумента. Учитывая, что neg arg = 0-arg, аргументом в данном случае будет считаться нуль, а не V1. Аналогично, например, инструкция shl eax,1 в случае eax равного 80000000h выставит OF в единицу, т.к. переноса в старший разряд не будет, но будет перенос из старшего разряда в CF.
Подумал, что ерунду полную написал. Имелось в виду в отношении переносов, а не при переходе от V1 к V2.
l_inc Спасибо за ответ. Если бы мне это было точно известно, то я бы и не спрашивал. Честно сказать я не понял, что с чем нужно хорить, перенос (заем), т.е. это бит C со старшим разрядом? Чего? Результата (V2)? Прошу написать формулой. Если это действительно так, то и анализ флага С тоже можно упростить и исключить из анализа операцию изменяющую флаг, например, флаг С = старший бит V1 ^ старший бит V2, так ли это? PS: Процессор, оперируя с числами, не знает какое это число знаковое или беззнаковое и выставляет флаги для этих чисел одинаково в зависимости от значения конкретных бит.
Vam Думаю, проще будет на примере показать. В самом низу — сумма. Между линиями — биты переноса. Код (Text): 11010010 + 01110110 -------- 11110110 -------- 01001000 Самый левый бит переноса - это, очевидно, CF. А предыдущий - бит переноса в старший разряд. Вот эти два бита и ксорятся. Т.е. OF = 1 xor 1 = 0. Соответственно знакового переполнения здесь не произошло. Аналогично для вычитания, но там ксорятся биты заёма ([в старший разряд] и [из старшего разряда]).
l_inc Ок, с этим разобрались. Следовательно, к сожалению, получение (расчет) битов переноса/заема не является тривиальной задачей и зависит от инструкции, изменяющей эти биты, т.е. алгоритм расчета этих битов будет разным для разных инструкций. В итоге, чтобы получить биты переноса/заема, необходимо сделать побитовую эмуляцию инструкции. Как получить эти биты для операций сдвига или операций умножения/деления? Всё это говорит о том, что эмуляция флагов C и O является сложной задачей. Конечно, есть ещё один метод получения этих флагов - динамический. Создаем эту инструкцию на ассемблере, ассемблируем, выполняем и сохраняем регистр флагов - получится универсально и быстрее статической эмуляции.
Vam xor битов переноса - это действия процессора для получения OF. Вытекают они из вполне очевидных условий знакового переполнения (OF = 1): 1) Либо превышение максимального положительного числа (7F...). 2) Либо уход в сторону уменьшения минимального отрицательного числа (80...). Т.е. каким образом эмулировать — это уже Ваше дело. И совсем не обязательно делать это так же, как и процессор. Например, OF выставляется в 0 всегда, когда не происходит смены знака (при переходе от V1 к V2). Если происходит смена знака, то надо рассматривать два случая: 1) Смена знака с + на -. OF выставляется в 0, если V1 меньше V2 со сброшенным знаковым битом. Иначе OF = 1. 2) Смена знака с - на +. OF выставляется в 0, если V2 меньше V1 со сброшенным знаковым битом. Иначе OF = 1. В случае операций однобитного сдвига процессор работает с OF точь-в-точь, как и с операциями сложения/вычитания. Пример (shl): Код (Text): <- 01011010 ———————— 010110100 Здесь результат совпадает с битами переноса. Т.к. произошёл перенос в старший разряд, но не произошло переноса из старшего разряда (1 xor 0), то OF выставляется в 1. В случае многобитных сдвигов OF согласно Intel Instruction Reference не изменяется. С умножением и делением ещё проще (см. Intel Instruction Reference).
нефик его ломать. люди, пользующиеся вмпротом либо дибилы, либо конкретно вылечены рекламой этого прота. Это же надо додумацца - изменять логику программы при прогоне ее через виртуальную машину! (флаги, фар переходы, свитчкейс) Хотя че взять с человека, который смотрит на асм код только и исключительно после борландовского компиля. Политех привет.
Небольшая статистика. Запротектил одну среднюю субрутину. Написано на C++ 6.0 Количество инструкций до: 919 После протекта: 40 822. По скорости примерно во столько же раз медленнее. Но впринципе это неважно, когда речь идеит о защите нескольких подпрограмм. Ради интереса сделал статистику : ДО ПОСЛЕ VM-ПРОТЕКТА PUSH = 65 PUSH = 1874 CALL = 24 CALL = 24 POP = 20 POP = 1803 POPAD = 0 POPAD = 0 PUSHAD = 0 PUSHAD = 0 PUSHFD = 0 PUSHFD = 519 POPFD = 0 POPFD = 9 JMP = 33 JMP = 5182 MOV = 255 MOV = 6061 SUB = 3 SUB = 1032 ADD = 77 ADD = 851 AND = 11 AND = 1228 XOR = 6 XOR = 8450 NOT = 1 NOT = 2913 NOP = 0 NOP = 0 INC = 33 INC = 5591 DEC = 65 DEC = 2652 TEST = 44 TEST = 43 CMP = 29 CMP = 29 JE = 7 JE = 10 JNE = 0 JNE = 0 JZ = 0 JZ = 0 JNZ = 82 JNZ = 81 LEA = 41 LEA = 350 RETN = 8 RETN = 15 JA = 4 JA = 4 JB = 8 JB = 8 LOOP = 0 LOOP = 0 SET = 1 SET = 1 ROL = 0 ROL = 254 MUL = 3 MUL = 3 SHR = 3 SHR = 28 SHL = 33 SHL = 101 ROR = 0 ROR = 595 XCHG = 0 XCHG = 3 STOS = 0 STOS = 0 REP = 0 REP = 771 LEAVE = 2 LEAVE = 2 IDIV = 0 IDIV = 0 NEG = 0 NEG = 411
Сорри за некропост - но насчёт Спектрума в посте №161 написана полная ересь, впрочем как и о сговоре MS и Intel )) Ну а если по теме - то тема плавно переехала вот сюда: http://exelab.ru/f/index.php?action=vthread&forum=13&topic=15906