侧信道攻击,从喊666到入门之——Unicorn的环境构建
作者: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函数有两个参数,一个是 argc
为9
,另一个是一个指针,指向一个指针数组,指针数组的第一个指针指向的是字符串"/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的环境构建的更多相关文章
- intel:spectre&Meltdown侧信道攻击(一)
只要平时对安全领域感兴趣的读者肯定都听过spectre&Meltdown侧信道攻击,今天简单介绍一下这种攻击的原理( https://www.bilibili.com/video/av1814 ...
- intel:spectre&Meltdown侧信道攻击(三)—— raw hammer
今天介绍raw hammer攻击的原理:这次有点“标题党”了.事实上,raw hammer是基于DRAM内存的攻击:所以理论上,只要是用了DRAM内存的设备,不论是什么cpu(intel.amd,或则 ...
- 第四十三个知识点:为AES描述一些基础的(可能无效)的对抗侧信道攻击的防御
第四十三个知识点:为AES描述一些基础的(可能无效)的对抗侧信道攻击的防御 原文地址:http://bristolcrypto.blogspot.com/2015/07/52-things-numbe ...
- 第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法
第四十五个知识点:描述一些对抗RSA侧信道攻击的基础防御方法 原文地址:http://bristolcrypto.blogspot.com/2015/08/52-things-number-45-de ...
- intel:spectre&Meltdown侧信道攻击(二)
上面一篇介绍了spectre&meltdown基本原理和简单的demo方案,今天继续学习一下该漏洞发现团队原始的POC:https://spectreattack.com/spectre.pd ...
- intel:spectre&Meltdown侧信道攻击(四)—— cache mapping
前面简单介绍了row hammer攻击的原理和方法,为了更好理解这种底层硬件类攻击,今天介绍一下cpu的cache mapping: 众所周知,cpu从内存读数据,最开始用的是虚拟地址,需要通过分页机 ...
- intel:spectre&Meltdown侧信道攻击(五)—— DRAM address mapping
前面介绍了row hammer,理论上很完美,实际操作的时候会面临很尴尬的问题:内存存储数据最小的单位是cell(就是个电容,充电是1,放电是0),无数个横着的cell组成row,无数个竖着的cell ...
- 嵌入式 -- WINKHUB 边信道攻击 (NAND Glitch)
0x00 前言 随着物联网IOT的飞速发展,各类嵌入式设备, 路由器安全研究也越来越火. 但因为跟以往纯软件安全研究的要求不同, 这类研究往往需要结合相应的硬件知识. 很多朋友困惑如何开始, 甚至卡在 ...
- ORW-测信道攻击
做SCTF时碰到一个没看过的题型,比赛结束之后才知道是orw的一个玩法,测信道攻击.主要特点就是只给使用open,read,但是不给write,即无法把flag输出到终端.这里可以通过把flag读到栈 ...
随机推荐
- Kafka常用topic操作命令汇总
offset topic consumer-group consumer producer producer-golang topic 工具 https://cwiki.apache.org/conf ...
- codeforces 1020 C Elections(枚举+贪心)
题意: 有 n个人,m个党派,第i个人开始想把票投给党派pi,而如果想让他改变他的想法需要花费ci元.你现在是党派1,问你最少花多少钱使得你的党派得票数大于其它任意党派. n,m<3000 思路 ...
- 【C++】随机数引擎
rand() 基本:使用随机数时,经常见到的是C标准库提供的函数rand(),这个函数会生成一个0到RAND_MAX之间的一个整形数: 分布:为了得到一个给定范围内的随机数,通常会对生成的随机数取余: ...
- 导出Chrome浏览器中保存的密码
title: 导出Chrome浏览器中保存的密码 date: 2018-02-11 17:54:51 tags: --- 导出Chrome浏览器中保存的密码 先知看到的,挺有意思,记录一下 不同浏览器 ...
- web渗透步骤流程
2013-11-13 23:03 (分类:网络安全) 这篇流程写的非常细,思路上很完整很全面,非常值得参考,做渗透思路要非常清晰,要不然我感觉真的容易乱,或者漏掉一些可能存在的点. 1.渗透目标 渗透 ...
- 一条Sql的Spark之旅
背景 SQL作为一门标准的.通用的.简单的DSL,在大数据分析中有着越来越重要的地位;Spark在批处理引擎领域当前也是处于绝对的地位,而Spark2.0中的SparkSQL也支持ANSI-SQL ...
- telnet命令连接服务器端
win7: (1).使用telnet命令: 控制面板--->程序--->打开或关闭Windows功能--->Telnet客户端[勾选] (2)连接(cmd) eg:telnet ww ...
- emmet笔记
1.div.div${div$}*6 生成 <div class="div1">div1</div> <div class="div2&qu ...
- Android中通过数组资源文件xml与适配器两种方式给ListView列表视图设置数据源
场景 实现效果如下 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 将布局改 ...
- xPath和html基础扫盲
xPath:一种HTML和XML的查询语言,他能在XML和HTML的树状结构中寻找节点 安装xPath: pip方法: pip install lxml win+R:cmd 打开命令控制台: 此 ...