C: printf() 的堆栈漏洞?
C: printf()'s stack vulnerability?
我写了一个简单的C程序来看一下printf()的栈帧
#include <stdio.h>
int main(void){
printf("%s");
}
我认为堆栈的工作方式是 main() 首先将“%s”压入堆栈,因此 printf 要么会出现段错误,要么会打印出垃圾。但是,在我的反汇编中,没有任何地方将“%s”压入堆栈。我打印出了 %fp 和 %sp 之间的所有值,但是其中 none 个包含“%s”。
main 的程序集转储:
0x00400950 <+0>: lui gp,0x2
0x00400954 <+4>: addiu gp,gp,-32224
0x00400958 <+8>: addu gp,gp,t9
0x0040095c <+12>: addiu sp,sp,-32
0x00400960 <+16>: sw ra,28(sp)
0x00400964 <+20>: sw s8,24(sp)
0x00400968 <+24>: move s8,sp
0x0040096c <+28>: sw gp,16(sp)
0x00400970 <+32>: lw v0,-32744(gp)
0x00400974 <+36>: nop
0x00400978 <+40>: addiu v0,v0,2864
0x0040097c <+44>: move a0,v0
0x00400980 <+48>: lw v0,-32688(gp)
0x00400984 <+52>: nop
0x00400988 <+56>: move t9,v0
0x0040098c <+60>: jalr t9
0x00400990 <+64>: nop
0x00400994 <+68>: lw gp,16(s8)
0x00400998 <+72>: move sp,s8
0x0040099c <+76>: lw ra,28(sp)
0x004009a0 <+80>: lw s8,24(sp)
0x004009a4 <+84>: addiu sp,sp,32
0x004009a8 <+88>: jr ra
0x004009ac <+92>: nop
如果“%s”没有存储在栈上,它存储在哪里?另外,它从哪里得到相应的字符串打印出来?
.file "1.c"
.section .rodata
.LC0:
.string "%s"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
movl [=10=], %eax
call printf
movl [=10=], %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010"
.section .note.GNU-stack,"",@progbits
我用过gcc生成程序集。该字符串未按照您的想法存储。字符串是静态存储的。
据我所知,mips arch 使用 a0 ~ a3 作为函数调用的前四个参数。
通常,在实现级别将发生的是 "%s"
字符串文字在某种静态存储中。当调用 printf
时,指向该字符串的指针作为参数传递。这并不一定意味着这个指针被压入堆栈。如何传递参数取决于参数传递约定。它可以加载到寄存器中。
在您的特定情况下,这里是 "%s"
准备通过的地方:
0x00400970 <+32>: lw v0,-32744(gp)
0x00400974 <+36>: nop
0x00400978 <+40>: addiu v0,v0,2864
0x0040097c <+44>: move a0,v0
首先从相对于全局指针寄存器的数据区加载基地址。然后将这个基地址偏移2864得到"%s"
的地址。然后地址被移动到 a0
,寄存器 v0
被重新用于计算 printf
的地址(由于它在共享库中,这很复杂)。
现在由于 "%s"
没有相应的 char *
参数,当然行为在形式上是未定义的。但实际行为是什么?
实际行为可能是 printf
会尝试以某种方式提取 char *
指针,也许是从堆栈中提取。 (可变参数函数的尾随参数通常只是放在堆栈上。)
现在由于调用者没有在那里放置参数,printf
提取一些 "garbage" 单词并将其视为 char *
,打印该单词指向的内存作为一个以 null 结尾的字符串。也就是说,如果该词指向有效内存。
如果您的目标是转储一些字节的堆栈内存,这根本就不可靠。你不知道什么样的值被解释为 char *
指针,或者它指向什么,或者它是否指向任何东西,更不用说堆栈了。
伪造的 char *
本身可能会从堆栈中拉出,但您实际上并没有打印该指针本身。
以下 可能会 为您提供几个字节的堆栈:
printf("%p\n");
此外,类似地,任何不带参数的数字转换都可以。原因是 %p
与 %s
不同,它实际上打印指针本身。如果 %p
的参数值是从堆栈中提取的,那么该值的打印表示会泄漏一些有关一小部分堆栈的信息。
我写了一个简单的C程序来看一下printf()的栈帧
#include <stdio.h>
int main(void){
printf("%s");
}
我认为堆栈的工作方式是 main() 首先将“%s”压入堆栈,因此 printf 要么会出现段错误,要么会打印出垃圾。但是,在我的反汇编中,没有任何地方将“%s”压入堆栈。我打印出了 %fp 和 %sp 之间的所有值,但是其中 none 个包含“%s”。
main 的程序集转储:
0x00400950 <+0>: lui gp,0x2
0x00400954 <+4>: addiu gp,gp,-32224
0x00400958 <+8>: addu gp,gp,t9
0x0040095c <+12>: addiu sp,sp,-32
0x00400960 <+16>: sw ra,28(sp)
0x00400964 <+20>: sw s8,24(sp)
0x00400968 <+24>: move s8,sp
0x0040096c <+28>: sw gp,16(sp)
0x00400970 <+32>: lw v0,-32744(gp)
0x00400974 <+36>: nop
0x00400978 <+40>: addiu v0,v0,2864
0x0040097c <+44>: move a0,v0
0x00400980 <+48>: lw v0,-32688(gp)
0x00400984 <+52>: nop
0x00400988 <+56>: move t9,v0
0x0040098c <+60>: jalr t9
0x00400990 <+64>: nop
0x00400994 <+68>: lw gp,16(s8)
0x00400998 <+72>: move sp,s8
0x0040099c <+76>: lw ra,28(sp)
0x004009a0 <+80>: lw s8,24(sp)
0x004009a4 <+84>: addiu sp,sp,32
0x004009a8 <+88>: jr ra
0x004009ac <+92>: nop
如果“%s”没有存储在栈上,它存储在哪里?另外,它从哪里得到相应的字符串打印出来?
.file "1.c"
.section .rodata
.LC0:
.string "%s"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
movl [=10=], %eax
call printf
movl [=10=], %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010"
.section .note.GNU-stack,"",@progbits
我用过gcc生成程序集。该字符串未按照您的想法存储。字符串是静态存储的。
据我所知,mips arch 使用 a0 ~ a3 作为函数调用的前四个参数。
通常,在实现级别将发生的是 "%s"
字符串文字在某种静态存储中。当调用 printf
时,指向该字符串的指针作为参数传递。这并不一定意味着这个指针被压入堆栈。如何传递参数取决于参数传递约定。它可以加载到寄存器中。
在您的特定情况下,这里是 "%s"
准备通过的地方:
0x00400970 <+32>: lw v0,-32744(gp)
0x00400974 <+36>: nop
0x00400978 <+40>: addiu v0,v0,2864
0x0040097c <+44>: move a0,v0
首先从相对于全局指针寄存器的数据区加载基地址。然后将这个基地址偏移2864得到"%s"
的地址。然后地址被移动到 a0
,寄存器 v0
被重新用于计算 printf
的地址(由于它在共享库中,这很复杂)。
现在由于 "%s"
没有相应的 char *
参数,当然行为在形式上是未定义的。但实际行为是什么?
实际行为可能是 printf
会尝试以某种方式提取 char *
指针,也许是从堆栈中提取。 (可变参数函数的尾随参数通常只是放在堆栈上。)
现在由于调用者没有在那里放置参数,printf
提取一些 "garbage" 单词并将其视为 char *
,打印该单词指向的内存作为一个以 null 结尾的字符串。也就是说,如果该词指向有效内存。
如果您的目标是转储一些字节的堆栈内存,这根本就不可靠。你不知道什么样的值被解释为 char *
指针,或者它指向什么,或者它是否指向任何东西,更不用说堆栈了。
伪造的 char *
本身可能会从堆栈中拉出,但您实际上并没有打印该指针本身。
以下 可能会 为您提供几个字节的堆栈:
printf("%p\n");
此外,类似地,任何不带参数的数字转换都可以。原因是 %p
与 %s
不同,它实际上打印指针本身。如果 %p
的参数值是从堆栈中提取的,那么该值的打印表示会泄漏一些有关一小部分堆栈的信息。