大神论坛

找回密码
快速注册
查看: 571 | 回复: 0

[语言编程类] 大神论坛 逆向脱壳分析基础学习笔记十三 汇编C语言类型...

主题

帖子

5

积分

初入江湖

UID
20
积分
5
精华
威望
10 点
违规
大神币
68 枚
注册时间
2021-03-14 10:40
发表于 2021-03-14 18:19
本帖最后由 kay2kay 于 2021-03-14 18:19 编辑

本文为本人的滴水逆向破解脱壳学习笔记之一,为本人对以往所学的回顾和总结,可能会有谬误之处,欢迎大家指出。
陆续将不断有笔记放出,希望能对想要入门的萌新有所帮助,一起进步


所有笔记链接:

大神论坛 逆向脱壳分析基础学习笔记一 进制篇
大神论坛 逆向脱壳分析基础学习笔记二 数据宽度和逻辑运算
大神论坛 逆向脱壳分析基础学习笔记三 通用寄存器和内存读写
大神论坛 逆向脱壳分析基础学习笔记四 堆栈篇
大神论坛 逆向脱壳分析基础学习笔记五 标志寄存器 
大神论坛 逆向脱壳分析基础学习笔记六 汇编跳转和比较指令
大神论坛 逆向脱壳分析基础学习笔记七 堆栈图(重点)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记八 反汇编分析C语言
大神论坛 逆向脱壳分析基础学习笔记九 C语言内联汇编和调用协定
大神论坛 逆向脱壳分析基础学习笔记十 汇编寻找C程序入口(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十一 汇编C语言基本类型
大神论坛 逆向脱壳分析基础学习笔记十二 汇编 全局和局部 变量(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十三 汇编C语言类型转换(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十四 汇编嵌套if else(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十五 汇编比较三种循环(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十六 汇编一维数组(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十七 汇编二维数组 位移 乘法(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十八 汇编 结构体和内存对齐(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记十九 汇编switch比较if else(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十 汇编 指针(一)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十一 汇编 指针(二)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十二 汇编 指针(三)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十三 汇编 指针(四)(需登录才能访问)
大神论坛 逆向脱壳分析基础学习笔记二十四 汇编 指针(五) 系列完结(需登录才能访问)

更多逆向脱壳资源,请访问  大神论坛

类型转换

前面我们已经在逆向基础笔记十一 汇编C语言基本类型中提到过类型转换这个概念,但在那里只是一笔带过,重点放在了比较大小上,类型转换自然涉及到C语言的基本类型有符号数和无符号数,如果有不熟悉的可以前往回顾,下面正式开始

类型转换的使用场景

类型转换一般为由数据宽度小的转换成数据宽度大的,不然可能会有高位数据被截断的现象,引起数据丢失(不知道高位截断的可以回顾笔记十一)

需要一个变量来存储一个数据,刚开始这个数据的数据宽度较小,后来发现存不下了,需要换一个数据宽度更大的变量来存储

类型转换相关汇编指令

MOVSX

符号扩展,再传送

MOV AL,0FF
MOVSX CX,AL
MOV AL,80
MOVSX CX,AL

MOVZX

扩展,再传送

MOV AL,0FF
MOVZX CX,AL
MOV AL,80
MOVSX CX,AL

类型转换例子

#include "stdafx.h"
int main(int argc, char* argv[])
{
unsigned char i=0xFF;
printf("%d\n",i);
int j=i+1;
i=i+1;
printf("%d\n",i);
printf("%d\n",j);
return 0;
}

我们来看看以上代码的运行结果:

image-20210303124927197

分析结果

首先输出的是一个无符号数 i,0xFF对应的十进制为255

接着输出的是i自增1后的结果,我们发现255+1变成了0,这是因为char的数据宽度为8位,最大便是0xFF了,再加上一就超出了char的数据宽度,也就是发生了上溢。于是数据变成了0

最后输出的是类型转换后i+1的结果,正确地显示为256

汇编观察

汇编代码

大致了解了产生上述结果的原因,用汇编来更透彻地分析:

image-20210303130034484

我们这里提取出 去除printf输出的部分,得到汇编代码如下:

8:        unsigned char i=0xFF;
0040D708 mov byte ptr [ebp-4],0FFh
10: int j=i+1;
0040D722 mov ecx,dword ptr [ebp-4]
0040D725 and ecx,0FFh
0040D72B add ecx,1
0040D72E mov dword ptr [ebp-8],ecx
11: i=i+1;
0040D731 mov edx,dword ptr [ebp-4]
0040D734 and edx,0FFh
0040D73A add edx,1
0040D73D mov byte ptr [ebp-4],dl

结果有些尴尬,,ԾㅂԾ,, 我们发现并没有用到前面所说的movx或movzx指令,但是先不着急,先看看这段汇编代码做了些什么

对应i赋值

很稀松平常的,char对应数据宽度为byte赋值

8:        unsigned char i=0xFF;
0040D708 mov byte ptr [ebp-4],0FFh

对应j赋值

接下来就是j的赋值

10:       int j=i+1;
0040D722 mov ecx,dword ptr [ebp-4]
0040D725 and ecx,0FFh
0040D72B add ecx,1
0040D72E mov dword ptr [ebp-8],ecx

可以看到:首先是直接将前面的 i 赋值给ecx,并且赋值的长度为dword,很明显将超出char长度的内容也赋值到了ecx

mov         ecx,dword ptr [ebp-4]

然后下一句很关键

and ecx,0FFh

与操作,将之前多超出的部分和0相与,也就是将超出的部分全部清零(用零填充),相当于MOVZX指令的零填充

接下来就是加一的操作

add ecx,1

最后就是将ecx赋值给了我们的变量j

mov         dword ptr [ebp-8],ecx

对应i=i+1

和前面j的赋值似曾相识,直接将前面i赋值给edx,并且赋值的长度为dword,很明显将超出char长度的内容也赋值到了edx

mov         edx,dword ptr [ebp-4]

接着也是与操作,超出来的部分清零

and         edx,0FFh

接下来就是加一的操作

add         edx,1

最后是赋值

mov         byte ptr [ebp-4],dl

将edx的低8位赋值给i

小总结

通过前面的分析,可以发现,无论是 i 自己+1还是用数据宽度较高的 j 来接收 i +1的结果

期间都是要先取出超出 i 数据宽度的dword长度的数据,然后再使用and 0xFF,把超出的部分清零

换言之,char的计算也会先转换为int的计算,最后再转回来

还有就是汇编指令并非一成不变,不是一定要使用movsx或movzx指令,也可以通过这种取出超出长度的数据,然后再将超出的部分清零的操作来实现类movzx指令的结果

自写汇编实现功能

前面虽然我们分析了,汇编代码,但很可惜编译器并没有使用movzx指令来实现操作,本着学习巩固的精神,我们自己写汇编来实现上述的功能,以此来加深对movzx的理解

#include "stdafx.h"
unsigned char i=0xFF;
int j=0;
void _declspec (naked) func(){
_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
//函数核心功能

//将i零扩充赋值给ecx
movzx ecx,i
//ecx自增1
inc ecx
//将ecx赋值给j
mov j,ecx
//直接让i自增1
inc i

//恢复现场
pop edi
pop esi
pop ebx
//降低堆栈
mov esp,ebp
pop ebp
//返回
ret
}
}

int main(int argc, char* argv[])
{
printf("%d\n",i);
func();
printf("%d\n",i);
printf("%d\n",j);
return 0;
}


执行后的结果为:

image-20210303135845896

和前面的执行结果一致

上面的代码看似很多,其实核心功能就只有四句:

//函数核心功能
//将i零扩充赋值给ecx
movzx ecx,i
//ecx自增1
inc ecx
//将ecx赋值给j
mov j,ecx
//直接让i自增1
inc i

我们这里使用了movzx指令,实现了相同的功能,也顺便引入了一个新的汇编语句:

inc eax 相当于 add eax,1     也就是eax=eax+1

优点是速度比add指令快,占用空间小

与之相对的便是:dec指令,自减1

dec eax 相当于 sub eax,1          也就是eax=eax-1


优点是速度比sub指令快,占用空间小

PS:不懂裸函数的可以戳这里:逆向基础笔记九C语言内联汇编和调用协定

以上我们的类型转换就暂告一段落了(●ˇ∀ˇ●),虽然还有movsx 有符号的没有去分析,但其实它和movzx并无太大的差别,就是要注意一些有符号数和无符号数的应用和符号位即可,

感兴趣的小伙伴可以自行研究,这里就不过多赘述了,ヾ( ̄▽ ̄)Bye~Bye~


返回顶部