最近在学习angr,里面有用到vex ir,之前对vex ir不了解,打算系统学习一遍。
本片文章主要参考angr官方文档:
简介:
1.code blocks:
代码被分割成很多小的代码片段(类型为'IRSB'),每个块表示1~50条指令。IRSB块是单入口,多出口。每个IRSB包含以下三种数据:
2.statements and expression
Statements(‘IRStmt'):代表有着副作用的操作,eg. 寄存器写,存储和分配给临时变量。
Expressiongs('IRExpr') :代表着没有副作用的操作,eg,算术运算,加载,常量
3.storage of guest state
“guest state”包含guest machine(即我们正在模拟的机器)的guest寄存器。默认情况下,它存储在VEX库用户提供的内存块中,通常称guest state(area)。要对这些寄存器进行操作,必须首先将它们从guest state读取(“Get”)为一个临时值。之后,可以将它们写回("put") guest state。
Get和Put的特征是进入guest状态的字节偏移量、一个有效给出被引用guest寄存器标识的小整数和一个指示要传输的值大小的类型。
基本的“Get”和“Put”操作足以对guest 上的正常固定寄存器建模。guest stated 的选定区域可以视为寄存器的循环数组(类型:“IRRegArray”),可以在运行时对其进行索引。这是用“GetI”和“PutI”原语完成的。这是描述旋转寄存器文件所必需的,例如x87 FPU堆栈、SPARC寄存器窗口和Itanium寄存器文件。
4.例子:考虑如下x86指令
可能的IR代码如下
------ IMark(0x24F275, 7, 0) ------ t3 = GET:I32(0) # get %eax, a 32-bit integer t2 = GET:I32(12) # get %ebx, a 32-bit integer t1 = Add32(t3,t2) # addl PUT(0) = t1 # put %eax
IMark只是IR声明,不代表实际的指令,指明了代码的地址0x24f275,原始指令的长度7,数字0和12偏移到%eax和%ebx的guest state。(这里的0代表%eax吧)
本例中的五个 statements 是:
addl %edx,4(%eax) ------ IMark(0x4000ABA, 3, 0) ------ t3 = Add32(GET:I32(0),0x4:I32) //0表示%eax寄存器 t2 = LDle:I32(t3) t1 = GET:I32(8) //8表示%edx寄存器 t0 = Add32(t2,t1) STle(t3) = t0 “LDle"和"STle"中的'le'是'little-endian'的缩写
typedef enum { Ity_INVALID=0x1100, Ity_I1, Ity_I8, Ity_I16, Ity_I32, Ity_I64, Ity_I128, /* 128-bit scalar */ Ity_F16, /* 16 bit float */ Ity_F32, /* IEEE 754 float */ Ity_F64, /* IEEE 754 double */ Ity_D32, /* 32-bit Decimal floating point */ Ity_D64, /* 64-bit Decimal floating point */ Ity_D128, /* 128-bit Decimal floating point */ Ity_F128, /* 128-bit floating point; implementation defined */ Ity_V128, /* 128-bit SIMD */ Ity_V256 /* 256-bit SIMD */ } IRType; /* 打印 IRType */ extern void ppIRType ( IRType ); /*获取 IRType 字节大小 */ extern Int sizeofIRType ( IRType ); /* 将 1/2/4/8 转换成Ity_I{8,16,32,64} */ extern IRType integerIRTypeOfSize ( Int szB ); IREndness用于加载IRExprs和存储IRStmts。 typedef enum { Iend_LE=0x1200, /* little endian */ Iend_BE /* big endian */ } IREndness;
常量
IRConsts在“ Const”和“ Exit” IRExprs中使用。 typedef enum { Ico_U1=0x1300, Ico_U8, Ico_U16, Ico_U32, Ico_U64, Ico_F32, /* 32-bit IEEE754 floating */ Ico_F32i, /* 32-bit unsigned int to be interpreted literally as a IEEE754 single value. */ Ico_F64, /* 64-bit IEEE754 floating */ Ico_F64i, /* 64-bit unsigned int to be interpreted literally as a IEEE754 double value. */ Ico_V128, /* 128-bit restricted vector constant, with 1 bit (repeated 8 times) for each of the 16 x 1-byte lanes */ Ico_V256 /* 256-bit restricted vector constant, with 1 bit (repeated 8 times) for each of the 32 x 1-byte lanes */ } IRConstTag;
一个常量,存储为带标记的联合体,tag 表示这是一个什么样的常数Ico是一个union,它控制着各个领域。如果 IRConst'c' 的c.tag等于Ico_U32,则它是32位常量,其值可以使用'c.Ico.U32'访问
7.call targets
描述要调用的帮助函数。名字部分纯粹是为了漂亮的打印而不是实际使用。regparms=n告诉后端被调用方已被声明为“__attribute__(regparm(n))”,尽管间接使用VEX_REGPARM(n)宏。在某些目标(x86)上,后端需要构造一个非标准序列来调用这样声明的函数。
mcx_mask 是一种记忆检查的标准。它指出在计算结果的定义时,哪些参数应该被视为“总是定义的”。mcx_mask的位0对应于args[0],位1对应于args[1]等。如果设置了位,则从定义性检查中排除相应的arg(因此“mcx”中的“x”)。
typedef struct { Int regparms; const HChar* name; void* addr; UInt mcx_mask; } IRCallee; /* Create an IRCallee. */ extern IRCallee* mkIRCallee ( Int regparms, const HChar* name, void* addr ); /* Deep-copy an IRCallee. */ extern IRCallee* deepCopyIRCallee ( const IRCallee* ); /* Pretty-print an IRCallee. */ extern void ppIRCallee ( const IRCallee* );
typedef struct _IRQop IRQop; /* forward declaration */ typedef struct _IRTriop IRTriop; /* forward declaration */ 对于每种类型的表达式,可以使用ppIrExpr()来显示 不同种类的表达式: typedef enum { Iex_Binder=0x1900, Iex_Get, Iex_GetI, Iex_RdTmp, Iex_Qop, Iex_Triop, Iex_Binop, Iex_Unop, Iex_Load, Iex_Const, Iex_ITE, Iex_CCall, Iex_VECRET, Iex_BBPTR } IRExprTag;
它们的含义在下面的IRExpr注释中解释。存储为标记的联合。 “标签”指示这是一种表达。 “ Iex”是持有字段的联合。 如果IRExpr'e'具有等于Iex_Load的e.tag,则它是一个负载表达式,并且可以使用'e.Iex.Load。<fieldname>'访问这些字段。
struct _IRExpr { IRExprTag tag; union { /* 仅用于Vex内的模式匹配。不应出现在vex之外 */ struct { Int binder; } Binder; /*在guest state的固定偏移量处读取来宾寄存器。ppIRExpr输出:GET:<ty>(<offset>),例如GET:I32(0)*/ struct { Int offset; /*guest state的偏移 */ IRType ty; /* 要读取值的类型 */ } Get; /* 在guest state下以非固定偏移量读取来guest存器。这允许循环索引到guest state的一部分,这对于建模guest存器的身份直到运行时才知道的情况至关重要。一个例子是x87 FP寄存器堆栈。 要作为循环数组处理的guest state的一部分在IRRegArray'descr'字段中描述。它保存数组中第一个元素的偏移量、每个元素的类型和元素的数量。 数组索引是相当间接地表示的,以一种使优化变得容易的方式:作为可变部分(“ix”字段)和恒定偏移量(“bias”字段)的总和。 由于索引是循环的,实际使用的数组索引计算为(ix+bias)%数组中的元素数。 举个列子如下: (96:8xF64)[t39,-7] 描述一个由8个F64类型值组成的数组,第一个值的guest state偏移量为96。此数组的索引位置是(t39-7)%8。 正确地获得数组大小/类型是很重要的,因为IR优化会密切关注这些信息,以便在单独的GetI和PutI事件之间建立混叠/非混叠,后者用于确定何时可以重新排序等。输入不正确的信息将导致模糊的IR优化错误。*/ ppIRExpr output: GETI<descr>[<ix>,<bias] eg. GETI(128:8xI8)[t1,0] struct { IRRegArray* descr; /* 被视为循环的guest state的一部分*/ IRExpr* ix; /* 索引到数组的可变部分 */ Int bias; /* 索引到数组的固定部分*/ } GetI; /*临时变量的存储 ppIRExpr output: t<tmp>, eg. t1*/ struct{ ITRemp tmp; /*临时变量号*/ }RdTmp; /*四元运算。*/ ppIRExpr output: <op>(<arg1>, <arg2>, <arg3>, <arg4>), eg. MAddF64r32(t1, t2, t3, t4) struct { IRQop* details; } Qop; /*三元运算。 ppIRExpr output: <op>(<arg1>, <arg2>, <arg3>), eg. MulF64(1, 2.0, 3.0) */ struct { IRTriop* details; } Triop; /*二元运算。 ppIRExpr output: <op>(<arg1>, <arg2>), eg. Add32(t1,t2) */ struct { IROp op; /* op-code */ IRExpr* arg1; /* operand 1 */ IRExpr* arg2; /* operand 2 */ } Binop; /*一元运算:ppIRExpr output: <op>(<arg>), eg. Neg8(t1)*/ struct { IROp op; /* op-code */ IRExpr* arg; /* operand */ } Unop; /*加载内存数据——一个普通的加载,而不是一个链接的加载。加载链接(和存储条件)由IRStmt.LLSC表示,因为加载链接有副作用,因此在语义上不是有效的IRExpr。 ppIRExpr output: LD<end>:<ty>(<addr>), eg. LDle:I32(t1)*/ struct { IREndness end; /* Endian-ness of the load */ IRType ty; /* Type of the loaded value */ IRExpr* addr; /* Address being loaded from */ } Load; /*常量表达式 ppIRExpr output: <con>, eg. 0x4:I32*/ struct { IRConst* con; /* The constant itself */ } Const; /*函数调用 ‘cee'中的'name’是函数名字,主要是为了更好的打印效果,'cee'中的'addr'保存函数地址。 ‘args' 是以NULL作为终结符的数字。IRType,表示参数类型。必须和函数调用相匹配。 函数调用必须满足以下几个条件 -没有副作用,函数结果只取决于传递过去的参数 -它可能不会查看或修改任何guest state,因为这样会隐藏插入器的guest state转换 -它可能无法访问guest内存,因为这会隐藏来自检测程序的guest内存事务 -它不能假定参数是按特定顺序计算的。未指定求值顺序。 原则上,允许arg向量包含一个IRExpr_VECRET(),尽管不是IRExpr_BBPTR()。但是,目前没有要求clean helper调用能够返回V128或V256值。因此这是不允许的。 ppIRExpr output: <cee>(<args>):<retty> eg. foo{0x80489304}(t1, t2):I32*/ struct { IRCallee* cee; /* 调用的函数. */ IRType retty; /* 返回类型. */ IRExpr** args; /* 表达式向量. */ } CCall; /*三元if-then-else运算符。如果cond不为零,则返回iftrue,否则返回iffalse。请注意,它是严格的,即在所有情况下都会对iftrue和iffalse进行评估。 ppIRExpr output: ITE(<cond>,<iftrue>,<iffalse>), eg. ITE(t6,t7,t8) */ struct { IRExpr* cond; /* Condition */ IRExpr* iftrue; /* True expression */ IRExpr* iffalse; /* False expression */ } ITE; } Iex; };
jump kinds
这描述了可以在来guest制流传输点传递给调度程序的提示。Re Ijk_InvalICache和Ijk_FlushDCache:guest state_must_有两个pseudo-registers,guest_CMSTART和guest_CMLEN,它们指定要无效的区域的开始和长度。CM代表“cache management”。这两个都是guest word的大小。在发出Ijk U InvalICache或Ijk U FlushDCache类跳转之前,相关toIR.c有责任确保这些值已填入适当的值。
Ijk_InvalICache请求使从请求范围获取的翻译无效。Ijk_FlushDCache请求刷新指定范围的D缓存。
Re Ijk_EmWarn和Ijk_EmFail:来宾状态必须有一个伪register guest_EMNOTE,它是32位的,与主机或来宾字大小无关。该寄存器应保持VexEmNote值,以指示退出的原因。
在Ijk_EmFail的情况下,exit是致命的(Vex生成的代码不能继续),因此跳转目标可以是任何东西。
Re Ijk_Sys_u(syscall jumps):guest state必须有一个伪register guest_IP_AT_syscall,它是来guest的大小。前端应该将其设置为最近执行的内核输入(系统调用)指令的IP。这使得备份guest以重新启动被信号中断的系统调用变得非常容易(也就是说,实际上完全有可能)。
Dirty helper calls
脏调用是一种灵活的机制,用于调用(可能有条件地)帮助函数或过程。helper函数可以读取、写入或修改客户机内存,也可以读取、写入或修改客户机状态。它可以接受参数并可选地返回值。通过存储私有状态,当使用相同的参数重复调用时,它可能返回不同的结果和/或执行不同的操作。
Memory Bus Events
Compare and Swap
typedef enum { Ist_NoOp=0x1E00, Ist_IMark, /* META */ Ist_AbiHint, /* META */ Ist_Put, Ist_PutI, Ist_WrTmp, Ist_Store, Ist_LoadG, Ist_StoreG, Ist_CAS, Ist_LLSC, Ist_Dirty, Ist_MBE, Ist_Exit } IRStmtTag;
typedef struct _IRStmt { IRStmtTag tag; union { /* A no-op (usually resulting from IR optimisation). Can be omitted without any effect. ppIRStmt output: IR-NoOp */ struct { } NoOp; /* META: 指令标记。标记表示单个机器指令的语句的开头(这些语句的结尾由下一个IMark或IRSB的结尾标记)。包含指令的地址和长度。 它还包含一个delta值。在尝试建立与地址和长度值的比较之前,必须从来guest序计数器值中减去增量,无论该程序计数器值是否引用此指令。对于x86、amd64、ppc32、ppc64和arm,delta值为零。对于Thumb 指令,delta值为 one。这是因为,在Thumb上,guest PC值(guest_R15T)是使用指令地址的前31位和lsb中的1进行编码的;因此,它们看起来(数字上)比它们所引用的指令的开头快1。IOW,guest_R15T 上有一个标准的ARM交互地址。 ppIRStmt output: ------ IMark(<addr>, <len>, <delta>) ------, eg. ------ IMark(0x4000792, 5, 0) ------, */ struct { Addr addr; /* instruction address */ UInt len; /* instruction length */ UChar delta; /* addr = program counter as encoded in guest state - delta */ } IMark; /*META:一个ABI提示,说明了这个平台的ABI。 目前,唯一的AbiHint是一个表示给定地址空间块[base。。base+len-1]已变得未定义。这在AMD64Linux和一些ppc变体上使用,以便向任何希望看到它们的人传递堆栈重分区提示。它还指示将要执行的下一条(动态)指令的地址。这是为了帮助Memcheck追踪原点。 ppIRStmt output: ====== AbiHint(<base>, <len>, <nia>) ====== eg. ====== AbiHint(t1, 16, t2) ======*/ struct { IRExpr* base; /* Start of undefined chunk */ Int len; /* Length of undefined chunk */ IRExpr* nia; /* Address of next (guest) insn */ } AbiHint; /*在来guest state的固定偏移量处编写来宾寄存器。 ppIRStmt output: PUT(<offset>) = <data>, eg. PUT(60) = t1 */ struct { Int offset; /* Offset into the guest state */ IRExpr* data; /* The value to write */ } Put; /*在来guest state下以非固定偏移量写入来宾寄存器。有关详细信息,请参见GetI表达式的注释。 ppIRStmt output: PUTI<descr>[<ix>,<bias>] = <data>, eg. PUTI(64:8xF64)[t5,0] = t1 */ struct { IRPutI* details; } PutI; /*将值赋给临时对象。注意,SSA规则要求每个tmp只分配给一次。IR健全性检查将拒绝包含一个临时块的任何块,该临时块没有被精确地分配给一次。 ppIRStmt output: t<tmp> = <data>, eg. t1 = 3*/ struct { IRTemp tmp; /* Temporary (LHS of assignment) */ IRExpr* data; /* Expression (RHS of assignment) */ } WrTmp; /*将值写入内存。这是一个普通的存储,而不是存储条件。要表示存储条件,请改用IRStmt.LLSC。 ppIRStmt output: ST<end>(<addr>) = <data>, eg. STle(t1) = t2*/ struct { IREndness end; /* Endianness of the store */ IRExpr* addr; /* store address */ IRExpr* data; /* value to write */ } Store; /*有人看守的商店。请注意,这是为了计算所有表达式字段(addr,data)而定义的,即使guard的计算结果为false。 ppIRStmt output: if (<guard>) ST<end>(<addr>) = <data> */ */ struct { IRStoreG* details; } StoreG; /*有防护的负载。请注意,这是为计算所有表达式字段(addr,alt)而定义的,即使guard的计算结果为false。 ppIRStmt output: t<tmp> = if (<guard>) <cvt>(LD<end>(<addr>)) else <alt> */ struct { IRLoadG* details; } LoadG; /*执行原子比较和交换操作。上面在IRCAS定义的注释中描述了语义。 ppIRStmt output: t<tmp> = CAS<end>(<addr> :: <expected> -> <new>) eg t1 = CASle(t2 :: t3->Add32(t3,1)) 表示地址t2处值的32位原子增量 还可以表示双元素ca,在这种情况下,<tmp>、<expected>和<new>都是由逗号分隔的项对。 struct { IRCAS* details; } CAS; /*根据存储数据,可以加载链接或存储条件。如果STOREDATA为空,则这是一个加载链接,这意味着数据正常地从内存加载,但地址的“保留”也存储在硬件中。 result = Load-Linked(addr, end) 转换规则总结: STOREDATA == NULL (LL): transfer type = type of RESULT STOREDATA != NULL (SC): transfer type = type of STOREDATA, and RESULT :: Ity_I1 struct { IREndness end; IRTemp result; IRExpr* addr; IRExpr* storedata; /* NULL => LL, non-NULL => SC */ } LLSC; /*调用(可能有条件地)有副作用的C函数(即“脏的”)。有关详细信息,请参阅IRDirty类型声明上方的注释。 ppIRStmt output: t<tmp> = DIRTY <guard> <effects> ::: <callee>(<args>) eg. t1 = DIRTY t27 RdFX-gst(16,4) RdFX-gst(60,4) ::: foo{0x380035f4}(t2)*/ struct { IRDirty* details; } Dirty; struct { IRMBusEvent event; } MBE; /* ppIRStmt output: if (<guard>) goto {<jk>} <dst> eg. if (t69) goto {Boring} 0x4000AAA:I32*/ struct { IRExpr* guard; /* Conditional expression */ IRConst* dst; /* Jump target (constant only) */ IRJumpKind jk; /* Jump kind */ Int offsIP; /* Guest state offset for IP */ } Exit; } Ist; } IRStmt;
basic block
类型环境:一堆语句、表达式等都是不完整的,没有一个环境指示每个IRTemp的类型。所以这就提供了一个。IR临时变量实际上只是无符号整数,因此它提供一个数组0。。n_类型使用-1个。
typedef struct { IRType* types; Int types_size; Int types_used; } IRTypeEnv;
typedef struct { IRTypeEnv* tyenv; IRStmt** stmts; Int stmts_size; Int stmts_used; IRExpr* next; IRJumpKind jumpkind; Int offsIP; } IRSB;
2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!
最后于 2天前 被wwzzww编辑 ,原因: