2020.03.23-2020.03.29

32位软件逆向技术

控制语句

条件设置指令(SETcc)

条件设置指令的形式是“SETcc r/m8”, r/m8表示8位寄存器或单字节内存单元
条件设置指令测试一些标志位,把结果记录的目标操作数中
测试:

  1. of(=1溢出)
  2. zf(=1操作后等于0/相等)
  3. cf(=1进位/低于)
  4. cf、zf结合判断
  5. sf(=1操作后结果为负)
  6. pf(=1,1个数为偶数)
  7. sf、of结合判断(=1小于)
  8. zf(=1或zf!=of,不大于)

https://brubbish.github.io/34199.html#标志寄存器

条件设置指令可以用来消除程序中的转移指令
如:

纯算法实现逻辑判断

一些编译器在优化的时候,会不改变原逻辑的情况下,使用数学技巧把代码中的一些逻辑分支语句转换为算术操作,减少程序中的条件转移指令,提高cpu的流水线的性能(…tql)

循环语句

可以进行反向引用
其他类型的分支语句,如if-else都是由低地址向高地址区域引用的(向下跳),依此可以将循环语句识别出来(调回去)
确定某段代码是循环代码后就可以分析计数器(一般用ecx)

如果编译时设置优化“Maximize Speed”:

1
2
3
xor ecx,ecx             ;变量初始化sum=0
xor eax,eax ;变量初始化i=0
....

数学运算符

如果编译器没有进行优化,则这些运算符很容易理解(…)所以下面都是经过优化的

整数的加法和减法

用lea代替add和sub
lea指令允许用户在一个时钟内完成对c=a+b+78h(某个数)的计算,其中abc都是在有寄存器的情况下才有效的,如:

1
2
3
4
5
lea c,[a+b+78]
=>
mov eax,dword ptr [esp] ;eax=a
mov ecx,dword ptr [esp] ;ecx=b
lea edx,dword ptr [ecx+eax+78]

时钟周期:一个时钟脉冲所需要的时间。(计组)

整数的乘法

一般被编译成mul(无符号)、imul(有符号)指令(运行慢),为了提高效率会用其他指令完成
如果一个数是2的幂,用左移指令shl;3、5、6、7、9等数用加法提高效率:如把exa*5写成"lea eax,[eax+4 *4];lea指令可以实现寄存器乘以2、4、8的运算

整数的除法

一般被编译成div、idiv指令,大概比乘法运算多消耗10倍cpu时钟
如果除数是2的幂,可用右移指令’shr a,n’:a是被除数,n是2的指数(进行无符号数计算)有符号数用sar指令

除法指令需要用到符号扩展指令cdq,作用是把eax中的数视为有符号数,将eax的最高位(符号位)扩展到edx中。如果eax最高位是1,执行后edx=FFFFFFFF;如果是0,edx=00000000。通过这种方法把32位有符号数变为edx:eax的64位有符号数,满足64位运算指令的需要。

对除法优化时会用乘法代替除法,优化的公式较多,一例如:倒数相乘a*b=a *(1/b)

(在下面的64位中描述更完整)

文本字符串

字符寻址指令

与字符指针处理相关的指令有mov、lea等
mov指令将地址/寄存器中的数据放到目的寄存器中,如mov eax [401000]/[ecx]
lea(load effective address)操作数是地址,lea eax,[addr] 就是把addr的地址放入eax中

1
2
3
lea eax,[401000]
等价于
mov eax,401000

计算字符串长度

c语言的strlen():

64位软件逆向技术

x64通用寄存器的名称第一个字母由e改为r,共16个;16个128位xmm寄存器(通常用于优化代码)

函数

栈平衡

每8字节的栈空间用来保存一个数据
尽量保证栈顶对齐值为16(可以被16整除)

调用约定

x86(32位)的函数调用有stdcall、__cdecl、fastcall等,x64只有一种寄存器快速调用约定:

  1. 整数:前四个参数用寄存器rcx→rdx→r8→r9,其余放在栈里,入栈顺序从右到左,任何大于8字节或不是1、2、4、8字节的参数用地址传递
  2. 浮点数:前四个参数用xmm寄存器完成:xmm0→xmm1→xmm2→xmm3

栈为函数的前四个参数预留了32字节的空间,当函数功能复杂时将寄存器的参数保存到这个预留栈空间。预览栈空间由函数调用者申请、平衡

数学运算

整数的除法

  1. 有符号数,除数为2^n
    x>=0,x/2^n=x>>n
    x<0,x/2^n=(x+(2^n-1))>>n
    除数为-2^n时最后用’neg 寄存器’指令求补
  2. 有符号数,除数为正,非2^n
    32位:x>=0,result=(xc>>32)+x>>n,<0时结果再+1
    64位:x>=0,result=(x
    c>>64)+x>>n,<0时结果再+1
  3. 有符号数,除数为负,非2^n
    32位:x>=0,result=(xc>>32)-x>>n,<0时结果再+1
    64位:x>=0,result=(x
    c>>64)-x>>n,<0时结果再+1
  • c为魔数
  1. 无符号数,除数位2^n
    用shr右移
  2. 无符号数,除数非2^n
    32位:x>=0,result=xc>>32>>n
    62位:x>=0,result=x
    c>>62>>n

整数取模

可以用除法指令,但是除法指令执行周期较长,通常优化将其转换成位运算或除法运算,再用除法运算进行优化

  1. 除数b=2^n
    取得被除数二进制数的最后n位,负数要在n位之前补1:
    x>=0,result=x&(b-1);x<0,result=(x&(b-1))-1|(~b-1)+1
  2. 除数b!=2^n
    采用“余数=被除数-商除数”
    result=x-x/b
    b

使用 OllyDbg 从零开始 Cracking

数制系统

十六进制负数

从00000000~FFFFFFFF
正数:00000000~7FFFFFFF
负数:80000000~FFFFFFFF(-1)
在od左下角的commandbar可查询十六进制数的十进制值和对应的ASCII字符:? [十六进制值]

寄存器

标志寄存器

  1. o,溢出标志
    记录了有符号数运算的结果是否发生了溢出,如果发生溢出OF=1,如果没有OF=0

  2. p,奇偶标志
    指令的结果用二进制表示时1的总数,如果为偶数PF=1,如果不为偶数PF=0

  3. z,零标志
    记录相关指令执行后结果是否为0,如果为0那么ZF=1,如果不为0那么ZF=0

  4. s,符号标志
    运算结果为负设置为1,结果为正设置为0

  5. c,进位标志
    记录了无符号运算的结果是否有向更高位的进位(9位),有置1,无置0

  6. 其他

python爬虫学习

https://brubbish.github.io/710f8e5f.html##正则表达式