【安全健行】(5):shellcode编码

2015/5/19 18:08:45

上一节我们介绍了基本shellcode的编写,采用的是exit()、setreuid()和execve()三个系统调用,实际中当然是根据自己的需要来选择合适的系统调用了,系统调用号需要查看syscalltable,参数的压入也是采取类似的方式,处理好堆栈,编写汇编代码并不十分困难。

这一节我们要来介绍下shellcode的编码,那么为什么要对shellcode进行编码呢?大致原因有以下几个:

  • 避免出现Bad字符,如\x00、\xa9等;
  • 避开IDS或其他网络检测器的检测;
  • 遵循字符串过滤器;

接下来,我们来简单介绍一种shellcode编码的方式。

一、简单的XOR编码

计算机中一种常见的位运算是XOR运算,即“按位异或”。当初自己记忆这个运算时还费了一些功夫才和“按位与|或”运算区分开。异或运算的本质是判断对应二进制位是否相同,若不同-->异-->True;若相同-->同-->False。因此可以说XOR运算是判断对应二进制位不同的运算

XOR运算有着很好的运算特性,即一个数与一个数XOR两次会得到自身:

0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 1 = 0
1 XOR 0 = 1
101 XOR 100 = 001
001 XOR 100 = 101 

我们利用这个特点可以构造shellcode编码和基本的加密,当然,密钥(上例中的0x100)自然要硬编码进shellcode了。

二、JMP/CALL XOR解码器

既然对shellcode编码,那么也就意味着要解码。我们的模型大概是下面的样子:

[decoder][encoded shellcodes] 

一般来说,如果解码器需要知道自己的位置,这样就可以计算出编码的shellcode的位置开始解码。确定解码器位置通常被称作GETPC,方法有许多种,今天我们来介绍其中的一种:JMP/CALL

JMP/CALL的思想是:

  1. JMP指令跳转到CALL指令;
  2. 该CALL指令位于编码的shellcode之前;
  3. CALL指令会创建一个新栈,因此将当前的EIP指针压栈(即编码shellcode的起始地址);
  4. CALL调用的过程将压栈的地址弹出保存到寄存器中;
  5. 利用保存的寄存器进行shellcode解码;
  6. JMP到shellcode处执行;

看起来复杂,我们看看下面的汇编代码就明白了,注意每条汇编语句后的执行顺序标号,可以帮助大家理解整个流程:

global _start

_start:
jmp short call_point    ;1. JMP to CALL

begin:
pop esi                 ;3.将栈中的shellcode地址弹出保存到寄存器esi中便于后续解码
xor ecx, ecx            ;4.清空ecx
mov cl, 0x0             ;5.shellcode长度设为0

short_xor:
xor byte[esi], 0x0      ;6.0x0是这里的编码key
inc esi                 ;7.ESI指针递增,遍历所有的shellcode字节
loop short_xor          ;8.循环直到shellcode解码完毕

jmp short shellcodes    ;9.跳过CALL直接到达shellcode段

call_point:
call begin              ;2.CALL begin过程,同时将当前的EIP即shellcode的起始地址压入栈中

shellcodes:             ;10.执行解码后的shellcode

这里放置解码后的shellcode 

整体的流程就是这样,只要仔细留心逻辑顺序和shellcode地址的压栈弹栈,JMP/CALL并不难理解。

Refer: Gray Hat Hacking: The Ethical Hacker‘s Handbook, Third Edition

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。