吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7082|回复: 29
收起左侧

[调试逆向] 逆向基础笔记十三 汇编C语言类型转换

[复制链接]
lyl610abc 发表于 2021-3-3 14:19
本帖最后由 lyl610abc 于 2021-3-12 16:33 编辑

继续更新个人的学习笔记,
其它笔记传送门
逆向基础笔记一 进制篇
逆向基础笔记二 数据宽度和逻辑运算
逆向基础笔记三 通用寄存器和内存读写
逆向基础笔记四 堆栈篇
逆向基础笔记五 标志寄存器
逆向基础笔记六 汇编跳转和比较指令
逆向基础笔记七 堆栈图(重点)
逆向基础笔记八 反汇编分析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~

免费评分

参与人数 7吾爱币 +7 热心值 +7 收起 理由
Joky + 1 + 1 热心回复!
福仔 + 2 + 1 用心讨论,共获提升!
yuanshengyin + 1 + 1 鼓励转贴优秀软件安全工具和文档!
songz + 1 + 1 谢谢@Thanks!
liu5653250 + 1 + 1 谢谢@Thanks!
bailemenmlbj + 1 + 1 谢谢@Thanks!
zhouqhui + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| lyl610abc 发表于 2021-3-5 13:13

补充

看到有评论说还是不懂 movzx 和 movsx

这里再补充一下关于movzx和movsx的区别

案例一

#include "stdafx.h"
void function(){
    char i=0xFF;
    int j=0;
    _asm{
        movsx eax,i
        mov j,eax
    }
    printf("%d\t%x\n",j,j);
    _asm{
        movzx eax,i
        mov j,eax
    }
    printf("%d\t%x\n",j,j);
}
int main(int argc, char* argv[])
{
    function();
    return 0;
}

运行结果

image-20210305124040692


分析

void function(){
    char i=0xFF;
    int j=0;
    _asm{
        movsx eax,i
        mov j,eax
    }
    printf("%d\t%x\n",j,j);
    _asm{
        movzx eax,i
        mov j,eax
    }
    printf("%d\t%x\n",j,j);
}

首先声明一个char类型的变量i,默认为有符号数,然后再声明一个变量j初始化

char i=0xFF;
int j=0;

然后就是先将i带符号扩展到eax,接着再用eax赋值给j

_asm{
        movsx eax,i
        mov j,eax
    }

带符号扩展解释:

首先将 i :0xFF转化为二进制 : 1111 1111

i是一个有符号数,最高位为符号位即1

符号扩展就是将要扩展的高位全部用符号位进行填充,这里就是将eax的高24位用1填充(低8位直接为i赋值),得到的结果转换为十六进制就是ffffffff,8个f,对应输出的结果


再看下面:

_asm{
        movzx eax,i
        mov j,eax
    }

和前面类似,只不过是将movsx改为movzx

无符号扩展解释:

直接将eax的高24位用0填充(低8位直接为i赋值),得到的结果为000000ff,2个f,对应输出的结果


案例二

只用ff可能看得还不是很清晰,再来一个8b(对应-117),不懂为何8b对应-117的可以前往:逆向基础笔记二 数据宽度和逻辑运算中查看置顶的回复

将上面的ff改为8b即可

void function(){
    char i=0x8b;
    int j=0;
    _asm{
        movsx eax,i
        mov j,eax
    }
    printf("%d\t%x\n",j,j);
    _asm{
        movzx eax,i
        mov j,eax
    }
    printf("%d\t%x\n",j,j);
}

运行结果

image-20210305130237686


分析

有了前面分析的经验,直接来看f8如何有符号扩展

先将8b转化为二进制数:

8b:1000 1011

这里的符号位也就是最高位为1,于是将eax的高24位用1(符号位)填充,低8位直接为8b,得到的结果转化为十六进制:ffffff8b


与之类似,无符号扩展则是将eax的高24位用0填充,低8位依旧为8b,得到的结果转化为十六进制:0000008b

得到的结果运行结果一致


总结

简单地来说,有符号扩展:movsx,就是将被扩展数的最高位(符号位),对应SF标志位的内容(修改SF标志位可以改变符号扩展的结果),填充到扩展的高位部分

而无符号扩展则直接用0填充扩展的高位部分

以上就是补充的全部内容了,欢迎提问,共同探讨提升(^-^

vdycmx 发表于 2021-3-3 15:34
JDLiux 发表于 2021-3-3 15:55
Project_re 发表于 2021-3-3 16:00
学习了,谢谢谢谢
liu5653250 发表于 2021-3-3 16:07
感谢大神,谢谢分享!
songz 发表于 2021-3-3 16:29
好难的样子,谢谢分享
a254156435 发表于 2021-3-3 16:32
感谢分享 太复杂了
sniper9527 发表于 2021-3-3 16:55
感谢分享
54ljj 发表于 2021-3-3 17:07
nice,感谢
yuanshengyin 发表于 2021-3-3 18:48
谢谢分享好的学习资料
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 00:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表