Оптимальный дизассемблер — IDA PRO. Действительно очень мощный дизассемблер, его не так-то просто взять на испуг. С классическими защитными приемами (типа прыжка в середину команды) он справляется даже не замечая, что тут было что-то защитное. Если на пути хакера встретится стена зашифрованного/упакованного кода, он напишет короткий (длинный) скрипт и расшифрует все, что нужно, даже не выходя из дизассемблера. Для p-кода будет написан отдельный плагин типа «докомпилятор».
Самым мощным антихакерским средством был и остается косвенный вызов функций по указателю, передаваемому в качестве аргумента. Хакер натыкается на что-то типа call eax и матерится в бессильной злобе, пытаясь определить, что содержится в eax на данный момент. Если материнская функция вызывается обычным способом, взломщику достаточно просто перейти по перекрестной ссылке, заботливо сгенерированной IDA, и подсмотреть, что передается функции. Однако если указатель инициализируется далеко от места вызова, хакеру придется раскрутить всю цепочку вызовов. Если же функция, вызывающая косвенную функцию, сама вызывается косвенным путем, то вообще наступают кранты! Остается запускать отладчик, устанавливать точку останова на call eax и смотреть ее значение вживую, однако точкам останова легко противостоять. К тому же в различные моменты времени eax может указывать на разные функции, тогда простое подглядывание eax ничего не даст. Дизассемблер ослепнет и оглохнет!
Листинг
пример программы, вызывающей функции по указателю
sub_sub_demo(int a, void *p, void *d)
{
// printf(«sub_sub_demo\n»);
if (—a) return ((int(*)(int, void*, void*))p)(a, p, d);
return 0;
}
sub_demo(int a, void *p, void *d)
{
// printf(«sub_demo\n»);
if (—a) return ((int(*)(int, void*, void*))d)(a, p, d);
return 0;
}
demo(int a, void *p, void *d)
{
// printf(«demo\n»);
((int(*)(int, void*, void *))p)(a, p, d);
}
main()
{
demo(0×69,sub_demo, sub_sub_demo);
}
В исходном тесте все понятно. Функция main вызывает функцию demo, передавая ей указатели на sub_demo и sub_demo, которые поочередно вызывают друг друга, каждый раз уменьшая счетчик на единицу. Короче, мы имеем цикл. Но какой! Ты оцени его дизассемблерный код.
Листинг
дизассемблерный листинг, демонстрирующий мощь косвенного вызова функций
.text:00401000 loc_401000: ; DATA XREF: _maino
.text:00401000 mov ecx, [esp+4]
.text:00401004 dec ecx
.text:00401005 jz short loc_401018
.text:00401007 mov eax, [esp+0Ch]
.text:0040100B push eax
.text:0040100C mov eax, [esp+0Ch]
.text:00401010 push eax
.text:00401011 push ecx
.text:00401012 call eax
.text:00401014 add esp, 0Ch
.text:00401017 retn
.text:00401020
.text:00401020 loc_401020: ; DATA XREF: _main+5o
.text:00401020 mov ecx, [esp+4]
.text:00401024 dec ecx
.text:00401025 jz short loc_401038
.text:00401027 mov eax, [esp+0Ch]
.text:0040102B mov edx, [esp+8]
.text:0040102F push eax
.text:00401030 push edx
.text:00401031 push ecx
.text:00401032 call eax
.text:00401034 add esp, 0Ch
.text:00401037 retn
.text:00401040
.text:00401060 _main proc near ; CODE XREF: start+AF p
.text:00401060 push offset loc_401000
.text:00401065 push offset loc_401020
.text:0040106A push 69h
.text:0040106C call sub_401040
.text:00401071 add esp, 0Ch
.text:00401074 retn
.text:00401074 _main endp
IDA Pro не смог распознать функции, хотя восстановил перекрестные ссылки на main — они еще ни о чем не говорят! Функции sub_demo и sub_sub_demo вызываются совсем не оттуда. Возьмем «функцию» loc_401000. Она принимает указатель в качестве аргумента и тут же вызывает его. А что это за указатель? Да хвост его знает! Перекрестной ссылки на материнскую функцию же нет. Единственный способ установить истину — проанализировать всю цепочку вызовов с самого начала программы, с функции main, но слишком трудоемко, так что даже не обсуждается (особенно если ломать реальную программу). Даже в таком случае приходится постоянно следить за указателями, которые пляшут как кони, причем, даже если развяжешь себе пупок, не сможешь понять, что находится в них в каждый конкретный момент…
Защита будет значительно усилена, если реализовать модель Маркова — функцию, возвращающую указатель на функцию. Такой прием программирования не слишком популярен, так как непривычен и лишен языковой поддержки. Язык С вообще не позволяет объявлять функции, возвращающие указатели на функции, поскольку такие определения рекурсивны и программисту приходится возиться с постоянным преобразованием типов, что не слишком украшает программу и является потенциальным рассадником ошибок. Тем не менее на автоматическое дизассемблирование моделей Маркова не способен ни один дизассемблер, в том числе IDA PRO.
Рассмотрим пару способов борьбы с файловыми мониторами и мониторами реестра. Самое простое, что только можно придумать, — через FindWindow находить главное окно монитора и затем либо закрывать его к чертям
э либо, посылая специальные Window-сообщения, удалять из списка «свои» обращения. Естественно, если хакер переименует окно (делается через FindWindow/SetWindowText), защита обломается по полной программе. Так что такой прием слишком ненадежен.
А что надежно? Хранить флаг регистрации вместе с настройками в одном ключе реестра в двоичном виде — вот тут его мониторинг ничего не даст. Хакер видит, что из такой ветви читается куча байтов, но ему неведомо, какой из них и за что отвечает. Выяснить можно только отладкой/дизассемблированием защищенной программы, сопротивляться чему несложно.