Категория > Новости > Фундаментальные основы хакерства. Разбираем самодеятельность компиляторов при трансляции оператора выбора - «Новости»
Фундаментальные основы хакерства. Разбираем самодеятельность компиляторов при трансляции оператора выбора - «Новости»12-06-2022, 00:01. Автор: Григорий |
странице автора.Ищем операторы switch-case-break в бинарном кодеДля улучшения читабельности программ в язык C был введен оператор множественного выбора — Легко показать, что
Если изобразить это ветвление в виде логического дерева, то образуется характерная «косичка». Трансляция оператора switch в общем случае Казалось бы, идентифицировать Однако в реальной жизни все происходит совсем не так. Компиляторы (даже неоптимизирующие) транслируют
#include <stdio.h>
int main()
{
int a = 0x666;
switch (a)
{
case 0:
printf("a == 0");
break;
case 1:
printf("a == 1");
break;
case 2:
printf("a == 2");
break;
case 0x666:
printf("a == 666h");
break;
default:
printf("Default");
}
}
Вывод приложения switch_casesТеперь посмотрим в IDA на результат дизассемблирования. Дерево распустило ветки во все стороны. МОЖНО сделать однозначный вывод: в дизассемблируемой программе присутствует оператор множественного выбора switch-case
mainproc near ; CODE XREF: __scrt_common_main_seh+107↓p
; DATA XREF: .pdаta:0000000140004018↓o
; Объявляем две локальные переменные,
; но почему две, если в исходном коде объявлена только одна?
var_18= dword ptr -18h
var_14= dword ptr -14h
; Резервируем место для локальных переменных
sub rsp, 38h
; Инициализируем локальные переменные:
; var_14 присваиваем значение 0х666, следовательно, это переменная a
mov [rsp+38h+var_14], 666h
mov eax, [rsp+38h+var_14]
Переменной
mov [rsp+38h+var_18], eax
; Сравниваем значение var_18 с нулем
cmp [rsp+38h+var_18], 0
; Если сравнение успешно, переходим в блок кода, выводящий в консоль "a == 0"
; Этот код получен трансляцией ветки case 0: printf("a == 0");
; Иначе продолжаем выполнение
jzshort loc_140001115
; Сравниваем значение var_18 с 1
cmp [rsp+38h+var_18], 1
; В случае успеха прыгаем внутрь блока кода для вывода "a == 1"
; Этот код получен трансляцией ветки case 1: printf("a == 1");
; Иначе продолжаем выполнение
jzshort loc_140001123
; Сравниваем значение var_18 с 2
cmp [rsp+38h+var_18], 2
; В случае равенства выводим "a == 2"
; Этот код получен трансляцией ветки case 2: printf("a == 2");
; Иначе продолжаем выполнение
jzshort loc_140001131
; Сравниваем var_18 и 0x666
cmp [rsp+38h+var_18], 666h
; Если равно, выводим "a == 666h"
; Этот код получен трансляцией ветки case 0x666: printf("a == 666h");
jzshort loc_14000113F
Если мы досюда добрались, значит, ни одно условие не сработало, поэтому выполняем дефолтное действие: делаем безусловный переход в блок кода для вывода строчки Default. Этот код получен трансляцией ветки
jmp short loc_14000114D
; ------------------------------------------------
loc_140001115:; CODE XREF: main+19↑j
; printf("a == 0");
lea rcx, _Format ; "a == 0"
call printf
А вот этот безусловный переход, выносящий управление за пределы
jmp short loc_140001159 ; break
; ------------------------------------------------
loc_140001123:; CODE XREF: main+20↑j
; printf("a == 1");
lea rcx, aA1; "a == 1"
call printf
jmp short loc_140001159 ; break
; ------------------------------------------------
loc_140001131:; CODE XREF: main+27↑j
; printf("a == 2");
lea rcx, aA2; "a == 2"
call printf
jmp short loc_140001159 ; break
; ------------------------------------------------
loc_14000113F:; CODE XREF: main+31↑j
; printf("a == 666h");
lea rcx, aA666h ; "a == 666h"
call printf
jmp short loc_140001159 ; break
; ------------------------------------------------
loc_14000114D:; CODE XREF: main+33↑j
; printf("Default");
lea rcx, aDefault ; "Default"
call printf
loc_140001159: ; Конец SWITCH ; CODE XREF: main+41↑j
; main+4F↑j ...
; Возвращаем 0
xor eax, eax
; Восстанавливаем стек
add rsp, 38h
retn
mainendp
Выглядит довольно прямолинейно. Дизассемблерный листинг можно условно разделить на две части: первая часть – сам оператор выбора, откуда каждое условие передает управление во вторую часть — конкретную ветку, соответствующую этому условию. Для сравнения взглянем, какой код построит C++Builder 10 на основе этой же программы:
public main
mainproc near ; DATA XREF: __acrtused+29↑o
; Как много локальных переменных!
var_38= dword ptr -38h
var_34= dword ptr -34h
var_30= dword ptr -30h
var_2C= dword ptr -2Ch
var_28= dword ptr -28h
var_24= dword ptr -24h
var_20= dword ptr -20h
var_1C= dword ptr -1Ch
var_18= dword ptr -18h
var_14= dword ptr -14h
var_10= qword ptr -10h
var_8= dword ptr -8
var_4= dword ptr -4
; Открываем кадр стека
push rbp
; Резервируем место для локальных переменных
sub rsp, 60h
; В RBP сохраняем указатель на дно стека
lea rbp, [rsp+60h]
; Инициализируем локальные переменные:
mov [rbp+var_4], 0
mov [rbp+var_8], ecx
mov [rbp+var_10], rdx
; var_14 присваиваем значение 0х666, следовательно, это переменная a
mov [rbp+var_14], 666h
; В ECX помещаем значение var_14
mov ecx, [rbp+var_14]
; Следующим элегантным образом сравниваем значение var_14 с нулем
test ecx, ecx
Команда
mov [rbp+var_18], ecx
Перейти обратно к новости |