kam1 - записи на клочке
Написание PIC кода на C/C++ 
11th-Oct-2015 09:19 pm
Бывает нужно, чтобы код выполнял довольно много сложных действий, и был позиционно-независимым (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


Полные исходники:
ссылка
This page was loaded Jul 2nd 2024, 2:54 am GMT.