2. Базовая информация
Хорошо, начнем очищение вашей головы от концепций MS-DOS'овского программирования: чарующих 16-ти битных смещений, способов, как стать резидентными... все, что мы использовали годами, теперь стало абсолютно бесполезным. Да, теперь все это бесполезно. В данном пособии, когда я говорю о Win32, я подразумеваю Windows 95 (обычные, OSR1, OSR2), Windows 98, Windows NT или Windows 3.x + Win32s. Наиболее главное изменение, на мой взгляд, заключается в том, что прерывания были заменены функциями API, а 16-ти битные регистры и смещения были заменены 32-х битными. Да, Windows открыла двери другим языками программирования, кроме ассемблера (таким как C), но я останусь с ассемблером навсегда: в большинстве случаев он более понятен и удобен для оптимизирования (привет, Super!). Как я сказал несколькими строчками выше, вы должны использовать функции API. Вы должны знать, что параметры должны быть в стеке, а функции API вызываются с помощью call.
PS: Также я должен сказать, что называю Win95 (и все ее версии) и Win98 как Win9X, а Win2000 как Win2k. Учтите это.
Изменения между 16-ти и 32-х битным программированием
Как правило мы будем работать с двойными словами вместо слов, и это открывает перед нами новые возможности. У нас есть на два сегмента больше, кроме уже известных CS, DS, ES и SS: FS и GS. И у нас есть новые 32-х битные регистры: EAX, EBX, ECX, EDX, ESI, EDI, EBP и ESP. Давайте посмотрим, как играть с регистрами: представьте, что у нас есть доступ к нижнему слову EAX. Что мы можем сделать? До этой части можно добраться с помощью AX, который и является нижним словом EAX. Например, EAX = 00000000, а мы хотим поместить 1234h в его нижнее слово. Мы просто должны сделать "mov ax, 1234h" и все. Но что, если нам нужен доступ к верхнему слову EAX? Для этих целей мы не можем использовать регистр: мы должны прибегнуть к какой-нибудь из инструкций вроде ROL (или SHL, если значение нижнего слова для нас неважно).
Давайте продолжим и рассмотрим типичную программу, которую пишет кодер, изучая новый язык: "Hello, world!" . "Hello, World" под Win32
Это очень легко. Мы должны использовать функцию API "MessageBoxA", поэтому мы определяем ее с помощью команды "extrn", а затем помещаем параметры в стек и вызываем эту функцию. Обратите внимание, что строки должны быть в формате ASCIIZ (ASCII, 0). Помните, что параметры должны помещаться в стек в перевернутом порядке.
;---[ CUT HERE ]-------------------------------------------------------------
.386 ; Процессор (386+)
.model flat ; Использует 32-х битные
; регистры
extrn ExitProcess:proc ; Функции API, которые
extrn MessageBoxA:proc ; использует программа
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
; С помощью директивы "extrn" мы указываем, какие API мы будем использовать;
; ExitProcess возвращает контроль операционной системе, а MessageBoxA ;
; показывает классическое виндовозное сообщение. ;
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
.data
szMessage db "Hello World!",0 ; Message for MsgBox
szTitle db "Win32 rocks!",0 ; Title of that MsgBox
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
; Здесь мы не будем помещать данные настоящего вируса. Фактически, только ;
; из-за того, что TASM отказывается ассемблировать код, если мы не поместим;
; здесь какие-нибудь данные. Как бы то ни было... Помещайте здесь данные ;
; 1-го поколения носителя вашего вируса. ;
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
.code ; Поехали!
HelloWorld:
push 00000000h ; Стиль MessageBox
push offset szTitle ; Заголовок MessageBox
push offset szMessage ; Само сообщение
push 00000000h ; Хэндл владельца
call MessageBoxA ; Вызов функции
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
; int MessageBox( ;
; HWND hWnd, // хэндл окна-владельца ;
; LPCTSTR lpText, // адрес текста сообщения ;
; LPCTSTR lpCaption, // адрес заголовка MessageBox'а ;
; UINT uType // стиль MessageBox'а ;
; ); ;
; ;
; Мы помещаем параметры в стек до вызова самой API-функции, и если вы ;
; помните, стек построен согласно принципу LIFO (Last In First Out), ;
; поэтому мы должны помещать параметры в перевернутом порядке (при всем ;
; уважении к автору данного туториала, я должен сказать, что он не прав ;
; вовсе не поэтому мы должны помещать данные в перевернутом порядке - прим.;
; пер.). Давайте посмотрим на краткое описание каждого из параметров этой ;
; функции: ;
; ;
; ¦ hWnd: идентифицирует окно-владельца messagebox'а, который должен быть ;
; создан. Если этот параметр равняется NULL, у окна с сообщением не будет;
; владельца. ;
; ¦ lpText: указывает на ASCIIZ-строку, содержащую сообщение, которое нужно;
; отобразить. ;
; ¦ lpCaption: указывает на ASCIIZ-строку, содержащую заголовок окна ;
; сообщения. Если этот параметр равен NULL, будет использован заголовок ;
; по умолчанию. ;
; ¦ uType: задает флаги, которые определяют содержимое и поведение ;
; диалогового окна. Это параметр может быть комбинацией флагов. ;
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
push 00000000h
call ExitProcess
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
; VOID ExitProcess( ;
; UINT uExitCode // код выхода для всех ветвей ;
; ); ;
; ;
; Эта функция эквивалентна хорошо известному Int 20h или функциям 00, 4С ;
; Int 21h. Она просто завершает выполнение текущего процесса. У нее только ;
; один параметр: ;
; ;
; ¦ uExitcode: задает код выхода для процесса и всех ветвей, выполнение ;
; которых будет прервано вызовом данной функции. Используйте функцию ;
; GetExitCodeProcess, чтобы получить код возврата процесса, а функцию ;
; GetExitCodeThread, чтобы получить код возврата треда. ;
;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;
end HelloWorld
;---[ CUT HERE ]-------------------------------------------------------------
Как вы можете видеть, это все очень просто программируется. а теперь, когда вы знаете, как сделать "Hello, world!", вы можете заразить весь мир .
Кольца
Я знаю, что все вы очень боитесь того, что сейчас последует, но, как продемонстрирую, все это на самом деле несложно. Вы должны уяснить: у процессора четыре уровня привилегий: Ring-0, Ring-1, Ring-2 и Ring-3, причем последний имеет больше всего ограничений, а первый подобен Валгалле для кодеров, почти полная свобода действий. Вспомните DOS, где мы всегда программировали в Ring-0... А теперь подумайте, что вы сможете делать то же самое под платформами Win32... Ладно, прекратим мечтать и начнем работать.
Ring-3 - это т.н. "пользовательский" уровень, на котором у нас множество ограничений. Кодеры Microsoft сделали ошибку, когда зарелизили Win95 и сказали, что она "незаражаема". Это было продемонстрировано еще до начала продаж системы Bizatch (которых еще неверно называли Boza, но это другая история). Программисты Microsoft думали, что вирус не сможет добраться до API. Но они не подумали об интеллектуальном превосходстве вирмейкеров... Конечно, мы можем писать вирус и на пользовательском уровне. Достаточно взглянуть на массу новых Win32 вирусов времени выполнения, которые релизятся в настоящее время, они все сделаны под Ring-3... Не поймите меня неверно, я не говорю, что это плохо, и между прочим, только Ring-3 вирусы могут работать под всеми версиями Win32. Они - будущее... в основном из-за того, что скоро будет релиз Windows NT 5.0 (или Windows 2000). Для успешной жизни вируса мы должны найти функции API (то, что написали Bizatch, размножалось плохо, потому что они "захардкодили" адреса API-функций, а они отличаются от версии к версии Windows), что мы можем сделать несколькими способами, о которых я расскажу позже.
Ring-0 - это другая история, очень отличная от Ring-3. Это уровень, на котором работает ядро. Разве это не прекрасно? Мы можем иметь доступ к портам, к местам, о которых не могли мечтать раньше... это почти что оргазм. Мы не можем выйти в Ring-0 без использования одного из специальных способов, таких как модификация IDT, техника "Call Gate", показанная SoPinKy/29A в 29A#3 или вставки в VMM, техника, продемонстрированная в вирусах Padania или Fuck Harry. Нам не нужны API-функции, так как мы работает напрямую с сервисами VxD и похоже, что их адреса одни и те же во всех версиях Win9x, поэтому мы можем их указывать явно. Я расскажу об этом подробнее во главе, посвященной Ring-0.
Важные сведения
Я думаю, что должен рассказать об этом вначале данного пособия, как бы то ни было, лучше об этом знать, чем не знать . Хорошо, давайте поговорим о внутренностях наших Win32-операционных систем.
Во-первых, вы должны уяснить несколько концепций. Давайте начнем с селекторов. Что такое селектор? Это очень просто. Это очень большой сегмент, и эта форма Win32-памяти также называется плоской памятью. Мы можем напрямую обращаться к 4 гигабайтам памяти (4.294.967.295 байтов) используя только 32-х битные смещения. И как организованна эта память? Давайте взглянем на одну из диаграмм, которые я так люблю делать:
Обратите внимание на одну вещь: у WinNT последние две секции находятся отдельно от первых двух. Ладно, теперь я помещу определения, которые вы должны знать. В остальной части туториала я буду исходить из того, что вы о них помните.
¤ VA:
VA расшифровывается как Virtual Address (виртуальный адрес). Это адрес чего-нибудь, но в памяти (помните, что в Windowz вещи на диске и в памяти не обязательно эквиваленты).
¤ RVA:
RVA расшифровывается как Relative Virtual Address. Очень важно четко это понять. RVA - это смещение на что-то, относительно того места, куда промэппирован файл (вами или системой).
¤ RAW-данные:
RAW-данные - это имя, которое мы используем для обозначения данных так, как они есть физически на диске (данные на диске != данные в памяти).
¤ Виртуальные данные:
Виртуальные данные - это имя, которым мы называем данные, когда они загруженны системой в память.
¤ Файловый мэппинг:
Техника, реализованная во всех операционных системах Win32, которая позволяет производить различные действия с файлом быстрее и затрачивать при этом меньше памяти, а также она более удобна, чем способы, практиковавшиеся в DOS. Все, что мы изменяем в памяти, также изменяется на диске. Файловый мэппинг - это единственный способ обмениваться информацией между различными процессами, который работает во всех Win32-операционных системах.
Как компилировать программы
Черт, я почти забыл об этом . Используются обычные параметры для компиляции ассемблерной программы под Win32. Все примеры в данном туториале компилируются со следующими параметрами (где 'program' - это имя файла .asm, но без какого-либо расширения):
tasm32 /m3 /ml program,,;
tlink32 /Tpe /aa program,program,,import32.lib
pewrsec program.exe
Я надеюсь, что это достаточно ясно. Вы также можете использовать make-файлы или написть .bat, который будет делать все автоматически (как сделал я!).
--------------------------------------------------------------------------------
[C] Billy Belcebu, пер. Aquila
<<< Назад Вперед >>>