20.1 MSVC
MSVC2010 /Ox选项编译:
Listing 20.1: Optimizing MSVC 2010: /Ox /GS- /MD
__a$ = 8 ; size = 4
__b$ = 12 ; size = 4
_comp PROC
mov eax, DWORD PTR __a$[esp-4]
mov ecx, DWORD PTR __b$[esp-4]
mov eax, DWORD PTR [eax]
mov ecx, DWORD PTR [ecx]
cmp eax, ecx
jne SHORT $LN4@comp
xor eax, eax
ret 0
$LN4@comp:
xor edx, edx
cmp eax, ecx
setge dl
lea eax, DWORD PTR [edx+edx-1]
ret 0
_comp ENDP
_numbers$ = -40 ; size = 40
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_main PROC
sub esp, 40 ; 00000028H
push esi
push OFFSET _comp
push 4
lea eax, DWORD PTR _numbers$[esp+52]
push 10 ; 0000000aH
push eax
mov DWORD PTR _numbers$[esp+60], 1892 ; 00000764H
mov DWORD PTR _numbers$[esp+64], 45 ; 0000002dH
mov DWORD PTR _numbers$[esp+68], 200 ; 000000c8H
mov DWORD PTR _numbers$[esp+72], -98 ; ffffff9eH
mov DWORD PTR _numbers$[esp+76], 4087 ; 00000ff7H
mov DWORD PTR _numbers$[esp+80], 5
mov DWORD PTR _numbers$[esp+84], -12345 ; ffffcfc7H
mov DWORD PTR _numbers$[esp+88], 1087 ; 0000043fH
mov DWORD PTR _numbers$[esp+92], 88 ; 00000058H
mov DWORD PTR _numbers$[esp+96], -100000 ; fffe7960H
call _qsort
add esp, 16 ; 00000010H
...
第四个参数传递了一个地址标签_comp,指向了comp()函数。
我们来看MSVCR80.DLL(包含C标准库函数的MSVC DLL模块)里该函数的内部调用:
Listing 20.2: MSVCR80.DLL
.text:7816CBF0 ; void __cdecl qsort(void *, unsigned int, unsigned int, int (__cdecl *)(const void *, const void *))
.text:7816CBF0 public _qsort
.text:7816CBF0 _qsort proc near
.text:7816CBF0
.text:7816CBF0 lo = dword ptr -104h
.text:7816CBF0 hi = dword ptr -100h
.text:7816CBF0 var_FC = dword ptr -0FCh
.text:7816CBF0 stkptr = dword ptr -0F8h
.text:7816CBF0 lostk = dword ptr -0F4h
.text:7816CBF0 histk = dword ptr -7Ch
.text:7816CBF0 base = dword ptr 4
.text:7816CBF0 num = dword ptr 8
.text:7816CBF0 width = dword ptr 0Ch
.text:7816CBF0 comp = dword ptr 10h
.text:7816CBF0
.text:7816CBF0 sub esp, 100h
....
.text:7816CCE0 loc_7816CCE0: ; CODE XREF: _qsort+B1
.text:7816CCE0 shr eax, 1
.text:7816CCE2 imul eax, ebp
.text:7816CCE5 add eax, ebx
.text:7816CCE7 mov edi, eax
.text:7816CCE9 push edi
.text:7816CCEA push ebx
.text:7816CCEB call [esp+118h+comp]
.text:7816CCF2 add esp, 8
.text:7816CCF5 test eax, eax
.text:7816CCF7 jle short loc_7816CD04
第四个参数comp传递函数指针,comp()有两个参数,参数被检测后才执行。
这种使用函数指针的方式有一定的风险。第一种原因是如果你用qsort()调用了错误的函数指针,可能造成程序崩溃,并且这个错误很难被发现。
第二个原因是即使回调函数类型完全正确,使用错误的参数调用函数可能会导致更严重的问题。进程崩溃不是最大的问题,最大的问题是崩溃的原因—编译器很难发现这种潜在的问题。
20.1.1 MSVC + OllyDbg
我们在OD中加载我们的例子,并在comp()函数下断点。
我们可以看到第一次comp()调用时是如何比较的:fig.20.1.OD代码窗口显示了比较的值。我们还可以看到SP指向的RA地址在qsort()函数空间里(实际上位于MSVCR100.DLL)。
按F8直到函数返回到qsort()函数:fig20.2.这里比较函数被调用。
第二次调用comp()—当前比较的值不相同:fig203。
Figure 20.1: OllyDbg: first call of comp()
Figure 20.2: OllyDbg: the code in qsort() right a_er comp() call
Figure 20.3: OllyDbg: second call of comp()
20.1.2 MSVC + tracer
我们来看成对比较,来对10个数字进行排序:1892, 45, 200, -98, 4087, 5, -12345, 1087, 88,-100000.
我们找到comp()函数中的CMP指令地址,并在其地址0x0040100C上设置断点。
tracer.exe -l:17_1.exe bpx=17_1.exe!0x0040100C
断点中断是的寄存器地址:
PID=4336|New process 17_1.exe
(0) 17_1.exe!0x40100c
EAX=0x00000764 EBX=0x0051f7c8 ECX=0x00000005 EDX=0x00000000
ESI=0x0051f7d8 EDI=0x0051f7b4 EBP=0x0051f794 ESP=0x0051f67c
EIP=0x0028100c
FLAGS=IF
(0) 17_1.exe!0x40100c
EAX=0x00000005 EBX=0x0051f7c8 ECX=0xfffe7960 EDX=0x00000000
ESI=0x0051f7d8 EDI=0x0051f7b4 EBP=0x0051f794 ESP=0x0051f67c
EIP=0x0028100c
FLAGS=PF ZF IF
(0) 17_1.exe!0x40100c
EAX=0x00000764 EBX=0x0051f7c8 ECX=0x00000005 EDX=0x00000000
ESI=0x0051f7d8 EDI=0x0051f7b4 EBP=0x0051f794 ESP=0x0051f67c
EIP=0x0028100c
FLAGS=CF PF ZF IF
...
过滤EAX和ECX得到:
EAX=0x00000764 ECX=0x00000005
EAX=0x00000005 ECX=0xfffe7960
EAX=0x00000764 ECX=0x00000005
EAX=0x0000002d ECX=0x00000005
EAX=0x00000058 ECX=0x00000005
EAX=0x0000043f ECX=0x00000005
EAX=0xffffcfc7 ECX=0x00000005
EAX=0x000000c8 ECX=0x00000005
EAX=0xffffff9e ECX=0x00000005
EAX=0x00000ff7 ECX=0x00000005
EAX=0x00000ff7 ECX=0x00000005
EAX=0xffffff9e ECX=0x00000005
EAX=0xffffff9e ECX=0x00000005
EAX=0xffffcfc7 ECX=0xfffe7960
EAX=0x00000005 ECX=0xffffcfc7
EAX=0xffffff9e ECX=0x00000005
EAX=0xffffcfc7 ECX=0xfffe7960
EAX=0xffffff9e ECX=0xffffcfc7
EAX=0xffffcfc7 ECX=0xfffe7960
EAX=0x000000c8 ECX=0x00000ff7
EAX=0x0000002d ECX=0x00000ff7
EAX=0x0000043f ECX=0x00000ff7
EAX=0x00000058 ECX=0x00000ff7
EAX=0x00000764 ECX=0x00000ff7
EAX=0x000000c8 ECX=0x00000764
EAX=0x0000002d ECX=0x00000764
EAX=0x0000043f ECX=0x00000764
EAX=0x00000058 ECX=0x00000764
EAX=0x000000c8 ECX=0x00000058
EAX=0x0000002d ECX=0x000000c8
EAX=0x0000043f ECX=0x000000c8
EAX=0x000000c8 ECX=0x00000058
EAX=0x0000002d ECX=0x000000c8
EAX=0x0000002d ECX=0x00000058
有34对。因此快速排序算法对10个数字排序需要34此对比操作。
20.1.3 MSVC + tracer (code coverage)
我们使用跟踪特性收集寄存器的值并在IDA中查看。
跟踪comp()函数所有指令:
tracer.exe -l:17_1.exe bpf=17_1.exe!0x00401000,trace:cc
IDA加载.idc脚本:fig20.4。
IDA给出了函数名字(PtFuncCompare)—IDA认为该函数指针被传递给qsort()。
可以看到a和b指向数组不同的位置,并且相差4-32bit的字节数。
0x401010 和 0x401012之间的指令从没有被执行:事实上comp()从来不返回0,因为没有相等的元素。
Figure 20.4: tracer and IDA. N.B.: some values are cutted at right