逆向分析篇三:数据类型分析 | xxx逆向分析篇三:数据类型分析 – xxx
菜单

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

六月 30, 2022 - FreeBuf

一、前言

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

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

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

二、数据类型分析

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,也可以看出数据长度为两字节。

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

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

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

X64

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

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

(2)int整数分析

X86

#include <stdio.h> #include <stdlib.h>  int main() {     int a = 0;     unsigned int b = 0;     system("pause");     return 0; }

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

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

X64

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

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

(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来存储数据,其他没什么区别。

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

#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类型。

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

在定义中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内存中

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

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

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

X64

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

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

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编码。

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

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

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

12.25如何转换成的00004441的呢,接下来就算一下。

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

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

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

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

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

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

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

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

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

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

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

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; }

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

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

赋值字符串

#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字符串的首地址,这样就达到了赋值的目的。

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

01017B30h内存地址存的是rkab

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

01017B34h内存地址存的是yss

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

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

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

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

003C7B30h内存地址存的是rkab

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

003C7B34h内存地址存的是

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

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

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

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

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

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; }

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

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

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的地址。

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

6、常量

#include <stdio.h> #include <stdlib.h> #define NUMBER 20  int main() {     const int nVar = NUMBER;     printf("conset=%d    #define=%drn", nVar, NUMBER);     system("pause");     return 0; }

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

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

#define与 const 的区别

#define

const

编译期间查找替换

编译期间检查 const 修饰的变量是否被修改

由系统判断是否被修改

由编译器限制修改


字符串定义在文件只读数据区,数据常量编译为立即数寻址方式,成为二进制代码的一部分

根据作用域决定所在的内存位置和属性

三、总结

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

本文作者:, 转载请注明来自FreeBuf.COM

# 逆向 # 逆向工程 # 逆向分析 # 逆向技术 # 逆向基础

Notice: Undefined variable: canUpdate in /var/www/html/wordpress/wp-content/plugins/wp-autopost-pro/wp-autopost-function.php on line 51