Восстановление управляющих конструкций обработки исключений языка Си++

№ 6(30) 2010

Е. 0. Деревенец, Е. Н. Трошина, А. В. Чернов

Восстановление управляющих конструкций обработки исключений языка Си++

Авторами рассматриваются некоторые методы декомпиляции программ на языке Си++ — методы восстановления структурных конструкций обработки исключений языка Си++ try-catch и оператора генерации исключений throw.

Введение

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

Декомпиляция — восстановление программы на языке высокого уровня из программы на языке низкого уровня, из объектного кода, исполняемых файлов или трасс выполнения; включает решение следующих подзадач:

1) выделение функций во входной программе;

2) восстановление управляющих конструкций языка высокого уровня;

3) анализ потоков данных, восстановление локальных переменных и аргументов функций;

4) восстановление базовых и производных типов данных;

5) генерация кода на языке высокого уровня.

Данная работа посвящена проблеме восстановления высокоуровневых управляющих структурных конструкций.

На сегодняшний день наиболее распространенные языки программирования, транслируемые в машинный код, — языки Си и Си++. Поэтому декомпиляция программ, написанных на этих языках, представляет наибольшую практическую ценность.

Настоящая статья является продолжением работы [1], посвященной восстановлению структурных конструкций языка Си if-then, if-then-else, while, do-while, for, switch. С точки зрения структурного анализа язык Си++ — это расширение языка Си. В языке Си++ появились встроенные средства, предназначенные для обработки ошибок — аппарат исключений. В данной работе проводится анализ особенностей восстановления конструкций языка Си++, предназначенных для обработки исключительных ситуаций: блоков try-catch и оператора генерации исключений throw. В качестве программы на языке низкого уровня рассматриваются программы на языке ассемблера.

Статья имеет следующую структуру. Первый раздел содержит обзор работ по теме восстановления структурных конструкций языка Си++ и обратной инженерии различных способов реализации механизма исключений в языке Си++. Во втором разделе представлены описания наиболее популярных способов реализации исключений компиляторами на платформах Windows и GNU/Linux и методы восстановления плат-

№ 6(30) 2010

формо-зависимой информации об обработчиках исключений. В третьем разделе указаны общие особенности структуры потока управления программ на Си++ и предложены модификации к алгоритму выделения функций во входной программе, основанному на поиске компонент связности в графе потока управления. Рассмотренные методы были реализованы в рамках декомпилятора TyDec, разрабатываемого авторами в Институте системного программирования РАН. Четвертый раздел содержит краткое описание выполненной прототипной реализации и результаты ее тестирования на программах, оттранслированных компиляторами для платформ Windows и GNU/Linux. В Заключении представлены основные выводы.

1. Обзор существующих решений

Управляющие структурные конструкции языка Си++ можно условно разделить на две группы: 1) явно выражаемые в графе потока управления программы; 2) те, в которых передача управления осуществляется неявно.

Задача восстановления структурных конструкций с помощью анализа графа потока управления достаточно исследована. В [2] рассмотрены два метода анализа потока управления: первый основан на построении доминирующих множеств вершин графа потока управления и может использоваться для восстановления циклических структурных конструкций, второй — интервальный анализ, обобщением которого является структурный анализ. Последний позволяет восстанавливать как циклические (циклы с пред- и постусловием), так и ациклические (условный оператор, оператор выбора) конструкции. В декомпиляторе TyDec реализован модифицированный алгоритм структурного анализа, выполняющий восстановление структурных конструкций указанных типов [1].

Конструкции обработки исключений try-catch не отображаются в графе потока управления в явном виде. Передача управ-

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

Вопросам обратной инженерии структур данных, используемых при обработке исключений в программах, скомпилированных Microsoft Visual С++, посвящена работа [4]. В ней рассматриваются механизм структурной обработки исключений (Structured Exception Handling, SEH) и реализация обработки исключений языка Си++ в компиляторе Microsoft Visual Studio с использованием этого механизма. Автором статьи разработан набор скриптов для дизассемблера IDA Pro [5], осуществляющий поиск во входной программе последовательностей инструкций, типичных для обработки исключений. Найденные обработчики исключений, связанные с ними переменные и структуры данных снабжаются в ассемблерном коде соответствующими комментариями. Выполняется уточнение границ функций, некорректно определяемых дизассемблером.

Листинг 1 содержит пример простейшей программы на языке Си++, использующей обработку исключений. Эта программа была скомпилирована Microsoft Visual С++, дизассемблирована IDA Pro с привлечением указанного набора скриптов и восстановлена декомпилятором HexRays. Пример модифицированного графа потока управления с уточненными границами функций представлен на рис. 1, где не связанные явными переходами вершины блоков catch корректно отнесены к функции main. Для блоков указан тип обрабатываемых ими исключений. Регионы try не восстановлены, их границы не обозначены.

Псевдокод, сгенерированный декомпилятором HexRays, содержит листинг 2 (см. стр. 45). В восстановленном коде видно, что в начале функции происходит заполнение структуры_$EHRec$ и регистрация с ее помощью обработчика исключений функции main. Далее создается объект исключения типа int, и осуществляется генерация исключения с использованием функции _CxxThrowException. Очевидно, де-

№ 6(30) 2010

компилятор HexRays не учитывает особенностей языка Си++ и не восстанавливает регионы try-catch.

Листинг 1. Пример программы на языке Си++

Реализация исключений языка Си++ в компиляторе Microsoft Visual С++ рассматривается в статье [6]. Автором разработана собственная библиотека обработки исключений для данного компилято-

ра, полностью заменяющая используемую по умолчанию.

Обработка исключений Dwarf2 в компиляторах GCC и Intel С++ Compiler описана в Itanium С++ ABI [7].

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

2. Восстановление конструкций обработки исключений языка Си++

Механизм исключений языка Си++ является средством нелокальной передачи управления в программах на данном языке и предназначен в первую очередь для обработки ошибочных ситуаций. Исключения

int main() {

try {

throw 34; } catch (int) { } catch (char) {

} catch (bool) { }

return 0;

}

+

-a Й

I

I g

£ SI

0

1

S §

t S

t I

I §

is

u u о CO

Рис. 1. Вид восстановленного графа потока управления функции main в IDA Pro

44

№ 6(30) 2010

Листинг2. Псевдокод функции main, восстанавливаемый декомпилятором HexRays

«

!

ва f

а

о

s

s £

S

int

usercall main<eax>(int a1<eax>, int a2<ebx>, int a3<edi>

int a4<esi>

EHRegistrationNodeCatch _$EHRec$; // [sp+14h] [bp-10h]@1

int v6 int v7 int v8 int s

// [sp+Ch] [bp-18h]@1

// [sp+8h] [bp-1Ch]@1

// [sp+4h] [bp-20h]@1

// [sp+2 4h] [bp+0h]@1

unsigned int _$EHCookie$; // [sp+0h] [bp-24h]@1

signed int v11; // [sp+10h] [bp-14h]@1

_$EHRec$.frameHandler _$EHRec$.pNext = a1;

v6 = a2

v7 = a4

v8 = a3

(int) ehhandler main;

$EHCookie$ = (unsigned int)& s л security cookie;

_$EHRec$.SavedESP = (int)&_$EHCookie$;

_$EHRec$.state = 0;

v11 = 34;

_CxxThrowException(&v11, &_TI1H);

return _tryend_main_3;

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

В программах на языке Си возможно реализовать механизм обработки ошибок, подобный исключениям Си++, с помощью функций эе^тр () и 1опд|тр (). Функция эе^тр () сохраняет контекст исполнения программы в переданный буфер. Функция 1опд|тр () восстанавливает контекст исполнения, ранее сохраненный эе^тр ().

Стандарт языка Си++ [8] определяет только семантику обработки исключений. Способ реализации зависит от использованного компилятора и целевой платформы.

В языке Си++ обработка исключения в общем случае состоит из следующих этапов:

1) поискподходящегообработчика;

2) раскрутка стека до нужного уровня;

3) вызов найденного обработчика.

Соответственно в программе должна

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

• использующие только статические структуры данных (исключения Dwarf2);

• использующие как статические, так и динамические структуры данных (исключения SjLj, реализации исключений на платформе Win32 в компиляторах Microsoft Visual С++, OpenWatcom, Borland С++ Builder).

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

Далее рассматриваются наиболее распространенные способы реализации обработки исключений языка Си++ для архитектур х86 и х86-64, предлагаются методы восстановления регионов try-catch и оператора throw.

Обработка исключений Dwarf2

Способ обработки исключений Dwarf2 используется по умолчанию компиляторами GCC и Intel С++ Compiler на GNU/Linux на архитектуре х86 и х86-64.

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

№ 6(30) 2010

бор регионов, называемых местами вызова (call site). В такие регионы выделяются участки программы, в которых может потребоваться обработка исключений: вызовы функций, места генерации исключений, соответствующие оператору throw в исходной программе. Для каждого места вызова известны типы обрабатываемых исключений, также им ставится в соответствие так называемая посадочная площадка (landing pad) — участок кода, на который передается управление в процессе обработки исключения, если среди типов обрабатываемых исключений для этого места вызова присутствует тип сгенерированного исключения.

Вся перечисленная информация содержится в исполняемом файле в виде статических структур данных; информация о стековых фреймах — в секции .eh_frame и ис-

пользуется библиотекой Unwind Library [9], выполняющей раскрутку стека. Формат структур, содержащихся в этой секции, основан на формате отладочной информации Dwarf [10] (что и дало название способу обработки исключений) и описан в [11]. Сведения об обработчиках исключений хранятся в виде областей LSDA в секции .дсс_ except_table. Каждой функции соответствует своя структура LSDA, ее формат показан на рис. 2. Общая схема поиска обработчика исключения приведена в [7].

Структура LSDA содержит информацию о местах вызова в исполняемом коде функции, таблицу обработчиков исключений (Action Record Table) и таблицу типов обрабатываемых исключений (Type Table), в которой имеются указатели на служебную информацию typeinfo типа обрабатываемого исключения. Указатель на соответствующую об-

Рис. 2. Структура Language-Specific Data Area

46

№ 6(30) 2010

ласть данных LSDA содержится в структурах Frame Description Entry секции .eh_frame.

Генерация исключения производится

через вызов функции_cxa_throw. Функция

принимает три аргумента:

1) указательнаобъектискпючения;

2) указатель на структуру typeinfo типа генерируемого исключения;

3) указатель на деструктор объекта исключения.

Память под объект исключения выделяется с помощью функции__cxa_allocate_

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

Главное достоинство исключений Dwarf2 — отсутствие накладных расходов при входе в функции, try-блоки и выходе из них.

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

Листинг 3. Алгоритм вое

записи в таблице типов. Этот номер запи- Si сывается в регистр edx перед переходом £ на площадку приземления. В регистр еах передается адрес объекта исключения. В площадке приземления происходит пере- g ход на обработчик нужного типа иекпюче- ^ ний в зависимости от переданного номера ^ (см. листинг 4). Площадка приземления все- ^ гда содержит принципиально одну и ту же Sg последовательность инструкций. Передан- g ный индекс сравнивается с возможными для данной площадки значениями, осуществля- сэ ется переход на код нужного блока catch. ^ Меняется только количество сравнений и переходов в зависимости от количества блоков catch. Поэтому адреса участков кода catch-блоков легко восстановить путем шаблонного анализа кода площадки приземления.

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

ановления блоков try-catch в формате Dwarf2

program = Program

eh frame = program.sections['.eh frame']

for fde in eh frame.frameDescriptionEntries:

for cs in fde.lsda.callSites:

print 'call site starts at , fde.start + cs.start

print 'call site ends at', fde.start + cs.start + cs.size

if cs.landingPad:

print 'landing pad at', fde.start + cs.landingPad

for ar in cs.actionRecords

Print 'region has handler for type' , ar.typeinfo

ного формата, то восстановление блоков try-catch заключается в разборе структур данных в секциях .eh_frame и .gcc_except_ table. Алгоритм такого разбора представлен в листинге 3.

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

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

Листинг 4.

Пример кода площадки приземления Dwarf2

movl %eax, -24

movl %edx, -28

cmpl $3, - 28 3

je .L2

cmpl $2, - 28 3

je .L4

cmpl $1, - 28 3

je .L5

movl -24(% ebp), %eax

movl %eax,

.LEHB1:

call Unwind Resume

.LEHE1:

№ 6(30) 2010

Обработка исключений SjLj

Способ обработки исключений SjLj используется по умолчанию компилятором MinGW1 GCC версии 3.4 на платформе Win32. Для сохранения и восстановления контекста исполнения применяют функции setjmp () и longjmp () или аналогичные им (это и дало название SjLj).

Исключения SjLj используют динамические структуры данных. При входе в функцию на стеке создается и заполняется SjLj_ Function_Context. Она содержит указатель на область данных LSDA, соответствующую функции. Bbi30B0M_Unwind_SjLj_Register созданная структура добавляется в конец стека таких структур, реализованного в виде связного списка (см. листинг 5). Перед выходом из функции происходит вызов _Unwind_ SjLj_Unregister, выталкивающий структуру из связного списка. Указанные структуры и функции определены в файле unwind-sjlj.c, являющемся частью компилятора GCC.

Формат области LSDA аналогичен ее формату в Dwarf2, но, в отличие от Dwarf2, запись места вызова (Call Site Entry) в SjLj

1 Minimalist GNU for Windows — порт GNU Compiler Collection (GCC), разработанный Microsoft Windows вместе с набором свободно распространяемых библиотек импорта и заголовочных файлов для Windows API.

не содержит информацию о его положении в коде. Вместо этого номер текущего региона и адрес площадки приземления хранятся в структуре SjLj_Function_Context и модифицируются по ходу работы функции.

Как и в Dwarf2, для генерации нестандартных исключений используется функция _cxa_throw. В отличие от Dwarf2, при

реализации исключений SjLj возникают накладные расходы, связанные с обслуживанием стека структур SjLj_Function_Context и присутствующие независимо оттого, происходит в работающей программе генерация исключений или нет. В качестве способа обработки исключений MinGW GCC версии 4 по умолчанию использует Dwarf2. Ввиду наличия описанных проблем с производительностью у исключений SjLj и последовательного отказа от их применения в компиляторе GCC в пользу Dwarf2 восстановление информации об обработчиках исключений SjLj в данной статье не рассматривается.

Обработка исключений в компиляторе Microsoft Visual С++

В компиляторе Microsoft Visual С++ для платформы Win32 исключения языка Си++ реализованы поверх структурной обработки исключений.

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

Регистрация функций-обработчиков исключений осуществляется посредством заполнения и регистрации структуры EXCEPTION_REGISTRATION [12] (см. листинг 6). Ее данные должны располагаться на стеке процесса или потока. Поле handler содержит указатель на пользовательский обработчик исключений; поле prev — указатель на предыдущую структуру в связанном списке

Листинг 5. Пример кода пролога функции, использующей исключения SjLj

Z1gv:

pushl %ebp

movl %esp %ebp

pushl %edi

pushl %esi

pushl %ebx

subl $92, %esp

movl $_ gxx personality sj0,

-44

movl $LLSDA14 2 6, -4 0

leal -36( %ebp), %eax

leal -12( %ebp), %edx

movl %edx

movl $L21 %edx

movl %edx 4

movl %esp 8

leal -68( %ebp), %eax

movl %eax

call Unwind SjLj Register

№ 6(30) 2010

структур ЕХСЕРТЮ1\1_РЕС13ТРАТЮ1\1. Вершины их стека расположены по адресу ЕЭ: [О].

Когда возникает исключение, операционная система проходит по связному списку

Листинг 6.

Структура ЕХСЕРТЮМ_РЕС18ТРАТЮМ

REGISTRATION содержит листинг 7. Ее расположение на стеке показано на рис. 3.

Компилятор Си++ для каждой функции создает и регистрирует отдельный SEH-об-

Листинг 7. Расширенная структура EXCEPTION_REGISTRATION

struct

EXCEPTION_REGISTRATION {

EXCEPTION_REGISTRATION *prev; DWORD handler;

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

Обработка исключений языка Си++

Для реализации исключений языка Си++ структура EXCEPTION_REGISTRATION расширяется дополнительными полями id и ebp. Формат расширенной структуры EXCEPTION.

struct

EXCEPTION_REGISTRATION {

EXCEPTION_REGISTRATION *prev; DWORD handler; int id; DWORD ebp;

работчик, код которого завершается вызовом функции__СххЕгатеНапсНегЗ. Пример

такого обработчика представлен в листинге 8. Обработчик выполняет проверку магического значения на стеке, используемого для обнаружения ошибок типа его переполнения, далее записывает адрес структуры !ипст!о в регистр еах и вызывает обработчик исключений языка Си++ из библиотеки времени выполнения компилятора. Структура {ипс^о имеет указа-

Рис. 3. Структура стека в программе, скомпилированной Microsoft Visual С++}

49

№ 6(30) 2010

+

-а Й

I

I §

£ Si

0

1 §

£

S ! I

I §

«

ss

u u о BQ

тель на таблицу try-блоков, каждая запись в которой содержит указатель на таблицу соответствующих catch-блоков [6]. Поля этой и связанных с ней структур изображены на рис. 4, где:

• signature — сигнатура структуры funcinfo, всегда равна 0x19930 522;

• unwind_count — количество записей в таблице раскрутки стека;

• punwind_table — указатель на таблицу раскрутки стека;

• tryblock_count — количество записей в таблице try-блоков;

• ptryblock_table — указатель на таблицу try-блоков;

• startjd — минимальный id, обрабатываемый try-блоком;

• endjd — максимальный id, обрабатываемый try-блоком;

• catchblock_count — количество catch-блоков в try-блоке;

• pcatchblock_table — таблица catch-блоков try-блока;

• val_or_ref — ненулевое значение, если оно передается по ссылке;

• ptypejnfo — указатель на typeinfo типа обрабатываемого исключения;

• offset — смещение относительно ebp объекта исключения на стеке (0 соответствует отсутствию объекта);

• catchblock_addr — адрес обработчика.

Листинг 8. Обработчик исключения SEH

ehhandler$ ?g@@YAXXZ:

mov edx, DWORD PTR [esp+8]

lea eax, DWORD PTR [edx+12]

mov ecx, DWORD PTR [edx-2 4]

xor ecx, eax

call

@ security check cookie@4

mov eax, OFFSET

ehfuncinfo$? g@@YAXXZ

jmp CxxFrameHandler3

Поиск подходящего обработчика исключения Си++ выполняется по значению, записанному в поле id структуры EXCEPTION. REGISTRATION функции в момент генерации исключения. При этом просматриваются все try-блоки из структуры funcinfo.

Рис. 4. Структура funcinfo и связанные структуры

50

№ 6(30) 2010

Если для очередного try-блока выполняется startjd < id < end_id, то среди catch-бло-ков данного try-блока производится поиск обработчика для типа сгенерированного исключения. Если подходящий обработчик найден, он вызывается. В противном случае просмотр списка try-блоков продолжается.

Значение поля id модифицируется во время работы функции в соответствии со структурой блоков обработки исключений исходной программы. Таким образом, использование исключений Microsoft Visual С++ порождает дополнительные накладные расходы по созданию и поддержанию в актуальном состоянии структуры EXCEPTION. REGISTRATION. Данные накладные расходы присутствуют вне зависимости оттого, происходит генерация исключений в исполняемой программе или нет.

Восстановление регионов try-catch

Границы регионов try-catch в программах, использующих исключения Microsoft Visual С++, заданы неявно через значения id. Для восстановления границ достаточно выполнить следующие действия:

1. С помощью сравнения с шаблонами выделить функции, формирующие на стеке структуру EXCEPTION_REGISTRATION, для каждой такой функции определить регистрируемый SEH-обработчик.

2. В найденных обработчиках установить адрес структуры funcinfo, передаваемый функции_CxxFrameHandler.

3. Выполнить разбор структур funcinfo.

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

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

3. Особенности структуры потока управления программ на языке Си++

Разбиение программы на функции — один из этапов декомпиляции. Наиболее часто он выполняется с помощью анализа графа потока управления. Граф рассматривается как неориентированный, в нем выделяются компоненты связности. Найденные компоненты связности принимаются за графы потоков управления функций.

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

Листинг 1 содержит пример простейшей программы, использующей обработку исключений. На рисунке 5 изображен граф потока управления этой программы, оттранслированной компилятором Microsoft Visual С++ под платформу Win32, на котором обработчики исключений ошибочно выделены в отдельные функции из-за того, что не связаны дугами с другими вершинами функции main.

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

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

№ 6(30) 2010

0: label "_main"

1: push ebp 2: mov ebp, esp 3: push Oxffffffff

4: push "_ehhandterS_main"

5; mov eax, dword ptr [fs] 6: push eax 7: push ecx 8: push eo< 9: push ebx 10: push esi II: push edi

12: mov eax, dword ptr ["_secunty_coofcie"]

13: xor eax, ebp 14: push ««x 15: lea eax, dword ptr [Oxfffffffi) + ebp + 0x4] 16; mov dword ptr (fs], eax 17: mov dword ptr fOxfffffifO + ebp], esp 18: mov dword ptr (OxfffffffO + ebp + Oxc], 0x0 19: mov dword ptr [Oxffffffec + ebp], 0x22

20: push "_TI1H" 21: lea eax, dword ptr [Oxffffffec 4- ebp] 22: push eax 23: call "_CxxThrowException@>8"

24: label "_catchS_marnjO" 25: mov eax, "_tryerwiS_ma5n$3" 26: ret OxO

46: label "_ehhandler$_main"

47: mov edx, dword ptr (esp + 0x8] 48: Г«а eaxr dword ptr [edx + Oxc] 49: mov ecx, dword ptr [edx + 0xffffffe8) 50; xor ecx, eax

Si: call _security_check_cookie<S>4"

52: mov eax. "_ehfur»cinfo$_mairV"

53: jmp "_CxxFram*Handf#r3"

27: label "_catchS_maän$l"

28: mov eax, "_tryern±$_main$3"

29: ret 0x0

30: label "_catchs maiл$2"

31: mov eax, "_tryend$_main$3"

32: ret 0x0

33: label "_instr33**

33: mov dword ptr [OxfffffffO + ebp + Oxc], Oxffffffff

34: label "_tryend$_main$3"

35. xor eax. eax

36: label "5LN3@main" I/ ecx, dword ptr [OxfffffffO + ebp + 0x4] 38: mov dword ptr [fs], ecx 39: pop ecx 40: pop edl 41 pop esi 42: pop ebx 43: mov esp, ebp 44: pop ebp 45. ret 0x0

Рис. 5. Граф потокауправления программы, содержащейся в листинге 1

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

4. Реализация и экспериментальная проверка

В рамках декомпилятора TyDec была выполнена прототипная реализация предложенных в предыдущих разделах подходов и методов. В него добавлена поддержка восстановления конструкций try-catch из программ на языке ассемблера для архитектуры х86, полученных в результате трансляции программ на языке Си++ компиляторами GCC, Intel С++ Compiler, Microsoft Visual С++ на платформах Linux и Windows.

Реализованная поддержка обработки исключений языка Си++ состоит из следующих основных этапов:

1) разбор и представление платформо-зависимой информации об исключениях Dwarf2 и Microsoft Visual С++;

2) реконструкция графа потока управления с учетом разобранной платформо-зави-симой информации;

3) разметка регионов try-catch и обработчиков исключений.

Прототипная реализация протестирована на наборе примеров, численные характеристики которых представлены в табл. 1. Колонка CLOC содержит количество строк в коде исходной программы; колонка ALOC — количество строк в программе на языке ассемблера, полученной в результате компиляции исходной программы.

Для всех тестов было успешно выполнено восстановление структур данных, связанных с исключениями, блоков обработки исключений. Далее приведены примеры вывода декомпилятора для некоторых функций из программ тестового набора (см. листинги 9,10).

№ 6(30) 2010

Таблица 1

Примеры для тестирования

Пример CLOC ALOC Описание

libantlr 2.7.7 (msvc) 3377 204 808 Библиотека генератора лексических и синтаксических анализаторов А1\1Т1Д скомпилированная МюгозоЙУ^иа! С++ 2005 для W¡n32

libantlr 2.7.7 (dwarf2) 3377 137 786 Библиотека генератора лексических и синтаксических анализаторов А1\1Т1Д скомпилированная вСС 4.3 для Ыпихх86

« !

ЛистингЭ. Восстановленная функция antlr::A NTLRException::~ANTLRException () из примера libantlr 2.7.7 (Dwarf2), файл ANTLRException. hpp

Листинг 10. Восстановленная функция TokenStreamSelector::nextToken () из примера libantlr 2.7.7 (msvc), файл TokenStreamSelector.cpp

Void _ZN5antlr14ANTLRExceptionD0Ev(struc

t structO* argl) {

int * vl;

struct structO * eax2;

void * ebp3;

int eax4;

int edx5;

void * ebp6;

int v7;

void * ebp8;

argl-

>field0 = &_ZTVN5antlrl4ANTLRExceptionE->field8;

v1=(int*)&argl->field4;

_LEHB2 4:

/* start of try block for landing pad .L268 */

_ZNSsD1Ev(v1); _LEHE24:

/* end of try block for landing pad .L268 */ eax2 = 1; if(eax2!=0) {

eax2=_ZdlPv(arg1); goto _LEHE25;

}

L268: /* landing pad */ *(int*)(ebp3+ -1)=eax4; if(edx5==-1) goto _L2 65;

v7=*(int*)(ebp6+ -1); _LEHB25: /* start of try block without landing pad */

Unwind Resume (v7); L265: /* exception handler for typeinfo at 16777471 */

eax2 = cxa call unexpected (*(int*)(ebp8+ -1));

_LEHE25: /* end of try block without landing pad */ return;

}

void ?nextToken@TokenStreamSelector@ antlr@

@UAE?AV?$TokenRefCount@VToken@antlr@@@2S

XZ

(int argl) {

void * espl; void * esp2;

void * ebp3; int v4;

int v5; int v6;

int ebx7; int v8;

int esi9; int v10;

int edill; int v12;

void * * fs13;

void * v14;

struct struct8 * v15;

struct struct8 * ecx16;

int v17; int v18;

int v19;

void * ebp2 0;

void * ebp21;

int * fs22;

esp1 = esp2 - 4;

ebp3 = esp1;

v4 = -1;

v5 = ehhandler$?nextToken@ TokenStreamSelector@antlr@

@UAE?AV?$TokenRefCount@VToken@

antlr@@@2@XZ; v6 = ebx7; v8 = esi9; v10 = edi11;

v12 = * security cookie^(int)ebp3; * fs13 = (void*)(-12 +((int)ebp3+4)); v14 = (void*)((int)esp1-4-4-4-4-8-4-4-4-4);

v15 = ecx16;

v17 = 0; /* start of try block 0 */ $LN3@nextToken: v18 = 0;

**vl5->fieldl6)(argl, vl2, vlO, v8, v6, v15, v14, v5, v4); v19 = v17 | 1; goto $LN4@nextToken;

v 53

№ 6(30) 2010

Заключение

В настоящей статье исследованы и предложены методы восстановления управляющих конструкций обработки исключений языка Си++ try-catch и оператора генерации исключений throw.

Так как способ реализации исключений Си++ не определен стандартом языка и зависит от используемого компилятора и целевой платформы, рассмотрены наиболее популярные способы реализации на платформах Linux и Windows и архитектуре х86 и х86-64. Для исключений Dwarf2, являющимся де-факто стандартом в среде GNU/Linux и используемых компилятором GCC на платформе Win32 исключений, реализованных в компиляторе Microsoft Visual С++ для платформы Win32, описаны внутренние структуры данных и предложены методы их анализа и восстановления границ регионов try-catch.

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

5 переходов. Эта особенность приводит к не! правильному восстановлению границ функ-! ций при использовании алгоритма, базирую-g щегося исключительно на выделении компо-! нент связности в графе потока управления. ¡5 Авторами предложен метод, позволяющий

путем добавления дополнительных дуг вос-

0 становить связность подграфов, соответст-

1 вующих функциям исходной программы на ^ Си+ + , и делающий граф подходящим для g применения указанного алгоритма разбие-I ния на функции.

Ц В рамках декомпилятора TyDec выпол-

I нена прототипная реализация предложен-

<§ ных методов, включающая разбор информа-

S, ции об исключениях Dwarf2 и Microsoft Visual

I С++, реконструкцию графа потока управле-

I ния и разметку обнаруженных регионов try-

с catch. Реализованная функциональность

6 протестирована на Си++-библиотеке гене-оо ратора лексических и синтаксических ана-

лизаторов ANTLR, и получены положительные результаты. Регионы обработки исключений были восстановлены полностью.

Представленный в статье метод восстановления управляющих конструкций реализован так же, как plug-in к интерактивному дизассеблеру IdaPro.

Описок литературы

1. Деревенец Е. О., Долгова К. Н. Структурный анализ в задаче декомпиляции // Прикладная информатика. 2009. №4. С. 63-80.

2. Muchnick Steven S. Advanced Compiler Design and Implementation, chapter 7. Morgan Kaufmann, 1997.

3. Troshina Katerina, ChernovAlexander, Derevenets Yegor. С Decompilation: Is It Possible? // Perspectives of System Informatics, June 2009.

4. Skochinsky Igor. Reversing Microsoft Visual С++ Parti: Exception Handling, http://www.openrce.org/ articles/full_view/21.

5. IDA Pro Disassembler — multi-processor, windows hosted disassembler and debugger. http://www.hex-rays.com/idapro/.

6. Kochhar Vishal. How a С + + compiler implements exception handling. April 2002. http://www.codeproject.com/KB/cpp/exception-handler.aspx.

7. Itanium С++ ABI, Section 7. Exception Handling Tables. http://www.codesourcery.com/public/cxx-abi/ exceptions.pdf.

8. ISO/IEC 14 882:1998. Standard for the С++ Programming Language.

9. System V Application Binary Interface AMD64 Architecture Processor Supplement, Section 6.2. Unwind Library Interface, December 2007. http://www.x86-64.org/documentation/abi.pdf.

10. The DWARF Debugging Standard. http://dwarfstd.org/.

11. Linux Standard Base Core Specification 3.0RC1, Chapter 8. Exception Frames. http://refspecs.freestandards.org/ LSB_3.0.0/ LSB-Core-generic/LSB-Core-generic/ehfra-mechpt.html.

12. Pietrek Matt. A Crash Course on the Depths of Win32 Structured Exception Handling. // Microsoft Systems Journal, January 1997. http://www.microsoft.com/ msj/0197/exception/exception.aspx.