Opis funkcji, parametrów i flag Silnika Polimorficznego Poly wykorzystywanego do polimorficznego szyfrowania danych.
Silnik Polimorficzny Poly posiada jedną funkcję do szyfrowania danych i jednoczesnego generowania kodu deszyfrującego.
unsigned int __stdcall _poly(
void * lpDecryptor,
void * lpOutput,
void * lpInput,
unsigned int iSize,
unsigned int lpVA,
unsigned int cMaxInstr,
unsigned int cMinInstr,
unsigned int iGarbage,
unsigned int iForceSize,
unsigned int lpRelativeAddr,
struct POLY_REGS * lpPolyOutRegs,
unsigned int iOptions,
void * lpWorkMem,
unsigned int iRandomSeed
);
function _poly(
lpDecryptor : PByteArray,
lpOutput : PByteArray,
lpInput : PByteArray,
iSize : DWORD,
lpVA : DWORD,
cMaxInstr : DWORD,
cMinInstr : DWORD,
iGarbage : DWORD,
iForceSize : DWORD,
lpRelativeAddr : DWORD,
prPolyOutRegs : TPolyRegsPtr,
iOptions : DWORD,
lpWorkMem : PByteArray,
iRandomSeed : DWORD
): DWORD;
push dwRandomSeed
push offset lpWorkMem
push iOptions
push lpPolyOutRegs
push lpRelativeAddr
push iForceSize
push iGarbage
push cMinInstr
push cMaxInstr
push lpVA
push iSize
push lpInput
push lpOutput
push lpDecryptor
call _poly
POLY_ATTACH_DATA
.lpInput
. Jeśli ustawiona jest flaga
POLY_ATTACH_DATA
wartość ta jest ignorowana (gdyż zaszyfrowane dane znajdą się w buforze lpDecryptor
razem
z kodem deszyfrującym).lpInput
.lpDecryptor
i lpWorkMem
.cMaxInstr
, cMinInstr
i iGarbage
, tak aby uzyskać
wymagany rozmiar.VA
lub RVA
) i szyfrujemy dane z innego
adresu wirtualnego znajdującego się w tym samym obrazie pliku wykonywalnego w pamięci.Struktura POLY_REGS
określająca wartości rejestrów CPU, które zostaną ustawione po zakończeniu działania
kodu deszyfrującego. Do poprawnego działania tego mechanizmu wymagane jest ustawienie flagi POLY_SET_REGS
oraz jednej lub więcej flag określających, który rejestr procesora ma mieć ustawioną wartość określoną w strukturze, np. POLY_SET_EAX
.
struct POLY_REGS
{
unsigned int regEax;
unsigned int regEcx;
unsigned int regEdx;
unsigned int regEbx;
unsigned int regEsp;
unsigned int regEbp;
unsigned int regEsi;
unsigned int regEdi;
};
Dodatkowe opcje bitowe dla silnika polimorficznego.
Nazwa | Wartość | Znaczenie |
---|---|---|
POLY_SET_REGS | 0x00000001 |
Po wykonaniu kodu deszyfrującego do rejestrów procesora zostaną zapisane wartości określone w strukturze POLY_REGS . |
POLY_SET_EAX | 0x00000002 |
Ustaw wartość wyjściową rejestru EAX . |
POLY_SET_ECX | 0x00000004 |
Ustaw wartość wyjściową rejestru ECX . |
POLY_SET_EDX | 0x00000008 |
Ustaw wartość wyjściową rejestru EDX . |
POLY_SET_EBX | 0x00000010 |
Ustaw wartość wyjściową rejestru EBX . |
POLY_SET_ESP | 0x00000020 |
Ustaw wartość wyjściową rejestru ESP . |
POLY_SET_EBP | 0x00000040 |
Ustaw wartość wyjściową rejestru EBP . |
POLY_SET_ESI | 0x00000080 |
Ustaw wartość wyjściową rejestru ESI . |
POLY_SET_EDI | 0x00000100 |
Ustaw wartość wyjściową rejestru EDI . |
POLY_SAVE_REGS | 0x00000200 |
Zachowaj wartość wszystkich 32 bitowych rejestrów procesora używając instrukcji PUSHAD (oprócz tych ustawianych flagą POLY_SET_REGS ) i przywróć ich wartość po wykonaniu kodu dekryptora korzystając z instrukcji POPAD . |
POLY_SAVE_FLAGS | 0x00000400 |
Zachowaj wartość flag procesora wykorzystując instrukcję PUSHFD i przywróć ich wartość wykonując instrukcję POPFD po wykonaniu kodu dekryptora. |
POLY_RETURN | 0x00000800 |
Jeśli ta flaga zostanie ustawiona, w kodzie dekryptora, na jego końcu umieszczona zostanie instrukcja powrotu Kod dekryptora może być jednak umieszczany bezpośrednio pomiędzy innymi blokami kodu lub po jego kodzie mogą być dodawane inne bloki kodu, dlatego nie jest wymagane umieszczanie na końcu jego kodu instrukcji powrotu. |
POLY_ATTACH_DATA | 0x00001000 |
Ustawienie tej flagi spowoduje, że zaszyfrowane dane znajdą się w jednym buforze z kodem dekryptora. Kod dekryptora nadpisze swój własny bufor w pamięci odszyfrowanymi danymi i po odszyfrowaniu danych, wskaźnik wskazujący na kod dekryptora będzie jednocześnie wskazywał na odszyfrowane dane. Dzięki tej fladze można tworzyć kompaktowe bloki danych zawierające jednocześnie zaszyfrowane dane jak i kod deszyfrujący. Jeśli ta flaga nie zostanie ustawiona, kod dekryptora wykorzysta wartość parametrów |
POLY_FLAGS_ALL | 0xFFFFFFFF |
Kombinacja wszystkich powyższych flag w postaci jednej wartości. |
lpDecryptor
.Jeśli funkcja poprawnie zaszyfruje dane i wygeneruje kod dekryptora, wtedy zwrócony zostanie rozmiar w bajtach
kodu znajdującego się w buforze wskazywanym przez lpDecryptor
.
Jeśli wystąpi błąd funkcja zwróci 0
.
Silnik posiada wiele opcji pozwalających przystosować rodzaj generowanego kodu do wielu potrzeb, dlatego najlepiej jego wykorzystanie zaprezentować na przykładzie.
Na poniższym przykładzie Silnik Polimorficzny Poly wykorzystany jest do zaszyfrowania wejściowego bloku danych oraz wygenerowania kodu deszyfrującego zawierającego jednocześnie zaszyfrowany blok danych, tak, że np. po zaszyfrowaniu całość zaszyfrowanego bloku wraz z dekryptorem można zapisać do dowolnego pliku i aby odszyfrować dane, wystarczy taki blok danych odczytać z powrotem do bufora pamięci z ustawionymi flagami wykonywalnymi i uruchomić kod dekryptora.
Poniższy przykład wykorzystuje wszystkie dostępne opcje i dodatkowo wykonuje 10000 iteracji w celu sprawdzenia poprawności szyfrowania i deszyfrowania danych.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include "poly.h"
// makro pomocnicze
#define RND_RANGE(min,max) (min + (rand() % (int)(max - min + 1)))
// liczba testowych iteracji
const int POLY_TEST_ITERATIONS = 10000;
// bezpieczny rozmiar bufora pamięci
const int POLY_DECRYPTOR_SIZE = 1024 * 2048 * 10;
// przykładowe dane wejściowe do zaszyfrowania
unsigned char cInputBuffer[] = { 0x11, 0x22, 0x33, 0x44 } ;
// bufor wyjściowy
unsigned char cOutputBuffer[sizeof(cInputBuffer)] = { 0 };
int main(int argc, char* argv[])
{
// zaalokuj bufor pamięci dla zaszyfrowanego bloku (pamięć musi mieć ustawioną flagę pozwalającą na wykonywanie kodu)
PVOID lpDecryptor = VirtualAlloc(nullptr, POLY_DECRYPTOR_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// roboczy bufor pamięci (ten sam rozmiar i flagi wykonywalne)
PVOID lpWorkMem = VirtualAlloc(nullptr, POLY_DECRYPTOR_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
for (int i = 0; i < POLY_TEST_ITERATIONS; i++)
{
//
// przygotuj parametry silnika Poly
//
// ustaw losowe wartości parametrom (pamiętaj, że im więcej np. instrukcji szyfrujących, tym
// większe muszą być rozmiary buforów pamięci lpDecryptor i lpWorkMem)
// minimalna liczba instrukcji szyfrujących
unsigned int iMinEncryptionCommands = RND_RANGE(10, 50);
// maksymalna liczba instrukcji szyfrujących
unsigned int iMaxEncryptionCommands = RND_RANGE(iMinEncryptionCommands, iMinEncryptionCommands + 10);
// liczba instrukcji zaciemniających (garbage / junks) przypadająca na instrukcję w kodzie deszyfrującym
unsigned int iGarbage = RND_RANGE(10, 50);
// wymuś rozmiar kodu deszyfrującego - jeśli ten parametr jest ustawiony, silnik Poly
// będzie tak długo generował kod deszyfrujący aż osiągnie ten rozmiar, jeśli wygenerowany
// kod będzie zbyt mały lub zbyt duży to w locie zmodyfikuje parametry iMinEncryptionCommands,
// iMaxEncryptionCommands oraz iGarbage i będzie do skutku próbował wygenerować kod, który
// spełni to kryterium (proszę upewnij się, że bufory lpDecryptor i lpWorkMem są większe niż
// ta wartość)
unsigned int iForceDecryptorSize = 0;
// jeśli ten parametr jest ustawiony, polimorficzny kod deszyfrujący użyje go do obliczenia
// adresu zaszyfrowanych danych w pamięci relatywnie do bieżącego położenia kodu
// deszyfrującego w pamięci (wartość ta może mieć negatywną wartość)
unsigned int iRelativeDataOutputOffset = 0;
// flagi
unsigned int iOptions = 0;
// POLY_SAVE_REGS - zachowaj stan wszystkich rejestrów i przywróć je po zakończeniu deszyfrowania
iOptions |= POLY_SAVE_REGS;
// POLY_SAVE_FLAGS - zachowaj stan wszystkich flag procesora i przywróć je po zakończeniu deszyfrowania
iOptions |= POLY_SAVE_FLAGS;
// POLY_ATTACH_DATA - dołącz zaszyfrowane dane do kodu dekryptora, kod deszyfrujący nadpisze
// własny bufor pomięci odszyfrowanymi danymi, co oznacza, że wskaźnik kodu deszyfrującego
// będzie jednocześnie wskazywał na odszyfrowane dane
//
// jeśli ta flaga nie jest ustawiona, zaszyfrowane dane będą musiały znajdować się pod
// ustalonym i statycznym adresem wirtualnym określonym przez parametr lpVA lub
// relatywnym adresem określonym przez parametr iRelativeDataOutputOffset
iOptions |= POLY_ATTACH_DATA;
// POLY_RETURN - umieść instrukcję powrotu (RET) na końcu funkcji deszyfrującej,
// tak, że dekryptor wróci do kodu, który go wywołał, polimorficzny dekryptor może
// być umieszczony również bezpośrednio pomiędzy innym kodem lub dowolny kod może być
// dodany po nim, dlatego nie zawsze musi wracać do kodu, który go wywołał
iOptions |= POLY_RETURN;
// POLY_SET_REGS - zwróć określone wartości w rejestrach procesora po deszyfrowaniu
// (muszą być one zdefiniowane w strukturze POLY_REGS)
iOptions |= POLY_SET_REGS;
// flaga oznacza, że rejestr EAX po deszyfrowaniu ma być ustawiony
iOptions |= POLY_SET_EAX;
// i przykładowo zwróćmy inną wartość w rejestrze EDX po deszyfrowaniu
iOptions |= POLY_SET_EDX;
// wartości wyjściowych rejestrów (wypełnij tylko te, które mają być ustawione)
POLY_REGS prPolyRegs = { 0 };
// ustaw wartość rejestru EAX jaka ma być ustawione po deszyfrowaniu, w tym
// przykładzie ustawmy tą wartość na rozmiar odszyfrowanego bufora danych
// (można tu ustawić wszystko)
prPolyRegs.regEax = sizeof(cInputBuffer);
// przykładowa wartość jaka ma być zwrócona w rejestrze EDX po deszyfrowaniu
// (można to podejrzeć za pomocą debuggera)
prPolyRegs.regEdx = 0xDEADBEEF;
// zaszyfruj dane i wygeneruj polimorficzny kod deszyfrujący
unsigned int dwOutputSize = _poly(
lpDecryptor, // bufor wyjściowy na polimorficzny kod / dane
cOutputBuffer, // bufor na zaszyfrowane dane (może być taki sam jak lpInput) (opcjonalnie)
cInputBuffer, // bufor z danymi wejściowymi do zaszyfrowania
sizeof(cInputBuffer), // rozmiar danych do zaszyfrowania
(unsigned int)&cOutputBuffer, // wirtualny adres gdzie znajdą się dane do odszyfrowania (opcjonalnie)
iMaxEncryptionCommands, // max. liczba instrukcji szyfrujących
iMinEncryptionCommands, // min. liczba instrukcji szyfrujących
iGarbage, // liczba instrukcji zaśmiecających na instrukcję dekryptora
iForceDecryptorSize, // wymuś rozmiar dekryptora (opcjonalnie)
iRelativeDataOutputOffset, // relatywny adres zaszyfrowanych danych (opcjonalnie)
&prPolyRegs, // rejestry wyjściowe (opcjonalnie)
iOptions, // dodatkowe opcje
lpWorkMem, // pamięć robocza
GetTickCount() // inicjalizator dla wewnętrznego generatora losowego
);
// sprawdź rozmiar wyjściowych danych (rozmiar dekryptora w bajtach)
if (dwOutputSize != 0)
{
PolyDecryptorFunction DecryptorFunction = reinterpret_cast<PolyDecryptorFunction>(lpDecryptor);
// wywołaj kod dekryptora i odszyfruj dane
unsigned int dwResult = DecryptorFunction();
// inny sposób na wywołanie kodu dekryptora (w assemblerze)
/*
__asm
{
int 3
mov eax, lpDecryptor;
call eax
int 3
}
*/
// zweryfikuj odszyfrowane dane (jeśli parametr POLY_ATTACH_DATA być ustawiony, wskaźnik
// lpDecryptor wskazuje jednocześnie na dekryptor a po jego uruchomieniu na odszyfrowane dane)
if (memcmp(reinterpret_cast<PVOID>(lpDecryptor), cInputBuffer, sizeof(cInputBuffer) != 0))
{
printf("Odszyfrowane dane są inne niż powinny być!\n");
_getch();
return 2;
}
}
else
{
printf("Nie udało się zaszyfrować danych!\n");
_getch();
return 1;
}
}
printf("Test Silnika Polimorficznego Poly Polymorphic udał się (%lu iteracji testowych)\n", POLY_TEST_ITERATIONS);
// zwolnij pamięć
VirtualFree(lpDecryptor, 0, MEM_RELEASE);
VirtualFree(lpWorkMem, 0, MEM_RELEASE);
_getch();
return 0;
}
Nagłówek | poly.h |
Biblioteka | poly.lib |
DLL | poly.dll |
Jeśli masz jakieś pytania dotyczące Silnika Polimorficznego Poly, masz jakieś uwagi, coś jest niejasne, napisz do mnie, chętnie odpowiem na każde Twoje pytanie.