| Категория >  Новости > Фундаментальные основы хакерства. Разбираем самодеятельность компиляторов при трансляции оператора выбора - «Новости» Фундаментальные основы хакерства. Разбираем самодеятельность компиляторов при трансляции оператора выбора - «Новости»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 -18hvar_14= dword ptr -14h; Резервируем место для локальных переменныхsub     rsp, 38h; Инициализируем локальные переменные:; var_14 присваиваем значение 0х666, следовательно, это переменная amov     [rsp+38h+var_14], 666hmov     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 с 1cmp     [rsp+38h+var_18], 1; В случае успеха прыгаем внутрь блока кода для вывода "a == 1"; Этот код получен трансляцией ветки case 1: printf("a == 1");; Иначе продолжаем выполнениеjzshort loc_140001123; Сравниваем значение var_18 с 2cmp     [rsp+38h+var_18], 2; В случае равенства выводим "a == 2"; Этот код получен трансляцией ветки case 2: printf("a == 2");; Иначе продолжаем выполнениеjzshort loc_140001131; Сравниваем var_18 и 0x666cmp     [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    printfjmp     short loc_140001159 ; break; ------------------------------------------------loc_140001131:; CODE XREF: main+27↑j; printf("a == 2");lea     rcx, aA2; "a == 2"call    printfjmp     short loc_140001159 ; break; ------------------------------------------------loc_14000113F:; CODE XREF: main+31↑j; printf("a == 666h");lea     rcx, aA666h     ; "a == 666h"call    printfjmp     short loc_140001159 ; break; ------------------------------------------------loc_14000114D:; CODE XREF: main+33↑j; printf("Default");lea     rcx, aDefault   ; "Default"call    printfloc_140001159: ; Конец SWITCH           ; CODE XREF: main+41↑j; main+4F↑j ...; Возвращаем 0xor     eax, eax; Восстанавливаем стекadd     rsp, 38hretnmainendpВыглядит довольно прямолинейно. Дизассемблерный листинг можно условно разделить на две части: первая часть – сам оператор выбора, откуда каждое условие передает управление во вторую часть — конкретную ветку, соответствующую этому условию. Для сравнения взглянем, какой код построит C++Builder 10 на основе этой же программы: public mainmainproc near ; DATA XREF: __acrtused+29↑o; Как много локальных переменных!var_38= dword ptr -38hvar_34= dword ptr -34hvar_30= dword ptr -30hvar_2C= dword ptr -2Chvar_28= dword ptr -28hvar_24= dword ptr -24hvar_20= dword ptr -20hvar_1C= dword ptr -1Chvar_18= dword ptr -18hvar_14= dword ptr -14hvar_10= qword ptr -10hvar_8= dword ptr -8var_4= dword ptr -4; Открываем кадр стекаpush    rbp; Резервируем место для локальных переменныхsub     rsp, 60h; В RBP сохраняем указатель на дно стекаlea     rbp, [rsp+60h]; Инициализируем локальные переменные:mov     [rbp+var_4], 0mov     [rbp+var_8], ecxmov     [rbp+var_10], rdx; var_14 присваиваем значение 0х666, следовательно, это переменная amov     [rbp+var_14], 666h; В ECX помещаем значение var_14mov     ecx, [rbp+var_14]; Следующим элегантным образом сравниваем значение var_14 с нулемtest    ecx, ecxКоманда  mov     [rbp+var_18], ecxПерейти обратно к новости |