浅析结构体的内存存储形式
本帖最后由 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
】
哎,我也不知道为什么这里图片链接的形式显示不出来,其实图片没有那么那么影响...真的需要这里可以查看网页源代码 【view-source:https://www.52pojie.cn/thread-1982267-1-1.html】
对应的<p><p>部分有图片的链接,实在实在觉得麻烦可以直接看pdf...本人水平也就这样QAQ,别骂我呜呜呜。有错误欢迎指正一起学习,只能说本质上其实也就不要想的太复杂。以上... stribik 发表于 2024-11-16 16:07
原来如此...用的是某md软件自带的网站图源,估计防盗链搬了...(晚点我试着传github看看那边图片托管行不 ...
图片也可以传到论坛上的
可以参考这个
https://www.52pojie.cn/forum.php?mod=viewthread&tid=717627&page=1#pid51478900 苏紫方璇 发表于 2024-11-16 15:30
你这个图片应该是防盗链的问题,f12网络可以看到图片返回403
原来如此...用的是某md软件自带的网站图源,估计防盗链搬了...(晚点我试着传github看看那边图片托管行不行)感谢大佬解答{:1_893:} 你这个图片应该是防盗链的问题,f12网络可以看到图片返回403 zixiangcode 发表于 2024-11-16 09:15
是我的问题吗?文章好像有点显示不全哎?
欸QAQ,好像我的图床崩掉了QAQ,走的是图床连接的形式,但是好像图床没有显示出来...我晚点看看怎么改QAQ 是我的问题吗?文章好像有点显示不全哎?
页:
[1]