Веселая отладка или как отправить процесс в sleep()

Веселая отладка или как отправить процесс в sleep()

Недавно я пытался проэксплуатировать уязвимость (CVE-2008-0532, http://www.securitylab.ru/vulnerability/348518.php, от FX) и у меня возникли проблемы с отладкой исполняемого CGI файла в месте расположения уязвимой функции

Автор: Corelan Team (Lincoln)

Недавно я пытался проэксплуатировать уязвимость (CVE-2008-0532, http://www.securitylab.ru/vulnerability/348518.php , от FX) и у меня возникли проблемы с отладкой исполняемого CGI файла в месте расположения уязвимой функции.

Вот в чем заключалась проблема: исполняемый CGI файл CSUserCGI.exe является дочерним процессом IIS, и может возникнуть только при вызове пользователя. Исполняемый сценарий, выполнив свою задачу, быстро закрывается… и мы не успеваем присоединиться отладчиком. И как же нам осуществить отладку? Поможет ли нам JIT_отладка (Just In Time)?

При вызове CGI сценария через HTTP, открытие и закрытие осуществляется очень быстро.

Пробуем:

Не вышло! Как же мне осуществить отладку? Я был уверен, что способ существовать должен.

Вызов у процесса функции sleep()

В то время sinn3r, один из участников нашей группы Corelan, закончил работу с HP NNM модулями, в ходе которой он столкнулся с несколькими схожими проблемами. Идея заключалась в том, чтобы поместить дочерний процесс в спящий режим и выиграть время для присоединения отладчика. Для этого был использован следующий код:

// (pseudo code):
>while (IsDebuggerPresent == false) {
sleep(1);
}
// Repair the prologue of the entry point you hijacked,

Я открыл выполняемый файл в Immunity и выбрал подходящую для перехвата функцию, (0×00401010). Мне хотелось пропустить часть изначальных вызовов ядра и среды, переместившись сразу к main().

Далее мне требовалось найти область в .text, которая не использовалась исполняемым файлом, и не повредила бы любую другую функцию, а также не помешала бы работе исполняемого файла. Моя цель заключалась в том, чтобы использовать существующую инструкцию вызова (в данном случае вызова 0×0040101) и изменить значение смещения вызова, для того, чтобы осуществить переход в область, в которой будет размещена моя процедура.

Я обнаружил отличное место в 0×00415362, я отредактирую вызов в 0x00414CD6 таким образом, чтобы он указывал на эту область.

Сейчас мне необходимо найти области kernel32.Sleep и kernel32.IsDebuggerPresent.  Поскольку это всего лишь патч на локальном компьютере, мне не требуется осуществлять поиск общих функций, и можно просто найти эти области в Immunity(Executable->Names). На моей системе Windows 2003 SP2 необходимыми адресами являются 0x77e424de и 0x77e5da00. На вашей системе они скорее всего будут другими.

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

Вначале я изменил смещение вызова (чтобы осуществить переход к моему коду, который будет размещен по адресу 0×00415362).

00414CD6 E8 87060000 CALL CSUserCG.00415362 ; jmp to custom routine

Инструкцию я разместил в 0×00415362

00415362 6A 01 PUSH 1
00415364 E8 75D1A277 CALL kernel32.Sleep
00415369 E8 9286A477 CALL kernel32.IsDebuggerPresent
0041536E 83F8 01 CMP EAX,1
00415371 ^75 EF JNZ SHORT CSuserCG.00415362
00415373 CC INT3
00415374 83C4 04 ADD ESP,4
00415377 E8 94BCFEFF CALL CSuserCG.00401010 ;go back
0041537C ^E9 5AF9FFFF JMP CSuserCG.00414CDB

В Immunity мы теперь можем нажать правой кнопкой мыши и сохранить изменения под новым именем. Я решил назвать новый файл NEWCSUserCGI.exe и поместить его в наш каталог CGI сценариев (C:\Inetpub\wwwroot\securecgi-bin).

Затем я переименовал исходный исполняемый файл в OLDCSUserCGI.exe и изменил NEWCSUserCGI.exe на исходное название файла CSUserCGI.exe.

На этот раз я запустил CGI сценарий с PoC кодом в URL и вуаля, сценарий продолжает работать!

Теперь пора проверить работоспособность публично доступного эксплоита, который должен вызывать переполнение буфера. В уведомлении FX говорится, что с помощью достаточно длинной строки после Logout+ можно вызвать переполнение буфера и получить контроль над EIP.

Уязвимая функция является подрограммой в функции, к которой мы прикрепились (0×00401010). Вначале мы видим, что создался буфер фиксированного размера 0×60 для аргумента Logout.

Происходит вызов msvcrt.strtok и поиск первой строки, которая оканчивается на «.»

Затем строка копируется в стек и вызывается переполнение буфера.

Код для вызова подобного переполнения буфера в стеке можно найти по адресу:

https://github.com/rapid7/metasploit-framework/blob/unstable/unstable-modules/exploits/untested/cisco_acs_ucp.rb

Автоматизация процесса эксплуатации

Я подумал, что у меня появилась отличная возможность создать сценарий, который автоматизировал бы процесс, или разработать его альтернативу. В конце рабочей недели я представил данную концепцию другим участникам своей группы и отправился домой с готовностью посвятить реализации этой идеи свои выходные. Как и следовало ожидать, я совсем забыл, что что у corelanc0d3r (автор mona.py) есть 5000 строк «кунг-фу к Python» для Immunity, так что он выполнил эту задачу раньше, чем я успел добраться домой, и все заслуги за указанный ниже сценарий принадлежат ему.

Данный автоматизированный сценарий без использования вызовов API kernel32 поместит приложение в режим сна и позволит прикрепить отладчик.

# binary patcher
# will inject routine to make the binary hang
# so you can attach to it with a debugger
#
# corelanc0d3r
# (c) 2012 - www.corelan.be
import sys,pefile,os,binascii
def patch_file(binaryfile):
routine = "\x33\xc0"
# xor eax,eax
routine += "\x83\xF8\x00" # cmp eax,0
routine += "\x74\xFB" # JE back to cmp
print "[+] Opening file %s" % binaryfile
pe = pefile.PE(binaryfile)
entrypoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint
base = pe.OPTIONAL_HEADER.ImageBase
print " - Original Entrypoint : 0x%x" % (base + entrypoint)
searchend = 0
startrva = 0
for section in pe.sections:
if section.Name.replace('\x00','') == '.text':
# code segment
print " - Finding a good spot in code segment at 0x%x" % (base + section.VirtualAddress)
print " Size : 0x%x" % section.SizeOfRawData
searchend = section.SizeOfRawData
startrva = section.VirtualAddress
#print
(section.Name, hex(section.VirtualAddress), hex(section.Misc_VirtualSize), section.SizeOfRawData )
if searchend > 0:
cnt = 0
consecutive = 0
stopnow = False
offsethere = 0
while cnt < searchend and not stopnow:
thisbyte = pe.get_dword_at_rva(startrva+cnt)
if thisbyte == 0:
if
offsethere == 0:
offsethere = startrva+cnt
consecutive += 1
else:
offsethere = 0
consecutive = 0
if consecutive >=
len(routine)+5:
stopnow=True
cnt = cnt + 1
print " - Found %d consecutive null bytes at offset 0x%x" % (consecutive,offsethere)
print " Distance from original entrypoint : %x bytes" % (offsethere - entrypoint)
jmpback = "%x" % (4294967295 - (offsethere - entrypoint + 4 + len(routine)))
print " Jmpback : 0x%s" % jmpback
routine += "\xe8"
routine += binascii.a2b_hex(jmpback[6:8])
routine += binascii.a2b_hex(jmpback[4:6])
routine += binascii.a2b_hex(jmpback[2:4])
routine += binascii.a2b_hex(jmpback[0:2])
print " - Injecting hang + redirect (%d bytes) at 0x%x" % (len(routine),(base+offsethere))
pe.set_bytes_at_rva(offsethere,routine)
print " - Setting new EntryPoint to 0x%x" % (base+offsethere)
pe.OPTIONAL_HEADER.AddressOfEntryPoint = offsethere
entrypoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint
print " - Entrypoint now set to : 0x%x" % (base + entrypoint)
print "[+] Saving file"
pe.write
(filename=binaryfile.replace(".exe","")+"_patched.exe")
print "[+] Patched."
else:
print "[-] No code segment found ?"
if len(sys.argv) == 2:
target = sys.argv[1]
if os.path.exists(target):
patch_file(target)
else:
print " ** Unable to find file '%s' **" % target else:
print "\nUsage : patchbinary.py filename\r\n"

Вся работа corelanc0d3r заключалась в том, чтобы воспользоваться pefile, модулем python, который позволяет выполнять чтение и работать с PE (Portable Executable) файлами. (просто чтобы вы знали, данный модуль устанавливается автоматически вместе с BackTrack и Immunity Debugger).

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

Далее сценарий осуществит поиск области в файле, которая содержит 12 последовательных нулевых байтов (при необходимости их можно заменить, например, на NOPS).

В данной области будет помещен ый код. Этот код очистит EAX, сравнит его с 0, а затем, если значение условия будет равно «true», вернется к выполнению кода. Таким образом мы получим зацикливание, пока не присоединим отладчик. После условного перехода (который обеспечивает бесконечный цикл), размещается вызов изначального положения входной точки. И наконец, происходит обновление точки входа RVA в PE файле, и теперь она указывает на наш код.

Другими словами, при запуске выполняемого файла, он просто зависнет (уйдет в бесконечный цикл). Во время прикрепления отладчика процесс приостановится. Затем необходимо найти область, в которую был вставлен asm код (адрес на самом деле находится сразу после обновленной точки входа), и либо заменить cmp инструкцию на NOP, либо просто заменить cmp eax,0 на cmp eax,1. Если не останавливать процесс, исполняемый файл просто выполнит то, что должен. Также можно приостановить работающее приложение, и оно остановиться на cmp, или на инструкции перехода внутри нашего кода.

Давайте запустим наш сценарий из командной строки и посмотрим, как все автоматически сработает.

После того, как сценарий найдет безопасную для вызова рекурсии область и выполнит запись, он создаст новый файл с именем _patched.exe.

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

Сейчас, если мы переименуем наш измененный файл и еще раз запустим PoC код, нам удастся запустить отладчик и нажать «pause» для выхода из цикла. Затем мы выходим из цикла, после чего будет осуществлен переход к точке входа.

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

Windbg?

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

Допустим, вы хотите изменить CMP инструкцию на CMP EAX,1. Для этого нам необходимо заменить один байт в памяти (в данном случае по адресу 0×00415337).

Прикрепив данный отладчик (и приостановив процесс), просто запустите следующую команду:

eb 0x00415337 0x01

eb означает edit byte. Существуют также такие  команды, как ew (edit word) и ed (edit dword).

После внесения изменений нажмите F5 (или введите «g») для того, чтобы процесс вышел из рекурсии и вернулся к исходной точке ввода.

После публикации данной статьи мне сообщили, что можно использовать \xeb\xfe, как шаблон исправления (который будет перемещаться сам к себе). Благодарности пользователям @fdfalcon и @pa_kt.

Цифровые следы - ваша слабость, и хакеры это знают.

Подпишитесь и узнайте, как их замести!