1.在出错的对话框弹出的时候暂时,看堆栈可以看到0013F348 0013F828 UNICODE "PI * 2 = ",于是找到:
00420D20 >/. 55 push ebp
00420D21 |. 8BEC mov ebp, esp
00420D23 |. 81EC 94000000 sub esp, 94
....
2.这些代码继续跟:
00413E00 |. 56 |push esi
00413E01 |. 50 |push eax
00413E02 |. FF35 C8A04200 |push dword ptr [42A0C8]
00413E08 |. E8 3FEFFFFF |call <__decode_pointer 004>
00413E0D |. 59 |pop ecx
00413E0E |. FFD0 |call eax
发现是这些代码导致的错误,把[42A0C8]里的东西解密(__decode_pointer调用了DecodePointer)一下然后调用
call eax通过动态方式调用了0041AE1F <Test.__fptrap>函数,而经过再三考虑这个是必经之路,说明[42A0C8]里的东西应该是关键。肯定有操作[42A0C8]这个地址的代码
3.写OD脚本把代码段找了一下(因为OPCODE里一定有42A0C8),一共3处:
temp: 00411DFF --------->写入值
00411DFD |. C705 C8A04200>mov dword ptr [42A0C8], <__cfltcvt_l >
temp: 00413E04 --------->读取值(在格式化字符串的时候被调用了)
temp: 0041678A --------->读取值(根本没人调用它)
基本调用关系OD可以分析出来
4.再次运行程序试着在执行call eax的时候把将__cfltcvt_l的地址给eax,竟然成功了!!!
那00411DFF这个地址是一定要执行的。可能还有其它的问题,因为这里有一些解密操作。
但是在00411DFF附近下断点并没有断下,看函数名是_fmath调过来的,而看调用最终到了:
0040F618 <Test.__cinit >/$ 833D 5C404200>cmp dword ptr [42405C], 0
0040F61F |. 74 1A je short 0040F63B
0040F621 |. 68 5C404200 push 0042405C
0040F626 |. E8 65670000 call <__IsNonwritableInCurrentIma>
0040F62B |. 85C0 test eax, eax
0040F62D |. 59 pop ecx
0040F62E |. 74 0B je short 0040F63B
0040F630 |. FF7424 04 push dword ptr [esp+4]
0040F634 |. FF15 5C404200 call dword ptr [42405C] ; <Test.__fpmath 00411e26 f libcmt:fpinit.obj>
0040F63A |. 59 pop ecx
0040F63B |> E8 AC660000 call <__initp_misc_cfltcvt_tab >
而__cinit这个函数是程序初始化时调用的,最后发现关键跳在
0040F62E |. 74 0B je short 0040F63B只要把它nop掉一切OK。
发现call <__IsNonwritableInCurrentImage>会访问PE头的节表第二个节的描述符判断是否是可写的。如果是可写的返回0,只读的返回1。
查了一下PE头原来.rdata节不是只读的。用LordPE修改一下。
OK!
5.回过头来再看一下加解密部分:
在[42A0C8]下内存读写断点可以跟到在:00415CEC <Test.__initp_misc_cfltcvt_tab >/$ 56 push esi
00415CED |. 57 push edi
00415CEE |. 33FF xor edi, edi
00415CF0 |> 8DB7 B0A04200 /lea esi, dword ptr [edi+42A0B0]
00415CF6 |. FF36 |push dword ptr [esi]
00415CF8 |. E8 D8CFFFFF |call <__encode_pointer 00412cd5>
00415CFD |. 83C7 04 |add edi, 4
00415D00 |. 83FF 28 |cmp edi, 28
00415D03 |. 59 |pop ecx
00415D04 |. 8906 |mov dword ptr [esi], eax
00415D06 |.^ 72 E8 \jb short 00415CF0
00415D08 |. 5F pop edi
00415D09 |. 5E pop esi
00415D0A \. C3 retn
__encode_pointer这个函数里调用的EncodePointer加密
这个函数里对一张地址表做了加密(包括42A0C8)其实这个函数就是在紧接着初始化之后就调用了。
后面就是每用一次里面的函数指针就解密一次。
根本原因在于.rdata的PE头属性为只读,初始化的时候并没有初始化地址表。
附OD脚本:
var temp
mov temp, 00401000
start:
find temp, #C8A042#
cmp $RESULT, 0
je Exit0
mov temp, $RESULT
log temp
inc temp
cmp $RESULT, 00422000
jb start
Exit0:
ret
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)