电脑疯子技术论坛|电脑极客社区

微信扫一扫 分享朋友圈

已有 555 人浏览分享

逆向分析篇三:数据类型分析

[复制链接]
555 0
本帖最后由 zhaorong 于 2022-7-4 12:07 编辑

一、前言

本次文章只用于技术讨论,学习,切勿用于非法用途,用于非法用途与作者无关!

所有环境均为本地环境分析,且在本机进行学习。

这章写的比较简单,主要就是对整数、浮点数、字符串、地址、指针、引用赋值进行了分析。

数据类型分析

1、整数类型

C/C++提供的整数数据类型有三种∶int、long、short。int 类型与 long类型在内存中都占4个字节,short 类型在内存中占
两个字节
由于二进制数不方便显示和阅读,因此内存中的数据采用十六进制数显示。一个字节由两个十六进制数组成在进制
转换中,一个十六进制数可用 4 个二进制数表示,每个二进制数表示1位,因此一个字节在内存中占8位。

(1)short短整型分析

X86

#include <stdio.h>
#include <stdlib.h>

int main()
{
    short a = 0;
    unsigned short b = 0;
    system("pause");
    return 0;
}

short短整型,宽度2个字节16位,unsigned用来标识数据类型为无符号类型,不带unsigned默认为带符号数据类型
xor eax,eax命令(异或:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。eax跟eax
值一样,所以eax异或eax,是在做清空操作。

看到word ptr [ebp-8],ax跟word ptr [ebp-14h],ax,大家有没有疑惑,不是short是2个字节,但是为什么0赋值给
a跟b的地址空间差了4个字节?因为数据宽度和地址长度是没有关系的,在32位程序中地址长度就是32位,数据类
型虽然是2字节但是地址空间使用的是4字节,在64位程序中同理。但是他保存的值是有区别的,short a = 0虽然
内存地址占了32位,但是a还是只有16位。ax为eax的低16位赋值给ebp-8并且接收宽度为word,也可以看出数据
长度为两字节。

QQ截图20220704110544.png

关于有符号与无符号,其实在内存中short他还是就2字节,要是在内存中光看是没什么的只是在执行的时
候结果会有所区别。有符号只是在最高位多了一个符号位,以至于当最高位为1即为负数,0为整数所以有
符号数会比无符号数短一截。

QQ截图20220704110625.png

X64

在64位程序中,除了地址宽度变长了之外,其他的跟32位程序没有区别。

100.png

(2)int整数分析

X86

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a = 0;
    unsigned int b = 0;
    system("pause");
    return 0;
}

可以看到,整型宽度为4字节,用dword来存储数据,其他没什么区别。

99.png

X64

整型也是相同,除了地址宽度变长了之外,其他的跟32位程序没有区别。

98.png

(3)long长整型分析

X86

#include <stdio.h>
#include <stdlib.h>

int main()
{
    long a = 0;
    unsigned long b = 0;
    system("pause");
    return 0;
}

long类型也是4字节,用dword来存储数据,其他没什么区别。

97.png

#include <stdio.h>
#include <stdlib.h>

int main()
{
    long a = 0;
    unsigned long b = 0;
    int c = 0;
    unsigned d = 0;
    system("pause");
    return 0;
}

通过long跟int进行比对,可以发现他们没有区别,所以在之后逆向中,看到一个数据是用dword类型接收
不一定就是int类型,他还有可能是long类型。

96.png

在定义中long型大小不低于int类型大小,但是没有明确说必须要大于int类型,在VC++跟VS中,long型只占4字节
我记得我高中第一个接触的语言VB中long类型就是8字节,java语言long类型也是8字节。C/C++声明64位长整型
用long long声明。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    long long a = 0;
    unsigned long long b = 0;
    system("pause");
    return 0;
}

可以看到使用了一个xmm的寄存器,因为long long类型是64位,是从大的放入小的地方,所以超出了32位的四字节
使用了xmm寄存器作为转换。xorps跟xor功能一样都是异或,只是处理的寄存器不同,它们都是在清空处理器里面内
容movpd其实是一个通道指令,用来处理XMM和MM寄存器的。

movlpd      qword ptr [ebp-0Ch],xmm0 //将低64位传入到ebp-0Ch内存中

95.png

XMM寄存器一共有8个,每个都是128位的寄存器。

94.png

X64

64位程序可以看见用qword接收,也没有使用xmm0寄存器来接收。

93.png

2、浮点数类型

#include <stdio.h>
#include <stdlib.h>

int main()
{
    float a = 12.25;  //占4字节
    double b = 25.45; //占8字节
    system("pause");
    return 0;
}

float a = 12.25;通过在内存中可以看到a的内容是00004441,因为它使用了一个IEEE编码。

92.png

float 类型在内存中占4字节(32位)。最高位用于表示符号;在剩余的31位中从右向左取8位用于
表示指数,其余用于表示尾数。

91.png

12.25如何转换成的00004441的呢,接下来就算一下。
首先算一下把他们都转换成二进制数,对于浮点数先将整数进行转换,以整数进行整除因为要转
成二进制所以除以2,最后倒着取就是1100

90.png

后面小数转二进制计算是,通过后面小数0.25乘要转的进制,不为整数就一直乘,乘到没有小数为止最终
小数部分二进制为01,所以12.25转二进制为1100.01

89.png

接下来二进制1100.01要进行移位,移到符号位的后面,也就是成1.10001,一共左移了三位,所以指数为3再加上127
它是用来区分正负数的,大于就是整数,小于就是负数。所以最后指数部分就是10000010

88.png

最后就是尾数部分,根据上一步移位之后是1.10001,尾数就是10001,因为float尾数部分一共有23位,所以将

10001后面填充0,知道满23位,即为10001000000000000000000

符号位:0
指数:10000010
尾数:10001000000000000000000
将三个合到一起:01000001010001000000000000000000

将01000001010001000000000000000000丢到计算器里可以看到这个值就是a在内存中的值。

86.png

3、字符和字符串

字符串是由多个字符按照一定排列顺序组成的,在C/C++中,以"0'作为字符串结束标记 每个字符都记录在一张表
中它们各自对应一个唯一编号,系统通过这些编号查找到对应的字符并显示。

(1)字符编码

在C++中,字符的编码格式分两种∶ASCI和 Unicode。Unicode是 ASCII的升级编码格式,它弥补了 ASCII 的不足
也是未来编码格式的趋势。ASCI编码在内存中占一个字节大小,由0~255之间的数字组成。每个数字表示一个符号
具体表示方式可查看 ASCI表。由于 ASCII 编码也是由数字组成的,故可以和整型互相转换,但整数不可超过 ASCII
的最大表示范围,因为多余部分将被舍弃。
由于ASCII 原来的表示范围太小,只能表示英文的26个字母和常用符号
在亚洲,ASCI 的表示范围完全不够用。仅汉字就足够占满 ASCII编码。因此,占双字节、表示范围为0~65535的
Unicode 编码产生了。

ASCII使用GB2312-80,又叫汉字国标码,保存了6763个常用汉字编码,用两个字节来表示一个汉字。在GB2312-80中用
区和位来定位,第一个字节保存每个区,共94个区;第二个字节保存每个区中的位,共94位。详细信息可查看GB2312-80
编码的说明。
Unicode 使用UCS-2编码格式,最多可存储 65536个字符。汉字博大精深,其中有简体字、繁体字 以及网
络中流行的火星文,它们的总和远远超过了UCS-2的存储范围,所以UCS-2编码格式中只保存了常用字。为了将所有的
汉字都容纳进来,Unicode 也采用了与ASCII类似的方式———用两个Unicode 编码解释一个汉字,称之为 UCS-4编
码格式。UCS-2 编码表的使用和ASCII码表的使用是一样的。每个数字编号在表中对应一个汉字,从0x4E00到0x9520
为汉字编码区。

(2)字符串类型分析

一共有两种赋值,一种赋值字符串,另一种是赋值字符,字符又可以直接赋值字符或编码值。

#include <stdio.h>
#include <stdlib.h>
#include <string>

int main()
{
    char a = 'a';
    char b = 65;
    system("pause");
    return 0;
}

看到字符和字符编码都可以直接进行赋值。

85.png

赋值字符串

#include <stdio.h>
#include <stdlib.h>
#include <string>

int main()
{
    char a[] = "rkabyss";
    system("pause");
    return 0;
}

我把rkabyss字符串赋值给a,底层操作是把01017B30h和01017B34h两个的地址里的内容分别存放到了
eax和ecx寄存器中再将寄存器内容分别放到ebp-0Ch和ebp-8位置,a指向的就是rkabyss字符串的首地
址这样就达到了赋值的目的。

84.png

01017B30h内存地址存的是rkab

83.png

01017B34h内存地址存的是yss\0

82.png

在32位中所有寄存器都只有四字节,那么要是字符串正好四个字符,观察底层是如何做的。

操作其实跟上边是一样的,003C7B30h地址内容放到eax寄存器中,再将eax寄存器内容放到ebp-0Ch位置唯
一不同的是\0的处理方式,它使用一个16位CX寄存器的低8位寄存器来存放\0,并将其放到rkab字符后边。

81.png

003C7B30h内存地址存的是rkab

80.png

003C7B34h内存地址存的是\0

79.png

上边看到的都是短的字符赋值,那么如果是长字符串底层如何操作呢。看下图,首先他给ecx进行了赋值,这个赋的值是要
循环的次数,然后引用了一个概念,就是源地址(esi)与目的地址(edi),将我们赋值的一大串字符串赋值给了源地址esi
第三行[ebp-24h]它是一个局部变量,他其实就是a指向的地方,目的地址(edi)获取[ebp-24h]的地址,其实就相当于edi
指向的地址就是a的地址。rep movs他是将源地址内容拷贝到目的地址并进行循环,循环次数就是ecx里面的值 循环完
也就把字符串全部赋值给了a。

78.png

397CD8h内存地址存的是赋值的一长串字符串。

77.png

4、布尔类型

布尔类型是用于判断执行结果的数据类型,它的判断比较值只有两种情况∶0与非0。C++中定义0为假,非0为真。使用bool
定义布尔类型变量。布尔类型在内存中占1字节。由于布尔类型只比较两个结果值∶真、假,实际上任何一种数据类型都可以
将其代替,如整型、字符型,甚至可以用位代替。在实际案例中也是难以将布尔类型数据还原成源码的,但是可以将其还原
成等价代码。布尔类型出现的场合都是在做真假判断,有了这个特性,还原成等价代码还是相对简单的。

5、地址、指针和引用

地址:取一个变量的地址使用“&”符号,只有变量才存在内存地址,常量没有地址(不包括const定义的伪常量)。
指针
:指针的定义使用"TYPE*",TYPE 为数据类型,任何数据类型都可以定义指针。指针本身也是一种数据类型
它用于保存各种数据类型在内存中的地址。指针变量同样可以取出地址,所以会出现多级指针。

引用:
引用的定义使用"TYPE&",TYPE 为数据类型。在C++中是不可以单独定义的并且在定义时就要进行初始化
引用表示一个变量的别名,对它的任何操作,本质上都是在操作它所表示的变量。

#include <stdio.h>
#include <stdlib.h>
#include <string>

int main()
{
    int a = 10;
    int* b = a&;
    *b = 50;
    system("pause");
    return 0;
}

69.png

这个就不多说了,实在没啥可说的,对比看一下就能明白了。

mov         dword ptr [ebp-0Ch],0Ah  //将16进制0Ah赋值给ebp-0Ch的地址
lea         eax,[ebp-0Ch]                          //取ebp-0Ch地址放入到eax中
mov         dword ptr [ebp-18h],eax //将 eax值放入到ebp-18h地址当中
mov         eax,dword ptr [ebp-18h] //将ebp-18h地址中的内容放到eax寄存器中
mov         dword ptr [eax],32h          //将16进制32h放入到eax当中

引用类型跟指针的区别。

#include <stdio.h>
#include <stdlib.h>
#include <string>

int main()
{
    int a = 10;
    int* b = &a;
    int& c = a;   //引用
    system("pause");
    return 0;
}

可以看到指针是通过手动进行取地址,而引用它就是取地址,从汇编可以看没有区别,区别在于他们语法不一样int* b= &a;
是右边&取a的地址赋值给指针b,而引用是把一个变量传给了&c,但是&c引用时去取了变量a的地址。

68.png

6、常量

#include <stdio.h>
#include <stdlib.h>
#define NUMBER 20

int main()
{
    const int nVar = NUMBER;
    printf("conset=%d    #define=%d\r\n", nVar, NUMBER);
    system("pause");
    return 0;
}

常量使用#define进行定义,下图可以看到在把常量赋值是直接将其值进行了赋值,它不像局部变量使用
栈空间存放,在使用常量是它是直接调用地址。

66.png

#define与 const 的区别

62.png

三、总结

大家如果觉的哪里写的不好,或则哪里没写到,欢迎大家留言交流,互相进步。写这篇也是在进行
总结把会的知识点进行总结。

您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

关注

0

粉丝

9021

主题
精彩推荐
热门资讯
网友晒图
图文推荐

Powered by Pcgho! X3.4

© 2008-2022 Pcgho Inc.