Всем салют!
С прошлого года у меня была идея сделать свой протектор. Данную идею я носил в себе уже довольно долго, но только на новогодних праздниках я приступил к реализации. Начал с полиморфного кода (если эта статья зайдет, то расскажу про полиморфный код). Потом перешел к другим вещам как загрузка PE из памяти и тд. И вот недавно дело дошло до билдера. Так как я исходники ищу уже в самой критической ситуации, то сначала я решил сделать подобную вещь своими силами. Формально передо мной стояла цель сделать псевдо-пакер.
Логика была совсем банальная. У нас есть билдер и лоадер.
Отлично, но как добавить код в PE файл лоадера? Для этого я решил взять и создать дополнительную секцию в лоадере. В эту секцию записать байты PE файла, а при запуске лоадер будет читать последнюю секцию файла, в которой как раз и будет наш зашифрованный PE.
- Лоадер - маленькая програмка, которая после сборки будет хранить в себе зашифрованный код нашего PE файла. От нее требуется только расшифровать файл и запустить его.
- Билдер - программа, в которой будет храниться лоадер. Билдер будет модифицировать лоадер, а именно добавлять в него зашифрованный код PE файла так, чтобы лоадер понял, что ему расшифровывать и исполнять.
Все пока звучит довольно реализуемо. Но как теперь запустить наш файл, Тут я решил поступить довольно убогим методом. Выполнить наши байты в памяти. Почему? Потому что это была первая идея. Плюсом данного метода является то, что код будет очень сложно получить через дамп, ибо придется искать то место, куда же происходила запись PE. Ну а минусов в этом методе вагон и маленькая тележка, начиная с запуска запротекченного файла через раз, и заканчивая тем, что не все PE Win32 Файлы поддерживаются.
Однако это все пример и не более. Для личных нужд данный софт более чем уместен.
Ладно, приступим уже к написанию кода. Я решил начать с лоадера. После того как вы включите максимальную оптимизацию по объему, отключите манифест и отладочную информацию, добавите код с вашим алгоритмом шифрования (я использовал blowfish), можно начать писать код.
Для начала напишем функцию для получения размера нашего файла (да, читать секцию из памяти мне было лень, однако так тоже можно)
int GetFileSize(const char* path)
{
FILE* file = fopen(path, "rb");
if (!file)
return 0;
fseek(file, 0, SEEK_END);
int fSize = ftell(file);
fclose(file);
return fSize;
}
Код
int GetFileSize(const char* path)
{
FILE* file = fopen(path, "rb");
if (!file)
return 0;
fseek(file, 0, SEEK_END);
int fSize = ftell(file);
fclose(file);
return fSize;
}
Дальше напишем функцию для получения последней секции в нашем PE файле
byte* ReadLastSection(DWORD& codeLen)
{
char CurrentFilePath[1024] = "";
GetModuleFileName(NULL, CurrentFilePath, 1024);
FILE* file = fopen(CurrentFilePath, "rb");
if (!file)
exit(~0);
DWORD fileSize = GetFileSize(CurrentFilePath);
BYTE* pByte = new BYTE[fileSize];
fread(pByte, sizeof(BYTE), fileSize, file);
PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(pByte);
PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(pByte + dos->e_lfanew);
PIMAGE_SECTION_HEADER first = IMAGE_FIRST_SECTION(nt);
PIMAGE_SECTION_HEADER last = first + (nt->FileHeader.NumberOfSections - 1);
fseek(file, last->PointerToRawData, 0);
fread(&codeLen, sizeof(DWORD), 1, file);
byte* raw = (byte*)malloc(codeLen * sizeof(byte));
if(!raw)
return nullptr;
fread(raw, sizeof(byte), codeLen, file);
delete[] pByte;
return raw;
}
Код
byte* ReadLastSection(DWORD& codeLen)
{
char CurrentFilePath[1024] = "";
GetModuleFileName(NULL, CurrentFilePath, 1024);
FILE* file = fopen(CurrentFilePath, "rb");
if (!file)
exit(~0);
DWORD fileSize = GetFileSize(CurrentFilePath);
BYTE* pByte = new BYTE[fileSize];
fread(pByte, sizeof(BYTE), fileSize, file);
PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(pByte);
PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(pByte + dos->e_lfanew);
PIMAGE_SECTION_HEADER first = IMAGE_FIRST_SECTION(nt);
PIMAGE_SECTION_HEADER last = first + (nt->FileHeader.NumberOfSections - 1);
fseek(file, last->PointerToRawData, 0);
fread(&codeLen, sizeof(DWORD), 1, file);
byte* raw = (byte*)malloc(codeLen * sizeof(byte));
if(!raw)
return nullptr;
fread(raw, sizeof(byte), codeLen, file);
delete[] pByte;
return raw;
}
Прикол заключается в том, что если подобное делать через функции WinAPI (CreateFile), то винда шлет тебя куда подальше и не дает читать файл.
Ну и на последнее - функция запуска файла из памяти. Данный метод также стар, как мой дед, так что те, кто занимается реверсом увидят здесь знакомые строки.
int RunPE(void* Image)
{
IMAGE_DOS_HEADER* pDosHeader;
IMAGE_NT_HEADERS* pNtHeader;
IMAGE_SECTION_HEADER* pSectionHeader;
PROCESS_INFORMATION PI;
STARTUPINFOA SI;
CONTEXT* CTX;
DWORD ImageBase;
void* pImageBase;
char CurrentFilePath[1024] = "";
pDosHeader = PIMAGE_DOS_HEADER(Image);
pNtHeader = PIMAGE_NT_HEADERS(DWORD(Image) + pDosHeader->e_lfanew);
GetModuleFileName(NULL, CurrentFilePath, 1024);
if(pNtHeader->Signature == IMAGE_NT_SIGNATURE)
{
ZeroMemory(&PI, sizeof(PI));
ZeroMemory(&SI, sizeof(SI));
if(CreateProcess(CurrentFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI))
{
CTX = LPCONTEXT(VirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE));
CTX->ContextFlags = CONTEXT_FULL;
if(GetThreadContext(PI.hThread, LPCONTEXT(CTX)))
{
ReadProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&ImageBase), 4, NULL);
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(pNtHeader->OptionalHeader.ImageBase), pNtHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(PI.hProcess, pImageBase, Image, pNtHeader->OptionalHeader.SizeOfHeaders, NULL);
for(int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++)
{
pSectionHeader = PIMAGE_SECTION_HEADER(DWORD(Image) + pDosHeader->e_lfanew + 248 + (i * 40));
WriteProcessMemory(PI.hProcess, LPVOID(DWORD(pImageBase) + pSectionHeader->VirtualAddress),
LPVOID(DWORD(Image) + pSectionHeader->PointerToRawData), pSectionHeader->SizeOfRawData, NULL);
}
WriteProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&pNtHeader->OptionalHeader.ImageBase), 4, NULL);
CTX->Eax = DWORD(pImageBase) + pNtHeader->OptionalHeader.AddressOfEntryPoint;
SetThreadContext(PI.hThread, LPCONTEXT(CTX));
ResumeThread(PI.hThread);
return 0;
}
}
}
return -1;
}
Код
int RunPE(void* Image)
{
IMAGE_DOS_HEADER* pDosHeader;
IMAGE_NT_HEADERS* pNtHeader;
IMAGE_SECTION_HEADER* pSectionHeader;
PROCESS_INFORMATION PI;
STARTUPINFOA SI;
CONTEXT* CTX;
DWORD ImageBase;
void* pImageBase;
char CurrentFilePath[1024] = "";
pDosHeader = PIMAGE_DOS_HEADER(Image);
pNtHeader = PIMAGE_NT_HEADERS(DWORD(Image) + pDosHeader->e_lfanew);
GetModuleFileName(NULL, CurrentFilePath, 1024);
if(pNtHeader->Signature == IMAGE_NT_SIGNATURE)
{
ZeroMemory(&PI, sizeof(PI));
ZeroMemory(&SI, sizeof(SI));
if(CreateProcess(CurrentFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI))
{
CTX = LPCONTEXT(VirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE));
CTX->ContextFlags = CONTEXT_FULL;
if(GetThreadContext(PI.hThread, LPCONTEXT(CTX)))
{
ReadProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&ImageBase), 4, NULL);
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(pNtHeader->OptionalHeader.ImageBase), pNtHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(PI.hProcess, pImageBase, Image, pNtHeader->OptionalHeader.SizeOfHeaders, NULL);
for(int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++)
{
pSectionHeader = PIMAGE_SECTION_HEADER(DWORD(Image) + pDosHeader->e_lfanew + 248 + (i * 40));
WriteProcessMemory(PI.hProcess, LPVOID(DWORD(pImageBase) + pSectionHeader->VirtualAddress),
LPVOID(DWORD(Image) + pSectionHeader->PointerToRawData), pSectionHeader->SizeOfRawData, NULL);
}
WriteProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&pNtHeader->OptionalHeader.ImageBase), 4, NULL);
CTX->Eax = DWORD(pImageBase) + pNtHeader->OptionalHeader.AddressOfEntryPoint;
SetThreadContext(PI.hThread, LPCONTEXT(CTX));
ResumeThread(PI.hThread);
return 0;
}
}
}
return -1;
}
Остается сделать точку входа, прикрутить шифрование и лоадер готов.
int main()
{
DWORD codeLength = 0;
int neweCodeLength = 0;
byte* raw = ReadLastSection(codeLength);
if (!raw)
exit(~0);
BLOWFISH cryptor(key, 32);
cryptor.SetIV(iv);
byte* dectypted = cryptor.Decrypt_CBC(raw, codeLength, &neweCodeLength);
delete[] raw;
RunPE(dectypted);
}
Код
int main()
{
DWORD codeLength = 0;
int neweCodeLength = 0;
byte* raw = ReadLastSection(codeLength);
if (!raw)
exit(~0);
BLOWFISH cryptor(key, 32);
cryptor.SetIV(iv);
byte* dectypted = cryptor.Decrypt_CBC(raw, codeLength, &neweCodeLength);
delete[] raw;
RunPE(dectypted);
}
На этом с лоадером покончено. Можете смело билдить его в Release конфигурации и копировать его hex код (для этого можно юзать HexNeo | HexEdit)
А дальше начинаются пляски с бубном. Нам нужно написать билдер. Хотя если знать, что делать, то создание билдера не выглядит таким страшным.
Для начала добавим функции для чтения и записи файла, а также выгрузки лоадера (повторяю, я не любитель WinAPI)
inline byte* ReadFile(const char* fileName, int&fSize)
{
fSize = GetFileSize(fileName);
byte* rawData = new byte[fSize];
FILE* file = fopen(fileName, "rb");
fread(rawData, sizeof(byte), fSize, file);
fclose(file);
return rawData;
}
inline void UnloadLoader(string& fileName)
{
fileName = "Loader-" + fileName;
FILE* file = fopen(fileName.c_str(), "wb+");
if(!file)
return;
fwrite(LOADER, sizeof(unsigned char), ARRAY_SIZE(LOADER), file);
fclose(file);
}
Код
inline byte* ReadFile(const char* fileName, int&fSize)
{
fSize = GetFileSize(fileName);
byte* rawData = new byte[fSize];
FILE* file = fopen(fileName, "rb");
fread(rawData, sizeof(byte), fSize, file);
fclose(file);
return rawData;
}
inline void UnloadLoader(string& fileName)
{
fileName = "Loader-" + fileName;
FILE* file = fopen(fileName.c_str(), "wb+");
if(!file)
return;
fwrite(LOADER, sizeof(unsigned char), ARRAY_SIZE(LOADER), file);
fclose(file);
}
Дальше надо реализовать функцию добавления новой секции. К сожалению просто так ее не добавишь, ибо надо учитывать выравнивание, а также данные предыдущей секции (то бишь последней). Однако это тоже не является проблемой, если знать структуру PE (тут инфы просто море, один хабр чего стоит).
inline DWORD align(DWORD size, DWORD align, DWORD addr)
{
if (!(size % align))
return addr + size;
return addr + (size / align + 1) * align;
}
inline bool AddSection(const char* filePath, const char* sectionName, DWORD sizeOfSection)
{
HANDLE file = CreateFile(filePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(file == INVALID_HANDLE_VALUE)
return false;
DWORD fileSize = GetFileSize(file, NULL);
BYTE* pByte = new BYTE[fileSize];
DWORD dw;
ReadFile(file, pByte, fileSize, &dw, NULL);
PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(pByte);
if(dos->e_magic != IMAGE_DOS_SIGNATURE)
return false;
PIMAGE_FILE_HEADER FH = PIMAGE_FILE_HEADER(pByte + dos->e_lfanew + sizeof(DWORD));
PIMAGE_OPTIONAL_HEADER OH = PIMAGE_OPTIONAL_HEADER(pByte + dos->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER SH = PIMAGE_SECTION_HEADER(pByte + dos->e_lfanew + sizeof(IMAGE_NT_HEADERS));
ZeroMemory(&SH[FH->NumberOfSections], sizeof(IMAGE_SECTION_HEADER));
CopyMemory(&SH[FH->NumberOfSections].Name, sectionName, 8);
SH[FH->NumberOfSections].Misc.VirtualSize = align(sizeOfSection, OH->FileAlignment, 0);
SH[FH->NumberOfSections].VirtualAddress = align(SH[FH->NumberOfSections - 1].Misc.VirtualSize, OH->SectionAlignment, SH[FH->NumberOfSections - 1].VirtualAddress);
SH[FH->NumberOfSections].SizeOfRawData = align(sizeOfSection, OH->FileAlignment, 0);
SH[FH->NumberOfSections].PointerToRawData = align(SH[FH->NumberOfSections - 1].SizeOfRawData, OH->FileAlignment, SH[FH->NumberOfSections - 1].PointerToRawData);
SH[FH->NumberOfSections].Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
SetFilePointer(file, SH[FH->NumberOfSections].PointerToRawData + SH[FH->NumberOfSections].SizeOfRawData, NULL, FILE_BEGIN);
SetEndOfFile(file);
OH->SizeOfImage = SH[FH->NumberOfSections].VirtualAddress + SH[FH->NumberOfSections].Misc.VirtualSize;
FH->NumberOfSections += 1;
SetFilePointer(file, 0, NULL, FILE_BEGIN);
WriteFile(file, pByte, fileSize, &dw, NULL);
CloseHandle(file);
delete[] pByte;
return true;
}
Код
inline DWORD align(DWORD size, DWORD align, DWORD addr)
{
if (!(size % align))
return addr + size;
return addr + (size / align + 1) * align;
}
inline bool AddSection(const char* filePath, const char* sectionName, DWORD sizeOfSection)
{
HANDLE file = CreateFile(filePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(file == INVALID_HANDLE_VALUE)
return false;
DWORD fileSize = GetFileSize(file, NULL);
BYTE* pByte = new BYTE[fileSize];
DWORD dw;
ReadFile(file, pByte, fileSize, &dw, NULL);
PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(pByte);
if(dos->e_magic != IMAGE_DOS_SIGNATURE)
return false;
PIMAGE_FILE_HEADER FH = PIMAGE_FILE_HEADER(pByte + dos->e_lfanew + sizeof(DWORD));
PIMAGE_OPTIONAL_HEADER OH = PIMAGE_OPTIONAL_HEADER(pByte + dos->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER SH = PIMAGE_SECTION_HEADER(pByte + dos->e_lfanew + sizeof(IMAGE_NT_HEADERS));
ZeroMemory(&SH[FH->NumberOfSections], sizeof(IMAGE_SECTION_HEADER));
CopyMemory(&SH[FH->NumberOfSections].Name, sectionName, 8);
SH[FH->NumberOfSections].Misc.VirtualSize = align(sizeOfSection, OH->FileAlignment, 0);
SH[FH->NumberOfSections].VirtualAddress = align(SH[FH->NumberOfSections - 1].Misc.VirtualSize, OH->SectionAlignment, SH[FH->NumberOfSections - 1].VirtualAddress);
SH[FH->NumberOfSections].SizeOfRawData = align(sizeOfSection, OH->FileAlignment, 0);
SH[FH->NumberOfSections].PointerToRawData = align(SH[FH->NumberOfSections - 1].SizeOfRawData, OH->FileAlignment, SH[FH->NumberOfSections - 1].PointerToRawData);
SH[FH->NumberOfSections].Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
SetFilePointer(file, SH[FH->NumberOfSections].PointerToRawData + SH[FH->NumberOfSections].SizeOfRawData, NULL, FILE_BEGIN);
SetEndOfFile(file);
OH->SizeOfImage = SH[FH->NumberOfSections].VirtualAddress + SH[FH->NumberOfSections].Misc.VirtualSize;
FH->NumberOfSections += 1;
SetFilePointer(file, 0, NULL, FILE_BEGIN);
WriteFile(file, pByte, fileSize, &dw, NULL);
CloseHandle(file);
delete[] pByte;
return true;
}
Дальше стоит написать метод для выгрузки данных в нашу секцию, это уже очень простая задача, так как никакое выравнивание нам не нужно, достаточно прыгнуть в начало последней секции и записать нужное кол-во байт. Кстати, лоадер как-то должен получить кол-во байт для чтения. Поэтому сначала записываем размер файла в байтах, а потом сами байты. Надеюсь это всем понятно.
inline bool AddCode(const char* filepath, byte* code, DWORD codeLen)
{
HANDLE file = CreateFile(filepath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE)
return false;
DWORD fileSize = GetFileSize(file, NULL);
BYTE* pByte = new BYTE[fileSize];
DWORD dw;
ReadFile(file, pByte, fileSize, &dw, NULL);
PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(pByte);
if (dos->e_magic != IMAGE_DOS_SIGNATURE)
return false;
PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(pByte + dos->e_lfanew);
PIMAGE_SECTION_HEADER first = IMAGE_FIRST_SECTION(nt);
PIMAGE_SECTION_HEADER last = first + (nt->FileHeader.NumberOfSections - 1);
SetFilePointer(file, last->PointerToRawData, NULL, FILE_BEGIN);
WriteFile(file, &codeLen, sizeof(DWORD), &dw, 0);
WriteFile(file, code, codeLen, &dw, 0);
CloseHandle(file);
delete[] pByte;
return true;
}
Код
inline bool AddCode(const char* filepath, byte* code, DWORD codeLen)
{
HANDLE file = CreateFile(filepath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == INVALID_HANDLE_VALUE)
return false;
DWORD fileSize = GetFileSize(file, NULL);
BYTE* pByte = new BYTE[fileSize];
DWORD dw;
ReadFile(file, pByte, fileSize, &dw, NULL);
PIMAGE_DOS_HEADER dos = PIMAGE_DOS_HEADER(pByte);
if (dos->e_magic != IMAGE_DOS_SIGNATURE)
return false;
PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(pByte + dos->e_lfanew);
PIMAGE_SECTION_HEADER first = IMAGE_FIRST_SECTION(nt);
PIMAGE_SECTION_HEADER last = first + (nt->FileHeader.NumberOfSections - 1);
SetFilePointer(file, last->PointerToRawData, NULL, FILE_BEGIN);
WriteFile(file, &codeLen, sizeof(DWORD), &dw, 0);
WriteFile(file, code, codeLen, &dw, 0);
CloseHandle(file);
delete[] pByte;
return true;
}
Ну вот и все, самое страшное позади, остается сделать точку входа, красочный дизайн и можно криптовать файлы.
int main()
{
cout << "file->";
string fileName = "";
getline(cin, fileName);
int fileSize = 0;
int newFileSize = 0;
byte* fileraw = ReadFile(fileName.c_str(), fileSize);
if (!fileraw)
return 0;
BLOWFISH cryptor(key, 32);
cryptor.SetIV(iv);
string loaderName = fileName;
UnloadLoader(loaderName);
byte* cryptData = cryptor.Encrypt_CBC(fileraw, fileSize, &newFileSize);
delete[] fileraw;
if (!AddSection(loaderName.c_str(), "PACK0", newFileSize + 0x40))
return 0;
if(!AddCode(loaderName.c_str(), cryptData, DWORD(newFileSize)))
return 0;
delete[] cryptData;
cout << "Loader generated!";
cin.get();
return 0;
}
Код
int main()
{
cout << "file->";
string fileName = "";
getline(cin, fileName);
int fileSize = 0;
int newFileSize = 0;
byte* fileraw = ReadFile(fileName.c_str(), fileSize);
if (!fileraw)
return 0;
BLOWFISH cryptor(key, 32);
cryptor.SetIV(iv);
string loaderName = fileName;
UnloadLoader(loaderName);
byte* cryptData = cryptor.Encrypt_CBC(fileraw, fileSize, &newFileSize);
delete[] fileraw;
if (!AddSection(loaderName.c_str(), "PACK0", newFileSize + 0x40))
return 0;
if(!AddCode(loaderName.c_str(), cryptData, DWORD(newFileSize)))
return 0;
delete[] cryptData;
cout << "Loader generated!";
cin.get();
return 0;
}
ГОТОВО! Наш костыльный криптор готов. Можем тестить на простых exe файлах (по моим тестам криптует большинство консольных программ).
![]()
![]()
Как видим в коде есть просто код выполнения файла в памяти, самих исходников нема.
Кстати есть еще видео где я пишу этот код в real tim`е, так что тоже можете глянуть.
На этом все. Спасибо, что прочитали, надеюсь данная статья вам пригодится при создании своих топе протекторов.
p.s
к сожалению ник ShockByte кто-то занял, поэтому пришлось использовать этот, это очень печально, однако.
#моястатья
Загрузка...