正式开始栈溢出了,先来一个最最最最简单的吧
检查
checksec pwn
32 位程序,RELRO 与 NX保护开启
拖进 ida32 反编译看main 函数
关键代码分析:
stream = fopen("/ctfshow_flag", "r");
打开文件 /ctfshow_flag 读取内容
if ( argc <= 1 )
{
puts("Try again!");
}
else
{
ctfshow((char *)argv[1]);
只要命令行参数个数大于 1 就会调用 ctfshow 函数
双击跟进 ctfshow 函数
ctfshow 的函数接受一个指向字符数组的指针 char *src 作为参数;
声明了一个名为 dest 的字符数组,长度为 104;
使用 strcpy 函数将 src 指向的字符串拷贝到 dest 中,返回指向 dest 的指针,即拷贝后的字符串的起始地址。strcpy 是一个典型的可能造成栈溢出的函数。
这里注意到如下代码:
fgets(flag, 64, stream);
signal(11, (__sighandler_t)sigsegv_handler);
从指定的输入流 stream 中读取最多 63 个字符(因为最后一个位置留给了空字符 '\0')到名为 flag 的字符数组中;当程序执行中发生段错误时,会触发 SIGSEGV 信号,此时程序会自动调用 sigsegv_handler 函数进行处理。
跟进 sigsegv_handler 函数:
在收到 SIGSEGV 信号时,打印 flag 变量的内容到标准错误流
那么我们可以让第二个命令行参数(argv[1])超过 dest 数组长度(104)
制造栈溢出,让程序崩溃进而输出 flag
验证:
我们先让第二个命令行参数刚好为 104 个字符进行测试
./pwnme 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
没有输出 flag,因为不存在溢出,程序没有出错
使用 105 个字符进行测试,这种情况会存在溢出
./pwnme 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
拿到 flag:ctfshow{102b6088-faed-47ce-b06c-0e7b863cac7e}