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

Родные приложения

Николай «gorl» Андреев

Хакер, номер #082, стр. 082-114-3


#pragma comment(linker, "/SUBSYSTEM:NATIVE")

#pragma comment(linker,"/BASE:0x00010000")

#pragma comment(lib, "ntdll.lib")

Вместо обычной подсистемы WINDOWS я указал NATIVE, чтобы компилятор понял, с чем имеет дело, немного сдвинул вниз виртуальный адрес, по которому будет доступен образ приложения в памяти, и подключил библиотеку для работы с ntdll.dll. Это все можно было сделать и путешествуя по Properties Pages проекта, но, имхо, текстом удобнее. Это, что касалось настроек, теперь плавно погружаемся в кодинг.

#define _X86_

#include gt;

#include "nt.h"

Вот таким незамысловатым образом начинается сам код программы (не считая прагм): два хедера – один из DDK (не забудь, кстати, прописать пути к основным папкам DDK, содержащим хедеры и либы) с описанием основных структур и типов, другой с описаниям функций, экспортируемых ntdll.dll. Определение идентификатора _X86_ нужно для того, чтобы компилятор знал, какой вариант структур в ntddk выбирать, а то они могут различаться в зависимости от платформы.

Как программирование, так и выполнения любого более или менее обычного приложения начинается с функции с функции main (WinMain, _tWinMain, в общем, название может быть любым, а смысл остается – точка входа), наш случай не исключение. У родных форточкам программ тоже есть main-функция, однако ее описание несколько отличается. Во-первых, у нее не два параметра, как у консольных приложений, и не 4, как у обычных Win32, а один – указатель на какую-то структуру. И я так бы и забил на его предназначение – ну, PVOID, ну, и черт с ним – если бы не исследования мастера Руссиновича. Этот указатель он окрестил как PSTARTUP_ARGUMENT и ввел несколько интересных определений:

// структура, указатель на которую передается точке входа,

// к слову, обычно именуемую NtProcessStartup…

// я от этого названия отказался

typedef struct {

ULONG Unknown[3];

PENVIRONMENT_INFORMATION Environment;

} STARTUP_ARGUMENT, *PSTARTUP_ARGUMENT;

// структура, на которую ссылается STARTUP_ARGUMENT

// из интересного содержит командную строку, с которой

// было запущено приложение и имя файла

typedef struct {

ULONG Unknown[21];

UNICODE_STRING CommandLine;

UNICODE_STRING ImageFile;

} ENVIRONMENT_INFORMATION, *PENVIRONMENT_INFORMATION;

Во-вторых, main-функция войдовая, то есть она ничего не возвращает. Я уверен, что ты в курсе, что обычно для завершения работы приложения в main’е используется что-нибудь вроде return 0, но если ты думаешь, что в нашей функции достаточно будет написать просто return, то ты ошибаешься. По ret’у мы ускачем неизвестно куда, так как в стеке может быть любая белиберда. Здесь подход иной:

NtTerminateProcess(NtCurrentProcess(), 0);

Вот так следует делать, если хочешь, чтобы твоя программа когда-нибудь завершилась. Если собрать все вышесказанное в кучку выйдет вот такая main-функция:

void native_main(PSTARTUP_ARGUMENT arg)

{

NtTerminateProcess(NtCurrentProcess(), 0);

}

Странное имя для main-функции, не правда ли? Компилятор тоже так подумает, поэтому надо его предупредить добавлением еще одной прагмы.

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