stribik 发表于 2024-11-15 19:58

浅析结构体的内存存储形式

本帖最后由 stribik 于 2024-11-16 15:18 编辑

从一个简单demo来说说c里面的结构体构成(其实c++也差不多),然后顺便了解一下他在内存中的存储形式。
【看了下,汇编部分的理解可能错的很多,欢迎下面一起讨论】

这里的这个小demo仅仅用来分析,不存在攻击点。

demo代码:

```java
#include <stdio.h>

// 定义学生结构体
struct Student {
    char name;// 姓名
    int id;         // 学号
};

int main() {
    int n;

    // 输入要处理的学生数量
    printf("请输入要输入的学生数量: ");
    scanf("%d", &n);

    // 创建一个学生结构体数组,用于存储多个学生的信息
    struct Student students;

    // 输入每个学生的姓名和学号
    for (int i = 0; i < n; i++) {
      printf("\n请输入第 %d 个学生的信息:\n", i + 1);
      printf("姓名: ");
      scanf("%s", students.name);
      printf("学号: ");
      scanf("%d", &students.id);
    }

    // 输出所有学生的信息
    printf("\n所有学生的信息:\n");
    for (int i = 0; i < n; i++) {
      printf("学生 %d - 姓名: %s, 学号: %d\n", i + 1, students.name, students.id);
    }

    return 0;
}

```

编译选项:

```java
gcc struct.c -o struct
```

简单解释一下代码逻辑:

在一开始实现了结构体中每一项的创建,然后可以读入对应的结构体的具体信息

先看到伪代码:

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731420276219-2c5197f5-a858-4f21-a83c-9db21a46d4d2.png)

两边可以非常明显的看到我们的伪代码还是非常不一样的...

红框部分就是我们的两次输入,所以也就比较耗堪我们这个玩意。大概就是使用一个全局的变量来实现结构体的表示,然后使用一个数组实现内存中的读入(其实这里大概也就看得出来)

开启动态调试,其实栈里面重要的还是这个:

(进行第一次读入操作)

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731422034756-2628d7c5-9509-4de5-b804-032527e47954.png)

(第二次读入)

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731422175783-5e204db2-f862-4710-ace1-8b18b7df0a6b.png)

看读玩之后完整的栈:

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731422331715-a81c2b15-a38d-4f88-84a1-9b47bf192a86.png)

看看两个对应的:

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731422660615-4613d348-a74e-4069-88e6-2d9c72f61217.png)

至于这个偏移量怎么来的。这里主要要满足两个点:1)结构体本身要对齐 2)每一个结构体元素有自己的空间。(我也没想明白,真要追究原因还是伪代码中的v11这里开始,这个56其实还是好理解的,这个就是结构体整体对齐的方式导致的50+2(填充)+4(int对象))

同时我们也可以看到,在d630的位置其实已经写下了我们的数组的位置,相当用这个方式变相的实现了地址的访问。(比较像指针的实现了)

接下来我们可以结合汇编的代码简单的看一下(我也不完全懂,先来一个自己胡编的解释版本了)



第一部分:初始化【初始化结构还有循环,其实这样说不太对,但是我们的主要目的是为了研究结构体,就放一起了】

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731668903239-be1fb7ba-3ccd-4442-8952-db7cbda06565.png)

先看到第一段用来读的,

重点看到红框部分:

```java
.text:00000000000011E7 lea rax,
.text:00000000000011EB mov rsi, rax
.text:00000000000011EE lea rax, unk_202C
.text:00000000000011F5 mov rdi, rax
.text:00000000000011F8 mov eax, 0
.text:00000000000011FD call ___isoc99_scanf
```

+ `lea rax, `:计算局部变量 `var_54` 的地址,并将其加载到 `rax` 中。
+ `mov rsi, rax`:将 `rax` 的值存储到 `rsi` 中,作为 `scanf` 函数的第二个参数。
+ `lea rax, unk_202C`:将未知数据的地址 `unk_202C` 加载到 `rax` 中。
+ `mov rdi, rax`:将 `rax` 的值存储到 `rdi` 中,作为 `scanf` 函数的第一个参数。
+ `mov eax, 0`:清空 `eax` 寄存器。
+ `call ___isoc99_scanf`:调用 `scanf` 函数进行输入

这里记两个点:scanf的第一个参数(unk_202C),这个偏向于是一个标记字符,格式化字符;第二个参数(,这个变相从rax最后传到了rsi上面,所以对应的第二个参数)

主要还是格式化字符串读数据,不重要

接下来我们继续看第二段:

```java
mov   ecx,
.text:0000000000001205               movsxdrax, ecx
.text:0000000000001208               sub   rax, 1
.text:000000000000120C               mov   , rax
.text:0000000000001210               movsxdrax, ecx
.text:0000000000001213               mov   r14, rax
.text:0000000000001216               mov   r15d, 0
.text:000000000000121C               imul    rdx, r15, 1C0h
.text:0000000000001223               imul    rax, r14, 0
.text:0000000000001227               lea   rsi,
.text:000000000000122B               mov   eax, 1C0h
.text:0000000000001230               mul   r14
.text:0000000000001233               add   rsi, rdx
.text:0000000000001236               mov   rdx, rsi
.text:0000000000001239               movsxdrdx, ecx
.text:000000000000123C               mov   rax, rdx
.text:000000000000123F               shl   rax, 3
.text:0000000000001243               sub   rax, rdx
.text:0000000000001246               shl   rax, 3
.text:000000000000124A               movsxdrax, ecx
.text:000000000000124D               mov   r12, rax
.text:0000000000001250               mov   r13d, 0
.text:0000000000001256               imul    rdx, r13, 1C0h
.text:000000000000125D               imul    rax, r12, 0
.text:0000000000001261               lea   rsi,
.text:0000000000001265               mov   eax, 1C0h
.text:000000000000126A               mul   r12
.text:000000000000126D               add   rsi, rdx
.text:0000000000001270               mov   rdx, rsi
.text:0000000000001273               movsxdrdx, ecx
.text:0000000000001276               mov   rax, rdx
.text:0000000000001279               shl   rax, 3
.text:000000000000127D               sub   rax, rdx
.text:0000000000001280               shl   rax, 3
.text:0000000000001284               mov   rdx, rax
.text:0000000000001287               mov   eax, 10h
.text:000000000000128C               sub   rax, 1
.text:0000000000001290               add   rax, rdx
.text:0000000000001293               mov   edi, 10h
.text:0000000000001298               mov   edx, 0
.text:000000000000129D               div   rdi
.text:00000000000012A0               imul    rax, 10h
.text:00000000000012A4               mov   rcx, rax
.text:00000000000012A7               and   rcx, 0FFFFFFFFFFFFF000h
.text:00000000000012AE               mov   rdx, rsp
.text:00000000000012B1               sub   rdx, rcx
```

这个就主要是为了处理结构体中的数据的初始化,以我薄弱的汇编知识简单解释一下这个:

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731670126845-9e59749f-0e8e-4ff6-a72e-b4119fccb7da.png)

存var_48,变相加载我们的var_54(所以我觉得这个var_54用来定位了我们栈帧中存入数据的位置),初始化其他寄存器

> 顺便简单说一下这个1C0怎么来的:
>
> 还记得我们前面所说的结构体整体大小的计算吗?1C0=56*8,这个是符合我们前面所说的整体的大小的
>

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731670325445-0de400a3-3dc1-4571-a243-6e7f90490a37.png)

位移实现数组定位(相当于我们找地址中的一个大的位置)

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731670440374-e5349fbd-58ef-4105-909b-5ef7ad9ee995.png)

唯一实现数组元素定位(相当于在定位小的门牌号)

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731670492164-85cf2759-b3f0-4ce5-8712-88c313daa48c.png)

继续结构体的计算...

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731670724189-ac28a07e-b3a2-4991-9310-7a9cc1df3915.png)

这段最最主要的目的是为了实现结构体整体的对齐。

mov eax, 1C0h 这里相当于一个结构体块的大小,然后往后就是在对齐栈、对齐结构体本身(调整偏移量)

movsxd rdx, ecx这里可能存了数组的偏移量,后续就是大概计算数组的偏移量,然后调整对齐对齐对齐...

到这里为止初始化完成了...[就是最最外面对应的for循环部分的初始化QAQ,这里实际上还没涉及我们的结构体正式部分]

这里我首先还原了IDA中的结构体部分并重命名了数据信息:

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731740325539-1ab4854d-a85e-467b-8f04-de86723c0792.png)

然后重点看到后面:

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731740392636-c91b8d8a-b054-4dd7-9e76-ff26ff66912f.png)



第二部分:结构体的读入

这个红框才是我们正式结构体的部分:

```java
text:0000000000001314 ; ---------------------------------------------------------------------------
.text:0000000000001314
.text:0000000000001314 loc_1314:                               ; CODE XREF: main+225↓j
.text:0000000000001314               mov   eax,
.text:0000000000001317               add   eax, 1
.text:000000000000131A               mov   esi, eax
.text:000000000000131C               lea   rax, asc_2030   ; "\n"
.text:0000000000001323               mov   rdi, rax      ; format
.text:0000000000001326               mov   eax, 0
.text:000000000000132B               call    _printf
.text:0000000000001330               lea   rax, asc_2056   ; "姓名: "
.text:0000000000001337               mov   rdi, rax      ; format
.text:000000000000133A               mov   eax, 0
.text:000000000000133F               call    _printf
.text:0000000000001344               mov   eax,
.text:0000000000001347               movsxdrdx, eax
.text:000000000000134A               mov   rax, rdx
.text:000000000000134D               shl   rax, 3
.text:0000000000001351               sub   rax, rdx
.text:0000000000001354               shl   rax, 3
.text:0000000000001358               mov   rdx,
.text:000000000000135C               add   rax, rdx
.text:000000000000135F               mov   rsi, rax
.text:0000000000001362               lea   rax, Student
.text:0000000000001369               mov   rdi, rax
.text:000000000000136C               mov   eax, 0
.text:0000000000001371               call    ___isoc99_scanf
.text:0000000000001376               lea   rax, Student.name+3
.text:000000000000137D               mov   rdi, rax      ; format
.text:0000000000001380               mov   eax, 0
.text:0000000000001385               call    _printf
.text:000000000000138A               mov   eax,
.text:000000000000138D               movsxdrdx, eax
.text:0000000000001390               mov   rax, rdx
.text:0000000000001393               shl   rax, 3
.text:0000000000001397               sub   rax, rdx
.text:000000000000139A               shl   rax, 3
.text:000000000000139E               lea   rdx,
.text:00000000000013A2               mov   rax,
.text:00000000000013A6               add   rax, rdx
.text:00000000000013A9               add   rax, 4
.text:00000000000013AD               mov   rsi, rax
.text:00000000000013B0               lea   rax, cnt
.text:00000000000013B7               mov   rdi, rax
.text:00000000000013BA               mov   eax, 0
.text:00000000000013BF               call    ___isoc99_scanf
.text:00000000000013C4               add   , 1
.text:00000000000013C8
.text:00000000000013C8 loc_13C8:                               ; CODE XREF: main+166↑j
.text:00000000000013C8               mov   eax,
.text:00000000000013CB               cmp   , eax
.text:00000000000013CE               jl      loc_1314
.text:00000000000013D4               lea   rax, Student.name+0Ch
.text:00000000000013DB               mov   rdi, rax      ; s
.text:00000000000013DE               call    _puts
.text:00000000000013E3               mov   , 0
.text:00000000000013EA               jmp   short loc_144E
```



最后简单说一下:

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731740706289-c1a41678-c2a8-462d-8d38-32beb8c29061.png)

这里大概就是在实现定位,找到对应的地方并写进去

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731740814321-23704c36-d5f8-4f74-b435-a2ef5f349247.png)

rdi、rsi实现参数传入

![](https://cdn.nlark.com/yuque/0/2024/png/40874278/1731740854341-633503fc-2469-4f8c-874f-cdba0a18af58.png)

实现逻辑和上面差不多,先一段格式化字符串输出,然后rdi、rsi实现数据读入。



然后就是实现循环执行....

基本就没有了。所以说从上面大概也看到得到,整个过程大概是什么样子的,总结就是不要想的太复杂,然后感觉unk开头的就比较像我们结构体的标识元素。本质上还是有点像一个大的tag标记结构体位置,通过内部偏移确定元素的位置(比如var_50这种,利用这种形式确定的内部位置),所以某种意义上结构体内部应该也是线性分布的(但其实也不影响,动态调试看得出来)

在一个要注意的点就是结构体的大小怎么来的。


【最后md图床识别不了QAQ,丢一个写好的pdf文档。但是错误挺多的,我也觉得没什么必要下载来看....检查页面源码可以看到对应的图片链接,详情在评论区顶置也说了,不过多赘述了QAQ














stribik 发表于 2024-11-16 14:22

哎,我也不知道为什么这里图片链接的形式显示不出来,其实图片没有那么那么影响...真的需要这里可以查看网页源代码 【view-source:https://www.52pojie.cn/thread-1982267-1-1.html】
对应的<p><p>部分有图片的链接,实在实在觉得麻烦可以直接看pdf...本人水平也就这样QAQ,别骂我呜呜呜。有错误欢迎指正一起学习,只能说本质上其实也就不要想的太复杂。以上...

苏紫方璇 发表于 2024-11-16 16:31

stribik 发表于 2024-11-16 16:07
原来如此...用的是某md软件自带的网站图源,估计防盗链搬了...(晚点我试着传github看看那边图片托管行不 ...

图片也可以传到论坛上的
可以参考这个
https://www.52pojie.cn/forum.php?mod=viewthread&tid=717627&page=1#pid51478900

stribik 发表于 2024-11-16 16:07

苏紫方璇 发表于 2024-11-16 15:30
你这个图片应该是防盗链的问题,f12网络可以看到图片返回403

原来如此...用的是某md软件自带的网站图源,估计防盗链搬了...(晚点我试着传github看看那边图片托管行不行)感谢大佬解答{:1_893:}

苏紫方璇 发表于 2024-11-16 15:30

你这个图片应该是防盗链的问题,f12网络可以看到图片返回403

stribik 发表于 2024-11-16 13:36

zixiangcode 发表于 2024-11-16 09:15
是我的问题吗?文章好像有点显示不全哎?

欸QAQ,好像我的图床崩掉了QAQ,走的是图床连接的形式,但是好像图床没有显示出来...我晚点看看怎么改QAQ

zixiangcode 发表于 2024-11-16 09:15

是我的问题吗?文章好像有点显示不全哎?
页: [1]
查看完整版本: 浅析结构体的内存存储形式