探测C库malloc元数据捕获野指针

暴雨天,写着玩。

野指针是令人讨厌的,很容易导致莫名其妙的踩内存错误,那么如何判断一个指针是不是野指针呢?

一般而言,用户使用的内存在MMAP_THRESHOLD(一般为128k)两边,分别由malloc和mmap管理:

  • malloc:C库的内存管理机制,一般用于小块内存管理。
  • mmap:操作系统提供的内存管理机制,一般用于大块内存管理。

这里不谈mmap,因为操作系统提供的机制太容易检测了。难的是C库的机制:

  • 内存在操作系统管理之外,完全自治,无法享受操作系统的段错误提示…

只好通过C库malloc元数据来detect咯,比方说你可以man -k malloc,获取很多malloc内存管理内部的一些信息,但是那没意思,照本宣科而已,不如自己手工来一点点地发现。

给出一个弱爆了的代码:

#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
	char *p1, *p2, *p3, *p4;
	int i, j;
	unsigned char *p;

	p1 = calloc(1, 32);
	memset(p1, 'c', 32); 
	p2 = calloc(1, 32); // 如果在这里alloc一个128的,那么由于切断了相同size的block,在free之后,就会同时出现prev,next两个指针。
	memset(p2, 'c', 32);
	p3 = calloc(1, 32);
	memset(p3, 'c', 32);
	p4 = calloc(1, 32);
	memset(p4, 'c', 32);
	// 观察内存地址的规律,发现间隔是32+16
	printf("%p  %p  %p  %p\n", p1, p2, p3, p4);
	p = p1;
	// 猜测base-16处存有元数据
	p -= 16;
	// 打印探测究竟
	for (i = 0; i < 48*4; ) {
		for (j = 0; j < 16; j++) {
			printf("%02x ", p[i++]);
		}
		printf("\n");
	}
	printf("\n");
	printf("\n");
	free(p1);
	free(p2);
	free(p3);
	free(p4);
	// 查看free之后的相同内存区域,观测元数据的变化
	for (i = 0; i < 48*4;) {
		for (j = 0; j < 16; j++) {
			printf("%02x ", p[i++]);
		}
		printf("\n");
	}
	printf("\n");
	// 重来一遍
	p1 = calloc(1, 32);
	memset(p1, 'c', 32);
	p2 = calloc(1, 32);
	memset(p2, 'c', 32);
	p3 = calloc(1, 32);
	memset(p3, 'c', 32);
	p4 = calloc(1, 32);
	memset(p4, 'c', 32);
	// 哦,原来是栈式分配,和内核的slab一样
	printf("%p  %p  %p  %p\n", p1, p2, p3, p4);
	for (i = 0; i < 48*4; ) {
		for (j = 0; j < 16; j++) {
			printf("%02x ", p[i++]);
		}
		printf("\n");
	}
	free(p1);
	free(p2);
	free(p3);
	free(p4);
	// 分配不同大小的block
	p1 = calloc(1, 32);
	memset(p1, 'c', 32);
	p2 = calloc(1, 64);
	memset(p2, 'd', 64);
	p3 = calloc(1, 128);
	memset(p3, 'e', 128);
	p = p1;
	p -= 16;
	printf("%p  %p  %p \n", p1, p2, p3);
	for (i = 0; i < p3 + 128 - p1 + 16; ) {
		for (j = 0; j < 16; j++) {
			printf("%02x ", p[i++]);
		}
		printf("\n");
	}
	printf("\n");
	free(p1);
	free(p2);
	free(p3);
	// 再来一遍
	p1 = calloc(1, 32);
	memset(p1, 'c', 32);
	p2 = calloc(1, 64);
	memset(p2, 'd', 64);
	p3 = calloc(1, 128);
	memset(p3, 'e', 128);
	p4 = calloc(1, 128);
	memset(p4, 'e', 128);
	p = p1;
	p -= 16;
	printf("%p  %p  %p %p\n", p1, p2, p3, p4);
	for (i = 0; i < p4 + 128 - p1 + 16; ) {
		for (j = 0; j < 16; j++) {
			printf("%02x ", p[i++]);
		}
		printf("\n");
	}
}

执行一遍:

[root@localhost check]# ./a.out
0x1a0a010  0x1a0a040  0x1a0a070  0x1a0a0a0
# 元数据第一个16字节中的0x31应该就是block的大小。
# 48 = 32 + 16 = 0x30
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63


00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
# free内存块占用了至少1个8字节存放元数据。
# 貌似是next or 上一个可用块的地址:0x01a0a000。
# 这个地址是包括了元数据的。
00 a0 a0 01 00 00 00 00 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
30 a0 a0 01 00 00 00 00 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
60 a0 a0 01 00 00 00 00 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63

0x1a0a0a0  0x1a0a070  0x1a0a040  0x1a0a010
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
0x1a0a010  0x1a0a0d0  0x1a0a120
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
60 a0 a0 01 00 00 00 00 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
90 a0 a0 01 00 00 00 00 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 51 00 00 00 00 00 00 00
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
00 00 00 00 00 00 00 00 91 00 00 00 00 00 00 00
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65

0x1a0a010  0x1a0a040  0x1a0a090 0x1a0a120
00 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63
00 00 00 00 00 00 00 00 51 00 00 00 00 00 00 00
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
00 00 00 00 00 00 00 00 91 00 00 00 00 00 00 00
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
00 00 00 00 00 00 00 00 91 00 00 00 00 00 00 00
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65
65 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65

很容易的一次探索,如果你面对一块内存,发现它的前16个字节包含至少一个附近的指针,而你根本就没有写过一个地址在内存块里,那么它超大概率就是 已经被free的被写入了free ptr的野指针 了!

接下来其实还有比较好玩的,照着这个思路,可以不参考任何文档,不参考源代码,直到把malloc的freelist管理方法全部dump出来,不断探测它的行为即可。

当然,我肯定知道内存管理有很多方法,malloc就有很多,本文我列举的这个是最low的,现在没啥人用这个了,高端的都看不起这种,线程不安全没有考虑,怎么怎么地的,所以说,然后呢?

不较真儿,本来就是玩嘛。


浙江温州皮鞋湿,下雨进水不会胖!

猜你喜欢

转载自blog.csdn.net/dog250/article/details/106783585