程序的完整流程
- 接收输入字符串
- v10 !=13 校验
- 校验失败→输出 error! 退出
- 校验成功→调用 rand () 生成 13 位数字
- 拼接成 NSSCTF {数字串} 输出
设置断点
if ( v10 != 13 ) { puts("error!"); exit(0); } puts("The length of flag is 13"); srand(seed); printf("NSSCTF{");
- 给
0x4008d8(v10!=13)设断点
在程序走至此处时
将v10改为13 - 给
0x400921(rand ())设断点
让程序每次生成一个数字就停,抄下这个数字,再让它继续生成下一个。
===调试步骤===
打开 WSL Kali 的终端
打开 GDB 并加载程序
cd /mnt/c/Users/ZZH/Desktop
ls
gdb ./string
步骤 1:给 rand () 设断点
- 执行下方代码反汇编flag函数
disas flag
在 GDB 中输入c(continue),回车,即可显示后续的汇编指令,直到找到包含cmp $0xd, %eax(0xd是 13 的十六进制)的行,就是v10 !=13的判断指令。
0x00000000004008d8 <+271>: cmpl $0xd,-0x1c(%rbp)
- 给校验判断处设断点(固定的地址)
b *0x4008d8
👉 这个地址就是代码里v10 !=13的判断行,设断点后程序会在校验前停下来。
- 继续运行程序
r
- 程序会提示你输入字符串,随便输一串(比如
1234567890123),按回车 —— 程序会停在0x4008d8这个断点处。
步骤 2:修改 v10 的值为 13(使校验通过)
- 复制下面这行,粘贴到
(gdb)后回车:
set {int}($rbp-0x1c) = 13
👉 v10存在rbp-0x1c这个内存位置,直接改成 13,就能跳过error!。
通过disas flag看到v10存在-0x1c(%rbp)这个位置,所以用$rbp-0x1c定位它
👉 $rbp:是 CPU 的一个寄存器,寄存器,必须加$
-0x1c:是 “偏移量”
例如:改小数:set {float}($rbp-0x8) = 3.14
步骤 3:继续运行到 rand () 断点
- 先给
rand()设断点,再继续程序:
b *0x400921 # rand()调用的地址 c # 继续运行
步骤 4:循环拿 13 位数字
- 依次按顺序执行如下程序
每执行finish会获取rand()的返回值;
p $rax %8 +1会输出当前位的数字;
c会跳转到下一个rand()断点。
finish
p $rax %8 +1计算器命令 把 $rax 寄存器里的数字除以 8 取余数,再加 1,然后把结果打印出来
c
p->print
$rax-> 64 位 CPU 的返回值寄存器
8->取余运算
+1
调用rand()函数时,函数生成的随机数会 “放到 $rax 里”
如果是 32 位程序,这个盒子是$eax
rand()生成的原始数字,一定在$rax里,所以我们要拿这个数来算。
0x0000000000400921 <+344>: call 0x400650 <rand@plt> # 调用rand(),返回值存在$rax 0x0000000000400926 <+349>: mov %eax,%edx # 把$rax(32位是$eax)的值复制到$edx 0x0000000000400928 <+351>: mov %edx,%eax 0x000000000040092a <+353>: sar $0x1f,%eax 0x000000000040092d <+356>: shr $0x1d,%eax 0x0000000000400930 <+359>: add %eax,%edx 0x0000000000400932 <+361>: and $0x7,%edx # 核心1:$edx & 0x7 →等价于$edx %8 0x0000000000400935 <+364>: sub %eax,%edx 0x0000000000400937 <+366>: mov %edx,%eax 0x0000000000400939 <+368>: add $0x1,%eax # 核心2:$eax = $eax +1 → 就是+1 0x000000000040093c <+371>: mov %eax,%esi 0x000000000040093e <+373>: mov $0x400a4c,%edi 0x0000000000400943 <+378>: mov $0x0,%eax 0x0000000000400948 <+383>: call 0x400600 <printf@plt> # 打印最终数字
算式来源的核心逻辑
- 用
disas flag找到rand()后的计算汇编; - 把汇编指令(
and $0x7/add $0x1)翻译成数学运算(%8/+1); - 结合
rand()返回值的存储位置拼成flag。
步骤 5:拼接flag
把输出的 13 个数字按顺序放进NSSCTF{}里,flag 为NSSCTF{5353316611126}、
有关rax %8取余与and 0x7,%edx按位与运算###
看汇编里的and $0xn(n 是数字),只要 n 是 “2 的次方 - 1”
(比如 7=2³-1、15=2⁴-1、3=2²-1),那它等价于 “% (n+1)”(取余 n+1);
如果 n 不是这个规律,就不能这么算
**只要 n 是 “2^k -1”,就等价于 % (n+1);否则算按位与,别公式。
- 总结:
遇到and $0xn,%寄存器指令,按 3 步走:
- 把 n 转成二进制(比如 7→0111,5→0101);
- 看二进制是不是 “全 1,没有 0”:
- 是 → 等价于「% (n+1)」;
- 否 → 按算按位与(别套取余);
- 结合程序后续的运算(比如 + 1),拼出最终公式。
