首页 / 资讯中心 / 文章详情

计算机系统基础实验:位操作、Binary Bombs与缓冲区溢出

计算机系统基础实验:位操作、Binary Bombs与缓冲区溢出 ★ FEATURED ARTICLE
简介这份华中科技大学计算机系统基础实验报告面向学习计算机系统导论、数据表示与底层编程的高校学生及自学者围绕整数与浮点数编码、位操作、补码运算、Binary Bombs拆解和缓冲区溢出攻击等实验展开可用于课程实验复盘、期末复习与调试思路梳理。压缩包共1个docx文档约1.6MB按实验报告体例收录实验概述、实验内容、设计源码与总结便于对照C语言函数实现和Linux环境下的操作要点。目前已有1388人学习下载在同类课程资料中具有一定参考价值。文档具体呈现lsbZero、byteNot、byteXor、logicalAnd、rotateLeft等位运算题目的实现思路也记录Binary Bombs逐阶段分析与缓冲区溢出检测的实践过程能帮助读者理解计算机内部如何存储和处理信息并积累排错与问题拆解经验适合需要补齐底层实验短板、对照源码查漏补缺的读者。1. 从一份实验报告说起三个实验正好卡在机器级表示的三道坎上翻这份计算机系统基础实验报告最先吸引我的不是目录而是它把最容易「原理看得懂、代码写不出」的三块内容串成了一条线数据表示、Binary Bombs、缓冲区溢出。报告里既有bits.c里 13 个函数的完整源码和算子预算也有炸弹六个阶段的 gdb 跟踪记录还有栈帧偏移的实测过程。它适合两类人正在做同类实验、被 Max ops 卡住的同学以及想重新过一遍机器级表示、又不想从教材第一章啃起的开发者。下面按「怎么跑起来、参数怎么改、哪里会出问题」的顺序拆。2. bits.c 位操作13 个函数的算子预算怎么分配才不超标数据表示这一实验的难点不在算法思想而在约束。13 个函数分成位操作、补码运算、浮点数三类每类都有Max ops上限和Rating难度分超过上限直接判错哪怕结果对。我在下面先把约束讲清再逐个拆写法最后给出验证流程。2.1 三条硬约束合法算子、Max ops、常量范围dlc是这套实验的规则检查器它不运行你的代码只做静态扫描。它检查的东西比大多数人以为的多算子白名单。每个函数头注释里都写了Legal ops比如lsbZero只允许! ~ ^ | 写了-、*、/、%就报错。算子计数。Max ops统计的是所有出现的运算符实例一个记 1 个移位记 1 个。注意是「实例」x y z算 2 个。常量范围。整数常量一般限制在 0255所以0xff合法0xffffffff不合法得用~0或(131)31造全 1 掩码。控制流限制。位操作和补码类函数禁止if、while、for浮点两题反而允许因为它们是unsigned函数。下表把 13 个函数的预算和核心思路对齐写代码前先看这张表比上来就写更省时间函数类别RatingMax ops核心思路lsbZero位操作15右移丢掉最低位再左移补 0byteNot位操作26造出第 n 字节为 0xff 的掩码异或byteXor位操作220右移取出双字第 n 字节异或后逻辑化logicalAnd位操作320!!两次把任意非零压成 1再相与logicalOr位操作320同上改成相或rotateLeft位操作325造低 n 位全 1 的掩码接住右移搬过来的位parityCheck位操作420折叠异或把 32 位压到 1 位mul2OK补码220比较符号位与次高位是否一致mult3div2补码212乘 3 后处理向零取整的舍入subOK补码320同号相减不溢出异号看结果符号absVal补码410用算术右移得到全 0 或全 1 掩码float_abs浮点210清符号位NaN 例外返回原值float_f2i浮点430拆分阶码按 127/150 分界处理提示先跑./dlc -e bits.c看每个函数实际用了几个算子比肉眼数准。-e会打印每个函数的算子计数超标的函数会单独标注。2.2 位操作与补码从 lsbZero 到 absVal 的写法拆解先说最简单的lsbZero。把最低有效位清 0直觉写法是x ~1但~1是0xfffffffe常量超标于是改用移位右移一位再左移一位。这里有个容易忽略的细节int的右移是算术右移负数右移会补符号位但紧接着左移一位后最低位仍然是 0符号位也回来了所以正负都不受影响。byteNot是这套代码里比较有代表性的写法用 0xff 左移 8n 位造掩码/* * byteNot - 把双字 x 的第 n 个字节按位取反 * n 取 0(最低字节) 到 3(最高字节) * 例: byteNot(0x12345678, 1) 0x1234A978 */ int byteNot(int x, int n) { int y 0xff; /* 单字节全 1 掩码 */ n n 3; /* 字节序号转位偏移1 字节 8 位 */ y y n; /* 掩码平移到目标字节位置 */ x x ^ y; /* 异或 1 即取反其它字节异或 0 不变 */ return x; }逻辑说明异或的性质是「与 1 异或取反与 0 异或保持」所以只要造出一个只有目标字节为全 1 的掩码一次异或就能完成局部取反。参数说明n是字节序号必须先左移 3 位换算成位偏移这是所有字节级操作的固定套路0xff是老式 C 写法等价于255写成十进制同样合法只是可读性差一点。byteXor是同一套路的反向用法先右移把目标字节挪到最低位再和0xff相与切干净最后异或取逻辑值。这里的关键点是 0xff必须在异或之前因为算术右移会把符号位铺满高位不切干净的话两个数的高位污染会直接算进结果里。最后用!!把任意非零值压成 1这个技巧在整套代码里反复出现是绕开if的标准手段。rotateLeft是位操作里最容易写错的一题。循环左移的本质是「左移 n 位丢掉的高位要补到低位去」所以右移32-n位把丢掉的位搬回来再用掩码只保留低 n 位/* * rotateLeft - 把 x 循环左移 n 位, 0 n 31 * 例: rotateLeft(0x87654321, 4) 0x76543218 */ int rotateLeft(int x, int n) { int z; /* (131)31 得到全 1, 左移 n 位后低 n 位为 0, 取反得低 n 位全 1 */ z ~(((1 31) 31) n); /* 32 (~n 1) 即 32 - n, 用补码加法避开减法运算符 */ x ((x (32 (~n 1))) z) (x n); return x; }逻辑说明x n负责把主体左移空出来的低 n 位是 0x (32-n)把溢出的高 n 位搬到最低位但高位会带上符号位的脏数据所以必须 z只留低 n 位两段没有重叠位用加法或按位或结果一样。参数说明32 (~n 1)就是32 - n因为~n 1是n的相反数补码这么写纯粹是为了避开减法运算符算子数也只多花 2 个。掩码z的构造用了(131)31这是造全 1 常量的通用写法比硬写0xffffffff安全。parityCheck判断 1 的个数奇偶思路是折叠把高 16 位和低 16 位异或问题规模减半重复 5 次后所有位的奇偶性汇总到最低位附近。这套代码取巧在最后一步用y 31直接取符号位而不是继续 1省了一个算子。需要注意的是这个写法依赖算术右移对负数的行为在 x86-64 的 gcc 默认设置下没问题换成-funsigned-char之类的极端配置要重新验证。补码类里absVal是最能体现「掩码思维」的一题/* * absVal - 求 x 的绝对值, -TMax x TMax * 例: absVal(-1) 1 */ int absVal(int x) { int y x 31; /* x 为负则 y 全 1, 否则全 0 */ x (y (~x 1)) ((~y) x); /* 负则取 ~x1, 否则保持 x */ return x; }逻辑说明x 31是一个不花钱的分支负数得全 1、非负得全 0y (~x1)在负数时生效~y x在非负时生效两段互斥所以相加就是选择。参数说明~x 1是求相反数的标准写法等价于-x省一个算子。mult3div2和subOK是同类思路前者要额外处理「负数右移向负无穷取整」和「C 语言除法向零取整」的差异后者靠「同号相减必不溢出」这条性质判断。2.3 浮点两题float_abs 与 float_f2i 的 127/150 分界浮点题允许用if和while所以重点从「省算子」变成「把 IEEE 754 的位布局记准」。单精度格式是 1 位符号、8 位阶码、23 位尾数阶码偏置值是 127尾数隐含一个前导 1这三点决定了float_f2i的全部逻辑。原文里float_f2i用 150 做分界这个 150 不是随便来的真实数值等于尾数乘以2^(阶码-127-23)把隐含位移进去之后指数就变成阶码 - 150所以 150 就是「小数点落在尾数最低位」的临界阶码。/* * float_f2i - 把单精度浮点位模式转成 int * 超出范围(含 NaN/无穷)统一返回 0x80000000 */ int float_f2i(unsigned uf) { unsigned num 0x80000000; /* 补上隐含的前导 1, 得到 24 位有效数字 */ int x (uf 0x007fffff) ^ 0x00800000; int order (uf 0x7f800000) 23; /* 取 8 位阶码 */ if (order 158) return num; /* 指数过大, 必溢出 */ if (order 127) return 0; /* 绝对值小于 1, 截断为 0 */ 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); } }逻辑说明(uf 0x007fffff) ^ 0x00800000是在尾数最高位补上隐含的 1异或和按位或结果相同写成异或只是沿用了原文风格。阶码大于 150 说明小数点要往右移尾数左移小于 150 说明要往左移尾数右移右移本身实现的就是截断正好对上 C 语言整数转换向零取整的语义。负数分支多一步取相反数因为补码负数右移是向下取整与向零取整差 1。参数说明order 158是溢出上界order 127直接归零这两个边界值对应2^31和1.0改动任何一个都会让边界用例挂掉。2.4 验证流程dlc 查规则、btest 查结果、driver 查边界写完不是结束这套实验的验证链有三层缺一层就可能出现「自己测全对、交上去全错」# 1. 规则检查, -e 顺便打印每个函数的算子数 ./dlc -e bits.c # 2. 全量测试, 逐函数跑标准用例 make btest ./btest # 3. 单函数调试, -f 指定函数名 ./btest -f float_f2i # 4. 指定输入验证边界值, 后面跟的是函数参数 ./btest -f float_f2i -1 0x4b000000命令说明./dlc -e bits.c只看静态规则不保证逻辑对./btest才跑用例输出里每个函数一行错的会打印期望值和实际值-1表示给函数传一个参数十六进制参数直接写0x前缀。我一般习惯先把dlc跑通再跑btest因为算子超标时btest的输出会混着规则错误排查起来更乱。原文里实验结果是 13 个函数全部通过说明这套写法在标准用例下是站得住的。3. Binary Bombs 拆弹用 gdb 断点和 objdump 反汇编逐阶段推进第二个实验是拆二进制炸弹一个 Linux 可执行程序六个阶段各要一个正确输入输错就打印BOOM!!!并退出。这一实验真正考的是「读汇编 用调试器验证假设」不是靠猜。下面按准备、推进、隐藏阶段三块讲每个阶段给出判断依据而不是直接给答案因为不同炸弹的答案本来就不一样。3.1 拆弹前的三件准备反汇编、断点、输入文件第一件事是把二进制摊开成可读的汇编同时准备好输入文件避免每次运行都手敲# 反汇编到文件, 后面全程以这个文件为主战场 objdump -d bomb asm.txt # 用文本工具定位各阶段入口 grep -n phase_ asm.txt # 准备输入文件, 一行一个阶段的答案 printf The future will be better tomorrow.\n0 1 1 2 3 5\n input.txt # gdb 加载, 先在所有爆炸点和各阶段入口下断点 gdb -q bombgdb 里最值的几条命令建议直接抄进笔记命令用途使用时机break explode_bomb在爆炸函数下断点每个阶段都留着防止程序直接退出break phase_1在阶段入口下断点定位当前阶段的参数准备run input.txt带输入文件运行避免反复手输x/s 0x804a204按字符串查看内存阶段 1 直接读比较串x/6dw $esp按 6 个十进制字查看栈阶段 2 看读入的数字info registers查寄存器快照判断当前走到哪个分支si/ni单步进入 / 单步跳过跟 call 进子函数时用si第二件事是理解断点策略。原文的做法是在每个阶段的开始代码前和explode_bomb前都下断点这个习惯很值前者让你在输入被解析之前就停下能看清栈上原始数据后者保证炸弹不会真的炸掉进程停在爆炸前你可以回头检查寄存器。少了后面这个断点一次输错就得重跑调试节奏全乱。第三件事是先把main读完。main里能看出每个阶段读几行、调用顺序、以及隐藏阶段的触发条件。原文阶段 1 的分析就是靠main里read_line的返回值落点反推出%eax里存的是用户输入再去和0x804a204比较。这个「从调用者看被调用者」的顺序比一上来就硬读phase_1快得多。3.2 六个阶段的推进顺序与判断依据六个阶段的难度是递增的但真正的分水岭在阶段 4前面三个阶段靠静态读汇编就能拿下后面三个必须动态跟。阶段 1 是字符串比较汇编里是一个call strings_not_equal两个参数分别来自%eax用户输入和一个立即数地址。判断依据很直接看movl $0x804a204, 0x4(%esp)这类指令立即数就是被比较串的地址x/s一读就出来。原文读出的结果是The future will be better tomorrow.这是一个完整句子长度超过一般猜测范围说明这题就是考「会不会用调试器读内存」。阶段 2 是循环。先用read_six_numbers这个函数名判断输入是 6 个整数再看循环体里的比较逻辑。原文的分析路径是先确认前两个数必须是 0 和 1再从循环体的加法推出「后一个数是前两个数之和」得到0 1 1 2 3 5。这里的关键判断是循环边界cmp后面跟的立即数决定循环几轮jle和jl的差别决定最后一轮算不算读错一个跳转就往错误方向推。阶段 3 是条件分支加跳转表。这题最容易卡的地方是jmp *0x804a260(,%eax,4)这种间接跳转它按第一个输入的值去跳转表里取地址。判断依据是先用cmpl $0x7, 0x28(%esp)和ja explode_bomb确认第一个输入必须小于 7再假设它是 0顺着跳转表落到某个分支读该分支里的mov $0x76, %eax和cmpl $0x104, 0x2c(%esp)得到字符v和数字 260。原文用 0 作为第一个输入是合理的试探策略跳转表里 0 号分支通常是第一条地址最好算。阶段 4 开始涉及递归调用和栈。这题的输入是两个整数其中一个会作为递归函数的参数。判断依据分两步先用x/s查格式串确认输入类型和个数再跟call进递归函数看递归出口条件。递归题的坑在于出口条件往往依赖参数的取值范围输入稍大就会栈溢出或者走到爆炸分支所以要用 gdb 在递归函数入口反复continue看参数怎么收敛。阶段 5 是指针和字符串。这题的输入通常是一个字符串程序会拿它的每个字符去做下标从一个字符表里取值。判断依据是先找到字符表的基地址再看下标运算的掩码常见是 0xf反推出每个位置需要什么字符。原文没展开但这类题的通用手法是在循环体里下断点每次迭代打印当前下标和取到的值一轮下来就能把映射关系摸清。阶段 6 是链表加结构体。这题最麻烦因为要同时满足「链表按某个字段排序」和「节点顺序符合输入」两个条件。判断依据是先找到链表头节点地址用x/3dw按三字一组把整条链打出来看清每个节点的值和序号再按排序要求反推输入顺序。原文目录里把这一阶段列为「链表/指针/结构」说明它同时考数据结构和指针运算。3.3 隐藏阶段字符串拼接触发与二次分析隐藏阶段不是每个炸弹都有触发条件写在第 4 阶段的解之后在正确解后面再附加一段特定字符串程序检测到就把隐藏阶段拉起来。原文明确写了这个机制所以拆完阶段 4 千万别急着往下走先看phase_4尾部有没有strings_not_equal之类的比较有就说明有隐藏入口。分析方法和前面一样但要注意隐藏阶段的入口函数不在main的调用序列里grep phase_未必能搜到。我的做法是先objdump全文搜explode_bomb的调用点把所有引用爆炸函数的位置列出来再逐个对照看哪个不在已拆阶段里那个就是隐藏阶段。这一步用grep -n explode_bomb asm.txt一行命令就能定位比到处翻汇编快。需要说明的是拆弹的具体答案和二进制密切相关不同环境编译出来的炸弹地址和常量都不同所以这份报告的价值在分析路径而不在答案本身。照抄答案最容易出问题因为地址一变全盘失效把「怎么找地址、怎么设断点、怎么读内存」这套方法学会换一个炸弹照样能拆。4. 栈帧与返回地址缓冲区溢出实验的偏移计算和边界控制第三个实验是缓冲区溢出目录里有但原文没展开内容。这一实验的教学价值在于把「函数调用时栈上发生了什么」从概念变成可测量的数字buf的起始地址在哪、返回地址在哪个偏移、覆盖多少字节才能改到它。下面的做法都在本地教学二进制上完成编译时关闭栈保护目的是观察栈布局不是面向真实目标的手法。4.1 栈帧布局从 buf 到返回地址差多少字节x86 下典型的函数栈帧布局可以用一张表描述清楚这张表是所有偏移计算的依据相对位置内容与溢出的关系%ebp 8起第一个参数、第二个参数一般不动%ebp 4返回地址覆盖目标决定 ret 跳去哪%ebp调用者保存的旧%ebp覆盖它会污染上一层栈帧%ebp - 4起局部变量、保存的寄存器中间可能有 canary%ebp - Nbuf起始地址读入的起点N 由sub指令决定判断 N 的方法在反汇编里函数开头的sub $0xNN, %esp就是给局部变量腾的空间buf一般从%esp或者%ebp - 偏移开始具体看lea指令把哪个地址传给了读入函数。原文里读入函数的第一个参数就是buf地址所以只要在调用读入函数之前停一下print $eax或者p/x $esp就能拿到实测值不用纯靠读汇编推。这里有个容易踩的坑编译器可能插入 canary、可能对齐栈到 16 字节、可能把buf放在%ebp下方任意位置所以「缓冲区大小 32 字节返回地址就在偏移 324」这种算法经常差几个字节。血泪经验是永远用实测偏移不要用结构体大小加 4 硬算。4.2 用 gdb 量偏移三步定位 buf 起点和返回地址量偏移的流程固定三步每步都用一个 gdb 命令落地# 1. 在读入函数调用处下断点, 通常是 gets / read / fgets gdb -q ./vuln (gdb) break *0x080484a0 # 地址取自 objdump 里 call 读入函数那条指令 (gdb) run payload.txt # 2. 断下后拿 buf 起始地址, 两种等价写法 (gdb) p/x $eax # 读入函数第一个参数常放在 eax 或栈上 (gdb) p/x $esp # 或者直接看当前栈顶 # 3. 拿返回地址位置, 用 info frame 一次看全 (gdb) info frame (gdb) p/x $ebp命令说明break *地址是硬编码地址下断点比break 函数名更准因为同名函数可能有多个info frame会直接打印saved eip的地址这个值减去buf起始地址就是精确偏移比数汇编靠谱得多。p/x用十六进制打印避免地址以十进制显示时看错位。我一般会把这三个值抄在纸上buf起始、%ebp、saved eip后面构造 payload 全用这三个数。偏移拿到之后返回地址本身的值也要确认因为它决定了覆盖后跳去哪。教学二进制通常会提供一个touch之类的函数反汇编里搜一下就能拿到入口地址这个地址就是 payload 末尾要填的 4 字节。4.3 构造 payload小端序、对齐与不可打印字节有了偏移和目标地址payload 就是「填充字节 目标地址」的拼接。用 Python 写比手搓十六进制安全因为小端序和长度对齐都交给库处理import struct, sys offset 44 # 实测偏移: buf 起点到 saved eip 的字节数 target 0x08048456 # 目标函数入口, 取自 objdump # 小端序打包 4 字节地址, I 表示小端无符号 32 位 payload bA * offset struct.pack(I, target) # 写到文件, 注意用二进制模式, 避免换行符被改写 with open(payload.txt, wb) as f: f.write(payload b\n) sys.stdout.write(payload length %d\n % len(payload))逻辑说明bA * offset先精确填满buf到返回地址之间的所有字节多一个少一个都会让返回地址错位struct.pack(I, target)把地址按小端序写成 4 字节x86 是小端机器写成大端就跳到错误地址。参数说明offset必须用上一步实测值target必须是目标函数的第一条指令地址取函数中间某条指令也能跳但可能缺栈帧准备导致崩溃。文件末尾补一个换行是有意的很多读入函数靠换行结束输入不补会一直等。注意如果目标地址或偏移里含0x0a换行、0x20空格这类字节某些读入函数会提前截断。遇到这种情况要么换目标地址跳到目标函数的下一条指令要么换用二进制安全的读入方式。实验做完之后值得顺手看一眼防护机制的效果同样的代码加-fstack-protector-strong重新编译再用同样的 payload 跑一遍程序会在__stack_chk_fail处中止。这一步比单纯「溢出成功」更有价值因为它把「为什么现代编译器默认开栈保护」从一句话变成了一个可复现的现象。再开-z noexecstack和系统级地址随机化同一条 payload 会连续失败这几个开关就是缓冲区溢出从「能打」变成「难打」的全部原因。5. 避坑记录从 dlc 报错到炸弹爆炸的五个现场这三个实验的错误信息都不算友好dlc只报行号和规则名炸弹只打印BOOM!!!溢出实验直接段错误。下面五条是我和身边人反复踩过的按「现象 → 原因 → 解决」写遇到了直接对号入座。5.1 dlc 报「bad expression」但代码看着没错现象./dlc bits.c报某行 bad expression人眼检查那行只有一个x 0xff。原因常量超范围0xff是 255 没问题但很多人顺手写0xffffff00这种掩码超过 255 就被判非法。解决用移位和取反造大常量~0xff或(131)31 8既合法又不增加太多算子。5.2 btest 全过但提交后算子超标现象本地./btest13 个函数全绿助教脚本判 0 分。原因只跑了功能测试没跑规则测试btest不检查Max ops算子数和合法算子只有dlc管。解决把./dlc -e bits.c加进提交前的固定流程输出里每个函数的算子数都要低于上限留 12 个余量防止不同版本dlc计数差异。5.3 炸弹阶段 2 输入对了还是炸现象按斐波那契推出来的 6 个数字输进去程序仍然BOOM!!!。原因读入函数对分隔符敏感有的是空格分隔有的是逗号还有的要求一行内恰好 6 个数字不能多不能少。解决先用x/s查read_six_numbers里的格式串确认分隔符再在read_six_numbers返回处下断点x/6dw看栈上实际读到了几个数读少了就是格式不对。5.4 gdb 断点打在 phase 函数上但程序先炸了现象break phase_3之后run没停到断点直接输出爆炸信息退出。原因前面某个阶段的输入是错的程序在阶段 2 就炸了根本走不到阶段 3也可能是断点地址被 PIE 重定位改变地址对不上。解决先break explode_bomb兜底再逐个阶段用continue推进看停在哪一步同时用info files确认二进制是不是 PIE是的话改用break phase_3这种符号断点而不是硬编码地址。5.5 溢出 payload 长度对了但跳转地址错位现象偏移算得和info frame一致payload 长度也对但ret之后跳到非法地址。原因目标地址里含有0x0a被gets之类的行读入函数当成换行提前截断实际写入的字节数比预期少。解决先x/8bx看栈上实际写入的字节确认有没有被截断再换目标地址跳到目标函数里不含0x0a的那条指令上一般往下挪一两条就能绕开。顺带养成习惯payload 生成脚本里加一行打印长度和十六进制内容出问题时省一半排查时间。6. 进阶把三个实验串成一条自动验证链单次跑通和稳定复现是两件事。我在带人做这套实验时发现绝大多数「昨天还对今天就不对」的情况都是因为验证步骤靠手敲中间漏了一步。把三个实验的验证串成一条链每次改完代码跑一遍比事后对着报错猜要省时间得多。具体做法是写一个入口脚本把规则检查、功能测试、反汇编、payload 生成四件事固定下来#!/bin/bash # 一次跑完三个实验的验证步骤, 任一步失败就退出 set -e echo [1/4] bits.c 规则检查 ./dlc -e bits.c echo [2/4] bits.c 功能测试 make -s btest ./btest echo [3/4] 炸弹反汇编刷新 objdump -d bomb asm.txt grep -c explode_bomb asm.txt echo [4/4] 溢出 payload 重建 python3 gen_payload.py ls -l payload.txt脚本说明set -e让任一步失败就停避免带着错误往下跑make -s静默编译输出干净grep -c顺手统计爆炸函数引用数个数变了往往意味着二进制换过之前的断点地址全部作废最后一步重建 payload 并打印文件大小长度异常一眼能看出来。这个脚本不长但它把「我改了什么、哪一步开始不对」变成了可定位的信息。另外一个值得坚持的习惯是每次实验开始前先把当前二进制的关键地址抄进一个文本文件包括六个阶段的入口、explode_bomb、字符串表基址、buf偏移、目标函数地址。这份清单看起来笨但换环境重编一次二进制之后所有硬编码地址都会变有清单就能逐条对照更新没有就只能从头再找一遍。从那以后我每次动这类底层实验都强制先跑一遍dlc -e和info frame把算子数和栈偏移这两个数字记下来再写代码翻车次数明显少了。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站