Делаем собственный инжектор

Давным-давно, во времена мифов и легенд, когда древние Боги были мстительны и жестоки и обрушивали на программистов все новые и новые проклятия... На хабре была опубликована статья про сплайсинг.

В качестве примера в статье был приведен довольно масштабный код, который воспринимался не особо легко. Захотелось разобраться в процессе инжекта, а также написать более простой и менее громоздкий код.
Вкратце, инжектинг - это подгрузка нашей библиотеки в сторонний процесс, а сплайсинг - перехват какой-либо функции (мы перехватывали WinAPI) и модификация её работы средствами этой самой библиотеки.
Пример будет состоять из двух частей: 1 - библиотека, 2 - инжектор, который будет внедрять библиотеку в целевой процесс. Библиотеку будем писать на masm, что позволит в разы сократить объемы кода, а инжектор - на Си.

Начнем с кода библиотеки [last updated on 15 aug 2010]. Что она делает? При подгрузке она заменяет первые 5 байт функции WinAPI, которую мы хотим перехватить, на JUMP на наш перехватчик. Мы можем это сделать в большинстве случаев, когда функция имеет пролог (то есть 100%, если функция принимает больше нуля аргументов, в этом случае в начале ее будут инструкции mov edi,edi; push ebp; mov ebp;esp, или же если функция имеет локальные переменные). Иногда функция не имеет пролога, но мы все равно можем осуществить перехват, если в начале функции имеются несущественные команды, например, NOP'ы (такое было обнаружено в функции GetTickCount на Windows Vista x64). Таких функций мало, судя по всему. Я приведу пример перехвата GetTickCount, но далеко не факт, что он сработает на вашей системе. Скажем, в XP SP3 кучи NOP'ов в начале тела функции нет. Всегда смотрите, как начинается функция без аргументов в системных библиотеках, прежде чем осуществлять ее перехват. У меня GetTickCount начиналась так:

Слишком короткие функции без пролога перехватить так просто не удастся (например, GetCurrentProcess или GetCommandLineA), потому что первые 5 байт - это уже их тело. Их перехват можно осуществить, например, изменив адреса в таблице импортов, если они там есть, но этот материал в данной статье не затронут. Что ж, теперь к коду. Я решил написать несколько макросов, чтобы удобно ставить и убирать хуки, а также вызывать оригинал функции.

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

Теперь рассмотрим код самого инжектора:

Код основан на этой статье с RSDN.

Исходный код dll'ки и инжектора одним архивом: скачать

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

С тех самых времен прошло много столетий... Боги были усмирены человеком, но теперь мы имеем новые проблемы, которые порой страшнее гнева всевышних - это, конечно, бурление говн недовольных разработчиков, считавших самих себя Богами программирования под всякие социальные ресурсы нынешнего времени, но свергнутых также, как когда-то и настоящие Боги.

В общем, делайте инжекты, ломайте чужой софт. Этот мир интересней, чем вам кажется. (с)

Делаем собственный инжектор: 21 комментарий

  1. нифига не понял с первого раза) надо будет поучиться) эт значит что ты не будешь больше делать инжекты?

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

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

  3. Вот моя "универсальная реализация"

    proc SetHook ModuleName,OldFuncAdr,NewFuncAdr,Old
    locals
    BaseMap dd 0
    fApi dd 0
    OldProtect dd ?
    JmpGate dd ?
    endl
    pushad
    mov edi,[OldFuncAdr]
    mov edi,[edi]
    mov [fApi],edi
    push edi
    pop esi
    xor ecx,ecx

    @@:
    cmp ecx,5
    jnb @1
    call Catchy32 ;Дизассемблер длин
    cmp eax,0
    jnz @2
    jmp @3
    @2:
    add esi,eax
    add ecx,eax
    jmp @b
    @1:
    push ecx
    mov ebx,ecx
    add ebx,5
    invoke VirtualAlloc,0,ebx,MEM_COMMIT or MEM_RESERVE,PAGE_EXECUTE_READWRITE
    test eax,eax
    je @3
    mov [JmpGate],eax
    cmp [ModuleName],0
    je @5
    cmp byte [edi],0e9h
    je @f
    cmp byte [edi],0e8h
    jne @5
    @@:
    invoke GetModuleHandle,[ModuleName]
    cmp eax,0
    je @3
    mov ecx,edi
    sub ecx,eax
    lea eax,[fApi]
    lea edx,[BaseMap]
    stdcall MapDll,[ModuleName],ecx,eax,edx
    cmp [BaseMap],0
    je @3
    @5:
    pop ecx
    mov eax,[JmpGate]
    stdcall MemSet,eax,ebx,90h
    stdcall MemCpy,eax,[fApi],ecx
    mov byte [eax+ecx],0e9h
    sub esi,eax
    sub esi,ecx
    sub esi,5
    mov [eax+ecx+1],esi
    lea ebx,[OldProtect]
    invoke VirtualProtect,edi,5,PAGE_EXECUTE_READWRITE,ebx
    mov eax,[NewFuncAdr]
    sub eax,edi
    sub eax,5
    mov byte [edi],0e9h
    mov [edi+1],eax
    invoke VirtualProtect,edi,5,[OldProtect],ebx
    mov eax,[Old]
    mov ecx,[JmpGate]
    mov [eax],ecx
    cmp [BaseMap],0
    je @3

    invoke UnmapViewOfFile,[BaseMap]
    @3:
    popad
    ret
    endp

  4. А у меня работает только если использовать VirtualProtectEx (на Windows7). В остальном все прекрасно. Огромное спасибо за отличный сайт.

  5. Я правильно понял, что в этом фрагменте кода загружается два байта,
    то есть адрес перехваченной функции в регистр сx,где eax адресс записанной функции,
    [eax+2],[eax+3] задают смещение и адресс искомой функции подменяется на адресс 0e9h в строке mov [eax].opcd, 0e9h-?
    А точнее подменяют адресс функции имеющей пролог-?

    IF have_prologue EQ 0
    mov cx, word ptr [eax]
    mov @CatStr(&func, _prologue1), cx
    mov cl, byte ptr [eax+2]
    mov @CatStr(&func, _prologue2), cl
    mov cx, word ptr [eax+3]
    mov @CatStr(&func, _prologue3), cx
    ENDIF

    assume eax: ptr JUMPNEAR
    mov [eax].opcd, 0e9h
    mov ecx, offset &hook_label
    sub ecx,@CatStr(&func, _hook)
    sub ecx,5
    mov [eax].reladdr,ecx
    assume eax:nothing

  6. Если я правильно понял:
    Вариант один
    1. Открываем процес, получаем привилегии
    2. В память пишем dll
    3. В память вставляем прыжок на dll
    Вариант два
    Такой метод используется в enbsiries(графическая настройка к играм)
    Подменяем dll на свою...

    1. Первый вариант не так.
      1. Открываем процесс.
      2. В память пишем код, который вызовет LoadLibrary (в качестве аргумента будет путь к нашей библиотеке) и завершит поток.
      3. Создаем новый поток в целевом процессе (CreateRemoteThread), в качестве точки старта указываем адрес, где расположен вышеупомянутый код, ждем завершения потока, закрываем хендл.

  7. Т.е.:
    1. Узнать адрес оригинальной функции
    2. Скопировать оригинальные аргументы функции
    3. Поставить переход на нашу функцию
    4. Выполнить необходимые действия
    (5. Исполнить функцию с оригинальными аргументами)
    6. ...
    Так или не так?

  8. Kaimi, а почему инжект не через создание удаленного потока через CreateRemoteThread по адресу LoadLibraryA ( полученному из GetProcAddress ), это же проще?

    STARTUPINFO *si = reinterpret_cast(VirtualAlloc(0, sizeof(si), MEM_COMMIT, PAGE_READWRITE));
    PROCESS_INFORMATION *pi = reinterpret_cast(VirtualAlloc(0, sizeof(pi), MEM_COMMIT, PAGE_READWRITE));

    si->cb = sizeof(si);

    CreateProcess(0, SystemPath, 0, 0, 0, CREATE_SUSPENDED | CREATE_NO_WINDOW, 0, 0, si, pi);

    HANDLE RemoteProc = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pi->dwProcessId);
    LPVOID LoadLibAddress = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
    LPVOID MemAlloc = VirtualAllocEx(RemoteProc, 0, strlen(DLL_NAME) + 1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    WriteProcessMemory(RemoteProc, (LPVOID)MemAlloc, DLL_NAME, strlen(DLL_NAME) + 1, NULL);
    CreateRemoteThread(RemoteProc, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibAddress, (LPVOID)MemAlloc, NULL, NULL);
    CloseHandle(RemoteProc);

  9. Скажите мне как ето все использовать, точнее как сделать чтоб можно было использовать программу для внедрения dll в другую программу, и как сделать dll)
    Извините за нубские запроси!

  10. Уведомление: Тащим пароли от Steam

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *