Создание защиты для скомпилированного приложения
У меня нет исходника приложения (написан на Delphi ), но мне нужно сделать, чтобы приложение запускалось только от каких-то условий, заданных мной (к примеру, наличия файла рядом). Целевое приложение я упаковал защитой от взлома.
Вот что мне сразу пришло в голову: сделать приложение, которое проверяет нужные условия и запускает целевое приложение, находящееся в конце файла (примерно как самораспаковывающийся архив). Но в этом сразу минус: в памяти целевое приложение будет "голым", и его можно будет задампить...
В интернете много разных загрузчиков есть, но мне очень не хочется использовать большую библиотеку для такого дела, т.е. если я делаю как сейчас то мой конечный файл весит 800 кб, а так будет 2-5 мб что не то вообще ..
Прошу предложить другой вариант.
UPD
попробовал засунуть целевое приложение в ресурсы
/////////////////////////////////////////////////////////////////////////////
//
// BIN
//
IDR_SAMPLE_FILE BIN "sample.exe"
и потом такая функция:
int RunPortableExecutable(HANDLE Image) {
IMAGE_DOS_HEADER* DOSHeader;
IMAGE_NT_HEADERS* NtHeader;
IMAGE_SECTION_HEADER* SectionHeader;
PROCESS_INFORMATION PI;
STARTUPINFO SI;
DWORD* ImageBase;
CONTEXT* CTX;
void* pImageBase;
int count;
char CurrentFilePath[1024];
DOSHeader = PIMAGE_DOS_HEADER(Image);
NtHeader = PIMAGE_NT_HEADERS(DWORD(Image) + DOSHeader->e_lfanew);
GetModuleFileName(0, CurrentFilePath, 1024);
if (NtHeader->Signature == IMAGE_NT_SIGNATURE) {
ZeroMemory(&PI, sizeof(PI));
ZeroMemory(&SI, sizeof(SI));
if (CreateProcessA(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, LPCVOID(CTX->Ebx + 8),LPVOID(&ImageBase), 4, 0);
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase),
NtHeader->OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);
if (pImageBase == 0) {
TerminateProcess(PI.hProcess, 0);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
startup(CurrentFilePath);
return 1;
}
WriteProcessMemory(PI.hProcess, pImageBase, Image, NtHeader->OptionalHeader.SizeOfHeaders, NULL);
for (count = 0; count < NtHeader->FileHeader.NumberOfSections; count++) {
SectionHeader = PIMAGE_SECTION_HEADER(DWORD(Image) + DOSHeader->e_lfanew + 248 + (count * 40));
WriteProcessMemory(PI.hProcess, LPVOID(DWORD(pImageBase) + SectionHeader->VirtualAddress),
LPVOID(DWORD(Image) + SectionHeader->PointerToRawData), SectionHeader->SizeOfRawData, 0);
}
WriteProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&NtHeader->OptionalHeader.ImageBase), 4, 0);
CTX->Eax = DWORD(pImageBase) + NtHeader->OptionalHeader.AddressOfEntryPoint;
SetThreadContext(PI.hThread, LPCONTEXT(CTX));
ResumeThread(PI.hThread);
return 0;
}
}
}
}
она работает, вот только не всегда:
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase),
NtHeader->OptionalHeader.SizeOfImage, 0x3000, PAGE_EXECUTE_READWRITE);
в этой части кода - pImageBase иногда получает значение 0, т.е. не удалось занять место в памяти, не понимаю от чего это происходит, Как при "удачном" так и при "неудачном" срабатывании в переменных значения одни и те же..
if (pImageBase == 0) {
TerminateProcess(PI.hProcess, 0);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
startup(CurrentFilePath);
return 1;
}
а тут я проверяю удалось ли сделать операцию и если нет то закрываю приложение и запускаю по новой и это " работает" т.е. иногда 3-4 раза перезапускается .. но понятно что такой вариант плохой, т.е вдруг у кого то будет не запускаться не 3-4 раза а 50... 100...
если кому-то нужно знать, что за функция startup то вот она:
VOID startup(LPCTSTR lpApplicationName)
{
// additional information
STARTUPINFO si;
PROCESS_INFORMATION pi;
// set the size of the structures
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// start the program up
CreateProcess(lpApplicationName, // the path
LPSTR(""), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
);
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
UPD
решил проблему с памятью добавив вот это :
NtUnmapViewOfSection(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase));
перед этим
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase),NtHeader->OptionalHeader.SizeOfImage, MEM_COMMIT |
MEM_RESERVE, PAGE_EXECUTE_READWRITE);
теперь вопрос, насколько это правильно? или не правильно вовсе?
Ответы (2 шт):
Защищать скомпилированное и лежащее в бинарнике ПО можно разными способами, но вот защитить его так, чтобы точно никто не подкопался, практически невозможно. Посмотрите, например, эту статью: https://habr.com/ru/company/skillbox/blog/440836/ Там написано, что защита без онлайн-проверки очень легко обходится. С онлайн-проверкой сложнее, но тоже обходится. Еще труднее, если у вас хардверный ключ - флешка с цифровой подписью. А в комментариях люди справедливо пишут, что даже от запроса аппаратного ключа можно отбиться. Есть коммерческие предприятия, которые предлагают защиту, например https://www.guardant.ru/ Или еще круче https://www.ilok.com/ Но для этого нужны хорошие деньги.
Так что обычно этим занимаются компании-издатели, а не инди-разработчики.
И еще цитата из статьи на хабре:
О защите ПО стоит подумать еще на стадии проектирования: после того как проект готов частично или полностью, изменить что-то будет непросто.
Как вариант кустарной защиты вы можете взять уникальный ID установленной у клиента копии Windows, например, из реестра
HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\MachineGuid
и при первом запуске на машине приписать хэш от этого значения к одному из своих бинарников. Если такое значение уже есть, второй раз оно уже перезаписываться не должно, а должно сверяться с записью в указанной ветке реестра. Если расходится - программу занесли на другую копию Windows, на другую машину, и она может молча не запускаться, чтобы человек не понял, что срабатывает копи-протекшн.
Fingerprint лучше собрать из нескольких аналогичных веток реестра. Естественно, они не должны меняться от установки, удаления или использования юзером какого-либо софта.
В итоге вот такой код, у меня вышел, для запуска приложения из ресурсов загружая его в память (теперь работает без сбоев) :
#include <iostream>
#include <string>
#include <Windows.h>
#include <TlHelp32.h>
#include <winternl.h>
#pragma comment(lib,"ntdll.lib")
EXTERN_C NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE, PVOID);
VOID startup(LPCTSTR lpApplicationName)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess(lpApplicationName, LPSTR(""), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
int RunPortableExecutable(HANDLE Image) {
IMAGE_DOS_HEADER* DOSHeader;
IMAGE_NT_HEADERS* NtHeader;
IMAGE_SECTION_HEADER* SectionHeader;
PROCESS_INFORMATION PI;
STARTUPINFO SI;
DWORD* ImageBase;
CONTEXT* CTX;
void* pImageBase;
int count;
char CurrentFilePath[1024];
DOSHeader = PIMAGE_DOS_HEADER(Image);
NtHeader = PIMAGE_NT_HEADERS(DWORD(Image) + DOSHeader->e_lfanew);
GetModuleFileName(0, CurrentFilePath, 1024);
if (NtHeader->Signature == IMAGE_NT_SIGNATURE) {
ZeroMemory(&PI, sizeof(PI));
ZeroMemory(&SI, sizeof(SI));
if (CreateProcessA(CurrentFilePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &SI, &PI)) {
CTX = LPCONTEXT(VirtualAlloc(NULL, sizeof(CTX), MEM_COMMIT, PAGE_READWRITE));
if (CTX != 0) {
CTX->ContextFlags = CONTEXT_FULL;
if (GetThreadContext(PI.hThread, LPCONTEXT(CTX))) {
ReadProcessMemory(PI.hProcess, LPCVOID(CTX->Ebx + 8), LPVOID(&ImageBase), 4, 0);
NtUnmapViewOfSection(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase));
pImageBase = VirtualAllocEx(PI.hProcess, LPVOID(NtHeader->OptionalHeader.ImageBase),
NtHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pImageBase == 0 ) {
TerminateProcess(PI.hProcess, 0);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
startup(CurrentFilePath);
return 1;
}
WriteProcessMemory(PI.hProcess, pImageBase, Image, NtHeader->OptionalHeader.SizeOfHeaders, NULL);
for (count = 0; count < NtHeader->FileHeader.NumberOfSections; count++) {
SectionHeader = PIMAGE_SECTION_HEADER(DWORD(Image) + DOSHeader->e_lfanew + 248 + (count * 40));
WriteProcessMemory(PI.hProcess, LPVOID(DWORD(pImageBase) + SectionHeader->VirtualAddress),
LPVOID(DWORD(Image) + SectionHeader->PointerToRawData), SectionHeader->SizeOfRawData, 0);
}
WriteProcessMemory(PI.hProcess, LPVOID(CTX->Ebx + 8), LPVOID(&NtHeader->OptionalHeader.ImageBase), 4, 0);
CTX->Eax = DWORD(pImageBase) + NtHeader->OptionalHeader.AddressOfEntryPoint;
SetThreadContext(PI.hThread, LPCONTEXT(CTX));
ResumeThread(PI.hThread);
CloseHandle(PI.hProcess);
CloseHandle(PI.hThread);
return 0;
}
}
}
}
}
// Fork Process From Resource
// Dynamically create a process from a resource file.
bool ForkProcessFromResource(int iResource)
{
HGLOBAL hResData;
HRSRC hResInfo;
LPVOID lpRes;
LPVOID lpMemory;
long int lSize;
HMODULE hModule;
bool bReturn;
hModule = GetModuleHandle(0);
bReturn = false;
if (!hModule)
return bReturn;
hResInfo = FindResource(hModule, MAKEINTRESOURCE(iResource), "BIN");
if (!hResInfo)
{
return bReturn;
}
hResData = LoadResource(hModule, hResInfo);
if (!hResData)
{
return bReturn;
}
lpRes = LockResource(hResData);
if (!lpRes)
{
FreeResource(hResData);
return bReturn;
}
lSize = SizeofResource(hModule, hResInfo);
lpMemory = new LPVOID[lSize];
ZeroMemory(lpMemory, lSize);
memcpy(lpMemory, lpRes, lSize);
bReturn = RunPortableExecutable(lpMemory) == 0;
FreeResource(hResData);
delete[] lpMemory;
return bReturn;
}
UPD
Всё таки не совсем решило проблему, т.е. на Windows 10 x64 запускается всегда, а вот на Windows 7 x64 почему-то нет - запустилось с 4го раза, что, теперь, может быть не так?