作者:backahasten 发表于小米安全中心微信公众号

0x00 前言

Unicorn可以模拟多种指令集的代码,在很多安全研究领域有很强大的作用,但是由于需要从头自己布置栈空间,代码段等虚拟执行环境,阻碍了他的使用,本文将会分析一个实例,并介绍Unicorn虚拟运行环境的构建。

本文的例子是一个白盒实现的DES算法,在riscrue的文章Unboxing the white box[1]中介绍了白盒攻击的类侧信道和类错误注入方法,并用这个程序作为例子。在riscure的代码中,由于python2和3对于字符串和bytes关系的变化很大,代码基本不可用。让我们来从头分析这个程序并编写设计Unicorn的代码。

Unicorn虽然可以脱离平台执行,但是在程序虚拟运行环境的设计阶段,逆向甚至动态调试都是不可避免的,Unicorn的运行环境构建分为:

  • 代码与程序段加载

  • 栈配置

  • 特殊寄存器配置

  • 外部调用patch

  • Unicorn中调试

0x001 代码与程序段加载

在这个过程中,我们需要把代码像正常程序加载一样放进我们的虚拟内存中,一般的步骤是,首先实例化一个CPU对象出来:

1mu =Uc(UC_ARCH_X86, UC_MODE_32)

上面这句话就是实例化了一个x86架构32位的CPU出来,之后就可以开始代码的加载和其他初始化(其他架构的配置请参考Unicorn官方文档)。代码段的加载以及接下来内存栈的初始化都可以使用如下的模板:

1mu.mem_map(address, size)            #分配一个内存空间,起始地址位address,大小为size
2mu.mem_write(address, data)          #在内存地址为address的位置存入data

首先进行代码段的载入,我们需要扫描ELF文件,根据程序头找到其中的代码段并进行加载。

1elf =ELFFile(open("./wbDES",'rb'))
2for seg in elf.iter_segments():
3    if seg.header.p_type =="PT_LOAD":
4        data =seg.data()
5        mapsz =PAGE_SIZE*int((len(data) +PAGE_SIZE)/PAGE_SIZE)
6        addr =seg.header.p_vaddr -(seg.header.p_vaddr %PAGE_SIZE)
7        mu.mem_map(addr, mapsz)
8        mu.mem_write(seg.header.p_vaddr, data)

在进行内存分配的时候,要注意对齐,按内存页的最小值倍数进行分配。

0x002 栈配置

接下来需要对栈开始配置,在开始配置栈之前,我们需要动态调试确定一下指定函数调用之前,栈里有什么东西。使用gdb对main函数进行调试,在main函数的开始处 0x80484c4设置断点并输入参数,启动GDB:

我们可以看出,main函数的栈结构是这样的:

地址 数值                  内容 备注
0xffffce8c    (esp)                               0xf7c29637  RET  
0xffffce90  (esp+4)                            9              argc  
0xffffce94                                          (esp+8) 0xffffcf24      argv 传入指针                      

我们继续查看0xffffcf24的内容:

1pwndbg> x/16sx 0xffffcf24
20xffffcf24:    0xffffd125  0xffffd137  0xffffd13a  0xffffd13d
30xffffcf34:    0xffffd140  0xffffd143  0xffffd146  0xffffd149
40xffffcf44:    0xffffd14c  0x00000000  0xffffd14f  0xffffd15a
50xffffcf54:    0xffffd16c  0xffffd19a  0xffffd1b0  0xffffd1bf

继续查看0xffffd125的内容,发现:

1pwndbg> x/16wx 0xffffd125
20xffffd125:    0x6d6f682f  0x696d2f65  0x2f62772f  0x45446277
30xffffd135:    0x32310053  0x00343300  0x37003635  0x62610038
40xffffd145:    0x00646300  0x31006665  0x44580066  0x54565f47
50xffffd155:    0x373d524e  0x47445800  0x5345535f  0x4e4f4953

如果不够直观,可以选择打印字符串:

1pwndbg> x/10s 0xffffd125
20xffffd125:    "/home/mi/wb/wbDES"
30xffffd137:    "12"
40xffffd13a:    "34"
50xffffd13d:    "56"
60xffffd140:    "78"
70xffffd143:    "ab"
80xffffd146:    "cd"
90xffffd149:    "ef"
100xffffd14c:    "1f"
110xffffd14f:    "XDG_VTNR=7"

到目前为止,我们可以确定栈空间是什么样子的。

首先,main函数有两个参数,一个是 argc9,另一个是一个指针,指向一个指针数组,指针数组的第一个指针指向的是字符串"/home/mi/wb/wbDES",第二个指向“12”,第三个指向“34”以此类推。接下来,根据分析所得的信息,开始进行栈空间参数的构建。

首先申请一段栈空间。

1STACK = 0xbfff0000
2STACK_SIZE = 0x10000
3mu.mem_map(STACK, STACK_SIZE)

栈的开始地址选择除了0x0附近之外的什么地方都可以,满足对齐即可,大小尽量大一些。

1SP = STACK +STACK_SIZE - 0x800

之后设置SP指针的位置,由于栈是向低地址增长的,所以我们有0x800大小的空间可以部署那些字符串参数。

1mu.reg_write(UC_X86_REG_ESP, SP)
2mu.reg_write(UC_X86_REG_EBP, SP)

设置ESP指针,有上图可知,EBP指针为0,表示函数中没有用到EBP寻址,为了安全起见设置程和ESP一样。

接下来开始布置值字符串,代码如下:

1start =0x100
2a = "./wbDES\x00"
3mu.mem_write(SP+start, a.encode())
4argv =[SP+start]
5start +=8
6for i in range(8):
7    argv.append(SP+start)
8    mu.mem_write(SP+start, b'ab')
9    start +=2
10    mu.mem_write(SP+start, bytes('\x00','utf-8'))
11    start +=1

之后开始布置指针数组:

1i =0
2for arg in argv:
3    mu.mem_write(SP+0x200+i*4, p32(arg))
4    i +=1       
5# NULL
6mu.mem_write(SP+0x200+i*4, p32(0))

最后开始布置函数参数那个区域的栈:

1RET = STACK
2mu.mem_write(SP+0x0, p32(RET)) # Return address @ sp
3mu.mem_write(SP+0x04, p32(len(argv))) # argc
4mu.mem_write(SP+0x08, p32(SP +0x200)) # argv

把返回地址写为栈顶的意义在于,在我们启动unicorn的时候,需要传入程序开始执行的位置和终止的位置,这样写实际上就是让函数返回到栈里,之后把栈顶的指针设置成结束位置,就不用去找函数终止的位置了。

0x003 Unicorn中调试

配置好了栈空间之后,我们还要看一下自己的配置对不对,和调试器中的值进行对比。可以使用:

1print(mu.mem_read(SP+0x100,64).hex())

打印出SP+0x100位置64个字节的值,与GDB进行对比。

现在我们来分别对比一下三个位置布置的对不对。

1print(mu.mem_read(SP+0x100,64).hex())#字符串
2print(mu.mem_read(SP+0x200,64).hex())#数组指针
3print(mu.mem_read(SP,64).hex()) #main参数

对应得到:

12e2f7762444553006162006162006162006162006162006162006162006162000000000000000000000000000000000000000000000000000000000000000000 #字符串
200f9ffbf08f9ffbf0bf9ffbf0ef9ffbf11f9ffbf14f9ffbf17f9ffbf1af9ffbf1df9ffbf00000000000000000000000000000000000000000000000000000000 #数组指针
30000ffbf0900000000faffbf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 #main参数

1pwndbg> x/80xb 0xffffce8c
20xffffce8c:    0x37    0x96    0xc2    0xf7    0x09    0x00    0x00    0x00
30xffffce94:    0x24    0xcf    0xff    0xff    0x4c    0xcf    0xff    0xff
40xffffce9c:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
50xffffcea4:    0x00    0x00    0x00    0x00    0x00    0x30    0xdc    0xf7
60xffffceac:    0x04    0xdc    0xff    0xf7    0x00    0xd0    0xff    0xf7
70xffffceb4:    0x00    0x00    0x00    0x00    0x00    0x30    0xdc    0xf7
80xffffcebc:    0x00    0x30    0xdc    0xf7    0x00    0x00    0x00    0x00
90xffffcec4:    0x67    0x1b    0x4d    0xc1    0x77    0xd5    0xfb    0xbb
100xffffcecc:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
110xffffced4:    0x00    0x00    0x00    0x00    0x09    0x00    0x00    0x00
12#0000ffbf0900000000faffbf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

1pwndbg> x/80xb 0xffffcf24
20xffffcf24:    0x25    0xd1    0xff    0xff    0x37    0xd1    0xff    0xff
30xffffcf2c:    0x3a    0xd1    0xff    0xff    0x3d    0xd1    0xff    0xff
40xffffcf34:    0x40    0xd1    0xff    0xff    0x43    0xd1    0xff    0xff
50xffffcf3c:    0x46    0xd1    0xff    0xff    0x49    0xd1    0xff    0xff
60xffffcf44:    0x4c    0xd1    0xff    0xff    0x00    0x00    0x00    0x00
70xffffcf4c:    0x4f    0xd1    0xff    0xff    0x5a    0xd1    0xff    0xff
80xffffcf54:    0x6c    0xd1    0xff    0xff    0x9a    0xd1    0xff    0xff
90xffffcf5c:    0xb0    0xd1    0xff    0xff    0xbf    0xd1    0xff    0xff
100xffffcf64:    0xfd    0xd1    0xff    0xff    0x2c    0xd2    0xff    0xff
110xffffcf6c:    0x4e    0xd2    0xff    0xff    0x5f    0xd2    0xff    0xff
12#00f9ffbf08f9ffbf0bf9ffbf0ef9ffbf11f9ffbf14f9ffbf17f9ffbf1af9ffbf1df9ffbf00000000000000000000000000000000000000000000000000000000

1pwndbg> x/80xb 0xffffd125
20xffffd125:    0x2f    0x68    0x6f    0x6d    0x65    0x2f    0x6d    0x69
30xffffd12d:    0x2f    0x77    0x62    0x2f    0x77    0x62    0x44    0x45
40xffffd135:    0x53    0x00    0x31    0x32    0x00    0x33    0x34    0x00
50xffffd13d:    0x35    0x36    0x00    0x37    0x38    0x00    0x61    0x62
60xffffd145:    0x00    0x63    0x64    0x00    0x65    0x66    0x00    0x31
70xffffd14d:    0x66    0x00    0x58    0x44    0x47    0x5f    0x56    0x54
80xffffd155:    0x4e    0x52    0x3d    0x37    0x00    0x58    0x44    0x47
90xffffd15d:    0x5f    0x53    0x45    0x53    0x53    0x49    0x4f    0x4e
100xffffd165:    0x5f    0x49    0x44    0x3d    0x63    0x32    0x00    0x58
110xffffd16d:    0x44    0x47    0x5f    0x47    0x52    0x45    0x45    0x54
12#2e2f7762444553006162006162006162006162006162006162006162006162000000000000000000000000000000000000000000000000000000000000000000

对比之后发现正确,如果不正确,需要更改代码进行微调。

接下来,我们需要设置一个调试hook,该hook函数的callback会在每句指令执行之前执行,便于我们发现问题。

1def hook_code(mu, address, size, user_data):  
2    print('>>> Tracing instruction at 0x%x, instruction size = 0x%x' %(address, size))

之后注册hook:

1mu.hook_add(UC_HOOK_CODE, hook_code)

开始执行:

1mu.emu_start(entry, RET)

很不幸,程序挂了,Unicorn给出里一个读取未分配空间的异常:

因为我们有调试,可以发现这个位置是在执行到0x80484e1的时候发生的,我们看下这个地址是什么指令。

发现是有关gs:0x14的操作,这个指令应该是栈cookie的操作,我们没分配的寄存器unicorn默认为0,所以我们需要在0x0空间给gs分配一个空间,这句话就可以跑过去了。

1mu.mem_map(0, 0x1000)

之后再运行:

发现程序好像跑飞了,往上翻,找到:

下断点调试,发现是跑到外部函数调用上去了:

分析之后发现,在main函数中有两处外部函数调用,我们直接patch掉他们的plt,让他们直接返回。

1mu.mem_write(0x80483BC, bytes('\xc3','utf-8'))
2mu.mem_write(0x80483EC, bytes('\xc3','utf-8'))

之后再运行就没有问题了。

我还针对栈的读取设置了hook,每次内存的写地址都会被记录,得到如下的图:

可以清楚的发现DES算法的轮结构,unicorn的调教到此完成,为下一步的研究做准备。

0x004 参考

[1]https://www.riscure.com/publication/unboxing-white-box/

[2]https://www.unicorn-engine.org/

[3]http://www.whiteboxcrypto.com/challenges.php

侧信道攻击,从喊666到入门之——Unicorn的环境构建的更多相关文章

  1. intel:spectre&Meltdown侧信道攻击(一)

    只要平时对安全领域感兴趣的读者肯定都听过spectre&Meltdown侧信道攻击,今天简单介绍一下这种攻击的原理( https://www.bilibili.com/video/av1814 ...

  2. intel:spectre&Meltdown侧信道攻击(三)—— raw hammer

    今天介绍raw hammer攻击的原理:这次有点“标题党”了.事实上,raw hammer是基于DRAM内存的攻击:所以理论上,只要是用了DRAM内存的设备,不论是什么cpu(intel.amd,或则 ...

  3. 第四十三个知识点:为AES描述一些基础的(可能无效)的对抗侧信道攻击的防御

    第四十三个知识点:为AES描述一些基础的(可能无效)的对抗侧信道攻击的防御 原文地址:http://bristolcrypto.blogspot.com/2015/07/52-things-numbe ...

  4. 第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法

    第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法 原文地址:http://bristolcrypto.blogspot.com/2015/08/52-things-number-45-de ...

  5. intel:spectre&Meltdown侧信道攻击(二)

    上面一篇介绍了spectre&meltdown基本原理和简单的demo方案,今天继续学习一下该漏洞发现团队原始的POC:https://spectreattack.com/spectre.pd ...

  6. intel:spectre&Meltdown侧信道攻击(四)—— cache mapping

    前面简单介绍了row hammer攻击的原理和方法,为了更好理解这种底层硬件类攻击,今天介绍一下cpu的cache mapping: 众所周知,cpu从内存读数据,最开始用的是虚拟地址,需要通过分页机 ...

  7. intel:spectre&Meltdown侧信道攻击(五)—— DRAM address mapping

    前面介绍了row hammer,理论上很完美,实际操作的时候会面临很尴尬的问题:内存存储数据最小的单位是cell(就是个电容,充电是1,放电是0),无数个横着的cell组成row,无数个竖着的cell ...

  8. 嵌入式 -- WINKHUB 边信道攻击 (NAND Glitch)

    0x00 前言 随着物联网IOT的飞速发展,各类嵌入式设备, 路由器安全研究也越来越火. 但因为跟以往纯软件安全研究的要求不同, 这类研究往往需要结合相应的硬件知识. 很多朋友困惑如何开始, 甚至卡在 ...

  9. ORW-测信道攻击

    做SCTF时碰到一个没看过的题型,比赛结束之后才知道是orw的一个玩法,测信道攻击.主要特点就是只给使用open,read,但是不给write,即无法把flag输出到终端.这里可以通过把flag读到栈 ...

随机推荐

  1. CSS颜色表示的几种方式

    在CSS中颜色有很多表示方式,今天列出一些常见的颜色表示方式及它们的用法. ①color:blue;  第一种,调用颜色属性,将颜色的英文输入在冒号后,以分号结束. 这种方法直接了当,但是能表示的颜色 ...

  2. ReLU函数

    Rectifier(neural networks) 在人工神经网络中,rectfier(整流器,校正器)是一个激活函数,它的定义是:参数中为正的部分. , 其中,x是神经元的输入.这也被称为ramp ...

  3. 小白学 Python 数据分析(3):Pandas (二)数据结构 Series

    在家为国家做贡献太无聊,不如跟我一起学点 Python 顺便问一下,你们都喜欢什么什么样的文章封面图,老用这一张感觉有点丑 人生苦短,我用 Python 前文传送门: 小白学 Python 数据分析( ...

  4. 真正解决百度编辑器UEditor上传图片跨域问题

    做前后端分离的项目用到UEditor,把上传图片程序拿出来放到了接口程序中,上传图片接口已经做了跨域处理,按理说编辑器中上传图片应该不会有问题.可是配置好图片上传路径后,运行,打开调试,测试一下,报错 ...

  5. Ops:jar包启动关闭脚本

    简介 公司开发架构为java语言的rpc dubbo架构,将功能分解为各个模块,模块较多,发布到环境上的应用为编译后的jar包和配置文件,以及启动关闭jar包的shell脚本.之前经常会出现进程启动不 ...

  6. Django ORM各种查询

    正向和反向查询 正向 ----> 关联字段在当前表中,从当前表向外查叫正向 反向 —> 关联字段不在当前表中,当当前表向外查叫反向 正向通过字段,反向通过表名查 表结构 from djan ...

  7. 给Hangfire的webjob增加callback和动态判断返回结果功能设计

    背景介绍 通常业务中需要用到定时执行功能,我用hangfire搭建了一个调度服务,这个调度服务是独立于业务逻辑的,具体可以参考文章:https://github.com/yuzd/Hangfire.H ...

  8. JVM解毒——JVM与Java体系结构

    你是否也遇到过这些问题? 运行线上系统突然卡死,系统无法访问,甚至直接OOM 想解决线上JVM GC问题,但却无从下手 新项目上线,对各种JVM参数设置一脸懵逼,直接默认,然后就JJ了 每次面试都要重 ...

  9. Elasticsearch原理学习--为什么Elasticsearch/Lucene检索可以比MySQL快?

    转载于:http://vlambda.com/wz_wvS2uI5VRn.html 同样都可以对数据构建索引并通过索引查询数据,为什么Lucene或基于Lucene的Elasticsearch会比关系 ...

  10. VFP调用API来控制USB摄像头,实现拍照或录像

    *--前提:VFP7.0以上;Windows 2K及以上*--控件:AVICAP32.DLL *--定义:一般放到主程序或表单(集)的Load事件中Public WM_CAP_DRIVER_DISCO ...