作者: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. mplayer的参数

    播放文件 使用 MPlayer 播放媒体文件最简单的方式是: mplayer <somefile>  MPlayer 会自动检测文件的类型并加以播放,如果是音频文件,则会在命令行中显示该播 ...

  2. js笔记(1)--第一天记录

    刚刚接触JavaScript这门语言不久,所以希望每一次都记一点东西下来,巩固下知识. 首先,写了一个demo,是来计算两个数字的和的,如果直接把 v1=text1.value,和 v2=text2. ...

  3. 洛谷P1649 【[USACO07OCT]障碍路线Obstacle Course】

    题目描述 Consider an N x N (1 <= N <= 100) square field composed of 1 by 1 tiles. Some of these ti ...

  4. 【5min+】 巨大的争议?C# 8 中的接口

    系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...

  5. Apache-Tomcat-Ajp漏洞(CVE-2020-1938)漏洞复现

    前言 Apache Tomcat会开启AJP连接器,方便与其他Web服务器通过AJP协议进行交互.由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器.此漏洞为文件包含漏洞,攻 ...

  6. 【学习底层原理系列】重读spring源码1-建立基本的认知模型

    开篇闲扯 在工作中,相信很多人都有这种体会,与其修改别人代码,宁愿自己重写. 为什么? 先说为什么愿意自己写: 从0-1的过程,是建立在自己已有认知基础上,去用自己熟悉的方式构建一件作品.也就是说, ...

  7. pytoch之 encoder,decoder

    import torch import torch.nn as nn import torch.utils.data as Data import torchvision import matplot ...

  8. Linux 邮件服务

    三个要点 1.smtp协议 2.搭建本地邮件服务器 3.使用外部邮件服务器 实现邮件功能 1.smtp协议           SMTP(Simple Mail Transfer Protocol)即 ...

  9. HTTP 1.1状态代码及其含义

    HTTP 1.1状态代码及其含义 100  Continue  初始的请求已经接受,客户应当继续发送请求的其余部分.(HTTP 1.1新) 101  Switching Protocols  服务器将 ...

  10. Mysql 保存emoji表情报错

    保存emoji表情错误 首先错误表现,抛出业务层报错之外,根源的数据库错误是: sql 错误码 1366 字符集相关错误. uncategorized SQLException; SQL state ...