Внедрение кода x86 в процесс x86 из процесса x64

Я понимаю названиенемного запутанный, поэтому позвольте мне объяснить, что яЯ пытаюсь сделать:

Я только что закончил писать простой DLL-инжектор для проверки концепции IЯ пытаюсь написать. Программа делает снимок текущих процессов, перечисляет дерево процессов и внедряет DLL в свой прямой родительский процесс. Теперь в идеальных условиях это работает отлично: 32-битная версия инжектора может внедряться в 32-битные родительские процессы, а 64-битная версия инжектора может внедряться в 64-битные родительские процессы.

Что я'Теперь я собираюсь внедрить 32-битную DLL в 32-битный родительский процесс из инжектора x64. Как только эта DLL вставлена, я надеялся затем внедрить вызов одной из функций, экспортируемых внедренной DLL. Я'Я не уверен, однако, если этоНа самом деле это возможно сделать. (Я'мы уже собрали некоторый код, чтобы определить, является ли родительский процесс 32-битным или 64-битным процессом, так чтоэто не проблема)

Сейчас я'мы уже нашли некоторый код, который, кажется, делает первую частьвнедрение предварительно скомпилированного машинного кода в процесс, (По крайней мере, я думаю, чточто это?s) Обычно после ввода вызова LoadLibraryW яполучить адрес, возвращаемый этим вызовом, добавить относительное смещение к экспортируемой функции, которую я хочу вызвать, и ввести вызов этой функции. В этом случае, однако, я могузагрузить 32-битную библиотеку в мой 64-битный инжектор, чтобы я могнайти относительное смещение функции, используяGetProcAddress как обычно. Я справился с этой проблемой, выполнив следующее:

Так как я могунайти смещение функции 32-битной DLL, используя обычные средства, ям в настоящее время чтение файла в буфер, используя этот буфер для заполненияIMAGE_NT_HEADERS32 struct и перечисляя IMAGE_EXPORT_DIRECTORY, чтобы найти имена и относительные смещения всех экспортируемых функций.

Итак, на данный момент у меня есть следующее:

32-битная DLL загружена в 32-битный процессЗначение, эквивалентное funcAddr при запуске следующего кода в 32-разрядном процессе:

Код:

HMODULE hInjectedDLL = LoadLibrary("mydll.dll");
DWORD funcAddr = (DWORD)GetProcAddress(hInjectedDLL, "ExportedFunc") - (DWORD)hInjectedDLL;

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

Есть идеи?

(Также я'Я знаю, что я могу избавить себя от многих проблем, просто скомпилировав две версии инжектора и запустив одну из них, когда родительский процесс 'архитектура процессора нене совпадают. Я'хотя я стараюсь не идти по этому пути.)

редактировать: Понял, что это может помочь объяснить, что яЯ на самом деле пытаюсь достичь в этом доказательстве концепции.

Я экспериментировал с идеей. Мне нужно было разрешить выполнение дочернего процесса в текущей консоли без необходимости, чтобы исходный процесс ожидал завершения дочернего процесса. С тех пор'нет встроенного API для этого в консольном приложении, выОбычно они привязаны к дереву процессов, все ожидают завершения своего дочернего процесса. Чтобы облегчить эту функциональность, я хочу сделать следующее:

впрыскивание

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

Решение нашей функции

Как только DLL установлена, инжектор определяет этот адрес функции, экспортируемой DLL. (Обычно ясделать это, позвонивCreateRemoteThread сделать начальную инъекцию, а затем использоватьGetExitCodeThread в этом потоке, чтобы получить базовый адрес DLL в родительском процессе. Как только я это получу,Простая арифметика, чтобы найти адрес нашей экспортируемой функции, которую я затем могу использовать, чтобы ввести второй вызов этой функции.

Называя нашу функцию

Экспортируемая функция будет выглядеть примерно так:

BOOL RewriteHProcess (HANDLE hProcess)

Инжектор снова будет использовать CreateRemoteThread для вызова этой функции из контекста родительского процесса, а hProcess является дескриптором процесса инжектора. На стороне DLL, функция будет делать одну из двух вещей (яЯ не совсем уверен, возможна ли моя первая идея, учитывая ограничения безопасности доступа к памяти между потоками, поэтому я собрал вторую идею, на которой можно отступить, если первая нене получается.)

RewriteHProcess откроет ранее приостановленный поток для чтения и записи и использованияReadProcessMemory, он будет искать процесс "Память для РУКОВОДСТВА к нашему процессу инжектора. (Мы'делает предположение, что родительский процесс в настоящее время блокирует дальнейшее выполнение с помощьюWaitForSingleObject функция. Я знаю, что командная строка делает, по крайней мере, и чтоПока я фокусируюсь) Затем DLL вызывает внутреннюю функцию для создания желаемого дочернего процесса, закрывает старый дескриптор и перезаписывает память дескриптором на наш новый дочерний процесс. В этот момент он убирает все, что может, и возвращает. Затем инжектор выполнит любую оставшуюся очистку, в которой он нуждается, возобновит приостановленный поток, закроет дескрипторы процесса и потока и завершит работу, оставив родительский процесс для продолжения блокировки, пока он ожидает завершения нового дочернего процесса.

Если этот маршрут неВозможно, мой запасной вариант состоял в том, чтобы просто приостановить блокирующий поток из инжектора, создать новый дочерний процесс в внедренной DLL, очистить и выйти из инжектора и подождать в DLL, пока дочерний процесс не завершится. В этот момент DLL будет очищать, возобновлять приостановленный поток и выгружать себя. (Однако недостаток этого маршрута заключается в том, что код возврата, который родительский процесс возвращает из инжектора, может не совпадать с кодом возврата нашего целевого дочернего процесса)

 imreal08 нояб. 2012 г., 22:19
Я неНе думаю, что звонить прямо это возможно. Вы можете обойти это, используя какой-то IPC. Может быть, вы можете создать компонент COM и предоставить нужный интерфейс. Это, вероятно, проблематично, я нене знаю, насколько вы контролируете родительский процесс.
 Charles Grunwald09 нояб. 2012 г., 00:30
@ user1354557: Спасибо, этот пост, на который вы ссылались, очень помог. Исходя из этого, я нашел еще несколько статей и постов в блоге, которые дали мне несколько лучших идей для использования. Пара постов, казалось, намекала на возможность внедрения предварительно скомпилированного кода MASM64 в 64-битный процесс из 32-битного процесса, но я их просматривал, так что я могу ошибаться. В любом случае, еще раз спасибо.
 Charles Grunwald09 нояб. 2012 г., 00:27
@Nick: Да, я на самом деле думаю об использовании какого-то механизма IPC - мне просто нужно прочитать об ограничениях того, что вы можете сделать в функции DllMain. Я вспоминаю, что существуют некоторые ограничения, поэтому я обычно использую экспортированный маршрут вызова функции. Я'Тем не менее, я определенно возьму второй взгляд, поскольку я продвигаюсь немного дальше с этим.
 user135455709 нояб. 2012 г., 00:01
У вас есть несколько вариантов. Если известно, что родительский процесс имеет LoadLibraryW в своем каталоге импорта, вы можете сделать ReadProcessMemory для соответствующей записи IAT; это всегда будет по определимому адресу. Для чего-то клункого, вы можете создать вспомогательный 32-битный процесс, который возвращает адрес LoadLibraryW. Но лучше всего написать небольшую заглушку сборки, в которой будет размещено 32-разрядное ядро32. В Интернете есть несколько ресурсов, которые показывают вам, как это сделать, например,blog.harmonysecurity.com/2009_06_01_archive.html

Ответы на вопрос(2)

если кто-то найдет этот вопрос и все еще запутался после прочтения комментариев, вы можете найти (по общему признанию уродливый) доказательство концепцииВот, Предполагая, что вы уже знаете обычную стратегию внедрения DLL, здесь 'объяснение того, что вынужно будет сделать по-другому. (с соответствующим кодом):

Расположение нашего экспорта (injdll32_64.c)

Обычно мы могли бы пойти сLoadLibrary/GetProcAddress маршрут, но так как эти два модуля предназначены для разных процессоров, мы должны действовать по-разному. КакРеми Лебо предложил в своем ответемы могли бы сделать это полностью на стороне сборки. С моей точки зрения, однако, написание сборки, чтобы найти базовый адресkernel32.dllнайдите таблицу экспорта и найдитеLoadLibrary а такжеGetProcAddress казалось болью в заднице. Вместо этого я обработал это на стороне C, читая в каждой DLL, ищаIMAGE_EXPORT_DIRECTORY для необходимого экспорта и хранения их RVA 's для дальнейшего использования.

Вызов нашего экспорта по RVA (injdll32.c,x86.final.asm)

Для моих целей мне нужно было, чтобы внедренная DLL выполнялась в основном потоке целевого приложения. Для этого я приостановил основной поток целевого приложения, выделил память для моего предварительно собранного машинного кода, заполнил заполнители соответствующими экспортными RVA, изменил EIP целевого приложения вызовами наGetThreadContext/SetThreadContextи возобновил приостановленную нить.(Необязательно в этом порядке) К сожалению, я так и не удосужился написать механизм для освобождения памяти, которую мы VirtualAlloc 'редактировалось в нашем целевом приложении, но одним из решений для этого было бы внедрение механизма уведомления инжектора, когда целевой процесс возвращается к своему первоначальному EIP. В этот момент для инжектора было бы безопасно использовать VirtualFree выделенную память. (В качестве альтернативы, вы всегда можете найти подходящую пещеру кода и устранить даже беспокойство по поводу освобождения памяти)

Решение Вопроса

VirtualAllocEx() выделить блок исполняемой памяти внутри целевого процесса, а затем использоватьWriteProcessMemory() записывать машинные инструкции x86 или x64 в этот блок памяти по мере необходимости. Пусть эти инструкции позвонят,LoadLibrary()GetProcAddress()экспортируемая функция DLL по мере необходимости. Тогда используйтеCreateRemoteThread() выполнить блок памяти. Ваш инжектор не может вызвать экспортированную функцию DLL напрямую, если она выполняется в отдельном процессе. Экспортируемая функция должна быть загружена и вызвана в контексте целевого процесса. И не вычитайте возвращаемое значениеLoadLibrary() из возвращаемого значения.GetProcAddress()GetProcAddress() возвращает прямой указатель памяти на функцию, чтобы ее можно было вызывать напрямую.

Обновить: вариация этого заключается в том, чтобы поместить весь ваш введенный код в DLLs точка входа (или точка входа порождает поток для запуска кода), когда она вызывается сDLL_ATTACH_PROCESS причина. Таким образом нет необходимости экспортировать какие-либо функции из DLL. Тогда вы можете использоватьVirtualAllocEx() а такжеWriteProcessMemory() хранить DLLs путь в целевой процесс, а затем используйтеCreateRemoteThread() вызыватьLoadLibrary() непосредственно. Функции ядра всегда имеют один и тот же адрес памяти в разных процессах, поэтому процесс внедрения может вызватьGetProcAddress() в своем собственном адресном пространстве, чтобы получить адресLoadLibrary() а затем передать этот указатель наlpStartAddress параметрCreateRemoteThread(), Таким образом, вы неНе нужно беспокоиться о написании любого кода сборки x86 / x64.

Этот метод более подробно описан вРаздел 3 этой статьи:

Три способа внедрить ваш код в другой процесс

 Charles Grunwald22 сент. 2013 г., 05:22
DllMain / DLL_ATTACH_PROCESS идея довольно умная - я не имелРассмотрена возможность обработки распределения или разрешения функций на целевой стороне. Wouldn»т звонитGetProcAddress в контексте инъекции (64-битный процесс) дать вам адрес 64-битного ядра32 's LoadLibrary хотя? (Например, Win7SP1 имеет 64-битную LoadLibraryW в 0x776B6F80 и 32-битную LoadLibraryW в 0x76C14913)

Ваш ответ на вопрос