【问题标题】:Format string attack - How to print argc value?格式字符串攻击 - 如何打印 argc 值?
【发布时间】:2021-02-01 08:40:31
【问题描述】:

我正在处理 C 中的格式字符串漏洞,我正在尝试通过终端中给出的 printf 命令打印“argc”整数的值。

我当前的代码是:

int main (int argc, char **argv) {

char buffer[32];

*More variables*

strncpy(buffer, argv[1], sizeof(buffer));
printf(buffer);

*More printf's*

}

我可能需要使用格式说明符将整数 argc 的内容打印到终端中,但我似乎找不到解决方案。我所有的猜测都得到了我所有的 argv 堆栈寄存器(%rsi、%rdx、%rcx、%r8d、%r9d)。

格式字符串应在终端中给出,如下例所示:

./format-string %d_%s

是否可以获取 argc 值?如果是,我该怎么做?

【问题讨论】:

  • 我不明白你在做什么。您是否正在尝试编写一个容易受到字符串格式化攻击的程序?你的目标是什么?
  • @Cheatah 不,我想破解这个程序并使用程序执行时给出的格式说明符获取 argc 变量的整数值
  • 您确定省略的代码或省略的环境不相关吗?正如给定的那样,在 x86-64 上,我认为没有办法获得argc,因为rdi(或Windows 上的rcx)被多次覆盖。但是在 SYS V x86-64 ABI 中,argc 在调用 _start 时在堆栈中,所以我认为您可以使用 %<k>n 访问它,其中 <k> 是一个合适的整数。
  • 我缺少上述格式的$。例如,这适用于我的环境:./format-string '%40$p'
  • 别猜了。看汇编代码

标签: c x86-64 exploit format-specifiers format-string


【解决方案1】:

我试图在评论中要求澄清一些问题,但您没有回答,所以我假设您在符合 SYS V x86-64 ABI 的环境中工作。

main 被调用时,argcrdi 中,但它很快被strncpyprintf 本身的调用覆盖:

main:
    sub     rsp, 40
    mov     rsi, QWORD PTR [rsi+8]
    mov     edx, 32
    mov     rdi, rsp                 ;OOOPS
    call    strncpy

    mov     rdi, rsp                 ;OOOPS
    xor     eax, eax
    call    printf
    
    xor     eax, eax
    add     rsp, 40
    ret

上面的代码是compiled output of your sample program(清理后)。

但是,SYS V x86-64 ABI 上的glibc 本身不会合成argc(就像Windows 的对应项必须做的那样,参见GetCommandLine 和类似的),这个值作为第一个值传递创建程序时在堆栈上(参见 ABI 规范的图 3.9)。

因此,您可以使用%d 格式跳过第一个 k - 1 个参数,即使用 %k$d 其中 k 来使用 printf是要找到的数字。

要查找k,您只需找到调用printfrspargc 的地址之间的偏移量。
但由于在创建进程时argc 位于堆栈底部,这等于在printf 的调用处找到rsprsp 的初始值之间的偏移量 >.

所以使用 gdb:

gdb --args format-string test
   b _start
   r
   i r rsp
     0x7fffffffdfa0   The initial value of RSP
   b printf
   c
   i r rsp
     0x7fffffffd9d8 The value AFTER printf is called. Add 8 to find it BEFORE the call
   q

现在 0x7fffffffdfa0 - (0x7fffffffd9d8 + 8) = 0x110

0x110 字节是 34 个参数(0x110/8 = 0x22),由于前四个参数在寄存器中,我们也需要跳过它们,加上 4。 最后,计数是基于 1 的并且包含差异,因此我们需要将 2 加到计数中。 最终值是,对于我的示例环境,34 + 4 + 2 = 40,导致命令:

./format-string '%40$d'

【讨论】:

  • 你好,玛格丽特!非常感谢您的回答,并对您的评论延迟表示抱歉!我试图复制你对 gdb 所做的事情,但是当第二次输入“i r rsp”时,我得到了相同的地址。通过反复试验,我设法发现参数是“%44$d”。为什么我得到相同的地址?也许我做错了什么......
  • 谢谢;玛格丽特。最后一个问题。我已经应用了你的逻辑,我得到的值是 42,但它应该是 44,因为 '%44$d' 是给出 argv 值的参数。我的值:0x7fffffffdf10 - (0x7fffffffddd8 + 8) = 0x130 和 0x130/8 = 0x26 和 38(十进制)+ 4 = 42。
  • 你是对的。不知何故,我报告了错误的计数。必须再添加两个,因为计数是基于一个(占一个),而差异是包容性的(占另一个)。我正在编辑,谢谢
  • 看看 main 的堆栈框架一直回到 _start 是个好主意。我在想这个漏洞只有在调试版本中才有可能(main 的 asm 将它的寄存器 args 溢出到它自己的堆栈帧中)。显然 gcc/glibc 的 _start 没有 pop rdi 来获取 argc(因为这会使 RSP 不对齐),所以无论您使用什么编译器选项,它仍然存在。
  • 是的,当单步执行到 __libc_start_main 时,似乎堆栈上的某个地方有第二个副本。 (运行几个参数,x /40gx $rsp 并寻找那个数字)。我想知道 _start 是否可能只是传递初始 rsp 的副本,但似乎某些东西必须创建单独的 argc、argv、envp args,并将它们传递给一个函数,该函数必须溢出它们以在 main 之前调用其他 init 函数.第一个用户空间指令(在 /lib64/ld-linux-x86-64.so.2 的 _start 中,不是动态链接可执行文件中的 CRT 入口点)确实在 a 之前执行 mov rdi, rsp call,有意思
【解决方案2】:

printf 正在使用系统 v x86-64 位 ABI,该 ABI 声明函数的所有参数都将在寄存器 rdi、rsi、rdx、rcx、r8、r9 中传递,然后进一步对参数进行赋值(如果存在)将传递到堆栈中反向顺序,因此在您的情况下,您将需要传递多个%p(取决于堆栈中已经存在多少数据),我们使用%p,因为我们希望将数据打印为64位值。简而言之,将多个%p 传递给 printf 将首先查看寄存器,然后查看存储在堆栈中的参数(从内存中读取)。 所以

%p%p%p%p%p%p%p%p%p  /* will print registers values first extra %p will start to read up from stack (feel free to add as you want but keep in mind it will result in segmentation fault eventually if reached a specific area in memory but not sure when)*/

【讨论】:

  • 谢谢@Khaled!这是一个很好的起点。您确定要“%p”吗?这样,我只能得到积分,即内存地址。如何确认该内存地址是否来自“argc”参数?
  • @MiguelSantana:在 x86-64 System V 中,%p 基本上是 %#lx 的同义词。有了足够的 64 位整数->十六进制转换,argc 就会出现在您以反优化调试模式构建的 if 的某个位置。 Otherwise main wouldn't spill it from RDI to memory in the first place。 (因为它没有被程序使用,所以不需要保存它。)无论如何,argc 可能只是一个 64 位整数的高或低一半,另一半是非零。如果是低半部分,%x 将忽略高。
  • @MiguelSantana:这个答案忽略了一个事实,即 main 的调试版本会将其 args 溢出到堆栈中。无论如何,您可以通过查看编译器生成的 asm 来确定哪个堆栈位置包含 argc。
【解决方案3】:

%d 用于整数,%s 用于字符串

#include <stdio.h>

int main (int argc, char **argv) 
{
    char buffer[32] = {0};
    strncpy(buffer, argv[1], sizeof(buffer));
    printf("argc = %d and argv[1] = %s\n", argc, buffer);
    return 0;
}

【讨论】:

  • 你好,爱尔兰。感谢您的回答。但这不是我想要的。我需要通过 argv 提供 printf 内部的内容。这来自终端执行。
  • 你的意思是找出哪个寄存器保存argc的值?
  • 是的,我希望能够通过在命令行中提供格式说明符来打印它。再次检查问题以获取更多详细信息
  • 可以,比如栈的寄存器。
  • 试过%edi? ,这里有一个帖子:[stackoverflow.com/questions/4196201/…
猜你喜欢
  • 2012-11-04
  • 2015-01-17
  • 2011-06-18
  • 2013-01-08
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 2015-04-05
相关资源
最近更新 更多