Вызывает переполнение буфера с помощью fgets
Я экспериментирую с переполнением буфера и пытаюсь перезаписать адрес возврата стека определенным вводом fgets
Это код:
void foo()
{
fprintf(stderr, "You did it.\n");
}
void bar()
{
char buf[20];
puts("Input:");
fgets(buf, 24, stdin);
printf("Your input:.\n", strlen(buf));
}
int main(int argc, char **argv)
{
bar();
return 0;
}
При обычном выполнении программа просто возвращает ваш ввод. Я хочу, чтобы он выводил foo() без изменения кода.
Моя идея состояла в том, чтобы переполнить буфер buf
введя 20 'A'
s. Это работает и вызывает ошибку сегментации. Моя следующая идея состояла в том, чтобы узнать адрес foo()
который \x4006cd
и добавить это к 20 'A'
s.
Насколько я понимаю, это должно переписать адрес возврата стека и заставить его перейти к foo
, Но это только вызывает segfault.
Что я делаю неправильно?
Обновление: Ассемблер сбрасывает главную
Dump of assembler code for function main:
0x000000000040073b <+0>: push %rbp
0x000000000040073c <+1>: mov %rsp,%rbp
0x000000000040073f <+4>: sub $0x10,%rsp
0x0000000000400743 <+8>: mov %edi,-0x4(%rbp)
0x0000000000400746 <+11>: mov %rsi,-0x10(%rbp)
0x000000000040074a <+15>: mov $0x0,%eax
0x000000000040074f <+20>: callq 0x4006f1 <bar>
0x0000000000400754 <+25>: mov $0x0,%eax
0x0000000000400759 <+30>: leaveq
0x000000000040075a <+31>: retq
End of assembler dump.
Foo
Dump of assembler code for function foo:
0x00000000004006cd <+0>: push %rbp
0x00000000004006ce <+1>: mov %rsp,%rbp
0x00000000004006d1 <+4>: mov 0x200990(%rip),%rax # 0x601068 <stderr@@GLIBC_2.2.5>
0x00000000004006d8 <+11>: mov %rax,%rcx
0x00000000004006db <+14>: mov $0x15,%edx
0x00000000004006e0 <+19>: mov $0x1,%esi
0x00000000004006e5 <+24>: mov $0x400804,%edi
0x00000000004006ea <+29>: callq 0x4005d0 <fwrite@plt>
0x00000000004006ef <+34>: pop %rbp
0x00000000004006f0 <+35>: retq
End of assembler dump.
бар:
Dump of assembler code for function bar:
0x00000000004006f1 <+0>: push %rbp
0x00000000004006f2 <+1>: mov %rsp,%rbp
0x00000000004006f5 <+4>: sub $0x20,%rsp
0x00000000004006f9 <+8>: mov $0x40081a,%edi
0x00000000004006fe <+13>: callq 0x400570 <puts@plt>
0x0000000000400703 <+18>: mov 0x200956(%rip),%rdx # 0x601060 <stdin@@GLIBC_2.2.5>
0x000000000040070a <+25>: lea -0x20(%rbp),%rax
0x000000000040070e <+29>: mov $0x18,%esi
0x0000000000400713 <+34>: mov %rax,%rdi
0x0000000000400716 <+37>: callq 0x4005b0 <fgets@plt>
0x000000000040071b <+42>: lea -0x20(%rbp),%rax
0x000000000040071f <+46>: mov %rax,%rdi
0x0000000000400722 <+49>: callq 0x400580 <strlen@plt>
0x0000000000400727 <+54>: mov %rax,%rsi
0x000000000040072a <+57>: mov $0x400821,%edi
0x000000000040072f <+62>: mov $0x0,%eax
0x0000000000400734 <+67>: callq 0x400590 <printf@plt>
0x0000000000400739 <+72>: leaveq
0x000000000040073a <+73>: retq
End of assembler dump.
2 ответа
Вы не считали с памятью aligment. Я немного изменил код, чтобы было легче найти нужное место.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int **x;
int z;
void foo()
{
fprintf(stderr, "You did it.\n");
}
void bar()
{
char buf[2];
//puts("Input:");
//fgets(buf, 70, stdin);
x = (int**) buf;
for(z=0;z<8;z++)
printf("%d X=%x\n", z, *(x+z));
*(x+3) = foo;
printf("Your input: %d %s\n", strlen(buf), buf);
}
int main(int argc, char **argv)
{
printf("Foo: %x\n", foo);
printf("Main: %x\n", main);
bar();
return 0;
}
С меньшим буфером, 2 в моем примере, я нашел обратный адрес на расстоянии 24 байта (x+3, для 8-байтовых указателей; 64 бита, без отладки, без оптимизации...) от начала буфера. Эта позиция может меняться в зависимости от размера буфера, архитектуры и т. Д. В этом примере мне удается изменить адрес возврата bar на foo. В любом случае при foo return вы получите ошибку сегментации, так как она не была правильно настроена на возврат в main.
Я добавил x и z как глобальные переменные, чтобы не изменять размер стека bar. Код будет отображать массив значений в виде указателей, начиная с buf[0]. В моем случае я нашел адрес main в позиции 3. Поэтому в конечном коде *(x+3) = foo. Как я уже сказал, эта позиция может меняться в зависимости от параметров компиляции, машины и т. Д. Чтобы найти правильную позицию, найдите адрес main (печатается перед строкой вызова) в списке адресов.
Важно отметить, что я сказал адрес в main, а не адрес main, потому что обратный адрес был установлен на строку после вызова bar, а не на начало main. Итак, в моем случае это был 0x4006af вместо 0x400668.
В вашем примере, с 20-байтовым буфером, насколько я знаю, он был выровнен по 32 байта (0x20).
Если вы хотите сделать то же самое с fgets, вам нужно выяснить, как набирать адрес foo, но если вы работаете на компьютере с архитектурой x86/x64, не забудьте добавить его в little enddian. Вы можете изменить код для отображения значений байт на байт, чтобы вы могли получить их в правильном порядке и набирать их, используя ALT+ номер. Помните, что числа, которые вы вводите, удерживая ALT, являются десятичными числами. Некоторые терминалы не будут дружелюбно обращаться с 0x00.
Мой вывод выглядит так:
$ gcc test.c -o test
test.c: In function ‘bar’:
test.c:21: warning: assignment from incompatible pointer type
$ ./test
Foo: 400594
Main: 400668
0 X=9560e9f0
1 X=95821188
2 X=889350f0
3 X=4006af
4 X=889351d8
5 X=0
6 X=0
7 X=95a1ed1d
Your input: 5 ▒▒`▒9
You did it.
Segmentation fault
void bar() { char buf[20]; puts("Input:"); fgets(buf, 24, stdin); printf("Your input:.\n", strlen(buf)); }
... Это работает и вызывает ошибку сегментации...
Компилятор, вероятно, заменяет fgets
с более безопасным вариантом, который включает проверку размера буфера назначения. Если проверка не пройдена, программа безоговорочно вызывает abort()
,
В этом конкретном случае вы должны скомпилировать программу с -U_FORTIFY_SOURCE
или же -D_FORTIFY_SOURCE=0
,