Dokumentacja Techniczna API Silnika Polimorficznego Poly

Opis funkcji, parametrów i flag Silnika Polimorficznego Poly wykorzystywanego do polimorficznego szyfrowania danych.

Opis funkcji szyfrującej

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
lpDecryptor [wyj]
Bufor wyjściowy na kod dekryptora. Ewentualnie znajdzie się tutaj zaszyfrowany blok danych, w przypadku użycia flagi POLY_ATTACH_DATA.
lpOutput [wyj, opcjonalnie]
Bufor wyjściowy, gdzie znajdą się zaszyfrowane dane. Może być taki sam jak bufor 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 [wej]
Bufor z danymi wejściowymi do zaszyfrowania.
iSize [wej]
Rozmiar w bajtach bufora wejściowego lpInput.
lpVA [wej, opcjonalnie]
Opcjonalny parametr określający adres wirtualny, który zostanie użyty przez kod dekryptora do odszyfrowania bloku danych. Jeśli wiemy pod jakim adresem wirtualnym znajdą się zaszyfrowane dane (np. nierelokowalny adres w pliku wykonywalnym) możemy ten parametr ustawić.
cMaxInstr [wej]
Maksymalna ilość instrukcji szyfrujących, które zostaną losowo wygenerowane w procesie szyfrowania. Im więcej instrukcji szyfrujących tym większy będzie wygenerowany kod i należy odpowiednio skorygować rozmiar buforów lpDecryptor i lpWorkMem.
iGarbage [wej]
Ilość instrukcji zaśmiecających, które zostaną umieszczone co instrukcję w kodzie funkcji deszyfrującej. Wartość może być ustawiona na 0. Instrukcje zaśmiecające skutecznie utrudniają analizę kodu deszyfrującego.
iForceSize [wej, opcjonalnie]
Opcjonalny parametr określający jaki dokładnie rozmiar w bajtach ma mieć wygenerowana funkcja deszyfrująca. Jeśli ten parametr jest ustawiony, funkcja będzie tak długo generowała różne wersje kodu deszyfrującego, dopóki nie uzyska określonego rozmiaru. Jeśli rozmiar będzie zbyt mały lub zbyt duży, automatycznie skorygowane zostaną parametry cMaxInstr, cMinInstr i iGarbage, tak aby uzyskać wymagany rozmiar.
lpRelativeAddr [wej, opcjonalnie]
Opcjonalny parametr, dzięki któremu kod deszyfrujący będzie w stanie obliczyć wskaźnik do zaszyfrowanych danych, których pozycja znajduje się w pamięci relatywnie do bufora, w którym aktualnie znajduje się funkcja deszyfrująca. Parametr ten może mieć negatywną wartość (jeśli dane znajdują się przed kodem funkcji deszyfrującej). Przykładem zastosowania tego parametru jest sytuacja, w której znamy docelowe położenie kodu funkcji deszyfrującej np. w obrazie pliku wykonywalnego (jako adres wirtualny VA lub RVA) i szyfrujemy dane z innego adresu wirtualnego znajdującego się w tym samym obrazie pliku wykonywalnego w pamięci.
lpPolyOutRegs [wej, opcjonalnie]

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;
};
iOptions [wej]

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 RET. Dzięki niej można wywoływać kod dekryptora jak normalną funkcję.

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 lpVA lub lpRelativeAddr do pobrania wskaźnika, gdzie znajdują się zaszyfrowane dane, które należy odszyfrować.

POLY_FLAGS_ALL 0xFFFFFFFF Kombinacja wszystkich powyższych flag w postaci jednej wartości.
lpWorkMem [wej]
Pamięć na bufor roboczy. Bufor musi posiadać flagi wykonywalne, a jego rozmiar musi być przynajmniej taki jaki bufora lpDecryptor.
iRandomSeed [wej]
Wartość inicjalizująca wewnętrzny generator pseudolosowy, gwarantująca generowanie za każdym razem innych mutacji kodu deszyfrującego.

Zwracana wartość

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.

Przykład w C++

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;
}

Wymagania

Nagłówek poly.h
Biblioteka poly.lib
DLL poly.dll

Masz pytania?

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.