Ку, киберрекруты. Сегодня расскажу об основных типах переменных, с которыми вы можете столкнуться при реверсировании кода на языке Си (и в общих чертах), и мы рассмотрим, как выполняется приведение на низком уровне. Это будет относительно быстро.
Casting - это способ "преобразования" переменной одного вида в переменную другого вида. Кстати, tutorialspoint делает это очень простым для понимания:
"Преобразование одного типа данных в другой известно как приведение типов или преобразование типов. Например, если вы хотите сохранить значение 'long' в простое целое число, вы можете преобразовать тип 'long' в 'int'. Вы можете преобразовывать значения из одного типа в другой явно, используя оператор cast следующим образом - (имя_типа) выражение".
Давайте рассмотрим простой пример:
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
int val = 2;
float a = 5.25;
int b = (int)a;
printf ("%d\n", b);
}
Дисазм выглядит следующим образом:
Код:
[0x55630491a6a4]> pdf
/ (fcn) sym.func 65
| sym.func ();
| ; var int local_ch @ rbp-0xc
| ; var int local_8h @ rbp-0x8
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x55630491a693 (sym.main)
| 0x55630491a6a4 55 push rbp
| 0x55630491a6a5 4889e5 mov rbp, rsp
| 0x55630491a6a8 4883ec10 sub rsp, 0x10
| 0x55630491a6ac c745f4020000. mov dword [local_ch], 2
| 0x55630491a6b3 f30f1005bd00. movss xmm0, dword [0x55630491a778] ; [0x55630491a778:4]=0x40a80000
| 0x55630491a6bb f30f1145f8 movss dword [local_8h], xmm0
| 0x55630491a6c0 f30f1045f8 movss xmm0, dword [local_8h]
| 0x55630491a6c5 f30f2cc0 cvttss2si eax, xmm0
| 0x55630491a6c9 8945fc mov dword [local_4h], eax
| 0x55630491a6cc 8b45fc mov eax, dword [local_4h]
| 0x55630491a6cf 89c6 mov esi, eax
| 0x55630491a6d1 488d3d9c0000. lea rdi, qword [0x55630491a774] ; "%d\n"
| 0x55630491a6d8 b800000000 mov eax, 0
| 0x55630491a6dd e86efeffff call sym.imp.printf ; int printf(const char *format)
| 0x55630491a6e2 90 nop
| 0x55630491a6e3 c9 leave
\ 0x55630491a6e4 c3 ret
[0x55630491a6a4]>
На первый взгляд, мы видим несколько новых инструкций, таких как
movss
и странная cvttss2si
, которую мы не видели в предыдущих сообщениях. Также используются новые регистры: xmm0
. Вкратце, регистры xmm, которые обычно идут от xmm0 до xmm7, используются для хранения чисел с плавающей точкой, они имеют размер 128 бит, так как float требует больше места, чем int.В такой ситуации, как эта, у нас обычно есть два варианта: мы можем либо попытаться интерпретировать эти инструкции одну за другой и выяснить, что делает программа, либо мы можем просто установить точку останова, например, после сохранения xmm0 в local_8h и просмотреть содержимое:
Код:
[0x5634635196a4]> pdf
/ (fcn) sym.func 65
| sym.func ();
| ; var int local_ch @ rbp-0xc
| ; var int local_8h @ rbp-0x8
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x563463519693 (sym.main)
| 0x5634635196a4 55 push rbp
| 0x5634635196a5 4889e5 mov rbp, rsp
| 0x5634635196a8 4883ec10 sub rsp, 0x10
| 0x5634635196ac c745f4020000. mov dword [local_ch], 2
| 0x5634635196b3 f30f1005bd00. movss xmm0, dword [0x563463519778] ; [0x563463519778:4]=0x40a80000
| 0x5634635196bb f30f1145f8 movss dword [local_8h], xmm0
| 0x5634635196c0 f30f1045f8 movss xmm0, dword [local_8h]
| ;-- rip:
| 0x5634635196c5 b f30f2cc0 cvttss2si eax, xmm0
| 0x5634635196c9 8945fc mov dword [local_4h], eax
| 0x5634635196cc 8b45fc mov eax, dword [local_4h]
| 0x5634635196cf 89c6 mov esi, eax
| 0x5634635196d1 488d3d9c0000. lea rdi, qword [0x563463519774] ; "%d\n"
| 0x5634635196d8 b800000000 mov eax, 0
| 0x5634635196dd e86efeffff call sym.imp.printf ; int printf(const char *format)
| 0x5634635196e2 90 nop
| 0x5634635196e3 c9 leave
\ 0x5634635196e4 c3 ret
[0x5634635196a4]>
[0x5634635196a4]> pxw @ 0x563463519778
0x563463519778 0x40a80000 0x3b031b01 0x00000040 0x00000007 ...@...;@.......
[0x5634635196a4]> drt fpu
frip = 0x00000000
frdp = 0x00000000
st0 = 0x00000000
st1 = 0x00000000
st2 = 0x00000000
st3 = 0x00000000
st4 = 0x00000000
st5 = 0x00000000
st6 = 0x00000000
st7 = 0x00000000
xmm0h = 0x40a80000
xmm0l = 0x00000000
xmm1h = 0x31747361632f2e
xmm1l = 0x524f4c4f435f534c
xmm2h = 0xff00000000000000
xmm2l = 0x00000000
xmm3h = 0x0000ff00
xmm3l = 0x00000000
xmm4h = 0x2f2f2f2f2f2f2f2f
xmm4l = 0x2f2f2f2f2f2f2f2f
xmm5h = 0x00000000
xmm5l = 0x00000000
xmm6h = 0x00000000
xmm6l = 0x00000000
xmm7h = 0x00000000
xmm7l = 0x00000000
x64 = 0x00000000
[0x5634635196a4]>
Как мы видим, сначала local_ch инициализируется значением 2, что легко, это должна быть простая переменная int, затем содержимое 0x563463519778 загружается в регистр xmm0 и попадает в переменную local_8h. Если мы проверим содержимое регистра xmm0 с помощью drt, то увидим 0x40a80000, что соответствует 5.25 в float.
Код:
rax2 Fx40a80000
5.250000f
Radare2 может помочь нам в этом, так как он может автоматически определять типы переменных, на данном этапе программы, если мы сделаем afta, мы увидим, как они изменятся:
Код:
[0x558e5889c6a4]> afvt local_8h float
[0x558e5889c6a4]> pdf
/ (fcn) sym.func 65
| sym.func ();
| ; var int local_ch @ rbp-0xc
| ; var float local_8h @ rbp-0x8
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x558e5889c693 (sym.main)
| 0x558e5889c6a4 55 push rbp
| 0x558e5889c6a5 4889e5 mov rbp, rsp
[...]
[0x558e5889c6a4]>
А с помощью afvt var type мы можем сами задать тип переменной. Что касается инструкций, используемых здесь:
Код:
| 0x5634635196b3 f30f1005bd00. movss xmm0, dword [0x563463519778] ; [0x563463519778:4]=0x40a80000
| 0x5634635196bb f30f1145f8 movss dword [local_8h], xmm0
| 0x5634635196c0 f30f1045f8 movss xmm0, dword [local_8h]
| ;-- rip:
| 0x5634635196c5 b f30f2cc0 cvttss2si eax, xmm0
Movss
(move scalar single precision floating point value) перемещает скалярное значение с плавающей точкой одинарной точности из операнда источника (второй операнд) в операнд назначения (первый операнд). Операнды источника и назначения могут быть регистрами XMM или 32-битными ячейками памяти. Таким образом, это в основном используется как обычный mov, но для валов с плавающей точкой, так как они больше. Затем cvttss2si (convert with truncation scalar single precision floating point value to integer) - это то, что в основном делает приведение здесь, оно усекает десятичную часть из float и извлекает int, переходя от 5.25 к 5. Остальная часть дизазма должна быть уже знакома читателю.Давайте теперь сделаем другой вид приведения, на этот раз мы перейдем от символа к целому числу.
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
char b = 'a';
int x = (int)b;
printf ("%d", x);
}
И если мы посмотрим на дизасм...
Код:
[0x55b8bc8c96a4]> pdf
/ (fcn) sym.func 44
| sym.func ();
| ; var int local_5h @ rbp-0x5
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x55b8bc8c9693 (sym.main)
| 0x55b8bc8c96a4 55 push rbp
| 0x55b8bc8c96a5 4889e5 mov rbp, rsp
| 0x55b8bc8c96a8 4883ec10 sub rsp, 0x10
| 0x55b8bc8c96ac c645fb61 mov byte [local_5h], 0x61 ; 'a' ; 97
| 0x55b8bc8c96b0 0fbe45fb movsx eax, byte [local_5h]
| 0x55b8bc8c96b4 8945fc mov dword [local_4h], eax
| 0x55b8bc8c96b7 8b45fc mov eax, dword [local_4h]
| 0x55b8bc8c96ba 89c6 mov esi, eax
| 0x55b8bc8c96bc 488d3d910000. lea rdi, qword [0x55b8bc8c9754] ; "%d"
| 0x55b8bc8c96c3 b800000000 mov eax, 0
| 0x55b8bc8c96c8 e883feffff call sym.imp.printf ; int printf(const char *format)
| 0x55b8bc8c96cd 90 nop
| 0x55b8bc8c96ce c9 leave
\ 0x55b8bc8c96cf c3 ret
[0x55b8bc8c96a4]>
Этот вариант очень прост, все происходит здесь:
Код:
| 0x55b8bc8c96ac c645fb61 mov byte [local_5h], 0x61 ; 'a' ; 97
| 0x55b8bc8c96b0 0fbe45fb movsx eax, byte [local_5h]
| 0x55b8bc8c96b4 8945fc mov dword [local_4h], eax
| 0x55b8bc8c96b7 8b45fc mov eax, dword [local_4h]
Байт 0x61 (ascii a) хранится в local_5h, что должно означать char b = 'a', затем он переходит в EAX (мы не используем rax, так как var - один байт) и оттуда переходит в local_4h, что должно означать int x = (int) b; затем передаются параметры и вызывается printf, легко. movsx используется здесь, так как он перемещает с расширением знака, это обычная техника, чтобы использовать его, когда дело доходит до приведения из int в char и из char в int (больше инфы:
Ссылка скрыта от гостей
)Завершим эту тему последним примером.
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
char b = 'a';
int x = (int)b;
printf ("%d\n", x);
printf ("%d\n", b);
}
Как видите, мы начинаем с целого символа 'a', храним его как char и int, используя две переменные, а затем выводим оба значения. Важно отметить, что в обоих случаях, когда мы передаем строку формата %d в printf, поскольку %d предназначен для печати значения int, мы оба раза получим на выходе 97! Таким образом, 0x61 представляет и 97 dec, и ascii 'a' - это одно и то же, единственное различие в том, что если мы хотим хранить int, нам потребуется в два раза больше места, чем для char, вот и все. Формат вывода будет зависеть от строки формата в printf.
Давайте посмотрим.
Код:
[0x5570cc50c6a4]> pdf
/ (fcn) sym.func 67
| sym.func ();
| ; var int local_5h @ rbp-0x5
| ; var int local_4h @ rbp-0x4
| ; CALL XREF from 0x5570cc50c693 (sym.main)
| 0x5570cc50c6a4 55 push rbp
| 0x5570cc50c6a5 4889e5 mov rbp, rsp
| 0x5570cc50c6a8 4883ec10 sub rsp, 0x10
| 0x5570cc50c6ac c645fb61 mov byte [local_5h], 0x61 ; 'a' ; 97
| 0x5570cc50c6b0 0fbe45fb movsx eax, byte [local_5h]
| 0x5570cc50c6b4 8945fc mov dword [local_4h], eax
| 0x5570cc50c6b7 8b45fc mov eax, dword [local_4h]
| 0x5570cc50c6ba 89c6 mov esi, eax
| 0x5570cc50c6bc 488d3db10000. lea rdi, qword [0x5570cc50c774] ; "%d\n"
| 0x5570cc50c6c3 b800000000 mov eax, 0
| 0x5570cc50c6c8 e883feffff call sym.imp.printf ; int printf(const char *format)
| 0x5570cc50c6cd 0fbe45fb movsx eax, byte [local_5h]
| 0x5570cc50c6d1 89c6 mov esi, eax
| 0x5570cc50c6d3 488d3d9a0000. lea rdi, qword [0x5570cc50c774] ; "%d\n"
| 0x5570cc50c6da b800000000 mov eax, 0
| 0x5570cc50c6df e86cfeffff call sym.imp.printf ; int printf(const char *format)
| 0x5570cc50c6e4 90 nop
| 0x5570cc50c6e5 c9 leave
\ 0x5570cc50c6e6 c3 ret
[0x5570cc50c6a4]>
[0x5570cc50c6a4]> afvd
var local_5h = 0x7ffce6d687eb 0xd688000000006161 aa......
var local_4h = 0x7ffce6d687ec 0xe6d6880000000061 a.......
[0x5570cc50c6a4]> dc
97
97
В конце концов, это всего лишь вопрос интерпретации.
Источник:
Ссылка скрыта от гостей
Последнее редактирование модератором: