好友
阅读权限20
听众
最后登录1970-1-1
|
鱼无论次
发表于 2017-4-14 19:13
本帖最后由 鱼无论次 于 2017-4-14 21:42 编辑
把自己最近的读书笔记与学习心得分享总结,希望各位看官能有所收获吧。
写的有点乱,如果有错误请指正。
不知道为什么我感觉看代码是歪的,可能是我从oneNote复制到有道笔记再复制过来
参考书籍:
C++反汇编与逆向技术
黑客免杀攻防
天书夜谈
需要用到的工具:
IDA
OD
编译环境:VS2013 Debug版
首先附上跳转表:
主要内容(不分先后顺序)
1.if-else反汇编
2.for反汇编
3.do-while反汇编与while反汇编(阉割版for循环)
4.总结for、do-while、while最容易记的特征
5.switch-case的三种不同表现方式
1.先来个最简单if语言看下再汇编层是如何体现出来的
渣渣代码:
int nNumberA = 2,nNumberB = 3;
002813DE mov dword ptr [nNumberA],2
002813E5 mov dword ptr [nNumberB],3
if (nNumberA > nNumberB)
002813EC mov eax,dword ptr [nNumberA]
002813EF cmp eax,dword ptr [nNumberB] //cmp比较两个操作数做减法,不送回结果,只影响标志位
002813F2 jle wmain+4Bh (028140Bh) //我们发现汇编指令满足条件是小于等于则跳转,绕过代码块,好像跟我们写的条件相反。
{
printf("A>B\r\n");
002813F4 mov esi,esp
002813F6 push 285858h
002813FB call dword ptr ds:[289114h]
00281401 add esp,4 //cdecl调用方式在函数内没有任何平衡参数操作,而在退出函数后对esp执行加8操作
00281404 cmp esi,esp //VS自带的检查堆栈平衡
00281406 call __RTC_CheckEsp (0281140h) //这个是VS自带的检查堆栈平衡
}
我们来做个测试
if (nNumberA == nNumberB)
000313EC mov eax,dword ptr [nNumberA]
000313EF cmp eax,dword ptr [nNumberB]
000313F2 jne wmain+4Bh (03140Bh) //jnz(不等于)汇编翻译的跳转是跳出去的条件,好像与我们处处作对一样
总结:
汇编翻译的跳转指令是跳出代码的条件,与你写的条件完全相反。希望各位以后看到汇编翻译的是 jne ,那么你写的条件就是等于
1.2继续if-else if-else代码的反汇编,这里我把VS的一些检查堆栈代码给删掉了
上渣渣代码:
int nNumberA = 5;
00B41A1E mov dword ptr [nNumberA],5
if (nNumberA == 2)
00B41A25 cmp dword ptr [nNumberA],2
00B41A29 jne wmain+44h (0B41A44h) //不成立跳到下一个if判断
{
printf("nNumberA=2\r\n");
00B41A2B mov esi,esp
00B41A2D push 0B458B0h
00B41A32 call dword ptr ds:[0B49114h]
00B41A38 add esp,4 //cdecl调用方式在函数内没有任何平衡参数操作,而在退出函数后对esp执行加8操作
00B41A42 jmp wmain+7Ah (0B41A7Ah) //如果代码成立执行到这里直接jmp跳出全部判断语句外面
}
else if(nNumberA == 3)
00B41A44 cmp dword ptr [nNumberA],3
00B41A48 jne wmain+63h (0B41A63h) //不成立跳到下一个if判断
{
printf("nNumberA=3\r\n");
00B41A4A mov esi,esp
00B41A4C push 0B45920h
00B41A51 call dword ptr ds:[0B49114h]
00B41A57 add esp,4 //cdecl调用方式在函数内没有任何平衡参数操作,而在退出函数后对esp执行加8操作
00B41A61 jmp wmain+7Ah (0B41A7Ah) //如果代码成立执行到这里直接jmp跳出全部判断语句的外面
}
Else //不用判断了什么都不是就直接执行了
{
printf("都不是\r\n");
00B41A63 mov esi,esp
00B41A65 push 0B459CCh
00B41A6A call dword ptr ds:[0B49114h]
00B41A70 add esp,4
}
2.最强的for循环反汇编
int Sum = 0;
0085139E mov dword ptr [Sum],0
for (int i = 0; i < 5; i++)
/************* int i=0 ************/
008513A5 mov dword ptr ,0 //这句就是int i=0,只初始化一次,初始化完就没有利用价值了
008513AC jmp wmain+37h (08513B7h) //直接跳到判断语句也就是i<5的地方
/************** int i = 0 ************/
/************** i++ **************/
008513AE mov eax,dword ptr
008513B1 add eax,1
008513B4 mov dword ptr ,eax
/************** i++ **************/
/************** i<5 ***************/
008513B7 cmp dword ptr ,5
008513BB jge wmain+48h (08513C8h) //还记得我们的汇编翻译特征码?唱反调,不大于等于则跳出去循环外
/************** i<5 ***************/
008513B7 cmp dword ptr ,5
008513BB jge wmain+48h (08513C8h) //还记得我们的汇编翻译特征码?唱反调,不大于等于则跳出去循环外
{
Sum = Sum + i;
008513BD mov eax,dword ptr [Sum]
008513C0 add eax,dword ptr
008513C3 mov dword ptr [Sum],eax
008513C6 jmp wmain+2Eh (08513AEh) //执行完代码跳回去执行i++操作
}
return 0;
008513C8 xor eax,eax
我带画张图帮助大家理解,图画的很渣:
for循环硬生生把i++插到中间去,不是按照我们的思维放在i<5的后面
3.do-while循环与while循环(阉割版for循环)
while (Sum < 5)
008C13A5 cmp dword ptr [Sum],5
008C13A9 jge wmain+36h (08C13B6h) //一开始就判断
{
Sum++;
008C13AB mov eax,dword ptr [Sum]
008C13AE add eax,1
008C13B1 mov dword ptr [Sum],eax
}
008C13B4 jmp wmain+25h (08C13A5h) //无条件跳转指令回到循环开始的地方
do-while语句的特征,不管怎么样先执行一遍再说
do
{
Sum = Sum + 1;
00B013A5 mov eax,dword ptr [Sum]
00B013A8 add eax,1
00B013AB mov dword ptr [Sum],eax //先执行一遍循环体代码再说
} while (Sum<5);
00B013AE cmp dword ptr [Sum],5
00B013B2 jl wmain+25h (0B013A5h) //再判断
return 0;
00B013B4 xor eax,eax
4.总结特征
For有2个jmp
While循环有1个jmp
Do-while没有jmp
5.switch-case的三种不同表现方式
方式一:case<3
代码如下
int NnumA = 2;
00B339CE mov dword ptr [NnumA],2
switch (NnumA)
00B339D5 mov eax,dword ptr [NnumA]
00B339D8 mov dword ptr [ebp-0D0h],eax
00B339DE cmp dword ptr [ebp-0D0h],1 //我们发现switch语句的特点就是把判断全部放在前面
00B339E5 je wmain+42h (0B339F2h)
00B339E7 cmp dword ptr [ebp-0D0h],2
00B339EE je wmain+5Fh (0B33A0Fh)
00B339F0 jmp wmain+7Ah (0B33A2Ah)
{
case 1:
{
printf("%d", NnumA);
00B339F2 mov esi,esp
00B339F4 mov eax,dword ptr [NnumA]
00B339F7 push eax
00B339F8 push 0B358A8h
00B339FD call dword ptr ds:[0B39114h]
00B33A03 add esp,8
00B33A06 cmp esi,esp
00B33A08 call __RTC_CheckEsp (0B311D1h)
break;
00B33A0D jmp wmain+7Ah (0B33A2Ah) //跳出判断外面
}
case 2:
{
printf("%d", NnumA);
00B33A0F mov esi,esp
00B33A11 mov eax,dword ptr [NnumA]
00B33A14 push eax
00B33A15 push 0B358A8h
00B33A1A call dword ptr ds:[0B39114h]
00B33A20 add esp,8
00B33A23 cmp esi,esp
00B33A25 call __RTC_CheckEsp (0B311D1h)
break;
}
}
return 0;
00B33A2A xor eax,eax
问题:
那么我们就滋生出了一个问题:假如我有10甚至100个判断,程序前面就写100个判断语句吗?那么我们来实验下
类似:
Cmp eax,1
Jxxx xxxx
Cmp eax,2
Jxxx xxxx
Cmp eax,3
Jxxx xxxx
Cmp eax,4
……….
这就引出了switch算法的优化问题
方式2:那么就出现我们第二种方式case > 3 并且case < 256
计算机是很聪明的,会优化算法。索引地址是从0开始的,我写1-4是方便阅读。其实真正是0-3
这四个值刚好就是我们case的4个值得首地址
部分代码:
case 1:
{
printf("%d", NnumA);
010313BF mov eax,dword ptr [NnumA]
}
case 2:
{
printf("%d", NnumA);
010313D3 mov eax,dword ptr [NnumA]
}
case 3:
{
printf("%d", NnumA);
010313E7 mov eax,dword ptr [NnumA]
}
case 4:
{
printf("%d", NnumA);
010313FB mov eax,dword ptr [NnumA]
}
方式3:大于255就采用了平衡二叉树方式优化算法
假如我们case 1000、700、500、300、100平衡二叉树表现形式
然后我们再上代码:
switch (NnumA)
008913CC mov eax,dword ptr [NnumA]
008913CF mov dword ptr [ebp-4Ch],eax
008913D2 cmp dword ptr [ebp-4Ch],1F4h //来中间值500做中间值,判断是否大于500
008913D9 jg wmain+55h (08913F5h) //大于500就跳到8913F5H
008913DB cmp dword ptr [ebp-4Ch],1F4h //判断是否等于500
008913E2 je wmain+91h (0891431h)
008913E4 cmp dword ptr [ebp-4Ch],64h //判断是否等于100
008913E8 je wmain+0B9h (0891459h)
008913EA cmp dword ptr [ebp-4Ch],12Ch //判断是否等于300
008913F1 je wmain+0A5h (0891445h)
008913F3 jmp wmain+0CBh (089146Bh) //都不是就退出啦
008913F5 cmp dword ptr [ebp-4Ch],2BCh //刚才大于500就跳到这里来了,判断是否等于700
008913FC je wmain+7Dh (089141Dh)
008913FE cmp dword ptr [ebp-4Ch],3E8h //判断是否等于1000
00891405 je wmain+69h (0891409h)
00891407 jmp wmain+0CBh (089146Bh) //都找不到就退出
自己画的大致图,虽然不是很严谨:
IDA反汇编图:
致谢
感谢15PB老师们的辛勤栽培!
|
免费评分
-
查看全部评分
|