[系统安全12]反汇编-if-else,三目运算符

C语言if-else与三目运算符解析
本文详细解析了C语言中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

【源码免费下载链接】:https://renmaiwang.cn/s/godad 在IT行业中,提升工作效能并减少失误的发生是采用自动化部署的关键手段之一。作为一种流行的自动化运维工具,Ansible广泛应用于服务器配置、应用程序部署以及系统管理等方面。本文将深入探讨如何通过`pip`进行离线安装,并完成Ansible及其依赖包的安装过程。理解其核心功能至关重要:Ansipher作为一个基于Python的开源平台,能够通过SSH连接到目标主机,无需在目标机器上安装代理即可实现远程任务执行、配置管理和应用部署。由于其依赖特定的Python库,确保这些组件能在目标系统中可用是必要的前提条件。离线安装场景通常发生在网络受限或安全要求较高的环境中,在这种情况下无法直接使用`pip`进行在线安装,因此需要在有网络连接时下载所有必要组件,并将文件打包成一个压缩包如“pipfiles”。“pipfiles”通常包含所有必需的Python包文件,这些资源是在预先下载的情况下创建的。具体步骤包括:首先下载并解压依赖包到临时目录;其次,在该目录中运行`pip install --no-index --find-links=.<包路径> <包名称>`以完成安装过程;最后通过`ansible --version`命令验证安装是否成功。在实际操作过程中,可能会遇到版本兼容性、依赖关系冲突以及文件损坏等问题,这些问题需要借助对Python环境和包管理系统的深入理解来解决。综上所述,离线安装Ansipher涉及对复杂技术栈的理解、资源管理和无网络环境下的操作能力。通过这一过程,我们可以有效克服网络限制,在各种环境中灵活应用Ansipher的自动化能力。
【源码免费下载链接】:https://renmaiwang.cn/s/6ourb 该Android项目源代码基于美团移动应用的UI界面进行复制,旨在为开发者提供学习与参考的实践平台。源码中包含多个核心页面的功能实现,包括无密码快速登录、个人中心、发现页等关键模块。其中,无密码快速登录功能通常采用手机号或第三方账号授权的方式简化用户登录流程,并可能集成微信及其官方提供的Quickpane组件(QCP)和QQ的公共接口(Public API)以实现身份验证。个人中心页面主要为用户提供个人信息管理界面,包括头像设置、订单查询等功能。在实现过程中,开发者可能使用RecyclerView展示列表数据,并结合Intent处理点击事件,跳转至相应详情页。此外,项目还涉及网络请求技术,例如Retrofit或Volley库的使用以更新用户信息等。发现页面则为用户提供新鲜内容浏览区域,在功能实现上可能包含推荐算法和动态内容加载机制。为了提升用户体验,项目采用PullToRefresh与LoadMore组件配合实现上拉刷新与下拉加载更多功能。App首页是用户进入应用后的初始展示界面,通常包括导航栏、轮播图等元素,并通过ViewPager实现滑动切换效果。团购页面则需要展示各类商品信息,可能包含商品图片、价格对比及评价展示模块,其中每个商品详情页可能嵌套多种组件以呈现详细信息。为了提高数据加载效率,项目采用Paging库分页技术进行数据分发。商家列表页面根据地理位置与评分等条件为用户提供排序功能,并结合SQLite数据库实现数据存储与检索,同时通过Google Maps API展示地图信息以便用户查看附近商家位置。该实践平台虽然未覆盖所有功能模块,但在Android开发中涵盖了多个核心技术点,包括UI设计、数据管理、网络通信、页面跳转及用户体验优化等内容。对于初学者而言,这是一个难得的学习机会,可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值