吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7589|回复: 33
收起左侧

[调试逆向] 逆向基础笔记十一 汇编C语言基本类型

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

继续更新个人的学习笔记,
其它笔记传送门
逆向基础笔记一 进制篇
逆向基础笔记二 数据宽度和逻辑运算
逆向基础笔记三 通用寄存器和内存读写
逆向基础笔记四 堆栈篇
逆向基础笔记五 标志寄存器
逆向基础笔记六 汇编跳转和比较指令
逆向基础笔记七 堆栈图(重点)
逆向基础笔记八 反汇编分析C语言
逆向基础笔记九 C语言内联汇编和调用协定
逆向基础笔记十 汇编寻找C程序入口
逆向基础笔记十二 汇编 全局和局部 变量
逆向基础笔记十三 汇编C语言类型转换
逆向基础笔记十四 汇编嵌套if else
逆向基础笔记十五 汇编比较三种循环
逆向基础笔记十六 汇编一维数组
逆向基础笔记十七 汇编二维数组 位移 乘法
逆向基础笔记十八 汇编 结构体和内存对齐
逆向基础笔记十九 汇编switch比较if else
逆向基础笔记二十 汇编 指针(一)
逆向基础笔记二十一 汇编 指针(二)
逆向基础笔记二十二 汇编 指针(三)
逆向基础笔记二十三 汇编 指针(四)
逆向基础笔记二十四 汇编 指针(五) 系列完结

C语言基本类型

不同于寻常的C语言基本数据类型的学习,这里以汇编的形式来学习不同数据类型的存储方式和差异

C语言的数据类型

C语言的基本类型属于C语言的数据类型的一部分:

image-20210302133616713

这里先从最简单的基本类型进行入手学习,在学习基本类型之前再温故一下先前学习过的汇编的数据类型

汇编的数据类型

数据类型 名称
BYTE 字节 8BIT
WORD 字=2字节 16BIT
DWORD 双字=4字节 32BIT

整数类型

C语言的整数类型有:char short int long

整数类型 字节 对应汇编
char 8BIT 1字节 byte
short 16BIT 2字节 word
int 32BIT 4字节 dword
long 32BIT 4字节 dword

大家可能会觉得疑惑,为什么int和long所表示的貌似一样?

这其实是历史遗留问题,在以前的16位计算机中,int的长度位2字节,但是在32位计算机中,int类型变成了4字节,而long类型原来便是4字节,现在在仍然是4字节


存储方式

接下来从汇编的角度来看看整数类型如何存储

#include "stdafx.h"
int main(int argc, char* argv[])
{
        char a=0xFF;
        short b=0xFF;
        int c=0xFF;
        long d=0xFF;
        return 0;
}

image-20210302141102561

image-20210302141230344

语句 对应汇编 单位
char a=0xFF; mov         byte ptr [ebp-4],0FFh byte
short b=0xFF; mov         word ptr [ebp-8],offset main+20h (0040d4e0) word
int c=0xFF; mov         dword ptr [ebp-0Ch],0FFh dword
long d=0xFF; mov         dword ptr [ebp-10h],0FFh dword

超出数据宽度赋值

上面的赋值都是在基本类型的数据宽度之内,那么如果超出数据宽度会如何?

修改赋值的内容为0x123456789

#include "stdafx.h"
int main(int argc, char* argv[])
{
        char a=0x123456789;
        short b=0x123456789;
        int c=0x123456789;
        long d=0x123456789;
        return 0;
}

然后再观察反汇编代码

image-20210302142033407

image-20210302142054391

语句 对应汇编 实际赋值 单位
char a=0x123456789; mov         byte ptr [ebp-4],89h 89h byte
short b=0x123456789; mov         word ptr [ebp-8],offset main+20h (0040d4e0) 6789h word
int c=0x123456789; mov         dword ptr [ebp-0Ch],23456789h 23456789h dword
long d=0x123456789; mov         dword ptr [ebp-10h],23456789h 23456789h dword

不难发现,所有赋值语句全部都高位截断了,即高位的部分全部舍去,只赋值了数据的低位

有符号数和无符号数分析

整数类型分为有符号(signed)和无符号(unsigned)两种

默认就是有符号的类型

通过汇编观察有符号和无符号在内存中存储时是否有差别,这里以char 为例

#include "stdafx.h"
int main(int argc, char* argv[])
{
        char signed a=0xFF;
        char unsigned b=0xFF;
        return 0;
}

image-20210302143418909

我们发现无符号数和有符号数在内存存储中并无差别,再一次印证了我们前面所学的:计算机并不关心数据是有符号数还是无符号数,决定一个数据是有符号数还是无符号数的是使用数据的我们,同一个数据使用不同的方式来解析

了解了有符号数和无符号数的本质后,再来谈谈有符号数和无符号数的注意事项

有符号数和无符号数注意场景

  • 类型转换
  • 比较大小
  • 数学运算
比较大小
同为有符号数时
#include "stdafx.h"
int main(int argc, char* argv[])
{
        char a=0xFF;
        char b=1;
        if(a>b){
                printf("a>b\n");
        }else if(a<b){
                printf("a<b\n");
        }else{
                printf("a=b\n");
        }
        return 0;
}

image-20210302144427893

同为无符号数时
#include "stdafx.h"
int main(int argc, char* argv[])
{
        char unsigned a=0xFF;
        char unsigned b=1;
        if(a>b){
                printf("a>b\n");
        }else if(a<b){
                printf("a<b\n");
        }else{
                printf("a=b\n");
        }
        return 0;
}

image-20210302144656770


汇编比较大小

看到这里,可能就会产生疑问,既然在内存中数据的存储是一样的,那么如何进行比较呢?

这里就要梦回先前的汇编跳转和比较指令

char的比较

我们可以看到:

比较有符号数
char a=0xFF;
char b=1;
if(a>b){
        printf("a>b\n");
}

image-20210302151435127

比较无符号数
char unsigned a=0xFF;
char unsigned b=1;
if(a>b){
        printf("a>b\n");
}

image-20210302151308701

我们发现这里的使用的jcc语句都是jle:jump less equal 小于等于才跳转(有符号数),和我们的a>b正好相反

但是我们会发现在有符号数那里使用了movsx指令,该指令为汇编语言数据传送指令MOV的变体。带符号扩展,并传送。

即它会将char从原本的byte扩展到dword,这样一来数据长度被扩展以后,自然就可以使用同一个jle指令来进行比较

int的比较

前面char的比较是通过数据宽度的扩展来实现比较的,那么当使用int时,无法扩展符号的数据宽度时,如何比较?

将上面的char改为int后再次观察反汇编代码

比较有符号数
int a=0xFF;
int b=1;
if(a>b){
        printf("a>b\n");
}

image-20210302152925765

比较无符号数
int unsigned a=0xFF;
int unsigned b=1;
if(a>b){
        printf("a>b\n");
}

image-20210302153115591

差别

我们可以发现,同样是比较,比较无符号数和有符号数时,分别对应两个不同的jcc语句

比较 jcc语句 含义
比较有符号数 jle main+3Dh(0040d7ed) 小于等于则跳转 (有符号数)
比较无符号数 jbe main+3Dh(0040d7ed) 小于等于则跳转 (无符号数)

浮点类型

C语言的浮点类型分为float和double

存储方式和规范

float和double在存储方式上都是遵从IEEE规范

float的存储方式如下图所示:

image-20210302153815823

double的存储方式如下图所示:

image-20210302153846569

由于double的长度比较长,我们下面就用float作为例子,实际上,double不过是比float精度更高了,在将浮点数转化为存储到内存中的二进制的步骤几乎一致

转化步骤

将一个float型转化为内存存储格式的步骤为:

  1. 先将这个实数的绝对值化为二进制格式
  2. 将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边
  3. 从小数点右边第一位开始数出二十三位数字放入第22到第0位。
  4. 如果实数是正的,则在第31位放入“0”,否则放入“1”
  5. 如果n是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”
  6. 如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。 如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位

看起来很复杂QAQ,但结合下面的实例来看就还好(。・∀・)ノ゙

在转化步骤中的第一步,又分为整数部分的二进制化和小数部分的二进制化

十进制整数二进制化

采用除留余数法

比如将11转化成二进制数

计算 余数
11/2=5 1
5/2=2 1
2/2=1 0
1/2=0 1
0 结束

11二进制表示为(从下往上):1011

注意到只要除以后结果为0便结束了,任意整数不断除以2最终都会等于0,因此所有整数都可以用二进制来精确表示

十进制小数二进制化

采用乘二取整法

比如将0.9转化为二进制数

计算 取整数部分
0.9*2=1.8 1
0.8(前面结果的小数部分)*2=1.6 1
0.6*2=1.2 1
0.2*2=0.4 0
0.4*2=0.8 0
0.8*2=1.6 1
0.6*2=1.2 1
…… ……

0.9二进制表示为(从上往下):110011……

很显然,上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,并非所有的小数都可以用二进制精确表示,就和十进制里也无法精确表示出1/3一样

实例

8.25f
#include "stdafx.h"
int main(int argc, char* argv[])
{
        float i=8.25f;
        return 0;
}

我们用反汇编查看8.25f

image-20210302165607153

00401028   mov         dword ptr [ebp-4],41040000h

我们会发现8.25f的表现形式为41040000h,接下来我们就来研究这个41040000h是怎么来的

一步步按先前提到的转化步骤来:

实数的绝对值化为二进制格式

先处理整数部分8.25

计算 余数
8/2=4 0
4/2=2 0
2/2=1 0
1/2=0 1

整数部分8转化为二进制:1000(从下往上)

再处理小数部分8.25

计算 取整数部分
0.25*2=0.5 0
0.5*2=1.0 1

0.25转化为二进制可表示为:01(从上往下)

于是8.25用二进制可表示为1000.01

填充尾数

接下来先填充尾数部分

先将二进制数转为用科学计数法表示

1000.01=1.00001*2的3次方        (小数点向左移动3位 指数为3)

1.**00001***2

就是将小数点后面的23位填入尾数部分,我们这里小数部分恰好能够精确地转化为二进制数,于是剩下的部分用0填充即可

如果是前面的0.9转化为的0.110011……=1.1100……*2的-1次方

则是截取小数点后面的23位填入1100……

符号位 指数部分 尾数部分
占用空间 1 8 23
存储内容 00001000000000000000000

此时尾部部分已经填充完毕,再填充符号位

填充符号位

符号位就简单多了,正数填充0,负数填充1即可

这里我们的8.5f是正数,填0结束

符号位 指数部分 尾数部分
占用空间 1 8 23
存储内容 0 00001000000000000000000
填充指数部分

指数部分的最高位填充看前面是前面将二进制数科学计数法化时,是进行了左移还是右移,左移填1,右移填0

前面我们得到了8.5f的转化:

1000.01=1.00001*2的3次方        (小数点向左移动3位 指数为3)

很明显我们的是左移,因此,最高位填写1

剩下的部分则是用指数减去1后二进制化填充即可

8.5f的指数为3,3-1=2

2的二进制为10

所以指数部分应该为:

10000010

于是整个填充完成

符号位 指数部分 尾数部分
占用空间 1 8 23
存储内容 0 10000010 00001000000000000000000

总共为0100 0001 0000 0100 0000 0000 0000 0000

转化为十六进制为4 1 0 4 0 0 0 0 0

和我们前面用汇编看到的结果一致

-8.25f
#include "stdafx.h"
int main(int argc, char* argv[])
{
        float i=-8.25f;
        return 0;
}

image-20210302170100469

00401028   mov         dword ptr [ebp-4],0C1040000h

-8.25f和8.25f相差的只有符号位,于是将前面的8.25f的符号位改为1即可

符号位 指数部分 尾数部分
占用空间 1 8 23
存储内容 1 10000010 00001000000000000000000

也就是1100 0001 0000 0100 0000 0000 0000 0000

转为十六进制是 C 1 0 4 0 0 0 0

和前面反汇编看到的结果一致

0.25f

这次看个纯小数在内存中如何存储,依旧采取相同的套路

实数的绝对值化为二进制格式
计算 取整数部分
0.25*2=0.5 0
0.5*1=1.0 1

0.25f转化为二进制:0.01

填充尾数

科学计数法表示:0.01=1.0*2的-2次方        (小数点向右移动2位 指数为-2 )

尾数填充0,1.0小数点后面全为0,于是尾数全部填0即可

符号位 指数部分 尾数部分
占用空间 1 8 23
存储内容 00000000000000000000000
填充符号位

0.25f是正数直接填充0即可

符号位 指数部分 尾数部分
占用空间 1 8 23
存储内容 0 00000000000000000000000
填充指数部分

科学计数法表示:0.01=1.0*2的-2次方        (小数点向右移动2位 指数为-2 )

向右移动,指数部分最高位填0

指数为-2,-2-1=-3

将-3转化为二进制:-3的十六进制对应fd,fd转为二进制:1111 1101

不知道为什么-3对应fd的可以看这里:逆向基础笔记二 数据宽度和逻辑运算

我们发现-3转为二进制共有8位,但是指数部分总共也只有8位,其中最高位还用作表示是右移填充了0,于是只截取低7位填充进指数部分剩下的内容

于是整个填充完成

符号位 指数部分 尾数部分
占用空间 1 8 23
存储内容 0 01111101 00000000000000000000000

总共为0011 1110 1000 0000 0000 0000 0000 0000

转化为十六进制为3 e 8 0 0 0 0 0

接下来用反汇编验证一下:

image-20210302172343260

得到的结果是一致的,好耶ヽ(✿゚▽゚)ノ

免费评分

参与人数 11吾爱币 +10 热心值 +10 收起 理由
erh + 1 + 1 谢谢@Thanks!
gy123xl + 1 用心讨论,共获提升!
二诗的Adidas + 1 + 1 谢谢@Thanks!
Coonly + 1 + 1 谢谢@Thanks!
gunxsword + 1 + 1 热心回复!
tocabd + 1 + 1 用心讨论,共获提升!
茫茫狐 + 1 + 1 我很赞同!
新手12138 + 1 + 1 我很赞同!
liwj + 1 一直没搞懂浮点 终于有你讲清楚了 非常感谢
fanvalen + 1 + 1 我很赞同!
国际豆哥 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

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

kwdwkiss 发表于 2021-3-9 17:35
“6.如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。 如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位”
指数部分,左移减一,右移取反,不是很明白为什么这样做,是什么原理?
先mark一下,再回来思考。

免费评分

参与人数 1吾爱币 +1 收起 理由
lyl610abc + 1 用心讨论,共获提升!

查看全部评分

Leoi_Chan 发表于 2023-10-20 16:51
本帖最后由 Leoi_Chan 于 2023-10-20 16:55 编辑

关于实例的 0.25f 部分,不是很理解,按照规范右移应该是无需减 1 才对。

填充指数部分

科学计数法表示:0.01 = 1.0 * 2 的 -2 次方        (0.01 小数点向左移动 2 位 指数为 -2 )

向右移动,指数部分最高位填 0

指数为 -2,-2-1=-3

将 -3 转化为二进制:-3 的十六进制对应 fd,fd 转为二进制:1111 1101

不知道为什么 -3 对应 fd 的可以看这里:逆向基础笔记二 数据宽度和逻辑运算

我们发现 -3 转为二进制共有 8 位,但是指数部分总共也只有 8 位,其中最高位还用作表示是右移填充了 0,于是只截取低 7 位填充进指数部分剩下的内容

我理解应该是:2 -->二进制:10 --> 最高位:0 ,左边补 "0",补足 7 位:000 0010 --> 最高位不变,剩下取反 0 111 1101

感谢分享,学到很多~{:301_998:}



{:301_998:}
tt0202 发表于 2021-3-2 18:02
liwj 发表于 2021-3-2 19:45
楼主排版很漂亮 是怎么做的 可以分享一下吗
wnt17410718 发表于 2021-3-2 19:47
感谢分享,楼主有心了
 楼主| lyl610abc 发表于 2021-3-2 19:49
liwj 发表于 2021-3-2 19:45
楼主排版很漂亮 是怎么做的 可以分享一下吗

typora+picgo+阿里云OSS
采用的是markdown的格式
upk 发表于 2021-3-2 21:28
先收藏,感谢分享
哥哥的肥皂 发表于 2021-3-3 07:38
感谢楼主的精心整理、分享,共同进步。楼主一定是个大神
Liture 发表于 2021-3-3 09:41
收藏了。
wapjwsy 发表于 2021-3-3 11:28
大神不紧讲解详细,美工也不错,顶顶。。。。。
头像被屏蔽
偶尔平凡 发表于 2021-3-3 12:26
提示: 作者被禁止或删除 内容自动屏蔽
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-21 19:45

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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