Пожалуйста, обратите внимание, что пользователь заблокирован
В общем-то захотелось попробовать написать какой-нибудь простой шеллкод на го. Понятное дело, компилятор го не даст нам этого сделать. Но компилятор имеет собирать PE/PE+ бинари, почему бы нам просто не запустить PE файл из шеллкода?
Вот тут есть реализации аналогичных проектов:
github.com
github.com
Подсмотрел кое-что там, и написал свой шеллкод лоадера PE, вес чуть больше 1 КБ.
Общая структура шеллкода на выходе |шеллкод PE-лоадера (1062 байта)|сигнатура+заголовок (36 байт)|байты самого PE (без заголовков)|, где:
- Сигнатура - простой набор байт:
- Заголовок - подобие NT-заголовка на минималках:
Как всё это формируется я думаю вы уже поняли, давайте перейдём к самому шелл-коду.
Для начала нам нужно спарсить наш кастомный заголовок. Он хранится после нашей сигнатуры. Нам нужно узнать где по какому адресу мы сейчас сидим, отыскать заголовок:
Так же ищем адреса нужных нам winapi (LoadLibraryA, GetProcAddres, VirtualAlloc), выделяем исполняемую память под PE.
Копируем секции в выделенную память:
Обрабатываем релоки:
Обрабатываем импорт:
Ну и запускаем:
Ну, собственно и всё.
Так же набросал простой консольный интерфейс:
- MsgBox_go.exe - x86 PE файл. В моём случае просто месседж бокс на го.
- -t - запустить шеллкод после сохранения в файл.
На выходе получаем 2 файла: pe_shell.bin - сам шеллкод, pe_shell.h - шеллкод в виде массива байт.
Все исходники в аттаче.
Вот тут есть реализации аналогичных проектов:
GitHub - hasherezade/pe_to_shellcode: Converts PE into a shellcode
Converts PE into a shellcode. Contribute to hasherezade/pe_to_shellcode development by creating an account on GitHub.
GitHub - Ly0k0/PE2Shellcode: A gadget for converting PE files to shellcode.
A gadget for converting PE files to shellcode. Contribute to Ly0k0/PE2Shellcode development by creating an account on GitHub.
Общая структура шеллкода на выходе |шеллкод 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;
}
Копируем секции в выделенную память:
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 - шеллкод в виде массива байт.
Все исходники в аттаче.