电子说
指令的汇编格式及功能根据条件码的值转移:
ja 大于时跳转
jae 大于等于
jb 小于
jbe 小于等于
je 相等
jna 不大于
jnae 不大于或者等于
jnb 不小于
jnbe 不小于或等于
jne 不等于
jg 大于(有符号)
jge 大于等于(有符号)
jl 小于(有符号)
jle 小于等于(有符号)
jng 不大于(有符号)
jnge 不大于等于(有符号)
jnl 不小于
jnle 不小于等于
jns 无符号
jnz 非零
js 如果带符号
jz 如果为零
例子:
mov eax, 1
cmp eax, 100
jle xiao_deng_yu_100
sub eax, 20
xiao_deng_yu_100:
add eax, 1
ret
这个例子中就是让eax中存储的值和100比较,如果小于等于则跳转到xiao_deng_yu_100处。 从这个例子也可以看出:
条件跳转指令这么多,怎么记住呢?这里面是有套路的: 首先,跳转指令的前面都是字母j,关键是j后面的的字母,比如j后面是ne,对应的是jne跳转指令,n和e分别对应not和equal,也就是“不相等”,也就是说在比较指令的结果为“不想等”的时候,就会跳转。
a: above
e: equal
b: below
n: not
g: greater
l: lower
s: signed
z: zero
好了,这里列出来了j后面的字母所对应的含义。根据这些字母的组合,和上述大概的规则,你就能清楚怎么写出这些跳转指令了。当然,这里有“有符号”和“无符号”之分,后面有机会再扯,读者也可以自行了解。
我们知道,在一个函数作用域中会使用到一些寄存器,如果在这个函数中又调用了另外一个函数,那这些寄存器信息可能会被覆盖掉,怎么办呢? 首先CPU会在内存中开辟一块空间,叫栈空间,CPU将这些寄存器压入到栈中,叫入栈,待另外一个函数返回时,再将当前栈中的信息恢复回来,叫出栈。
格式:push src
功能: 把数据src压入到栈中
注意:src可以是寄存器或者内存中的数据。
格式:pop dst
功能: 把数据弹出到到栈中
注意:dst可以是通用寄存器和段寄存器,但不能是CS,可以是字存储单元。
格式:call 标号
功能:跳转到另外一个函数去执行。
举例:
eax_plus_1s:
add eax, 1
ret
main:
mov eax, 0
call eax_plus_1s
ret
这里的eax_plus_1s就是一个函数,使用call跳转
call方法执行之前CPU还会做一个动作,就是将当前eip保存起来,然后再去跳转,这是为了函数执行完成后可以找到回来执行的地址。
好了。有了以上基础后,我们再来看C/C++中的hack环节。
那我们就拿前面的举得例子来讲解下hack过程
我们打开vs中输入下面一段代码:
int main()
{
int a = 0;
int b = ++a;
return 0;
}
如何反汇编呢, 在main的两个括号{}处打两个断点,然后执行程序,右击选择反汇编 。 得到的反汇编代码如下:
int main()
//段1:初始化
{
001947C0 push ebp
001947C1 mov ebp,esp
001947C3 sub esp,0D8h
001947C9 push ebx
001947CA push esi
001947CB push edi
001947CC lea edi,[ebp-18h]
001947CF mov ecx,6
001947D4 mov eax,0CCCCCCCCh
001947D9 rep stos dword ptr es:[edi]
001947DB mov ecx,offset _BCEF6C65_20221229-demo-cAndCpp@cpp (01A8052h)
001947E0 call @__CheckForDebuggerJustMyCode@4 (0173F1Eh)
//段2:
int a = 0;
001947E5 mov dword ptr [a],0
int b = ++a;
001947EC mov eax,dword ptr [a]
001947EF add eax,1
001947F2 mov dword ptr [a],eax
001947F5 mov ecx,dword ptr [a]
001947F8 mov dword ptr [b],ecx
return 0;
001947FB xor eax,eax
//段3:反初始化
}
001947FD pop edi
001947FE pop esi
001947FF pop ebx
00194800 add esp,0D8h
00194806 cmp ebp,esp
00194808 call __RTC_CheckEsp (0173AC8h)
0019480D mov esp,ebp
0019480F pop ebp
00194810 ret
这段汇编代码怎么读呢?由于main方法中没有其他函数调用,所以CPU会按顺序执行这段代码。 我们将代码分为3段: 段1:首先来看前面这段代码:
001947C0 push ebp
001947C1 mov ebp,esp
001947C3 sub esp,0D8h
001947C9 push ebx
001947CA push esi
001947CB push edi
001947CC lea edi,[ebp-18h]
001947CF mov ecx,6
001947D4 mov eax,0CCCCCCCCh
001947D9 rep stos dword ptr es:[edi]
001947DB mov ecx,offset _BCEF6C65_20221229-demo-cAndCpp@cpp (01A8052h)
001947E0 call @__CheckForDebuggerJustMyCode@4 (0173F1Eh)
这段代码不用管太多,只是对函数栈的一个初始化的操作,ebp指向栈底,esp指向栈底,还有一些就是当前栈的基地址,返回地址等等信息。因为函数中可能还会调用函数,所以每个函数调用都会有自己的栈,也叫函数的栈帧,函数的深度就代表栈深,但是CPU中的寄存器又是共享的,函数a在使用这些寄存器的时候,函数b又要使用了,那怎么办?方法就是 将寄存器中的数值压入栈帧中保存起来,等函数b结束后,再从栈帧中恢复起来就好了 。
下面看重点代码:
段2:
int a = 0;
001947E5 mov dword ptr [a],0 //1
int b = ++a;
001947EC mov eax,dword ptr [a] //2
001947EF add eax,1 //3
001947F2 mov dword ptr [a],eax //4
001947F5 mov ecx,dword ptr [a] //5
001947F8 mov dword ptr [b],ecx //6
return 0;
001947FB xor eax,eax //7
分析:
以上过程需要注意点:
a的值变化是在b的值变化之前,所以++a是先a+1,然后再将a+1赋值给b。
下面我们再来看下这段代码:
int main()
{
int a = 0;
int b = a++;
return 0;
}
反汇编后:同样找到关键代码:
int a = 0;
00AB47E5 mov dword ptr [a],0 //1
int b = a++;
00AB47EC mov eax,dword ptr [a] //2
00AB47EF mov dword ptr [b],eax //3
00AB47F2 mov ecx,dword ptr [a] //4
00AB47F5 add ecx,1 //5
00AB47F8 mov dword ptr [a],ecx //6
return 0;
00AB47FB xor eax,eax
}
分析:
这个过程可以看到CPU是先赋值给b,然后再让a进行+1的操作。
通过对a++和++a的分析,也可以看到hack可以让我们了解底层是如何执行我们写的代码的。
下面我们再来看一个函数调用过程:
int maxab(int a,int b) {
return a > b ? a : b;
}
int main()
{
maxab(3, 4);
return 0;
}
反汇编后:
maxab(3, 4);
00A347E5 push 4 //1
00A347E7 push 3 //2
00A347E9 call maxab (0A13794h) //3
00A347EE add esp,8 //4
int maxab(int a,int b) {
return a > b ? a : b;
00A32685 mov eax,dword ptr [a] //5
00A32688 cmp eax,dword ptr [b] //6
00A3268B jle __$EncStackInitStart+2Ch (0A32698h) //7
00A3268D mov ecx,dword ptr [a] //8
00A32690 mov dword ptr [ebp-0C4h],ecx //9
00A32696 jmp __$EncStackInitStart+35h (0A326A1h) //10
00A32698 mov edx,dword ptr [b] //11
00A3269B mov dword ptr [ebp-0C4h],edx //12
00A326A1 mov eax,dword ptr [ebp-0C4h] //13
}
分析:
上面讲解了一个max函数的hack过程,如果你对hack过程还比较模糊,还可以使用vs提供的 内存监视器查看具体内存变化 :
好了,关于函数的hack分析过程就这里了,大部分操作其实还是要理解汇编一些基本指令以及CPU和内存,栈的模型等。
本篇文章主要讲解了hack的定义以及hack过程中需要了解的几个基本知识: 如 寄存器,内存,CPU以及一些基本汇编指令 。并使用两个例子来讲解如何在C/C++中进行hack的过程。
全部0条评论
快来发表一下你的评论吧 !