Pull to refresh

Comments 75

UFO just landed and posted this here
Насколько я помню оригинальное обсуждение с MsRem — этот пикод был результатом работы одной из его наработок (некоего протектора) который он в итоге так и не опубликовал. Сам протектор строил пикод через дизассемблер.
UFO just landed and posted this here
Если честно не в курсе, т.к. у меня разработка идет только под Windows :)
Но вообще на www.crackmes.de/ можно найти много разных вариантов :)
UFO just landed and posted this here
UFO just landed and posted this here
Нет, такой точки нет, вся логика целиком спрятана в Brainfuck скрипте, поэтому как бы ни хотелось — придется разбирать именно его.
Более-того. Автор кейгенми мог бы и поиздеваться написав его интерпретатор и выложив его в исходных кодах. Исходный код ничем бы не помог реверсеру, пришлось бы в любом случае применять голову!
Согласен, но… имеем то, что имеем. Видимо у автора были на то причины не писать интерпретатор, а выкладывать именно в том виде с которым и шла работа :)
Спасибо, вспомнил молодость!
А статья наверно заняла чемпионское мест на хабре по размеру!
Спасибо :) Правда у меня почему-то не получается маленькие писать, постоянно такие вот «талмуды» выходят :)
Rouse Ну вся соль этого кейгенми это проверка серийника в терминах ВМ. Следовательно нужно было начать с этапа «У нас имеется вирт.машина и пикод». Там паковка и лоадер достаточно понятны ;)
Я хотел расмотреть все в комплексе, если разнести по кускам — то получится что-то не совсем читабельное (мы же это уже обсуждали :)
Очень хорошо получилось, спасибо!
Имхо, лучше одну цельную статью, чем маленькую с недоговорками, или множество маленьких, разбитые на части, где продолжение неизвестно когда выйдет. А когда выйдет — забудется предыдущая часть. В общем, респект вам за такую статью, было очень интересно прочесть.
Сколько бы не росла производительность процессоров, им всё равно будет чем заниматься.
Спасибо за интересную статью. Даже не предполагал, что методы защиты столь сложны.
Это еще не сложны :) Коммерческие протекторы (особенно ручной работы под конкретный продукт) это настоящие произведения искусства. Там используются кучи матана, включая алгебры в конечных полях, коды Рида-Соломона, вложенные виртуальные машины и много других страшных слов. А еще нормальный протектор делает так, что защищаемая программа никогда не появляется в оперативной памяти целиком. Грубо говоря, код расшифровывается только на момент исполнения, а потом уничтожается. Это чтобы нельзя было сдампать процесс. И это помимо штатных утилитарных действий по противодействию отладке и обнаружению исполнения в виртуальной машине.

Ну а вообще, это отличная иллюстрация того, почему программы по прежнему тормозят :)
Про «кучу матана» Вы загнули.

А что до конечных полей, так любой код что использует современные средства шифрования к примеру AES уже использует конечные поля сам того не ведая. Применение кодов Рида-Соломона, разве что в статьях Криса Касперски можно почитать, а на практике не слышал чтобы хоть кто-то применял корректирующие коды, а знаком со многими, кто разрабатывает системы защиты ПО.

А с остальным: вирт. машины, никогда не появляется целиком в памяти — согласен.

Добавлю что при разработке защиты ПО спасает не знание математики, а опыт reverse-engineering. Другими словами, Вы не сможете написать хороший протектор, если ни разу не поломали другой протектор! Причем под взломом понимается разработка статического распаковщика, а не динамического.

Также играет огромную роль знание системного программирования как работают отладчики и дизассемблеры + фантазия. В свое время до 2003-го года один из русских системщиков в своем протекторе выставлял в PE32-файлах рандомные данные в качестве отладочной информации. Так вот системному загрузчику наплевать на отладочную информацию, а вот отладчики сходили с ума. Фантазия! ;)
Позволю себе не согласится с данными утверждениями.
Криптографический матаппарат работающий с использованием полей Галуа крайне неудачная точка виртуализации кода всвязи с сильной просадкой производительности его алгоритма, поэтому он обычно используется в виде нативного кода вызываемого VM.
Подход с искажением данных исполнения, для восстановления которых могут применяться коды Рида-Соломона — это очень редкий подход, просто немного затрудняющий анализ кода исследуемого приложения (да и вообще я за всю жизнь таких только два встречал).
Динамический декрипт кода, также не оправдывает возлагаемых на него надежд, я думаю вы помните историю с армой, которая распаковала сама себя…
Вложенные VM — да, вот это прямое попадание, здесь есть просадка (tсли не считаnь обфусцированного кода выполняемого в цикле оригинальной VM)
Ну а защита от дампа — это, пардон, нельзя даже рассматривать как защиту. Так — шутка…
UFO just landed and posted this here
Софт защищается разный, оправдано. А по поводу невзломанного софта без хардварных ключей — мне такие экземпляры не известны.
Динамический декрипт кода, также не оправдывает возлагаемых на него надежд

Здравствуйте. Почему, если не секрет? Интересная же идея. Я не видел готовых реализаций, но как РоС кодил что-то вида: 1 процедура распаковывает другую, выполняет, потом вторая третью, при этом освобождая память, выделенную под первую (или перезаписывая мусором). Такой себе «свиток» получается, когда целиком тела программы в памяти нет. Если это еще завернуть в ту же ВМ, имхо очень сложно будет такое осилить.
з.ы. никакие протекторы и подобную ересь не пишу, это просто хобби.
Потом распакованные блоки дампятся и вставляются в тело программы, а код распаковки выпиливается за ненадобностью.
Но поиграться реверсеру придется ).
Кстати, пользуясь случаем, хотел поблагодарить за сайт (и блог). Давно его читаю, нашел много интересного. Единственное — почему-то не работает ссылка на скачивание оффлайн версии (без книг, книги отдельно выкачал).
В зависимости от защиты, много можно автоматизировать. Под DOS когда-то была программа autohack, которая для многих таких программ автоматически всё расшифровывала.
Оффлайн версия находилась на стороннем ресурсе и собиралась тамошним скриптом. Все это было давно и уже не работает, надо как время будет выпилить ссылку :)
Такие посты огорчают меня. Чувствуешь себя ничтожеством :)
Ну почему? Это просто видимо не ваша область и всего-то. К примеру я очень сильно плаваю в Базах Данных и к примеру вот такие статьи навивают на меня тоску: habrahabr.ru/post/27439/
Ибо читая их я тоже понимаю насколько далек от этой области :)
Rouse Ты все-таки не отошел от «читерства» ;) Напомню о чем я:
В боевых приложениях у реверсера нету правильного серийного номера и он вынужден работать с тем что есть. При реверсе кейгенми более правильным было исследовать без использования правильного серийника, так реверсер более приближен к боевым условиям. Ведь цель любого кейгемни\крякмиса это позволить реверсеру оттачивать навыки. При знании правильного серийника реверсер может проверить себя на правильность оптимизации пикода более быстрым неправильным способом. А без использования правильного серийника он вынужден учиться тем навыкам, которые действительно нужны и в боевых условиях.
А вообще рад что такой мануал появился ;) Знаю еще одну интересная «игрушку» ;)
Изначально keygenme шел с валидной парой, поэтому я рассмматривал именно этот вариант.
Впрочем, отсутствие валидной пары всего лишь немного задержало бы поный реверс алгоритма, ибо в этом случае пришлось бы дополнительно проанализировать одну единственную процедуру логики во всех семи матоперациях (за исключением пятой).
Ну лишние часа полтора в итоге заняло бы (граф же сразу все показывает).
Здорово! Правда, я дочитал только до того момента, когда оказалось, что там внутри виртуальная машина и надо разбираться еще и с ней.
А вот то, что автор там все до конца разобрал — это реальный героизм! Сколько же терпения надо, не говоря уже про знания…
На самом деле терпения нужно не много.
Чистого времени было затрачено в районе 28 часов, из которых первоначальный анализ (до второго конверта включительно) около 30 минут и разбор логики VM в районе двух часов. Остальное время заняло рутинное написание инструментария, без которого никак (а это почти девять вечеров).
Но правда у меня было небольшое преимущество — такой реверс, это практически один из основных этапов моей основной работы (нет, я не вирусный аналитик), поэтому можно сказать я занимался своей повседневной рутиной :)
Прочитал на одном дыхании, огромное спасибо, всегда хотел узнать как идут размышления в голове реверсеров ибо самому данная тема небезынтересна.
А не смотрели визуализатор из IDA?
Там есть отдельный .exe-шник, который показывает файлы, отформатированные (если правильно помню) в формате gdl. Можно геренировать последние самостоятельно, при этом по желанию раскрашивать разными цветами, выделять курсивом, полужирным и т.д. — и потом скармливать результат визуализатору.

Мне в своё время очень помогло, когда отлаживал свою реализацию B-tree. Наглядности хотелось, а вот заморачиваться собственной отрисовкой ну совсем нет.
Если честно нет, я IDA в повседневной работе мало использую (только как инструмент для внешнего анализа) поэтому мог и пропустить такой момент :)
Основной инструмент всегда был и есть Olly
Кстати, позволю себе небольшой оффтоп, раз уж вы про Олли сказали. Вторую версию таки допилили до юзабельного состояния? В смысле плагинов, анти-антиотладчика, скриптов и прочего? В то время, когда я ее щупал, там ничего не было, даже половины обычных инструментов и окон.
Мое мнение, что вторая олька допилена и хорошо работает. Вот только народ с первой версии не стремится слезать. Сейчас все больше люди стремятся под IDA Pro, т.к. все что нужно реверсеру там есть и даже IDA-Stealth плагин или WinDbg, если под Windows
Вторую ольку использую сейчас как основной инструмент — рекомендую.
НО!
Проблема с плагинами, поэтому у меня всегда на подхвате первая версия с полным настроенным комплектом нужных инструментов.
Вполне вероятно что под двойку уже есть решения, но у меня есть старый принцип — я ленив, и самому тестировать ошибки не охота (а тем более разбираться ошибки ли это в моем коде или в плагине).
Поэтому я все еще жду заключений от людей, которых я считаю экспертами, о том что «плагин Х во второй ольке работает корректно».
Огромное спасибо за подробный разбор более-менее современной защиты!

С вашими выводами согласен. Замусоривание расшифровщика выполнено не на высшем уровне. Наибольшую сложность представляет применение Brainfuck в виртуальной машине. А если бы там был не Brainfuck (для которого существуют хоть какой-то инструментальный софт), а какая-нибудь доморощенная тьюринговская трясина — то было бы еще сложнее.

В защитах уже давно стало модным применение виртуальных машин. Инструменты анализа, такие как IDA Pro или отладчики, обычно приспособлены к конкретной машине и поэтому неприменимы для анализа пи-кодов. Писать каждый раз транслятор, как в вашем случае — из Brainfuck в x86 ASM — тоже непродуктивное решение. Напрашивается создание гибких средств анализа и отладки, для которых можно задать произвольную виртуальную машину в каком-то виде. Быть может, в антивирусных компаниях такие средства уже имеются.
Гарантированно имеются, ребята из касперского развернули всю логику данной VM за вечер (информация, скажем так, из инсайда :).
Но я бы удивился если бы у них на такой случай небыло бы инструментов :)
Напрашивается создание гибких средств анализа и отладки, для которых можно задать произвольную виртуальную машину в каком-то виде.

В IDA это есть. Можно к примеру на python'e написать процессорный модуль для чего угодно www.hexblog.com/?p=116
Спасибо, интересная ссылка, раньше не обращал на внимания на такие возможности IDA
Спасибо за статью! А вы не могли бы написать про «читерские» патчи и защиту от них?
Немного не понял, вы говорите про защиту от изменения данных в виртуальной памяти приложения?
Я имею ввиду обход защиты через патч. Т. е. распаковку и вставку JMP в обход защиты.
Круто! =) Спасибо за отличную статью.
Я бы наверное про брейнфак в последнюю очередь подумал внутри криптора…
Вот поэтому и нужно изучать теорию и эзотерические языки программирования! Раз в протекторах применяются виртуальные машины — нужно знать, какими они бывают. А вариантов на самом деле немного. Брейнфак представляет собой едва ли не минимально возможный набор команд виртуальной машины. Поэтому, даже если бы там был не «чистый» брейнфак, а какая-нибудь вариация на его тему — все равно теория, разработанная для брейнфака, оказалась бы применимой в какой-то мере.
Я бы по другому сказал: надо изучать «computer science».
>>едва ли не минимально возможный
Ошибаетесь, есть еще меньше, к примеру машина Поста! Опять же из мира computer science. Сети Петри, Конечные цепи Маркова(некоторые умельцы черпают идеи и отсюда). А есть еще и Копирование Бита – Простейшая Вычислительная Машина
Крутая магия.
Всю жизнь хотел разобраться в таких вещах — но не нужно было, а теперь уже и времени нет.
Статья++.

*На правах старческого ворчания*
В начале восьмидесятых троллей ещё не было, но Марк 'MZ' (0x4D, 0x5A) Збиковски уже был, и был он тролль.
По его стандарту, валидным заголовком для первых двух байт .EXE являлись не только символы 'MZ', но также и символы 'ZM', которые очень редко встречались в природе.
На чём, кстати, в своё время валилось не малое количество «определителей типа файла» и прочих отладчиков.

Так вот, задачка «в тему» для первого класса церковно-приходской школы.
Написать алгоритм, сравнивающий строку из двух символов на равенство 'MZ' или 'ZM', используя не более одного if (т.е. else if использовать нельзя).
… имеется в виду, написать в командах ассемблера (без всяких «или» в аргументе if)
function IsNotMZorZM(wParam: Word): Boolean;
asm
  cmp ax, $4D5A
  jne @check_5a4d
  xor eax, eax
  ret
@check_5a4d:
  xor ax, $5A4D
  or  al, ah
end;


Возвращает True если wParam не равен $4D5A или $5A4D
Здесь все равно применяется два условных оператора. Один из них отсутствует в тексте этой подпрограммы, но он подразумевается в программе, вызывающей ее.
Ну с наружи то всеравно кто-то должен проверить результат? Тем более про это в условиях задачи не упоминалось.
Я попробовал сделать без jxx используя маску ксора 0х1717 (примерно так, как в варинате от EvilsInterrupt чуть ниже), но там оч много коллизий…
Разделение алгоритма на «снаружи» и «внутри» — это просто способ замаскировать еще один условный оператор.

Чисто математически можно предложить решение, основанное на сумме и модуле разности кодов символов. Обе эти операции коммутативны, и проверив результат обеих на равенство нужным числам, можно выполнить одно ветвление.

Если бы в ассемблере x86 была целочисленная операция модуля — получилось бы эффективно и на ассемблере. Но можно вычислить модуль разности и с трюками. Например: код первого символа хранится в ax, код второго — в bx. Тогда:

sub bx, ax; разность в bx, если она отрицательна — установлен флаг C
sbc ax, ax; размножение знака во всех битах регистра ax
xor bx, ax; условная инверсия bx — первый этап получения противоположного числа
sub bx, ax; условное увеличение на 1 — второй этап получения противоположного числа

Модуль разности не может быть больше 255, поэтому можно смело взять его младшие 8 бит. Сумма символов, однако, будет занимать 9 бит, и ни один бит отбрасывать нельзя, иначе будут ложные срабатывания. Но если разместить эти 8+9=17 бит на 32-битном регистре — то после этого можно одной командой сравнения или xor получить результат на флаге Z и далее будет всего одна команда ветвления.
Плиз, пишите ответ в base64. Те кто хочет ответ, раскодирует, это не сложно. А те кто не хочет подсказки, от него будет скрыто.
Если вам что-либо непонятно в моем ответе — спросите, мне несложно будет разъяснить.
Наоборот! Пояснение более чем понятно. Я о другом. О том что людей желающих поломать голову на хабре много! Программистов желающих хорошую головоломку не следует так обижать давая им ответ сразу! Показать свою крутость всегда можно другими средствами, то что Вы знаете ответ Вы могли бы показать скрыв ответ с помощью base64. Те кто хочет ответа могут раскодировать, а те кто хотят поломать голову не увидят ответ сразу! И овцы сыты и волки целы!
Ничего не понимаю. Я просто предложил вариант ответа, как и остальные. Может быть, он неоптимальный, и есть более эффективные способы. Почему вы не обращались с подобными просьбами к остальным, кто тоже предлагал свои варианты ответа?
Тут просто вас просят не давать ответ в открытом виде, чтобы он небыл подсказкой для тех, кто хочет самостоятельно решить данную задачу и прятать тело ответа в виде покрытого через Base64 блока данных, например вот так:

ZnVuY3Rpb24gQ2hlY2tfSXNOb3RNWm90Wk0oQSwgQjogQnl0ZSk6IEJvb2xlYW47CmFzbQogIC8vIGFsIC0g0L/QtdGA0LLRi9C5INCx0LDQudGCCiAgLy8gZGwgLSDQstGC0L7RgNC+0Lkg0LHQsNC50YIKICAvLyBlY3ggLSDQvdCw0LrQvtC/0LjRgtC10LvRjNC90YvQuSDQsdGD0YTQtdGAINC00LvRjyDRgNC10LfRg9C70YzRgtCw0YLQsCAoMCAtINC10YHQu9C4INCy0YHQtSDRg9GB0LvQvtCy0LjRjyDQv9GA0L7RiNC70LgpCiAgbW92enggZWN4LCBhbAogIG1vdnp4IGVkeCwgZGwKICB4b3IgZWN4LCBlY3gKICBwdXNoIGVheAogIHB1c2ggZWR4CiAgLy8g0L/QtdGA0LLQvtC1INGD0YHQu9C+0LLQuNC1IC0gKChBICogQikgLSAkMUIxMikgPSAwCiAgaW11bCBlZHgKICBzdWIgZWF4LCAkMUIxMgogIG1vdiBlY3gsIGVheAogIC8vINCy0YLQvtGA0L7QtSDRg9GB0LvQvtCy0LjQtSAtIChBICsgQiAtICRBNykgPSAwCiAgbW92IGVheCwgW2VzcF0KICBhZGQgZWF4LCBbZXNwICsgNF0KICBzdWIgZWF4LCAkYTcKICBhZGQgZWN4LCBlYXgKICAvLyDRgtGA0LXRgtGM0LUg0YPRgdC70L7QstC40LUgLSAoKEEgeG9yICRBQSkgKyAoQiB4b3IgJEFBKSAtICQxRDcpID0gMAogIG1vdiBlYXgsIFtlc3BdCiAgeG9yIGVheCwgJEFBCiAgbW92IGVkeCwgW2VzcCArIDRdCiAgeG9yIGVkeCwgJEFBCiAgYWRkIGVheCwgZWR4CiAgc3ViIGVheCwgJDFENwogIGFkZCBlY3gsIGVheAogIC8vINGA0LXQt9GD0LvRjNGC0LDRgiDQuNC3INCx0YPRhNC10YDQsCDQsiBlYXggKDAgLSDQtdGB0LvQuCDQstGB0LUg0L/RgNC+0LLQtdGA0LrQuCDRg9GB0L/QtdGI0L3RiykKICB4b3IgZWF4LCBlYXgKICBhZGQgYWwsIGNsCiAgb3IgYWwsIGNoCiAgc2hyIGVjeCwgMTYKICBvciBhbCwgY2wKICBvciBhbCwgY2gKICAvLyDQv9GA0LDQstC40Lwg0YHRgtC10LoKICBhZGQgZXNwLCA4CmVuZDs=

Это кстати и есть ответ на эту задачу, без использования JXX, посмотреть декодированный текст можно преобразовав его обратно при помощи вот такого сервиса: base64.ru/
Либо можно прятать ответ под спойлер (ели не охота возиться с Base64 декодированием).

К примеру вот так :)
function Check_IsNotMZorZM(A, B: Byte): Boolean;
asm
  // al - первый байт
  // dl - второй байт
  // ecx - накопительный буфер для результата (0 - если все условия прошли)
  movzx ecx, al
  movzx edx, dl
  xor ecx, ecx
  push eax
  push edx
  // первое условие - ((A * B) - $1B12) = 0
  imul edx
  sub eax, $1B12
  mov ecx, eax
  // второе условие - (A + B - $A7) = 0
  mov eax, [esp]
  add eax, [esp + 4]
  sub eax, $a7
  add ecx, eax
  // третье условие - ((A xor $AA) + (B xor $AA) - $1D7) = 0
  mov eax, [esp]
  xor eax, $AA
  mov edx, [esp + 4]
  xor edx, $AA
  add eax, edx
  sub eax, $1D7
  add ecx, eax
  // результат из буфера в eax (0 - если все проверки успешны)
  xor eax, eax
  add al, cl
  or al, ch
  shr ecx, 16
  or al, cl
  or al, ch
  // правим стек
  add esp, 8
end;


Ну и сразу пример вызова данной функции:

var
  A, B: Integer;
begin
  for A := 0 to 255 do
    for B := 0 to 255 do
      if not Check_IsNotMZorZM(A, B) then
        Writeln(IntToHex(A, 2), IntToHex(B, 2));
  Readln;
end.


Выводимый результат:

4D5A
5A4D
Кстати вот так можно еще проще:

Возвращает True если передано MZ или ZM
function Check_IsMZorZM(A, B: Byte): Boolean;
asm
  and eax, $FF
  and edx, $FF
  mov ecx, eax
  xor ecx, edx
  sub ecx, $17
  imul edx
  lea ecx, [eax + ecx - $1B12]
  cmp ecx, 0
  setz al
end;

push bx
mov bx, ax
xor ax,bx
pop bx
sub ax,0x1717
; ax - будет отличным от нуля. если изначально ax был не равен 'MZ' или 'ZM'

Проверил на всем диапазоне от 0 до 0xFFFF? ;)
Я просто примерно такой-же вариант первым делом рассматривал :)

И у тебя тут явно ошибка, ибо вот это даст ноль в любом случае:

mov bx, ax
xor ax,bx
>>церковно-приходской школы.
Всегда хотел знать откуда это пошло в среде системщиков?! Еще Sergio /HITECH в своей рассылке про церковно приходскую школу упоминял )
Я правильно понял, что защищённая этой системой программа была скомпилирована в байткод брэйнфака?
А сама программа с такой защитой не начала адски тормозить?
Да. правильно. А тормозить там нечему, т.к. обфускация одной реальной ASM инструкции сводится примерно к 250-ти ASM строчкам, что практически не показатель. В современных коммерческих протекторах размер кода эмулирущего выполнение инструкции в разы больше и не тормозит ничего (ну в смысле это можно заметить только замеряя специально, а для человека это не заметно).
Колоссальный труд, огромное спасибо!
Именно из-за таких статей хабр-торт.
Статья грандиозная и написана понятно, спасибо вам! Читал я раньше про всё это веселье с ассемблером и отладчиками, обычно уже после второго-третьего листинга нить повествования терялась. А тут всё очень понятно описано, дочитал до конца, снова появилось желание попробовать поковырять exe'шники пары программ, которые давно хотел «поломать».

Немного офтопа. Посмотрел на графы, и хотел бы спросить у сообщества: есть ли какие-то более-менее адекватные бесплатные инструменты, которые могли бы строить нечто подобное по исходному коду CPP, а ещё лучше не графы, а сразу алгоритмы, хотя бы примерно. Всё программы, которые я нашёл в сети, работают очень криво, либо вообще не работают.
Sign up to leave a comment.

Articles