VEX IR学习
2023-02-Update:很早之前想要重新整理一下来着,一直鸽着。希望今年五月份可以完成orz
搞这个的原因是课题那边,把基本块转成VEX IR后需要进行一些modify。老师:你这周就先把VEX IR (和LLVM IR )看懂
所以这篇的要求也就是能看懂和在变量名之类的做一点优化(统一)就行了
代码1
从程序里转了一段中间代码:
1 | IRSB { |
angr输出的反编译代码:
1 | >>> irsb.pp() |
对应ida中选中的部分:
分析1
首先第一行表示函数的开始(IRSB在此处无意义)
IRSB:
后续的一堆tx:Ity blabla 代表临时变量,同时指明了变量类型
1 | quote:Temporary variables. VEX uses temporary variables as internal registers. |
IMark没有实际意义,仅作为一个statement,括号中的内容分别代表了在程序中的起始地址、指令长度和(不知道,不过只见过值=0)
对寄存器操作前需要读取该寄存器[GET]
操作结束后将值写回[PUT]
t0 = GET:I32(ebp):t0 gets ebp, which is a 32bit integer
t12 = Sub32(t13,0x00000004): t12 = t13 - 0x4
PUT(esp) = t12: esp = t12
[Update a register with the value of the given IR Expression.]
STle(t12) = t0: [t12] = t0
Update a location in memory, “le” in STle (and LDle) stands for “little-endian”
1 | 0~7行大概干了个 |
09行的命令很直白,但10~15指的是啥呢…似乎跟10~13出现的"cc_*"有挺大关系,但“cc_”只出现在arm架构的文档中:
1 | # NOTE 2: Something is goofy w/r/t archinfo and VEX; cc_op3 is used in ccalls, but there's |
17~22对应了:
1 | mov [ebp-4], 0 |
t15=t12+0xfffffffc,此时t12=ebp。看起来很奇怪,但其实把t15指向了ebp-4的位置,(不过为啥不用减法了?…)
24~26行实现了两条语句:
1 | 0x80491c4: lea eax, [0x804a021] |
24行:没有实际意义
26行:
此时t2=esp,把esp指向的值改为0x0804a021d
相当于mov [esp],[0x804a021]
所以其实是再进行了优化的(可能这就是RISC吧…)
28~34:
1 | 0x80491cd: lea eax, [ebp - 8] |
但并没有像上面一样绕过eax,直接对内存的进行修改,应该是因为针对一个基本块分析的时候要保存寄存器最后的值,不能再优化了
最后的跳转:
放一个官方对于跳转命令的整理
1 | typedef |
代码2
1 | IRSB { |
对应汇编代码
1 | 0x80491fd: cmp dword ptr [ebp - 8], 0xa |
suprise…汇编好简洁
03行:t2=[t4]
1 | LDle:The value stored at a memory address, with the address specified by another IR Expression. |
11行:
1 | Iop_1Uto32, /* :: Ity_Bit -> Ity_I32, unsigned widen */ |
看一下寄存器的定义,t14的大小也是一个bit
所以这个命令的作用就是把它扩展成32位的无符号数,放到t13里
13行:
1 | Iop_32to1, /* :: Ity_I32 -> Ity_Bit, just select bit[0] */ |
再取最低位放到t15里
讲真没看懂这波操作为啥要这么多次转换
分析3_来波大的
1 | >>> irsb.vex.pp() |
angr的汇编代码
1 | >>> irsb.pp() |
这段代码是
1 | int a[] = {1,2,3,4,5}; |
的循环部分
01~04:执行后
1 | t26=rbp-0x2c |
结合下一步的操作,不是很懂意义何在…
11行:出现了vex ir 指令,Sto,S指的是singed widen;类似的Uto中,U指的是unsigned widen
对于这行代码的理解可以参考汇编中的movsxd:
1 | MOVSXD r64, r/m32 Move doubleword to quadword with sign-extension. |
到57行前都是些重复的语句
57行:
mul64:官方文档中写道,mul代表的是signless的,相对的有以下乘法:
1 | /* -- Ordering not important after here. -- */ |
看到这行产生了个疑惑:imul是带符号的,但mul64是个无符号的乘法
再往下看看,发现,
1 | t16=Mul64(t78,t68) |
完成了以下三条汇编指令:
1 | 0x401189: imul rcx, rdx |
sar64:官方文档中没有描述作用,猜测和x64汇编相似,即算术右移(用符号位补)
cqo:将rax中的符号复制到rdx的每个位上
64~70行:
简单的一条
1 | idiv rcx |
被转换成了七行ir
1 | 64 | t84 = 64HLto128(t81,t16) |
涉及到了几个没有见过的VEX IR 指令:
64HLto128:两个64位的寄存器里的值组合扩展到128位的寄存器中
DivModS128to64::: V128,I64 -> V128,of which lo half is div and hi half is mod
128HIto64:128位数据中的高64位复制到64位寄存器中
82~85行,出现了“cc_*”的“寄存器”,但似乎对理解这个基本块的VEX IR完全没有影响…(不会是因为代码片段还不够长吧orz)
第二段
1 | >>> irsb.vex.pp() |
第一部分13行:cmpLT32S
官方文档中的解释很短:
1 | /* Standard integer comparisons */ |
该结果为在x86汇编中为指令jge服务(大于等于则跳转)
所以小于3时t8=t20=t16=1
因此cmpLT应该是“前小于后返回1”
所以"CmpL*"是只能比较小于或等于?
注:试了一下还真是这样的,形成的中间代码会把其他判断方式转换成"<“/”="
注2:在微软的文档搜到了SSE2指令集,里边的比较指令和 vex ir 长得还蛮像的↓
来个aarch64的
用aarch64-linux-gnu-gcc编译了相同的代码:
1 |
|
用angr加载的时候会产生警告,但(对于这个样例)没有影响
注:上一个应该是因为找不到动态链接库,编译选项设置静态编译后可以正常加载
发现vex ir用到的寄存器是根据程序cpu架构改变的。如i386、amd64下用到PUT(rip)、PUT(eip),在aarch64下则是PUT(pc)
选取下面这句代码的中间代码:
1 | *(_DWORD *)(qword_4999C8 + 4) = aa; |
aarch64
1 | 00 | ------ IMark(0x400778, 4, 0) ------ |
amd64
1 | 00 | ------ IMark(0x40126d, 7, 0) ------ |