格式字符串漏洞 - 用于读取随机地址的堆栈组织



我正在尝试了解格式字符串漏洞如何工作以及我们如何读取任何地址。

我可以实时检查在 printf 中输入"%x %x %x"将如何从字符串地址正上方的堆栈中弹出元素。

这是堆栈在 printf 中的外观:

(...)
0x7fffffffe018:    0x000000000040052c
0x7fffffffe020:    0x00007fffffffe180    
0x7fffffffe028:    0x00007fffffffe168
0x7fffffffe030:    *0x00007fffffffe48d* << address of argument   
0x7fffffffe038:    0x00007ffff7dd4e80 
0x7fffffffe040:    0x00007ffff7de9d60    
0x7fffffffe048:    0x00007ffff7ffe268
0x7fffffffe050:    0x0000000000000000    
0x7fffffffe058:    0x0000000000400563 <</ return address after printf
0x7fffffffe060:    0x00007fffffffe168    
0x7fffffffe068:    0x0000000200400440
(...)

0x7fffffffe48d是内存中更远的"%x %x %x"字符串的地址:

0x7fffffffe469:    "/home/.../C/format_string/test"
*0x7fffffffe48d*:    "%x %x %x"
0x7fffffffe4ac:    "SSH_AGENT_PID=..."

所以从逻辑上讲,这将在此基础上输出 3 个元素,即:

ffffe168 ffffe180 40052c 

现在,我不明白的是,如果我在参数中放置一个随机地址,比如:"\x15\xff\x0A\x23 %x %x %x %x %s",为什么"\x15\xff\x0A\x23"实际上存储在堆栈上并由"%s"读取?

从我上面看到的,只有整个字符串的地址放在堆栈上(0x00007fffffffe48d),而不是字符(实际上是我打算读取的地址)本身。

换句话说,无论我把什么放在我的字符串里,我只能控制地址的内容:

0x7fffffffe48d:    "blablabla %x %x %x"

但不是从堆栈中弹出的内容。

你是对的,代码不会在堆栈上推送字符串的内容。
它只是推送其地址。
在此处使用缓冲区溢出技术将不允许您覆盖返回地址。

不幸的是,您没有显示 C 代码。

以下 C 代码会将其字符串数据放在堆栈上,因此可以被利用。
以下示例来自:http://insecure.org/stf/smashstack.html

基于堆栈的字符串 - 可利用

void overrunmybuffer(char *str) {
   char buffer[16];           <<-- local fixed sized array, stored on the stack.
   //should have used `strncpy()`    
   strcpy(buffer,str);  <<-- strcpy will blindly push 256 bytes in a 16 byte buffer
}
void main() {
  char large_string[256];
  int i;
  for( i = 0; i < 255; i++)
    large_string[i] = 'A';
  overrunmybuffer(large_string);
}

基于堆的字符串 - 难以利用
在您的代码中,该函数如下所示:

void cannotoverrunstack(char *str) {
   char *buffer;       <<-- pointer to char, only the address is stored on stack
   buffer = malloc(16);  
   strcpy(buffer,str); <<-- some other data in the heap will be overwritten
                            but not the stack.
} 

请注意,堆上的数据覆盖可能会也可能不会触发访问冲突,并可能覆盖有用的数据。
由于在堆中分配数据的随机方式,这比覆盖堆栈有用得多。
堆栈框架具有非常可预测的布局,因此非常易于操作。

缓冲区溢出问题的正确解决方案是永远不要使用strcpy而只使用这样的strncpy

保存固定大小缓冲区的代码

void cannotoverrunmybuffer(char *str) {
   char buffer[16];           <<-- local fixed sized array, stored on the stack.
   strncpy(buffer,str,sizeof(buffer)-1);  <<-- only copy first 15 bytes. 
   buffer[15] = 0;                       <<-- !!put terminating 0 in!
}

不要忘记强制将字符串归零,否则您最终会读取超过字符串的末尾,从而导致损失。如果你正在使用 Unicode 字符串,你需要输入两个终止的 0。

更好的是使用自动扩展的字符串,例如Java或Delphi中使用的字符串(System::OpenString C++)。缓冲区溢出是不可能的,因为它们会自动从 0 扩展到 2GB。

最新更新