@shellex说:No public Twitter messages.
  • Pages

  • Topics

  • 随便看看

  • 路边社评论员

    • zhangshine:
      呵呵,99宿舍网,好搞 »
    • zhangshine:
      好囧o(╯□╰)o »
    • 御前:
      你大爺 老子可是半專業的!剛入了L號I4...挖哈哈哈3k3(血淚可惜原來的非凡沒帶來北京 要不可以... »
    • alswl:
      @shellex 哦,发现我随便点开2张喜欢的,都进了deviantwear囧~ »
    • alswl:
      @shellex 不错不错,我还专门跑去那个网站看了,貌似是卖T恤的?我个人最喜欢彩虹色的桌面 »
    • alswl:
      我是来看图的~ »
    • SunnyGao:
      从来木考虑过要备案囧...... »
    • wangxxx:
      在学雷锋日 前来留名 »
    • makestory:
      乐观预计2020年之前这款游戏可以正式上市。 »
    • xiao3:
      你的模板都太暗!暗色系~~ »
    • shellexy:
      贴上应付猫和苏的桌面点名http://img.ly/A3thttp://img.ly/A3V文字什么... »
    • shellexy:
      @shellex 我点击了【回复这个鸟人。】,结果发现是回复猫猫。为啥上次还是黑色字体的,这回又... »

初尝Linux栈溢出

利用栈缓冲区溢出的本质,就是利用程序的漏洞和缺陷,使用精心构造的输入去触发溢出,改变EIP寄存器的值,从而能够控制程序的流程。

但是EIP寄存器可不是像通用寄存器方便的,能说改就改,这可是整个程序流的根本所在。但是难改是难改,但是不代表没法改嘛。

程序在执行子过程时总是要根据过程地址通过跳转指令重新设置eip的值,原来的值则和别的状态一起暂时保存在栈中,这就给了Shellex可乘之机。

Shellex的平台:

$ uname -a
Linux shellex-laptop 2.6.27-7-generic #1 SMP Tue Nov 4 19:33:20 UTC 2008 i686 GNU/Linux

首先Shellex写了这么一个程序

#include "stdio.h"
#include "string.h"
int fun(char *str) {
    char buffer[10];
    strcpy(buffer,str);
    printf("%s",buffer);
    return 0;
}
int main(int argc,char *argv[]) {
    int i=0;
    char *str;
    str=argv[1];
    fun(str);
    return 0;
}

然后用gcc编译一下,再使用gdb加载进入,下断点在12行,然后以参数123456运行

$ gcc -g -fno-stack-protector test.c -o test
$ gdb test
...
(gdb) b 14
Breakpoint 1 at 0x804843e: file test.c, line 14.

(gdb) r 123456
Starting program: /home/shellex/Desktop/test 123456

Breakpoint 1, main (argc=2, argv=0xbfb70044) at test.c:14
14	    str=argv[1];

接下来偶反汇编一下main函数:

(gdb) disassemble main
Dump of assembler code for function main:
0x08048426 :	lea    0x4(%esp),%ecx
0x0804842a :	and    $0xfffffff0,%esp
0x0804842d :	pushl  -0x4(%ecx)
0x08048430 :	push   %ebp
0x08048431 :	mov    %esp,%ebp
0x08048433 :	push   %ecx
0x08048434 :	sub    $0x14,%esp
0x08048437 :	movl   $0x0,-0xc(%ebp)
0x0804843e :	mov    0x4(%ecx),%eax
0x08048441 :	add    $0x4,%eax
0x08048444 :	mov    (%eax),%eax
0x08048446 :	mov    %eax,-0x8(%ebp)
0x08048449 :	mov    -0x8(%ebp),%eax
0x0804844c :	mov    %eax,(%esp)
0x0804844f :	call   0x80483f4
0x08048454 :	mov    $0x0,%eax
0x08048459 :	add    $0x14,%esp
0x0804845c :	pop    %ecx
0x0804845d :	pop    %ebp
0x0804845e :	lea    -0x4(%ecx),%esp
0x08048461 :	ret
End of assembler dump.

可以看到,在0×0804844f处,执行了call fun,接下来的0×08048454就是fun执行完毕以后接着执行的起始地址,先记住这个地址 0×08048454。

接着next, si, si, si进入fun函数, 反汇编fun函数:

(gdb) disassemble fun
Dump of assembler code for function fun:
0x080483f4 :	push   %ebp
0x080483f5 :	mov    %esp,%ebp
0x080483f7 :	sub    $0x18,%esp
0x080483fa :	mov    0x8(%ebp),%eax
0x080483fd :	mov    %eax,0x4(%esp)
0x08048401 :	lea    -0xa(%ebp),%eax
0x08048404 :	mov    %eax,(%esp)
0x08048407 :	call   0x804831c
0x0804840c :	lea    -0xa(%ebp),%eax
0x0804840f :	mov    %eax,0x4(%esp)
0x08048413 :	movl   $0x8048530,(%esp)
0x0804841a :	call   0x804832c

0x0804841f :	mov    $0x0,%eax
0x08048424 :	leave
0x08048425 :	ret
End of assembler dump.

然后查看一下栈顶指针和栈的情况。

(gdb) i reg $esp
esp            0xbfb6ff8c	0xbfb6ff8c
(gdb) x/16x $esp
0xbfb6ff8c:	0x08048454	0xbfb70658	0x08049ff4	0xbfb6ffb8
0xbfb6ff9c:	0x00000000	0xbfb70658	0xbfb6ffc0	0xbfb70018
0xbfb6ffac:	0xb7df7685	0x08048480	0x08048340	0xbfb70018
0xbfb6ffbc:	0xb7df7685	0x00000002	0xbfb70044	0xbfb70050

会发现栈顶0xbfb6ff8c的值刚好就是我们先前记录的那个返回地址0×08048454。因为执行call fun的时候,这条指令相当于:

push %eip
jmp 0x8048430

我们知道%eip即下一条指令的值,而call fun的下一条指令地址刚好就是0×08048454。
只有这样,fun函数在返回的时候才能从栈中知道应该回到哪里去执行。
接着看看buffer:

(gdb) n
6	    strcpy(buffer,str);
(gdb) p (char*)buffer
$1 = 0xbfb6ff7e "..."
(gdb) x/16x $esp
0xbfb6ff70:	0x00000000	0x00000000	0xbfb70634	0xb7e4fdae
0xbfb6ff80:	0xb7efe849	0x08049ff4	0xbfb6ffa8	0x08048454
0xbfb6ff90:	0xbfb70658	0x08049ff4	0xbfb6ffb8	0x00000000
0xbfb6ffa0:	0xbfb70658	0xbfb6ffc0	0xbfb70018	0xb7df7685

现在程序为buffer分配了一块内存,从0xbfb6ff7e到0xbfb6ff8b,大小为10字节。紧跟在它后面的,是main函数中进入fun函数前的栈顶地址(0xbfb6ffa8)和是返回值的地址(0×08048454)
接着运行:

(gdb) n
7	    printf("%s",buffer);
(gdb) x/16x $esp
0xbfb6ff70:	0xbfb6ff7e	0xbfb70658	0xbfb70634	0x3231fdae
0xbfb6ff80:	0x36353433	0x08049f00	0xbfb6ffa8	0x08048454
0xbfb6ff90:	0xbfb70658	0x08049ff4	0xbfb6ffb8	0x00000000
0xbfb6ffa0:	0xbfb70658	0xbfb6ffc0	0xbfb70018	0xb7df7685

可以看到从0xbfb6ff7e开始的6个字节被覆盖成了31, 32, 33, 34, 35, 36 (x86平台的地址遵循高位在前,低位在后的原则)

现在好了,如果我们复制字符串的时候不太小心,超过了14字节,就会把后面的返回值地址给覆盖掉(如果你打算不返回main函数的话,main的栈顶地址就无所谓了)。

覆盖掉的后果?

0x08048424 :	leave  #相当于mov %ebp %esp和pop %ebp
0x08048425 :	ret    #相当于pop eip

呵呵,覆盖掉以后,再Pop出来的eip就不是原来那个eip咯~

如何利用?

在刚才那个程序的基础上编写如下程序:

#include
#include "stdio.h"
#include "string.h"
void foo(){
    char *a="Shit.\n";
    __asm__ __volatile__(
        "movl	$6, %%edx;\n"	//str len
        "movl	%0, %%ecx;\n"	//str addr
        "movl	$1, %%ebx;\n"		//file handle (stdout)
        "movl	$4, %%eax;\n"		//system call number (sys_write)
        "int	    $0x80;\n"		// call kernel
        //# and exit"
        "movl	$0, %%ebx;\n"		// first argument: exit code
        "movl	$1, %%eax;\n"		// system call number (sys_exit)
        "int	    $0x80;\n"		// call kernel
        :
        :"m"(a)
        :"%eax");
}
int fun(char *str)
{
    char buffer[10];
    strcpy(buffer,str);
    printf("%s",buffer);
    return 0;

}
int main(int argc,char **argv)
{
    char str[]="AAAAAAAAAA"
               "AAAAAAA";

    *((int*)(&str[14]))=(int)foo;

    fun(str);
    return 0;
}

这个程序也非常简单。可以看到,除了main中的微妙变化,别的就是多加了一个fun函数。这个函数使用Linux中断直接调用系统调用,执行了一个打印字符串”Shit.”的操作 和 一个退出程序操作。之所以使用内联汇编的原因是偶不敢面对溢出成功后混乱的栈间关系(很难重定位啦,偶是个新手哈)
不过没关系,看上去这个程序它始终只用到了main和foo,fun函数就是幌子。

是么? 来运行一下:

./oft
Shit.

Shit. foo居然把’Shit’打印了出来!谜底将在gdb中呈现。
先跟刚才一样,看看返回地址:

...
0x08048498 :	mov    %eax,(%esp)
0x0804849b :	call   0x8048423
0x080484a0 :	mov    $0x0,%eax
...

然后断在了23行strcpy之前。来看看buffer被赋值前的内存状况:

(gdb) x/16x $esp
0xbfa31630:	0x00000000	0x00000000	0x00000000	0x00000000
0xbfa31640:	0x00000000	0x00000000	0xbfa31678	0x080484a0
0xbfa31650:	0xbfa31662	0x08049ff4	0xbfa31668	0x080482e8
0xbfa31660:	0x4141cff4	0x41414141	0x41414141	0x41414141

再看看buffer被赋值后的内存状况:

(gdb) n
24	    printf("%s",buffer);
(gdb) x/16x $esp
0xbfa31630:	0xbfa3163e	0xbfa31662	0x00000000	0x41410000
0xbfa31640:	0x41414141	0x41414141	0x41414141	0x080483f4
0xbfa31650:	0xbfa31690	0xbfa316e8	0xb7eb9685	0x080484c0
0xbfa31660:	0x08048340	0xbfa316e8	0xb7eb9685	0x41410001

在一大堆41的后面,返回地址已经变成foo的入口地址了~
当然随后fun一退出,eip就指向了foo,foo就开始执行了咯。

(gdb) n
25	    return 0;
(gdb) i reg eip
eip            0x804844e	0x804844e
(gdb) si
27	}
(gdb)
Cannot access memory at address 0x41414145
(gdb)
foo () at oft.c:4
4	void foo(){
(gdb) i reg eip
eip            0x80483f4	0x80483f4

bwt: 最后出现的访问错误是因为pop出的%ebp地址是错误的(它也被覆盖啦~但是为什么要加4呢?偶也没看懂,有搞安全的朋友告诉Shellex一下啦。谢谢)

(gdb) i reg ebp
ebp            0x41414141	0x41414141
  1. On February 21, 2009 at 1:43 am
    basicthinker :

    此文不发groups岂不可惜

    Notify
  2. On February 21, 2009 at 7:06 am

    @basicthinker,
    发呗。

    Notify
  3. On February 21, 2009 at 8:47 pm
    Dustman :

    分析反汇编 用gcc -S命令和objdump -d 来分析 比单用GDB的反汇编貌似更舒服些

    Notify
  4. On February 22, 2009 at 12:17 am
    shellex :

    函数很短,直接在GDB看比较方便而已。当然了,可以让他Gdb生成汇编文件或者用OBJdump去分析

    Notify

Leave a Reply