Valgrind VEX IR
2020-04-03 04:43:37 Author: bbs.pediy.com(查看原文) 阅读量:340 收藏

[调试逆向] [原创] Valgrind VEX IR

3天前 287

最近在学习angr,里面有用到vex ir,之前对vex ir不了解,打算系统学习一遍。

本片文章主要参考angr官方文档:

简介:

1.code blocks:

         代码被分割成很多小的代码片段(类型为'IRSB'),每个块表示1~50条指令。IRSB块是单入口,多出口。每个IRSB包含以下三种数据:

  •          -a type enviroment, 它指示IRSB中存在的每个临时值的类型。
  •          -a list of statement, 表示对应的代码
  •          -a jump that exits from the end the 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 是:

  •    the Imark
  •   三条对临时变量的赋值
                        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编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-258508.htm
如有侵权请联系:admin#unsafe.sh