• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Создание шелл-кода из любого кода с помощью Visual Studio и C++

Artem N

(L2) cache
Пользователь
Регистрация
28.11.2020
Сообщения
329
Реакции
278
Узнайте, как преобразовать любой код в стабильный шелл-код с помощью Visual Studio 2019 и C++ в простых шагах!

В этой статье мы будем использовать Visual Studio 2019 и C++ для создания независимого машинного 64-битного кода, который можно использовать в качестве шелл-кода, Эта операция требует особых навыков, но после прочтения статьи вы сможете очень легко создать свой шелл-код...

Введение

Шелл-код - одна из самых популярных методик в атаках и взломах, посмотрим что о нём говорится в Wikipedia:
Шелл-код обычно внедряется в память эксплуатируемой программы, после чего на него передается управление путём переполнения стека, или при переполнении буфера в куче, или используя атаки форматной строки. Передача управления шелл-коду осуществляется перезаписью адреса возврата в стеке адресом внедрённого шелл-кода, перезаписью адресов вызываемых функций или изменением обработчиков прерываний. Результатом этого является выполнение шелл-кода, который открывает командную строку для использования взломщиком.
Итак. Всё это очень правильно, но я понимаю шелл-код как кусок кода, который может быть выделен и выполнен динамически - без зависимостей от API операционной системы или рантайма. Могу быть не прав, но это лучшее определение того, что мы сейчас будем делать, поэтому...
Начнём!

Предисловие

Причины, по которым я взялся за всё это:
1. Прокачать свои знания в программировании и в понимании как работает компьютер;
2. Улучшить безопасность моих программ и игр;
3. Это чертовски весело!

Важное замечание! Всё это можно использовать в плохих целях, но как вы знаете... Это меч! Защитить или навредить... решать вам!

Используя эту технику, вы сможете преобразовать критические и важные участки вашего кода (такие, как проверка лицензии) в шелл-код и зашифровать его в вашем приложении. Когда вам нужно проверить лицензию, вы просто расшифровываете код в куче, выполняете его. После проверки - освобождаете память... это осложняет отладку и не позволяет выполнить статический анализ приложения.

Подготовка проекта и окружения

1. Необходимые инструменты и программы

- Visual Studio 2019
- VC++ Build Tools
- CFF Explorer (PE Viewer/Editor)
- HxD (Hex Editor)

2. Создаём пустые проекты
1. Запускаем Visual Studio 2019
2. Создаём два проекта C++
3. С именами code_gen и code_tester
4. В свойствах конфигурации проекта code_gen установите Тип конфигурации: "Динамическая библиотека (.dll)"
5. Для code_tester - "Приложение (.exe)"
6. Установите для обоих проектов Конфигурацию Release и Платформу x64.

3. Настройка DLL, независимой от API
В данный момент проект code_gen имеет зависимость стандартной библиотеки C++. Выполните следующие шаги, чтобы сделать его полностью независимым.

1 Добавьте cpp-файл (не с-файл) в проект code_gen и напишите следующий код:
C:
extern "C" bool _code()
{
    return true;
}

2 В свойствах проекта code_gen настройте следующие опции:
- Дополнительно > Использовать отладочные библиотеки: Нет
- Дополнительно > Оптимизация всей программы: Без оптимизации всей программы
- C/C++ > Общие > Формат отладочной информации: Нет
- C/C++ > Общие > Проверка SDL: Нет (/sdl-)
- C/C++ > Создание кода > Включить C++ исключения: Нет
- C/C++ > Создание кода > Библиотека времени выполнения: Многопоточная (/MT)
- C/C++ > Создание кода > Проверка безопасности: Отключить проверку безопасности (/GS-)
- C/C++ > Язык > Режим совместимости: Нет (/permissive)
- C/C++ > Язык > Стандарт языка C++: Стандарт ISO C++17 (/std:c++17)
- Компоновщик > Ввод > Дополнительные зависимости: Пусто
- Компоновщик > Ввод > Игнорировать все стандартные библиотеки: Да (/NODEFAULTLIB)
- Компоновщик > Отладка > Создавать отладочную информацию: Нет
- Компоновщик > Отладка > Создавать файл сопоставления: Да (/MAP)
- Компоновщик > Система > Подсистема: Машинный код (/SUBSYSTEM:NATIVE)
- Компоновщик > Оптимизация > Ссылки: Нет (/OPT:NOREF)
- Компоновщик > Дополнительно > Точка входа: _code
- Компоновщик > Дополнительно > Без точки входа: Да (/NOENTRY)
Замечание: изменяя точку входа на _code, мы предотвращаем создание DLL, содержащей только ресурсы, так же компоновщик не будет использовать стандартный main/DllMain в качестве точки входа.

4. Настройка текстового приложения
Приложение не требует какой-то специфичной настройки, сейчас наше внимание сосредоточено на написании кода, модификации и последующем запуске.
Добавьте main.cpp в проект code_tester и напишите там следующий код:
C++:
// main.cpp

#include <windows.h>
#include <iostream>

using namespace std;

int main()
{
    return EXIT_SUCCESS;
}
Итак, всё готово!

Обычный метод

Давайте начнём с чего-то небольшого и простого, используя только математику, изменим функцию _code на указанную:
C++:
extern "C" int _code(int x, int y)
{
    return x * y + (x + y);
}
Сейчас компилируем и в результате вы должны получить DLL-файл, если же нет... проверьте все пункты выше и убедитесь, что всё сделано без ошибок, что конфигурация корректна.

img01.png


Нам нужны только два файла: .dll и .map. В dll находится исполняемый код, а map-файл содержит информацию об адресах, используемых компоновщиком, но наиболее важное его содержимое - это виртуальные адреса и смещения кода, на которые наш код отображается.

1. Открываем файл code_gen.dll в CFF Explorer.

img02.png


Как можно заметить, у нашей DLL нет таблица адресов Импорта/Экспорта (IAT/EAT) и только три секции, последние две из них не нужны: одна содержит данные для отладки, вторая - ресурсы, такие как информация о версии файла. Код, который мы ищем, находится в секции .text.
И единственная информация, которая нам нужна в этой секции - Virtual Address и Raw Address.

2. Откройте code_gen.dll в HxD или любом другом шестнадцатеричном редакторе, нажмите Ctrl+G или выберите Search->Goto... и введите Raw Address... Это тот самый код, который мы ищем! Довольно просто, не так ли?

img03.png


Опкод 0xC3 - это инструкция RETN, которая указывает нам на конец нашей функции, все прочие нулевые байты не нужны.

3. Выделите их, скопируйте через меню Edit->Copy as->C и вставьте в main.cpp.
Код должен выглядеть следующим образом:
C++:
#include <windows.h>
#include <iostream>

using namespace std;

unsigned char _code_raw[9] = { 0x8D, 0x42, 0x01, 0x0F, 0xAF, 0xC1, 0x03, 0xC2, 0xC3 };

int main()
{
    return EXIT_SUCCESS;
}

4. Сейчас мы должны создать прототип нашей функции, добавьте этот код в глобальной области видимости:
C:
typedef int(*_code_t)(int, int);

5. Пришло время установить на данные флаг выполнения, чтобы процессор мог выполнить наш код, добавьте следующее в функцию main:
C++:
DWORD old_flag;
VirtualProtect(_code_raw, sizeof _code_raw, PAGE_EXECUTE_READWRITE, &old_flag);

6. И последний шаг, чтобы выполнение происходило проще, добавьте этот код до возврата из функции:
C++:
_code_t fn_code = (_code_t)(void*)_code_raw;
int x = 500; int y = 1200;
printf("Result of function : %d\n", fn_code(x, y));

7. Скомпилируйте code_tester и запустите его, бац! Это работает! Результат должен выглядеть так:
Result of function : 601700
Что ж, это был очень простой способ без аллокаций памяти, какого-либо шифрования/дешифрования или сжатия/распаковки. Но всё же достаточно хороший, чтобы появилось понимание что здесь происходит.
Теперь... перейдём на следующий уровень!

Продвинутый метод

Выше у нас был простой код, но когда он станет более сложным (сжатие, шифрование, проверка лицензии и т.п.), то будет уже не так просто получить смещение по любому адресу в двоичном коде. Поэтому нам нужен map-файл.
Также, мы не использовали библиотеку времени исполнения и WinAPI. Но в программах все это нужно всегда. Этот момент мы обсудим во второй части статьи.
В этой части статьи мы создадим два шелл-кода: один для шифрования буфера, а второй для дешифрования буфера.

1. Склонируйте репозиторий tiny_aes_c к себе, скопируйте оттуда только aes.c и aes.h в свой проект code_gen.
2. Измените code.cpp следующим образом:
C++:
// code.cpp
extern "C"
{
    #include "aes.h"

    bool _encrypt(void* data, size_t size)
    {
        // Encryption Code Area //
        return true;
    }
}
Замечание: можно не использовать extern "C", если измените имя файла code.cpp на code.c, но в дальнейшем вы не сможете использовать ни одну функцию C++. В любом случае, большинство библиотек C++ основаны на C, и все они совместимы друг с другом.
tiny-aes-c основан на языке C, и в нём используются лишь некоторые функции среды выполнения C, которые компилятор оптимизирует до чистого машинного кода. Это означает, что наш шелл-код будет оптимизирован компилятором. И это хорошо!

3. Напишите код шифрования следующим образом и не используйте данные на стеке - у вас не должно быть секции .data в вашем DLL-файле после компиляции. Посмотрите на код, в нём все данные расположены внутри функции.
Вот код для шифрования:
C++:
// code.cpp
extern "C"
{
    #include "aes.h"

    bool _encrypt(void* data, size_t size)
    {
        // Allocate data on heap
        struct AES_ctx ctx;
        unsigned char key[32] = {
        0xBB, 0x17, 0xCA, 0x8C, 0x69, 0x7F, 0xA1, 0x89,
        0x3B, 0xCF, 0xA8, 0x12, 0x34, 0x6F, 0xB6, 0xE8,
        0x79, 0x89, 0xDA, 0xD0, 0x0B, 0xA9, 0xA1, 0x1B,
        0x5B, 0x38, 0xD0, 0x4A, 0x20, 0x4D, 0xB8, 0x0E};
        unsigned char iv[16] = {
        0xA3, 0xF3, 0xD4, 0xC5, 0x5E, 0xCD, 0x41, 0xA6,
        0x22, 0xC9, 0x8D, 0xE5, 0xA3, 0xBB, 0x29, 0xF1};

        // Initialize encrypt context
        AES_init_ctx_iv(&ctx, key, iv);

        // Encrypt buffer
        AES_CBC_encrypt_buffer(&ctx, (uint8_t*)data, size);
        return true;
    }
}

4. Скомпилируйте и откройте code_gen.dll в CFF Explorer:

img04.png


Как вы видите, появилась секция .rdata, причина её появления - данные из файла tiny-aes-c для массивов sbox и rsbox. Без этих данных код работать не будет.

Ручное объединение двух секций, исправление каждого адреса в коде довольно сложно сделать без ошибок и потребуется очень много времени, но...
Есть одна волшебная директива компилятора, которая нам здесь поможет!
Добавьте следующий код в начало code.cpp:
C++:
#pragma comment(linker, "/merge:.rdata=.text")

5. Скомпилируйте и снова откройте code_gen.dll в CFF Explorer:

img05.png



Бум! Исправлено, сейчас наш код расположен за данными, которые ему нужны.

img06.png


6. Откройте code_gen.dll в HxD или любом другом шестнадцатеричном редакторе, нажмите Ctrl+G или выберите Search->Goto... и выберите секцию .text. Нажмите Ctrl+E или выберите Edit->Select Block, введите реальный размер секции .text. Скопируйте буфер как Си-массив и вставьте его в новый заголовочный файл проекта code_tester как shellcode_encrypter_raw.h. Имя массива должно совпадать с именем файла.
Замечание: чтобы облегчить извлечение кода, вы можете прямо в CFF Explorer щёлкнуть правую кнопку мышки на секции и выбрать пункт Dump section. Но иногда размер секции больше ожидаемого, поэтому ручной способ предпочтительней.

7. Измените в проекте code_tester файл main.cpp следующим образом:
C++:
// main.cpp
#include <windows.h>
#include <stdio.h>
#include <fstream>
#include <vector>
#include "shellcode_encrypter_raw.h"

using namespace std;
typedef bool(*_encrypt)(void*, size_t);

#define ENC_SC_RAW shellcode_encrypter_raw
#define FUNCTION_OFFSET 0

int main(int argc, char* argv[])
{
    // Check for commands count
    if (argc != 4) return EXIT_FAILURE;

    // Get commands values
    char* input_file   = argv[1];
    char* process_mode = argv[2];
    char* output_file  = argv[3];

    // Change code protection
    DWORD old_flag;
    VirtualProtect(ENC_SC_RAW, sizeof ENC_SC_RAW, PAGE_EXECUTE_READWRITE, &old_flag);

    // Declaring encrypt function
    _encrypt encrypt = (_encrypt)(void*)&ENC_SC_RAW[FUNCTION_OFFSET];

    // Read input file to vector buffer
    ifstream input_file_reader(argv[1], ios::binary);
    vector<uint8_t> input_file_buffer(istreambuf_iterator<char>(input_file_reader), {});

    // Add padding to input file data
    for (size_t i = 0; i < 16; i++)
         input_file_buffer.insert(input_file_buffer.begin(), 0x0);
    for (size_t i = 0; i < 16; i++) input_file_buffer.push_back(0x0);

    // Encrypting file buffer
    if (strcmp(process_mode, "-e") == 0) encrypt(input_file_buffer.data(),
                                         input_file_buffer.size());

    // Save encrypted buffer to output file
    fstream file_writter;
    file_writter.open(output_file, ios::binary | ios::out);
    file_writter.write((char*)input_file_buffer.data(), input_file_buffer.size());
    file_writter.close();

    // Code successfully executed
    printf("OK"); return EXIT_SUCCESS;
}

8. OK, следующий шаг - найти смещение функции _encrypt в шелл-коде. Откройте code_gen.map в текстовом редакторе (я использую Notepad++).

9. Ищем _encrypt и должны найти эту строчку:
0001:000012c0 _encrypt 00000001800022C0 f code.obj
Мы узнали относительный виртуальный адрес нашей функции (0x22C0), но нам нужен фактический адрес.

10. Вернёмся в CFF Explorer и найдём там виртуальный адрес секции .text, который равен 0x1000. Всё что нам остаётся сделать - вычесть один из другого и получить в результате 0x12C0. Это и есть искомое смещение. Обновим наш код:
C++:
#define FUNCTION_OFFSET 0x12C0

11. Скомпилируйте и проверьте его с помощью командной строки:
code_tester.exe some_image.jpg -e some_image_encrypted.jpg

[ЯДЕРНЫЙ ВЗРЫВ!]

Результат выполнения - OK. Это означает, что полученный файл полностью зашифрован алгоритмом AES-256!

12. Итак, чтобы сгенерировать шелл-код дешифратора, выполните точно эти шаги, за исключением:
- Используйте AES_CBC_decrypt_buffer вместо AES_CBC_encrypt_buffer;
- Измените прототип функции следующим образом:
C++:
typedef bool(*_crypt)(void*, size_t);

Вот как должен выглядеть код в main.cpp:
C++:
// main.cpp
#include <windows.h>
#include <stdio.h>
#include <fstream>
#include <vector>
#include "shellcode_encrypter_raw.h"
#include "shellcode_decrypter_raw.h"

using namespace std;
typedef bool(*_crypt)(void*, size_t);

#define ENC_SC_RAW shellcode_encrypter_raw
#define DEC_SC_RAW shellcode_decrypter_raw
#define FUNCTION_OFFSET 0x12C0

int main(int argc, char* argv[])
{
    // Check for commands count
    if (argc != 4) return EXIT_FAILURE;

    // Get commands values
    char* input_file   = argv[1];
    char* process_mode = argv[2];
    char* output_file  = argv[3];

    // Validate process mode
    if (strcmp(process_mode, "-e") != 0 &&
        strcmp(process_mode, "-d") != 0) return EXIT_FAILURE;

    // Change code protection
    DWORD old_flag;
    VirtualProtect(ENC_SC_RAW, sizeof ENC_SC_RAW, PAGE_EXECUTE_READWRITE, &old_flag);
    VirtualProtect(DEC_SC_RAW, sizeof DEC_SC_RAW, PAGE_EXECUTE_READWRITE, &old_flag);

    // Declaring encrypt function
    _crypt encrypt = (_crypt)(void*)&ENC_SC_RAW[FUNCTION_OFFSET];
    _crypt decrypt = (_crypt)(void*)&DEC_SC_RAW[FUNCTION_OFFSET];

    // Read input file to vector buffer
    ifstream input_file_reader(argv[1], ios::binary);
    vector<uint8_t> input_file_buffer(istreambuf_iterator<char>(input_file_reader), {});

    // Add padding to input file data
    if(strcmp(process_mode, "-d") == 0) goto SKIP_PADDING;
    for (size_t i = 0; i < 16; i++)
         input_file_buffer.insert(input_file_buffer.begin(), 0x0);
    for (size_t i = 0; i < 16; i++) input_file_buffer.push_back(0x0);

    // Encrypting/Decrypting file buffer
    SKIP_PADDING:
    if (strcmp(process_mode, "-e") == 0) encrypt(input_file_buffer.data(),
                                                 input_file_buffer.size());
    if (strcmp(process_mode, "-d") == 0) decrypt(input_file_buffer.data(),
                                                 input_file_buffer.size());

    // Save encrypted buffer to output file
    fstream file_writter;
    file_writter.open(output_file, ios::binary | ios::out);
    if (strcmp(process_mode, "-e") == 0)
        file_writter.write((char*)input_file_buffer.data(), input_file_buffer.size());
    if (strcmp(process_mode, "-d") == 0)
        file_writter.write((char*)&input_file_buffer[16],
                            input_file_buffer.size() - 32);
    file_writter.close();

    // Code successfully executed
    printf("OK"); return EXIT_SUCCESS;
}

13. Скомпилируйте и проверьте его с помощью командной строки:
code_tester.exe some_image_encrypted.jpg -d some_image_decrypted.jpg

Вот и всё! Теперь у вас есть два небольших шелл-кода, которые выполняют шифрование/дешифрование!
После использования код можно затереть. Также, вы можете паковать/шифровать его разными ключами, распаковывая/расшифровывая лишь по мере необходимости.

Заключение

Это конец первой части. Во второй части мы создадим EXE/DLL упаковщика/протектора с нуля, используя только C++ тем же способом, но с намного более сложными вещами, такими как resolving, обфускация, перенаправления вызовов и т.д.
Надеюсь, вам понравилась эта статья. Не стесняйтесь задавать свои вопросы в комментариях. Следите за обновлениями!

---
Автор статьи - The Ænema. Оригинал тут - https://www.codeproject.com/Articles/5304605/Creating-Shellcode-from-any-Code-Using-Visual-Stud
Переведено специально для xss.pro.

Мой первый перевод в жизни, буду благодарен за указание неточностей (в личку). Жду вторую часть, постараюсь и её перевести. Всем новых знаний!
 

Вложения

  • 01_shellcode_tutorial_starter_kit_vs16_x64.zip
    3.1 КБ · Просмотры: 46
  • 02_shellcode_tutorial_basic_approach_vs16_x64.zip
    9.4 КБ · Просмотры: 43
  • 03_shellcode_tutorial_advanced_approach_chapter1_vs16_x64.zip
    32.7 КБ · Просмотры: 40
  • 04_shellcode_tutorial_advanced_approach_chapter2_vs16_x64_version_2.zip
    39.4 КБ · Просмотры: 46
Белочка и чортики! Ничего не сказано ни про "rip relative addressing x64" ни про поиск адреса Kernel32 и Ntdll.
Надеюсь, это будет во второй части, которую автор планировал опубликовать ещё вчера.
 
Вряд ли всё это будет, конечно. Это же codeproject: там не то, чтобы низкоуровнего очень уж мало (хватает для самообразования), но уж чего-чего, а блекухи точно нет. Так-то надеюсь, после второй части будет и третья с чем-нибудь интересным :cool:
 
про поиск адреса Kernel32 и Ntdll
Вот хороший репозиторий для поиска нужных функций по хешам:
github.com/zerosum0x0/defcon-25-workshop/tree/master/src/importless/importless

тут find_module на вход получает хеш строки в PWSTR и отдает HMODULE (например kernelbase.dll) можно использовать только для поиска дллки
а find_api это уже комплексная функция которая принимает хеш dll-Библиотеки из PWSTR и хеш Winapi-Функции из LPSTR внутри вызывает find_module и парсит таблицу экспорта функций и отдает FARPROC

из минусов - я бы переделал функцию хеширования, что бы все символы приводились к нижнему регистру, и не нужно было вводить длину строки, потому что в шеллкоде у нас получается еще нет strlen
C:
unsigned int HashValue(const char* str)
{
    unsigned int h = 0;
    while (*str) {
        h = (h >> 13) | (h << (32 - 13));
        h += *str >= 'a' ? *str - 32 : *str;
        str++;
    }
    return h;
}
unsigned int HashValue(const wchar_t* str)
{
    unsigned int h = 0;
    while (*str) {
        h = (h >> 13) | (h << (32 - 13));
        h += *str >= L'a' ? *str - 32 : *str;
        str++;
    }
    return h;
}
Да и раз по ману у нас Стандарт ISO C++17 можно сделать подсчет хешей во время компиляции

К тому же это уберет лишние строки, а компилятор С++ творит с ними нечто ужасное и тяжеловесное, и лучше сроки хранить в каком нибудь obj файле скомпилированным в ассемблере
 
Прошёл месяц, автор вторую часть не выложил до сих пор. Ну и в каменты подтянулся самый сознательный буржуй:
Most things qualify but I see absolutely no constructive reason to show people how to write malicious code. It might be useful to show people how to prevent such things and how to detect use of this technique. As it is the limited use of this technique is not nearly enough to justify the potential damage from publicizing this.
Showing people how to write malicious code is very, VERY dangerous and that's exactly what this does. If I were in charge of this site articles of this nature would be prohibited.

Где он там увидел malicious code - затрудняюсь сказать. Даже и не знаю, хватит ли у автора смелости опубликовать вторую часть.
 
Прошёл месяц, автор вторую часть не выложил до сих пор. Ну и в каменты подтянулся самый сознательный буржуй:



Где он там увидел malicious code - затрудняюсь сказать. Даже и не знаю, хватит ли у автора смелости опубликовать вторую часть.
Всем интересна как сделать шеллкод именно на плюсах? Или можна забабахать статью о том как на асме или сишечке?
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Или можна забабахать статью о том как на асме или сишечке?
Шеллкод, в классическом понимании, это всегда ассемблер. Все остальное от лукавого ;)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
При желании можно и на Расте сделать (no_std) и на Дэ (-betterC) и на Ниме скорее всего.
 
При желании можно и на Расте сделать (no_std) и на Дэ (-betterC) и на Ниме скорее всего.
Можно на чём угодно сделать мне кажется, если руки прямые.
А так, было бы интересно взглянуть, как на расте подобное делают
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Learn how to convert any code to stable shellcode with Visual Studio 2019 and C ++ in easy steps!

In this article, we will use Visual Studio 2019 and C ++ to create independent machine 64-bit code that can be used as shellcode.This operation requires special skills, but after reading the article, you should be able to create your own shellcode very easily. ...

Introduction

Shellcode is one of the most popular techniques in attacks and hacking, let's see what Wikipedia says about it:

So. This is all very correct, but I understand shellcode as a piece of code that can be allocated and executed dynamically - without dependencies on the operating system API or runtime. I may be wrong, but this is the best definition of what we are going to do now, so ...
Let's get started!

Foreword

The reasons why I took it all:
1. Improve your knowledge of programming and understanding how a computer works;
2. Improve the security of my programs and games;
3. It's a hell of a lot of fun!

Important note! All of this can be used for bad purposes, but as you know ... It's a sword! Protect or harm ... it's up to you !

Using this technique, you can convert critical and important sections of your code (such as license checks) into shellcode and encrypt it in your application. When you need to check the license, you simply decrypt the code on the heap, execute it. After checking, you free up memory ... this complicates debugging and does not allow static analysis of the application.

Preparing the project and environment

1. Required tools and programs

- Visual Studio 2019
- VC ++ Build Tools
- CFF Explorer (PE Viewer / Editor)
- HxD (Hex Editor)

2. Create empty projects
1. Launch Visual Studio 2019
2. Create two C ++ projects
3.With the names code_gen and code_tester
4. In the configuration properties of the code_gen project , set the Configuration Type: " Shared Library (.dll) "
5. For code_tester - " Application (.exe) "
6. Install the Release Configuration and the x64 Platform for both projects .

3. Setting up an API independent DLL
The code_gen project currently has a C ++ standard library dependency. Follow these steps to make it completely independent.

1 Add a cpp -file (not a -file) to the code_gen project and write the following code:
[CODE = c] extern "C" bool _code ()
{
return true;
} [/ CODE]

2 In the properties of the code_gen project , configure the following options:
- Advanced> Use Debug Libraries: No
- Advanced> Optimization of the whole program: Without optimizing the whole program
- C / C ++> General> Debug Information Format: No
- C / C ++> General> SDL Check: None (/ sdl-)
- C / C ++> Code Generation> Include C ++ Exceptions: No
- C / C ++> Code Generation> Runtime Library: Multithreaded (/ MT)
- C / C ++> Code Generation> Security Check: Disable Security Check (/ GS-)
- C / C ++> Language> Compatibility Mode: None (/ permissive)
- C / C ++> Language> C ++ Language Standard: ISO C ++ 17 Standard (/ std: c ++ 17)
- Linker> Input> Additional Dependencies: Empty
- Linker> Input> Ignore all standard libraries: Yes (/ NODEFAULTLIB)
- Linker> Debug> Generate Debug Information: No
- Linker> Debug> Generate Mapping File: Yes (/ MAP)
- Linker> System> Subsystem: Machine Code (/ SUBSYSTEM: NATIVE)
- Linker> Optimization> References: None (/ OPT: NOREF)
- Linker> Advanced> Entry Point: _code
- Linker> Advanced> No Entry Point: Yes (/ NOENTRY)


4. Setting up a text application
The application does not require any specific configuration; now our attention is focused on writing code, modifying it and then launching it.
Add main.cpp to your code_tester project and write the following code there:
[CODE = cpp] // main.cpp

#include <windows.h>
#include <iostream>

using namespace std;

int main ()
{
return EXIT_SUCCESS;
} [/ CODE]
So you're done!

The usual method

Let's start with something small and simple, using only math, change the _code function to this:
[CODE = cpp] extern "C" int _code (int x, int y)
{
return x * y + (x + y);
} [/ CODE]
Now we compile and as a result you should get a DLL file, if not ... check all the points above and make sure that everything was done without errors, that the configuration is correct.

[ATTACH = full] 23376 [/ ATTACH]

We only need two files: .dll and .map. The dll contains the executable code, and the map file contains information about the addresses used by the linker, but its most important content is the virtual addresses and code offsets to which our code is mapped.

1. Open the code_gen.dll file in CFF Explorer.

[ATTACH = full] 23377 [/ ATTACH]

As you can see, our DLL does not have an Import / Export Address Table (IAT / EAT) and only three sections, the last two of them are not needed: one contains data for debugging, the second contains resources such as file version information. The code we're looking for is in the .text section .
And the only information we need in this section is Virtual Address and Raw Address .

2. Open code_gen.dll in HxD or any other hex editor, press Ctrl + G or select Search-> Goto ... and enter Raw Address ... This is the code we are looking for! Pretty simple, isn't it?

[ATTACH = full] 23378 [/ ATTACH]

Opcode 0xC3 is a RETN instruction that points us to the end of our function, all other zero bytes are not needed.

3. Select them, copy them through the Edit-> Copy as-> C menu and paste them into main.cpp .
The code should look like this:
[CODE = cpp] #include <windows.h>
#include <iostream>

using namespace std;

unsigned char _code_raw [9] = {0x8D, 0x42, 0x01, 0x0F, 0xAF, 0xC1, 0x03, 0xC2, 0xC3};

int main ()
{
return EXIT_SUCCESS;
} [/ CODE]

4. Now we need to prototype our function, add this code in the global scope:
[CODE = c] typedef int (* _ code_t) (int, int); [/ CODE]

5. It's time to set the execution flag on the data so that the processor can execute our code, add the following to the main function:
[CODE = cpp] DWORD old_flag;
VirtualProtect (_code_raw, sizeof _code_raw, PAGE_EXECUTE_READWRITE, & old_flag); [/ CODE]

6. And the last step, to make the execution easier, add this code before returning from the function:
[CODE = cpp] _code_t fn_code = (_code_t) (void *) _ code_raw;
int x = 500; int y = 1200;
printf ("Result of function:% d \ n", fn_code (x, y)); [/ CODE]

7. Compile code_tester and run it, bam! It works! The result should look like this:

Well, it was a very simple way without memory allocations, any encryption / decryption or compression / decompression. But still good enough to have an understanding of what's going on here.
Now ... let's move on to the next level!

Advanced method

Above, we had a simple code, but when it becomes more complex (compression, encryption, license verification, etc.), it will not be so easy to get the offset at any address in the binary code. Therefore, we need a map file.
Also, we did not use the runtime library and WinAPI. But in programs, all this is always needed. We will discuss this point in the second part of the article.
In this part of the article, we will create two shellcodes, one for encrypting the buffer and one for decrypting the buffer.

1. Skloniruyte repository tiny_aes_c to him, there is only copy aes.c and aes.h in your project code_gen .
2. Modify code.cpp as follows:
[CODE = cpp] // code.cpp
extern "C"
{
#include "aes.h"

bool _encrypt (void * data, size_t size)
{
// Encryption Code Area //
return true;
}
} [/ CODE]

tiny-aes-c is based on the C language and uses only a few features of the C runtime that the compiler optimizes to pure machine code. This means our shellcode will be optimized by the compiler. And this is good!

3. Write the encryption code as follows and don't use data on the stack - you shouldn't have a .data section in your DLL after compilation . Look at the code, all the data is inside the function.
Here is the encryption code:
[CODE = cpp] // code.cpp
extern "C"
{
#include "aes.h"

bool _encrypt (void * data, size_t size)
{
// Allocate data on heap
struct AES_ctx ctx;
unsigned char key [32] = {
0xBB, 0x17, 0xCA, 0x8C, 0x69, 0x7F, 0xA1, 0x89,
0x3B, 0xCF, 0xA8, 0x12, 0x34, 0x6F, 0xB6, 0xE8,
0x79, 0x89, 0xDA, 0xD0, 0x0B, 0xA9, 0xA1, 0x1B,
0x5B, 0x38, 0xD0, 0x4A, 0x20, 0x4D, 0xB8, 0x0E};
unsigned char iv [16] = {
0xA3, 0xF3, 0xD4, 0xC5, 0x5E, 0xCD, 0x41, 0xA6,
0x22, 0xC9, 0x8D, 0xE5, 0xA3, 0xBB, 0x29, 0xF1};

// Initialize encrypt context
AES_init_ctx_iv (& ctx, key, iv);

// Encrypt buffer
AES_CBC_encrypt_buffer (& ctx, (uint8_t *) data, size);
return true;
}
} [/ CODE]

4. Compile and open code_gen.dll in CFF Explorer:

[ATTACH = full] 23381 [/ ATTACH]

As you can see, the .rdata section has appeared , the reason for its appearance is the data from the tiny-aes-c file for the sbox and rsbox arrays. Without this data, the code will not work.

Manual merging of two sections, correcting each address in the code is quite difficult to do without errors and will take a lot of time, but ...
There is one magic compiler directive that will help us here!
Add the following code to the top of code.cpp :
[CODE = cpp] #pragma comment (linker, "/merge:.rdata=.text") [/CODE]

5. Compile and reopen code_gen.dll in CFF Explorer:

[ATTACH = full] 23382 [/ ATTACH]


Boom! Fixed, now our code is located behind the data it needs.

[ATTACH = full] 23383 [/ ATTACH]

6. Open code_gen.dll in HxD or any other hex editor, press Ctrl + G or select Search-> Goto ... and select the .text section . Press Ctrl + E or select Edit-> Select Block, enter the actual size of the .text section . Copy the buffer as a C array and paste it into the new header file of the code_tester project as shellcode_encrypter_raw.h . The array name must be the same as the file name.


7. Change the project code_tester file main.cpp follows:
[CODE = cpp] // main.cpp
#include <windows.h>
#include <stdio.h>
#include <fstream>
#include <vector>
#include "shellcode_encrypter_raw.h"

using namespace std;
typedef bool (* _ encrypt) (void *, size_t);

#define ENC_SC_RAW shellcode_encrypter_raw
#define FUNCTION_OFFSET 0

int main (int argc, char * argv [])
{
// Check for commands count
if (argc! = 4) return EXIT_FAILURE;

// Get commands values
char * input_file = argv [1];
char * process_mode = argv [2];
char * output_file = argv [3];

// Change code protection
DWORD old_flag;
VirtualProtect (ENC_SC_RAW, sizeof ENC_SC_RAW, PAGE_EXECUTE_READWRITE, & old_flag);

// Declaring encrypt function
_encrypt encrypt = (_encrypt) (void *) & ENC_SC_RAW [FUNCTION_OFFSET];

// Read input file to vector buffer
ifstream input_file_reader (argv [1], ios :: binary);
vector <uint8_t> input_file_buffer (istreambuf_iterator <char> (input_file_reader), {});

// Add padding to input file data
for (size_t i = 0; i <16; i ++)
input_file_buffer.insert (input_file_buffer.begin (), 0x0);
for (size_t i = 0; i <16; i ++) input_file_buffer.push_back (0x0);

// Encrypting file buffer
if (strcmp (process_mode, "-e") == 0) encrypt (input_file_buffer.data (),
input_file_buffer.size ());

// Save encrypted buffer to output file
fstream file_writter;
file_writter.open (output_file, ios :: binary | ios :: out);
file_writter.write ((char *) input_file_buffer.data (), input_file_buffer.size ());
file_writter.close ();

// Code successfully executed
printf ("OK"); return EXIT_SUCCESS;
} [/ CODE]

8. OK, the next step is to find the offset of the _encrypt function in the shellcode. Open code_gen.map in a text editor (I'm using Notepad ++).

9. We are looking for _encrypt and should find this line:

We learned the relative virtual address of our function ( 0x22C0 ), but we need the actual address.

10. Let's go back to CFF Explorer and find there the virtual address of the .text section , which is equal to 0x1000 . All we have to do is subtract one from the other and get 0x12C0 as a result . This is the desired offset. Let's update our code:
[CODE = cpp] #define FUNCTION_OFFSET 0x12C0 [/ CODE]

11. Compile and test it using the command line:


[NUCLEAR EXPLOSION!]

The result is OK . This means that the resulting file is fully encrypted with the AES-256 algorithm!

12. So, to generate the decoder shellcode, follow exactly these steps, except:
- Use AES_CBC_decrypt_buffer instead of AES_CBC_encrypt_buffer;
- Change the function prototype as follows:
[CODE = cpp] typedef bool (* _ crypt) (void *, size_t); [/ CODE]

This is how the code in main.cpp should look like :
[CODE = cpp] // main.cpp
#include <windows.h>
#include <stdio.h>
#include <fstream>
#include <vector>
#include "shellcode_encrypter_raw.h"
#include "shellcode_decrypter_raw.h"

using namespace std;
typedef bool (* _ crypt) (void *, size_t);

#define ENC_SC_RAW shellcode_encrypter_raw
#define DEC_SC_RAW shellcode_decrypter_raw
#define FUNCTION_OFFSET 0x12C0

int main (int argc, char * argv [])
{
// Check for commands count
if (argc! = 4) return EXIT_FAILURE;

// Get commands values
char * input_file = argv [1];
char * process_mode = argv [2];
char * output_file = argv [3];

// Validate process mode
if (strcmp (process_mode, "-e")! = 0 &&
strcmp (process_mode, "-d")! = 0) return EXIT_FAILURE;

// Change code protection
DWORD old_flag;
VirtualProtect (ENC_SC_RAW, sizeof ENC_SC_RAW, PAGE_EXECUTE_READWRITE, & old_flag);
VirtualProtect (DEC_SC_RAW, sizeof DEC_SC_RAW, PAGE_EXECUTE_READWRITE, & old_flag);

// Declaring encrypt function
_crypt encrypt = (_crypt) (void *) & ENC_SC_RAW [FUNCTION_OFFSET];
_crypt decrypt = (_crypt) (void *) & DEC_SC_RAW [FUNCTION_OFFSET];

// Read input file to vector buffer
ifstream input_file_reader (argv [1], ios :: binary);
vector <uint8_t> input_file_buffer (istreambuf_iterator <char> (input_file_reader), {});

// Add padding to input file data
if (strcmp (process_mode, "-d") == 0) goto SKIP_PADDING;
for (size_t i = 0; i <16; i ++)
input_file_buffer.insert (input_file_buffer.begin (), 0x0);
for (size_t i = 0; i <16; i ++) input_file_buffer.push_back (0x0);

// Encrypting / Decrypting file buffer
SKIP_PADDING:
if (strcmp (process_mode, "-e") == 0) encrypt (input_file_buffer.data (),
input_file_buffer.size ());
if (strcmp (process_mode, "-d") == 0) decrypt (input_file_buffer.data (),
input_file_buffer.size ());

// Save encrypted buffer to output file
fstream file_writter;
file_writter.open (output_file, ios :: binary | ios :: out);
if (strcmp (process_mode, "-e") == 0)
file_writter.write ((char *) input_file_buffer.data (), input_file_buffer.size ());
if (strcmp (process_mode, "-d") == 0)
file_writter.write ((char *) & input_file_buffer [16],
input_file_buffer.size () - 32);
file_writter.close ();

// Code successfully executed
printf ("OK"); return EXIT_SUCCESS;
} [/ CODE]

13. Compile and test it using the command line:


That's all! You now have two little shellcodes that do encryption / decryption!
After use, the code can be overwritten. Also, you can pack / encrypt it with different keys, unpacking / decrypting only as needed.

Conclusion

This is the end of the first part. In the second part, we will create a wrapper / protector EXE / DLL from scratch using only C ++ in the same way, but with much more complex things like resolving, obfuscation, call redirects, etc.
Hope you enjoyed this article. Feel free to ask your questions in the comments. Stay tuned!

---
The author of the article is The Ænema. The original is here - https://www.codeproject.com/Articles/5304605/Creating-Shellcode-from-any-Code-Using-Visual-Stud
Translated specifically for xss.pro.

My first translation in my life, I will be grateful for the indication of inaccuracies (in a personal). I'm waiting for the second part, I'll try to translate it too. All new knowledge!
Bro can you please guide or write a tutorial on how to use netwire c/c++ array shellcode and create a dll or exe that bypass in runtime ?
 
Судя по всему второй части не будет... А жаль...
Вторая часть будет, две недели назад наконец-то появилась! Вот она, прост я пока плотно занят и нет свободного времени. Постараюсь перевести на этой неделе, максимум на следующей.
Сделал - https://xss.pro/threads/59996/, приятного чтения!
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх