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

Пишем шеллкод на Golang | PE to shellcode

Jeffs

(L1) cache
Забанен
Регистрация
28.12.2018
Сообщения
611
Реакции
358
Пожалуйста, обратите внимание, что пользователь заблокирован
В общем-то захотелось попробовать написать какой-нибудь простой шеллкод на го. Понятное дело, компилятор го не даст нам этого сделать. Но компилятор имеет собирать PE/PE+ бинари, почему бы нам просто не запустить PE файл из шеллкода?
Вот тут есть реализации аналогичных проектов:
Подсмотрел кое-что там, и написал свой шеллкод лоадера PE, вес чуть больше 1 КБ.
Общая структура шеллкода на выходе |шеллкод PE-лоадера (1062 байта)|сигнатура+заголовок (36 байт)|байты самого PE (без заголовков)|, где:
- Сигнатура - простой набор байт: BYTE sign[] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 };. В дальнейшем нам поможет отыскать заголовок.
- Заголовок - подобие NT-заголовка на минималках:
C:
struct ShellHead
{
    DWORD peSize; // размер PE
    DWORD offsetSection; // смещение до заголовка секций
    DWORD numberOfSection; // кол-во секций
    DWORD offsetRelocation; // смещение директории релоков
    DWORD imageBase; // адрес, по которому нужно загрузить PE (не используется)
    DWORD offsetImportTable; // смещение директории импорта
    DWORD entryPoint; // смещение до мейн-функции
};
Как всё это формируется я думаю вы уже поняли, давайте перейдём к самому шелл-коду.
Для начала нам нужно спарсить наш кастомный заголовок. Он хранится после нашей сигнатуры. Нам нужно узнать где по какому адресу мы сейчас сидим, отыскать заголовок:
C:
DWORD_PTR GetShellHead(ShellHead** head)
{
    DWORD_PTR currAddr = NULL;
    _asm
    {
        call fun;
    fun:
        pop eax; // вытаскиваем адрес, который был помещён в регистр eax интструкцией выше (call)
        mov currAddr, eax;
    }

    // от этого адреса начинаем искать нашу сигнатуру, после неё уже идёт сам заголовок
    BYTE sign[] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 };
    while (currAddr++)
    {
        if (MemCmp((LPVOID)currAddr, sign, 8) == 0)
        {
            currAddr += 8;
            break;
        }
    }

    *head = (ShellHead*)currAddr;
    currAddr += sizeof(ShellHead);

    return currAddr;
}
Так же ищем адреса нужных нам winapi (LoadLibraryA, GetProcAddres, VirtualAlloc), выделяем исполняемую память под PE.
Копируем секции в выделенную память:
C:
void CopySections(Shell shell)
{
    PIMAGE_SECTION_HEADER sections = (PIMAGE_SECTION_HEADER)(shell.data + shell.head->offsetSection);
    for (WORD i = 0; i < shell.head->numberOfSection; i++)
    {
        if (sections->SizeOfRawData)
            MemCopy((PVOID)((DWORD_PTR)shell.peMem + sections->VirtualAddress), (PVOID)((DWORD_PTR)shell.data + sections->PointerToRawData), sections->SizeOfRawData);
        sections++;
    }
}
Обрабатываем релоки:
C:
void ProcessRelocs(Shell shell)
{
    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)shell.peMem;
    PIMAGE_BASE_RELOCATION baseRelocs = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)shell.peMem + shell.head->offsetRelocation);

    if ((PDWORD_PTR)baseRelocs == (PDWORD_PTR)dos)
        return;

    while ((baseRelocs->VirtualAddress + baseRelocs->SizeOfBlock) != 0)
    {
        WORD* pLocData = (WORD*)((PBYTE)baseRelocs + sizeof(IMAGE_BASE_RELOCATION));
        int numberOfReloc = (baseRelocs->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);

        for (int i = 0; i < numberOfReloc; i++)
        {
            if ((DWORD)(pLocData[i] & 0xf000) == 0x3000)
            {
                PDWORD_PTR pAddress = (PDWORD_PTR)((DWORD_PTR)dos + baseRelocs->VirtualAddress + ((DWORD)pLocData[i] & 0x0fff));
                DWORD dwDelta = (DWORD)dos - shell.head->imageBase;
                *pAddress += dwDelta;
            }

        }

        baseRelocs = (PIMAGE_BASE_RELOCATION)((PBYTE)baseRelocs + baseRelocs->SizeOfBlock);
    }
}
Обрабатываем импорт:
C:
void ProcessImport(Shell shell)
{
    PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)shell.peMem + shell.head->offsetImportTable);
    char* lpDllName = NULL;
    HMODULE hDll = NULL;
    PIMAGE_THUNK_DATA lpImportNameArray = NULL;
    PIMAGE_IMPORT_BY_NAME lpImportByName = NULL;
    PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL;
    FARPROC lpFuncAddress = NULL;
    DWORD i = 0;

    while (TRUE)
    {
        if (pImportTable->OriginalFirstThunk == 0)
            break;

        lpDllName = (char*)((DWORD)shell.peMem + pImportTable->Name);
        hDll = shell.loadLibraryA(lpDllName);
        if (hDll == NULL)
        {
            pImportTable++;
            continue;
        }


        i = 0;
        lpImportNameArray = (PIMAGE_THUNK_DATA)((DWORD_PTR)shell.peMem + pImportTable->OriginalFirstThunk);
        lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)((DWORD_PTR)shell.peMem + pImportTable->FirstThunk);
        while (TRUE)
        {
            if (lpImportNameArray[i].u1.AddressOfData == 0)
                break;


            lpImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)shell.peMem + lpImportNameArray[i].u1.AddressOfData);

            if (0x80000000 & lpImportNameArray[i].u1.Ordinal)
            {
                lpFuncAddress = shell.getProcAddress(hDll, (LPCSTR)(lpImportNameArray[i].u1.Ordinal & 0x0000FFFF));
            }
            else
            {
                lpFuncAddress = shell.getProcAddress(hDll, (LPCSTR)lpImportByName->Name);
            }
            lpImportFuncAddrArray[i].u1.Function = (DWORD)lpFuncAddress;
            i++;
        }

        pImportTable++;
    }
}
Ну и запускаем:
C:
void Execute(Shell shell)
{
    PDWORD_PTR main = (PDWORD_PTR)((DWORD_PTR)shell.peMem + (DWORD_PTR)shell.head->entryPoint);
    ((void(*)(void))main)();
}
Ну, собственно и всё.
Так же набросал простой консольный интерфейс:
pe2shellcode.exe MsgBox_go.exe -t, где:
- MsgBox_go.exe - x86 PE файл. В моём случае просто месседж бокс на го.
- -t - запустить шеллкод после сохранения в файл.
На выходе получаем 2 файла: pe_shell.bin - сам шеллкод, pe_shell.h - шеллкод в виде массива байт.
Все исходники в аттаче.
 

Вложения

  • pe2shellcode.zip
    2.1 МБ · Просмотры: 21
Пожалуйста, обратите внимание, что пользователь заблокирован
То чувство, когда заходишь в статью про шеллкод на голанге, чтобы посмотреть, как человек изъебнулся, чтобы получись из голанга шеллкод и видишь:
Понятное дело, компилятор го не даст нам этого сделать. Но компилятор имеет собирать PE/PE+ бинари, почему бы нам просто не запустить PE файл из шеллкода?
Фейспалм...
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Пишем шк на С, чтобы использовать его в Го ?
То чувство, когда заходишь в статью про шеллкод на голанге, чтобы посмотреть, как человек изъебнулся, чтобы получись из голанга шеллкод и видишь:

Фейспалм...
На самом го вряд ли выйдет шеллкод написать, а способом, описанным в теме, это сделать очень просто. Правда кому в здравом уме придёт в голову писать шеллкоды на го... я даже не знаю.
 


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