Пожалуйста, обратите внимание, что пользователь заблокирован
Поиск уязвимостей можно автоматизировать! В прошлом номере мы уже рассматривали несколько действенных приемов с использованием IDAPython. А в этот раз попробуем автоматизировать поиск ошибок в такой конструкции, как цикл, разработав для этого полезный скрипт на Python, использующий возможности IDA Pro.
ВВЕДЕНИЕ
При поиске уязвимостей в программах с закрытым кодом цикл — один из ключевых паттернов, в котором часто присутствуют ошибки безопасности. Идентификация циклов часто оказывается одним из ключевых факторов реверс-инжиниринга. На функциональном уровне распознать цикл немудрено: его отличительная черта — это переход в обратном направлении, приводящий к повторному выполнению кода. То есть цикл — это многократно исполняемая последовательность инструкций. А единичное выполнение тела цикла именуется итерацией (повторением). В отличие от конструкции inline memcpy, рассматриваемой в предыдущей части, цикл может не иметь счетчика. Но он всегда имеет условие окончания повторений.
На приведенном примере два блока представлены в виде графа. Каждый блок имеет две точки: ту, на которую выполняется переход, и ту, с которой управление переходит далее. Путь идентификации цикла состоит в построении дерева доминаторов — родословной графа потока управления. На рисунке блок А является корнем дерева («В компьютерных лесах деревья растут сверху вниз». Б. Шнайер) и доминатором блока B, по-другому говоря — предком (предикатом). Соответственно, блок B — потомок блока А. Ключевая особенность графа потока управления цикла в том, что потомок ссылается на предка.
Рис. Структура цикла
Идентифицировать цикл через построение дерева доминаторов способен плагин к IDA Loop Detection, скрипт findloop из Immunity Debugger, Loop colorizer от Ильфака Гуильфанова. Но найти – это не то же самое, что и понять. Поэтому перейдем к примерам уязвимостей в циклах и их анализу.
КОЛЕСО ПЕРЕРОЖДЕНИЙ
Самая известная уязвимость в рассматриваемом паттерне — это переполнение буфера в интерфейсе RPC DCOM. Печально известная уязвимость стала результатом непроверяемого цикла копирования строки при выделении имен серверов из путей в формате UNC.
UNC-строка задается в формате «\\сервер\ресурс\путь» и передается в юникоде. Приведенный цикл пропускает первые четыре байта (символы \\) и копирует данные в приемный буфер вплоть до обнаружения завершающего обратного слеша (символа «\»), без какой-либо проверки размера. Такой цикл (который ограничен не количество копируемых байт, а фактом встречи с определенным значением) попадает в прицел эксплоитера, который размыкает его лишь после перезаписи управляющего элемента в стеке. То есть поток выполнения программы в цикле освобождается лишь при встрече с какой-то инструкцией, но не после определенного количества повторов. Такая ошибка программирования является фундаментальной для безопасного программирования.
Подобный натюрморт наблюдается в дефектном коде бюллетеня MS08-067:
Но и современность также балует уязвимыми циклами. Взглянем на недавнюю уязвимость в SAP NetWeaver (CVE-2012-2611):
Условие выхода из тела в приведенном примере — это встреча с нулем. В поиске зеро обусловленный поток управления в таком цикле может проходить не по всем блокам тела цикла. От чего фактор записи в память и условия выхода из тела не меняются. Таким образом, без построения дерева доминаторов можно обойтись. Достаточно найти путь между начальным и конечным адресами. Нас интересуют только точки выхода (условные джампы с вышестоящими инструкциями сравнения) и контролируемость значений, участвующих в сравнении.
Как известно, подход к операции сравнения качественно может изменить то, что ожидалось. Иначе получается как всегда. В следующем примере приветливо сияют женской логикой знаковый переход и инструкция умножения. Причем в умножении участвуют те, от кого зависит выход. Взглянем на целочисленное переполнение в XnView:
В этом примере арифметические операции с регистрами — операторами сравнения и знаковый переход привлекает внимание несовершенством целочисленных операций. И как всегда, арифметические операции участвуют в колдовстве.
Подведем промежуточный итог. Что же такое уязвимость цикла? Исходя из корня слова «определение», обозначим границы контроля за циклом. Из приведенных выше листингов дефектного кода следует, что интересность цикла — это фактор записи + фактор контроля за условием выхода из цикла.
В следующем примере показаны варианты контроля за циклом. Речь об уязвимости в Microsoft Vector Graphic rendering Engine (CVE-2006-4868). Вот код:
Пример от остальных мало чем отличается, а приведен, чтобы показать то, что называется false positives — отрицательный результат анализа. ZERT (Zeroday Emergency Response Team) добавила проверку значения, находящегося по адресу [ecx+4], тем самым условие выхода из цикла стало «прибито гвоздями» (захардкодено). Microsoft же в своем патче добавила проверку итератора. Здесь их парочка, но проверки одного достаточно:
Отметим, что, если значение esi контролируется, при знаковом переходе после сравнения можно было бы по-прежнему «обладать телом цикла». Но использование беззнакового перехода jnb закрыло путь-дорожку к эксплуатации. В патче от ZERT также семейка знаковых переходов была обделена вниманием.
В итоге имеем проверку одного из итераторов и одного из элементов проверки выхода из цикла. То есть, для скрипта возникает две задачи обработки двух последовательностей инструкций:
и
Куда же без основной задачи реверс-инжиниринга: «будет ли Х иметь значение Y после заданного набора инструкций?». Наряду с inline memcpy, анализ циклов также нуждается в трассировке регистров.
Продолжая тему false positive (истинно негативных) признаков, следует обратить внимание на то, что цикл может быть подобен одной из инструкций stosb/stosw/stosd, пишущих в память константу.
Но вернемся к пониманию прекрасного — свойствам потенциально уязвимого цикла:
• записи в память непостоянного значения;
• отсутствию корректной проверки выхода из цикла.
С этим багажом начинаем реализовывать задачу поиска тела цикла и проверки на вшивость. Основная задача — анализ условий выхода из тела цикла.
КОНТРОЛИРУЕМЫЙ ВЫХОД ИЗ ТЕЛА. АСТРАЛЬНЫЕ ПРАКТИКИ
Предлагаемое заклинание кода имеет следующие этапы:
1. Поиск тела цикла (пикап). Составляем список всех условных переходов и адресов тела.
2. Поиск паттерна записи.
3. Поиск условий выхода.
• Поиск условных переходов, указывающих на выход из тела.
• Поиск и анализ инструкций сравнения.
4.1. Трассировка операнда инструкции сравнения.
4.2.Поиск итератора и трассировка его операнда.
Алгоритм поиска тела цикла базируется на построении трассы выполнения от начала цикла до точки, с которой все возвращается на круги своя. Для части циклов эта трасса будет полностью охватывать тело. И это не единственное ограничение предлагаемого концепт-скрипта. С вложенными циклами и с теми, где адрес макушки цикла больше, чем адрес его стоп, также работа не ведется.
Деятельность по исследованию цикла начинается с того, что функция берет адрес перехода назад. И, используя адрес начала цикла, ищет путь к нему. Тем самым проходя по всему телу либо его части, попутно составляя список адресов тела и условных переходов.
Затем, используя список адресов, находим паттерн записи в память — инструкцию mov, где 0-й операнд — память, а 1-й — регистр (прим.: mov [eax],ecx). Темная сторона этого свойства (деготь в меде) проявляется, когда 1-й операнд — постоянное значение (прим.: 0xBAADF00D — потеря аппетита от такой пищи гарантирована).
Далее ищутся условные переходы, указывающие за пределы цикла.
Теперь от каждого jump-а, указывающего из тела, ведется поиск компараторов — инструкций сравнения cmp, test. Нулевой операнд каждого «сравнивателя» скармливается функции трассировки, дабы познать значение регистра-операнда.
Трассировка нужна и в отношении операнда итератора (счетчика). Если итераторов несколько, то необходимо проверить операнд каждого из них, в связи с возможностью присутствия проверки количества прохождений цикла. Трассировка итератора отлична от трассировки нулевого операнда инструкций сравнения cmp и test. Как видно из примеров уязвимых циклов, если операнд итератора сравнивается с константой и переход после сравнения беззнаковый, то это однозначный провал операции по захвату тела. При знаковом переходе надежда еще есть.
Составляем список итераторов тела
Затем для каждого элемента списка итераторов вызываем трассирующую функцию
Трассировщик обращает внимание на инструкции-пересыльщики, инструкцию call, если трассируемый регистр eax. Инструкция xor, часто использующаяся для того, чтобы захардкодить счетчик, подлежит обязательному поиску в качестве признака неинтересного цикла. Арифметические инструкции (могущие быть причастными к ошибкам преобразования чисел между знаковыми и беззнаковыми) стоят, как обычно, на особом счету.
Итак, анализ циклов — неординарная задача. Рассмотренный скрипт обладает ограниченным функционалом. Расширив его, исследователь уязвимостей может облегчить задачи поиска уязвимостей в двоичном коде.
Файл, предоставленный автором статьи.
Источник
ВВЕДЕНИЕ
При поиске уязвимостей в программах с закрытым кодом цикл — один из ключевых паттернов, в котором часто присутствуют ошибки безопасности. Идентификация циклов часто оказывается одним из ключевых факторов реверс-инжиниринга. На функциональном уровне распознать цикл немудрено: его отличительная черта — это переход в обратном направлении, приводящий к повторному выполнению кода. То есть цикл — это многократно исполняемая последовательность инструкций. А единичное выполнение тела цикла именуется итерацией (повторением). В отличие от конструкции inline memcpy, рассматриваемой в предыдущей части, цикл может не иметь счетчика. Но он всегда имеет условие окончания повторений.
На приведенном примере два блока представлены в виде графа. Каждый блок имеет две точки: ту, на которую выполняется переход, и ту, с которой управление переходит далее. Путь идентификации цикла состоит в построении дерева доминаторов — родословной графа потока управления. На рисунке блок А является корнем дерева («В компьютерных лесах деревья растут сверху вниз». Б. Шнайер) и доминатором блока B, по-другому говоря — предком (предикатом). Соответственно, блок B — потомок блока А. Ключевая особенность графа потока управления цикла в том, что потомок ссылается на предка.
Рис. Структура цикла
Идентифицировать цикл через построение дерева доминаторов способен плагин к IDA Loop Detection, скрипт findloop из Immunity Debugger, Loop colorizer от Ильфака Гуильфанова. Но найти – это не то же самое, что и понять. Поэтому перейдем к примерам уязвимостей в циклах и их анализу.
КОЛЕСО ПЕРЕРОЖДЕНИЙ
Самая известная уязвимость в рассматриваемом паттерне — это переполнение буфера в интерфейсе RPC DCOM. Печально известная уязвимость стала результатом непроверяемого цикла копирования строки при выделении имен серверов из путей в формате UNC.
Код:
mov ax, [еах+4]
cmp ах, ʹ\ʹ
jz short loc_761AE698
sub edx, ecx
beginloop:
mov [ecx], ax ;пишем
inc ecx ;итератор
inc ecx
mov ax, [ecx+edx]
cmp ax, ʹ\ʹ ;проверка с ʹ\ʹ
jnz short beginloop
UNC-строка задается в формате «\\сервер\ресурс\путь» и передается в юникоде. Приведенный цикл пропускает первые четыре байта (символы \\) и копирует данные в приемный буфер вплоть до обнаружения завершающего обратного слеша (символа «\»), без какой-либо проверки размера. Такой цикл (который ограничен не количество копируемых байт, а фактом встречи с определенным значением) попадает в прицел эксплоитера, который размыкает его лишь после перезаписи управляющего элемента в стеке. То есть поток выполнения программы в цикле освобождается лишь при встрече с какой-то инструкцией, но не после определенного количества повторов. Такая ошибка программирования является фундаментальной для безопасного программирования.
Подобный натюрморт наблюдается в дефектном коде бюллетеня MS08-067:
Код:
begin_loop:
mov eax, dword ptr [ebx]
movzx ecx,word ptr [eax]
cmp ecx,5Ch ;проверка с ʹ\ʹ
je out_of_loop ;на выход
mov eax,dword ptr [ebx]
cmp eax,dword ptr [esi]
je out_of_loop ;на выход
mov eax,dword ptr [ebx]
sub eax,2 ;итератор
mov dword ptr [ebx],eax ;пишем
jmp begin_loop
Но и современность также балует уязвимыми циклами. Взглянем на недавнюю уязвимость в SAP NetWeaver (CVE-2012-2611):
Код:
begin_loop:
cmp edx,2
mov [ebp+DataEnd], TraceInfo
jnz copy_with_unicode_conversion ;переход внутри
;тела
mov dx, TraceInfo
mov [ebp+eax*2+var_d],dx ;запись в память
jmp loop_end
copy_with_unicode_conversion:
movzx cx, byte ptr [TraceInfo]
mov [ebp+eax*2+var_d],cx ;запись в память
loop_end:
cmp [ebp+eax*2+var_d],0 ;пока не встретится
;ноль
jz out_of_loop
…
add eax,1 ;инкремент
add TraceInfo,edx
jmp begin_loop
Условие выхода из тела в приведенном примере — это встреча с нулем. В поиске зеро обусловленный поток управления в таком цикле может проходить не по всем блокам тела цикла. От чего фактор записи в память и условия выхода из тела не меняются. Таким образом, без построения дерева доминаторов можно обойтись. Достаточно найти путь между начальным и конечным адресами. Нас интересуют только точки выхода (условные джампы с вышестоящими инструкциями сравнения) и контролируемость значений, участвующих в сравнении.
Как известно, подход к операции сравнения качественно может изменить то, что ожидалось. Иначе получается как всегда. В следующем примере приветливо сияют женской логикой знаковый переход и инструкция умножения. Причем в умножении участвуют те, от кого зависит выход. Взглянем на целочисленное переполнение в XnView:
Код:
begin_loop:
xor ecx,ecx
mov edx,[edi]
mov cx,[ebp+e]
imul ecx,eax ;операция умножения с
;условиями выхода
mov [edx+eax*4],ecx
mov ecx,[edi+8]
inc eax
cmp eax,ecx
jl short begin_loop
В этом примере арифметические операции с регистрами — операторами сравнения и знаковый переход привлекает внимание несовершенством целочисленных операций. И как всегда, арифметические операции участвуют в колдовстве.
Подведем промежуточный итог. Что же такое уязвимость цикла? Исходя из корня слова «определение», обозначим границы контроля за циклом. Из приведенных выше листингов дефектного кода следует, что интересность цикла — это фактор записи + фактор контроля за условием выхода из цикла.
В следующем примере показаны варианты контроля за циклом. Речь об уязвимости в Microsoft Vector Graphic rendering Engine (CVE-2006-4868). Вот код:
Код:
begin_loop:
mov edx, [ecx+8]
mov ebx, [ecx]
mov dx, [ebx+edx*2]
test dx, dx
jz short loc_5DEDED2F ;пока не 0
cmp dx, 20h
jnz short loc_5DEDED1E ;пока не 20h
test esi, esi
jg short loc_5DEDED33
jmp short loc_5DEDED24
loc_5DEDED1E:
mov [edi], dx
inc esi
inc edi
inc edi
loc_5DEDED24:
inc dword ptr [ecx+8]
mov edx, [ecx+8]
cmp edx, [ecx+4]
jl short begin_loop
Пример от остальных мало чем отличается, а приведен, чтобы показать то, что называется false positives — отрицательный результат анализа. ZERT (Zeroday Emergency Response Team) добавила проверку значения, находящегося по адресу [ecx+4], тем самым условие выхода из цикла стало «прибито гвоздями» (захардкодено). Microsoft же в своем патче добавила проверку итератора. Здесь их парочка, но проверки одного достаточно:
Код:
cmp esi, 0FEh
jnb short skip_copy
…
mov [edi], dx
inc esi
inc edi
inc edi
Отметим, что, если значение esi контролируется, при знаковом переходе после сравнения можно было бы по-прежнему «обладать телом цикла». Но использование беззнакового перехода jnb закрыло путь-дорожку к эксплуатации. В патче от ZERT также семейка знаковых переходов была обделена вниманием.
В итоге имеем проверку одного из итераторов и одного из элементов проверки выхода из цикла. То есть, для скрипта возникает две задачи обработки двух последовательностей инструкций:
Код:
cmp reg32, imm32 ;сравнение итератора
…
inc|add|sub|dec reg32 ;итератор
и
Код:
cmp reg32, imm32 ;сравнение операнда проверки
…
cmp 0opnd, 1opnd ;где reg32 — один из операндов
jump out_of_loop ;на выход из цикла
Куда же без основной задачи реверс-инжиниринга: «будет ли Х иметь значение Y после заданного набора инструкций?». Наряду с inline memcpy, анализ циклов также нуждается в трассировке регистров.
Продолжая тему false positive (истинно негативных) признаков, следует обратить внимание на то, что цикл может быть подобен одной из инструкций stosb/stosw/stosd, пишущих в память константу.
Но вернемся к пониманию прекрасного — свойствам потенциально уязвимого цикла:
• записи в память непостоянного значения;
• отсутствию корректной проверки выхода из цикла.
С этим багажом начинаем реализовывать задачу поиска тела цикла и проверки на вшивость. Основная задача — анализ условий выхода из тела цикла.
КОНТРОЛИРУЕМЫЙ ВЫХОД ИЗ ТЕЛА. АСТРАЛЬНЫЕ ПРАКТИКИ
Предлагаемое заклинание кода имеет следующие этапы:
1. Поиск тела цикла (пикап). Составляем список всех условных переходов и адресов тела.
2. Поиск паттерна записи.
3. Поиск условий выхода.
• Поиск условных переходов, указывающих на выход из тела.
• Поиск и анализ инструкций сравнения.
4.1. Трассировка операнда инструкции сравнения.
4.2.Поиск итератора и трассировка его операнда.
Алгоритм поиска тела цикла базируется на построении трассы выполнения от начала цикла до точки, с которой все возвращается на круги своя. Для части циклов эта трасса будет полностью охватывать тело. И это не единственное ограничение предлагаемого концепт-скрипта. С вложенными циклами и с теми, где адрес макушки цикла больше, чем адрес его стоп, также работа не ведется.
Деятельность по исследованию цикла начинается с того, что функция берет адрес перехода назад. И, используя адрес начала цикла, ищет путь к нему. Тем самым проходя по всему телу либо его части, попутно составляя список адресов тела и условных переходов.
Python:
while addr!=startaddr:
# Получаем ссылки
xref1=Rfi rstB(addr)
xref2=Rnext(addr,0)
# Отсев ненужных ссылок
if xref2!=0xffffffff and GetMnem(addr)!="call" and GetMnem(addr)!="jmp":
# Добавляем адрес с условным переходом в список
branchpoints.append(addr)
# Проверка на пропасть
if xref1 == 0xffffffff:
break
addr=xref1
# Добавляем текущий адрес к списку с телом цикла
bodyaddr.append(addr)
Затем, используя список адресов, находим паттерн записи в память — инструкцию mov, где 0-й операнд — память, а 1-й — регистр (прим.: mov [eax],ecx). Темная сторона этого свойства (деготь в меде) проявляется, когда 1-й операнд — постоянное значение (прим.: 0xBAADF00D — потеря аппетита от такой пищи гарантирована).
Python:
for addr in bodyaddr:
if GetMnem(addr)=="mov":
# Список "памятных" типов: базовый
# регистр + индексный регистр, базовый
# регистр + индексный регистр + смещение
memtypes=[3,4]
if GetOpType(addr,0) in memtypes:
# Если 1-й операнд регистр
if GetOpnd(addr,1)==1:
# Цикл пишущий, вернем истину
return 1
Далее ищутся условные переходы, указывающие за пределы цикла.
Python:
for addr in branchpoints:
# Получаем ссылки
xref1=Rfi rst(addr)
xref2=Rnext(addr,0)
# Если одна ссылка указывает на тело
if xref1 in bodyaddr:
# займемся второй
# Функция-ищейка попытается найти тело за 20
# шагов и помашет флажком
fl ag=SearchBodyAddr(xref2)
else:
fl ag=SearchBodyAddr(xref1)
if fl ag==1:
# Удалим переход-инсайдер
branchpoints.remove(addr)
Теперь от каждого jump-а, указывающего из тела, ведется поиск компараторов — инструкций сравнения cmp, test. Нулевой операнд каждого «сравнивателя» скармливается функции трассировки, дабы познать значение регистра-операнда.
Python:
# Список инструкций сравнения
cmps=['cmp','test']
# за небольшое кол-во шагов
for count in range(5):
# пока не найден сравниватель
while GetMnem(addr) not in cmps:
addr=Rfi rstB(addr)
if GetMnem(addr)=="test":
# Если "внетелесный джамп" один-одинешенек,
# то имеем дело с проверкой на ноль
# (test reg,reg эквивалентно cmp reg,0)
if len(branchpoints)==1:
# Добрые вести
print "input is exit condition!"
vulncount+=1
break
# Встречая cmp, где 1-й операнд != константе
if GetMnem(addr)=="cmp" and GetOpType(addr,1)!=5:
# передаем нулевой операнд трассировщику
reg=GetOpnd(addr,0)
TraceVal(reg,addr,iters)
break
# Нашла коса на камень
if GetMnem(addr)=="cmp" and GetOpType(addr,1)==5:
# Печалька
hardcoded=1
# Не теряя надежду, шагая вниз, ищем j[gl]
for count in range(5):
addr=Rfi rst(addr)
if GetMnem(addr) in signjumps:
vulncount+=1
Трассировка нужна и в отношении операнда итератора (счетчика). Если итераторов несколько, то необходимо проверить операнд каждого из них, в связи с возможностью присутствия проверки количества прохождений цикла. Трассировка итератора отлична от трассировки нулевого операнда инструкций сравнения cmp и test. Как видно из примеров уязвимых циклов, если операнд итератора сравнивается с константой и переход после сравнения беззнаковый, то это однозначный провал операции по захвату тела. При знаковом переходе надежда еще есть.
Код:
inc reg32
cmp reg32, imm32
jnb out_loop
Составляем список итераторов тела
Python:
# Список возможных итераторов
iterlist=["inc","add","sub","dec"]
# Припасаем вместилище
iters=[]
# В списке адресов тела ищем цели
for addr in bodyaddr:
if GetMnem(addr) in iterlist:
# Ищущий да зааппендится
iters.append(addr)
return iters
Затем для каждого элемента списка итераторов вызываем трассирующую функцию
Код:
for addr in iters:
reg=GetOpnd(addr,0)
TraceVal(reg,endaddr)
Трассировщик обращает внимание на инструкции-пересыльщики, инструкцию call, если трассируемый регистр eax. Инструкция xor, часто использующаяся для того, чтобы захардкодить счетчик, подлежит обязательному поиску в качестве признака неинтересного цикла. Арифметические инструкции (могущие быть причастными к ошибкам преобразования чисел между знаковыми и беззнаковыми) стоят, как обычно, на особом счету.
Python:
# Подозревая signed/unsigned mismatch
suspectedins=["movsx","sub","add"]
# Посыльщики
movers=["mov","movzx"]\
# Виды адресации [ebp+esi],[ebp+esi+8]
memtypes=[3,4]
# Берем адрес начала функции
parent = GetFunctionAttr(addr,0)
while addr != parent or addr!=0xffffffff:
# На случай чанкед-функций
# ищем начало фрагмента
if GetMnem(addr)=="push" and GetOpnd(addr,0)=="ebp":
print "prolog"
break
# Получаем ссылку
addr = Rfi rstB(addr)
# Ищем пациента
if GetOpnd(addr,0)==reg:
# Если значение возвращается функцией
if GetMnem(addr)=="call" and reg=="eax":
print reg,"returned by call"
break
# Обнуление счетчика — частое событие
if GetMnem(addr)=="xor":
print reg,"xored"
hardcoded=1
break
# Встреча с подозреваемыми без алиби
if GetMnem(addr) in suspectedins and addr not in iters:
print "suspected ins",reg,"at addr",hex(addr)
vulncount+=1
# Или объект трассировки меняется
# или разводим руками: из памяти пришло,
# в память уйдет
if GetMnem(addr) in movers:
if GetOpType(addr,1)==1:
reg=GetOpnd(addr,1)
if GetOpType(addr,1) in memtypes:
print reg,"from memory"
break
Итак, анализ циклов — неординарная задача. Рассмотренный скрипт обладает ограниченным функционалом. Расширив его, исследователь уязвимостей может облегчить задачи поиска уязвимостей в двоичном коде.
Файл, предоставленный автором статьи.
Python:
#A script for search vuln loop. A public version.
from idaapi import *
from idautils import *
import string
bodyaddr=[]
branchpoints=[]
endaddr=get_screen_ea()
iterlist=[]
iters=[]
signjumps=["jg","jge","jl","jle"]
vulncount=0
hardcoded=0
def Pickuper(addr,startaddr):
global branchpoints
global bodyaddr
infinityflag=0
#collects addresses of path(body loop)
bodyaddr.append(addr)
while addr!=startaddr:
xref1=RfirstB(addr)
xref2=Rnext(addr,0)
#otsev call
if xref2!=0xffffffff and GetMnem(addr)!="call" and GetMnem(addr)!="jmp":
#in massiv branchpoints stored points of exit of basic blocks - jumpmassiv
branchpoints.append(addr)
print "branchpoints.append(ea)",hex(addr),hex(xref1),hex(xref2),"start",hex(startaddr)
#contra infinity path
for id in branchpoints:
if branchpoints.count(id) >1:
infinityflag=1
branchpoints=UniqListCreator(branchpoints)
if xref1 == 0xffffffff:
break
addr=xref1
if infinityflag==1:
addr=xref2
#collect addresses of path(body loop)
bodyaddr.append(addr)
comment=hex(addr)
MakeComm(addr, comment)
#paint
SetColor(addr,CIC_ITEM,0xe5f3ff)
if addr==startaddr:
print "found startaddr!"
return
return
def UniqListCreator(list):
list2=[]
for id in list:
if id not in list2:
list2.append(id)
return list2
def Writesearcher():
global bodyaddr
#search the first wr-pattern
#don`t search all patterns
for addr in bodyaddr:
if GetMnem(addr)=="mov":
#print "mov at addr",hex(addr)
#if memory reference
memtypes=[3,4]
if GetOpType(addr,0) in memtypes:
#if 1opnd register
if GetOpType(addr,1)==1:
print "This is WRiting loop"
#this is writing loop
return 1
def Outloop_jumps_searcher(endaddr):
global branchpoints
global bodyaddr
outloopjumps=[]
fakejumps=[]
flag=0
for addr in branchpoints:
xref1=Rfirst(addr)
xref2=Rnext(addr,0)
if xref1 in bodyaddr:
flag=SearchBodyAddr(xref2)
else:
flag=SearchBodyAddr(xref1)
if flag==1:
#remove jumps-insiders
fakejumps.append(addr)
for id in fakejumps:
if id in branchpoints:
branchpoints.remove(id)
def SearchBodyAddr(xref):
#try to search loopbody
flag=0
global bodyaddr
for count in range(20):
xref=Rfirst(xref)
comment=hex(xref)
MakeComm(xref, comment)
#paint
SetColor(xref,CIC_ITEM,0xcbe4e4)
if xref in bodyaddr:
flag=1
break
print "searchendloop",hex(xref)
if xref==0xffffffff:
flag=0
break
count=+1
print "flag of insideloop jump",flag
return flag
def CmpSearch(addr):
global branchpoints
global iters
global signjumps
global vulncount
global hardcoded
#for 5 steps search cmp,test ins
cmps=['cmp','test']
for count in range(5):
while GetMnem(addr) not in cmps:
addr=RfirstB(addr)
if GetMnem(addr)=="test" and GetOpnd(addr,0)==GetOpnd(addr,1):
print "test at", hex(addr)
if len(branchpoints)==1:
print "input is exit condition!"
vulncount+=1
break
if GetMnem(addr)=="cmp" and GetOpType(addr,1)!=5:
print "cmp at", hex(addr)
reg=GetOpnd(addr,0)
TraceVal(reg,addr,iters)
break
if GetMnem(addr)=="cmp" and GetOpType(addr,1)==5:
hardcoded=1
for count in range(5):
addr=Rfirst(addr)
print hex(addr)
if GetMnem(addr) in signjumps:
vulncount+=1
return
break
count=+1
return vulncount
def SearchFuncProlog(addr):
if GetMnem(addr)=="push" and GetOpnd(addr,0)=="ebp":
return 1
def TraceVal(reg,addr,iters):
hardcoded=0
global vulncount
global signjumps
suspectedins=["movsx","sub","add"]
movers=["mov","movzx"]
#mem constants[ebp+arg_0],[eax],[eax+8]
memtypes=[2,3,4]
print reg, hex(addr)
parent = GetFunctionAttr(addr,0)
while addr != parent or addr!=0xffffffff:
if SearchFuncProlog(addr)==1:
print "prolog"
break
comment=hex(addr)
MakeComm(addr, comment)
addr = RfirstB(addr)
if GetOpnd(addr,0)==reg:
comment=hex(addr)
MakeComm(addr, comment)
if GetMnem(addr)=="lea":
break
if GetMnem(addr)=="call" and reg=="eax":
print "at call ins stop trace"
break
if GetMnem(addr)=="xor":
print reg,"xored"
hardcoded=1
break
if GetMnem(addr) in suspectedins and addr not in iters:
print "suspectedins", hex(addr),reg
vulncount+=1
#if cmp reg,imm
if GetMnem(addr)=="cmp" and GetOpType(addr,1)==5:
hardcoded=1
for count in range(5):
addr=Rfirst(addr)
if GetMnem(addr) in signjumps:
vulncount+=1
break
break
if GetMnem(addr) in movers:
#if mov
if GetOpType(addr,1)==1:
reg=GetOpnd(addr,1)
if GetOpType(addr,1) in memtypes:
print reg,"from memory"
break
return vulncount,hardcoded
def IterSearch():
global bodyaddr
global iters
iterlist=["inc","add","sub","dec"]
for addr in bodyaddr:
if GetMnem(addr) in iterlist:
iters.append(addr)
return iters
def main(endaddr):
global branchpoints
global bodyaddr
startaddr=0
vulncount=0
global hardcoded
global signjums
#searching startaddr
xref1=Rfirst(endaddr)
xref2=Rnext(endaddr,0)
if xref1 < endaddr:
startaddr=xref1
if xref2 < endaddr:
startaddr=xref2
else:
print "Try other loop"
return
print "startaddr", hex(startaddr),hex(xref1),hex(xref2),"endaddr",hex(endaddr)
#search bodyloop. Takes start and end addresses of loop
Pickuper(endaddr,startaddr)
for id in branchpoints:
print "branchpoint:",hex(id)
for id in bodyaddr:
print "body:",hex(id)
#search for writing loop
if Writesearcher()==1 and len(branchpoints)>1:
Outloop_jumps_searcher(endaddr)
elif Writesearcher()!=1:
print "loop is not interesting"
return
for addr in branchpoints:
vulncount=CmpSearch(addr)
if hardcoded==1:
print "vulncount=",vulncount,"hardcoded=",hardcoded
return
iters=IterSearch()
for id in iters:
print "iter", hex(id)
for addr in iters:
reg=GetOpnd(addr,0)
vulncount,hardcoded=TraceVal(reg,endaddr,iters)
print "vulncount=",vulncount,"hardcoded=",hardcoded
for addr in branchpoints:
if GetMnem(addr) in signjumps:
vulncount+=1
print "Loop are analyzed. Vulncount=",vulncount,"hardcoded=",hardcoded
if __name__ == "__main__":
main(endaddr)
Источник
Последнее редактирование: