R0 CREW

Malware Analysis Tutorial 16: Return Oriented Programming (Return to LIBC) Attack (Перевод: coldfire)

Перейти к содержанию

Цели урока:

  1. Понимание техники обратно ориетнированого возврата [к LibC]
  2. Анализ полезной нагрузки данных LibC
  3. Попрактиковаться в анализе параметров вызываемой функции и стека

1. Введение

В этом уроке будет показана атака называемая Возврат к Libc (Return to LibC). Идея заключается в том, что бы составить цепочку системных вызовов путем установки содержимого стека таким образом, что бы целевой процесс, когда делает возврат с одного системного вызова переходил на следующий системный вызов, установленный в стеке. Наиболее общая идея атаки Возврат к LibC заключается в активном использование обратно ориентированного программирования [1], где любая произвольная программа может быть выполнена путем установки содержимого стека.

Обычно, атака возврат к LibC (Return to LibC) применима к атаке переполнение буфера, таким образом, что она может обойти защиту ОС, которая устанавливает стек, как не исполняемый(отметим, что она не внедряет шел код, но использует связку параметров функции и адресов возврата).

В этом уроке, мы покажем как автор Max++ применяет атаку Возврат к Libc (Return to LibC). Вместо установки секции кода потока, автор напрямую реализует желаемую программную логику, как часть содержимого стека, что сохраняет позволяет избежать много неприятностей по внедрение нового потока в запущенный процесс [смотри информацию про это в уроках 14 и 15].

Мы проанализируем код по адресу 0x3C1390 в этом уроке.

2. Подготовка

(0) Запустите WinXP в ОТЛАДОЧНОМ режиме. Теперь на вашей гостевой системе, запустите консоль и перейдите по пути “c:\Program Files\Debugging Tools for Windows (x86)”, где установлен WinDbg. Наберите “windbg -b -k com:pipe,port=\.\pipe\com_12” (проверьте, что номер com порта в вашем образе Vbox установлен). Когда WinDbg проициализируется, наберите ‘g’(go) дважды, что бы продолжить его работу.

(1) Теперь запустите IMM в образе WinXP, удалите все аппаратные и программные точки останова (см. View->Breakpoints и View->Hardware Breakpoints).

(2) Перейдите по адресу 0x4012DC и установите аппаратную точку останова ( почему не программную? Потому та область памяти будет перезаписана и заново извлечена, и поэтому программная точка останова будет удалена). Обратите особое внимание, что когда вы попадете на адрес 0x4012DC, сделайте правый клик и установите аппаратную точку останова. (сейчас вы там увидите «мусор»)

(3) Нажмите F9 несколько раз и вы окажетесь на адресе 0x4012DC. Вы остановитесь на нескольких точках останова перед тем как оказаться на адресе 0x4012DC. Если вы обратили внимание, промежуточные точки останова возникают из-за трюка с int 2d (пояснения смотри в уроке 3 — 5). Просто игнорируйте их и продолжайте(используя F9) пока не прерветесь на адресе 0x4012DC.

Рис. 1 показывает код, который вы должны увидеть. Как вы можете видеть, он идет сразу перед вызоввом RtlAddVectoredException, где аппаратная точка останова устанавливается, для того, что бы прервать вызов функции LdrLoadDll(см. Урок 11). На данный момент, код по адресу 0x3C24FB еще не извлечен. Если вы перейдете по адресу 0x3C24FB сейчас, то IMM будет жаловаться, что этот адрес не доступен.

Рис. 1. Код по адресу 0x4012DC

(4) Теперь прокрутите вниз около 2 страниц и установите ПРОГРАММНУЮ ТОЧКУ ОСТАНОВА на адресе 0x401417. Он находится сразу после вызова LdrLoadDll(“lz32.dll”), где Мах++ заканчивает загрузку lz32.dll. Потом нажмите SHIFT+F9 несколько раз пока не достигните 0x401417 ( вы дважды прерветесь на адресе 0x7C90D500, который находится где-то внутри ntdll.zwMapViewSection, которая вызывается LdrLoadDll).

Рис. 2. Код по адресу 0x401407

(5) Теперь мы установим точку останова на адрес 0x3C1390. Перейдите по адресу 0x3C1390 и установите ПРОГРАММНУЮ ТОЧКУ ОСТАНОВА. Нажмите SHIFT+F9, что бы оказаться на адресе 0x3C1390. ( Вы можете увидеть предупреждение, что этот адрес находится вне диапазона сегмента кода, просто проигнорируйте его).

(Рис. 3 показывает код, который вы должны увидеть по адресу 0x3C1390. Первая инструкция должна быть PUSH SS:[EBP-8], следующая за ней — вызов функции: CALL DWORD PTR DS: [3D1128] (см. рис. 3). Эта функция вызывает RtlCreateUserThread. Мы начнем анализ тут!

Рис. 3. Код, который начинается по адресу 0x003C1390

3. Создание целевого потока

Функция, которую мы будем анализировать, располагается по адресу 0x003C1393(см. Рис. 3). Функция RtlCreateUserThread является не документированной функицей Windows. Немного погуглив, мы сможем найти определение этой функции как показано ниже (взято с [2]):

RtlCreateUserThread(
     IN HANDLE [B][U]ProcessHandle[/U][/B],
     IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
     IN BOOLEAN CreateSuspended,
     IN ULONG StackZeroBits,
     IN OUT PULONG StackReserved,
     IN OUT PULONG StackCommit,
     IN PVOID [B][U]StartAddress[/U][/B],
     IN PVOID StartParameter OPTIONAL,
     OUT PHANDLE ThreadHandle,
     OUT PCLIENT_ID ClientID
);

Несколько параметров могут нас заинтересовать:

  1. ProcessHandle – дескриптор процесса для создания в нем потока
  2. StartAddress – когда поток создан, определяет адрес его выполнения

Рис. 4 показывает содержимое стека, когда мы попадает на инструкцию CALL по адресу 0x3C1390.

Рис. 4. Содержимое стека для вызова Call RtlCreateUserThread

Вы можете сделать заключение, что ProcessHandle равен 0х44. Если вы следовали уроку 15, вы должны вспомнить, что 0х44 является дескриптором целевого процесса(например, smss.exe). Стартовый адрес равен 0x7C96149B (обратите внимание! IMM уже нашло его. Это адрес входа в RtlExitUserThread!!!) Тут возникает смешная ситуация, поскольку автор пытается внедриться в поток, который сразу себя заканчивается. Но у него\ее есть дополнительный трюк. Давайте перейдем к следующей функции.

4. Получение контекста потока

Следующий системный вызов, который мы будем анализировать, располагается по адресу 0x3C13B5. Это вызов zwGetContext (смотри рис. 5). Простой поиск zwGetContext или ntGetContext показывает нам прототип функции zwGetContext. Она принимает два параметра (1) дескриптор threadHandle, и (2) PCONTEXT pContext. Очевидно, что первый параметр — дескриптор потока, который мы исследуем, а второй ( т. е. Pcontext) — указатель, который сохраняет точку входа структуры CONTEXT. Если вы посмотрите на рис. 5, вы должно быть заметили, что pContex = 0x0012D288.

Рис. 5. Вызов zwGetContext

Теперь мы заинтересованы в просмотре контекстов структуры CONTEXT по адресу 0x0012D288. Конечно, вы можете использовать IMM для анализа их прямо в дампе памяти. Но есть по лучше способ. Исользуя WinDbg можно показать структуры данных ядра. Для этих целей, запустите WinDbg in the VBox Windows imageFile-> Attack Process -> Max++, а затем нажмите и выберите, что бы запустить Noninvasively. (как показано на рис. 6).

Рис. 6. Запуск WinDebug в режиме Noninvasive

Теперь наберите “dt _CONTEXT -r2 0x0012D288”, в следствии чего мы получим дамп структуры _CONTEXT. Отметим, что _CONTEXT используется ОС для получения следа запущенного контекста(главным образом значения регистров) потока, когда контекст переключается. Как показано на рис. 7, начальный контекст нового потока в smss.exe обладает несколькими интересными регистрами:

  1. Первый, мы можем видеть все аппаратные точки останова, которые были очищены.
  2. Все сегментные регистры GS/FS/ES/DS еще не установлены.
  3. EIP равно 0x7c96149b. ( Если вы найдете этого в IMM->View->Modules->ntdll->Names. Вы сможете найти адрес входа функции RtlExitUserThread, сравнивания с выше описанным).
  4. ESP равен 0x3fff8 (обратите внимание: это адрес адресного пространства smss.exe, не Мах++.ехе).

Рис. 7. Начальный дамп _CONTEXT по адресу 0x0012D288

5. Установка стека smssexe

Далее, Мах++ устанавливает стек для нового потока должным образом. Давайте посмотрим на рис. 8. Начиная с 0x003C13C3 до 0x003C1455, Мах++ готовит начальный контекст в своем собственном стеке(вы можете видеть много операций с регистром EBP). Далее по адресу 0x3C145F, вызывается наиболее важная функция zwWriteVirtualMemory!

Рис. 8. Установка целевого стека нового потока smss.exe’a

С документации zwWriteVirtualMemory или ntWriteVirtualMemory, мы можем получить, что она принимает 4 параметра: (1) дескриптор целевого процесса (мы можем проверить, что это дескриптор smss.exe), (2) целевой адрес (в нашем случаи он равен 0x3FF000!) (3) начальный адрес (в адресном пространстве Мах++): 0x0012D580, (4) количество байта для записи ( 0х4с)!

Рис. 9. Контекст для внедрения

Давайте теперь посмотри на 0x4c байтов для записи в целевой процесс smss.exe. Они показаны на рис. 9. Отметим, что данные начинаются по адресу 0x0012D580 и заканчиваются по адресу 0x0012D5CC, то есть, первое слово = 0x7C90CFD0 ( т. е. Первое слово первой строчки Hex Dumpа на рис. 9, учитывайте порядок байтов) в первой строчке и последнее слово равно 0xFFFFFFFF (первое слово в последней строчке). Отметим, что когда копируется целевое адресное пространство smss.exe, начиная с 0x3FF000, мы должны получить 0x7C90CFD0 и по адресу 0x3FF048 мы должны получить FFFFFFFF.

6. Установка контекста потока smss.exe

Как показано на рис. 8, последний трюк — вызов функции zwSetContextThread. Используя такой же трюк, как в разделе 4, мы можем легко найти описание всех параметров zwSetContextThread. Она изменяет значение регистра ESP на 0x3FF000, EBP на 0x0, EIP - 0x7C90DF30 (using IMM->View->Modules->Names), мы можем найти, что это zwWaitForSingleObject!

7. Анализ ататки Return to LibC

Теперь рассмотрим самое интересное. Первая инструкция, которая будет выполнена новым потоком — точка входа zwWaitForSingleObject (handle, alertable, timeout). Но где находятся параметры? Если вы читали про соглашение о передаче параметров этих ntdll функции, то вы можете сказать, что когда выполнится первая инструкция NTDLL функции, мы будем иметь:

   ESP --> адрес возврата
   ESP+4:  1й параметр
   ESP+8:  2й параметр
  ESP+c:  3й параметр

Таким образом, мы получаем? Адрес возврата (смотри рис.9) равен 0x7C90CFDO (zwClose). Параметры для функции zwWaitForSingleObject:

handle: 0х54 ( что за дескриптор? Это задание для тебя)
alertable: 0
timeOut: 0

Когда zwWaitForSingleObject заканчивается, произойдет прыжок на функцию zwClose. Параметр для zwClose — 0x54 (тот же дескриптор). Когда zwClose завершится, следующая функция - 0x0x7C90D1F0 (zwDelayExecution)!

Задание: Закончите анализ атаки ReturnToLibC, используемую Мах++'ом и проанализируйте, что она пытается сделать. Подсказка: если вы не можете найти документация для zwXYZ(), вы можете поискать ntXYZ().

© Translated by coldfire from r0 Crew

Ссылки

[1] E. Buchanan, R. Roemer, and S. Savage, “Return-Oriented Programming: Exploits Without Code Injection”, Blackhat USA 2008.
[2] “Undocumented Functions of NTDLL”

UP