高天借Python AST实现代码混淆 替换常量并动态劫持内置函数

自动化防御性编程,让你变得无法取代! — 码农高天

码农高天

开发者借助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)

中文简报

高天借 Python AST 实现代码混淆:替换常量并动态劫持内置函数 开发者借助Python内置AST模块在语法树层面进行节点等价重写,可在规避静态代码检查的同时彻底破坏代码可读性,且完全保留原有的运行逻辑。 视频借用“防御性编程”的职场黑色幽默,深入浅出地演示了如何借助 Python 原生 ast 模块实现全自动化的代码混淆。主讲人以一段素数判断代码切入,利用 ast.NodeTransformer 将易读的布尔常量替换为求值等价的复杂表达式,并倒置比较操作符的两端顺序。随后,视频进一步利用 Python 内置命名空间的查找机制,将 print、range 等内置函数随机重命名,并配合 Base64 编码与 exec 动态注入全局映射。整套流程在规避静态代码检查报错且完全保持执行结果一致的前提下,完整展示了从代码解析、AST 节点篡改到逆向反解输出的工业级代码混淆管线。 码农高天提出“自动化防御性编程”思路,利用 Python 内置标准库 ast 解析、修改抽象语法树并反向生成等价代码,在保留本地清晰源码的同时自动化混淆线上逻辑。 (0:00) 通过继承 ast.NodeTransformer,开发者可利用 ast.parse 借力构造复杂等价表达式替换 True/False 常量,并对调 Compare 节点的左右操作数,实现语义不变但视觉混乱的效果。 (2:30) 利用 Python 内置函数非系统保留字的特性,通过模块级变量拦截内置函数查找链,配合 Base64 编码与 exec 动态注入别名映射,使函数调用在源码层面呈现出完全不同的语义。 (7:12)

Reading summary

码农高天提出了一种基于 Python 内置 ast 模块的“自动化防御性编程”思路,通过在保留清晰源码的同时自动化生成功能等价但极难阅读的混淆代码,兼顾自身维护与代码保护。 基于 AST 的自动化混淆管线 (0:00) 视频首先指出传统“防御性编程”被戏谑为制造他人无法阅读的代码以保全岗位,但前提是开发者自己必须能读懂。因此,合理的做法是保留可读源代码,借助脚本自动化生成逻辑等价的混淆代码。单纯使用文本替换修改常量不仅手段低端,面对复杂语法结构时也极易失效。 为此,高天引入了 Python 标准库 ast。通过 ast.parse 将源码解析为抽象语法树后,代码的层次(如函数定义、条件分支、常量等)均以节点形式呈现。配合 ast.unparse 将树重新还原为 Python 源码的特性,开发者即可构建“源码 -> AST -> 节点篡改 -> 源码”的完整自动化混淆闭环。 常量动态化与比较符顺序颠倒 (2:30) 修改语法树的核心依赖于继承 ast.NodeTransformer,通过编写 visit_Constant 等方法挂钩并替换目标节点,并在重写后调用 ast.fix_missing_locations() 补全元信息以防报错。为了避免直接定义 False = True 等破坏逻辑或触发 Linter 静态检查的操作,高天利用值等价转换技巧,将直观的布尔值替换为随机抽取的复杂动态表达式(例如借助 ast.parse("1 == 1").body[0].value 生成的 Compare 节点)。 针对比较操作,程序通过篡改 Compare 节点,将人们习惯的变量在前、常量在后(如 n % i == 0)倒置为常量在前(0 == n % i)。由于 Compare 节点属于非叶子节点,转换过程中必须先调用 self.generic_visit(node) 完成子节点的递归处理,同时调整左值与比较对象列表首项的位置。 内置函数重定向与 Base64 注入 (7:12) 除常量与运算符外,高天进一步对内置函数展开混淆。由于 Python 中的 range、print 等并非保留字,而是内置命名空间全局变量,因此在模块级别定义同名全局变量即可完成作用域查找拦截。具体做法是提取 builtins 库中的所有函数名并打乱生成一一映射,使看似调用 round 的代码实际执行 range,看似调用 isinstance 的代码实际执行 print。 在 AST 层面,程序通过 visit_Call 拦截函数调用并完成改名,同时用集合记录实际用到的内置函数以避免冗余声明。为防直接声明破绽过大,混淆脚本将赋值声明代码生成文本后进行 Base64 编码,并在目标文件头部注入 exec(base64.b64decode(...)) 动态执行。测试表明,混淆后的素数判断脚本功能完全一致,正常输出了 1 到 49 间的所有素数。 高天总结指出,该方案在工业界实质属于代码混淆技术,本期内容旨在以娱乐化形式引导开发者掌握 Python ast 库的底层能力,规避 Linter 报错。但他强调,在现实开发工作中并不建议真的向团队提交此类混淆代码。

Source quotes

[0:00] 码农高天: 你自己得能看得懂,如果你写的那个代码你自己也看不懂,那就白防了对吧,公司留你也没有用。 [7:12] 码农高天: Base64 这个东西呢不是加密,但它可以让你的意图不那么明确的直接出现在源代码上

Original transcript

[0:00] 这期视频我们来讲防御性编程这个词呢它原来是个好词啊它本来指的是你在编程的时候多想一点可能发生的意外情况啊让你的程序更稳定但是这两年呢,它引申出来了一个新的意思我写的这个程序如果没有人能看得懂就没有人能够取代我为之防御性变成但是这种防御性编程有一个前提对吧就是你写的那个代码呢你自己得能看得懂如果你想了那个代码你自己也看不走那就白访了对吧公司留你也没有用所以这期视频呢我们来讲这个自动化防御性编程也就是你手里留着一份你自己能看得懂的原代码然后根据这个元代码再生成一份别人看不懂的元代码好我们看这个例子啊这个例子特别简单就是一个判断素数的函数小约2返回false 否则的话过一遍 如果能被爱整除 返回false 否则返回去然后呢我们打印1到49里面的所有素数结果也很直观 没什么新鲜的那我们怎么来进行防御性编程呢?首先我们看这个True啊False之类的是不是太直观了这别人一眼就能看出来我们想返回的是真还是假对吧那我们就想能不能把这个程序里的true和false换成其他等价的值这时候你肯定觉得很简单对吧我直接做一个文本替换不就完了吗但是文本替换这种事太low了对吧不符合我们频道的风格再说文本替换可能对付一下处跟foss还行面对一些其他的长量可能不是那么好用所以今天给大家介绍一个python的built module叫做ASTAST就是Abstract Syntax Tree,中文叫做抽象语法术它可以把我们的原代码根据给定的语法变成一个竖状结构比如说这里我们把刚才的文件读进来然后 parse 一下打印这棵树这个数大概就是这个样子上来定义了一个名字叫 is prime 的函数这个函数有一个body8体的第一个部分对应的是一个if statement这里如果n小于2的话就返回一个常量false我们可以看到这个节点有一个constant value 等于 false这里就是我们感兴趣的地方了我们希望把这个 constant 换成一个别的东西下面大家也可以简单看一下啊比如说这是一个for loop每一个ASC的节点对应着一种语法结构这个for loop的body里也是一个if statement同样的我们可以找到这种Constant的节点当然大家可以看到啊除了True跟False之外像零这种常量也是Constant节点而AST这个module还给了一个非常实用的功能叫做unparse也就是他可以把一个语法术再恢复成原代码当然这个原代码不一定一模一样它只是恢复成可以生成这个语法术的原代码比如说我们看啊安帕出来的原代码跟我们写的原代码几乎是一模一样的只是中间少了一个空行所以我们就可以利用这个机制从原代码生成语法术然后修改语法术中我们需要修改的部分最后把修改过的语法术再变回原代码我们就完成了这个防御的过程好 那怎么修改这个语法术呢AST提供了一个可以继承的class叫做Node Transformer通过这个class我们可以进行对ast里面节点的修改而修改的方式就是在我们衍生出来的class里面定义这种visit下滑线什么东西的方法当然啊 这里需要大家对Python AST的节点比较熟比如这里我们知道我们要找的是Constant Expression这个大家就只能去翻文档也没有什么速成的方法回到代码 我们知道我们要找的节点叫做Constant所以我们就定一个方法叫做 visit 下方线 constant这个方法会在语法术变利的时候被调用而我们需要返回一个替代这个节点的新节点我们这里写了一个非常简单的逻辑,让大家先熟悉一下如果这个Constant节点的value是true的话我们就返回一个false constant节点反之如果是false我们就返回一个true节点如果都不是的话我们就返回原节点也就是不修改接下来我们建立这个transformer instance用这个visit函数产生一个新的数如果我们对节点进行了修改,大家要记得运行一下这个Fix Missing Locations这个函数会帮你在新加进去的节点上面填入一些跟原代码有关的信息最后我们把这个语法术变回原代码然后把它打印出来大家可以对比一下啊我们把所有的true都变成false当然这只是一个例子我们把函数的功能改了对吧这是不行的我们要保证函数的功能同时让其他人读懂那这里我们就有很多方案了对吧比如说我们可以定一个全局变量叫做false然后把它的值设成处但那样的话呢第一你要想办法写入全局变量第二你的link很有可能会出错取决于你怎么写入的这个全局变量所以这里我们采用另外一种方式我们可以把true跟false 替换成一些直式true跟false表达式这里我们稍微做一个小 trick因为想手动构造一个表达式的node 相对来说比较难所以这里我们把表达式的字符串直接parse一下 [5:00] 套取8.0的value这样我们就相当于用了ast这个酷帮我们构建了一个这个表达式对应的node我们看这里我们随便写了两个表达式 一个结果是true 一个结果是false但是这里你要是高兴的话你可以弄一堆表达事然后random对吧你整一个大的list里面全是一些奇奇怪的表达式然后这些表达式的结果都是true然后对于每一个不同的处都random随机选一个这样你程序里的每一个赤文Fall's的表达识都不一样对吧直接都马上看崩了我们现在这个代码就明显比刚才更难读了对吧已经看不清哪返回的是true 哪返回的是foss了接下来我们干嘛呢我们做一个小修改吧我们在写比较的时候这两个比较的东西经常是有一个顺序的这个顺序一般是符合我们人类的正常思维的我们这里就可以把等号两边的东西调个个儿在绝大多数情况下啊A等于等于B就意味着B等于等于A当然了 这是Python一切极有可能 对吧如果你写那个代码不能这么处理啊你就别这么处理这回我们要处理的节点是Compare节点compare节点相比于concent的节点就稍微更复杂一点了首先这个节点下面还有其他的节点也就是说Compare节点不是叶子节点其次 Python 支持这种连续的 compare因此它的操作和比较的东西都是List那我们为了简单起见 我们只处理只有一个compare的情况好 那由于这个节点不是叶子节点我们在进行处理之前必须要先调用一下这个Generic visit 坛数也就是让我这个节点包含的其他节点先处理 visit 这个事比如假设我Compare里面有一个A等于等于True那这个true我还要通过visit constant把它变掉呢所以要先变先调用这个generic visit变完了我包含的节点我再来变自己如果我比较的对象只有一个并且比较的方法是等于等于我就把左和右给换掉把右边的放左边来把左边的放右边去改完之后大家看这里我们的原代码就从n与i等于等于0变成0等于n与i了当然这个改动可能有点小对吧大家看着不顾意那我们就来点过瘾的啊我们知道Python有很多built-in函数对吧是在原代码里面可以直接使用的比如说这里我们使用到了 range 是 built-in 函数同样的print也是beauting函数那这个built-n函数看起来实在太好懂了对不对我们能不能让它看着不这么好懂呢我们知道在python里,built-in函数并不是reserved word,他们只是一些语言level的全区变量而已也就是说只要我定义一些module level的全局变量就可以在他寻找built in函数的时候给他截下来我们在这里换一下函数名所对应的函数就可以让这段代码看似调用的一个函数实际上调用的是另外一个函数了这里首先我们建立了一个Mapping我们把所有built in 函数的函数名拿出来然后让每一个函数的函数名随机对应一个函数建立一个新的一一对应的关系接着我们来处理这个call节点同样的也要先generic visit 因为call节点下面也有其他的节点如果靠这个节点使用的函数是通过名字来调用的我们就检查一下这个名字是不是在我们的Mapping里也就是调用的函数名是不是一个built in函数如果是的话 我们建立一个新的靠节点 把他的名字变成我们给他对应的那个名也就是在这个原代码里面我们把所有的built-in的函数都换了一个名换成了其他的built-in函数比如我们随便跑一下这块range就变成了round对吧然后下面的print变成了isInstance当然如果光震变它程序不对对吧因为我们只是自己建立了一下这个Mapping我们写出来的文件并不知道我们改了这件事所以我们要在module之前改一下全聚变量我们想达到的效果大约是round = built-ins.range, isInstance = built-ins.print但是如果直接这么写的话看着太low了对吧被人一眼就看出破绽了我们多多少少得让他更加难以识别一点所以我们做这么一件事首先建立一个set啊把所有用到的built in 函数都记录下来这个操作是让我们少生成一点没有必要的代码我们不需要把所有我们Mapping的built-end函数都写出来只需要把用到的写出来就可以了每一次改node的时候把这个用过的builtins记录下来接下来我们生成一段在module运行之前需要运行的代码首先我们要 import built-ins然后对于每一个mapping我们都做一下这个target等于builtins的原来那个function比如这里可能是Round=builtins.range也就是我们建立了一个新的全局变量round,然后这个round的值是builtins.range这样我们调查 [10:00] Round的时候实际上调用的是内置函数range最后呢我们把这段代码啊给做一个B64Base64这个东西呢不是加密但它可以让你的意图不那么明确的直接出现在原代码上我们把这段生成的代码放到我们刚才原代码的前面用exec执行一下这样我们就建立了我们全局变量的那个Mapping我们看运行完了之后是这个效果我们把刚才打印的东西写入一个文件可以看到左边就是我们新的打印素数的代码了简单看一看可以说还是比较离谱的吧我们运行一下啊还是原来的味道功能没有任何改变可以说我们还是比较圆满的完成了任务啊当然这只是抛砖引玉对吧我们这期视频呢只是带你入个门告诉你有什么样的方法你可以用这个方法对AST进行任何你想做的修改你可以去改变量名对吧你可以去改其他的一些长量我们这里就不一一演示了那其实这种所谓防御性编程呢自古以来就有只不过呢那时候他有一个更专业的名字叫做混淆那我们这次给大家带来的呢是一个相对娱乐性一点的混淆就是怎么让你的Linter不报警的情况下还能让这个代码让人看不懂当然了真的编程的时候不推荐大家这么做就是娱乐一下好那这期视频这里希望大家记得点赞祝点赞跟投币的同学们都既不需要防御性编程也不会被公司毕业