PHP反序列化字符串逃逸:从CTF piapiapia看长度边界漏洞

最近复盘了一道挺经典的 CTF 题目,代号叫 piapiapia。第一眼看上去平平无奇:注册、登录、改资料、看资料,典型 Web 题套路。但真正卡住人的不是 SQL 注入也不是 XSS,而是一个听起来很拗口的东西——PHP 反序列化字符串逃逸。说白了,就是利用一个不起眼的字符串替换函数,让序列化字符串的长度和内容对不上号,从而篡改反序列化后的对象结构。这篇文章我把这道题的底层原理、payload 构造过程、踩坑点全部拆开讲一遍。不管你是准备 CTF 的选手,还是在搞代码审计的开发者,只要搞懂这一招,反序列化类的题目至少能少走一半弯路。

这个漏洞的核心点非常集中:代码里一定存在一个unserialize(),并且传入的数据在序列化之后、反序列化之前,被某个preg_replace()改过。听起来条件很苛刻,但现实中很多过滤和转义函数就是这么用的。当年我第一次做这题时,盯着代码看了半天也没想明白where被替换成hacker到底有什么用。直到我把序列化字符串的字节长度一个个数清楚,才意识到这不是一个简单的过滤缺陷,而是一种精巧的边界失控。下面我就从这个角度,一步步带你把整条链路走通。

1. 项目概况与攻击面分析

1.1 这道题到底在考什么

piapiapia 这道题给了一个比较完整的 Web 应用:用户可以注册、登录、上传个人信息,然后查看自己的资料。表面上看功能很正常,但如果你拿到了源码,审计下来会发现两件关键的事:

第一,应用把用户资料序列化后存储,读取时直接unserialize,序列化字符串里包含了用户输入的用户名、手机号、自我介绍等字段。第二,代码里有一个统一的过滤函数,里面用了preg_replace()对用户输入或者最终生成的字符串做了替换。具体替换规则因不同版本略有差异,但核心思路是:把某个短字符串替换成一个更长的字符串,比如把where替换成hacker,或者反过来把长字符串替换成短字符串。

这两点加在一起,就构成了字符串逃逸的充分条件。如果替换发生在序列化之前,那只是普通的字符串内容变化;但如果替换发生在序列化之后、反序列化之前,序列化结构中的长度声明是旧的,内容却已经被改了,长度差就会导致解析器读错边界。

这个题目典型的解法是通过逃逸往对象里注入一个额外的字段,最终把某段文件路径覆盖成目标文件,配合后面的file_get_contents把读取结果输出到页面上。整个过程不需要猜 flag,也不需要打数据库,纯粹靠字符串层面的逻辑漏洞。

1.2 攻击链路的整体视角

把整道题的攻击链拆开看,大概是这样的:

  1. 找到所有进入unserialize()的入口,追踪这个参数是从哪里组装出来的。
  2. 确认在组装成序列化字符串之后,有没有经过任何会改变字符串长度的操作,尤其是preg_replace。
  3. 计算每个替换单元造成的长度差,算出需要多少个替换单元来让溢出的字节数刚好等于恶意结构体的长度。
  4. 构造输入,让溢出部分恰好成为一个完整的序列化字段,比如s:5:"phone";s:5:"config";。
  5. 反序列化成功,多出的字段被解析,后续代码在这个字段上做了敏感操作,最终拿到目标文件内容。

这个链路里最容易被忽略的其实是第 3 步。很多人看到替换规则后第一反应是想当然地在输入里塞一个完整的序列化 payload,结果反序列化直接报错。原因很简单:你塞进去的字符串大部分被当成了字符串值本身,根本没有机会参与到语法解析。只有长度补偿之后多出来的那部分字节,才会被 PHP 当作结构字符去解析。所以构造 payload 前第一件事,就是把字节数算得清清楚楚。

1.3 为什么叫“逃逸”

“逃逸”这个词用在这里特别形象。正常情况下,一个字符串字段内部的内容无论是什么,都会被双引号包裹,由前面的长度声明限制,外部语法和内部内容井水不犯河水。但现在因为长度声明失效,内容区的末尾多出了几个字节,这几个字节里如果恰好带着双引号、分号、字母 s,就会被 PHP 误认为新的结构字符,相当于从字符串的“数据区”逃逸到了“语法区”。

理解了这一点,再看网上那些 writeup 里的妖魔鬼怪 payload,就不会觉得神奇了。它们本质上都是在数据区里埋了一段伪语法,然后通过长度差让这段伪语法在解析时恰好从数据区里“漏”出来,成为真正的代码结构。

2. 反序列化字符串逃逸的原理拆解

2.1 先回顾一下序列化字符串的结构

在讲逃逸之前,必须把 PHP 序列化的基本格式吃透。一个简单的数组序列化后长这样:

a:2:{s:8:"username";s:5:"admin";s:5:"phone";s:3:"123";}

这段结构拆开来看:

  • a:2表示这是一个包含 2 个元素的数组;
  • {和}是数组的边界;
  • s:8:"username"表示一个长度为 8 的字符串,内容是username;
  • ;是字段之间的分隔符;
  • 每个数组元素由键和值组成,键是s:8:"username",值是s:5:"admin"。

注意这里的数字都是按字节算的,不是按字符个数。如果是中文、emoji 这类多字节字符,长度计算特别容易出错,后面我会专门讲。

PHP 的unserialize()在解析s:5:"admin";时,流程是:先读到s表示字符串,再读到5表示长度,然后跳过引号,连续读取 5 个字节,读完后再读一个双引号作为字符串结束标记,接着读分号作为当前字段结束。问题就出在这个“连续读取 N 个字节”上——如果内容里混入了超级长的字节,或者内容比声明的长度短,PHP 根本不会关心,它只按长度去读。

2.2 变长逃逸:内容变长了,长度声明没变

这是 piapiapia 这类题目最常用的手法。假设过滤函数做了这么一件事:

$str = preg_replace('/hi/', 'hello', $str);

hi长度是 2,hello长度是 5,每次替换会让字符串增加 3 个字节。

再看这段序列化字符串:

a:2:{s:8:"username";s:2:"hi";s:5:"phone";s:3:"123";}

如果hi被替换成hello,最终变成:

a:2:{s:8:"username";s:2:"hello";s:5:"phone";s:3:"123";}

现在s:2告诉你 username 的长度是 2,但实际上内容变成了 5 个字节。反序列化读取时,PHP 只会读he作为值,然后期待下一个字符是双引号。结果下一个字符是l,直接报错。

那怎么利用呢?我们可以通过控制输入中hi的数量,让多出来的字节数刚好等于一段完整的序列化字段。比如我想注入一个admin字段,内容是yes,这段恶意结构就是:

";s:5:"admin";s:3:"yes";

注意它开头是";,这是因为在字符串值读完之后,PHP 还需要读一个双引号和分号来结束当前字段。我们故意让多出来的字节从这里开始,PHP 读到的第一个字符就是",它会认为是 username 的结束引号,接着读;结束当前键值对,然后继续解析后面的s:5:"admin";s:3:"yes";,这样就成功注入了一个新字段。

2.3 减少逃逸:内容变短,长度声明没变

与变长逃逸对应的是“减少逃逸”,也就是替换函数把长字符串替换成短字符串,比如hello变成hi。这时实际内容比声明长度短,PHP 读取完内容后会多读后面的字符来凑够声明的长度,这些被多读进来的字符会被吞进当前字符串的值里,导致原本的结构边界发生偏移。

减少逃逸的构造思路是:刻意让当前字段的声明长度大于实际内容,让 PHP 把后面的";、字段名、字段值等原有结构的一部分吞掉,从而改变接下来的解析结果。减少逃逸往往能直接吞掉不需要的字段,实现“删字段”或者“改字段”的效果,但它对长度精度的要求更苛刻,差一个字节整个序列化就会崩。实战中变长逃逸更常见,因为它的溢出部分可以直接放新结构,不用去猜原有尾部的边界。

两种逃逸的对比可以看这个表:

类型替换方向长度差解析表现典型用法
变长逃逸短变长实际内容比声明长多出的字节漏到语法区注入新字段
减少逃逸长变短实际内容比声明短后续字符被吞进值吞掉旧结构,覆盖字段

不管哪种,共同的前提都是:替换发生在序列化之后。如果替换发生在反序列化之前,长度和内容会同步变化,不会产生边界错位。

3. 从零构造一次字符串逃逸攻击

3.1 搭建一个可复现的最小示例

为了把原理讲透,我写了一个非常小的模拟环境,完全靠变量拼接,不做任何黑盒干扰。这个示例就是 piapiapia 的缩小版,保留了最核心的两个点:序列化后替换、反序列化利用。

假设后端代码长这样:

<?php $username = $_POST['username']; $phone = '123'; $profile = [ 'username' => $username, 'phone' => $phone ]; $data = serialize($profile); // 过滤函数:把所有 hi 替换成 hello $data = preg_replace('/hi/', 'hello', $data); $profile = unserialize($data); echo '<pre>'; var_dump($profile);

这段代码的漏洞点非常明确:serialize()之后调用了preg_replace(),然后直接unserialize()。我们的目标是让反序列化出来的数组多一个admin字段,内容为yes,也就是让最终数组变成:

[ 'username' => '这里是什么不重要', 'admin' => 'yes', 'phone' => '123' ]

3.2 计算溢出长度和 payload

每个hi会变成hello,长度从 2 变成 5,增加 3 字节。如果我们在用户名里放 n 个hi,替换后就会多出 3n 个字节。

我们要注入的结构是:

";s:5:"admin";s:3:"yes";

数一下这个结构的字节数(注意所有引号、分号、冒号都要算进去):

  • ";2 字节
  • s:5:4 字节
  • "admin"7 字节
  • ;1 字节
  • s:3:4 字节
  • "yes"5 字节
  • ;1 字节

总共 24 字节。所以我们需要 3n = 24,也就是 n = 8 个hi,才能刚好让溢出部分等于这个恶意结构。

接下来是输入的用户名怎么组装。直接写hihihi...加恶意结构行不行?我们看序列化后的样子。serialize()时,用户名是按原始输入的字节长度来声明长度的。如果输入是 8 个hi加上恶意结构,原始长度 L = 16 + 24 = 40。替换后,8 个hi变成 8 个hello,长度 40,加上恶意结构 24,总共 64。反序列化时按 L = 40 读取用户名内容,读到的就是替换后的前 40 字节,也就是 8 个hello正好 40 字节。读完这 40 字节后,接下来的 24 字节恰好是恶意结构,PHP 会先读一个双引号闭合当前字符串,再读分号结束当前字段,然后解析后面的s:5:"admin";s:3:"yes";,新字段就这么诞生了。

这里其实有个小技巧:我们可以不先从8个hi+恶意结构开头数,而是在恶意结构前面加一段任意填充字符,因为这些填充字符会算进 L 里被读走。所以更通用的构造方式是:

username = (hi 重复 n 次) + 任意填充 + 恶意结构

只要满足:替换后多出的 3n 个字节正好等于恶意结构的长度,并且恶意结构刚好位于溢出区开头,就能成功。

为了保险,我用 8 个hi,不加额外填充。那么用户名输入就是:

hihihihihihihihi";s:5:"admin";s:3:"yes";

注意hi是英文字母 h 和 i,别跟中文的“嗨”搞混。

3.3 编写 payload 生成脚本

手工数 24 字节容易数错,实战里我都是写一个小脚本自动拼。这里给出一个 PHP 生成脚本,直接拿来用就行:

<?php // 构造恶意结构 $malicious = '";s:5:"admin";s:3:"yes";'; // 计算恶意结构长度 $mal_len = strlen($malicious); echo "恶意结构长度: $mal_len\n"; // 每个 hi 替换为 hello 增加 3 字节 $inc = 3; $n = $mal_len / $inc; echo "需要 hi 的数量: $n\n"; // 生成用户名 $username = str_repeat('hi', $n) . $malicious; // 演示整个流程 $profile = ['username' => $username, 'phone' => '123']; $data = serialize($profile); $data = preg_replace('/hi/', 'hello', $data); echo "替换后的序列化字符串:\n$data\n\n"; $result = unserialize($data); var_dump($result);

跑一遍这个脚本,输出里应该能看到admin字段成功出现。这个脚本的价值在于:以后遇到任何替换规则,只需要改$inc和$malicious,几秒钟就能生成一个新的可用 payload,不用每次手数。

3.4 攻击成功的标志

演示结果里,unserialize不再报错,而且 var_dump 出来的数组有三个元素。username字段的值会是一大串hellohello...,这很正常,因为 PHP 按照声明长度读走了前 40 字节,而这段内容恰好是替换后的 8 个 hello。真正起作用的是后面的admin字段。

在实际的 piapiapia 题目里,注入的字段可能不是admin,而是某个后续会参与文件读取的路径字段,比如把手机号字段覆盖成config.php,或者加入一个file字段让程序读取 flag 文件。原理完全一样,只是最后读取文件的那段代码决定了你要覆盖哪个字段名。

3.5 从最小示例回到现实题目

真实题目里,替换规则往往是where变成hacker,这两者的长度差是多少?where5 字节,hacker6 字节,增加 1 字节。如果恶意结构长度是 24,就需要 24 个where。所以你在网上看到的 writeup 里,用户名经常是一长串wherewherewhere...,这就是在凑长度。

有些变体里替换规则是preg_replace('/\'/', '\\\'', ...),转义单引号会让长度增加,也能达到同样效果。还有一些题目会用 base64、urlencode 等编码函数代替preg_replace,本质都一样:只要序列化之后的内容和序列化之前的长度声明不一致,就有逃逸的可能。

做题时我一般先定位过滤函数,看它处理的是原始输入还是序列化后的字符串。如果是后者,再数一下替换前后的长度差,剩下的就是套公式。

4. 常见问题与排错心得

4.1 长度计算到底哪里容易翻车

这个漏洞的所有成败都系于长度计算。我自己踩过的坑主要有三个:

第一,把英文字符长度按 1 算是对的,但一旦混入中文、emoji,PHP 里strlen()和肉眼看到的字符个数完全不同。比如中文在 UTF-8 下是 3 个字节,如果恶意结构里包含中文,必须用strlen()实测,不能用眼睛数。

第二,preg_replace的替换次数可能受正则影响。如果替换模式是/hi/i之类的不区分大小写,那HI、Hi也会被替换,长度差就不再是单纯的strlen('hello') - strlen('hi')了。做题前用一段小程序先测一下实际替换结果,永远比在脑子里硬算靠谱。

第三,拼接 placehodler 时忘了算结尾。比如恶意字段s:5:"admin"s:3:"yes";看着人畜无害,但如果少了开头的";,反序列化时字符串闭合引号找不到,直接报Error at offset。构造出的 payload 最好先在本地环境跑一遍,再放到题目里试。

4.2 数组闭合和字段覆盖问题

前面我没有在恶意结构里加},因为一旦加上},PHP 会认为整个数组在这个位置结束了,后面原有的phone字段会变成多余内容,导致反序列化失败。所以我们的恶意结构通常是注入一个新字段,但不提前闭合数组。

如果想覆盖已有字段,比如把phone改成指定值,光靠注入同名字段还不够,因为 PHP 反序列化遇到重复键时,后面的值会覆盖前面的值。如果你注入的phone在原来的phone之前,最终生效的仍然是原来的phone。这种情况下有两种思路:一是把恶意结构中注入的字段放在原有字段后面,让它成为同一个键的第二次出现;二是构造一个长度极大的字段值,把原有尾部的phone字段直接吞进这个值里,再从被吞的内容后面重新组装出想要的结构。第二种方法更接近减少逃逸的思路,难度也更大。

我在复现原题时最常用的技巧还是加一个新字段,然后通过代码逻辑里的判断条件去触发敏感行为。比如后端原本用photo字段作为头像路径,我注入一个新的photo字段,并且在它前面保留原phone的结构,最终读取头像路径时就会用到我注入的这个值。具体覆盖哪个字段,完全取决于目标代码读的是哪个键。

4.3 过滤位置不同,结果完全不同

这是很多新手最容易忽略的。同样一段preg_replace,放在serialize()之前还是之后,是两种完全不同的情况。

如果放在serialize()之前,比如先$username = preg_replace('/hi/', 'hello', $username),再塞进数组做serialize(),那序列化里面的长度值也是替换后的长度,长度和内容是一致的,根本不会产生逃逸。反序列化也不会出错,只是用户输入的值被改写了而已。

如果放在serialize()之后,比如题目代码里那样,先serialize($profile),然后对整个序列化字符串做替换,这时长度值已经写死,才可能出现逃逸。

所以拿到代码第一件事,画出数据流:用户输入 → 过滤 → 序列化 → 存储/传输 → 反序列化。只要过滤函数在序列化之后、反序列化之前的某个环节执行了,就可以考虑字符串逃逸。如果过滤只在最外层做了一次,但后面序列化时又把过滤后的结果组装了进去,也可能产生二次逃逸,那种题目会更绕。

4.4 防御端有哪些值得注意的做法

站在漏洞挖掘角度讲完利用,反过来看防御,其实这个问题一点也不难防。最直接的办法就是永远不要对不可信数据直接unserialize()。如果业务必须存数组,优先用 JSON 序列化,json_encode()和json_decode()不存在长度声明,就没有逃逸的土壤。

如果一定要用 PHP 序列化,至少要做到:

  • 对反序列化的数据加一层校验,反序列化之前用hash_equals比对签名,签名使用的密钥只在服务器端保存;
  • 如果只需要对象数据,可以限制unserialize允许的类列表,用allowed_classes参数过滤掉不认识的类;
  • 对所有输入做长度一致性校验,序列化后和序列化前对比长度,一旦发现异常直接丢弃;
  • 过滤函数尽量不要放在序列化之后的链路上。你以为的“统一过滤”,很可能帮你把漏洞喂给了反序列化器。

这些措施成本都不高,但很多项目里unserialize往往隐蔽在缓存组件、session 处理、RPC 请求解析等地方,审计时要格外注意。

最后再分享一个实操习惯。做这类题目,我通常先把目标环境的 PHP 版本确认好,不同版本的unserialize对非法结构的报错容忍度有差异。本地验证时,我习惯直接写一个error_reporting(E_ALL); ini_set('display_errors', 1);,这样一旦长度算错,报错 offset 能直接告诉我错在第几个字节。把 payload 生成脚本和验证脚本放到同一个目录下,每次调完$inc和$malicious立刻跑一遍,比手动在脑子里模拟快得多。字符串逃逸这东西,看着玄乎,实际就是一层窗户纸。你把长度差算明白,把序列化结构背熟,再用脚本自动生成 payload,很快就能形成肌肉记忆。以后再遇到反序列化题,先查过滤位置,再算长度差,大概率能少踩很多坑。

相关新闻

HYSYS Skill自动化更新:WorkBuddy v2.1.0实战指南

HYSYS Skill自动化更新:WorkBuddy v2.1.0实战指南

1. 项目概述&#xff1a;当流程模拟工程师开始“写代码”而不是“点鼠标”你有没有过这样的经历&#xff1a;凌晨两点&#xff0c;刚跑完第7轮HYSYS稳态模拟&#xff0c;发现一个物性方法参数填错了——不是改一个地方&#xff0c;而是要打开32个流股、15个单元操作、8个物流表…

2026/10/20 16:36:29 阅读更多 →
手把手教你计算Zigbee 16位网络地址:Cskip公式与工程配置

手把手教你计算Zigbee 16位网络地址:Cskip公式与工程配置

前一阵子接手一个智能照明的调试&#xff0c;网络里三十来个节点&#xff0c;灯总是不定期掉线。抓包工具里一串16位网络地址&#xff0c;0x0017、0x00A2、0x023B&#xff0c;密密麻麻&#xff0c;谁是谁根本对不上号。如果只是找节点倒还好办&#xff0c;慢慢翻64位MAC也能对应…

2026/10/20 16:35:27 阅读更多 →
Delphi 12安装EhLib控件包指南:从安装步骤到排错实战

Delphi 12安装EhLib控件包指南:从安装步骤到排错实战

简介&#xff1a;EhLib VCL 10.2.42 for Delphi 12 Athens 是一套面向 Delphi 开发者的原生 VCL 控件库&#xff0c;覆盖数据库网格、报表、图形图表、日期时间与对话框等常见开发场景&#xff0c;可显著增强桌面应用的数据展示和界面定制能力&#xff0c;广泛适用于进销存、ER…

2026/10/20 16:35:27 阅读更多 →

最新新闻

开放式代码审查:从规则沉淀到AI辅助的团队实践

开放式代码审查:从规则沉淀到AI辅助的团队实践

那一次Review&#xff0c;至今让我印象深刻。我自认为提交了一段“完美”的代码&#xff1a;类型明确、命名讲究、注释齐全&#xff0c;甚至提前把函数的三种边界情况都测了一遍。结果同事在评审里只问了我三个问题&#xff1a;为什么定义成接口而不是具体类型&#xff1f;三秒…

2026/10/20 22:55:46 阅读更多 →
SQL孤岛与间隙问题全解析:从窗口函数到连续登录实战

SQL孤岛与间隙问题全解析:从窗口函数到连续登录实战

做时间序列分析的朋友一定碰到过这样的场景&#xff1a;一张表里存着用户的登录记录&#xff0c;日期断断续续&#xff0c;你不仅要找出哪几天没登录&#xff0c;还要把连续登录的几天拼成一个“段”来统计。又或者&#xff0c;你面前是一堆带时间戳的告警事件&#xff0c;需要…

2026/10/20 22:55:46 阅读更多 →
机器人系统十年演进:从脉冲伺服到数字孪生的架构变革

机器人系统十年演进:从脉冲伺服到数字孪生的架构变革

要说这十年机器人系统最大的变化&#xff0c;我得先讲个真实场景。2024年我在客户现场调一套柔性上料系统&#xff0c;机器人配上3D视觉和力控打磨单元&#xff0c;工程师们坐在办公室里用数字孪生平台远程调试&#xff0c;坐标变换、轨迹规划、碰撞检测全在仿真环境里跑完&…

2026/10/20 22:55:46 阅读更多 →
Diagram-Design:结构化视觉表达的底层工程能力

Diagram-Design:结构化视觉表达的底层工程能力

1. 项目概述&#xff1a;这不是画图&#xff0c;是用结构化思维解决真实问题的底层能力“diagram-design”这个词组乍看像某个设计软件的插件名&#xff0c;或者某次内部培训的课程代号&#xff0c;但在我过去十年带过的几十个跨领域项目里&#xff0c;它其实代表一种被严重低估…

2026/10/20 22:55:46 阅读更多 →
diagram-design:结构化思维的可视化推演方法

diagram-design:结构化思维的可视化推演方法

1. 项目概述&#xff1a;这不是画图&#xff0c;是用结构化思维解决真实问题“diagram-design”这个词组乍看像一个工具名或软件功能&#xff0c;但在我过去十年带过的几十个跨领域项目里&#xff0c;它从来不是指“用Visio拖几个方框”&#xff0c;而是一套把模糊想法落地为可…

2026/10/20 22:55:46 阅读更多 →
动作单元驱动的能量流管理:知识工作者的复利提效法

动作单元驱动的能量流管理:知识工作者的复利提效法

1. 项目概述&#xff1a;这不是“躺平”&#xff0c;而是用系统性思维重构日常工作的能量守恒“躺平挖 alpha”这个标题一出来&#xff0c;很多人第一反应是调侃——又一个打鸡血式的时间管理法&#xff1f;或者干脆是反内卷的消极宣言&#xff1f;其实完全不是。我在某实验室带…

2026/10/20 22:54:43 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式: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/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/20 9:50:45 阅读更多 →