Ку, киберрекруты. Продолжаю курс реверса с использованием radare2. Сегодня я расскажу вам о некоторых базовых структурах данных, таких как одномерные массивы и массивы char, которые можно интерпретировать как строки.
Первая часть
Вторая часть
Мы будем рассматривать пример за примером, поскольку я считаю, что это один из самых простых способов обучения, и мы сосредоточимся на понимании дизазма, а не на объяснении множества функций r2.
Массивы
Массивы - это простые структуры данных, содержащие один или несколько элементов определенного типа. Массивы позволяют нам легко ссылаться на несколько элементов с помощью одного единственного тега, вместо того чтобы объявлять множество переменных.
Рассмотрим следующий код:
Там мы объявляем числовой массив из 5 целых чисел, мы можем ссылаться на эти позиции по их числовому индексу, затем мы можем рассматривать каждую из них как отдельную переменную. Так что на практическом уровне особой разницы нет.
Давайте проверим это внутри r2
Опять же, мы можем разбить наш анализ на разные части. Если мы посмотрим внимательно, то увидим, что внутри функции объявлены некоторые "переменные":
Как мы уже видели ранее, local_8h связана с защитой стека, поэтому здесь нет проблем, но мы видим там еще 6 переменных, странно, ведь мы только что объявили массив и переменную sum.
Если мы проследим за этими 6 переменными в коде, то увидим:
Итак, они инициализируются на 200, 150, 100, странное значение и 300. Подождите, что это за странное значение? Оно должно быть -50!
ммм... но если мы используем rax2
Проблема решена. Как мы видим, компилятор интерпретирует массив как набор переменных, выделенных в памяти одна за другой. Кстати, мы можем легко проверить это, поставив точку останова после завершения инициализации:
И сбросить начальный адрес памяти:
Вот и все, все эти переменные собираются в памяти одна за другой, как это делают массивы.
То, что происходит дальше, не представляет для нас никакой сложности. Регистр edx используется для хранения суммы всех этих чисел, затем переменная sum используется для хранения конечного значения и бам! printf.
Давайте теперь проверим то же самое, но используя вместо этого одиночные переменные:
Здесь больше нечего объяснять... как я уже сказал... одно и то же! Так что здесь компилятор определил, что код делает точно то же самое, и поэтому решил обработать его таким же образом...
Но вы спросите себя: хорошо, массив из 5 позиций может быть прочитан как 5 независимых переменных, но что если у нас есть массив из 100 элементов? Будет ли это выглядеть так же странно?
Давайте посмотрим!
Сначала мы объявляем, затем заполняем, в конце печатаем.
Как и ожидалось, мы не видим там 100 независимых переменных, вместо этого мы видим три из них, вещь для защиты стека, затем еще пару, которые могут соответствовать первой и второй для индексов.
Вместо этого программа будет использовать регистры в качестве индексов, давайте проверим это, скажем, для первого цикла, того, который инициализирует массив:
Сначала инициализируется переменная "local_1a8h", затем происходит переход сюда:
После прыжка программа сравнивает инициализированное значение с 99 (i < 100), затем, если оно меньше 99, возвращается назад, чтобы выполнить код, находящийся внутри цикла. Если же оно равно или больше, программа продолжит выполнение.
Сначала программа перемещает содержимое значения (i) в eax, затем, поскольку в данном случае eax - это только 32-битная часть RAX, она использует cdqe для значения, чтобы оно работало в 64-битном режиме.
P.S.
В 64-битном режиме размер операции по умолчанию равен размеру регистра назначения. Использование префикса REX.W продвигает эту инструкцию (CDQE при продвижении) для работы с 64-битными операндами. В этом случае CDQE копирует знак (бит 31) двойного слова в регистре EAX в старшие 32 бита RAX.
После этого он перемещает результат в edx (edx здесь используется как временный регистр) и, наконец, сохраняет его в своей позиции внутри массива.
Но как вычисляется эта позиция в массиве? У нас есть следующее: [rbp + rax4 - 0x1a0], то же самое можно представить как: [(rbp - 0x1a0) + rax4] И как мы можем легко видеть: (rbp - 0x1a0) - это просто место внутри памяти, место, которое было зарезервировано в памяти для массива,
После этого на каждой итерации добавляется rax*4, как мы знаем, в этот момент rax будет содержать значение нашей переменной (i), так как int имеет размер 4, значение rax будет умножено на 4, чтобы правильно распределить int по его позиции внутри массива.
Короче говоря, (rbp - 0x1a0) - это базовый адрес массива, а rax*4 - индекс.
Остальная часть кода, то же самое, партнер.
Инициализация массивов
Массивы могут быть предварительно инициализированы, но в данном случае это будет одно и то же:
То же самое
Массивы символов / строки
Здесь все становится немного интереснее, на этот раз мы объявим массив char, который может быть интерпретирован как строка, мы будем использовать scanf для чтения из пользовательского ввода:
В данном случае у нас есть только одна переменная local_30h.
local_30h находится на расстоянии 0x30 (48 dec) байт от rbp, поэтому вполне логично, что этот var является текстовым массивом char, в котором будет размещена наша строка.
Мы можем поставить точку останова в точке, где на него ссылаются в первый раз, прямо перед прыжком в scanf.
Поэтому давайте запишем этот адрес: 0x7ffdfa5e7920, так как наша строка будет храниться там.
Вот оно! Остальная часть кода довольно проста, local_30h будет снова передана в printf и выведен результат.
Как обычно, мы можем получить доступ к элементам массива char по отдельности, используя индекс, рассмотрим этот вариант:
Эта программа в основном объявляет массив char, читает из пользовательского ввода, чтобы сохранить текст прямо там, а затем обращается к массиву как "глобально", так и по индексу.
Внутри radare2 это выглядит следующим образом.
Прежде всего, мы обнаруживаем две переменные:
Первый, мы его уже знаем, это может быть базовый адрес массива символов, но тогда другой… он расположен всего в одном байте от базового адреса… давайте это учтем. Мы даже переименовали их, чтобы было понятнее.
Мы уже знакомы с тем, как «строка» читается с помощью scanf:
Но тогда мы получаем это:
В первой инструкции мы видим, что movzx извлекает первый байт из text_array (Копирует содержимое исходного операнда (регистра или ячейки памяти) в целевой операнд (регистр) и нулем расширяет значение. Размер преобразованного значения зависит от операнда -размер атрибута.)
Затем этот байт (al) перемещается в edx с помощью movsx (копирует содержимое исходного операнда (регистра или ячейки памяти) в целевой операнд (регистр), а знак расширяет значение до 16 или 32 бит)
Мы можем легко сделать вывод, что это в основном извлекает первый символ массива, затем указатель на этот массив перемещается в rsi через rax, и мы вызываем printf.
Следующий блок кода делает то же самое, используя обнаруженную нами переменную «pos1» вместо «index». Давайте посмотрим, на этот раз мы можем отладить его для более легкой визуализации.
После установки точки останова прямо перед movsx, если мы проверим регистр, мы увидим, что
Точно, rdx (и al) = 52 = код ascii для R, являющегося ARTIK входом. Тайна раскрыта
Источник:
Первая часть
Вторая часть
Мы будем рассматривать пример за примером, поскольку я считаю, что это один из самых простых способов обучения, и мы сосредоточимся на понимании дизазма, а не на объяснении множества функций r2.
Массивы
Массивы - это простые структуры данных, содержащие один или несколько элементов определенного типа. Массивы позволяют нам легко ссылаться на несколько элементов с помощью одного единственного тега, вместо того чтобы объявлять множество переменных.
Рассмотрим следующий код:
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
int num[5];
int sum;
num[0] = 200;
num[1] = 150;
num[2] = 100;
num[3] = -50;
num[4] = 300;
sum = num[0] + num[1] + num[2] + num[3] + num[4];
printf("SUM IS %d", sum);
}
Там мы объявляем числовой массив из 5 целых чисел, мы можем ссылаться на эти позиции по их числовому индексу, затем мы можем рассматривать каждую из них как отдельную переменную. Так что на практическом уровне особой разницы нет.
Давайте проверим это внутри r2
Код:
[0x7f84fc8ab090]> afl
0x562febd98000 5 292 -> 293 sym.imp.__libc_start_main
0x562febd98588 3 23 sym._init
0x562febd985b0 1 6 sym.imp.__stack_chk_fail
0x562febd985c0 1 6 sym.imp.printf
0x562febd985d0 1 6 sym.imp.getchar
0x562febd985e0 1 6 sub.__cxa_finalize_248_5e0
0x562febd985f0 1 43 entry0
0x562febd98620 4 50 -> 40 sym.deregister_tm_clones
0x562febd98660 4 66 -> 57 sym.register_tm_clones
0x562febd986b0 4 49 sym.__do_global_dtors_aux
0x562febd986f0 1 10 entry1.init
0x562febd986fa 1 26 sym.main
0x562febd98714 3 129 sym.funcion
0x562febd987a0 4 101 sym.__libc_csu_init
0x562febd98810 1 2 sym.__libc_csu_fini
0x562febd98814 1 9 sym._fini
0x562febf98fe0 1 1020 reloc.__libc_start_main_224
[0x7f84fc8ab090]> s sym.funcion
[0x562febd98714]> pdf
/ (fcn) sym.funcion 129
| sym.funcion ();
| ; var int local_24h @ rbp-0x24
| ; var int local_20h @ rbp-0x20
| ; var int local_1ch @ rbp-0x1c
| ; var int local_18h @ rbp-0x18
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x562febd98703 (sym.main)
| 0x562febd98714 55 push rbp
| 0x562febd98715 4889e5 mov rbp, rsp
| 0x562febd98718 4883ec30 sub rsp, 0x30 ; '0'
| 0x562febd9871c 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x562febd98725 488945f8 mov qword [local_8h], rax
| 0x562febd98729 31c0 xor eax, eax
| 0x562febd9872b c745e0c80000. mov dword [local_20h], 0xc8 ; 200
| 0x562febd98732 c745e4960000. mov dword [local_1ch], 0x96 ; 150
| 0x562febd98739 c745e8640000. mov dword [local_18h], 0x64 ; 'd' ; 100
| 0x562febd98740 c745ecceffff. mov dword [local_14h], 0xffffffce ; 4294967246
| 0x562febd98747 c745f02c0100. mov dword [local_10h], 0x12c ; 300
| 0x562febd9874e 8b55e0 mov edx, dword [local_20h]
| 0x562febd98751 8b45e4 mov eax, dword [local_1ch]
| 0x562febd98754 01c2 add edx, eax
| 0x562febd98756 8b45e8 mov eax, dword [local_18h]
| 0x562febd98759 01c2 add edx, eax
| 0x562febd9875b 8b45ec mov eax, dword [local_14h]
| 0x562febd9875e 01c2 add edx, eax
| 0x562febd98760 8b45f0 mov eax, dword [local_10h]
| 0x562febd98763 01d0 add eax, edx
| 0x562febd98765 8945dc mov dword [local_24h], eax
| 0x562febd98768 8b45dc mov eax, dword [local_24h]
| 0x562febd9876b 89c6 mov esi, eax
| 0x562febd9876d 488d3db00000. lea rdi, qword str.SUM_IS__d ; 0x562febd98824 ; "SUM IS %d" ; const char * format
| 0x562febd98774 b800000000 mov eax, 0
| 0x562febd98779 e842feffff call sym.imp.printf ; int printf(const char *format)
| 0x562febd9877e 90 nop
| 0x562febd9877f 488b4df8 mov rcx, qword [local_8h]
| 0x562febd98783 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x562febd9878c 7405 je 0x562febd98793
| | 0x562febd9878e e81dfeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x562febd98793 c9 leave
\ 0x562febd98794 c3 ret
[0x562febd98714]>
Опять же, мы можем разбить наш анализ на разные части. Если мы посмотрим внимательно, то увидим, что внутри функции объявлены некоторые "переменные":
Код:
; var int local_24h @ rbp-0x24
| ; var int local_20h @ rbp-0x20
| ; var int local_1ch @ rbp-0x1c
| ; var int local_18h @ rbp-0x18
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x562febd98703 (sym.main)
| 0x562febd98714 55 push rbp
| 0x562febd98715 4889e5 mov rbp, rsp
| 0x562febd98718 4883ec30 sub rsp, 0x30 ; '0'
| 0x562febd9871c 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x562febd98725 488945f8 mov qword [local_8h], rax
Как мы уже видели ранее, local_8h связана с защитой стека, поэтому здесь нет проблем, но мы видим там еще 6 переменных, странно, ведь мы только что объявили массив и переменную sum.
Если мы проследим за этими 6 переменными в коде, то увидим:
Код:
| 0x562febd9872b c745e0c80000. mov dword [local_20h], 0xc8 ; 200
| 0x562febd98732 c745e4960000. mov dword [local_1ch], 0x96 ; 150
| 0x562febd98739 c745e8640000. mov dword [local_18h], 0x64 ; 'd' ; 100
| 0x562febd98740 c745ecceffff. mov dword [local_14h], 0xffffffce ; 4294967246
| 0x562febd98747 c745f02c0100. mov dword [local_10h], 0x12c ; 300
Итак, они инициализируются на 200, 150, 100, странное значение и 300. Подождите, что это за странное значение? Оно должно быть -50!
ммм... но если мы используем rax2
Код:
red@blue:~/c/chapter4$ rax2 0x0ffffffffffffffce
-50
red@blue:~/c/chapter4$
red@blue:~/c/chapter4$ rax2 -50
0xffffffffffffffce
Проблема решена. Как мы видим, компилятор интерпретирует массив как набор переменных, выделенных в памяти одна за другой. Кстати, мы можем легко проверить это, поставив точку останова после завершения инициализации:
И сбросить начальный адрес памяти:
Код:
| 0x562febd98747 c745f02c0100. mov dword [local_10h], 0x12c ; 300
| ;-- rip:
| 0x562febd9874e b 8b55e0 mov edx, dword [local_20h]
[0x562febd98714]> afvd
var local_8h = 0x7ffcf5d7ad88 0xebc835f94cbbc100 ...L.5..
var local_20h = 0x7ffcf5d7ad70 0x00000096000000c8 ........
var local_1ch = 0x7ffcf5d7ad74 0x0000006400000096 ....d...
var local_18h = 0x7ffcf5d7ad78 0xffffffce00000064 d.......
var local_14h = 0x7ffcf5d7ad7c 0x0000012cffffffce ....,...
var local_10h = 0x7ffcf5d7ad80 0x0000562f0000012c ,.../V..
var local_24h = 0x7ffcf5d7ad6c 0x000000c80000562f /V......
[0x562febd98714]> pxw @ 0x7ffcf5d7ad70
0x7ffcf5d7ad70 0x000000c8 0x00000096 0x00000064 0xffffffce ........d.......
0x7ffcf5d7ad80 0x0000012c 0x0000562f 0x4cbbc100 0xebc835f9 ,.../V.....L.5..
0x7ffcf5d7ad90 0xf5d7ada0 0x00007ffc 0xebd98708 0x0000562f ............/V..
Вот и все, все эти переменные собираются в памяти одна за другой, как это делают массивы.
То, что происходит дальше, не представляет для нас никакой сложности. Регистр edx используется для хранения суммы всех этих чисел, затем переменная sum используется для хранения конечного значения и бам! printf.
Давайте теперь проверим то же самое, но используя вместо этого одиночные переменные:
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
int sum;
int num0 = 200;
int num1 = 150;
int num2 = 100;
int num3 = -50;
int num4 = 300;
sum = num0 + num1 + num2 + num3 + num4;
printf("SUM is %d", sum);
}
Код:
[0x5570354d96a4]> pdf
/ (fcn) sym.func 94
| sym.func ();
| ; var int local_18h @ rbp-0x18
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_ch @ rbp-0xc
| ; var int local_8h @ rbp-0x8
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x5570354d9693 (sym.main)
| 0x5570354d96a4 55 push rbp
| 0x5570354d96a5 4889e5 mov rbp, rsp
| 0x5570354d96a8 4883ec20 sub rsp, 0x20
| 0x5570354d96ac c745e8c80000. mov dword [local_18h], 0xc8 ; 200
| 0x5570354d96b3 c745ec960000. mov dword [local_14h], 0x96 ; 150
| 0x5570354d96ba c745f0640000. mov dword [local_10h], 0x64 ; 'd' ; 100
| 0x5570354d96c1 c745f4ceffff. mov dword [local_ch], 0xffffffce ; 4294967246
| 0x5570354d96c8 c745f82c0100. mov dword [local_8h], 0x12c ; 300
| 0x5570354d96cf 8b55e8 mov edx, dword [local_18h]
| 0x5570354d96d2 8b45ec mov eax, dword [local_14h]
| 0x5570354d96d5 01c2 add edx, eax
| 0x5570354d96d7 8b45f0 mov eax, dword [local_10h]
| 0x5570354d96da 01c2 add edx, eax
| 0x5570354d96dc 8b45f4 mov eax, dword [local_ch]
| 0x5570354d96df 01c2 add edx, eax
| 0x5570354d96e1 8b45f8 mov eax, dword [local_8h]
| 0x5570354d96e4 01d0 add eax, edx
| 0x5570354d96e6 8945fc mov dword [local_4h], eax
| 0x5570354d96e9 8b45fc mov eax, dword [local_4h]
| 0x5570354d96ec 89c6 mov esi, eax
| 0x5570354d96ee 488d3d9f0000. lea rdi, qword str.SUM_is__d ; 0x5570354d9794 ; "SUM is %d"
| 0x5570354d96f5 b800000000 mov eax, 0
| 0x5570354d96fa e851feffff call sym.imp.printf ; int printf(const char *format)
| 0x5570354d96ff 90 nop
| 0x5570354d9700 c9 leave
\ 0x5570354d9701 c3 ret
[0x5570354d96a4]>
Здесь больше нечего объяснять... как я уже сказал... одно и то же! Так что здесь компилятор определил, что код делает точно то же самое, и поэтому решил обработать его таким же образом...
Но вы спросите себя: хорошо, массив из 5 позиций может быть прочитан как 5 независимых переменных, но что если у нас есть массив из 100 элементов? Будет ли это выглядеть так же странно?
Давайте посмотрим!
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
int arr[100];
for (int i=0;i<100;i++){
arr[i]=i;
}
for (int i=0;i<100;i++){
printf("val %d",arr[i]);
}
}
Сначала мы объявляем, затем заполняем, в конце печатаем.
Код:
:> pdf
/ (fcn) sym.func 160
| sym.func ();
| ; var int local_1a8h @ rbp-0x1a8
| ; var int local_1a4h @ rbp-0x1a4
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x5567f0ed8703 (sym.main)
| 0x5567f0ed8714 55 push rbp
| 0x5567f0ed8715 4889e5 mov rbp, rsp
| 0x5567f0ed8718 4881ecb00100. sub rsp, 0x1b0
| 0x5567f0ed871f 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x5567f0ed8728 488945f8 mov qword [local_8h], rax
| 0x5567f0ed872c 31c0 xor eax, eax
| 0x5567f0ed872e c78558feffff. mov dword [local_1a8h], 0
| ,=< 0x5567f0ed8738 eb1c jmp 0x5567f0ed8756
| .--> 0x5567f0ed873a 8b8558feffff mov eax, dword [local_1a8h]
| :| 0x5567f0ed8740 4898 cdqe
| :| 0x5567f0ed8742 8b9558feffff mov edx, dword [local_1a8h]
| :| 0x5567f0ed8748 89948560feff. mov dword [rbp + rax*4 - 0x1a0], edx
| :| 0x5567f0ed874f 838558feffff. add dword [local_1a8h], 1
| :| ; JMP XREF from 0x5567f0ed8738 (sym.func)
| :`-> 0x5567f0ed8756 83bd58feffff. cmp dword [local_1a8h], 0x63 ; [0x63:4]=-1 ; 'c' ; 99
| `==< 0x5567f0ed875d 7edb jle 0x5567f0ed873a
| 0x5567f0ed875f c7855cfeffff. mov dword [local_1a4h], 0
| ,=< 0x5567f0ed8769 eb29 jmp 0x5567f0ed8794
| .--> 0x5567f0ed876b 8b855cfeffff mov eax, dword [local_1a4h]
| :| 0x5567f0ed8771 4898 cdqe
| :| 0x5567f0ed8773 8b848560feff. mov eax, dword [rbp + rax*4 - 0x1a0]
| :| 0x5567f0ed877a 89c6 mov esi, eax
| :| 0x5567f0ed877c 488d3dc10000. lea rdi, qword str.val__d ; 0x5567f0ed8844 ; "val %d"
| :| 0x5567f0ed8783 b800000000 mov eax, 0
| :| 0x5567f0ed8788 e833feffff call sym.imp.printf ; int printf(const char *format)
| :| 0x5567f0ed878d 83855cfeffff. add dword [local_1a4h], 1
| :| ; JMP XREF from 0x5567f0ed8769 (sym.func)
| :`-> 0x5567f0ed8794 83bd5cfeffff. cmp dword [local_1a4h], 0x63 ; [0x63:4]=-1 ; 'c' ; 99
| `==< 0x5567f0ed879b 7ece jle 0x5567f0ed876b
| 0x5567f0ed879d 90 nop
| 0x5567f0ed879e 488b4df8 mov rcx, qword [local_8h]
| 0x5567f0ed87a2 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x5567f0ed87ab 7405 je 0x5567f0ed87b2
| | 0x5567f0ed87ad e8fefdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x5567f0ed87b2 c9 leave
\ 0x5567f0ed87b3 c3 ret
:>
Как и ожидалось, мы не видим там 100 независимых переменных, вместо этого мы видим три из них, вещь для защиты стека, затем еще пару, которые могут соответствовать первой и второй для индексов.
Вместо этого программа будет использовать регистры в качестве индексов, давайте проверим это, скажем, для первого цикла, того, который инициализирует массив:
Код:
| 0x5567f0ed872e c78558feffff. mov dword [local_1a8h], 0
| ,=< 0x5567f0ed8738 eb1c jmp 0x5567f0ed8756
| .--> 0x5567f0ed873a 8b8558feffff mov eax, dword [local_1a8h]
| :| 0x5567f0ed8740 4898 cdqe
Сначала инициализируется переменная "local_1a8h", затем происходит переход сюда:
Код:
| :`-> 0x5567f0ed8756 83bd58feffff. cmp dword [local_1a8h], 0x63 ; [0x63:4]=-1 ; 'c' ; 99
| `==< 0x5567f0ed875d 7edb jle 0x5567f0ed873a
| 0x5567f0ed875f c7855cfeffff. mov dword [local_1a4h], 0
После прыжка программа сравнивает инициализированное значение с 99 (i < 100), затем, если оно меньше 99, возвращается назад, чтобы выполнить код, находящийся внутри цикла. Если же оно равно или больше, программа продолжит выполнение.
Код:
| .--> 0x5567f0ed873a 8b8558feffff mov eax, dword [local_1a8h]
| :| 0x5567f0ed8740 4898 cdqe
| :| 0x5567f0ed8742 8b9558feffff mov edx, dword [local_1a8h]
| :| 0x5567f0ed8748 89948560feff. mov dword [rbp + rax*4 - 0x1a0], edx
| :| 0x5567f0ed874f 838558feffff. add dword [local_1a8h], 1
Сначала программа перемещает содержимое значения (i) в eax, затем, поскольку в данном случае eax - это только 32-битная часть RAX, она использует cdqe для значения, чтобы оно работало в 64-битном режиме.
P.S.
В 64-битном режиме размер операции по умолчанию равен размеру регистра назначения. Использование префикса REX.W продвигает эту инструкцию (CDQE при продвижении) для работы с 64-битными операндами. В этом случае CDQE копирует знак (бит 31) двойного слова в регистре EAX в старшие 32 бита RAX.
После этого он перемещает результат в edx (edx здесь используется как временный регистр) и, наконец, сохраняет его в своей позиции внутри массива.
Но как вычисляется эта позиция в массиве? У нас есть следующее: [rbp + rax4 - 0x1a0], то же самое можно представить как: [(rbp - 0x1a0) + rax4] И как мы можем легко видеть: (rbp - 0x1a0) - это просто место внутри памяти, место, которое было зарезервировано в памяти для массива,
После этого на каждой итерации добавляется rax*4, как мы знаем, в этот момент rax будет содержать значение нашей переменной (i), так как int имеет размер 4, значение rax будет умножено на 4, чтобы правильно распределить int по его позиции внутри массива.
Короче говоря, (rbp - 0x1a0) - это базовый адрес массива, а rax*4 - индекс.
Остальная часть кода, то же самое, партнер.
Инициализация массивов
Массивы могут быть предварительно инициализированы, но в данном случае это будет одно и то же:
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
int sum=0;
int i;
int num[5] ={200, 150, 100, -50, 300};
for(i=0;i<=4;i++) sum += num[i];
printf("SUM is %d", sum);
}
Код:
[0x00000714]> pdf
/ (fcn) sym.func 141
| sym.func ();
| ; var int local_28h @ rbp-0x28
| ; var int local_24h @ rbp-0x24
| ; var int local_20h @ rbp-0x20
| ; var int local_1ch @ rbp-0x1c
| ; var int local_18h @ rbp-0x18
| ; var int local_14h @ rbp-0x14
| ; var int local_10h @ rbp-0x10
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x00000703 (sym.main)
| 0x00000714 55 push rbp
| 0x00000715 4889e5 mov rbp, rsp
| 0x00000718 4883ec30 sub rsp, 0x30 ; '0'
| 0x0000071c 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=0x19b8 ; '('
| 0x00000725 488945f8 mov qword [local_8h], rax
| 0x00000729 31c0 xor eax, eax
| 0x0000072b c745d8000000. mov dword [local_28h], 0
| 0x00000732 c745e0c80000. mov dword [local_20h], 0xc8
| 0x00000739 c745e4960000. mov dword [local_1ch], 0x96
| 0x00000740 c745e8640000. mov dword [local_18h], 0x64 ; 'd'
| 0x00000747 c745ecceffff. mov dword [local_14h], 0xffffffce ; 4294967246
| 0x0000074e c745f02c0100. mov dword [local_10h], 0x12c
| 0x00000755 c745dc000000. mov dword [local_24h], 0
[...]
То же самое
Массивы символов / строки
Здесь все становится немного интереснее, на этот раз мы объявим массив char, который может быть интерпретирован как строка, мы будем использовать scanf для чтения из пользовательского ввода:
C:
# include <stdio.h>
main(){
func();
getchar();
getchar();
}
func(){
char text[40];
printf("Name?: ");
scanf("%s", &text);
printf("Hi, %s\n", text);
}
В данном случае у нас есть только одна переменная local_30h.
Код:
[0x55d494124789]> pdf
/ (fcn) sym.func 111
| sym.func ();
| ; var int local_30h @ rbp-0x30
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x55d494124773 (sym.main)
| 0x55d494124789 55 push rbp
| 0x55d49412478a 4889e5 mov rbp, rsp
| 0x55d49412478d 4883ec30 sub rsp, 0x30 ; '0'
| 0x55d494124791 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x55d49412479a 488945f8 mov qword [local_8h], rax
| 0x55d49412479e 31c0 xor eax, eax
| 0x55d4941247a0 488d3ddd0000. lea rdi, qword str.Name_: ; 0x55d494124884 ; "Name?: "
| 0x55d4941247a7 b800000000 mov eax, 0
| 0x55d4941247ac e86ffeffff call sym.imp.printf ; int printf(const char *format)
| 0x55d4941247b1 488d45d0 lea rax, qword [local_30h]
| 0x55d4941247b5 4889c6 mov rsi, rax
| 0x55d4941247b8 488d3dcd0000. lea rdi, qword [0x55d49412488c] ; "%s"
| 0x55d4941247bf b800000000 mov eax, 0
| 0x55d4941247c4 e877feffff call sym.imp.__isoc99_scanf
| 0x55d4941247c9 488d45d0 lea rax, qword [local_30h]
| 0x55d4941247cd 4889c6 mov rsi, rax
| 0x55d4941247d0 488d3db80000. lea rdi, qword str.Hi___s ; 0x55d49412488f ; "Hi, %s\n"
| 0x55d4941247d7 b800000000 mov eax, 0
| 0x55d4941247dc e83ffeffff call sym.imp.printf ; int printf(const char *format)
| 0x55d4941247e1 90 nop
| 0x55d4941247e2 488b55f8 mov rdx, qword [local_8h]
| 0x55d4941247e6 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x55d4941247ef 7405 je 0x55d4941247f6
| | 0x55d4941247f1 e81afeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x55d4941247f6 c9 leave
\ 0x55d4941247f7 c3 ret
[0x55d494124789]>
local_30h находится на расстоянии 0x30 (48 dec) байт от rbp, поэтому вполне логично, что этот var является текстовым массивом char, в котором будет размещена наша строка.
Мы можем поставить точку останова в точке, где на него ссылаются в первый раз, прямо перед прыжком в scanf.
Код:
[0x55d494124789]> db 0x55d4941247b5
[0x55d494124789]> dc
hit breakpoint at: 55d4941247b5
[0x55d494124789]>
[0x55d494124789]> dr
rax = 0x7ffdfa5e7920
[0x55d494124789]> pxw @ 0x7ffdfa5e7920
0x7ffdfa5e7920 0x00000001 0x00000000 0x9412484d 0x000055d4 ........MH...U..
0x7ffdfa5e7930 0x032ee9a0 0x00007ff9 0x00000000 0x00000000 ................
Поэтому давайте запишем этот адрес: 0x7ffdfa5e7920, так как наша строка будет храниться там.
Код:
[0x55d494124789]> db 0x55d4941247cd
[0x55d494124789]> dc
Name?: ARTIK
hit breakpoint at: 55d4941247cd
[0x55d4941247cd]> pxw @ 0x7ffdfa5e7920
0x7ffdfa5e7920 0x49545241 0x0000004b 0x9412484d 0x000055d4 ARTIK...MH...U..
Вот оно! Остальная часть кода довольно проста, local_30h будет снова передана в printf и выведен результат.
Как обычно, мы можем получить доступ к элементам массива char по отдельности, используя индекс, рассмотрим этот вариант:
C:
# include <stdio.h>
main(){
func();
getchar();
getchar();
}
func(){
char text[40];
printf("Name?: ");
scanf("%s", text);
printf("Hey, %s. First letter: %c\n", text, text[0]);
printf("Ho, %s. Second letter: %c\n", text, text[1]);
}
Эта программа в основном объявляет массив char, читает из пользовательского ввода, чтобы сохранить текст прямо там, а затем обращается к массиву как "глобально", так и по индексу.
Внутри radare2 это выглядит следующим образом.
Код:
[0x55d2fe6d3789]> pdf
/ (fcn) sym.func 149
| sym.func ();
| ; var int local_30h @ rbp-0x30
| ; var int local_2fh @ rbp-0x2f
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x55d2fe6d3773 (sym.main)
| 0x55d2fe6d3789 55 push rbp
| 0x55d2fe6d378a 4889e5 mov rbp, rsp
| 0x55d2fe6d378d 4883ec30 sub rsp, 0x30 ; '0'
| 0x55d2fe6d3791 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x55d2fe6d379a 488945f8 mov qword [local_8h], rax
| 0x55d2fe6d379e 31c0 xor eax, eax
| 0x55d2fe6d37a0 488d3dfd0000. lea rdi, qword str.Name_: ; 0x55d2fe6d38a4 ; "Name?: "
| 0x55d2fe6d37a7 b800000000 mov eax, 0
| 0x55d2fe6d37ac e86ffeffff call sym.imp.printf ; int printf(const char *format)
| 0x55d2fe6d37b1 488d45d0 lea rax, qword [local_30h]
| 0x55d2fe6d37b5 4889c6 mov rsi, rax
| 0x55d2fe6d37b8 488d3ded0000. lea rdi, qword [0x55d2fe6d38ac] ; "%s"
| 0x55d2fe6d37bf b800000000 mov eax, 0
| 0x55d2fe6d37c4 e877feffff call sym.imp.__isoc99_scanf
| 0x55d2fe6d37c9 0fb645d0 movzx eax, byte [local_30h]
| 0x55d2fe6d37cd 0fbed0 movsx edx, al
| 0x55d2fe6d37d0 488d45d0 lea rax, qword [local_30h]
| 0x55d2fe6d37d4 4889c6 mov rsi, rax
| 0x55d2fe6d37d7 488d3dd10000. lea rdi, qword str.Hey___s._First_letter:__c ; 0x55d2fe6d38af ; "Hey, %s. First letter: %c\n"
| 0x55d2fe6d37de b800000000 mov eax, 0
| 0x55d2fe6d37e3 e838feffff call sym.imp.printf ; int printf(const char *format)
| 0x55d2fe6d37e8 0fb645d1 movzx eax, byte [local_2fh]
| 0x55d2fe6d37ec 0fbed0 movsx edx, al
| 0x55d2fe6d37ef 488d45d0 lea rax, qword [local_30h]
| 0x55d2fe6d37f3 4889c6 mov rsi, rax
| 0x55d2fe6d37f6 488d3dcd0000. lea rdi, qword str.Ho___s._Second_letter:__c ; 0x55d2fe6d38ca ; "Ho, %s. Second letter: %c\n"
| 0x55d2fe6d37fd b800000000 mov eax, 0
| 0x55d2fe6d3802 e819feffff call sym.imp.printf ; int printf(const char *format)
| 0x55d2fe6d3807 90 nop
| 0x55d2fe6d3808 488b4df8 mov rcx, qword [local_8h]
| 0x55d2fe6d380c 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x55d2fe6d3815 7405 je 0x55d2fe6d381c
| | 0x55d2fe6d3817 e8f4fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x55d2fe6d381c c9 leave
\ 0x55d2fe6d381d c3 ret
[0x55d2fe6d3789]>
Прежде всего, мы обнаруживаем две переменные:
Код:
/ (fcn) sym.func 149
| sym.func ();
| ; var int local_30h @ rbp-0x30
| ; var int local_2fh @ rbp-0x2f
| ; var int local_8h @ rbp-0x8
Первый, мы его уже знаем, это может быть базовый адрес массива символов, но тогда другой… он расположен всего в одном байте от базового адреса… давайте это учтем. Мы даже переименовали их, чтобы было понятнее.
Мы уже знакомы с тем, как «строка» читается с помощью scanf:
Код:
| 0x55d2fe6d37b1 488d45d0 lea rax, qword [text_array]
| 0x55d2fe6d37b5 4889c6 mov rsi, rax
| 0x55d2fe6d37b8 488d3ded0000. lea rdi, qword [0x55d2fe6d38ac] ; "%s"
| 0x55d2fe6d37bf b800000000 mov eax, 0
| 0x55d2fe6d37c4 e877feffff call sym.imp.__isoc99_scanf
Но тогда мы получаем это:
Код:
| 0x55d2fe6d37c9 0fb645d0 movzx eax, byte [text_array]
| 0x55d2fe6d37cd 0fbed0 movsx edx, al
| 0x55d2fe6d37d0 488d45d0 lea rax, qword [text_array]
| 0x55d2fe6d37d4 4889c6 mov rsi, rax
| 0x55d2fe6d37d7 488d3dd10000. lea rdi, qword str.Hey___s._First_letter:__c ; 0x55d2fe6d38af ; "Hey, %s. First letter: %c\n"
| 0x55d2fe6d37de b800000000 mov eax, 0
| 0x55d2fe6d37e3 e838feffff call sym.imp.printf ; int printf(cons
В первой инструкции мы видим, что movzx извлекает первый байт из text_array (Копирует содержимое исходного операнда (регистра или ячейки памяти) в целевой операнд (регистр) и нулем расширяет значение. Размер преобразованного значения зависит от операнда -размер атрибута.)
Затем этот байт (al) перемещается в edx с помощью movsx (копирует содержимое исходного операнда (регистра или ячейки памяти) в целевой операнд (регистр), а знак расширяет значение до 16 или 32 бит)
Мы можем легко сделать вывод, что это в основном извлекает первый символ массива, затем указатель на этот массив перемещается в rsi через rax, и мы вызываем printf.
Следующий блок кода делает то же самое, используя обнаруженную нами переменную «pos1» вместо «index». Давайте посмотрим, на этот раз мы можем отладить его для более легкой визуализации.
Код:
| 0x55d2fe6d37e8 0fb645d1 movzx eax, byte [pos1]
| 0x55d2fe6d37ec 0fbed0 movsx edx, al
| ;-- rip:
| 0x55d2fe6d37ef b 488d45d0 lea rax, qword [text_array]
| 0x55d2fe6d37f3 4889c6 mov rsi, rax
| 0x55d2fe6d37f6 488d3dcd0000. lea rdi, qword str.Ho___s._Second_letter:__c ; 0x55d2fe6d38ca ; "Ho, %s. Second letter: %c\n"
| 0x55d2fe6d37fd b800000000 mov eax, 0
После установки точки останова прямо перед movsx, если мы проверим регистр, мы увидим, что
Код:
[0x55d2fe6d37d0]> dr
rax = 0x00000052
rbx = 0x00000000
rcx = 0x00000000
rdx = 0x00000052
Точно, rdx (и al) = 52 = код ascii для R, являющегося ARTIK входом. Тайна раскрыта
Источник:
Ссылка скрыта от гостей
Последнее редактирование модератором: