《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.15-1.17)
2016-12-29 12:01
381 查看
1.15. 循环
1.15.1. 简单例子
x86
x86中的LOOP指令,检查ECX中的值,如果不为0就不断跳到标签重复执行代码。LOOP并不是很方便,一般的编译器也不会用它,所以如果在代码中出现就一定是手写的。对于for循环中的i值,在x86中,代码的逻辑就是,每次检查EAX的值是否是i的最大值,如果是,就跳出循环,否则不断跳回代码初始位置。
GCC的实现也差不多。上述都是未优化版本。
如果我们把/0x优化标签打开,则此时i不会存在栈中了,而是使用一个单独的寄存器ESI来存放i的值。这是MSVC的版本。
如果用GCC,要用-03参数来打开优化。简直了,GCC简单粗暴,就从2到9全部执行一遍,call printing_function写了八遍。这叫做loop unwinding。如果我们把i的上限提高到100,则GCC会老老实实用上述的循环方式,其中用EBX来分配i的值。
x86: OllyDbg
用ESI来表示i控制循环。ESI最大值为9。CMP ESI, 0A JL xxxx
x86:tracer
tracer.exe可以手动追踪。找到PUSH ESI的地址,然后开始追踪。在该地址设置断点,tracer会打印出寄存器的状态。tracer.log
还可以生成载入IDA的文件,查看反汇编和指令执行情况。
ARM
未优化的Keil(ARM模式)
i存在R4寄存器,MOV R4, #2初始化i。MOV R0,R4 BL printing_funcion
以上两条构成循环体
CMP R4,#0xA BLT loc_35c
以上两句是比较和分支
优化的Keil(Thumb模式)
差不多,去掉了一些不必要的跳转。优化的Xcode(LLVM)(Thumb-2模式)
直接把循环展开,还把f()函数内联了,直接调用其中的printf函数,展开了8次。在比较简单函数的情况下用这个比较好。ARM64:优化的GCC
ARM64:未优化的GCC
这两个似乎没有任何区别。MIPS
初始化i之后,先检查i的值,再执行循环体。有些编译器在知道循环体肯定会执行的情况下,把检查i值和循环体执行对调。
1.15.2 内存复制步骤
实际中的内存复制每次迭代复制4或8字节,使用SIMD、向量化。我们给出了一个简单的my_memcpy函数实现,每次复制一个字节。直接的实现
x64
RDI 目标地址 RSI 源地址 RDX 块大小 mov cl, BYTE PTR [rsi+rax] mov BYTE PTR [rdi+rax]
加载到RSI+i,存储到RDI+i
ARM64
X0 目标地址X1 源地址
X2 块大小
X3 存放i
Thumb模式
R0 目标地址R1 源地址
R2 块大小
ARM的ARM模式
充分利用了条件后缀指令。LDRBCC, STRBCC, ADDCC, BCC
MIPS
新指令:LBU, Load Byte Unsigned,SB,Store Byte。1.15.3. 结论
ARM中通常用R4表示计数器变量。(类似x86中的ECX)add [counter], 1 相当于 MOV REG, [counter] INC REG MOV [counter], REG
如果循环次数不多,用EBX=[counter]
有时编译器会调动语句块位置,用跳转连接。
1.15.4. 练习
http://challenges.re/54http://challenges.re/55
http://challenges.re/56
打印出从1到100。
http://challenges.re/57
打印1,4,7,10一直到…
1.16. 简单的C字符串处理
1.16.1. strlen()
其实现是用while()。const char *eos=str; while(*eos++); return(eos-str-s);
这里利用了str以\0结尾的特性
x86
未优化的MSVC
第一个MOVSX从内存中一个地址读取byte放到32位寄存器。MOV with Sign-Extended,8-31位位数字,1-8为符号补位。对于有符号数的复制非常有用。未优化的GCC
用MOVZX代替了MOVSX。拓展部分用0填充。可用来替换指令对xor eax, eax/mov al, […]
SETNA:if ZF==0,设置AL为1
SETNZ al,接下来几条表明如果al不为0,跳转到loc_80483F0。
优化的MSVC
EDX存放指向字符串的指针。EDX放到EAX CL = *EAX EAX++ CL==0?
如果不是就继续循环,如果是,计算eax和edx的差,再减1即字符串长度。
优化的MSVC+OllyDbg
优化的GCC
NOT把所有位取反。ARM
32位ARM
未优化的Xcode4.6.3(LLVM)(ARM模式)
两个变量:eos, str。先把两个变量存到栈上。循环从loc_2CB8开始,eos->R0,R0+1->eos,LDRSB与x86中的MOVSX相同,从内存地址读并拓展到32位。string->R0,然后是CMP和BEQ。优化的Xcode4.6.3(LLVM)(Thumb模式)
不一样的是不需要栈了,直接把str放在R0,eos放在R1。LDRB.W R2, [R1], #1 先将R1加载到R2,再R1+1 MVNS类似x86中NOT,所有位取反。 MVNS R0, R0 ADD R0, R1
这两条达到str-eos-1效果。
优化的Keil(ARM模式)
与上面的基本差不多,不过计算str-eos-1的方法发生变化。SUBEQ R0, R1, R0 SUBEQ R0, R0, #1
ARM64
优化的GCC
未优化的GCCC
MIPS
没有NOT指令,有NOR,=OR+NOT。1.17. 替换算术指令
ADD和SUB可以互换,LEA常用于简单算术运算。1.17.1. 乘法
使用加法实现乘法
return a*8 用了3次加法实现 add eax, eax 这条指令出现三次
使用移位实现乘法
a*4 shl eax, 2 左移两位即可
ARM:LSL r0, r0, #2
GCC:SLL r0,a0, 2
使用移位、减法、加法实现乘法
即使是乘7或17也能完成(不用乘法)
x86
a*7 EAX=EAX-ECX=ECX*8-ECX=ECX*7=A*7 a*28 EAX=a*7,EAX<<2=(a*7)*4=a*28 a*17 EAX=EAX<<4=EAX*16=a*16 EAX=EAX+a=a*16+a=a*17
ARM中也一样,与x86实
4000
现基本相同。
Thumb模式不太一样,a*28没法优化,要用MULS。
MIPS的a*7和a*17一样,a*8=a0<<5-a0<<2=a0*32-a0*4。
64位
x64
a*7=a*8-a a*28=RID<<5-RDI*4 a*17-a<<4+a
ARM64
a*7=a<<3-a a*28=a<<5-a<<2 a*17=a+a<<4
Booth乘法算法
使用加法和移位替换乘法1.17.2. 除法
使用位移实现除法a/4 shr eax, 2
ARM中是LSR r0, r0, #2
1.17.3. 练习
http://challenges.re/59其实就是a*7的实现。
相关文章推荐
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.7)
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.18)
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.8-1.9)
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.14)
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.4)
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.10-1.12)
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.1-1.3)
- 《Reverse Engineering for Beginners》 - 第1章 代码模式 - 笔记(1.5-1.6)
- 逆向基础8:循环结构-Reverse Engineering for Beginners
- 设计模式笔记:单例模式(C++代码)
- 创建型模式之 工厂、简单工厂、抽象工厂 简单图析和代码分析 笔记
- 论文笔记 TIE: Principled Reverse Engineering of Types in Binary Programs
- 工作笔记:单例模式的作用好处和代码
- 《大话设计模式》之--第1章 代码无错就是优?----简单工厂模式
- 通用代码学习笔记--单例模式
- 大话设计模式 第1章 代码无错就是优? 简单工厂模式
- Java 基础一些代码练习笔记(策略模式)
- 大话设计模式-第1章代码无错就是优?-简单的工厂模式
- Reverse engineering NAND Flash for fun and profit
- 【笔记】Eclipse and Java for Total Beginners—008