码农高天
开发者借助Python内置AST模块在语法树层面进行节点等价重写,可在规避静态代码检查的同时彻底破坏代码可读性,且完全保留原有的运行逻辑。
AI-assisted summary of 自动化防御性编程,让你变得无法取代! — original source. Check the original for full context.
Source published:
Key ideas
- 码农高天提出“自动化防御性编程”思路,利用 Python 内置标准库 ast 解析、修改抽象语法树并反向生成等价代码,在保留本地清晰源码的同时自动化混淆线上逻辑。 (0:00)
- 通过继承 ast.NodeTransformer,开发者可利用 ast.parse 借力构造复杂等价表达式替换 True/False 常量,并对调 Compare 节点的左右操作数,实现语义不变但视觉混乱的效果。 (2:30)
- 利用 Python 内置函数非系统保留字的特性,通过模块级变量拦截内置函数查找链,配合 Base64 编码与 exec 动态注入别名映射,使函数调用在源码层面呈现出完全不同的语义。 (7:12)
Detailed notes
## Background 该视频由“码农高天”于 2024 年 7 月 27 日发布在 YouTube 平台。视频简介指出,本期内容利用 Python 标准库中的 ast 模块实现自动化的“防御性编程”,戏称只要能写出只有自己才能维护的代码,就能在职场中变得无法被取代。
## TL;DR 视频借用“防御性编程”的职场黑色幽默,深入浅出地演示了如何借助 Python 原生 ast 模块实现全自动化的代码混淆。主讲人以一段素数判断代码切入,利用 ast.NodeTransformer 将易读的布尔常量替换为求值等价的复杂表达式,并倒置比较操作符的两端顺序。随后,视频进一步利用 Python 内置命名空间的查找机制,将 print、range 等内置函数随机重命名,并配合 Base64 编码与 exec 动态注入全局映射。整套流程在规避静态代码检查报错且完全保持执行结果一致的前提下,完整展示了从代码解析、AST 节点篡改到逆向反解输出的工业级代码混淆管线。
## Atmosphere & style 视频整体洋溢着程序员专属的黑色幽默与反讽风格,将“职场防毕业”的戏谑调侃与底层硬核的抽象语法树技术严谨地缝合在一起。高天的讲解通俗风趣且层层递进,现场改写 AST 节点并运行验证的过程既让人忍俊不禁,又带来抽丝剥茧般的极客快感。观众在惊叹混淆后代码“面目全非”的戏剧性效果之余,也能在轻松愉悦的氛围中扎实掌握 Python 代码解析与重构的核心技术。
## Bulletin - [00:00](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=0s) 码农高天提出“自动化防御性编程”思路,利用 Python 内置标准库 ast 解析、修改抽象语法树并反向生成等价代码,在保留本地清晰源码的同时自动化混淆线上逻辑。 - [02:30](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=150s) 通过继承 ast.NodeTransformer,开发者可利用 ast.parse 借力构造复杂等价表达式替换 True/False 常量,并对调 Compare 节点的左右操作数,实现语义不变但视觉混乱的效果。 - [07:12](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=432s) 利用 Python 内置函数非系统保留字的特性,通过模块级变量拦截内置函数查找链,配合 Base64 编码与 exec 动态注入别名映射,使函数调用在源码层面呈现出完全不同的语义。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=0s) 防御性编程衍生出“写出别人看不懂的代码以防被取代”的幽默解构,但核心前提是开发者自己必须保留可读且可维护的原版源码。 - [00:35](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=35s) 基于纯文本替换的混淆不仅手段低级,面对复杂语法结构时极易破坏语法,体面的方案是操作 AST(抽象语法树)。 - [01:00](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=60s) Python 内置的 ast.parse 能够将源码转化为树状语法结构,而 ast.unparse 则能将语法树反向还原为完全可执行的 Python 源码。 - [02:30](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=150s) 修改 AST 的核心是继承 ast.NodeTransformer,利用访问者模式定义针对特定节点(如 visit_Constant)的挂钩处理方法。 - [02:45](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=165s) 在人工修改或替换语法树节点后,必须调用 ast.fix_missing_locations() 补齐行号与偏移量等元信息,以避免 unparse 或后续执行崩溃。 - [03:10](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=190s) 直接重定义 False = True 极易触发 Linter 报警,而将布尔常量替换为 ast.parse 解析出的复杂动态等价表达式则更为隐蔽安全。 - [04:30](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=270s) 在修改非叶子节点(如 Compare 节点)时,必须先调用 self.generic_visit(node) 完成子节点的递归处理,再进行父级节点的结构倒置。 - [05:10](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=310s) Python 允许连续比较语法,比较运算符与比较对象以列表存储;单等于比较中左右值的颠倒(如 0 == n % i)不会破坏逻辑,但能极大增加人类阅读负担。 - [07:12](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=432s) Python 的 print 与 range 等内置函数不是系统保留字,而只是 builtins 命名空间下的变量,可在模块作用域通过同名全局变量进行拦截重定向。 - [08:50](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=530s) 在 visit_Call 替换函数名时,维护一个 set 集合记录实际使用的内置函数,避免生成冗余的全局声明,实现按需映射。 - [09:30](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=570s) 直接在源码显式声明别名映射容易暴露意图,利用 Base64 编码混淆注入逻辑并用 exec 执行,可实现更深层次的代码伪装。 - [10:30](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=630s) 这类“防御性编程”在工业界的严谨术语为代码混淆(Code Obfuscation),视频意在以趣味案例教学 ast 库的高阶用法,而非提倡在真实生产中提交不可读代码。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=0s) 自动化防御性编程与 AST 概念引入
“防御性编程”原本是一个褒义词,指程序员在编写代码时预先考虑可能出现的各种异常情况,以此增强程序的健壮性与稳定性。然而这两年该词衍生出了一层全新的黑色幽默含义:只要我写的代码没人能看懂,就没有人能取代我,以此实现职业保全。但这存在一个根本前提——“你自己得能看得懂,如果你写的那个代码你自己也看不懂,那就白防了对吧,公司留你也没有用。”为此,本期视频提出了“自动化防御性编程”的思路:手中保留一份自己可读的清晰源代码,通过脚本自动化生成另一份功能完全等价但他人完全看不懂的代码。
码农高天以一段朴素的 Python 素数判断脚本为例展示该思路。该脚本包含一个 `is_prime` 函数(小于 2 返回 `False`,若能被遍历变量整除则返回 `False`,否则返回 `True`)并打印 1 到 49 之间的所有素数。代码中的 `True` 与 `False` 过度直观,任何同行一眼就能理解其意图。如果只用文本替换来修改常量,手段不仅低端,且面对更复杂的语法结构时极易失效。
为了体面且严谨地混淆代码,视频引入了 Python 内置的标准库 `ast`(Abstract Syntax Tree,抽象语法树)。它能将 Python 源代码按照语言语法解析为树状结构。将素数判断文件读入并调用 `ast.parse` 解析后,打印出的语法树清晰展示出代码的层次:顶层为名为 `is_prime` 的 `FunctionDef` 节点,其 `body` 的首部对应一个 `If` 语句,内部包含返回常量的 `Return` 节点。节点内部带有属性 `Constant(value=False)`,而循环体内的判断条件与数字 `0` 也以 `Constant` 节点的形式存在。
```mermaid flowchart TD accTitle: 基于 AST 的自动化代码混淆与还原流程 accDescr: 展示源代码如何经由 ast.parse 构建语法树,再经 NodeTransformer 遍历重写节点,最后由 ast.unparse 生成混淆后代码的闭环。
subgraph ParseStage ["1. 解析阶段"] A["清晰的原始 Python 代码"] -->|"ast.parse()"| B["抽象语法树 (AST)"] end
subgraph TransformStage ["2. 混淆转换阶段"] B --> C["NodeTransformer 子类"] C --> D["遍历并重写目标节点<br/>(Constant / Compare / Call)"] D -->|"ast.fix_missing_locations()"| E["修改后的抽象语法树"] end
subgraph UnparseStage ["3. 生成阶段"] E -->|"ast.unparse()"| F["混淆后的等价源码<br/>(外加 Base64 解码前置逻辑)"] end ```
`ast` 模块不仅支持解析,还提供了一个非常强大的逆向工具 `ast.unparse`,它可以将语法树重新还原成可以执行的 Python 源代码。除少了一个空行外,还原后的代码几乎与手写代码完全一致。利用这套“源代码 -> 抽象语法树 -> 遍历修改节点 -> 还原为源代码”的管线,便能自动化实现对源代码的深度防御与混淆。
---
[02:30](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=150s) 利用 NodeTransformer 混淆布尔常量与倒置比较符
修改 AST 的核心工具是继承 `ast.NodeTransformer`。该类支持访问者模式,允许开发者定义类似 `visit_Constant` 的方法来挂钩并篡改特定的语法树节点。在遍历树时该方法被触发,其返回值将直接取代原节点。针对节点属性的认识没有速成捷径,必须查阅 Python 官方 AST 文档。在重写节点后,还必须运行 `ast.fix_missing_locations()` 补全代码行号与偏移量等元信息,以确保 `unparse` 或后续执行不崩溃。
直接修改代码逻辑(如把 `True` 硬改成 `False`)会破坏原有功能,因此必须采取“值等价转换”。若定义名为 `False = True` 的全局变量,不仅容易被 Linter 静态检查工具报警,操作起来也极别扭。高天在此采用了一个技巧:将布尔常量替换为求值结果仍为布尔值的复杂动态表达式。为了避免手动拼装复杂 AST 表达式节点的繁琐,可以直接使用 `ast.parse("1 == 1").body[0].value` 借力生成所需的表达式节点。如果进一步追求混淆效果,完全可以构建一个包含多种奇怪表达式的列表,在替换时随机抽取,使每个布尔值长相完全不同,让阅读者“直接都马上看崩了”。
```mermaid flowchart LR accTitle: 语法节点混淆转换示例 accDescr: 展示将简单的 True 常量替换为复杂表达式节点,以及利用 Generic Visit 倒置比较运算符两端左右子节点的过程。
subgraph ConstantConfusion ["布尔常量混淆"] C1["Constant(value=True)"] -->|"ast.parse 获取表达式节点"| C2["Compare 节点<br/>例如 (1!= 2) 等复杂随机表达式"] end
subgraph CompareConfusion ["比较符号颠倒"] CMP1["Compare 节点<br/>左: (n % i) | 右: 0"] -->|"generic_visit 预处理子节点<br/>+ 左右互换"| CMP2["Compare 节点<br/>左: 0 | 右: (n % i)"] end ```
接下来是颠倒比较顺序。人类通常习惯将变量放在等号左侧、常量放在右侧(如 `n % i == 0`)。通过修改 AST 中的 `Compare` 节点,可以在保持逻辑等价的前提下将左右两边倒置为 `0 == n % i`。但处理 `Compare` 节点比 `Constant` 更复杂: 1. `Compare` 节点不是叶子节点,它内部包含子节点(如被比较的变量或布尔值);因此在篡改它之前,必须先调用 `self.generic_visit(node)`,优先让子节点完成访问变换(例如若内部包含 `True`,需先被替换掉)。 2. Python 原生支持连续比较(如 `a < b < c`),其比较操作符和比较对象在 AST 中以列表形式存储。为了保持演示代码紧凑,高天仅处理了单个等于比较(`==`)的场景,把左值(`left`)与比较对象列表的首项(`comparators[0]`)进行对调。
- `ast.NodeTransformer`:通过定义 `visit_<NodeType>` 方法截获并替换指定类型的节点。 - `ast.fix_missing_locations()`:自动为人工构造或篡改后的 AST 节点填充源码位置信息,防止编译与反解报错。 - `self.generic_visit(node)`:深度优先遍历的保障,非叶子节点在自我替换前必须先递归处理子节点。
---
[07:12](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=432s) 内置函数随机混淆与 Base64 隐蔽动态映射
颠倒比较运算符的视觉冲击力尚显不足,高天进一步对内置函数(built-in functions)展开混淆。在 Python 中,`range`、`print` 等内置函数并非系统保留字(reserved words),而仅仅是语言层面的内置命名空间全局变量。这意味着只需在模块级别(module-level)定义同名全局变量,就能在解释器作用域查找链中完成拦截。“只要我定义一些 module level 的全局变量,就可以在他寻找 built-in 函数的时候给他截下来。我们在这里换一下函数名所对应的函数,就可以让这段代码看似调用的一个函数,实际上调用的是另外一个函数了。”
```mermaid flowchart TD accTitle: 内置函数重定向与 Base64 注入流程 accDescr: 展示内置函数如何被随机映射改名,并收集实际用到的映射项,经 Base64 编码由 exec 注入全局命名空间的完整链条。
subgraph MappingGen ["映射与节点替换"] M1["获取所有 built-in 函数名列表"] --> M2["打乱并生成随机一一映射<br/>(如: range -> round, print -> isinstance)"] M2 --> M3["Call 节点访问 (visit_Call)"] M3 --> M4["将原函数名替换为映射别名<br/>并记录至 used_builtins 集合"] end
subgraph RuntimeInjection ["运行时动态映射注入"] M4 --> I1["根据 used_builtins 生成前置声明代码<br/>'import builtins; round = builtins.range;...'"] I1 --> I2["Base64 编码生成的字符串"] I2 --> I3["原代码最前端注入:<br/>exec(base64.b64decode(...))"] end ```
具体的实现步骤如下: 1. **建立全局内置函数随机映射**:提取 `builtins` 库中的所有内置函数名,将其打乱后与原函数名建立一个全新的一对一映射关系。 2. **拦截 AST 中的 Call 节点**:在继承的 transformer 中编写 `visit_Call` 方法。由于 `Call` 节点亦非叶子节点,同样要先调用 `self.generic_visit(node)`。检查调用的函数名是否处于内置函数映射中;若是,则将调用的函数节点名替换为映射后的假名字。在此过程中,`range` 会被偷梁换柱成 `round`,`print` 会变成 `isinstance`。 3. **记录实际使用项**:为了避免生成大量无用的全局变量声明,在替换 AST 节点的同时用一个 `set` 集合收集当前代码中真正用到的内置函数,做到“按需生成”。 4. **生成前置拦截逻辑并使用 Base64 伪装**:直接在源代码头部写 `round = builtins.range` 会让人一眼看穿破绽。因此,先动态拼接出引入 `builtins` 并为变量赋值的 Python 代码文本,再将其转换为 Base64 编码的字符串。最后,在生成的目标代码开头注入 `exec(base64.b64decode(...))` 逻辑。虽然“Base64 这个东西呢不是加密,但它可以让你的意图不那么明确的直接出现在源代码上”。
将处理完毕的代码写入文件并执行,终端正常输出了 1 到 49 间的所有素数,“还是原来的味道,功能没有任何改变”。
高天最后总结道,这套技巧更严肃的工业界称谓其实是“代码混淆”(Code Obfuscation)。这期视频旨在以娱乐化形式带大家入门 Python 内置的 `ast` 库,演示如何在规避代码检查工具(Linter)报警的同时,将代码逻辑隐藏到让别人无法阅读的程度。他在视频结尾调侃道,现实工作中并不推荐大家真的把这类代码提交给团队,并为观众送上祝福:“祝点赞跟投币的同学们都既不需要防御性编程,也不会被公司毕业。”
## Notable quotes > [00:00](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=0s) 你自己得能看得懂,如果你写的那个代码你自己也看不懂,那就白防了对吧,公司留你也没有用。 — 码农高天
> [07:12](https://www.youtube.com/watch?v=L2PK7hrCxjA&t=432s) Base64 这个东西呢不是加密,但它可以让你的意图不那么明确的直接出现在源代码上 — 码农高天
## Mentioned 码农高天, Python, ast, NodeTransformer, Base64, builtins
## Video description 这期视频我们利用ast这个内置库来进行一个自动化的“防御性编程”。如何让自己无法取代?只要写出来只有自己能维护的代码就好了!
[Source](https://www.youtube.com/watch?v=L2PK7hrCxjA)