![]() |
Написание шелкода на СИ About Хотел бы поделиться с общественностью, методом написания шелкодов и прочих инжектируемых функций на Си. В части использую Visual Studio. Статья рассчитана на людей, кто уже умеет настраивать проект, писать без использования CRT, имеет общее представление о работе с памятью. Желательно быть ознакомленным со статьёй об уменьшении размера программы (https://forum.antichat.net/thread270620.html ). Т.к. там затрагивается вопрос об отказе от CRT. Сразу у многих возникнет вопрос: Почему именно Си? Ведь Трукодеры пишут трушелкоды на ассемблере. На данный вопрос можно ответить следующим образом:
Как у любой монеты есть две стороны, так и у этого подхода есть свои плюсы и минусы. Плюсы:
Минусы:
Theory Перед началом разработки шелкода, требуется разобраться, что он будет делать и каким образом выполняться. Фактически все варианты можно сгруппировать на два больших типа:
В частности отличия одного типа от другого в том, что в первом случае требуется самостоятельно определить свою базу (местонахождение кода), затем найти адрес системных библиотек (в частности kernel32.dll она же kernelbase.dll для Win 7). Во втором случаем, мы может сразу передать структуру через параметры, где уже будут занесены адреса нужных нам функций, в частности LoadLibrary и GetProcAddress. Первый тип довольно специфичен, редко используется в повседневной деятельности и имеет как таковые минимальные предназначения (скачать и запустить или ему подобное), да и к тому же уже существует большое количество реализация под все случаи жизни. Поэтому разбирать более подробно будем второй тип, так как он применяется более часто и используется не только в плохих намереньях. Notes При разработке шелкода надо руководствоваться следующими принципами и концепциями мировоздания:
EntryPoint Как бы всё красиво не выглядело бы, мы всё равно рано или поздно нарвёмся на трудности, связанные с особенностями компилятора и языка:
Подготавливаем проект:
Step 1 Шелкод может выглядеть по-разному, в зависимости от типа, но основа следующая: Код:
DWORD __stdcall ShellCode_Start(SHELLCODE_PARAM* Param);
Пример структуры: Код:
#pragma pack(push, 1) // убираем выравнение, чтобы не было глюковУзнать размер шелкода довольно проблематично на первый взгляд. Т.к. он находится где-то в файле. На деле мы можем узнать его, относительно других функций. К примеру, разместив код следующим образом: Код:
DWORD __stdcall ShellCode_Start(SHELLCODE_PARAM* Param)Код:
int Size = (int)( (ULONG_PTR) ShellCode_End – (ULONG_PTR) ShellCode_Start);Но, не всё так хорошо, как хотелось бы. На деле мы видим следующее:
Глюк Release версии заключается в том, что компилятор при оптимизации сам решает что, как и куда поставить в файле. Поэтому последовательность функций в исполняемом файле он сохранять не собирается. Отключать оптимизацию тоже не хочется, из-за того что размер увеличится. Step 3 Не всё так плачевно как кажется и оптимизацию всё же можно использовать. Microsoft позаботилась о задании порядка следования функций. При линковке можно задать последовательность функции через /ORDER, но это не всегда удобно. Есть другой вариант. Странным образом недокументированный MS (но при этом они его юзают). Вернее он документирован, но опущено важное для нас свойство. Microsoft позволяет задать для каждой функции то, в какой секции она будет находится. Но при этом они ничего не говорят про возможность установки последовательности функций в исполняемом файле. Выглядит это следующим образом: Код:
// код будет в .text секции, но будет иметь метку aaaStep 4 По началу может возникнуть сложность с сохранением шелкода. Но на деле нет ничего сложного.
Вот и всё. Если требуется преобразовать в HEX или еще как нибудь, то можно Сделать так: Код:
BYTE* p = (BYTE*) ShellCode_Start;C учетом вышеописанного получается следующий шаблон: Код:
typedef LPVOID (__stdcall *VIRTUAL_ALLOC)(LPVOID, SIZE_T, DWORD, DWORD);Важный момент: При инициализации структруры, адрес функций заполняются непостредственно через GetProcAddress. Конструкции вида Param.fGetProcAddress = (GET_PROC_ADDRESS)GetProcAddress; Или Param.fGetProcAddress = (GET_PROC_ADDRESS)&GetProcAddress; Недопустимы, из-за того, что будет ссылка не на саму функцию, а на заглушку в таблице импорта. ExitProcess Вот собственно и всё, что хотел донести до вас. Все вопросы постите в комментах, по возможности отвечу. Удачи в начинаниях! (С) SLESH 2011 |
Большое спасибо за статью.
Не совсем понятно, для чего здесь: Цитата:
Инициализация строк рулит ) Выглядит жестко, но эффективно. Еще как вариант, чтобы узнать длину функции можно от адреса следующей команды, которая идет после ret, отнять адрес начала функции. Но это более сложный вариант, так как нужно подключать дизассемблер длин. |
молодец.надо будет детально изучить.блин пора си учить.)
|
Мне непонятно - разве компилятор в таком шеллкоде не натыкивает VA ?
|
2 Chrome~ OUT IN и прочие это фишки придуманные MS для своего компилятора. по факту они ничего не значат и можно их оформить как
#define IN #define OUT Это сделано для того чтобы код был более понятнее, т.е. для отображения что делается с переменной - извлекаются данные или записываются. Очень актуально для работы с памятью. 2 _nic в пределах одно функции нет никаких VA. а именно 1) все переменные в стеке, по этому к ним обращения идут типа [esp + offset] и [ebp + offset] 2) вызов доп функций может привести к появлению VA, но у нас все функции становятся подставляются как inline по этому по факту их нет. 3) адреса апишек у нас хранятся также или локально или в структуре, по этому опять же все через стек. если есть сомнения, то при компиляции поставь опцию, чтобы создавался asm файл. И в нем можно всё хорошо пронаблюдать. |
Если все запихать в main то VA небудет? И секцию кода из получившейся экзешки можно будет как шеллкод использовать?При этом не жертвую оптимизациями?
|
Ну можно и так. только не забывай что main - это crt и надо преензнать точку входа.
Можно еще проще, каждую функцию в нужном порядка засунуть в определенную секцию (не .text) чтобы от туда уже выдрать. Хотя данный подход более удобен, потому что на выходе (после исполнения файла) можно с легкостью получить код в нужном формате |
Всех с наступающим!
по теме: обязательно если это точка входа в Ваш шеллкод и если она принимает параметры пишите с модификатором __declspec(dllexport) вот так Цитата:
стандартный сишный порядок передачи аргументов или на __stdcall а при компиляции шеллкода в режиме агрессивной оптимизации точка входа может сменить соглашение с __cdecl или __stdcall на __usercall что значит компилятор в целях оптимизации может как угодно начать передавать ей аргументы я на это нарывался как то а когда мы пишем __declspec(dllexport) то создаем некий контракт на соглащение о вызове потому что предпологаеться что для этой функции в будущем будут генерировать вывоз по четко определенному сишному или __stdcall соглашению исходя из прототипа и компилятор не имеет права искажать вызов в целях оптимизации Цитата:
предопределенным адресам только когда вызывает API через IAT или когда явно указатель на функцию передаеш например в функцию CreateThread это не страшно надо просто прибавлять дельту к такому указателю например CreateThread(ThreadEntry + Delta) и все все остальные вызовы например шеллкод состоит из нескольких функций дергаются по относительным адресам например call (call_me - ($ + 5)) или call (0 - ($ + 5 - call_me)) (просто две разные формулы вычисления одного и того же) или по абсолютным но вычисленным во время выполнения |
в си несильно селён...сижу разбираюсь...
не вкурил что за функция Addr = fVirtualAlloc(NULL.... ) что она делает ? |
Цитата:
Цитата:
|
| Время: 13:31 |