ESIL
最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
ESIL代表'可求值字符串形中间语言',旨在为各种目标CPU opcode提供一个-like的语义描述。可以对ESIL表示进行求值(运行),用于模拟一些独立的指令。每个ESIL表示都通过逗号进行分隔,大致可用如下的形式描述ESIL虚拟机:
同计算器中类似,ESIL使用一个基于栈的解释器。其接受两类参数:数值和操作符,数值被压栈,操作符则将栈顶值弹出(或者说该操作符的参数),操作完成后将结果(如果有的话)压栈。可以将ESIL当作指令的一种逆波兰表示(post-fix表示)。
让我们看看一个例子:
可以猜猜这条语句在做什么,如果将这个逆波兰表示的语句转换为正常的指令,可以得到:
我们可以看到这正是x86指令push ebp
的另一种形式!是不是很cool?ESIL的目标是能够对CPU的常见操作进行解释,比如二进制算数操作、内存加载和存储、系统调用等。通过这种方法,如果能够将指令转化为ESIL,即使面对一个加密架构且我们没有该架构对应的debug,我们也能在观察到该程序在运行时都做了什么。
r2的可视化模式对于观察ESIL执行非常有帮助。
底下是些很重要的环境变量,可用于观察程序行为:
asm.emu
告诉r2是否将ESIL的信息展示出来。如果设置为true
,反汇编的注释区域中将显示经过该指令后寄存器和内存地址会如何变化。例如,假设这里有一条指令减去了寄存器中的值,注释中会告诉你原先的值是多少,最终结果又是多少。这真的特别有用,使得你无需坐在位置上手动追踪数值的变化。
但是它存在的一个问题是一次显示的信息太多了,而我们有时候不需要这么多信息。针对这个问题,r2有一个很好的折中方案,就是使用emu.str
变量(在<=2.2版本是asm.emustr
)。不输出每个寄存器的值,取而代之的是一些真正有用的信息。例如,显示程序中引用地址上的字符串,或者判断哪个跳转分支比较可能发生。
第三个重要的变量就是asm.esil
,这个开关控制反汇编不再显示真实的反汇编指令,而是显示它们的ESIL表示,描述该指令正进行什么操作。 因此如果你想要看看指令是如何以ESIL表示的,只需将asm.esil
设置为true即可。
在可视化模式下可以用0
键切换至ESIL表示。
"ae" : 对ESIL进行求值。
"aes" : ESIL单步.
"aeso" : ESIL步过.
"aesu" : ESIL单步直到某个地址.
"ar" : 显示/修改ESIL寄存器.
这里是ESIL VM使用的完整指令集:
ESIL Opcode
Operands
Name
Operation
example
TRAP
src
Trap
Trap signal
$
src
Syscall
syscall
$$
src
Instruction address
Get address of current instruction stack=instruction address
==
src,dst
Compare
stack = (dst == src) ; update_eflags(dst - src)
<
src,dst
Smaller (signed comparison)
stack = (dst < src) ; update_eflags(dst - src)
[0x0000000]> "ae 1,5,<" 0x0 > "ae 5,5" 0x0"
<=
src,dst
Smaller or Equal (signed comparison)
stack = (dst <= src) ; update_eflags(dst - src)
[0x0000000]> "ae 1,5,<" 0x0 > "ae 5,5" 0x1"
>
src,dst
Bigger (signed comparison)
stack = (dst > src) ; update_eflags(dst - src)
> "ae 1,5,>" 0x1 > "ae 5,5,>" 0x0
>=
src,dst
Bigger or Equal (signed comparison)
stack = (dst >= src) ; update_eflags(dst - src)
> "ae 1,5,>=" 0x1 > "ae 5,5,>=" 0x1
<<
src,dst
Shift Left
stack = dst << src
> "ae 1,1,<<" 0x2 > "ae 2,1,<<" 0x4
>>
src,dst
Shift Right
stack = dst >> src
> "ae 1,4,>>" 0x2 > "ae 2,4,>>" 0x1
<<<
src,dst
Rotate Left
stack=dst ROL src
> "ae 31,1,<<<" 0x80000000 > "ae 32,1,<<<" 0x1
>>>
src,dst
Rotate Right
stack=dst ROR src
> "ae 1,1,>>>" 0x80000000 > "ae 32,1,>>>" 0x1
&
src,dst
AND
stack = dst & src
> "ae 1,1,&" 0x1 > "ae 1,0,&" 0x0 > "ae 0,1,&" 0x0 > "ae 0,0,&" 0x0
|
src,dst
OR
stack = dst | src
> "ae 1,1,|" 0x1 > "ae 1,0,|" 0x1 > "ae 0,1,|" 0x1 > "ae 0,0,|" 0x0
^
src,dst
XOR
stack = dst ^src
> "ae 1,1,^" 0x0 > "ae 1,0,^" 0x1 > "ae 0,1,^" 0x1 > "ae 0,0,^" 0x0
+
src,dst
ADD
stack = dst + src
> "ae 3,4,+" 0x7 > "ae 5,5,+" 0xa
-
src,dst
SUB
stack = dst - src
> "ae 3,4,-" 0x1 > "ae 5,5,-" 0x0 > "ae 4,3,-" 0xffffffffffffffff
*
src,dst
MUL
stack = dst * src
> "ae 3,4,*" 0xc > "ae 5,5,*" 0x19
/
src,dst
DIV
stack = dst / src
> "ae 2,4,/" 0x2 > "ae 5,5,/" 0x1 > "ae 5,9,/" 0x1
%
src,dst
MOD
stack = dst % src
> "ae 2,4,%" 0x0 > "ae 5,5,%" 0x0 > "ae 5,9,%" 0x4
!
src
NEG
stack = !!!src
> "ae 1,!" 0x0 > "ae 4,!" 0x0 > "ae 0,!" 0x1
++
src
INC
stack = src++
> ar r_00=0;ar r_00 0x00000000 > "ae r_00,++" 0x1 > ar r_00 0x00000000 > "ae 1,++" 0x2
--
src
DEC
stack = src--
> ar r_00=5;ar r_00 0x00000005 > "ae r_00,--" 0x4 > ar r_00 0x00000005 > "ae 5,--" 0x4
=
src,reg
EQU
reg = src
> "ae 3,r_00,=" > aer r_00 0x00000003 > "ae r_00,r_01,=" > aer r_01 0x00000003
+=
src,reg
ADD eq
reg = reg + src
> ar r_01=5;ar r_00=0;ar r_00 0x00000000 > "ae r_01,r_00,+=" > ar r_00 0x00000005 > "ae 5,r_00,+=" > ar r_00 0x0000000a
-=
src,reg
SUB eq
reg = reg - src
> "ae r_01,r_00,-=" > ar r_00 0x00000004 > "ae 3,r_00,-=" > ar r_00 0x00000001
*=
src,reg
MUL eq
reg = reg * src
> ar r_01=3;ar r_00=5;ar r_00 0x00000005 > "ae r_01,r_00,*=" > ar r_00 0x0000000f > "ae 2,r_00,*=" > ar r_00 0x0000001e
/=
src,reg
DIV eq
reg = reg / src
> ar r_01=3;ar r_00=6;ar r_00 0x00000006 > "ae r_01,r_00,/=" > ar r_00 0x00000002 > "ae 1,r_00,/=" > ar r_00 0x00000002
%=
src,reg
MOD eq
reg = reg % src
> ar r_01=3;ar r_00=7;ar r_00 0x00000007 > "ae r_01,r_00,%=" > ar r_00 0x00000001 > ar r_00=9;ar r_00 0x00000009 > "ae 5,r_00,%=" > ar r_00 0x00000004
<<=
src,reg
Shift Left eq
reg = reg << src
> ar r_00=1;ar r_01=1;ar r_01 0x00000001 > "ae r_00,r_01,<<=" > ar r_01 0x00000002 > "ae 2,r_01,<<=" > ar r_01 0x00000008
>>=
src,reg
Shift Right eq
reg = reg << src
> ar r_00=1;ar r_01=8;ar r_01 0x00000008 > "ae r_00,r_01,>>=" > ar r_01 0x00000004 > "ae 2,r_01,>>=" > ar r_01 0x00000001
&=
src,reg
AND eq
reg = reg & src
> ar r_00=2;ar r_01=6;ar r_01 0x00000006 > "ae r_00,r_01,&=" > ar r_01 0x00000002 > "ae 2,r_01,&=" > ar r_01 0x00000002 > "ae 1,r_01,&=" > ar r_01 0x00000000
|=
src,reg
OR eq
reg = reg | src
> ar r_00=2;ar r_01=1;ar r_01 0x00000001 > "ae r_00,r_01,|=" > ar r_01 0x00000003 > "ae 4,r_01,|=" > ar r_01 0x00000007
^=
src,reg
XOR eq
reg = reg ^ src
> ar r_00=2;ar r_01=0xab;ar r_01 0x000000ab > "ae r_00,r_01,^=" > ar r_01 0x000000a9 > "ae 2,r_01,^=" > ar r_01 0x000000ab
++=
reg
INC eq
reg = reg + 1
> ar r_00=4;ar r_00 0x00000004 > "ae r_00,++=" > ar r_00 0x00000005
--=
reg
DEC eq
reg = reg - 1
> ar r_00=4;ar r_00 0x00000004 > "ae r_00,--=" > ar r_00 0x00000003
!=
reg
NOT eq
reg = !reg
> ar r_00=4;ar r_00 0x00000004 > "ae r_00,!=" > ar r_00 0x00000000 > "ae r_00,!=" > ar r_00 0x00000001
---
---
---
---
----------------------------------------------
=[] =[*] =[1] =[2] =[4] =[8]
src,dst
poke
*dst=src
> "ae 0xdeadbeef,0x10000,=[4]," > pxw 4@0x10000 0x00010000 0xdeadbeef .... > "ae 0x0,0x10000,=[4]," > pxw 4@0x10000 0x00010000 0x00000000
[] [*] [1] [2] [4] [8]
src
peek
stack=*src
> w test@0x10000 > "ae 0x10000,[4]," 0x74736574 > ar r_00=0x10000 > "ae r_00,[4]," 0x74736574
|=[] |=[1] |=[2] |=[4] |=[8]
reg
nombre
code
> >
SWAP
Swap
Swap two top elements
SWAP
PICK
n
Pick
Pick nth element from the top of the stack
2,PICK
RPICK
m
Reverse Pick
Pick nth element from the base of the stack
0,RPICK
DUP
Duplicate
Duplicate top element in stack
DUP
NUM
Numeric
If top element is a reference (register name, label, etc), dereference it and push its real value
NUM
CLEAR
Clear
Clear stack
CLEAR
BREAK
Break
Stops ESIL emulation
BREAK
GOTO
n
Goto
Jumps to Nth ESIL word
GOTO 5
TODO
To Do
Stops execution (reason: ESIL expression not completed)
TODO
ESIL VM有一个内置的状态标志,其是只读的,可用于将这些标志导入到基础的目标CPU标志位上。这是由于ESIL VM在每次操作后都计算所有标志位的改变,而目标CPU只在特定情况或特定语句下更新标志位。
内置标志位以$
字符为前缀。
目标opcode转化为以逗号分隔的ESIL表示:
内存访问是通过[]
操作符定义的:
默认的操作数大小是根据目的操作数大小决定的:
?
操作符将根据其参数的值决定是否执行{}
中的表达式
如果为0 -> 跳过
若不为0 -> 执行
如果想在某个条件下执行多个表达式,需将表达式放入{}
中:
空格,换行以及其他字符将被忽略,因为在执行一个ESIL的第一个步骤就是移除空字符。
系统调用需要特殊处理,其通过表达式开头的$
进行标识,可以选择传给它一个数值,用于指定系统调用。一个ESIL模拟器必须能够处理系统调用,参阅(r_esil_syscall)
如IRC上所讨论,目前的ESIL实现是类似下面这样的:
这种实现方式可读性更好,但对于栈不够友好。
NOP以空字符串进行表示,而如前文所述,系统调用则通过$
命令标记。例如,0x80, $
。其将ESIL机器中的模拟请求转发给一个回调函数,该回调函数为特定的OS/kernel实现了系统调用。
TRAP
命令实现了陷阱指令,用于为非法指令、除0、内存读取错误或特定架构中所规定的情形抛出异常。
以下是一个快查表,用于从ESIL字符串中检索信息。相关信息可能会在列表的第一个表达式中找到。
Common operations:
Check dstreg
Check srcreg
Get destinaion
Is jump
Is conditional
Evaluate
Is syscall
CPU标志通常在RReg配置文件中定义为单个的位寄存器,它们有时能在'flg'寄存器类型下找到
VM变量相关内容:
他们没有预定义的宽度,因此在之后可以很容易地扩展至128,256和512位,例如在MMX,SSE,AVX,Neon SIMD上。
变量数目无限制,这是为了SSA-格式的兼容性。
寄存器名字没有特殊语法,只是字符串而已。
数值可以以RNum所支持的任意基数进行表示(dec,hex,oct,binary ...)。
每个ESIL的后端都需要一个相关联的RReg配置,描述ESIL上寄存器应遵循的规范
What to do with them? What about bit arithmetics if use variables instead of registers?
ADD ("+")
MUL ("*")
SUB ("-")
DIV ("/")
MOD ("%")
AND "&"
OR "|"
XOR "^"
SHL "<<"
SHR ">>"
ROL "<<<"
ROR ">>>"
NEG "!"
在撰写本文时,ESIL尚不支持FPU。但是您可以使用r2pipe实现对不受支持的指令的支持。 Eventually we will get proper support for multimedia and floating point.
ESIL指定解析控制流的命令必须为大写。请记住,某些体系结构具有大写的寄存器名称。相应的寄存器配置文件中应注意不要重复使用以下任何内容:
rep cmpsb
这些用“ TODO”命令表示。充当"BREAK"的作用,但会显示警告消息,说明一条指令未实现且将不会被模拟。例如:
为减轻ESIL解析的压力,我们需要有一个方法,能执行自省表达式以提取我们所需的数据。例如我们想获取jump的目的地址,ESIL表达式的解析器应提供一个API以完成这个工作,使得分析表达式提取信息变得更容易。
我们需要有一个方式能获取'rip'的数值,这个例子很简单。但这里还有一个更复杂的例子,类似条件分支。我们需要能从表达式中获取:
opcode type
destination of a jump
condition depends on
all regs modified (write)
all regs accessed (read)
对于模拟执行来说能在解析器中设置hook是很重要的,如此一来我们可通过扩展该程序实现分析,而不必一次又一次地修改它。也就是说,每次要执行指令操作时,都会调用一个用户挂钩。例如,它可以用于确定'RIP'是否要更改,或者指令是否会更新堆栈。 之后,我们可以将该回调函数一分为多,以拥有event-based的分析API,比如在javascript中:
若要了解API,参见hook_flag_read()
, hook_execute()
和hook_mem_read()
。 For the API, see the functions hook_flag_read()
, hook_execute()
and hook_mem_read()
. 若想覆盖操作,则相应的回调函数应该返回true或1。例如,拒绝对某块内存区域的读取,或者避免内存写入,从而使其变为只读。 若想跟踪ESIL表示的解析过程,则回调函数返回false或0即可。
其他需要绑定到外部功能才能起作用的操作,在本例中就是r_ref
和r_io
,必须在ESIL VM初始化时定义它们。
Io Get/Set
Selectors (cs,ds,gs...)