【黑客免杀攻防】读书笔记8 - 软件逆向工程基础2(if-else,三目运算符)
0x1 if-else分支
if-else分支4种状态
1.1 以常量为判断条件的简单if-else分支
C源代码:
单层if-else判断,常量为判断条件
int _tmain(int argc, _TCHAR* argv[])
{
int nTest = 1;
if (nTest>0)
printf("Hello world!\r\n");
else
printf("Hello everybody!\r\n");
return 0;
}
if-else分支用的都是反比,Debug版本的if-else分支特征如下:
010170DE mov ?????,????? ;赋值到寄存器,然后地址与真假值对比
010170E5 cmp ?????,0x0 ;比较数值
010170E9 jle X9-7.010170FA ;比较后的跳转,如果不执行分支1就执行分支2的位置
........... ;进行一定操作的分支1
010170F8 jmp X9-7.01017107 ;分支1执行完毕后,无条件跳转到结束位置,结束判断
010170FA push 9-7.010A5E64 ;进行一定操作的分支2
010170FF call 9-7.01013CE7 ;进行一定操作的分支2
01017104 add esp,0x4
01017107 xor eax,eax
先把值存放到寄存器中,cmp对比数值,跳转的方式不一定是JLE,也可能是JXX。如果执行完分支1(if)内容后,无条件跳转到结束的位置。如果没有执行分支1,那会跳转到分支2的位置,执行完后顺序执行到结束的位置。
1.2 以变量为判断条件的简单if-else分支
C源代码:
单层if-else判断,变量为判断条件
int _tmain(int argc, _TCHAR* argv[])
{
if (argc > 0)
printf("Hello world!\r\n");
else
printf("Hello everybody!\r\n");
return 0;
}
反汇编代码:
cmp ?????,????? ;比较数值
jxx AAAAAA ;比较方式
.....some code ;分支一
retn
AAAAAA ; JXX后面的地址
.....some code ; 分支二
retn
release版为每一个分支后面都添加上了结束代码。
编译器这样做其实仅增加了一个字节的体积,但是减少了一个跳转指令。
1.3 以常量为判断条件的复杂if-else分支
C源代码:
两个连续的if-else判断,常量为判断条件
int _tmain(int argc, _TCHAR* argv[])
{
int nTest = 1;
if (nTest>0) // 第一个if-else
printf("Hello!\r\n");
else
printf("Hello everybody!\r\n");
if (nTest>0) // 第二个if-else
printf("World!\r\n");
else
printf("Hello everybody!\r\n");
printf("End!\r\n");
return 0;
}
Debug版本汇编:
00B170DE mov XXXXX,XXXXX ;将数值移动到堆栈中
00B170E5 cmp XXXXX,XXXXX ;比较数值
00B170E9 jle X9-9.00B170FA ;跳转
....A分支1行为
00B170F8 jmp X9-9.00B17107 ;如果分支1行为执行了就跳过A分支2的行为
00B170FA ....A分支2行为
00B17107 cmp XXXXX,XXXXX
00B1710B jle X9-9.00B1711C
....B分支1行为
00B1711A jmp X9-9.00B17129 ;无条件跳转,越过B分支2行为
00B1711C ....B分支2行为
00B17129 push 9-9.00BA5E80 ;ASCII "End!
00B1712E call 9-9.00B13CE7 ;printf函数
00B17133 add esp,0x4
00B17136 xor eax,eax
release版生成的代码:
不可达的分支都有编辑器初期给剪掉了,VS2015跟书上的例子不一样。进去Main()函数后直接就是输出常量的值,然后调用printf函数了。。
00A71000 push 9-9.00A86448 ; format = "Hello! 字符串
00A71005 call 9-9.printf ; printf函数
00A7100A push 9-9.00A86454 ; format = "World! 字符串
00A7100F call 9-9.printf ; printf
00A71014 push 9-9.00A86460 ; format = "End! 字符串
00A71019 call 9-9.printf ; printf
00A7101E add esp,0x
00A71021 xor eax,ea
00A71023 retn
1.4 以变量为判断条件的复杂if-else分支
C源代码:
两个连续的if-else判断,变量为判断条件
int _tmain(int argc, _TCHAR* argv[])
{
if (argc>0)
{
if (argc == 1)
printf("Hello!\r\n");
else
printf("Hello everybody!\r\n");
}
else
{
if (argc == 1)
printf("World!\r\n");
else
printf("Hello everybody!\r\n");
}
return 0;
}
Debug版本汇编:
00E470DE cmp [arg.1],0x0
00E470E2 jle X9-10.00E47108 ; 最外层的if分支
00E470E4 cmp [arg.1],0x1
00E470E8 jnz X9-10.00E470F9 ; 内层第一个if分支
00E470EA push 9-10.00ED5E50 ;
00E470EF call 9-10.00E43CE7 ; 调用printf函数输出"Hello!
00E470F4 add esp,0x4
00E470F7 jmp X9-10.00E47106
00E470F9 push 9-10.00ED5E5C ; 内层第一个else分支内容开始
00E470FE call 9-10.00E43CE7 ; 调用printf函数输出"Hello everybody!
00E47103 add esp,0x4
00E47106 jmp X9-10.00E4712A
00E47108 cmp [arg.1],0x1 ; 最外层的else分支
00E4710C jnz X9-10.00E4711D ; 内层第二个if分支
00E4710E push 9-10.00ED5E74 ;
00E47113 call 9-10.00E43CE7 ; 调用printf函数输出 "World!
00E47118 add esp,0x4
00E4711B jmp X9-10.00E4712A
00E4711D push 9-10.00ED5E5C ; 内层第二个else分支
00E47122 call 9-10.00E43CE7 ; 调用printf函数输出"Hello everybody!
00E47127 add esp,0x4
00E4712A xor eax,eax
release版生成的代码:
直接就是变量当成常量输出了,所有的流程只剩下会被执行的一部分汇编代码。
00C91000 push ebp
00C91001 mov ebp,esp
00C91003 mov eax,[arg.1]
00C91006 test eax,eax
00C91008 jle X9-10.00C91020 ; 最外层的if-else分支
00C9100A cmp eax,0x1
00C9100D jnz X9-10.00C91020 ; 内层第一个if-else分支
00C9100F push 9-10.00CA6448 ;
00C91014 call 9-10.printf ; 调用printf函数输出"Hello!
00C91019 add esp,0x4
00C9101C xor eax,eax
00C9101E pop ebp
00C9101F retn
00C91020 push 9-10.00CA6454 ;
00C91025 call 9-10.printf ; 调用printf函数输出"Hello everybody!
00C9102A add esp,0x4
00C9102D xor eax,eax
00C9102F pop ebp
00C91030 retn
1.5 识别三目运算符
1. 有序常量的三目运算
C源代码:
int _tmain(int argc, _TCHAR* argv[])
{
return argc==1 ? 2:3;
}
Debug反汇编特征:
Debug下的汇编与常见的if-else无异。
0039773B cmp dword ptr [ebp+0x8], 0x1 ; 比较
0039773F jnz short 0039774D ; 分支跳转
00397741 mov dword ptr [ebp-0xC4], 0x2; 分支1内容:赋值2
0039774B jmp short 00397757 ; 无条件跳转:执行完分支1,就不在执行分支2
0039774D mov dword ptr [ebp-0xC4], 0x3; 分支2内容:赋值3
00397757 mov eax, dword ptr [ebp-0xC4]
0039775D pop edi ; 9-11.<ModuleEntryPoint>
0039775E pop esi
Relese反汇编特征:
00121272 cmp dword ptr [ebp+0x8], 0x1; 比较
00121276 setne al ; 判断ZF标志位,判断是否对al赋值
00121279 add eax, 0x2 ; 将eax与2相加
0012127C pop ebp
setne这个指令含义为:
if ZF=1 then cl=0
if ZF=0 then cl=1
程序最后的返回结果只能有两种,即2与3,而setne指令则会根据ZF位的影响来决定是给al(也可以理解为eax)赋值1还是0。
如果比较相等,则eax的值会被置为0,加上2之后正好返回2;而如果不等的话自然就会返回3了。
2. 稍复杂的有序常量的三目运算
C源代码:
int _tmain(int argc, _TCHAR* argv[])
{
printf("11111111111111");
return argc == 1 ? 6:8;
}
Relese反汇编指令:
00281272 cmp dword ptr [ebp+0x8], 0x1
00281276 setne al
00281279 lea eax, dword ptr [eax*2+0x6]
00281280 pop ebp
三目运算只可能等于6或8,因此编译器用了一个lea指令对其进行加法运算。
如果条件为假,那么eax里的值为0,执行完lea指令后的最终结果为6;
如果条件为真,那么eax里的值为1,执行完lea指令后的最终结果为8;
有序常量的三目运算符特征如下:
xor reg32, reg32 ;任意可用的寄存器,但两个操作数所用的寄存器必须相同
cmp dword ptr [ebp+0x8],?? ;比较对比数与参照数
setne reg8 ;XOR中所用到的寄存器(或其低位寄存器)
3. 无序常量的三目运算
C源代码:
int _tmain(int argc, _TCHAR* argv[])
{
printf("1111111111111111111111111");
return argc == 1 ? 68 : 6;
}
Relese反汇编指令:
00C61270 mov eax, 0x6 ;将参数传递给eax
00C61275 cmp dword ptr [ebp+0x8], 0x1;对比
00C61279 mov ecx, 0x44
00C6127E cmove eax, ecx ;等于0的时候传送
我用VS2015生成的代码和书上的反汇编指令是不一样的。
cmove指令的含义是:
cmove S, D //等于0时传送
返回值会是6或68,先把6的值传到eax寄存器中。cmp对比后,下一条指令是把68的值传到ecx寄存器里。
如果等于0,就用ecx寄存器值传送到eax寄存器中,否则不传送。
4. 值为变量的三目运算
C源代码:
int _tmain(int argc, _TCHAR* argv[])
{
printf("111111");
return argc == 1 ? 6:(int)argv;
}
Relese反汇编指令:
00AC126D mov eax, dword ptr [ebp+0xC]; 将局部变量保存到eax中
00AC1270 add esp, 0x4 ; 平衡堆栈
00AC1273 cmp dword ptr [ebp+0x8], 0x1; 比较数值是否等于1
00AC1277 mov ecx, 0x6 ; 将分支1内容赋值到ecx
00AC127C cmove eax, ecx ; 根据CMP影响的标志位结果判断是否传送
00AC127F pop ebp
00AC1280 retn
在以上C语言程序例子我用VS2015编译后,OD反汇编出来的指令与书上的指令还是不一样的。与无序常量的三目运算反汇编后的指令差不多。
这说明VS版本编译器更新后,编译优化做了很大的改动。接下来的日子还是要通过不断地练习,才能让自己的水平真正的见长起来。
小结:
编译器之所以这样做是因为每执行一次寄存器赋值操作要比偶尔一次的流程转移操作更加高效,需要付出的代价更小。
0x2 参考
《黑客免杀攻防》 软件逆向工程(4)
http://blog.csdn.net/dalerkd/article/details/41251595
【黑客免杀攻防】读书笔记8 - 软件逆向工程基础2(if-else,三目运算符)的更多相关文章
- 【黑客免杀攻防】读书笔记7 - 软件逆向工程基础1(函数调用约定、Main函数查找)
0x1 准备工作 1.1.准备工具 IDA:交互式反汇编工具 OllyDbg:用户层调试工具 Visual Studio:微软开发工具 1.2.基础知识 C++开发 汇编语言 0x2 查找真正的mai ...
- 【黑客免杀攻防】读书笔记5 - PE格式讲解
0x01 MS-DOS头 MS-DOS头部的字段重点关注e_magic与最后一个e_lfanew是需要关注的. 第一个e_magic字段的值为4D5A,作用是可以作为判断这个文件是否是PE文件. 最后 ...
- 【黑客免杀攻防】读书笔记6 - PE文件知识在免杀中的应用
0x1 PE文件与免杀思路 基于PE文件结构知识的免杀技术主要用于对抗启发式扫描. 通过修改PE文件中的一些关键点来达到欺骗反病毒软件的目的. 修改区段名 1.1 移动PE文件头位置免杀 工具:PeC ...
- 【黑客免杀攻防】读书笔记18-最终章Anti Rootkit
1.免杀技巧的遏制 1.1.PE文件 入口点不在第一个区段或在最后一个区段 入口点处代码附近只有一小段代码 入口点在正常范围之外 入口点为一个无效的值,实际入口点为TLS的入口点 区段名重复或者不属于 ...
- 【黑客免杀攻防】读书笔记2 - 免杀与特征码、其他免杀技术、PE进阶介绍
第3章 免杀与特征码 这一章主要讲了一些操作过程.介绍了MyCCL脚本木马免杀的操作,对于定位特征码在FreeBuf也曾发表过类似工具. VirTest5.0特征码定位器 http://www.fre ...
- 【黑客免杀攻防】读书笔记15 - 源码免杀、C++壳的编写
1.源码免杀 1.1 定位产生特征的源码 定位文件特征 1.根据MyCCL的特征码定位工具,定位出有特征的地址 2.根据VS的反汇编窗口,输入有特征的地址得到特征地址与源码的关系 3.插入Messag ...
- 【黑客免杀攻防】读书笔记10 - switch-case分支
0x1 switch-case分支 switch-case其实就是if-else语句的另一种体现形式.但大于3之后的switchc-case.编译器会对代码进行优化. 1.1 简单switch-cas ...
- 【黑客免杀攻防】读书笔记17 - Rootkit基础
1.构建Rootkit基础环境 1.1.构建开发环境 VS2012+WDK8 1.2.构建基于VS2012的调试环境 将目标机.调试机配置在同一个工作组内 sVS2012配置->DRIVER-& ...
- 【黑客免杀攻防】读书笔记14 - 面向对象逆向-虚函数、MFC逆向
虚函数存在是为了克服类型域解决方案的缺陷,以使程序员可以在基类里声明一些能够在各个派生类里重新定义的函数. 1 识别简单的虚函数 代码示例: #include "stdafx.h" ...
随机推荐
- 机器学习工作流程第一步:如何用Python做数据准备?
这篇的内容是一系列针对在Python中从零开始运用机器学习能力工作流的辅导第一部分,覆盖了从小组开始的算法编程和其他相关工具.最终会成为一套手工制成的机器语言工作包.这次的内容会首先从数据准备开始. ...
- emwin 之变量定义位置
@2018-08-13 小记 本意是想在回调函数中定义一变量暂存下拉框操作前的的设定值,与后期更改的设定值作比较后更新操作,但结果失败了 分析后,此变量定义为局部变量,emwin回调函数又是事件触发型 ...
- Android设置RadioButton在文字的右边
效果图如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro ...
- 【洛谷P4097】Segment 李超线段树
题目大意:维护一个二维平面,给定若干条线段,支持询问任意整数横坐标处对应的纵坐标最靠上的线段的 id,相同高度取 id 值较小的,强制在线. 题解:初步学习了李超线段树.李超线段树的核心思想在于通过标 ...
- Access,MSSQL:随机读取N条记录
今天试着将一个网站使用的mssql转换为Access,但网站首页有一段代码是随机读取n条记录: SQL Server:Select TOP N * From TABLE Order By NewID( ...
- IDEA集成有道翻译插件/maven帮助插件/mybatis插件
(一)IDEA集成有道翻译插件:https://www.cnblogs.com/a8457013/p/7814335.html 插件下载地址:http://plugins.jetbrains.com/ ...
- C++11并发——多线程lock_gurad ,unique_lock (三)
http://www.cnblogs.com/haippy/p/3346477.html struct defer_lock_t {}; 该类型的常量对象 defer_lock(defer_lock ...
- Docker从入门到飞升:基础配置安装
导读 Docker近几年非常火,因为它是容器虚拟化,更能够充分提高硬件资源的使用率.其实利用率高不算什么,它最大的优势是能给让运维人员或者开发人员快速部署和交付资源,大大提高了工作效率.几乎所有的大企 ...
- Swing的特性
1.Swing组件的多样化 2.MVC(model-view-controller)体系结构 3.可存取性支持 4.支持键盘代替鼠标的操作 5.设置边框 6.使用图标 ———————————————— ...
- Python基础【day02】:元组和购物车练习的知识点
一.元组 元组其实跟列表差不多,也是存一组数,只不是它一旦创建,便不能再修改,所以又叫只读列表 用途:一般情况下用于自己写的程序能存下数据,但是又希望这些数据不会被改变,比如:数据库连接信息等 1.元 ...