88
99栈帧相关实验。
1010
11- 本学期,我们仍然将金老师 ICS 第三个 Lab 回炉重造,添加更多讲解和小工具提示 ,以加深各位同学对栈帧和协程的理解,并探索一些相关应用,丰富同学们的知识面。
11+ 本学期,我们仍然将金老师 ICS 第三个 Lab 回炉重造,减轻代码工作量并添加更多讲解和提示 ,以加深各位同学对栈帧和协程的理解,并探索一些相关应用,丰富同学们的知识面。
1212
1313本次Lab由五个部分组成,含三个主题:
1414
@@ -136,12 +136,17 @@ void foo() {
136136
137137> [! tip]
138138>
139- > 注意 cmd 字符串对应地址处的值在输入完后不变;
139+ > ** 注意** : cmd 字符串对应地址处的值在输入完后不变;
140+ >
140141> 我们的目标类似于执行 `system(" ./malware" )` ,如果你已经构造成功了 payload 使程序执行 `system(" ./malware" )` ,但程序在 `system` 函数的内部崩溃了,这在我们的预期内。** 也就是说,只需要见到 `You have successfully detonated the bomb! Congratulations!` 这一行就算通过实验。**
142+ >
141143> 造成这种情况的原因是:`system` 内部某些汇编语句对栈的对齐要求很高,如果没有对齐至 0x 10 ,就会导致程序崩溃。如果你使用 GDB 进行调试,你就可以看到这几条非常“挑剔”的汇编指令。
144+ >
142145> 在正常执行一个函数时,`rsp` 寄存器是向 0x 10 对齐的;调用某一个函数时使用的是 `call` 指令,会往栈上压入一个 8 字节的返回地址(这里就破坏了对齐),然后跳转到函数的起始位置。因此,每个函数都会假设自己刚刚被调用时,`rsp` 寄存器是不向 0x 10 对齐的。
146+ >
143147> 然而,我们通过劫持返回地址调用某个函数时,我们并没有使用 `call` 指令,而是直接跳转到了函数的开头。这就导致函数开始时,栈反而向 0x 10 对齐了,这破坏了 `system` 函数的假设,导致程序崩溃。
144- > 解决办法很简单:** 不要直接调用 `system` 函数,而是调用一个中间函数,并且跳过函数开头的一条 `push rbp` 指令。** 这类似于手动破坏栈的对齐,使得 `system` 函数可以正常执行。
148+ >
149+ > 解决办法很简单:不要直接调用 `system` 函数,而是调用一个中间函数,并且跳过函数开头的一条 `push rbp` 指令。这类似于手动破坏栈的对齐,使得 `system` 函数可以正常执行。
145150
146151# ## 实验之后的思考题
147152
@@ -173,18 +178,19 @@ void foo() {
173178
174179** Task 2.1 ** `dark- calc` 有一个未使用的 `grade_eval` 函数。尝试在阅读代码后,通过 `payload_dark.py` 构建新的 payload,** 在 GDB 中** 导入到程序 `dark- calc` 执行 `grade_eval` ,输出大家理想的成绩!** 提交 GDB 运行结果截图(有输出语句 `Hope A will be your grade!` 即可)与 `payload_dark.py` 作为评分依据。无自动评分。**
175180
176- ** Problem 2.1 ** 查询资料,解释为什么不存在< s> (TA 没有发现)< / s> 通过直接执行 `./ dark- calc < payload_dark` 或 `python3 payload_dark.py | ./ dark- calc` 执行 `grade_eval` 的方法。
177-
178181> [! tip]
179182>
180183> 可以用与 Task 1.1 相似的方法保存 payload 并进行调试。
181184>
182- > 在 GDB 中,默认程序的栈空间位置固定,我们需要在栈空间内执行代码,期望效果是** 赋值输入参数** 并** 调用函数** 。
185+ > 在 GDB 中,默认程序的栈空间位置在** 一次重启期间** 固定。
186+ > 我们需要在栈空间内执行代码,期望效果是** 赋值输入参数** 并** 调用函数** 。
183187>
184188> 大家学过 CSAPP ,打过 BombLab,想必对常用 x86- 64 汇编指令的机器码有一些了解,此处给出 mov 和 jmp 指令的机器码:
185- > - 64 位立即数移动 `mov $ imm, % rxx` :`48 c7 [Mod+ Reg+ R/ M](1 byte) [imm](4 byte)` ,`Mod` 指定寄存器模式(`11 ` 为寄存器寻址),`Reg` 源操作数(这里是立即数,所以置为 0 ),`R/ M` 指定目标寄存器;
186- > - 跳转到 `rxx` 寄存器指向的地址`jmp *% rxx` :`ff [Mod+ Reg+ R/ M](1 byte)` , `Mod` 指定寄存器模式(`11 ` 为寄存器寻址),`Reg` 设为 `100 ` 指定此指令为 `jmp` 指令,`R/ M` 指定目标寄存器;
187- > - < s> 善用人工智能工具编写汇编代码。< / s>
189+ >
190+ > - 64 位立即数移动 `mov $ imm, % rxx` :`48 c7 [Mod+ Reg+ R/ M](1 byte) [imm](4 byte)` ,`Mod` 指定寄存器模式(2 位,`11 ` 为寄存器寻址),`Reg` 源操作数(3 位,这里是立即数,所以置为 0 ),`R/ M` 指定目标寄存器(3 位);
191+ > - 跳转到 `rxx` 寄存器指向的地址`jmp *% rxx` :`ff [Mod+ Reg+ R/ M](1 byte)` , `Mod` 指定寄存器模式(2 位,`11 ` 为寄存器寻址),`Reg` 设为 `100 ` 指定此指令为 `jmp` 指令(3 位,此处代表指令模式选择值),`R/ M` 指定目标寄存器(3 位);
192+ >
193+ > < s> 善用人工智能工具编写汇编/ 机器代码(逃< / s>
188194
189195# # 三、栈溢出的防御
190196
@@ -207,16 +213,22 @@ gcc -fno-pie -no-pie -o dark-calc-my dark-calc.c
207213
208214** Problem 3.1** 这种防御机制是否能够彻底“防御”栈溢出漏洞?
209215
210- ** Problem 3.2** 尝试查询资料,复原出之前实验中 ` dark-calc ` 的编译指令(有一个关键参数)。
216+ ** Problem 3.2** 尝试查询资料,复原出之前实验中 ` dark-calc ` 的编译指令。
217+
218+ > [ !tip]
219+ > 通过特定命令行参数,可以不开启防御机制;
220+ > 为触发可写栈空间漏洞,编译时包含了额外文件,不仅包含 ` dark-calc.c ` 。
211221
212- ## 四、栈帧的更多应用(协程)
222+ ## 四、栈帧的更多应用——协程
213223
214224> 改编自 2024 StackLab 的第三部分(源于 2022 年 Kieray Lab),减少协程部分的代码工作量,替换为阅读思考题。
215225
216226在前两项任务中,我们已经加深了对程序运行时的栈帧的认识,接下来,我们使用栈帧和一定的汇编语言,来给 C 语言实现一些更加现代的功能吧。
217227
218228在本实验中,你可以在 coroutine 文件夹下,使用 ` make clean && make ` 来编译代码,使用 ` ./program ` 来运行代码。
219229
230+ ** 注意** :本实验中,各测试** 默认注释掉** ,需手动删除 ` main.cpp ` 中的注释符号来开启;提交时,请留下你能通过的测试,** 将其他测试注释掉** 。
231+
220232### 前言
221233
222234> 在本实验中,你并不需要清晰地知道进程和线程的区别。你可以理解为一个进程可以开启多个线程,让 CPU 的不同核心同时计算不同的功能。
@@ -333,7 +345,7 @@ funcB:
333345
334346接下来,我们将通过汇编实现 save 和 restore 的功能(显然,不考虑汇编和调用库函数的纯 C 语言难以实现这个功能),不过在此之前我们先着手用汇编语言写一个简单的函数吧。
335347
336- ** Problem 4.5** 请用不超过5条汇编指令<sup >2</sup >实现函数 naive_func,直接写在实验报告中。该函数的功能为:将函数的返回地址保存到第一个参数所指定的内存地址,然后返回0 。可参考伪代码:
348+ ** Problem 4.5** 请用不超过5条汇编指令<sup >2</sup >实现函数 naive_func,直接写在实验报告中。该函数的功能为:将函数的返回地址保存到第一个参数所指定的内存地址,然后返回 0 。可参考伪代码:
337349
338350``` pseudocode
339351naive_func(void **p):
@@ -427,8 +439,9 @@ catch{
427439** Task 4.2** 请在 ` context.c ` 中实现函数 ` __err_stk_push ` 和 ` __err_stk_pop ` 。如果一切顺利,在完成本节内容后,你将能通过 test3、test4 和 test5。
428440
429441> [ !tip]
442+ >
430443> C 语言提供了 cleanup 属性,被添加该属性的变量会在生命周期结束时执行对应的函数,可以理解为类似析构函数的功能,可参考[ 这里] ( https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html ) 。我们实现了 ` __err_cleanup ` 函数作为对 ` __err_try ` 执行清理操作的接口。
431- > 可以阅读 ` context.h ` 中对 ` try ` 、` catch ` 和 ` throw ` 的宏定义,帮助理解错误处理栈的应用场景,为接下来的 Task 4.3 做准备。
444+ > ** 可以阅读 ` context.h ` 中对 ` try ` 、` catch ` 和 ` throw ` 的宏定义** ,帮助理解错误处理栈的应用场景,为接下来的 Task 4.3 做准备。
432445
433446### 基于上下文切换的生成器(协程)
434447
@@ -495,9 +508,11 @@ funcB:
495508
496509在已给出的 ` __generator ` 结构体中,我们使用 ` data ` 变量来传输 ` yield ` 和 ` send ` 的参数。为方便起见,我们将程序的主函数也视作一个generator(只不过你不需要手动给这个generator制作一个栈空间和初始上下文),因此全局变量 ` __now_gen ` 被初始化为了 ` &__main_gen ` ,且每个generator都有自己的异常处理栈。
497510
498- ** Task 4.3** 请实现 ` generator ` 构造函数;完成此内容后,你将能通过 test6、test7 和 test8。
511+ ** Task 4.3** 根据自己编写的上下文保存和恢复规则,参照样例解释,实现 ` generator ` 构造函数的** 上下文构建部分** ;完成此内容后,你将能通过 test6、test7 和 test8。
512+
513+ ** Problem 4.3** 请阅读 ` context.c ` 中的函数 ` send ` 、 ` yield ` 、 ` back_to_reality ` ,` context.h ` 中对 ` try ` 、` catch ` 和 ` throw ` 的宏定义,并在报告中解释这些函数和宏定义的功能。
499514
500- ** Problem 4.3 ** 请阅读 ` context.c ` 中的函数 ` send ` , ` yield ` , ` context.h ` 中对 ` try ` 、 ` catch ` 和 ` throw ` 的宏定义,并在报告中解释这些函数和宏定义的实现细节 。
515+ ** Problem 4.4 ** 任选 test6、test7、test8 中的一个,使用 GDB 动态调试,运用 ` x/10gx $rsp ` 指令查看至少两种不同位置处开始的栈帧或是伪栈帧并截图,体会协程中开辟虚拟栈空间的设计 。
501516
502517### 协程的一些实际应用
503518
0 commit comments