不修改布尔变量值时,如何用GCC与GDB输出exploit.c内置flag
前置编译准备
如果直接用默认参数编译,GCC的优化器会检测到if(c)分支的条件永远为假,直接把整个printf代码块当成死代码移除,后续根本没有可以操控的执行入口,因此需要搭配指定参数编译:
- 执行编译命令:
gcc -g -O0 -no-pie -fno-dce exploit.c -o exploit-g:嵌入完整调试符号,方便GDB关联代码行、内存地址-O0:关闭所有编译优化,不改变原始代码的执行逻辑-no-pie:关闭进程地址随机化,固定程序加载的内存地址,降低操作复杂度-fno-dce:禁用死代码消除,保留本应被删掉的if分支printf代码块
关键汇编位置定位
启动GDB调试程序:gdb ./exploit,执行disassemble main查看main函数的汇编代码,找到两个核心位置:
- 布尔变量c的赋值位置:对应汇编行是
mov BYTE PTR [rbp-0xX], 0x0,这行是给c写入false的地方,整个操作全程不修改这个内存位置的取值 - 条件跳转位置:紧跟赋值语句后面的两行:
- 一行是
cmp BYTE PTR [rbp-0xX], 0x0,比较c的值是否为0 - 一行是
je <跳转目标地址>,如果c为0就直接跳转到函数return的位置,跳过中间的printf代码
跳转指令的下一行开始,就是if块内部的printf调用逻辑,正常运行永远执行不到这里。
- 一行是
典型的反汇编片段参考:
0x0000000000001149 <+21>: mov BYTE PTR [rbp-0x1],0x0 ; c=false赋值,全程不修改 0x000000000000114e <+26>: cmp BYTE PTR [rbp-0x1],0x0 ; 比较c的值 0x0000000000001152 <+30>: je 0x1178 <main+68> ; c为0则跳转到return ; 以下为if块的printf代码,正常运行不可达 0x0000000000001154 <+32>: lea rax,[rip+0x2e99] ; 加载printf格式串 ... 0x0000000000001172 <+60>: call 0x1030 <printf@plt> ; 调用printf输出flag
执行流操控(全程不修改c的原始值)
两种完全不触碰c内存的操作方式均可触发flag输出:
方式1:跳过条件跳转,顺着流程走进if块
- 在条件跳转指令位置下断点:
b *main+30(对应上面例子里的je指令位置) - 启动程序运行到断点:
run,此时可以用x/d $rbp-0x1验证c的值,确认还是原始的0(false) - 修改指令寄存器rip,让程序不执行je跳转,直接走到if块代码:
set $rip = main+32(也就是je指令的下一个执行位置) - 继续运行程序:
continue,此时程序会顺着逻辑执行if块里的printf调用,直接打印出flag内容,全程c的内存值从未被修改。
方式2:直接跳转至printf调用位置,完全绕过判断逻辑
- 在main函数入口下断点:
b main - 运行程序停在main入口:
run,此时程序还没执行给c赋值的步骤 - 直接把rip的值设置到printf的call指令位置:
set $rip = main+60(对应上面例子里的printf调用地址) - 继续运行程序:
continue,程序会直接开始执行printf的参数计算、输出逻辑,连c的赋值步骤都没有走到,自然完全不会改动c的原始取值。
最终输出的flag内容
按照代码里的异或计算逻辑,最终输出的字符串是flaggy-flag,完整打印行是The flag is: flaggy-flag
内容的提问来源于stack exchange,提问作者Mark Ezberg
相关产品推荐
相关产品推荐

