HackThisSite - Extended Basic Mission 01
Challenge
You have to give input to a C program which gives you the length of the string. How would you crash it?
给一个会给出字符串长度的 C 程序喂输入,怎样把它搞崩?
关卡页只给出被点名的那一个函数:
1 | void blah(char *str) |
状态:live 已通关(partially verified:当时提交的字符串未被留存)。缺陷函数和溢出点都在本地实际跑过,验收口径由代码本身推得;但本账号当时的原始提交串未被留存,无法再次核对。
Solution
blah 实质只有两步:在栈上声明 200 字节的
lol,把调用方的字符串整段拷进去。strcpy
的原型是
strcpy(char *dst, const char *src),它只拿到源和目标两个地址,不接收目标容量,于是拷贝多少字节完全由
src 里 NUL 的位置决定:写 strlen(src) + 1
个字节(含结尾的 NUL),中途不做任何边界检查。
lol 是 blah
栈帧里的局部数组,紧邻它的更深处是保存的帧指针和返回地址。当
strlen(str) >= 200 时,strcpy
写出的字节越过 lol
的边界压到相邻栈数据上;返回地址一旦被非预期字节覆盖,blah
执行 ret 时就跳向垃圾地址,进程随即崩溃。
题面称该程序给出字符串的长度,那段驱动代码没有展示,泄漏出来的只有这个不做长度检查的
blah。
按题面补一个最小驱动(读参数、调用 blah、打印
strlen),存为 replica.c:
1 |
|
同一份源码编两份,GCC 默认会插栈保护,另编一份关掉,看两种失败形态:
1 | $ gcc -O0 -fno-stack-protector -o replica_nossp replica.c |
逐长度扫一遍(199 / 200 / 201 / 210 / 250 / 500 个
A):
1 | replica_nossp len=199 exit=0 |
两条路径都算崩溃:无保护的版本靠覆盖返回地址拿到 SIGSEGV;有保护的版本在越界写坏 canary 后、函数返回前被拦下(SIGABRT)。无保护版本要到 210 才段错误、201 还活着,崩溃阈值取决于具体栈布局,不是整齐的 201。
溢出只由长度决定、跟输入内容无关:任何
strlen(str) > 200
的输入都会越界,所以检查端要模拟这次溢出,判据只能是长度而不是某个固定串。实践上给足余量,输入一段明显更长的字符串(这里用
250 个 A)。
formkey
每次加载关卡页都会变,取页面和提交必须在同一次运行里完成;提交还要带
Referer: <关卡页>,否则模板页丢弃这次尝试。
Key points
strcpy的签名里没有目标容量,写入量由src决定,是 C 里最常见的崩溃源;gets、sprintf同属无界接口。- 写入字节数是
strlen(src) + 1,所以恰好 200 个字符也已经越界一个 NUL 字节;真正段错误的阈值还受栈布局影响。 - 开栈保护时失败信号是
SIGABRT(
stack smashing detected)而不是 SIGSEGV,两者都满足题面crash it的要求。 - 修法:
strncpy(dst, src, sizeof dst - 1)后手动补 NUL,或在入口先校验长度再拷。