SQL注入原理详解:从数据到代码的越界与手工验证

1. 从一条登录框说起:SQL注入到底在注什么

很多人第一次接触SQL注入,都是从一条看起来人畜无害的登录框开始的。用户名输入admin,密码随便填个123456,点击登录,页面提示“密码错误”。然后你把用户名改成admin' --,密码依然随便填,结果直接进了后台。这个场景在十多年前的网站上司空见惯,放到今天虽然少了很多,但原理层面的东西一点没变——SQL注入的本质,是用户输入的数据被当成了SQL代码来执行。

这个项目标题叫“SQL注入-1:sql注入基本原理”,我理解它想做的事情,是把注入这件事从“怎么打”拉回到“为什么能打”的层面。市面上讲注入的文章,一上来就是union select一把梭,或者直接甩sqlmap跑一遍,但真正把“数据为什么变成了代码”这件事讲透的并不多。我写这篇东西,就是想把这个链条从头到尾捋一遍,让刚入门的人知道自己在干什么,也让已经会跑工具的人回头补上那块缺失的拼图。

这篇文章适合谁看?如果你是刚学Web安全的学生,或者做开发想搞清楚自己写的代码哪里出了问题,再或者你是运维、测试岗,想理解渗透测试报告里那些注入点到底意味着什么,那这篇内容应该能帮到你。我不假设你有很深的数据库基础,但你需要知道最基本的SQL语句长什么样,比如SELECT、WHERE、AND这些关键词是干嘛的。如果这些还不熟,建议先花半小时补一下,再回来看,效果会好很多。

整篇内容我会围绕几个核心问题展开:SQL注入的底层原理是什么,为什么一个单引号就能改变整个查询的逻辑,数据库在接收到拼接字符串时到底做了什么,以及在实际操作中怎么去判断一个点是否存在注入、怎么去验证、怎么去利用。过程中我会补充一些实际踩过的坑和排查技巧,这些东西在教科书里通常不会写,但真正干活的时候特别有用。

2. 注入的底层逻辑:当数据越过了那条边界

2.1 拼接字符串:一切问题的起点

要理解SQL注入,得先理解一个非常朴素的事实:很多Web应用在执行数据库查询时,是把用户输入直接拼接到SQL语句里的。这不是什么高深的设计,反而是最直觉的写法。比如一个查询用户信息的接口,后端代码可能是这样的:

username = request.GET.get('username') sql = "SELECT * FROM users WHERE name = '" + username + "'" cursor.execute(sql)

这段代码的逻辑很直白:拿到用户传来的username,用单引号把它包起来,拼成一个完整的SQL语句,然后交给数据库执行。如果用户传的是alice,数据库收到的就是:

SELECT * FROM users WHERE name = 'alice'

这没问题。但如果用户传的是alice' OR '1'='1,数据库收到的就变成了:

SELECT * FROM users WHERE name = 'alice' OR '1'='1'

OR '1'='1'永远为真,于是这条查询会返回users表里的所有记录。用户输入的数据,在这里不再只是“数据”,而是变成了SQL语句结构的一部分。这就是注入的起点。

我经常用一个类比来解释这件事:假设你给助理写了一张便签,上面写着“请把冰箱里的苹果拿给我”。正常情况下助理会去拿苹果。但如果你在便签上写的是“请把冰箱里的苹果拿给我,然后把冰箱里所有东西都扔掉”,助理照做了,问题不在助理,而在于你写的便签本身被当成了指令。SQL注入就是用户往便签里塞了额外的指令,而程序没有做任何区分。

2.2 数据库是怎么“看”这条语句的

很多人会好奇:数据库难道分不清哪些是用户输入、哪些是原始代码吗?答案是,在拼接模式下,数据库根本分不清。数据库接收到的就是一个完整的字符串,它按照SQL语法去解析这个字符串,遇到单引号就认为是字符串常量的开始或结束,遇到OR就认为是逻辑运算符,遇到--就认为是注释。它没有“这是用户输入”的元信息,它只认语法。

这就引出了一个关键点:注入能够成功,前提是用户输入能够改变SQL语句的语法结构。如果用户输入只是被当作字符串常量的一部分,那它永远只是数据。但一旦用户输入里包含了单引号、双引号、括号、注释符这些具有语法意义的字符,并且程序没有对这些字符做转义或过滤,那么用户输入就能“越狱”,从数据域跳到代码域。

我用一个更具体的例子来说明这个边界。假设原始SQL是:

SELECT * FROM articles WHERE id = 1 AND status = 'published'

如果id参数是用户可控的,用户传1,语句正常。用户传1 OR 1=1,语句变成:

SELECT * FROM articles WHERE id = 1 OR 1=1 AND status = 'published'

由于AND的优先级高于OR,实际执行的是id = 1 OR (1=1 AND status = 'published'),结果会返回所有id为1的文章,以及所有已发布的文章。如果用户传1; DROP TABLE articles; --,那在某些支持多语句的数据库驱动下,articles表可能直接被删掉。当然,现代驱动大多默认禁用多语句,但这个例子足以说明问题的严重性。

2.3 注入点的三种常见形态

在实际测试中,注入点并不总是像登录框那么明显。我把它归纳为三种常见形态,理解这三种形态有助于你在面对一个陌生系统时快速定位可能的入口。

第一种是字符串型注入。这是最经典的形态,用户输入被包裹在单引号或双引号里。比如WHERE name = '$input'。这种注入点的特征是,输入一个单引号会导致SQL语法错误,页面返回500或者报错信息。测试方法很简单:输入',看是否报错;输入'',看是否恢复正常。如果'报错而''正常,基本可以确定存在字符串型注入。

第二种是数字型注入。用户输入没有被引号包裹,直接拼在数字位置。比如WHERE id = $input。这种注入点输入单引号不会报错,因为单引号在数字上下文里本身就是语法错误,但报错信息可能被吞掉。测试方法是输入1 AND 1=1和1 AND 1=2,对比返回结果是否不同。如果1 AND 1=1返回正常而1 AND 1=2返回空,说明注入存在。

第三种是搜索型注入。这种常见于模糊查询,比如WHERE title LIKE '%$input%'。用户输入被包裹在百分号和引号之间。测试时输入%' AND 1=1 --,如果返回所有记录,说明注入存在。这种注入点的特殊之处在于,你需要同时闭合百分号和引号,注释掉后面的部分。

这三种形态的区分很重要,因为后续构造payload时,闭合方式完全不同。字符串型需要闭合引号,数字型不需要,搜索型需要闭合%'。我见过不少新手拿着字符串型的payload去打数字型注入点,结果怎么都不成功,问题就出在这里。

2.4 为什么预编译能解决问题

理解了注入的原理,就能理解为什么预编译(Prepared Statement)是解决注入的根本方案。预编译的核心思想是:先把SQL语句的结构确定下来,再把用户输入作为参数传进去。数据库在执行时,已经知道哪些部分是代码、哪些部分是数据,用户输入无论包含什么字符,都只会被当作数据来处理,不会改变语句结构。

用之前的例子对比:

# 拼接方式,有注入风险 sql = "SELECT * FROM users WHERE name = '" + username + "'" # 预编译方式,无注入风险 sql = "SELECT * FROM users WHERE name = ?" cursor.execute(sql, (username,))

在预编译方式下,即使username传的是alice' OR '1'='1,数据库也只会把它当作一个普通的字符串去匹配name字段,不会把OR '1'='1'解析成逻辑运算符。这就是预编译的威力——它从语法层面切断了数据变成代码的路径。

但这里有个常见的误区:不是用了预编译就绝对安全。如果预编译只用在部分参数上,而其他参数仍然是拼接的,那注入依然存在。另外,如果表名、列名、排序字段这些不能作为参数的地方是用户可控的,那预编译也帮不上忙,因为这些位置本身就不支持参数化。这种情况下需要用白名单校验,而不是依赖预编译。

3. 手工验证注入:从单引号到数据回显

3.1 第一步:判断闭合方式

拿到一个疑似注入点,第一件事不是急着上工具,而是手工判断闭合方式。这一步决定了后续所有payload的构造。我通常的做法是依次尝试几个字符,观察页面反应。

先输入一个单引号'。如果页面报错,说明单引号被带入了SQL语句并且破坏了语法结构,大概率是字符串型注入。如果页面正常,再输入一个双引号",看是否报错。如果都不报错,尝试输入\,看是否报错,因为反斜杠在某些数据库里是转义符,可能导致引号逃逸。

如果单引号报错,接着输入两个单引号''。如果页面恢复正常,基本可以确认是字符串型注入,因为两个单引号在SQL里表示一个转义后的单引号,语法上是合法的。如果''仍然报错,那可能不是注入点,或者闭合方式不是单引号。

对于数字型注入,单引号通常不会报错,因为数字上下文里单引号本身就是非法的,但有些程序会捕获异常并返回统一错误页。这时候需要用逻辑判断:输入1 AND 1=1和1 AND 1=2,对比返回内容。如果前者正常、后者异常或返回空,说明注入存在。

注意:有些站点开启了WAF,输入单引号可能直接被拦截并返回403。这时候不要急着放弃,尝试用URL编码、大小写混合、内联注释等方式绕过。比如'可以写成%27,AND可以写成/*!AND*/。但绕过WAF是另一个话题,这里先聚焦原理。

3.2 第二步:确定字段数

确认注入存在后,下一步是确定当前查询返回的字段数。这一步是为了后续用UNION SELECT做数据回显做准备。方法有两种:ORDER BY和UNION SELECT。

ORDER BY的思路是:ORDER BY 1、ORDER BY 2、ORDER BY 3……依次递增,直到页面报错。报错时的前一个数字就是字段数。比如ORDER BY 3正常、ORDER BY 4报错,说明查询返回3个字段。

SELECT * FROM users WHERE id = 1 ORDER BY 3 --

UNION SELECT的思路是:UNION SELECT 1、UNION SELECT 1,2、UNION SELECT 1,2,3……直到页面正常返回。正常时的数字就是字段数。

SELECT * FROM users WHERE id = 1 UNION SELECT 1,2,3 --

两种方法各有优劣。ORDER BY更隐蔽,因为不涉及UNION关键词,不容易被WAF拦截。UNION SELECT更直接,而且能顺便确定哪些字段会回显到页面上。我通常先用ORDER BY确定字段数,再用UNION SELECT确定回显位。

3.3 第三步:找数据回显位

确定字段数后,用UNION SELECT把每个位置替换成数字,观察页面上哪些数字被显示出来了。比如字段数是3,构造:

SELECT * FROM users WHERE id = 1 UNION SELECT 1,2,3 --

如果页面上显示了2和3,说明第2和第3个字段会回显到页面。这两个位置就是后续提取数据的“窗口”。如果页面只显示2,那就只能用第2个字段来输出数据。

这里有个细节:UNION前面的查询最好让它的返回结果为空,这样页面显示的就是UNION后面的数据。常用的技巧是把id设为一个不存在的值,比如id = -1,或者用AND 1=2让前面的查询返回空。

SELECT * FROM users WHERE id = -1 UNION SELECT 1,2,3 --

如果页面正常显示2和3,说明回显位确定成功。接下来就可以把数字替换成数据库函数,比如database()、user()、version(),来获取数据库信息。

3.4 第四步:提取数据

回显位确定后,提取数据就是按部就班的事情。以MySQL为例,常用的信息收集函数包括:

函数作用示例
database()当前数据库名UNION SELECT 1,database(),3
user()当前数据库用户UNION SELECT 1,user(),3
version()数据库版本UNION SELECT 1,version(),3
@@datadir数据目录UNION SELECT 1,@@datadir,3
@@version_compile_os操作系统UNION SELECT 1,@@version_compile_os,3

获取表名和列名需要查询information_schema库。比如获取当前数据库的所有表名:

SELECT * FROM users WHERE id = -1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema = database() --

获取指定表的所有列名:

SELECT * FROM users WHERE id = -1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name = 'users' --

最后提取具体数据:

SELECT * FROM users WHERE id = -1 UNION SELECT 1,group_concat(username,0x3a,password),3 FROM users --

0x3a是冒号的十六进制表示,用来分隔用户名和密码。group_concat会把多行结果拼成一个字符串,方便一次性输出。

实操心得:group_concat默认有长度限制,通常是1024字节。如果数据量很大,会被截断。可以用SET SESSION group_concat_max_len = 999999来临时调大,但需要数据库权限支持。如果没有权限,就只能用LIMIT分页提取,效率会低很多。

4. 报错注入与盲注:没有回显时怎么办

4.1 报错注入:让数据库自己说出答案

很多时候,UNION SELECT的回显位并不存在,页面不会直接显示查询结果。这时候如果页面会返回数据库的报错信息,就可以用报错注入。报错注入的核心思路是:构造一个会让数据库报错的语句,把想查询的数据拼接到错误信息里。

MySQL里常用的报错函数有extractvalue和updatexml。以extractvalue为例:

SELECT * FROM users WHERE id = 1 AND extractvalue(1, concat(0x7e, database())) --

extractvalue的第二个参数要求是合法的XPath格式,如果传入~database()这种非法格式,数据库会报错,错误信息里会包含~后面的内容,也就是数据库名。页面如果显示了这个错误信息,数据就泄露了。

updatexml的原理类似:

SELECT * FROM users WHERE id = 1 AND updatexml(1, concat(0x7e, (SELECT table_name FROM information_schema.tables WHERE table_schema = database() LIMIT 0,1)), 1) --

报错注入的优点是速度快,不需要逐字符猜解。缺点是依赖页面回显错误信息,如果程序关闭了错误显示,或者把错误统一处理成自定义页面,就用不了。

4.2 布尔盲注:用真假来判断

如果页面既不回显数据,也不回显错误,那就只能靠“盲注”。布尔盲注的思路是:构造一个条件判断,根据页面返回的正常或异常来推断条件的真假。

比如判断数据库名的第一个字符是不是s:

SELECT * FROM users WHERE id = 1 AND substring(database(),1,1) = 's' --

如果页面正常返回,说明条件为真,第一个字符是s。如果页面异常或返回空,说明条件为假。通过遍历所有可能的字符,就能逐字符猜出完整的数据。

布尔盲注的缺点是效率极低。一个数据库名如果有10个字符,每个字符有26个字母加10个数字共36种可能,最坏情况下需要尝试360次。实际测试中通常用二分法优化,把36种可能压缩到6次左右,但整体速度仍然很慢。

4.3 时间盲注:当页面连真假都不区分时

最极端的情况是,页面无论条件真假都返回同样的内容,布尔盲注也失效了。这时候可以用时间盲注:构造一个条件判断,如果条件为真,就让数据库延迟几秒再返回。

MySQL里用sleep()函数:

SELECT * FROM users WHERE id = 1 AND IF(substring(database(),1,1) = 's', sleep(5), 0) --

如果第一个字符是s,页面会延迟5秒才返回。如果不是,页面立即返回。通过测量响应时间,就能判断条件真假。

时间盲注的优点是几乎适用于所有场景,只要注入存在且数据库支持延时函数。缺点是速度最慢,而且受网络波动影响大。我实测下来,一个字符平均需要5到8秒,一个10字符的数据库名需要将近1分钟。如果数据量大,时间成本会非常高。

注意事项:时间盲注的延迟时间不要设得太短,否则容易被网络抖动干扰。我通常设5秒,如果网络状况差,会设8到10秒。另外,有些数据库的sleep()函数在特定条件下会被优化掉,比如在WHERE子句里如果前面的条件已经为假,sleep()可能不会执行。这时候需要用AND确保前面的条件为真,或者用CASE WHEN来构造。

4.4 三种盲注方式的对比

方式依赖条件速度适用场景
报错注入页面回显错误信息快开发环境、错误未屏蔽
布尔盲注页面真假状态可区分中错误被屏蔽但页面有差异
时间盲注数据库支持延时函数慢页面完全无差异

实际测试中,我会按这个顺序尝试:先看有没有回显位,有就用UNION;没有回显位就看有没有报错,有就用报错注入;没有报错就看页面真假是否有差异,有就用布尔盲注;最后才用时间盲注。这个顺序是从快到慢,能节省大量时间。

5. 注入之外:那些容易踩的坑和排查技巧

5.1 编码问题导致的“注入不存在”

我遇到过好几次这样的情况:手工测试时单引号报错,但用工具跑却提示“not injectable”。排查后发现是编码问题。有些站点会对用户输入做URL解码、HTML实体解码、或者Unicode解码,如果工具发送的payload没有经过同样的编码处理,到达数据库时就已经变形了,自然无法触发注入。

比如站点会把%27解码成',但工具发送的是',经过站点的解码逻辑后可能变成了其他字符。这时候需要手动调整payload的编码方式,或者在工具里配置相应的编码选项。

另一个常见问题是字符集不匹配。比如数据库用的是GBK编码,而payload里包含某些特殊字符,可能导致宽字节注入。这种情况下单引号被反斜杠转义后,反而和前面的字节组合成了一个新的汉字,从而逃逸出引号。宽字节注入的原理稍微复杂一些,但核心还是编码层面的问题。

5.2 参数污染与二次注入

有些站点会对用户输入做一次过滤,比如把AND替换成空字符串,把SELECT替换成空字符串。这种过滤看起来能挡住注入,但实际上可以用双写绕过:ANANDD经过替换后变成AND,SELSELECTECT变成SELECT。这种绕过方式在早期的WAF里非常常见。

二次注入是另一种容易被忽略的场景。用户输入在第一次写入数据库时被转义了,看起来安全。但当这些数据被第二次从数据库读出并拼接到新的SQL语句时,转义已经失效了,注入就发生了。这种注入点很难通过黑盒测试发现,通常需要结合代码审计。

5.3 权限限制下的信息收集

不是所有注入点都能直接读到数据。有些数据库用户权限很低,只能访问特定的库或表。这时候需要先判断当前用户的权限。MySQL里可以用SHOW GRANTS或者查询information_schema.user_privileges来查看权限。

如果当前用户没有information_schema的访问权限,那就只能靠猜表名和列名。常见的表名有users、admin、member、user,常见的列名有username、password、email。这种猜解方式效率很低,但在权限受限的情况下是唯一的办法。

实操心得:如果information_schema被禁用,可以尝试查询mysql.user表(需要root权限),或者用LOAD_FILE()读取系统文件。但这些操作对权限要求更高,而且可能触发安全告警。在实际测试中,如果发现权限受限,我会先评估是否有必要继续深入,避免在不必要的情况下触发防护机制。

5.4 常见问题速查表

问题现象可能原因排查方向
单引号不报错不是字符串型注入,或错误被屏蔽尝试数字型判断,检查错误处理逻辑
单引号报错但双引号不报错闭合方式是单引号用两个单引号验证
ORDER BY一直正常字段数超出预期,或注入点无效尝试更大的数字,检查是否被过滤
UNION SELECT报错字段数不匹配重新确定字段数
回显位不显示数据回显位被过滤或编码尝试用concat拼接,检查页面编码
时间盲注延迟不稳定网络抖动或数据库负载增加延迟时间,多次测试取平均
工具提示不可注入但手工可以编码或WAF问题检查工具编码配置,尝试绕过WAF

5.5 从防御角度看注入

写到这里,我想从防御的角度补充几句。理解注入原理的最终目的,是为了写出更安全的代码。预编译是根本方案,但还有一些细节需要注意。

第一,不要信任任何用户输入。不管是表单、URL参数、Cookie还是HTTP头,只要进入SQL语句,就必须经过处理。第二,最小权限原则。数据库用户只授予必要的权限,不要用root连接数据库。第三,错误信息不要直接返回给用户。自定义错误页面可以避免泄露数据库结构。第四,定期做代码审计和渗透测试。自动化工具能发现大部分常见注入,但逻辑漏洞和二次注入需要人工审查。

我在实际项目中见过太多“用了ORM就安全”的误解。ORM确实能减少注入风险,但如果ORM里用了原生SQL拼接,或者用了extra()、raw()这类方法,注入依然存在。安全不是一个开关,而是一个持续的过程。

6. 个人体会:注入学习中的几个关键节点

回顾我自己学注入的过程,有几个节点印象很深。第一个节点是理解“数据变成代码”这个核心概念。在那之前,我只是机械地记payload,换个场景就不会用了。理解原理之后,面对陌生的注入点,我能自己推导出闭合方式和payload构造方法。

第二个节点是手工注入的熟练。工具确实快,但工具是黑盒,你不知道它为什么成功、为什么失败。手工注入一遍,从单引号到字段数到回显位到提取数据,整个流程走下来,对注入的理解会深很多。我建议每个学注入的人都至少手工完成一次完整的注入,不要一上来就依赖工具。

第三个节点是遇到盲注。第一次碰到没有回显、没有报错、页面真假也不区分的场景,我一度以为没有注入。后来学了时间盲注,才发现原来还能这样玩。这个节点让我意识到,注入的形态远比想象中丰富,不能因为一种方法失效就断定没有注入。

最后一个节点是开始写防御代码。当你自己写代码的时候,才会真正理解预编译为什么能解决问题,也才会注意到那些容易被忽略的细节,比如表名不能参数化、排序字段需要白名单校验。攻防两端都走过一遍,对注入的理解才算完整。

如果让我给刚入门的人一个建议,那就是:不要急着学绕过WAF,不要急着用工具跑站,先把最基本的原理和手工流程吃透。基础打牢了,后面学什么都快。注入的手法会随着数据库版本和防护策略变化,但底层原理十几年没变过,把原理搞懂,比记一百个payload都有用。

相关新闻

matplotlib中文乱码显示方框?一套工程化字体配置方案彻底解决

matplotlib中文乱码显示方框?一套工程化字体配置方案彻底解决

刚开始用matplotlib画图,第一张图就满屏小方框,标题、坐标轴全是豆腐块一样的空框框,换成个折线图画中文字体,依旧原样。这个问题在中文开发者圈子里几乎算是入门必经关卡,项目标题看着简单——matplotlib无法显示中文…

2026/10/20 20:05:13 阅读更多 →
AI绘画提示词反推总差口气?掌握这3招还原图像生成底层逻辑

AI绘画提示词反推总差口气?掌握这3招还原图像生成底层逻辑

我玩AI绘画也有不短的时间了,几乎每次看到别人发的惊艳图,第一反应都不是“哇”,而是“这图提示词到底怎么写的”。所以我也很自然地走上了反推提示词这条路。但很快我就发现一个问题:用各种反推工具拿到的词,哪怕看起…

2026/10/20 20:05:13 阅读更多 →
放弃图形化工具,用Git Bash高效管理本地文档的完整指南

放弃图形化工具,用Git Bash高效管理本地文档的完整指南

1. 为什么我最终放弃了图形化工具,回归 Git Bash 管理本地文档很多人第一次接触版本管理,都是从图形化客户端开始的。点几下按钮,仓库就建好了,提交记录也看得见,似乎没什么门槛。但用得越久,我越发现一个尴…

2026/10/20 20:04:05 阅读更多 →

最新新闻

YOLO26目标检测训练到RK3588部署:ONNX与RKNN适配实践

YOLO26目标检测训练到RK3588部署:ONNX与RKNN适配实践

YOLO26 目标检测的训练到瑞芯微平台适配,这条链路我最近刚完整跑通一遍。先说结论:真正卡人的地方不在训练本身,而在从 PyTorch 模型走向 RKNN 运行时的那几步——ONNX 导出、算子兼容、INT8 量化,每一个都埋着不少雷。我是在给一…

2026/10/20 20:43:28 阅读更多 →
梯度增强PINN:攻克Allen-Cahn方程多陡峭界面难题

梯度增强PINN:攻克Allen-Cahn方程多陡峭界面难题

Allen-Cahn方程是我调过的最“拧巴”的PDE之一。它表达式极简,一个∂u/∂t ε∂u/∂x u - u,但解的画面完全不简单:能量驱动下的相分离会让解在空间里劈出好几条尖锐的界面,这些界面随时间演化、移动、合并。所谓“多非常陡峭区…

2026/10/20 20:43:28 阅读更多 →
计算机教材内容策划与写作:从概念到工程实践

计算机教材内容策划与写作:从概念到工程实践

抱歉,我无法处理这个请求。

2026/10/20 20:43:28 阅读更多 →
从图像到数据:HaoCurve曲线提取实战与避坑指南

从图像到数据:HaoCurve曲线提取实战与避坑指南

简介:HaoCurve(薅曲线)是一套面向科研人员、工程师与数据分析者的MATLAB曲线绘制工具源码,适合需要在MATLAB环境中快速生成、编辑和分析数据曲线图的用户,尤其对希望深入理解绘图脚本实现、进行二次开发的进阶使用者更…

2026/10/20 20:43:28 阅读更多 →
CLI编码代理失败过程解剖:从轨迹日志到根因分析

CLI编码代理失败过程解剖:从轨迹日志到根因分析

读到一篇很有意思的论文,标题叫《Failure as a Process: An Anatomy of CLI Coding Agent Trajectories》。这两年命令行AI编程工具越来越多,从最初的“能写一段函数”到现在能在真实仓库里自己定位问题、改代码、跑测试,甚至提交补丁。但大多…

2026/10/20 20:43:28 阅读更多 →
小橘子V4.790:本地可调试塔防策略模拟器

小橘子V4.790:本地可调试塔防策略模拟器

简介:本资源是《向僵尸开炮》游戏的自动化对局辅助工具——小橘子V4.790版本完整部署包,面向游戏AI开发者、自动化测试工程师及休闲游戏技术爱好者,用于快速实现人机对战环境下的策略执行、界面识别与操作模拟。压缩包共741个文件&#xff0c…

2026/10/20 20:42:25 阅读更多 →

日新闻

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实现详解

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

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

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

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

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

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

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在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 阅读更多 →