如果一个程序使用了为数不多的FPU(Float Point Unit,浮点运算单元)指令,那么我们可以采用人工排查的方式把它们逐一筛选出来。
以Microsoft的表格工具软件Excel为例。我们可能要研究它对录入公式的处理方法,例如除法操作。
首先要用IDA加载Office 2010里的excel.exe(本章以版本号为14.0.4756.1000的excel为例),然后生成完整的指令清单并将其保存为后缀名为.lst的文本文件。接下来,我们就可以用终端指令检索指令清单中的全部FDIV指令(Floationg Point Divide,浮点数除法)。严格说来,使用grep不能检索出全部的浮点数除法运算指令。如果除数是常量,那么编译器就不太可能分配FDIV指令。当然这种特例不在我们的研究范围之内。
cat EXCEL.lst | grep fdiv | grep -v dbl_ > EXCEL.fdiv
总共有144个匹配结果。
然后,我们在Excel里输入计算公式“=(1/3)”,并检查每个指令的运行结果。
在调试器(或tracer)里逐一排查除法运算指令以后,我们幸运地发现在第14个FDIV指令就是我们要找的指令:
.text:3011E919 DC 33 fdiv qword ptr [ebx]
PID=13944|TID=28744|(0) 0x2f64e919 (Excel.exe!BASE+0x11e919)
EAX=0x02088006 EBX=0x02088018 ECX=0x00000001 EDX=0x00000001
ESI=0x02088000 EDI=0x00544804 EBP=0x0274FA3C ESP=0x0274F9F8
EIP=0x2F64E919
FLAGS=PF IF
FPU ControlWord=IC RC=NEAR PC=64bits PM UM OM ZM DM IM
FPU StatusWord=
FPU ST(0): 1.000000
此时,第一个参数(被除数)被保存在ST(0)中,而除数则保存在[EBX]中。
FDIV后面的FSTP指令,将结果写入内存:
.text:3011E91B DD 1E fstp qword ptr [esi]
在FSTP指令处设置断点,我们能看到下述运算结果:
PID=32852|TID=36488|(0) 0x2f40e91b (Excel.exe!BASE+0x11e91b)
EAX=0x00598006 EBX=0x00598018 ECX=0x00000001 EDX=0x00000001
ESI=0x00598000 EDI=0x00294804 EBP=0x026CF93C ESP=0x026CF8F8
EIP=0x2F40E91B
FLAGS=PF IF
FPU ControlWord=IC RC=NEAR PC=64bits PM UM OM ZM DM IM
FPU StatusWord=C1 P
FPU ST(0): 0.333333
为了验证我们的结果,我们做一个简单而有趣的试验:在内存中对具体的内存单元直接进行修改,以便得到“直接运算可能不能生成的效果”。
比如:
tracer -l:excel.exe bpx=excel.exe!BASE+0x11E91B,set(st0,666)
PID=36540|TID=24056|(0) 0x2f40e91b (Excel.exe!BASE+0x11e91b)
EAX=0x00680006 EBX=0x00680018 ECX=0x00000001 EDX=0x00000001
ESI=0x00680000 EDI=0x00395404 EBP=0x0290FD9C ESP=0x0290FD58
EIP=0x2F40E91B
FLAGS=PF IF
FPU ControlWord=IC RC=NEAR PC=64bits PM UM OM ZM DM IM
FPU StatusWord=C1 P
FPU ST(0): 0.333333
Set ST0 register to 666.000000
这样的话,我们在Excel的相关单元就会发现数字666。从这一点可以看出,我们找到了正确的指令位置。
图60.1 通过修改内存发现效果
如果我们调试的是64位的同版本Excel程序,我们只会找到12条FDIV指令。而我们关注的运算指令是第3个FDIV指令:
tracer.exe -l:excel.exe bpx=excel.exe!BASE+0x1B7FCC,set(st0,666)
大概是在编译64位的Excel程序时,编译器使用SSE指令替代了float和double型数据的除法运算指令。其中,SSE指令集的DIVSD指令就出现了268次。