下图取自Blackhat演示文稿。下面是代码:
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4
5 int
6 main (int argc, char *argv[]){
7 char *buf, *buf2;
8
9 buf = (char *) malloc(1024);
10 buf2 = (char *) malloc(1024);
11
12 printf("buf=%p\n", buf);
13 printf("buf2=%p\n", buf2);
14 strcpy(buf, argv[1]);
15 strcpy(buf2, argv[2]);
16 printf("buf=%s\n", buf);
17 printf("buf2=%s\n", buf2);
18 free(buf2);
19 return 0;
20 }
我在第18行上放置了一个断点,并检查了内存。这是运行以下命令之后的转储:
./basicheap $(python -c 'print("A"*1000 + " " +"XXXXABCDEFGH")')
这是
buf1
的内存转储:[0x08048543]> pxw 0x50 @ [fcnvar.local_1ch]-8
0x0804b158 0x00000000 0x00000411 0x41414141 0x41414141 ........AAAAAAAA
0x0804b168 0x41414141 0x41414141 0x41414141 0x41414141 AAAAAAAAAAAAAAAA
0x0804b178 0x41414141 0x41414141 0x41414141 0x41414141 AAAAAAAAAAAAAAAA
0x0804b188 0x41414141 0x41414141 0x41414141 0x41414141 AAAAAAAAAAAAAAAA
0x0804b198 0x41414141 0x41414141 0x41414141 0x41414141 AAAAAAAAAAAAAAAA
这是
buf2
的内存转储:[0x08048543]> pxw 0x50 @ [fcnvar.local_20h]-8
0x0804b568 0x00000000 0x00000411 0x58585858 0x44434241 ........XXXXABCD
0x0804b578 0x48474645 0x00000000 0x00000000 0x00000000 EFGH............
0x0804b588 0x00000000 0x00000000 0x00000000 0x00000000 ................
0x0804b598 0x00000000 0x00000000 0x00000000 0x00000000 ................
0x0804b5a8 0x00000000 0x00000000 0x00000000 0x00000000 ................
输出显示
0x411
为两个块的大小,无论如何都是1024 + 17位。这是两个块的标头中的第一条(也是唯一一条)信息。在Shellcoder中,作者正在尝试演示如何溢出buf1
来覆盖buf2
的标头信息。glibc的编写者是否放弃了标头信息中前一个块的大小,还是其他的东西
PS:我使用的构建命令是
gcc -no-pie -g basicheap.c -o basicheap
#1 楼
您链接到的图似乎是错误的。前一个块的大小存储在当前块iff中,前一个块是空闲的。此图像更适合于分配的堆块的外观。
来源:https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/
下面的代码来演示这一点。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
char *buf1, *buf2, *buf3;
buf1 = (char *) malloc(1024);
buf2 = (char *) malloc(1024);
buf3 = (char *) malloc(1024);
printf("buf1=%p\n", buf1);
printf("buf2=%p\n", buf2);
printf("buf3=%p\n", buf3);
memset(buf1, 'A', 1024);
memset(buf2, 'B', 1024);
memset(buf3, 'C', 1024);
free(buf2);
return 0;
}
注意:我取了三个块,而不是两个块,否则释放第一个块可以将其合并为顶部块。我们将释放中间块并在执行
free
调用之前和之后检查块内容。二进制文件是使用
gcc -m32 -g ./heap.c
编译的。像往常一样在gdb中调试二进制文件,并在free(buf2)
调用处的第20行设置一个断点。 br /> malloc
返回指向chunk + 8
的指针,因此我们从每个地址中减去了8。此外,我还在上面的图像中突出显示了块大小(堆块的第二个成员)。 Glibc堆块始终对齐为8个字节,这意味着最后三位始终为零。因此,这三个位用于存储其他信息(0x409
,A
和M
位)。P
转换为二进制时会变成0x409
。 /> 要获得真正的块大小,我们必须舍弃最后三个位(
100 0000 1001
A
M
),即,我们应仅考虑P
。当将其转换为十进制时,我们得到1032。 回到gdb,让我们跳过
100 0000 1000
调用并再次检查这些块。块2已被释放。让我们看一下第3块(
free
)。因此,先前的大小仅在释放块2之后才存储在块3中,而不是在使用中时存储。 我之前指的
buf3
位代表0x408
。当使用上一个块时,将设置此位。由于先前的块现在可用,因此该位设置为0。因此,第二个成员现在还包含等于1032
的P
(而不是PREV_INUSE
)。如上图所示,0x408
位未设置。希望这能清除您的理解。
评论
太棒了!非常感谢您的努力!
–Solidak
18-09-9在9:22