一般用5个步骤实现
先用高级语言编写shellcode程序
编译并反汇编这个shellcode程序
从汇编级分析程序执行流程
整理生成的汇编代码
尽量减小他的体积使他可注入
提取opcode,创建shellcode
shellcode地址问题
在shellcode里使用相对地址需要一些技巧。可以把shellcode在内存中的开始地址或 shellcode的重要元素复制到寄存器里,然后根据寄存器里的地址,精心构造每条指令。
实现这个方法有一个非常经典的技巧,就是shellcode以一条跳转指令开始,跳转指令跳过 shellcode后转到调用指令,直接跳到调用指令可以设置相对寻址。执行调用指令时,紧跟在调用指令之后的指令的地址将被压入栈。这个技巧有一个关键之处,就是你必须把想作为相对地址的基地址直接放在调用指令之后。那么,当调用指令被执行后,我们的基地址将自动保存在栈上, 而我们不必提前知道这个地址是什么。
我们最终还是要执行shellcode,因此,在跳转之后,调用指令将立即调用shellcode,而这将把执行控制交给shellcode。最后的修改效果是使紧跟在跳转之后的第一条指令是pop ESI,这条 指令将从栈上弹出基址并把它放入esi。至此,就可以根据esi的距离(或偏移量)来引用shellcode 里的代码。
让我们通过一段伪代码说明整个过程
jmp short GotoCall
shellcode:
pop esi
....
<shellcode meat>
...
GotoCall:
call shellcode
Db '/bin/sh'
(1) 第一条指令跳到GotoCall, GotoCall立即执行CALL指令。
(2) CALL指令把字符串(/bin/sh)第一个字节的地址压入栈。
(3) CALL 指令调用shellcode。
(4) shellcode的第一条指令是pop ESI,这条指令将把字符串(/bin/sh)地址的值载入esi。
(5) 至此,就可以用相对地址执行shellcode了。
既然地址问题解决了,就可以先用伪代码写shellcode,然后用真正的汇编指令替换伪代码, 从而得到梦寐以求的shellcode。在编写过程中,还需要在字符串的尾部保留一些占位符(这里是9B),如下:' /bin/ shJAAAAKKKK'
这些占位符有什么用处呢?我们将把系统调用所需要的3个参数中的2个(将被载入ECX、EDX)保存在这些占位符里。因为字符串的第一个字节的地址保存在ESI里,所以,对于替换和 把这些值复制到寄存器来说,很容易就能确定它们在内存中的位置。另外,可以通过“复制到占位符”方法,用空值有效终止这些字符串。步骤如下。
(1) 用xor EAX, EAX的结果(空值)填充EAX。
(2) 把AL复制到紧挨/bin/sh的字节位置来终止/bin/sh字符串。记住,因为xor EAX, EAX 指令把EAX变为空值,所以AL为空。为了把AL复制到正确的位置,必须算出/bin/sh的开头到J 占位符之间的距离(偏移量)。
(3) 得到保存在ESI里的字符串开头的地址,把它复制到EBX。
(4) 把EBX里的值(现在是字符串开头的地址)复制到AAAA占位符。这是execve系统调用要求的、将被执行的、指向二进制文件的参数指针。需要再次计算距离(偏移量)。
用正确的偏移量把保存在EAX的空值复制到KKKK占位符。
(6) 此时,不再需要用空值填充EAX了,因此,我们把execve的系统调用值(0X0b)复制 到AL。
(7) 把字符串的地址载入EBX。
(8) 把保存在AAAA占位符里的地址(一个指向字符串的指针)载入ECX。
(9) 把保存在KKKK占位符里的地址(一个指向空值的指针)载入EDX。
(10) 执行int 0x80。
将被翻译成shellcode的最终的汇编代码看起来像下面这样:
Section .text
global _start
_start:
jmp shoert GotoCall
shellcode:
pop esi
xor eax,eax
mov byte [esi+7],al
lea ebx,[esi]
mov long [esi+8],ebx
mov long [esi+12],eax
mov byte al,0x0b
mov ebx,esi
lea ecx,[esi+8]
lea edx,[esi+12]
int 0x80
GotoCall:
call shellcode
Db '/bin/shJAAAAKKKK'
缓冲区溢出的攻击技术
低地址 高地址
esp ebp
| buffer |返回地址|上个函数找賴
| NOPNOP . . SHELLCODE| RET .. RET
I I
+ — - - +
上面这种方法一般用尸被溢出的变量比较大,足以容纳Shellcode
低地址 高地址
esp ebp
| buffer I返回地址 丨 上个函数栈帧
| RET RET | RFT | NOP NOP SHELLCODE
+------>-------+
上面这种方法一般用户被溢出的变量比较小,不足以容纳Shellcode。但是这两种方法 Shellcode的地址都没法确定,传统的方法是在攻击程序里用一个汇编语句来获得当前的esp 的值:
__asm__ (“mov %eep, eax”);
然后加上偏移和在Shellcode前而加上大量nop指令來确保返回地址落入Shellcode对于本地溢出,还有一种更好的办法可以精确定位Shellcode地址:
攻击串3
低地址 高地址
esp ebp
| buffer | 返回地址丨上个函数栈帧 I环境变置
| RET .,. RET | RET | | SHELLCODE
+------------>----------------+
这种方法把Shellcode放在环境变量里,这样能精确定位Shellcode
先来看一 下堆栈最幵始的使用情况
堆栈数据
环境变臞
程序路径 <----- 0xbffffffc
0x0000000 <----- 0xc00000000
0xc0000000己经是不可访问地址,也就是所谓的栈底、Oxbffffffc开始的四个字节总是为0,那么用Oxbffffffc减去程序路径长度和后面的结束符0以及Shellcode长度和后面的结束符0就可以精确得到shellcode开始的地址有了这些信息.那么就很容易写出缓冲区溢出漏洞的攻击方法