格式字符串直接参数行为

逆向工程 开发 arm64
2021-06-12 02:31:44

我正在调查 arm64 上的格式字符串漏洞(与 musl libc 相关联),并且在调试输出时遇到了一些奇怪的行为。

从反编译来看,该程序存在一个经典的格式化字符串漏洞,归结为:

fprintf(stdout, user_controlled_data);

使用重复的格式说明符(例如%p%p%p%p),我可以通过包含数千个这样的字符来转储大量内存。这按预期工作。

当我尝试直接参数访问时出现问题。出于某种原因,%1$p有效但无效%2$p,但 3 到 12 次有效,之后我尝试的一切都失败了。通过“失败”,我的意思是不打印任何值,除了在代码中自动添加到我的字符串中的换行符以某种方式被吃掉了。在调试器中,fprintf返回 -1,并errno设置为 0x16,我认为这是 EINVAL。

对于这个特定场景,我需要能够读取/写入数千个特定堆栈偏移量。但是我无法打印它来确认,因为直接参数访问不起作用。可以通过使用重复字符查看目标参数,但由于其他限制,我需要直接参数才能继续工作。

现在,我明白这是在“未定义的行为”领域,但我在系统上编译了一个易受攻击的测试二进制文件(与 Glibc 静态链接,我可能应该尝试对抗 musl),它按预期工作,没有问题(例如%9000$p打印一些东西)。

有什么会导致这种行为,还是我遗漏了什么?如果需要,我可以提供更多信息。

1个回答

发布问题后,我开始思考我使用的 libc 的不同之处。我再次编译了基本的测试二进制文件,但使用了 musl,并且还看到了那里的意外行为。这似乎是由于printfmusl 中实现

        if (isdigit(s[1]) && s[2]=='$') {
            l10n=1;
            argpos = s[1]-'0';
            s+=3;
        }

如果%后面正好有一个数字,然后是 a ,它只使用直接参数$这解释了为什么我无法打印任何更大的堆栈偏移。

编辑:这似乎符合标准

"%n$",其中 n 是 [1,{NL_ARGMAX}] 范围内的十进制整数,给出了参数在参数列表中的位置。

然后,查看NL_ARGMAX的定义

NL_ARGMAX

调用 printf() 和 scanf() 函数时数字的最大值。最小可接受值:9

所以看起来实现确实是在遵循标准,允许最小值为9,这对于编写紧凑格式字符串漏洞利用来说肯定是不方便的。