Windows逆向安全(一)之基础知识(八)
if else嵌套
这次来研究if else嵌套在汇编中的表现形式,本次以获取三个数中最大的数这个函数为例子,分析if else的汇编形式
求三个数中的最大值
首先贴上代码:
#include "stdafx.h"
int result=0;
int getMax(int i,int j,int k){
if(i>j){
if(i>k){
return i;
}else{
return k;
}
}else{
if(j>k){
return j;
}else{
return k;
}
}
}
int main(int argc, char* argv[])
{
result=getMax(1,2,3);
printf("%d\n",result);
result=getMax(1,3,2);
printf("%d\n",result);
result=getMax(2,1,3);
printf("%d\n",result);
result=getMax(2,3,1);
printf("%d\n",result);
result=getMax(3,1,2);
printf("%d\n",result);
result=getMax(3,2,1);
printf("%d\n",result);
return 0;
}
先验证执行的结果是正确的:
确认可以函数是可以取出三个数的最大值的,于是开始分析该函数
为方便观看,将多余的验证删去,直接改为
getMax(1,2,3);
汇编代码
然后我们观察汇编代码
函数外部
28: getMax(1,2,3);
0040D7C8 push 3
0040D7CA push 2
0040D7CC push 1
0040D7CE call @ILT+10(func) (0040100f)
0040D7D3 add esp,0Ch
依次压入参数,然后调用函数,最后再堆栈外平衡,重点在函数内部,进去看看
函数内部
7: int getMax(int i,int j,int k){
0040D760 push ebp
0040D761 mov ebp,esp
0040D763 sub esp,40h
0040D766 push ebx
0040D767 push esi
0040D768 push edi
0040D769 lea edi,[ebp-40h]
0040D76C mov ecx,10h
0040D771 mov eax,0CCCCCCCCh
0040D776 rep stos dword ptr [edi]
8: if(i>j){
0040D778 mov eax,dword ptr [ebp+8]
0040D77B cmp eax,dword ptr [ebp+0Ch]
0040D77E jle getMax+32h (0040d792)
9: if(i>k){
0040D780 mov ecx,dword ptr [ebp+8]
0040D783 cmp ecx,dword ptr [ebp+10h]
0040D786 jle getMax+2Dh (0040d78d)
10: return i;
0040D788 mov eax,dword ptr [ebp+8]
0040D78B jmp getMax+42h (0040d7a2)
11: }else{
12: return k;
0040D78D mov eax,dword ptr [ebp+10h]
0040D790 jmp getMax+42h (0040d7a2)
13: }
14:
15: }else{
16: if(j>k){
0040D792 mov edx,dword ptr [ebp+0Ch]
0040D795 cmp edx,dword ptr [ebp+10h]
0040D798 jle getMax+3Fh (0040d79f)
17: return j;
0040D79A mov eax,dword ptr [ebp+0Ch]
0040D79D jmp getMax+42h (0040d7a2)
18: }else{
19: return k;
0040D79F mov eax,dword ptr [ebp+10h]
20: }
21: }
22: }
0040D7A2 pop edi
0040D7A3 pop esi
0040D7A4 pop ebx
0040D7A5 mov esp,ebp
0040D7A7 pop ebp
0040D7A8 ret
函数内部有不少代码是用来保护现场 初始化堆栈 恢复现场的,这里将其过滤掉,看判断语句:
判断语句
8: if(i>j){
0040D778 mov eax,dword ptr [ebp+8]
0040D77B cmp eax,dword ptr [ebp+0Ch]
0040D77E jle getMax+32h (0040d792)
9: if(i>k){
0040D780 mov ecx,dword ptr [ebp+8]
0040D783 cmp ecx,dword ptr [ebp+10h]
0040D786 jle getMax+2Dh (0040d78d)
10: return i;
0040D788 mov eax,dword ptr [ebp+8]
0040D78B jmp getMax+42h (0040d7a2)
11: }else{
12: return k;
0040D78D mov eax,dword ptr [ebp+10h]
0040D790 jmp getMax+42h (0040d7a2)
13: }
14:
15: }else{
16: if(j>k){
0040D792 mov edx,dword ptr [ebp+0Ch]
0040D795 cmp edx,dword ptr [ebp+10h]
0040D798 jle getMax+3Fh (0040d79f)
17: return j;
0040D79A mov eax,dword ptr [ebp+0Ch]
0040D79D jmp getMax+42h (0040d7a2)
18: }else{
19: return k;
0040D79F mov eax,dword ptr [ebp+10h]
20: }
21: }
22: }
参数分析
i>j
先来看看i>j的反汇编语句
0040D778 mov eax,dword ptr [ebp+8]
0040D77B cmp eax,dword ptr [ebp+0Ch]
0040D77E jle getMax+32h (0040d792)
比较第一个参数和第二个参数
jle:jump less equal,小于等于则跳转(有符号数)
跳转地址:0040d792
16: if(j>k){
0040D792 mov edx,dword ptr [ebp+0Ch]
i>k
9: if(i>k){
0040D780 mov ecx,dword ptr [ebp+8]
0040D783 cmp ecx,dword ptr [ebp+10h]
0040D786 jle getMax+2Dh (0040d78d)
比较第一个和第三个参数
jle:jump less equal,小于等于则跳转(有符号数)
跳转地址:0040d78d
11: }else{
12: return k;
0040D78D mov eax,dword ptr [ebp+10h]
0040D790 jmp getMax+42h (0040d7a2)
可以分析出,如果第一个参数小于等于第三个参数则跳转到0040D78D,并将第三个参数赋值给eax作为返回值,这条线路为(k>i>j)
否则执行返回指令,将第一个参数赋给eax作为返回值,这条线路为(i>j且i>k)
10: return i;
0040D788 mov eax,dword ptr [ebp+8]
0040D78B jmp getMax+42h (0040d7a2)
j>k
16: if(j>k){
0040D792 mov edx,dword ptr [ebp+0Ch]
0040D795 cmp edx,dword ptr [ebp+10h]
0040D798 jle getMax+3Fh (0040d79f)
比较第二个和第三个参数
jle:jump less equal,小于等于则跳转(有符号数)
跳转地址:0040d79f
18: }else{
19: return k;
0040D79F mov eax,dword ptr [ebp+10h]
可以分析出,如果第二个参数小于等于第三个参数则跳转到0040D79F,并将第三个参数赋值给eax作为返回值,这条线路为(i<=j<=k)
否则返回执行返回命令,将第二个参数赋值给eax作为返回值,这条线路为(i<=j且k<=j)
17: return j;
0040D79A mov eax,dword ptr [ebp+0Ch]
0040D79D jmp getMax+42h (0040d7a2)
总结
不难发现,三个数求最大值,只需两两比较就可以得出结果
分析if else的关键在于观察涉及的参数和jcc语句
此案例中就是直接采取了cmp 外加 jle来进行分支的选择和跳转
因为不符合条件的才要跳转走,所以在条件比较中,是大于的比较如i>j,所使用的汇编为jle 小于等于的比较
不按套路比较
此次案例并不能代表所有情况,实际分析要具体看情况来采取分析,有的程序可能就是不按套路出牌,先看看按套路出牌的程序,然后我们自己来模拟个不按套路的
正常套路
拿两个数的比较为例
#include "stdafx.h"
int getMax2(int i,int j){
if(i>j){
return i;
}else{
return j;
}
}
int main(int argc, char* argv[])
{
getMax2(1,2);
return 0;
}
先看一般的汇编代码:
9: if(i>j){
0040D778 mov eax,dword ptr [ebp+8]
0040D77B cmp eax,dword ptr [ebp+0Ch]
0040D77E jle getMax2+25h (0040d785)
10: return i;
0040D780 mov eax,dword ptr [ebp+8]
0040D783 jmp getMax2+28h (0040d788)
11: }else{
12: return j;
0040D785 mov eax,dword ptr [ebp+0Ch]
13: }
14: }
依旧是采用cmp 和 jle来进行判断,和套路一致
不按套路
完整代码
#include "stdafx.h"
int __declspec(naked) myGetMax(int i,int j){
__asm{
//保留调用前堆栈
push ebp
//提升堆栈
mov ebp,esp
sub esp,0x40
//保护现场
push ebx
push esi
push edi
//初始化提升的堆栈,填充缓冲区
mov eax,0xCCCCCCCC
mov ecx,0x10
lea edi,dword ptr ds:[ebp-0x40]
rep stosd
//函数核心功能
//取出参数
mov eax,dword ptr ds:[ebp+8]
//比较参数
cmp eax,[ebp+0xC]
jge _ret
mov eax,[ebp+0xC]
_ret:
//恢复现场
pop edi
pop esi
pop ebx
//降低堆栈
mov esp,ebp
pop ebp
//返回
ret
}
}
int main(int argc, char* argv[])
{
int result=myGetMax(1,2);
printf("%d\n",result);
result=myGetMax(4,3);
printf("%d\n",result);
return 0;
}
功能代码分析
这里截取出我们自己实现比较的那段代码
//函数核心功能
//取出参数
mov eax,dword ptr ds:[ebp+8]
//比较参数
cmp eax,[ebp+0xC]
jge _ret
mov eax,[ebp+0xC]
_ret:
//恢复现场
pop edi
pop esi
pop ebx
//降低堆栈
mov esp,ebp
pop ebp
//返回
ret
首先我们这里将第一个参数赋值给eax
然后比较eax和第二个参数,也就是比较第一个参数和第二个参数
这边使用的就不是jle而是jge了
jge:jump greater equal,即大于等于则跳转
前面已经将第一个参数赋值给了eax,而eax又是作为返回值来传递的
当第一个参数大于等于第二个参数时,就可以直接返回了
如果不是则不跳转,执行下面的将第二个参数赋值给eax作为返回值
这里注意到我在汇编中自己定义了一个段:_ret,来作为跳转的地址来使用
最后测试一下结果:
可以正确得到两个数中的最大值