Появилась значит такая задача — получить код, который можно загружать по любому адрусу в памяти при минимальных ограничениях (юзание классов, аналоги операторов new и delete, доступ к WinAPI, COM, и т.п...)
Соответственно основные требования к требуемой программе — загружать WinAPI функции динамически через LoadLibrary (а саму LoadLibrary получить другим методом), никаких глобальных и статических переменных, строк и т.п.
Первая задача — получить адрес адрес Kernel32.dll, затем получить адреса самых важных функций:
Адреса загружаемых из DLL функций мы поместили в класс BaseMain. Сам этот класс можно поместить на стеке в главной функции (EntryPoint) программы:
Теперь необходимо разобраться с памятью — т.к. глобальные переменные юзать нельзя, то у каждого класса, которому необходим доступ к WinAPI или другим функциям добавим поле void *pMain, которое будет указателем на созданный объект BaseMain:
Допустим даже все используемые классы будут унаследованы от RClass. Тогда использование функций BaseMain будет следующим:
Следующая важная задача — реализация аналога оператора new (чтобы у классов вызывался конструктор). Если просто написать MyClass cls = (MyClass*)PMAIN->Malloc(sizeof(MyClass)), то ничего хорошего из этого не выйдет, т.к. даже если нам не нужно вызывать конструктор класса, таблица виртуальных методов не будет заполнена и возможны вылеты там где не ожидаем. Поэтому реализуем функцию MyNew():
Соответственно основные требования к требуемой программе — загружать 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 (таблица импорта абсолютно пустая), секция данных также не содержит никакой информации.