Бывает нужно, чтобы код выполнял довольно много сложных действий, и был
позиционно-независимым (PIC). Такой код, как правило, идёт в виде бинарника,
на вход которого передаётся управление. Применяется такой код в качестве
полезной нагрузки (к чему-либо =) ).
Обычно такой код пишут на ассемблере, и компилируют как raw binary.
Но если нужно, чтобы выполнялось достаточно много сложных действий, то такой
подход становится очень трудоёмок.
Однако можно написать нужный функционал на C/C++.
Сейчас разберём, как это можно сделать в Visual studio.
По умолчанию, студия не умеет генерировать код в виде сырого бинарника,
поэтому придётся пойти на ряд ухищрений. Будем получать наш кусок кода путём
компилирования программы в одну секцию и дампу её сторонними средствами.
Используемые опции сборки:
--[ Компилятор:
/Od /Os /Oy /Zp1 /J /Gy /GR- /GS- /Gs- /GL /EHa-
, где
/Od - отключить оптимизацию при компиляции полностью
/Os - сделать оптимизацию по размеру (размещает функции друг за другом
вплотную)
/Oy - пропуск указателя стека (чтобы использовалась адресация
относительно sp, не bp) (если не поддерживается компилятором, то
можно убрать)
/Zp1 - упаковывать структуры с выравниваем в 1 байт
/J - char по умолчанию - беззнаковый
/GR- - запрещаем C++ RTTI
/GS- - запрещаем security checks
/Gs- - запрещаем stack checking calls
/GL - включаем кодогенерацию во время линковки (обычно позволяет генерировать более компактный код, но при желании можно отключить)
/EHa- - запрещаем C++ исключения
--[ Линкер
/INCREMENTAL:NO /LTCG /NODEFAULTLIB /MANIFEST:NO /DYNAMICBASE:NO /FIXED /NXCOMPAT:NO /FILEALIGN:1 /OPT:NOREF
, где
/INCREMENTAL:NO - запрещаем инкрементальную линковку. Избавляет нас от
выравнивания для функций
/NODEFAULTLIB - не линкуем стандартные либы. Теперь мы сами по себе.
/DYNAMICBASE:NO - запрещаем ASLR
/FIXED - фиксированный базовый адрес загрузки
/NXCOMPAT:NO - говорим, что приложение несовместимо с DEP
/FILEALIGN:1 - устанавливаем файловое выравнивание в 1 байт. Ключ не
документирован, при выравнивании в 1 байт приложение
уже не будет нормально запускаться windows ).
/OPT:NOREF - запретить линкеру удалять неиспользуемый код
--[ Код
Также в теле программы нужно добавить следующие директивы:
#include "app.h"
#pragma section(".code", execute, read, write)
#pragma comment(linker, "/MERGE:.text=.code")
#pragma comment(linker, "/MERGE:.data=.code")
#pragma comment(linker, "/MERGE:.rdata=.code")
//#pragma comment(linker, "/MERGE:.reloc=.code")
#pragma comment(linker, "/SECTION:.code,ERW")
#pragma comment(linker,"/SUBSYSTEM:CONSOLE")
#pragma comment(linker,"/ENTRY:main")
#pragma check_stack(off)
#pragma code_seg(".code")
Чтобы код собирался и для x64 разрядности, нужно добавить заглушку для функции
chkstk. Она линкуется из CRT библиотеки и проверяет целостность стека, но нам
она совершенно не нужна.
Заглушка:
// x64 stub
#if defined(_M_X64)
extern "C" void __chkstk() {}
#endif // #if defined(_M_X64)
Важно:
При такой сборке имеет значение порядок функций в файле, где расположено тело
функции-точки входа. Т.е, первая идущая будет первой в бинаре. Поэтому наша
main функция должна быть первой. (Можно задавать также порядок принудительно
линкеру с помощью опции /Fo, но это как-то не кошерно).
Итак, наш код имеет вид:
#pragma section(".code", execute, read, write)
#pragma comment(linker, "/MERGE:.text=.code")
#pragma comment(linker, "/MERGE:.data=.code")
#pragma comment(linker, "/MERGE:.rdata=.code")
//#pragma comment(linker, "/MERGE:.reloc=.code")
#pragma comment(linker, "/SECTION:.code,ERW")
#pragma comment(linker,"/SUBSYSTEM:CONSOLE")
#pragma comment(linker,"/ENTRY:main")
#pragma check_stack(off)
#pragma code_seg(".code")
// func prototypes
int func();
int func2();
int main()
{
int val = 0x7;
int val2 = 1 + val;
val2 += func();
return 0;
}
int func()
{
return 12;
};
int func2()
{
return 0x69;
};
// x64 stub
#if defined(_M_X64)
extern "C" void __chkstk() {}
#endif // #if defined(_M_X64)
После сборки получаем exe'шник, вырезаем подручными средствами секцию .code в
файл - точка входа расоложена как раз в начале.
Пример собранного куска кода для данной программы (x86):
0x00000000 55 push ebp
0x00000001 8bec mov ebp, esp
0x00000003 51 push ecx
0x00000004 51 push ecx
0x00000005 c745f8070000. mov dword [ebp - 8], 7
0x0000000c 8b45f8 mov eax, dword [ebp - 8]
0x0000000f 40 inc eax
0x00000010 8945fc mov dword [ebp - 4], eax
0x00000013 e80c000000 call 0x24
0x00000018 0345fc add eax, dword [ebp - 4]
0x0000001b 8945fc mov dword [ebp - 4], eax
0x0000001e 33c0 xor eax, eax
0x00000020 8be5 mov esp, ebp
0x00000022 5d pop ebp
0x00000023 c3 ret
0x00000024 55 push ebp
0x00000025 8bec mov ebp, esp
0x00000027 6a0c push 0xc ; 12
0x00000029 58 pop eax
0x0000002a 5d pop ebp
0x0000002b c3 ret
0x0000002c 55 push ebp
0x0000002d 8bec mov ebp, esp
0x0000002f 6a69 push 0x69 ; 'i' ; 105
0x00000031 58 pop eax
0x00000032 5d pop ebp
0x00000033 c3 ret
Полные исходники:
ссылка