《逆向工程实战》笔记

对于《逆向工程实战》第一章~第二章的学习记录。

第一章 x86与x64

1.数据移动

1.


2.下面的内存访问形式常用于访问数组类对象;

3.另一类数据移动指令使用隐式的源和目标地址,如SCAS和STOS指令;




2.算术运算

1.移位;


2.乘法;


3.除法;

3.栈操作与函数调用

1.压栈和出栈;


2.函数调用——如果被调用函数有局部变量,那么该函数会在起始处通过减小esp值来增长栈大小,然后所有的局部变量都可以通过ebp加上一个负的偏移量来访问;而此函数调用时传入的参数是通过ebp加上一个正的偏移量来访问的;

4.控制流

1.区分变量是有符号类型还是无符号类型的方法之一——根据下述条件代码判断;

2.switch-case;

3.循环;

  • 在机器码层级,循环是通过jcc和jmp组合指令来实现的,也即是通过if-else和goto结构实现的;
  • 有些循环也可以通过LOOP指令实现,重复执行代码块ECX次。
5.系统机制
  1. 虚拟地址转换;
  2. 异常/中断处理;
6.综合练习
  1. 逆向实践关键——阅读技术手册(如Intel/AMD参考手册)和线上文档;
  2. 中断寄存器IDT/IDTR;
7.x64

1.寄存器组与数据类型——GPR——通用寄存器;


2.数据移动;

3.规范地址;

4.函数调用


第二章 ARM

1.基本特性

1.CPU体系架构-RISC和CISC;

  • X86属于复杂指令集系统(CISC);
  • ARM,MIPS属于精简指令集系统(RISC);
  • 与x86相比,arm的指令集更小,但提供的通用寄存器更多;

2.arm和x86的区别;



3.cpu如何区分接下来要执行的arm指令是什么指令集?


2.数据类型与寄存器

1.数据类型;

2.寄存器;


3.关于PC——如果直接读取PC值,则其值遵循定义;在调试时PC则指向将要执行的指令;

3.系统级控制与设置
4.指令集介绍

1.arm指令集的独特之处;


5.数据加载与存储

####### (1)LDR与STR



这里对上图中的代码进行补充解释——
(1) SUBS.w  R8, R3, R5    //[.W 是一个可选的指令宽度说明符。它不会影响为此指令的行为,它只是确保生成 32 位指令];
                     //此条指令执行运算R8=R3-R5,后面的S是条件标志码,表示把借位结果写入CPSR寄存器;
(2) LDRB    R7,[R5]     //LDR指令后面的B后缀限定了传送的操作数的长度为一个8位的字节数据,LDRB指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。
(3) 这里之所以判定R5是基地址,R8是偏移量,应该是因为偏移量一般都是通过运算得到的,而基地址一般都是直接赋值得到的。 

这里对上图中的代码进行补充解释——
(1) LDR.w   R2, [R0,R3,LSL#2]    //LSL逻辑左移,每逻辑左移1位就相当于原数值进行乘2操作;
                     //此条指令相当于执行R2=R0+R3*4;
(2) UXTH    R4,R3     //UXTH指令——半字被无符号扩展到32 位(高16位清0);
(3) ORRS.w  R3, R3, #2    //ORR{条件}{S}  目的寄存器,操作数1,操作数2;
                          //ORR指令用于在两个操作数上进行逻辑戒运算,并把结果放置到目的寄存器中;
                          //操作数1应该是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数1的某些位;
                          //该指令设置R3的0、1位为1,其余位保持不变.

参考链接如下:

(1)常用的ARM指令集知识
(2)ARM汇编指令;
(3)ARM的六大类指令集—LDR、LDRB、LDRH、LDM、STR、STRB、STRH、STM;
(4)ARM—– 移位操作(LSL、ASL、LSR、ASR、ROR、RRX);
(5)ARM(CM3)的汇编指令;

####### (2)LDR的其他用途


####### (3)LDM与STM



####### (4)PUSH与POP



ARM的栈操作指令区分了各种情况如下图,比起x86体系更为复杂,需要根据具体的指令判断入栈和出栈的具体顺序和操作。


ARM模式的指令集里没有PUSH/POP指令(是用诸如STMFD SP!,{R4,LR}等指令代替的),只有Thumb模式里的指令集才有PUSH/POP指令,故可由此确定程序的指令集类型是ARM还是thumb。

关于ARM中的栈指令的更多参考资料——

(1)ARM的栈指令;
(2)ARM汇编基础速成7:栈与函数;

6.函数与函数调用

x86/x64只有一个用于函数调用的指令(cALL)和一个用于分支跳转的指令(JMP)。与之不同,根据目标地址的编码方式,ARM则提供了多种指令。调用函数的时候,处理器需要知道函数返回后从哪里继续执行,这个位置通常被称为返回地址。

在x86中,CALL指令在跳转到目标函数之前隐式地把返回地址压入栈顶,等到目标函数执行完毕,会把栈顶弹出到EIF,从而从返回地址恢复执行。ARM体系结构上的机制本质上也是如此,只有几点微小的区别。

  • 首先,返回地址可以保存在栈上,也可以保存在链接寄存器(LR)中;
  • 调用结束后要恢复执行,需要显式地把返回地址从栈上弹出到PC寄存器,否则会无条件跳转到LR。
  • 其次,根据目标地址的最低位(LSB)不同,一次分支跳转可以在ARM状态和Thumb状态之间切换。
  • 第三点,ARM定义了标准调用惯例:前4个32位参数通过寄存器(R0~R3)传递,其余的参数放在栈上。返回值保存在R0中。

ARM中函数调用的指令是B、BX、BL和BLx。

  • B是一条简单的无条件跳转指令,与x86中的JMP指令相同,通常在循环和条件执行内部用于跳转到开头或跳出循环,还可用于调用永不返回的函数。B指令只能使用标签偏移量作为其目标地址,而不能使用寄存器。这种情况下,B指令的语法为B imm,其中imm是相对于当前指令的偏移量。需要记住的很重要一点是,因为ARM和Thumb指令是2字节或4字节对齐的,所以目标偏移量必须是一个偶数。
  • Bx是分支跳转并交换( Branch and Exchange),与B指令的相似之处在于,两者都是把控制转移到某个目标地址处,但Bx能够在ARM和 Thumb状态间切换,并且目标地址是保存在寄存器中的。分支跳转指令以X结尾,表示这条指令能够支持状态切换。如果目标地址的最低位是1,那么处理器会自动切换到Thumb状态,否则就执行在ARM状态。指令的格式是Bx<寄存器>,其中寄存器中存有目标地址。这条指令有两种最常用的方式,一种是通过跳转到LR(也即Bx LR)从函数返回,还有一种是用于切换到不同状态的代码(也即从ARM切换到 Thumb状态或反之)。在编译后的代码中,函数结尾处几乎总会出现Bx LR,基本上与x86中的RET相同。
  • BL是分支跳转并连接( Branch with Link),它类似于B指令,但它可以在把控制切换到目标偏
    移量之前把返回地址保存到LR。这也许是和x86中的cALL指令最为接近的一条指令,经常在函数调用中使用。BL指令格式与B指令相同(也就是说,只接受偏移量参数)。
  • BLx是指跳转加连接和交换( Branch with Link and Exchange)。与B类似,BLx也可以支持状态切换。两者主要的区别在于,BLx可以接受偏移量或寄存器作为跳转目标,而且在BLx指令使用偏移量的情况下,处理器总是会切换状态(ARM到 Thumb或反之)。 因为这条指令与B的特性相同,也可以把它当作x86中的cALL指令。实际使用中,Blx和Bl都用于实现函数调用。如果函数在32MB范围之内,通常使用BL,而目标区域不确定(比如函数指针)的时候常用BLx。通常在 Thumb状态下运行的时候,使用Bx指令调用库例程,而在ARM状态下使用BL。
7.算术运算


8.分支跳转与条件执行


各种具体的比较指令示例详见博客《汇编与对应c伪码》中对应章节的内容。

9.杂项

10.综合练习

开始研究一个未知函数时,第一步就是要确定关于这个函数已经有哪些已知的事实。下面列出了针对上图中代码的已知事实,以及推导过程,值得后续的分析参考。

接下来需要研究那两个未知的结构体,显然无法恢复结构体的完整布局,因为这个函数中只引用了其部分成员,但仍然可以推导出一些字段的类型信息。


如下图所示这一段分析还是有点小懵,主要是不太明白怎么就直接判断出数组类型的?

最后是对于上述整个分析过程的总结——