资讯动态

计算机系统基础实验全解析:位运算、Binary Bombs与缓冲区溢出

发布时间:2026/10/20 16:11:08 来源:尧图企业网站定制
简介这份华中科技大学计算机系统基础实验报告面向高校计算机专业学生及系统底层原理学习者围绕数据表示、位操作、补码运算与浮点数操作等核心内容展开帮助读者通过动手实践理解计算机内部如何处理和存储信息。资源包内含1个docx文档大小约1.6MB完整收录了课程实验报告涵盖实验概述、实验设计、源码实现与实验总结等模块涉及lsbZero、byteNot、byteXor、logicalAnd等位运算函数的实现思路以及Binary Bombs拆解和缓冲区溢出攻击分析。目前已有1387人学习下载适合正在修读计算机系统基础课程或准备相关实验的学生参考可借助报告中的源码注释与设计说明掌握有限运算条件下实现指定功能的技巧并加深对二进制编码、补码表示及安全漏洞原理的理解为后续系统编程与软件开发打下基础。1. 从一份实验报告说起这套计算机系统基础实验到底能跑出什么很多人第一次接触计算机系统基础都是从“写代码”直接跳到“看汇编”这一步翻车的。这份实验报告把三个硬核实验串成了一条线数据表示、Binary Bombs 拆弹、缓冲区溢出攻击。它不是那种只贴结论的文档而是把 bits.c 里 13 个位运算函数的实现、gdb 反汇编拆炸弹的完整过程、以及栈帧覆盖的验证步骤都留了下来。适合谁正在上计算机系统基础课、需要交实验报告的学生或者想补一补底层知识、但不想啃厚教材的开发者。你拿到手就能照着复现也能直接改造成自己的实验记录。2. 实验一拆解bits.c 里 13 个位运算函数的实现套路2.1 数据表示实验的约束与评分机制这个实验的核心不是“写出来就行”而是在严格的运算符限制下写出来。每个函数都标了 Legal ops 和 Max ops比如 lsbZero 只允许用! ~ ^ | 最多 5 个操作符。评分从 1 到 4 分分越高越难。实验环境是 Linux C编译后跑 btest 验证正确性。我一般会先把所有函数的约束抄到一张纸上写一个勾一个不然很容易在某个函数上超了操作符数量还不自知。2.2 位操作类函数lsbZero、byteNot、byteXor先看最简单的 lsbZero把最低有效位置 0int lsbZero(int x) { // x 右移一位再左移一位最低位自然变 0 x x 1; x x 1; return x; }逻辑很直白右移丢掉最低位左移补回 0。参数 x 是任意整数返回值就是最低位清零后的结果。注意这里用的是算术右移还是逻辑右移不影响结果因为最后左移会把高位补回来。byteNot 稍微绕一点要把第 n 个字节取反int byteNot(int x, int n) { // 构造一个第 n 个字节全 1、其余为 0 的掩码 int y 0xff; n n 3; // n 乘以 8换算成位数 y y n; // 掩码移到第 n 个字节 x (x ^ y); // 异或实现按位取反 return x; }这里 n 的范围是 0 到 3分别对应最低字节到最高字节。y 初始为 0xff左移 n 位后正好覆盖目标字节。异或的特性是“相同为 0不同为 1”所以和全 1 异或就等于取反。byteXor 比较两个数第 n 个字节是否相同int byteXor(int x, int y, int n) { n n 3; x x n; y y n; x x 0xff; // 只保留最低字节 y y 0xff; return !!(x ^ y); // 异或后转成逻辑 0 或 1 }先把 x 和 y 都右移到目标字节落到最低位然后用 0xff 掩码截取。异或结果为 0 说明相同非 0 说明不同再用!!归一化成 0 或 1。2.3 补码与逻辑运算logicalAnd、logicalOr、rotateLeftlogicalAnd 和 logicalOr 考察的是怎么用位运算模拟逻辑运算int logicalAnd(int x, int y) { // 先把 x 和 y 转成逻辑值再按位与 x (!(!x)) (!(!y)); return x; } int logicalOr(int x, int y) { x (!(!x)) | (!(!y)); return x; }!(!x)的作用是把任意非零值变成 1零还是 0。然后再做按位与或按位或结果就是逻辑运算的结果。这里有个细节不能直接写x y因为实验禁止使用逻辑运算符。rotateLeft 是左旋比普通左移多了一步“把移出去的高位补到低位”int rotateLeft(int x, int n) { int z; // 构造低 n 位为 1、高位为 0 的掩码 z ~(((1 31) 31) n); x ((x (32 (~n 1))) z) (x n); return x; }(1 31) 31得到全 1 的掩码左移 n 位后取反就得到低 n 位为 1 的 z。32 (~n 1)等价于32 - n用来把 x 的高 n 位右移到低位。最后两部分相加就是左旋结果。注意 n 的范围是 0 到 31n 为 0 时 z 全 0结果就是 x 本身。2.4 溢出判断与浮点数mul2OK、subOK、float_f2i溢出判断是补码运算里的重点。mul2OK 判断 2*x 是否溢出int mul2OK(int x) { // 比较第 31 位和第 30 位是否相同 int m; m ((x 31) 0x1) ^ ((x 30) 0x1); return m ^ 0x1; }2*x 等价于左移一位。左移后新的符号位是原来的第 30 位如果它和原来的符号位不同说明符号变了溢出。所以取第 31 位和第 30 位异或再和 1 异或得到 1 表示不溢出0 表示溢出。subOK 判断 x-y 是否溢出int subOK(int x, int y) { int m (x 31) 1; int n (y 31) 1; // 符号不同且结果符号和 x 不同则溢出 x (m ^ n) (m ^ (((x (~y 1)) 31) 1)); return (!x); }~y 1是求 -y 的补码。溢出条件是两个操作数符号不同且结果符号和被减数符号不同。这里用异或和与运算把条件串起来最后取反返回。float_f2i 是浮点数转整数要处理阶码和尾数int float_f2i(unsigned uf) { unsigned num 0x80000000; int x (uf 0x007fffff) ^ 0x00800000; // 补上隐含的 1 int order (uf 0x7f800000) 23; // 提取阶码 if (order 158) return num; // 超出 int 范围 if (order 127) return 0; // 小于 1 else if (((uf 31) 1) 1) { // 负数 if (order 150) return ~(x (order - 150)) 1; else return ~(x (150 - order)) 1; } else { // 正数 if (order 150) return x (order - 150); else return x (150 - order); } }阶码 127 对应 2^0150 对应 2^23。order 大于 158 说明数值超过 int 最大值返回 0x80000000。小于 127 说明绝对值小于 1返回 0。负数用补码转换正数直接移位。这里 order 和 150 的比较决定了小数点移动方向。3. Binary Bombs 实战用 gdb 和 objdump 拆掉六个阶段3.1 反汇编准备与阶段 1 字符串比较拆炸弹的第一步是把可执行文件反汇编objdump -d bomb asm.txt然后在 asm.txt 里找 phase_1 的位置。阶段 1 是字符串比较核心是找到和输入比较的那个字符串地址。在 phase_1 里看到调用 strings_not_equal参数来自栈上的两个地址。回到 main 函数发现其中一个地址存的是 read_line 的返回值也就是用户输入。另一个地址是 0x804a204用 gdb 查看gdb bomb (gdb) x/s 0x804a204输出是The future will be better tomorrow.这就是阶段 1 的答案。这一步的关键是理解函数调用时参数怎么通过栈传递以及怎么用 gdb 查看内存中的字符串。3.2 阶段 2 循环与阶段 3 条件分支阶段 2 是循环。反汇编里看到 read_six_numbers说明要输入 6 个数字。代码里先比较第一个和第二个数要求分别是 0 和 1。然后进入一个循环每次取前两个数之和作为下一个数。所以序列是 0, 1, 1, 2, 3, 5。阶段 3 是条件分支代码里有cmpl $0x7, 0x28(%esp)和ja跳转说明第一个输入必须小于 7。然后是一个跳转表jmp *0x804a260(,%eax,4)根据第一个数跳转到不同分支。假设输入 0跳到对应分支后看到mov $0x76, %eax和cmpl $0x104, 0x2c(%esp)所以第二个字符是 vASCII 0x76第三个数是 2600x104。这里用 gdb 单步跟踪跳转表是最稳的方法直接看汇编容易漏掉分支。3.3 阶段 4 递归调用与阶段 5 指针阶段 4 涉及递归。先用x/sb 0x804a3cf查看输入格式发现是两个整数。phase_4 里调用了一个递归函数 func4需要分析递归的终止条件和返回值。常见做法是在 func4 入口设断点观察每次递归的参数变化反推出满足条件的输入。阶段 5 是指针。代码里有一串字符通过某种映射关系取字符。反汇编里看到movzbl指令和数组索引说明是根据输入的数字去取对应位置的字符。用 gdb 查看那个数组的地址把内容打印出来再根据索引规则拼出字符串。3.4 阶段 6 链表与隐藏阶段阶段 6 是链表/指针/结构。代码里先读入 6 个数字然后根据这些数字对链表节点排序。每个节点有一个值和下一个节点的地址。反汇编里能看到节点结构的偏移量比如0x8(%eax)存的是下一个节点指针。需要输入一组数字使得排序后的链表值按降序排列。用 gdb 查看链表头地址逐个打印节点值就能反推出输入顺序。隐藏阶段只有在阶段 4 的解后面附加特定字符串才会出现。这个字符串通常藏在反汇编的某个数据段里用strings bomb或者查看.rodata段能找到线索。4. 缓冲区溢出攻击从栈帧布局到覆盖返回地址4.1 实验原理与栈帧结构缓冲区溢出的本质是程序向缓冲区写入超过其容量的数据覆盖了相邻的栈空间。在 x86 栈帧里局部变量在低地址返回地址在高地址。如果缓冲区没有边界检查多写的数据就会覆盖返回地址。当函数返回时CPU 跳转到被覆盖的返回地址从而改变程序执行流。这个实验通常提供一个有漏洞的可执行文件要求通过输入特定长度的字符串覆盖返回地址跳转到目标函数。实验环境是 Linux需要关闭 ASLR 或者用 gdb 查看实际地址。4.2 定位溢出点与构造攻击字符串先用 gdb 运行程序输入一个长字符串观察崩溃时的 EIP 值(gdb) run (gdb) info registers eip如果 EIP 是 0x41414141即 AAAA说明偏移量就是缓冲区大小加上栈上其他局部变量的空间。常见做法是用 pattern_create 生成一个唯一序列崩溃后看 EIP 对应序列中的位置精确算出偏移。然后构造攻击字符串填充字节 目标地址。目标地址通常是某个函数比如touch或secret的入口地址用objdump -d或 gdb 的p命令获取。python -c print A*offset \xef\xbe\xad\xde注意地址是小端序要按字节倒过来写。如果实验环境有栈保护或地址随机化可能需要额外处理但基础实验一般会关掉这些保护。4.3 验证攻击结果与常见失败原因攻击成功后程序会跳转到目标函数打印出成功信息或者拿到 shell。如果失败常见原因有偏移量算错、地址包含空字节、栈对齐问题。我一般会在 gdb 里单步跟踪ret指令看 EIP 到底跳到了哪里。如果跳到了非法地址说明地址写错了如果跳到了正确地址但没执行可能是栈不可执行或者权限问题。5. 避坑与排查拆炸弹和位运算实验里最容易翻车的几个点5.1 位运算函数超了 Max ops 还不自知现象btest 报错说某个函数操作符数量超了。原因写的时候没数或者用了和-但没意识到它们也算操作符。解决每写一个函数就数一遍把~n 1这种固定搭配记成两个操作符。我一般会在代码旁边注释当前用了几个操作符。5.2 gdb 查看字符串地址时输出乱码现象x/s 0x804a204显示的不是可读字符串。原因地址写错了或者该地址存的是指针而不是字符数组。解决先用x/wx看这个地址存的是什么如果是一个地址值再用x/s解引用。另外注意大小端有时候需要x/s *0x804a204。5.3 阶段 3 跳转表分析时跳错分支现象输入第一个数后程序直接爆炸没有进入预期分支。原因跳转表的基地址或索引计算错了或者第一个数超出了 0 到 6 的范围。解决在jmp指令处设断点看%eax的实际值再对照跳转表地址逐个验证。用x/8wx 0x804a260查看跳转表内容。5.4 缓冲区溢出偏移量算错现象EIP 被覆盖成了部分填充字符比如 0x41414142。原因偏移量差了一个字节可能是缓冲区大小记错了或者栈上还有对齐填充。解决用 pattern_create 和 pattern_offset 精确计算不要靠猜。另外注意字符串结尾的空字节也会占一个位置。5.5 浮点数转整数时阶码边界处理错误现象float_f2i 在阶码等于 158 或 150 时返回错误结果。原因边界条件没考虑清楚比如 order 等于 158 时应该返回 0x80000000但代码里用了大于号。解决把阶码和临界值的关系画成数轴逐个确认大于、小于、等于分别对应什么操作。测试时用 btest 的边界用例覆盖。6. 二次开发与改造把这套实验报告变成自己的学习工具这份实验报告最大的价值不是答案本身而是它展示了一条从“写位运算”到“读汇编”再到“改执行流”的完整路径。如果你想二次开发我建议从三个方向入手。第一把 bits.c 里的函数改写成 Python 版本用位运算模拟同样的逻辑。比如 rotateLeft 在 Python 里可以直接用((x n) | (x (32 - n))) 0xffffffff但你要理解为什么 C 版本要那么绕。改写的过程能帮你把补码和移位的细节吃透。第二给 Binary Bombs 写一个自动化拆弹脚本。用 pwntools 或者纯 Python 的 subprocess 模块把每个阶段的答案硬编码进去然后自动输入、自动验证。更进一步可以尝试用 angr 这样的符号执行工具自动求解虽然对新手来说门槛高但跑通一次对逆向工程的理解会深很多。第三把缓冲区溢出实验改造成 CTF 风格的题目。自己写一个有漏洞的 C 程序编译时关掉栈保护然后尝试用 ROP 链绕过不可执行栈的限制。这个改造过程会逼着你去看更多的汇编指令和调用约定。验证改造是否成功的方法很简单跑一遍 btest 看位运算函数是否全过跑一遍 bomb 看是否能拆到隐藏阶段跑一遍溢出程序看是否能拿到 shell。我一般会在每个阶段结束后用 git 打一个 tag这样改崩了可以随时回滚。从那以后我每次做底层实验都会先把反汇编输出到文件再用 gdb 逐行对照绝不凭感觉猜地址。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑