探測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的,現在沒啥人用這個了,高端的都看不起這種,線程不安全沒有考慮,怎麼怎麼地的,所以說,然後呢?

不較真兒,本來就是玩嘛。


浙江溫州皮鞋溼,下雨進水不會胖!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章