• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Статья Типы переменных и приведение. Реверс инжиниринг с использованием radare2. Часть 6

prew.jpg

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

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

В конце концов, это всего лишь вопрос интерпретации.

Источник:
 
Последнее редактирование модератором:
  • Нравится
Реакции: main и Xulinam
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!