EBP寄存器一般用于对参数、局部变量进行寻址。由于仅仅只有7个通用寄存器,
因此优化的编译器并不希望将一些寄存器永久性地分配出来做这些事情。逆向
的目标的时候经常看到的是ESP。esp是堆栈指针寄存器,当
程序中诸如push eax、
push ebx 之类指令的时候,堆栈顶部的指针就会随之变化,发生"漂移"(借用反汇编解密一书
中的话.呵呵),这给分析过程带来很多麻烦。不过不用怕,采用IDA基本能很直观的解释出来,
基本不用花费太大力气就能看明白。但不是所有的人手上都会有这样强大的
工具
在手上,而且如果太依赖这种"聪明"的工具对你的逆向能力没太大帮助,如果你还是
一个新手的话。
本文的目的力求通过分析一小段代码来阐述这个头痛的问题,希望能起到抛砖引玉之用!
复制内容到剪贴板
代码:
下面是一段C++的代码,严格来说,它更像C.. :)
它的作用是挂起/恢复 指定PID的所有线程
DWORD __stdcall SetThreadState( DWORD pid ,BOOL lParam){
HANDLE hThreadSnap = NULL;
THREADENTRY32 te32;
memset(&te32,0,sizeof(te32));
te32.dwSize = sizeof(THREADENTRY32);
hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);
if (Thread32First(hThreadSnap, &te32)) {
do{
if (pid == te32.th32OwnerProcessID){
HANDLE hThread = OpenThread(
THREAD_SUSPEND_RESUME,
FALSE,
te32.th32ThreadID);
if(hThread != NULL){
if(lParam)
SuspendThread(hThread);
else
ResumeThread(hThread);
CloseHandle(hThread);
}
}
} while (Thread32Next(hThreadSnap,&te32));
}
CloseHandle( hThreadSnap );
return 0;
}
采用VS2003默认配置编译成Release版本后,拖入od,来到这个函数,看看(在分析的时候尽量不要去看源代码,如果实在不明白再去看也不会有什么损失):
/*401000*/ SUB ESP,1C // 这里一开始就分配了0x1c个字节,常见的push esp 、mov ebp,esp之类的指令消失了...
/*401003*/ XOR EAX,EAX
/*401005*/ MOV DWORD PTR SS:[ESP],EAX //后面的几个赋值都是通过esp做基址来偏移
/*401008*/ MOV DWORD PTR SS:[ESP+4],EAX //目前还不能确定它是一个数组还是一个结构
/*40100C*/ MOV DWORD PTR SS:[ESP+8],EAX
/*401010*/ MOV DWORD PTR SS:[ESP+C],EAX
/*401014*/ MOV DWORD PTR SS:[ESP+10],EAX
/*401018*/ PUSH EBX
/*401019*/ MOV DWORD PTR SS:[ESP+18],EAX // 想想这里为什么是0x18 ?
/*40101D*/ PUSH EAX
/*40101E*/ PUSH 4
/*401020*/ MOV DWORD PTR SS:[ESP+24],EAX
/*401024*/ MOV DWORD PTR SS:[ESP+C],1C
/*40102C*/ CALL <SuspendP._CreateToolhelp32Snapshot@8 004010c0 f kernel32:KERNEL32.dll>
/*401031*/ LEA ECX,DWORD PTR SS:[ESP+4] // 到这里看到Thread32First这个API后,
// 基本可以确定它是一个结构(参阅:Thread32First函数),这里取得结构起始地址初始化
/*401035*/ MOV EBX,EAX //API返回值给EBX
/*401037*/ PUSH ECX
/*401038*/ PUSH EBX
/*401039*/ CALL <SuspendP._Thread32First@8 004010cc f kernel32:KERNEL32.dll>
/*40103E*/ TEST EAX,EAX
/*401040*/ JE SHORT SuspendP.00401097
/*401042*/ PUSH EBP //此时只ESP“漂移”了4,为什么?想想API可都是__stdcall调用约定,当然除了个别的.
/*401043*/ MOV EBP,DWORD PTR SS:[ESP+2C] // 取第2个参数,2c-4(漂移量)还是超过了分配的内存大小,可以肯定它取的不是局部变量
/*401047*/ PUSH ESI // 计算公式为:当前地址=当前偏移量-"漂移量"-分配的内存大小-4(返回地址)
/*401048*/ PUSH EDI // 最后得到为8,esp+8就是第二参数了
/*401049*/ MOV EDI,DWORD PTR DS:[<&KERNEL32.OpenThread>]
/*40104F*/ NOP
/*401050*/ MOV EDX,DWORD PTR SS:[ESP+30] // 0x30-12-28(分配内存大小)=8,8-4(返回地址),即第一个参数
/*401054*/ CMP EDX,DWORD PTR SS:[ESP+1C] // 0x1c-漂移量(12)小于分配的内存大小,可以肯定取的是局部变量
/*401058*/ JNZ SHORT SuspendP.00401085 // 计算公式:当前地址=当前偏移量(0x1c)-漂移量(12)-4,额,
// 它取得是最开始那句MOV DWORD PTR SS:[ESP+C],EAX的数据
/*40105A*/ MOV EAX,DWORD PTR SS:[ESP+18] // 取内部数据MOV DWORD PTR SS:[ESP+8],EAX
/*40105E*/ PUSH EAX
/*40105F*/ PUSH 0
/*401061*/ PUSH 2
/*401063*/ CALL EDI
/*401065*/ MOV ESI,EAX
/*401067*/ TEST ESI,ESI
/*401069*/ JE SHORT SuspendP.00401085
/*40106B*/ TEST EBP,EBP
/*40106D*/ PUSH ESI
/*40106E*/ JE SHORT SuspendP.00401078
/*401070*/ CALL DWORD PTR DS:[<&KERNEL32.SuspendThread>]
/*401076*/ JMP SHORT SuspendP.0040107E
/*401078*/ CALL DWORD PTR DS:[<&KERNEL32.ResumeThread>]
/*40107E*/ PUSH ESI
/*40107F*/ CALL DWORD PTR DS:[<&KERNEL32.CloseHandle>]
/*401085*/ LEA ECX,DWORD PTR SS:[ESP+10] // 取内部数据,MOV DWORD PTR SS:[ESP],EAX
/*401089*/ PUSH ECX
/*40108A*/ PUSH EBX
/*40108B*/ CALL <SuspendP._Thread32Next@8 004010c6 f kernel32:KERNEL32.dll>
/*401090*/ TEST EAX,EAX
/*401092*/ JNZ SHORT SuspendP.00401050
/*401094*/ POP EDI
/*401095*/ POP ESI
/*401096*/ POP EBP
/*401097*/ PUSH EBX
/*401098*/ CALL DWORD PTR DS:[<&KERNEL32.CloseHandle>]
/*40109E*/ XOR EAX,EAX
/*4010A0*/ POP EBX
/*4010A1*/ ADD ESP,1C
/*4010A4*/ RETN 8[
本帖最后由 血腥魔术师 于 2008-6-29 22:50 编辑 ]