Search
Write a publication
Pull to refresh

Базонезависимый код и C++

Появилась значит такая задача — получить код, который можно загружать по любому адрусу в памяти при минимальных ограничениях (юзание классов, аналоги операторов new и delete, доступ к WinAPI, COM, и т.п...)
Соответственно основные требования к требуемой программе — загружать WinAPI функции динамически через LoadLibrary (а саму LoadLibrary получить другим методом), никаких глобальных и статических переменных, строк и т.п.

Первая задача — получить адрес адрес Kernel32.dll, затем получить адреса самых важных функций:
// Стандартный метод получить хэндл Kernel32.dll
inline HMODULE GetKernel32(void)
{
__asm
{
mov eax,dword ptr fs:[30h]
mov eax,dword ptr [eax+0ch]
mov esi,dword ptr [eax+1ch]
lodsd
mov eax,dword ptr [eax+08h]
}
}
//-------------------------------------------------
// По хэшу функции и хэндлу модуля получаем адрес функции
//-------------------------------------------------
LPVOID GetProcAddressEx(HMODULE hModule, DWORD dwProcNameHash)
{
PIMAGE_OPTIONAL_HEADER poh = (PIMAGE_OPTIONAL_HEADER)
((char*)hModule + ((PIMAGE_DOS_HEADER)hModule)->e_lfanew +
sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER));
PIMAGE_EXPORT_DIRECTORY ped = (IMAGE_EXPORT_DIRECTORY*)RVATOVA(hModule,
poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD *pdwNamePtr = (DWORD*)RVATOVA(hModule, ped->AddressOfNames);
WORD *pwOrdinalPtr = (WORD*)RVATOVA(hModule, ped->AddressOfNameOrdinals);
DWORD *pAddrTable = (DWORD*)RVATOVA(hModule, ped->AddressOfFunctions);
int nOrdinal = -1;
unsigned int i;
for (i = 0; i < ped->NumberOfNames; i++, pdwNamePtr++, pwOrdinalPtr++, pAddrTable++)
{
char *s = (char*)RVATOVA(hModule, *pdwNamePtr);
if (CalcHash(s) == dwProcNameHash)
{
nOrdinal = *pwOrdinalPtr;
break;
}
}
pAddrTable = (DWORD*)RVATOVA(hModule, ped->AddressOfFunctions);
if (i == ped->NumberOfNames || nOrdinal<0) return 0;
DWORD dwRVA = pAddrTable[nOrdinal];
DWORD ret = (DWORD)RVATOVA(hModule, dwRVA);
return (LPVOID)ret;
}

//--------------------------------------------------
class BaseMain
{
public:
HMODULE hKernel;
HMODULE (WINAPI *pfLoadLibraryA)(LPCSTR lpLibFileName);
FARPROC (WINAPI *pfGetProcAddress)(HMODULE hModule,LPCSTR lpProcName);
VOID (WINAPI *pfExitProcess)(UINT uExitCode);
// Функции для работы с памятью
HANDLE hHeap;
HANDLE (WINAPI *pfHeapCreate)(DWORD flOptions,DWORD dwInitialSize,DWORD dwMaximumSize);
//...............................
System::Std::RFolder *Root;
public:
void Init();
void* Malloc(size_t size);
void Free(void *mem);
void Memset(void *mem,UCHAR val,size_t size);

};
void BaseMain::Init()
{
hKernel = GetKernel32();
void **p = (void**)&pfLoadLibraryA;
*p = GetProcAddressEx(hKernel,CalcHash("LoadLibraryA"));
p = (void**)&pfGetProcAddress;
*p = GetProcAddressEx(hKernel,CalcHash("GetProcAddress"));
// ..... Тут получаем адреса других функций....
}

#define PMAIN ((System::Main::BaseMain*)this->pMain)



Адреса загружаемых из DLL функций мы поместили в класс BaseMain. Сам этот класс можно поместить на стеке в главной функции (EntryPoint) программы:

void MyMain()
{
System::Main::BaseMain main;
main.Init();
........
main.pfExitProcess(0); // вместо ExitProcess(0); , т.к. программа скомпилирована без RTL
}



Теперь необходимо разобраться с памятью — т.к. глобальные переменные юзать нельзя, то у каждого класса, которому необходим доступ к WinAPI или другим функциям добавим поле void *pMain, которое будет указателем на созданный объект BaseMain:

class RClass
{
public:
void *pMain;
public:
//..........................
RClass* Init0(void *p){pMain=p;/*...*/}
};

void MyMain()
{
System::Main::BaseMain main;
main.Init();
RClass rclass;
rclass.Init0(&main);
//..................



Допустим даже все используемые классы будут унаследованы от RClass. Тогда использование функций BaseMain будет следующим:

// PMAIN у нас определено как #define PMAIN ((System::Main::BaseMain*)this->pMain)

void RClass::Func()
{
void *p = PMAIN->Malloc(100); // Вместо malloc
PMAIN->pfHeapAlloc(...); // Или любая функция, загруженная в BaseMain
}



Следующая важная задача — реализация аналога оператора new (чтобы у классов вызывался конструктор). Если просто написать MyClass cls = (MyClass*)PMAIN->Malloc(sizeof(MyClass)), то ничего хорошего из этого не выйдет, т.к. даже если нам не нужно вызывать конструктор класса, таблица виртуальных методов не будет заполнена и возможны вылеты там где не ожидаем. Поэтому реализуем функцию MyNew():

// Переопределяем оператор new(p) Class;
void * operator new(size_t size,void *p)
{
return p;
}
template T* MyNew(void *pMain)
{
void *p = ((System::Main::BaseMain*)pMain)->Malloc(sizeof(T));
T* t = new(p) T; // тут не будет выделена память для t (используется p), но будет вызван конструктор
t->Init0(pMain); // также инициализируем pMain в классе
return t;
}



Таким образом мы получили основной функционал для написания базонезависимого кода. Для удаления константных строк можно использовать скрипты обработки cpp файлов (которые заменяют строки вида "String" на код strings->GetString(143) // 143 - StringID ).

Полученный exe файл обладает минимальным размером (16 кб, учитывая что выравнивание секций по 4 кб), не импортирует никаких DLL (таблица импорта абсолютно пустая), секция данных также не содержит никакой информации.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.