Привет, уважаемый читатель!
Для многих исполняемые файлы являются каким-то черным ящиком.
Для сисадминов - это "сундук с кодом, который че то делает", для программиста - "результат работы компилятора", а для обычного пользователя это вообще просто название.
Но тут я описал средненького админа, прикладного или веб программиста и недалекого пользователя. А что такое это все для системного программиста?
Разбираемся
Начнем издалека. С картинок про котиков. Что такое картинка? Обычно это jpg/png файл. Он содержит в себе графическую информацию, иногда метаданные ( местоположение там и так далее ). Все это имеет в файле определенное положение. Но графические изображения - довольно простые файлы по сравнению с PE.
Стоп, что? PE? Да, Portable Executable ( PE ) - это общее название формата .exe, .dll, .sys, .com файлов.
Он также состоит из информации, которая имеет определенное положение. Как в графических - пиксели, метаданные. Только тут код и информация о том, как он хранится и что использует.
Давайте посмотрим. Простое приложение на Си, знакомое всем.
Что тут происходит? Приложение выводит Hello, World, 10. Но как? Давайте скомпилируем его при помощи gcc ( порт windows - mingw ).
Мы получили exe файл. При запуске на консоль выводит Hello, World, 10 и закрывается.
Из чего этот файл состоит? Итак, для начала. Когда мы запускаем exe файл загрузчик Windows проверяет его на валидность. То есть, является ли это экзешником?
Для этого он открывает файл для чтения и смотрит первые два байта: MZ - сигнатура PE файла. Попробуйте открыть наш исполняемый файл блокнотом и заменить там первые два байта на что-то другое. Запустим измененный файл. Я получаю такую ошибку:
Это основная сигнатура. Принадлежит она так называемому DOS заголовку. То есть, он перекочевал к нам из DOS. Там же находится смещение до PE заголовка, называемое e_lfanew.
Всего есть следующие заголовки: DOS, PE, опциональный, директория данных, таблица секций.
Рассмотрим по порядку.
В PE заголовке находится информация о типе нашего файла: EXE, DLL, ...
В опциональным то, как следует загружать нашу программу в память.
В директории данных смещение до таблицы импорта ( об этом подробнее далее )
В таблице секций находятся... внезапно, указатели на секции. Что такое вообще секции? Вспомните наш графический файл. Там всякая графическая информация, метаданные. Вот их можно разделить на две секции, например. То же самое и тут! Это секции кода, данных, ресурсов и прочей информации. Она вся структурирована.
Что такое таблица импорта?
Вот и добрались мы до нее. Ради нее мы в основном и писали тот наш хеллоуворлд. Что такое вызов функции printf? Это стандартная функция библиотеке C, определена она в Windows в microsoft visual c runtime: msvcrt.dll.
Dll файл - не зря он называется библиотекой. Он ничем почти не отличается от обычной exe программы, кроме того, что все функции в ней можно вызвать из вне. То есть, это скомпилированный файл функционала разного, который делится на функции. Одна из таких функций - printf.
Когда линковщик собирает машинный код из объектных файлов, которые сделал компилятор, и видит вызов printf ( или другой функции ), то он добавляет ее в таблицу импорта. Эта таблица просто показывает, какие функции программа использует и откуда. Во время загрузки нашего файла в память загрузчиком Windows, он загружает также в память msvcrt.dll и пишет в таблицу адрес функции printf, чтобы наша программа могла его использовать.
Есть замечательный бесплатный сервис pedump.me. Он показывает основную информацию о загруженном файле, также сканирует его антивирусом и позволяет дизассемблировать точку входа ( об этом потом ).
Давайте заного скомпилируем наш файл и загрузим туда.
Последнее время сервис лагает, когда даешь прямую ссылку. Поэтому если будет недоступно - не обессудьте. http://pedump.me/731066ad98599c8cf134336d22541e27/#info
Перейдем во вкладку imports. И увидим... о Боже, огромное количество функций! Откуда они? Почему так?
Я прокоментировал. Чтобы получить подробное описание каждой из функции забиваете в гугл апи, например, WriteConsole и первая ссылка на Microsoft MSDN с описанием и прототипом функции.
Посмотрим на файл в PeDump
http://pedump.me/d9f2795493dea83b7d19d714e266299d/#info
Бинго! Ничего лишнего, никаких зависимостей. Библиотеки kernel32.dll и user32.dll есть в каждой Windows, начиная с Win98, заканчивая Win10 ( на момент статьи больше винд не было )
Правда такие трюки могут давать False Positive от антивирусов, так как их обычно используют вирусописатели для избавления своих программ от зависимостей языка.
И плюшечка от IDA Pro, в этот раз я предоставлю весь код, который только есть. Как видим, ничего лишнего!
Для многих исполняемые файлы являются каким-то черным ящиком.
Для сисадминов - это "сундук с кодом, который че то делает", для программиста - "результат работы компилятора", а для обычного пользователя это вообще просто название.
Но тут я описал средненького админа, прикладного или веб программиста и недалекого пользователя. А что такое это все для системного программиста?
Разбираемся
Начнем издалека. С картинок про котиков. Что такое картинка? Обычно это jpg/png файл. Он содержит в себе графическую информацию, иногда метаданные ( местоположение там и так далее ). Все это имеет в файле определенное положение. Но графические изображения - довольно простые файлы по сравнению с PE.
Стоп, что? PE? Да, Portable Executable ( PE ) - это общее название формата .exe, .dll, .sys, .com файлов.
Он также состоит из информации, которая имеет определенное положение. Как в графических - пиксели, метаданные. Только тут код и информация о том, как он хранится и что использует.
Давайте посмотрим. Простое приложение на Си, знакомое всем.
#include <stdio.h> int main() { printf("Hello, World, %d\n", 10); return 0; }
Что тут происходит? Приложение выводит Hello, World, 10. Но как? Давайте скомпилируем его при помощи gcc ( порт windows - mingw ).
Мы получили exe файл. При запуске на консоль выводит Hello, World, 10 и закрывается.
Из чего этот файл состоит? Итак, для начала. Когда мы запускаем exe файл загрузчик Windows проверяет его на валидность. То есть, является ли это экзешником?
Для этого он открывает файл для чтения и смотрит первые два байта: MZ - сигнатура PE файла. Попробуйте открыть наш исполняемый файл блокнотом и заменить там первые два байта на что-то другое. Запустим измененный файл. Я получаю такую ошибку:
Это основная сигнатура. Принадлежит она так называемому DOS заголовку. То есть, он перекочевал к нам из DOS. Там же находится смещение до PE заголовка, называемое e_lfanew.
Всего есть следующие заголовки: DOS, PE, опциональный, директория данных, таблица секций.
Рассмотрим по порядку.
В PE заголовке находится информация о типе нашего файла: EXE, DLL, ...
В опциональным то, как следует загружать нашу программу в память.
В директории данных смещение до таблицы импорта ( об этом подробнее далее )
В таблице секций находятся... внезапно, указатели на секции. Что такое вообще секции? Вспомните наш графический файл. Там всякая графическая информация, метаданные. Вот их можно разделить на две секции, например. То же самое и тут! Это секции кода, данных, ресурсов и прочей информации. Она вся структурирована.
Что такое таблица импорта?
Вот и добрались мы до нее. Ради нее мы в основном и писали тот наш хеллоуворлд. Что такое вызов функции printf? Это стандартная функция библиотеке C, определена она в Windows в microsoft visual c runtime: msvcrt.dll.
Dll файл - не зря он называется библиотекой. Он ничем почти не отличается от обычной exe программы, кроме того, что все функции в ней можно вызвать из вне. То есть, это скомпилированный файл функционала разного, который делится на функции. Одна из таких функций - printf.
Когда линковщик собирает машинный код из объектных файлов, которые сделал компилятор, и видит вызов printf ( или другой функции ), то он добавляет ее в таблицу импорта. Эта таблица просто показывает, какие функции программа использует и откуда. Во время загрузки нашего файла в память загрузчиком Windows, он загружает также в память msvcrt.dll и пишет в таблицу адрес функции printf, чтобы наша программа могла его использовать.
Есть замечательный бесплатный сервис pedump.me. Он показывает основную информацию о загруженном файле, также сканирует его антивирусом и позволяет дизассемблировать точку входа ( об этом потом ).
Давайте заного скомпилируем наш файл и загрузим туда.
Последнее время сервис лагает, когда даешь прямую ссылку. Поэтому если будет недоступно - не обессудьте. http://pedump.me/731066ad98599c8cf134336d22541e27/#info
Перейдем во вкладку imports. И увидим... о Боже, огромное количество функций! Откуда они? Почему так?
Это все функции, которые использует наше маленькое на первый взгляд приложение. Почти в любой си программе есть так называемый CRT код. Именно с него начинается выполнение. Он инициализирует нашу программу для правильной работы с msvcrt библиотекой. Выделяет память, для работы malloc и так далее.
Сам наш код после компиляции выглядит так
; int __cdecl main(int argc, const char **argv, const char **envp)
public _main
_main proc near
Format= dword ptr -10h
var_C= dword ptr -0Ch
argc= dword ptr 8
argv= dword ptr 0Ch
envp= dword ptr 10h
push ebp
mov ebp, esp
and esp, 0FFFFFFF0h
sub esp, 10h
call ___main
mov [esp+10h+var_C], 0Ah
mov [esp+10h+Format], offset Format ; "Hello, World, %d\n"
call _printf
mov eax, 0
leave
retn
_main endp
Этот листинг на языке ассемблера был получен при помощи интерактивного дизассемблера IDA Pro. Чтобы не вдаваться в подробности, скажу, что это мнемоническое представление команд процессора.
Это только наша функция, а там есть еще тонны кода, который добавляет в наш файл gcc.
И зачем нам это? Не надо нам это, продолжим.
Ну и где системное программирование? Хочу его!
До этого я вам показал в общих чертах, как устроен исполняемый файл. А давайте сделаем тот же HelloWorld, только размером намного меньше!
Давайте скомпилируем при помощи gcc следующий файл.
>gcc --entry _mymain C:\c\test.c -o C:\c\out.exe -nostartfiles -node
faultlibs -mwindows -lkernel32 -luser32 -Os -s -Wl,--gc-sections
faultlibs -mwindows -lkernel32 -luser32 -Os -s -Wl,--gc-sections
#include <windows.h> // описание WinAPI функций
int mymain()
{
AllocConsole(); // создаем консоль
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // получаем идентификатор потока вывода
char *pOut = (char*)VirtualAlloc(0, 256, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // выделяем память размером 256. Аналог сишного malloc(256);
if (!pOut)
ExitProcess(-1);
wsprintfA(pOut, "Hello, World, %d", 10); // собираем строку
DWORD dwWritten;
WriteConsole(hOut, pOut, lstrlenA(pOut), &dwWritten, 0); // выводим в консоль
CloseHandle(hOut); // закрывает описатель
VirtualFree(pOut, 0, MEM_RELEASE); // освобождает память
ExitProcess(0); // выход из процесса
}
Я прокоментировал. Чтобы получить подробное описание каждой из функции забиваете в гугл апи, например, WriteConsole и первая ссылка на Microsoft MSDN с описанием и прототипом функции.
Посмотрим на файл в PeDump
http://pedump.me/d9f2795493dea83b7d19d714e266299d/#info
Бинго! Ничего лишнего, никаких зависимостей. Библиотеки kernel32.dll и user32.dll есть в каждой Windows, начиная с Win98, заканчивая Win10 ( на момент статьи больше винд не было )
Правда такие трюки могут давать False Positive от антивирусов, так как их обычно используют вирусописатели для избавления своих программ от зависимостей языка.
И плюшечка от IDA Pro, в этот раз я предоставлю весь код, который только есть. Как видим, ничего лишнего!
.text:00401000 public start
.text:00401000 start proc near
.text:00401000
.text:00401000 nStdHandle = dword ptr -38h
.text:00401000 dwSize = dword ptr -34h
.text:00401000 flAllocationType= dword ptr -30h
.text:00401000 flProtect = dword ptr -2Ch
.text:00401000 lpReserved = dword ptr -28h
.text:00401000 NumberOfCharsWritten= dword ptr -0Ch
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 push esi
.text:00401004 push ebx
.text:00401005 sub esp, 30h
.text:00401008 call AllocConsole
.text:0040100D mov [esp+38h+nStdHandle], 0FFFFFFF5h ; nStdHandle
.text:00401014 call GetStdHandle
.text:00401019 push ebx
.text:0040101A mov esi, eax
.text:0040101C mov [esp+38h+flProtect], 4 ; flProtect
.text:00401024 mov [esp+38h+flAllocationType], 3000h ; flAllocationType
.text:0040102C mov [esp+38h+dwSize], 100h ; dwSize
.text:00401034 mov [esp+38h+nStdHandle], 0 ; lpAddress
.text:0040103B call VirtualAlloc
.text:00401040 sub esp, 10h
.text:00401043 test eax, eax
.text:00401045 mov ebx, eax
.text:00401047 jnz short loc_401052
.text:00401049 mov [esp+38h+nStdHandle], 0FFFFFFFFh
.text:00401050 jmp short loc_4010C0
.text:00401052 ; ---------------------------------------------------------------------------
.text:00401052
.text:00401052 loc_401052: ; CODE XREF: start+47 j
.text:00401052 mov [esp+38h+flAllocationType], 0Ah
.text:0040105A mov [esp+38h+dwSize], offset aHelloWorldD ; "Hello, World, %d"
.text:00401062 mov [esp+38h+nStdHandle], eax ; LPSTR
.text:00401065 call wsprintfA
.text:0040106A mov [esp+38h+nStdHandle], ebx ; lpString
.text:0040106D call lstrlenA
.text:00401072 push edx
.text:00401073 lea edx, [ebp+NumberOfCharsWritten]
.text:00401076 mov [esp+38h+lpReserved], 0 ; lpReserved
.text:0040107E mov [esp+38h+flProtect], edx ; lpNumberOfCharsWritten
.text:00401082 mov [esp+38h+flAllocationType], eax ; nNumberOfCharsToWrite
.text:00401086 mov [esp+38h+dwSize], ebx ; lpBuffer
.text:0040108A mov [esp+38h+nStdHandle], esi ; hConsoleOutput
.text:0040108D call WriteConsoleA
.text:00401092 sub esp, 14h
.text:00401095 mov [esp+38h+nStdHandle], esi ; hObject
.text:00401098 call CloseHandle
.text:0040109D push ecx
.text:0040109E mov [esp+38h+flAllocationType], 8000h ; dwFreeType
.text:004010A6 mov [esp+38h+dwSize], 0 ; dwSize
.text:004010AE mov [esp+38h+nStdHandle], ebx ; lpAddress
.text:004010B1 call VirtualFree
.text:004010B6 sub esp, 0Ch
.text:004010B9 mov [esp+38h+nStdHandle], 0 ; uExitCode
.text:004010C0
.text:004010C0 loc_4010C0: ; CODE XREF: start+50 j
.text:004010C0 call ExitProcess
.text:004010C0 start endp
Комментариев нет:
Отправить комментарий