Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #91, ИЮЛЬ 2006 г.

Экстремальная оптимизация

Крис Касперски

Хакер, номер #091, стр. 091-120-3


.text:00401000 E8 FF 2F 00 00 call near ptr GetVersion

...

.idata:00404004 ?? ?? ?? ?? extrn GetVersion:dword ; DWORD GetVersion(void)

Так вот где собака зарыта! Компилятор сгенерировал переход по адресу, где расположено двойное слово, принадлежащее таблице импорта (секция .idata) и содержащее указатель на API-функцию GetVersion.

Неудивительно, что попытка интерпретации таблицы импорта, как исполняемого кода, приводит к краху, и, чтобы программа заработала правильно, необходимо использовать косвенную адресацию, заключив имя функции в квадратные скобки и выставив перед ними знак префикса cs: или ds:.

Правильный код выглядит так:

__asm{

call ds:[GetVersion] ; косвенный вызов API-функции

}

При вызове функций, представленных в двух вариантах — ASCII и UNICODE, — мы можем указывать суффиксы A и W явно, а можем использовать «каноническое» имя функции без суффиксов, и тогда компилятор самостоятельно выберет нужный вариант (в зависимости от настроек по умолчанию или ключей компиляции). Вот косвенный вызов функции CreateProcess без указания суффиксов, предоставляющий компилятору свободу выбора одного из двух вариантов:

__asm{

; тут мы передаем аргументы

call ds:[CreateProcessW] ; косвенный вызов функции с суффиксом W

}

А вот его дизассемблерный листинг. Вызывается именно та функция, которая была указана:

.text:0040101E db 3Eh ; ds:

.text:0040101E call CreateProcessW ; вызывается UNICODE-версия функции

А теперь - косвенный вызов функции CreateProcess без указания суффиксов, предоставляющий компилятору свободу выбора одного из двух вариантов:

__asm{

; тут мы передаем аргументы

call ds:[CreateProcess] ; косвенный вызов функции без суффиксов

}

Ну что же, компилятор выбрал ASCII-вариант, что соответствует его настройкам по умолчанию:

.text:0040101E db 3Eh ; ds:

.text:0040101E call CreateProcessA ; вызывается ASCII-версия функции

При вызове функций типа system квадратные скобки ставить уже нельзя! Функция system является частью библиотеки времени исполнения (RTL — Run Time Library), линкуемой статическим образом, поэтому call system сработает, как и ожидалось, а вот call ds:[system] передаст управление по адресу 83EC8B55h, попытавшись проинтерпретировать начало функции system как указатель:

.text:0040100B 3E FF 15 1A 10 40 00 call dword ptr system

; косвенный вызов статически линкуемой функции

; приводит к тому, что первые 4 байта функции

; интерпретируются как указатель, и управление

; передается по адресу 83EC8B55h

...

.text:00401018 system proc near ; начало функции system

.text:00401018 55 push ebp

.text:00401019 8B EC mov ebp, esp

.text:0040101B 83 EC 10 sub esp, 10h

.text:0040101E 56 push esi

Таким образом, при вызове функций из ассемблерных вставок всегда следует учитывать специфику конкретной вызываемой функции, не надеясь на то, что компилятор сделает это за нас.

При программировании на чистом ассемблере подобная проблема не возникает, поскольку имена и типы вызовов функций всегда объявляются вручную (или через включаемые файлы), и мы заранее знаем, как именно интерпретирует их транслятор. При работе с ассемблерными вставками подобной уверенности у нас нет. В частности, если компилятор решил использовать инкрементную линковку, то имя функции интерпретируется уже не как указатель на двойное слово из таблицы импорта, а как указатель на «переходник», представляющий собой jmp [pFunc], то есть нам квадратные скобки не нужны!

Назад на стр. 091-120-2  Содержание  Вперед на стр. 091-120-4