Ку, киберрекруты. В этой статье мы немного подробнее рассмотрим массивы char и строки, чтобы заложить прочную основу темы и перейти к изучению более сложных структур данных (таких, как многомерные массивы и структуры).
Краткие сведения о массивах char
Как мы видели в предыдущем посте, строки могут быть объявлены как массивы char фиксированного размера, затем они могут быть выведены с помощью printf с использованием %s для формата.
Но интересно отметить, что scanf будет читать все, что находится перед пробелом, давайте проверим это:
После установки точки останова сразу после printf и запуска программы, нас попросят ввести данные, мы можем ввести что-то вроде "sample text", и здравый смысл подсказывает нам, что вывод после выполнения printf должен быть "Hi, sample text", но нет.
Как мы видим, scanf сохранил только "sample", пропустив " text". Это потому, что scanf означает scan formated, поэтому нам нужно указать вход, который мы ожидаем, нам нужно определить спецификатор формата.
Вызов функции в таком виде:
Позволяет регистрировать места. scanf считывает данные из ввода на основе спецификатора, точно так же, как printf выводит содержимое.
Другими методами получения строк со входа или их печати являются gets и puts. Gets получает символы со стандартного ввода и сохраняет их в виде строки, а puts печатает символы со стандартного вывода. Оба действуют так же, как printf и scanf, главное отличие в том, что здесь нет спецификации на формат. Основное различие здесь исходит от формата (
Рассмотрим следующий код:
Здесь puts используется так же, как printf, а gets - для чтения из stdin, давайте посмотрим:
В этом случае строка "Name..." передается в функцию puts с помощью rdi, мы также видим, что eax обнуляется, вероятно, потому, что не используются векторные регистры. Затем у нас есть функция gets:
Опять же, local_30h используется как указатель на считываемую строку и передается в функцию gets через регистр rdi.
Если мы отладим функцию, то на этот раз мы сможем увидеть, как сохраняется полная строка, включая белые пробелы.
Вуаля! Как мы видим, на этот раз в памяти хранится полная строка. Важно отметить, что использование таких функций, как scanf и gets, вообще не рекомендуется, если мы пытаемся построить что-то серьезное, так как пользовательский ввод не контролируется вообще. Как мы видели, при использовании gets, пользовательский ввод буквально заносится в память, начиная с определенного адреса памяти, связанного с началом массива char, поэтому если пользователь введет действительно большую строку символов, что-то намного большее, чем пространство массива, это, вероятно, приведет к поломке программы, также, поскольку gets читает все, что пользователь выводит на stdin и сохраняет в памяти, пользователь может даже ввести код, который может быть выполнен.
Мы можем взаимодействовать с массивами char разными способами. Функция strlen, которая, вероятно, встречается во многих программах и ctf'ах, возвращает длину строки.
strlen проходит через строку и считает, сколько позиций имеет хит, он проходит строку char за char, пока не встретит нулевой терминатор (\0). Сейчас мы проверим, что это значит:
Давайте сосредоточимся на функции strlen.
Как обычно, строка находится внутри local_30h. Поэтому эта ссылка загружается в rdi в качестве параметра, а затем вызывается strlen. Давайте поставим несколько точек останова и проанализируем программу.
Мы видим, как "SAMPLE TEXT" был сохранен как строка внутри local_30h, и если мы обратим немного больше внимания на дамп памяти, то увидим, что строка заканчивается 0x00, что является нулевым терминатором, поэтому strlen будет продолжать читать, пока не достигнет этого нулевого терминатора.
Если мы продолжим исследование strlen, то увидим, что функция возвращает свое значение (длину строки), используя регистр RAX.
Как мы видим, rax содержит 0xb = 11dec, а "SAMPLE TEXT" имеет длину 11 (с учетом пробела).
Библиотека для работы со строками
Библиотека строк "string.h" содержит различные функции, очень полезные для работы со строками. Эти функции позволяют нам манипулировать строками различными способами, здесь мы рассмотрим, как они полезны для копирования строк.
Рассмотрим следующую программу:
Мы видим, что сверху включается файл strings.h, затем объявляются 3 массива символов и используется strcpy для копирования содержимого одного из них в другой.
Мы можем легко определить используемую библиотеку strings с помощью afl
и с ii/il
И код:
На этот раз дисазм выглядит больше, но пусть это вас не пугает, почти все нам уже известно. Начнем с того, что увидим, как три переменные определяются r2.
Затем строка получается с помощью gets и вызывается strcpy, давайте посмотрим:
Как мы видим, строка (пользовательский ввод) будет сохранена в local_60h, затем local_60h и local_30h будут переданы в strcpy, в этот момент мы уже знаем, что local_60h содержит пользовательский ввод, но local_30h? это будет уничтожение, поэтому пользовательский ввод будет скопирован туда. Давайте отладим это
И если мы проверим их:
Строка скопирована Также обратите внимание, что обе строки имеют \0-окончание, поэтому strcpy будет продолжать копирование, пока не встретит \0.
Позже, в коде, вызывается strNcpy. Это другая функция, "n" в ней относится к следующему: с помощью strncpy содержимое одной строки копируется в другую, но копируются только первые n байт. Давайте проверим это:
Это очень просто, опять же, передаются оба адреса памяти происхождения/назначения, но также передается и 4, количество байт, которое нам нужно скопировать.
Было скопировано всего 4 байта. Важно отметить, что в случае с strncpy нулевой терминатор не вставляется по умолчанию, и это имеет смысл, потому что, возможно, наша цель - просто "слить" пару массивов, а не копировать строку в пустой массив.....
Если мы хотим разрезать массив, мы можем вручную вставить нулевой терминатор таким образом:
Как мы видим, в конце строки вручную добавляется ноль.
На этом пока все, как и было сказано, мы перейдем к более сложным структурам в следующем посте.
Источник:
Краткие сведения о массивах char
Как мы видели в предыдущем посте, строки могут быть объявлены как массивы char фиксированного размера, затем они могут быть выведены с помощью printf с использованием %s для формата.
C:
# include <stdio.h>
main(){
func();
getchar();
getchar();
getchar();
}
func(){
char text[40];
printf("Name?: ");
scanf("%s", &text);
printf("Hi, %s\n", text);
}
Но интересно отметить, что scanf будет читать все, что находится перед пробелом, давайте проверим это:
Код:
[0x5653b864f78e]> pdf
/ (fcn) sym.func 111
| sym.func ();
| ; var int local_30h @ rbp-0x30
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x5653b864f773 (sym.main)
| 0x5653b864f78e 55 push rbp
| 0x5653b864f78f 4889e5 mov rbp, rsp
| 0x5653b864f792 4883ec30 sub rsp, 0x30 ; '0'
| 0x5653b864f796 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x5653b864f79f 488945f8 mov qword [local_8h], rax
| 0x5653b864f7a3 31c0 xor eax, eax
| 0x5653b864f7a5 488d3dd80000. lea rdi, qword str.Name_: ; 0x5653b864f884 ; "Name?: "
| 0x5653b864f7ac b800000000 mov eax, 0
| 0x5653b864f7b1 e86afeffff call sym.imp.printf ; int printf(const char *format)
| 0x5653b864f7b6 488d45d0 lea rax, qword [local_30h]
| 0x5653b864f7ba 4889c6 mov rsi, rax
| 0x5653b864f7bd 488d3dc80000. lea rdi, qword [0x5653b864f88c] ; "%s"
| 0x5653b864f7c4 b800000000 mov eax, 0
| 0x5653b864f7c9 e872feffff call sym.imp.__isoc99_scanf
| 0x5653b864f7ce 488d45d0 lea rax, qword [local_30h]
| 0x5653b864f7d2 4889c6 mov rsi, rax
| 0x5653b864f7d5 488d3db30000. lea rdi, qword str.Hi___s ; 0x5653b864f88f ; "Hi, %s\n"
| 0x5653b864f7dc b800000000 mov eax, 0
| 0x5653b864f7e1 e83afeffff call sym.imp.printf ; int printf(const char *format)
| 0x5653b864f7e6 90 nop
| 0x5653b864f7e7 488b55f8 mov rdx, qword [local_8h]
| 0x5653b864f7eb 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x5653b864f7f4 7405 je 0x5653b864f7fb
| | 0x5653b864f7f6 e815feffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x5653b864f7fb c9 leave
\ 0x5653b864f7fc c3 ret
[0x5653b864f78e]> db 0x5653b864f7dc
[0x7f1e4dda4090]> dc
Name?: sample text
hit breakpoint at: 5653b864f7dc
[0x5653b864f7dc]>
После установки точки останова сразу после printf и запуска программы, нас попросят ввести данные, мы можем ввести что-то вроде "sample text", и здравый смысл подсказывает нам, что вывод после выполнения printf должен быть "Hi, sample text", но нет.
Код:
| 0x5653b864f7c9 e872feffff call sym.imp.__isoc99_scanf
| 0x5653b864f7ce 488d45d0 lea rax, qword [local_30h]
| 0x5653b864f7d2 4889c6 mov rsi, rax
| 0x5653b864f7d5 488d3db30000. lea rdi, qword str.Hi___s ; 0x5653b864f88f ; "Hi, %s\n"
| ;-- rip:
| 0x5653b864f7dc b b800000000 mov eax, 0
| 0x5653b864f7e1 e83afeffff call sym.imp.printf ; int printf(const char *format)
| 0x5653b864f7e6 90 nop
| 0x5653b864f7e7 488b55f8 mov rdx, qword [local_8h]
| 0x5653b864f7eb 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x5653b864f7f4 7405 je 0x5653b864f7fb
| | 0x5653b864f7f6 e815feffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x5653b864f7fb c9 leave
\ 0x5653b864f7fc c3 ret
[0x5653b864f7dc]> dr
rax = 0x7ffceeaaabd0
rbx = 0x00000000
rcx = 0x7f1e4dd9e560
rdx = 0x7f1e4dd9f8d0
r8 = 0x00000000
r9 = 0x00000000
r10 = 0x00000000
r11 = 0x5653b864f88e
r12 = 0x5653b864f660
r13 = 0x7ffceeaaacf0
r14 = 0x00000000
r15 = 0x00000000
rsi = 0x7ffceeaaabd0
rdi = 0x5653b864f88f
rsp = 0x7ffceeaaabd0
rbp = 0x7ffceeaaac00
rip = 0x5653b864f7dc
rflags = 0x00000206
orax = 0xffffffffffffffff
[0x5653b864f7dc]> pxw @ 0x7ffceeaaabd0
0x7ffceeaaabd0 0x706d6173 0x0000656c 0xb864f84d 0x00005653 sample..M.d.SV..
Как мы видим, scanf сохранил только "sample", пропустив " text". Это потому, что scanf означает scan formated, поэтому нам нужно указать вход, который мы ожидаем, нам нужно определить спецификатор формата.
Format specifier | Description | Supported data types |
---|---|---|
%c | Character | char unsigned char |
%d | Signed Integer | short unsigned short int long |
%e or %E | Scientific notation of float values | float double |
%f | Floating point | float |
%g or %G | Similar as %e or %E | float double |
%hi | Signed Integer(Short) | short |
%hu | Unsigned Integer(Short) | unsigned short |
%i | Signed Integer | short unsigned short int long |
%l or %ld or %li | Signed Integer | long |
%lf | Floating point | double |
%Lf | Floating point | long double |
%lu | Unsigned integer | unsigned int unsigned long |
%lli, %lld | Signed Integer | long long |
%llu | Unsigned Integer | unsigned long long |
%o | Octal representation of Integer. | short unsigned short int unsigned int long |
%p | Address of pointer to void void * | void * |
%s | String | char * |
%u | Unsigned Integer | unsigned int unsigned long |
%x or %X | Hexadecimal representation of Unsigned Integer | short unsigned short int unsigned int long |
%n | Prints nothing | |
%% | Prints % character |
Вызов функции в таком виде:
scanf("%[^\n]",text);
Позволяет регистрировать места. scanf считывает данные из ввода на основе спецификатора, точно так же, как printf выводит содержимое.
Puts and gets
Другими методами получения строк со входа или их печати являются gets и puts. Gets получает символы со стандартного ввода и сохраняет их в виде строки, а puts печатает символы со стандартного вывода. Оба действуют так же, как printf и scanf, главное отличие в том, что здесь нет спецификации на формат. Основное различие здесь исходит от формата (
Ссылка скрыта от гостей
), так как, например, по умолчанию gets не останавливается, если встречает пробел. То же самое с puts, puts просто выгрузит содержимое массива char, интерпретируя его как строку.Рассмотрим следующий код:
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
char text[40];
puts("Name?: ");
gets(text);
printf("Ho, %s\n", text);
}
Здесь puts используется так же, как printf, а gets - для чтения из stdin, давайте посмотрим:
Код:
[0x5618704127a4]> pdf
/ (fcn) sym.func 99
| sym.func ();
| ; var int local_30h @ rbp-0x30
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x561870412793 (sym.main)
| 0x5618704127a4 55 push rbp
| 0x5618704127a5 4889e5 mov rbp, rsp
| 0x5618704127a8 4883ec30 sub rsp, 0x30 ; '0'
| 0x5618704127ac 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x5618704127b5 488945f8 mov qword [local_8h], rax
| 0x5618704127b9 31c0 xor eax, eax
| 0x5618704127bb 488d3dd20000. lea rdi, qword str.Name_: ; 0x561870412894 ; "Name?: "
| 0x5618704127c2 e859feffff call sym.imp.puts ; int puts(const char *s)
| 0x5618704127c7 488d45d0 lea rax, qword [local_30h]
| 0x5618704127cb 4889c7 mov rdi, rax
| 0x5618704127ce b800000000 mov eax, 0
| 0x5618704127d3 e888feffff call sym.imp.gets ; char*gets(char *s)
| 0x5618704127d8 488d45d0 lea rax, qword [local_30h]
| 0x5618704127dc 4889c6 mov rsi, rax
| 0x5618704127df 488d3db60000. lea rdi, qword str.Ho___s ; 0x56187041289c ; "Ho, %s\n"
| 0x5618704127e6 b800000000 mov eax, 0
| 0x5618704127eb e850feffff call sym.imp.printf ; int printf(const char *format)
| 0x5618704127f0 90 nop
| 0x5618704127f1 488b55f8 mov rdx, qword [local_8h]
| 0x5618704127f5 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x5618704127fe 7405 je 0x561870412805
| | 0x561870412800 e82bfeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x561870412805 c9 leave
\ 0x561870412806 c3 ret
[0x5618704127a4]>
В этом случае строка "Name..." передается в функцию puts с помощью rdi, мы также видим, что eax обнуляется, вероятно, потому, что не используются векторные регистры. Затем у нас есть функция gets:
Код:
| 0x5618704127c7 488d45d0 lea rax, qword [local_30h]
| 0x5618704127cb 4889c7 mov rdi, rax
| 0x5618704127ce b800000000 mov eax, 0
| 0x5618704127d3 e888feffff call sym.imp.gets ; char*gets(char *s)
Опять же, local_30h используется как указатель на считываемую строку и передается в функцию gets через регистр rdi.
Если мы отладим функцию, то на этот раз мы сможем увидеть, как сохраняется полная строка, включая белые пробелы.
Код:
| 0x5618704127d3 e888feffff call sym.imp.gets ; char*gets(char *s)
| 0x5618704127d8 488d45d0 lea rax, qword [local_30h]
| 0x5618704127dc 4889c6 mov rsi, rax
| 0x5618704127df 488d3db60000. lea rdi, qword str.Ho___s ; 0x56187041289c ; "Ho, %s\n"
| 0x5618704127e6 b800000000 mov eax, 0
| 0x5618704127eb e850feffff call sym.imp.printf ; int printf(const char *format)
| 0x5618704127f0 90 nop
| 0x5618704127f1 488b55f8 mov rdx, qword [local_8h]
| 0x5618704127f5 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x5618704127fe 7405 je 0x561870412805
| | 0x561870412800 e82bfeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x561870412805 c9 leave
\ 0x561870412806 c3 ret
[0x5618704127a4]> db 0x5618704127df
[0x5618704127a4]> dc
Name?:
SAMPLE TEXT
hit breakpoint at: 5618704127df
[0x5618704127a4]> dr
rax = 0x7ffe03c95df0
rbx = 0x00000000
rcx = 0x7f9f10693a00
rdx = 0x7f9f106958d0
r8 = 0x561870acd67c
r9 = 0x7f9f108a34c0
r10 = 0x561870acd010
r11 = 0x00000246
r12 = 0x561870412680
r13 = 0x7ffe03c95f10
r14 = 0x00000000
r15 = 0x00000000
rsi = 0x7ffe03c95df0
rdi = 0x7ffe03c95df1
rsp = 0x7ffe03c95df0
rbp = 0x7ffe03c95e20
rip = 0x5618704127df
rflags = 0x00000246
orax = 0xffffffffffffffff
[0x5618704127a4]> pxw @ 0x7ffe03c95df0
0x7ffe03c95df0 0x504d4153 0x5420454c 0x00545845 0x00005618 SAMPLE TEXT..V..¡
Вуаля! Как мы видим, на этот раз в памяти хранится полная строка. Важно отметить, что использование таких функций, как scanf и gets, вообще не рекомендуется, если мы пытаемся построить что-то серьезное, так как пользовательский ввод не контролируется вообще. Как мы видели, при использовании gets, пользовательский ввод буквально заносится в память, начиная с определенного адреса памяти, связанного с началом массива char, поэтому если пользователь введет действительно большую строку символов, что-то намного большее, чем пространство массива, это, вероятно, приведет к поломке программы, также, поскольку gets читает все, что пользователь выводит на stdin и сохраняет в памяти, пользователь может даже ввести код, который может быть выполнен.
Strlen
Мы можем взаимодействовать с массивами char разными способами. Функция strlen, которая, вероятно, встречается во многих программах и ctf'ах, возвращает длину строки.
C:
# include <stdio.h>
main(){
func();
getchar();
}
func(){
char text[40];
puts("Name?: ");
gets(text);
printf("Hi, %s\n", text);
printf("Length: %d chars", strlen(text));
}
strlen проходит через строку и считает, сколько позиций имеет хит, он проходит строку char за char, пока не встретит нулевой терминатор (\0). Сейчас мы проверим, что это значит:
Код:
[0x55df8ad0a7e4]> pdf
/ (fcn) sym.func 131
| sym.func ();
| ; var int local_30h @ rbp-0x30
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x55df8ad0a7d3 (sym.main)
| 0x55df8ad0a7e4 55 push rbp
| 0x55df8ad0a7e5 4889e5 mov rbp, rsp
| 0x55df8ad0a7e8 4883ec30 sub rsp, 0x30 ; '0'
| 0x55df8ad0a7ec 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x55df8ad0a7f5 488945f8 mov qword [local_8h], rax
| 0x55df8ad0a7f9 31c0 xor eax, eax
| 0x55df8ad0a7fb 488d3df20000. lea rdi, qword str.Name_: ; 0x55df8ad0a8f4 ; "Name?: "
| 0x55df8ad0a802 e849feffff call sym.imp.puts ; int puts(const char *s)
| 0x55df8ad0a807 488d45d0 lea rax, qword [local_30h]
| 0x55df8ad0a80b 4889c7 mov rdi, rax
| 0x55df8ad0a80e b800000000 mov eax, 0
| 0x55df8ad0a813 e888feffff call sym.imp.gets ; char*gets(char *s)
| 0x55df8ad0a818 488d45d0 lea rax, qword [local_30h]
| 0x55df8ad0a81c 4889c6 mov rsi, rax
| 0x55df8ad0a81f 488d3dd60000. lea rdi, qword str.Hi___s ; 0x55df8ad0a8fc ; "Hi, %s\n"
| 0x55df8ad0a826 b800000000 mov eax, 0
| 0x55df8ad0a82b e850feffff call sym.imp.printf ; int printf(const char *format)
| 0x55df8ad0a830 488d45d0 lea rax, qword [local_30h]
| 0x55df8ad0a834 4889c7 mov rdi, rax
| 0x55df8ad0a837 e824feffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x55df8ad0a83c 4889c6 mov rsi, rax
| 0x55df8ad0a83f 488d3dbe0000. lea rdi, qword str.Length:__d_chars ; 0x55df8ad0a904 ; "Length: %d chars"
| 0x55df8ad0a846 b800000000 mov eax, 0
| 0x55df8ad0a84b e830feffff call sym.imp.printf ; int printf(const char *format)
| 0x55df8ad0a850 90 nop
| 0x55df8ad0a851 488b55f8 mov rdx, qword [local_8h]
| 0x55df8ad0a855 644833142528. xor rdx, qword fs:[0x28]
| ,=< 0x55df8ad0a85e 7405 je 0x55df8ad0a865
| | 0x55df8ad0a860 e80bfeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x55df8ad0a865 c9 leave
\ 0x55df8ad0a866 c3 ret
[0x55df8ad0a7e4]>
Давайте сосредоточимся на функции strlen.
Код:
| 0x55df8ad0a82b e850feffff call sym.imp.printf ; int printf(const char *format)
| 0x55df8ad0a830 488d45d0 lea rax, qword [local_30h]
| 0x55df8ad0a834 4889c7 mov rdi, rax
| 0x55df8ad0a837 e824feffff call sym.imp.strlen ; size_t strlen(const char *s)
| 0x55df8ad0a83c 4889c6 mov rsi, rax
Как обычно, строка находится внутри local_30h. Поэтому эта ссылка загружается в rdi в качестве параметра, а затем вызывается strlen. Давайте поставим несколько точек останова и проанализируем программу.
Код:
[0x55df8ad0a7e4]> dc
Name?:
SAMPLE TEXT
Hi, SAMPLE TEXT
hit breakpoint at: 55df8ad0a834
[0x55df8ad0a834]> dr
rax = 0x7ffcfc542170
[...]
[0x55df8ad0a834]> pxw @ 0x7ffcfc542170
0x7ffcfc542170 0x504d4153 0x5420454c 0x00545845 0x000055df SAMPLE TEXT..U..
Мы видим, как "SAMPLE TEXT" был сохранен как строка внутри local_30h, и если мы обратим немного больше внимания на дамп памяти, то увидим, что строка заканчивается 0x00, что является нулевым терминатором, поэтому strlen будет продолжать читать, пока не достигнет этого нулевого терминатора.
Если мы продолжим исследование strlen, то увидим, что функция возвращает свое значение (длину строки), используя регистр RAX.
Код:
[0x55df8ad0a834]> dc
hit breakpoint at: 55df8ad0a83c
[0x55df8ad0a834]> dr
rax = 0x0000000b
Как мы видим, rax содержит 0xb = 11dec, а "SAMPLE TEXT" имеет длину 11 (с учетом пробела).
Библиотека для работы со строками
Библиотека строк "string.h" содержит различные функции, очень полезные для работы со строками. Эти функции позволяют нам манипулировать строками различными способами, здесь мы рассмотрим, как они полезны для копирования строк.
Рассмотрим следующую программу:
C:
# include <stdio.h>
# include <string.h>
main(){
func();
getchar();
}
func(){
char text1[40], text2[40], text3[10];
printf("Enter a string NOW: ");
gets(text1);
strcpy(text2, text1);
printf("Copied string = %s\n", text2);
strncpy(text3, text1, 4);
printf("4 first chars = %s\n", text3);
}
Мы видим, что сверху включается файл strings.h, затем объявляются 3 массива символов и используется strcpy для копирования содержимого одного из них в другой.
Мы можем легко определить используемую библиотеку strings с помощью afl
Код:
:~/chapter5$ radare2 ./string
[0x000006d0]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x000006d0]> afl
0x00000000 2 25 sym.imp.__libc_start_main
0x00000630 3 23 sym._init
0x00000660 1 6 sym.imp.strncpy
0x00000670 1 6 sym.imp.strcpy
0x00000680 1 6 sym.imp.__stack_chk_fail
0x00000690 1 6 sym.imp.printf
0x000006a0 1 6 sym.imp.getchar
0x000006b0 1 6 sym.imp.gets
0x000006c0 1 6 sub.__cxa_finalize_248_6c0
0x000006d0 1 43 entry0
0x00000700 4 50 -> 40 sym.deregister_tm_clones
0x00000740 4 66 -> 57 sym.register_tm_clones
0x00000790 4 49 sym.__do_global_dtors_aux
0x000007d0 1 10 entry1.init
0x000007da 1 26 sym.main
0x000007f4 3 171 sym.func
0x000008a0 4 101 sym.__libc_csu_init
0x00000910 1 2 sym.__libc_csu_fini
0x00000914 1 9 sym._fini
[0x000006d0]>
и с ii/il
Код:
[0x7f47eeef5090]> il
[Linked libraries]
libc.so.6
1 library
[0x7f47eeef5090]> ii
[Imports]
1 0x55896339b660 GLOBAL FUNC strncpy
2 0x55896339b000 WEAK NOTYPE _ITM_deregisterTMCloneTable
3 0x55896339b670 GLOBAL FUNC strcpy
4 0x55896339b680 GLOBAL FUNC __stack_chk_fail
5 0x55896339b690 GLOBAL FUNC printf
6 0x55896339b000 GLOBAL FUNC __libc_start_main
7 0x55896339b6a0 GLOBAL FUNC getchar
8 0x55896339b000 WEAK NOTYPE __gmon_start__
9 0x55896339b6b0 GLOBAL FUNC gets
10 0x55896339b000 WEAK NOTYPE _ITM_registerTMCloneTable
11 0x55896339b000 WEAK FUNC __cxa_finalize
2 0x55896339b000 WEAK NOTYPE _ITM_deregisterTMCloneTable
6 0x55896339b000 GLOBAL FUNC __libc_start_main
8 0x55896339b000 WEAK NOTYPE __gmon_start__
10 0x55896339b000 WEAK NOTYPE _ITM_registerTMCloneTable
11 0x55896339b000 WEAK FUNC __cxa_finalize
[0x7f47eeef5090]>
И код:
Код:
[0x5585de6317f4]> pdf
/ (fcn) sym.func 171
| sym.func ();
| ; var int local_6ah @ rbp-0x6a
| ; var int local_60h @ rbp-0x60
| ; var int local_30h @ rbp-0x30
| ; var int local_8h @ rbp-0x8
| ; CALL XREF from 0x5585de6317e3 (sym.main)
| 0x5585de6317f4 55 push rbp
| 0x5585de6317f5 4889e5 mov rbp, rsp
| 0x5585de6317f8 4883ec70 sub rsp, 0x70 ; 'p'
| 0x5585de6317fc 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x5585de631805 488945f8 mov qword [local_8h], rax
| 0x5585de631809 31c0 xor eax, eax
| 0x5585de63180b 488d3d120100. lea rdi, qword str.Enter_a_string_NOW: ; 0x5585de631924 ; "Enter a string NOW: "
| 0x5585de631812 b800000000 mov eax, 0
| 0x5585de631817 e874feffff call sym.imp.printf ; int printf(const char *format)
| 0x5585de63181c 488d45a0 lea rax, qword [local_60h]
| 0x5585de631820 4889c7 mov rdi, rax
| 0x5585de631823 b800000000 mov eax, 0
| 0x5585de631828 e883feffff call sym.imp.gets ; char*gets(char *s)
| 0x5585de63182d 488d55a0 lea rdx, qword [local_60h]
| 0x5585de631831 488d45d0 lea rax, qword [local_30h]
| 0x5585de631835 4889d6 mov rsi, rdx
| 0x5585de631838 4889c7 mov rdi, rax
| 0x5585de63183b e830feffff call sym.imp.strcpy ; char *strcpy(char *dest, const char *src)
| 0x5585de631840 488d45d0 lea rax, qword [local_30h]
| 0x5585de631844 4889c6 mov rsi, rax
| 0x5585de631847 488d3deb0000. lea rdi, qword str.Copied_string_____s ; 0x5585de631939 ; "Copied string = %s\n"
| 0x5585de63184e b800000000 mov eax, 0
| 0x5585de631853 e838feffff call sym.imp.printf ; int printf(const char *format)
| 0x5585de631858 488d4da0 lea rcx, qword [local_60h]
| 0x5585de63185c 488d4596 lea rax, qword [local_6ah]
| 0x5585de631860 ba04000000 mov edx, 4
| 0x5585de631865 4889ce mov rsi, rcx
| 0x5585de631868 4889c7 mov rdi, rax
| 0x5585de63186b e8f0fdffff call sym.imp.strncpy ; char *strncpy(char *dest, const char *src, size_t n)
| 0x5585de631870 488d4596 lea rax, qword [local_6ah]
| 0x5585de631874 4889c6 mov rsi, rax
| 0x5585de631877 488d3dd00000. lea rdi, qword str.4_first_chars____s ; 0x5585de63194e ; "4 first chars = %s\n"
| 0x5585de63187e b800000000 mov eax, 0
| 0x5585de631883 e808feffff call sym.imp.printf ; int printf(const char *format)
| 0x5585de631888 90 nop
| 0x5585de631889 488b4df8 mov rcx, qword [local_8h]
| 0x5585de63188d 6448330c2528. xor rcx, qword fs:[0x28]
| ,=< 0x5585de631896 7405 je 0x5585de63189d
| | 0x5585de631898 e8e3fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
| `-> 0x5585de63189d c9 leave
\ 0x5585de63189e c3 ret
[0x5585de6317f4]>
На этот раз дисазм выглядит больше, но пусть это вас не пугает, почти все нам уже известно. Начнем с того, что увидим, как три переменные определяются r2.
Затем строка получается с помощью gets и вызывается strcpy, давайте посмотрим:
Код:
| 0x5585de63181c 488d45a0 lea rax, qword [local_60h]
| 0x5585de631820 4889c7 mov rdi, rax
| 0x5585de631823 b800000000 mov eax, 0
| 0x5585de631828 e883feffff call sym.imp.gets ; char*gets(char *s)
| 0x5585de63182d 488d55a0 lea rdx, qword [local_60h]
| 0x5585de631831 488d45d0 lea rax, qword [local_30h]
| 0x5585de631835 4889d6 mov rsi, rdx
| 0x5585de631838 4889c7 mov rdi, rax
| 0x5585de63183b e830feffff call sym.imp.strcpy ; char *strcpy(c
Как мы видим, строка (пользовательский ввод) будет сохранена в local_60h, затем local_60h и local_30h будут переданы в strcpy, в этот момент мы уже знаем, что local_60h содержит пользовательский ввод, но local_30h? это будет уничтожение, поэтому пользовательский ввод будет скопирован туда. Давайте отладим это
Код:
[0x5585de6317f4]> db 0x5585de631840
[0x5585de6317f4]> dc
Enter a string NOW: SAMPLE TEXT
hit breakpoint at: 5585de631840
[0x5585de631840]> pdf
[...]
| 0x5585de631838 4889c7 mov rdi, rax
| 0x5585de63183b e830feffff call sym.imp.strcpy ; char *strcpy(char *dest, const char *src)
| ;-- rip:
| 0x5585de631840 b 488d45d0 lea rax, qword [local_30h]
| 0x5585de631844 4889c6 mov rsi, rax
| 0x5585de631847 488d3deb0000. lea rdi, qword str.Copied_string_____s ;
И если мы проверим их:
Код:
[0x5585de631840]> afvd
var local_8h = 0x7fffd173cd38 0x71dec5cbb8695200 .Ri....q
var local_60h = 0x7fffd173cce0 0x5420454c504d4153 SAMPLE T @rsi ascii
var local_30h = 0x7fffd173cd10 0x5420454c504d4153 SAMPLE T @rdi ascii
var local_6ah = 0x7fffd173ccd6 0x0000000000000000 ........ r15
[0x5585de631840]>
[0x5585de631840]> pxw @ 0x7fffd173cce0
0x7fffd173cce0 0x504d4153 0x5420454c 0x00545845 0x00000000 SAMPLE TEXT.....
0x7fffd173ccf0 0x00000009 0x00000000 0x39a71660 0x00007fa0 ........`..9....
0x7fffd173cd00 0xd173cd68 0x00007fff 0x00f0b5ff 0x00000000 h.s.............
0x7fffd173cd10 0x504d4153 0x5420454c 0x00545845 0x00005585 SAMPLE TEXT..U..
Строка скопирована Также обратите внимание, что обе строки имеют \0-окончание, поэтому strcpy будет продолжать копирование, пока не встретит \0.
Позже, в коде, вызывается strNcpy. Это другая функция, "n" в ней относится к следующему: с помощью strncpy содержимое одной строки копируется в другую, но копируются только первые n байт. Давайте проверим это:
Код:
| 0x5585de631858 488d4da0 lea rcx, qword [local_60h]
| 0x5585de63185c 488d4596 lea rax, qword [local_6ah]
| 0x5585de631860 ba04000000 mov edx, 4
| 0x5585de631865 4889ce mov rsi, rcx
| 0x5585de631868 4889c7 mov rdi, rax
| 0x5585de63186b e8f0fdffff call sym.imp.strncpy ; char *strncpy(char *dest, const char *src, size_t n)
| 0x5585de631870 488d4596 lea rax, qword [local_6ah]
Это очень просто, опять же, передаются оба адреса памяти происхождения/назначения, но также передается и 4, количество байт, которое нам нужно скопировать.
Код:
[0x5585de631840]> db 0x5585de631870
[0x5585de631840]> dc
Copied string = SAMPLE TEXT
hit breakpoint at: 5585de631870
[0x5585de631840]> afvd
var local_8h = 0x7fffd173cd38 0x71dec5cbb8695200 .Ri....q
var local_60h = 0x7fffd173cce0 0x5420454c504d4153 SAMPLE T @rsi ascii
var local_30h = 0x7fffd173cd10 0x5420454c504d4153 SAMPLE T ascii
var local_6ah = 0x7fffd173ccd6 0x00000000504d4153 SAMP.... @rdi ascii
Было скопировано всего 4 байта. Важно отметить, что в случае с strncpy нулевой терминатор не вставляется по умолчанию, и это имеет смысл, потому что, возможно, наша цель - просто "слить" пару массивов, а не копировать строку в пустой массив.....
Если мы хотим разрезать массив, мы можем вручную вставить нулевой терминатор таким образом:
C:
# include <stdio.h>
# include <string.h>
main(){
func();
getchar();
}
func(){
char text1[40], text2[40], text3[10];
printf("ENTER A STRING: ");
gets(text1);
strcpy(text2, text1);
printf("Copied string = %s\n", text2);
strncpy(text3, text1, 4);
text3[4] = '\0';
printf("4 FIRST LETTERS %s\n", text3);
}
Как мы видим, в конце строки вручную добавляется ноль.
Код:
0x561d7710e86b e8f0fdffff call sym.imp.strncpy ; char *strncpy(char *dest, const char *src, size_t n)
| 0x561d7710e870 c6459a00 mov byte [local_66h], 0
| 0x561d7710e874 488d4596 lea rax, qword [local_6ah]
| 0x561d7710e878 4889c6 mov rsi, rax
| 0x561d7710e87b 488d3dd70000. lea rdi, qword str.4_FIRST_LETTERS__s ; 0x561d7710e959 ; "4 FIRST LETTERS %s\n"
| 0x561d7710e882 b800000000 mov eax, 0
На этом пока все, как и было сказано, мы перейдем к более сложным структурам в следующем посте.
Источник:
Ссылка скрыта от гостей
Последнее редактирование модератором: