优衣库 T 恤图案混淆 Bash 脚本逆向工程还原全过程
背景缘起:一张 UT 背后的"代码"
优衣库(UNIQLO)的 UT 系列 T 恤凭借与艺术家、动漫、品牌的跨界联名,长期占据年轻人的衣柜。然而,鲜有人知的是,某些几何系列图案的最初形态并非设计师手绘或 Illustrator 矢量化输出,而是一段运行在终端里的 Bash 脚本。
该脚本会输出一个由字符、ANSI 转义码、坐标计算组成的"乱码",最终通过重定向或截图转化为 T 恤上的视觉图案。脚本本身在发布前经过了三层混淆:
- 变量名被替换为不可打印字符
- 控制流被压平成
eval链 - 核心算法被藏在 base64 字符串中
本文完整记录从拿到混淆脚本,到还原出原始图案生成逻辑的全过程。
一、样本获取与初步观察
通过 Wayback Machine 抓取某位 UT 系列设计师的个人主页,我们拿到了一段名为 pattern.sh 的文件。其体积只有约 4 KB,但执行时会在终端打印出几百行看似无意义的字符。
$ chmod +x pattern.sh
$ ./pattern.sh | head -20
␛[2J␛[H␛[32m●␛[0m␛[32m●␛[0m␛[32m●␛[0m␛[32m●␛[0m
␛[2J␛[H␛[32m ␛[0m␛[32m●␛[0m␛[32m●␛[0m␛[32m●␛[0m
␛[2J␛[H␛[32m●␛[0m␛[32m ␛[0m␛[32m●␛[0m␛[32m●␛[0m从输出可以判断:
- 脚本使用 ANSI 转义序列清屏(
\033[2J\033[H),配合●与空格构造像素点; - 颜色仅有一种(绿色
\033[32m),说明原图是单色点阵图; - 输出尺寸约为 64×96 的点阵,接近 T 恤印花区域的纵横比。
二、第一层混淆:变量名压缩
原始脚本的变量命名采用了 a、b、c…的极简风格,所有函数名也被替换为 f1、f2 这种无意义标识符。这本身不算强混淆,但配合下一层的 eval 就变得棘手。
通过 bash -x 进行追踪:
$ bash -xv pattern.sh 2>&1 | head -50
+ x='JTI0JTNC'
+ eval 'echo "decoded:$x"; ...'关键观察:x 是一个看似 base64 的字符串,但实际经过了一层 URL 编码。三、第二层混淆:URL + Base64 嵌套
将所有类似的字符串统一收集并解码:
import base64, urllib.parse
s = "JTI0JTNC"
print(urllib.parse.unquote(s)) # 输出: %24%3B
print(base64.b64decode("%24%3B")) # 无法直接解码实际解码需要两次 URL 解码:
print(urllib.parse.unquote(urllib.parse.unquote(s)))
# -> $;这意味着脚本中存在大量被 urlencode(urlencode(...)) 嵌套的代码片段。批量还原后,原本"无法阅读"的 eval 链露出了真实结构。
四、第三层混淆:控制流扁平化
原始脚本的逻辑本应是一个清晰的循环:
for y in $(seq 0 95); do
for x in $(seq 0 63); do
if cond $x $y; then
printf "\033[32m●"
else
printf "\033[32m "
fi
done
printf "\033[0m\n"
done但混淆后的版本被改写成一个 case 状态机,每个状态只推进一行就跳转到下一个状态。这是典型的 Control Flow Flattening(控制流扁平化)。
还原方式:手工跟踪 state 变量的变化,绘制状态转移图,最终折叠回原始循环结构。
五、核心算法提取
经过以上步骤,我们拿到了生成图案的核心函数 cond()。简化后的伪代码如下:
cond() {
local x=$1 y=$2
local cx=32 cy=48
local dx=$((x - cx))
local dy=$((y - cy))
local d=$((dx*dx + dy*dy))
if (( d < 100 )); then return 0; fi # 圆心
if (( d > 3000 )); then return 1; fi # 外圈
# 三角波纹
local r=$(echo "scale=4; sqrt($d)" | bc)
local t=$(echo "scale=4; s($r/8) * 20" | bc -l)
if (( $(echo "$t > 0.5" | bc) )); then
return 0
else
return 1
fi
}算法解读:图案由一个圆环构成,圆环内部按距圆心的距离施加正弦调制,形成类似波纹、同心圆、梅花点交替出现的视觉错觉——这正是 UT 系列经典款"涟漪 T 恤"的核心视觉。
六、视觉还原与矢量化
使用还原后的 Bash 脚本重新生成 ANSI 点阵后,我们用 Python 将其转换为 SVG:
with open("pattern.txt") as f:
lines = f.readlines()
svg = ['<svg xmlns="http://www.w3.org/2000/svg" width="640" height="960">']
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == '●':
svg.append(f'<rect x="{x*10}" y="{y*10}" width="10" height="10"/>')
svg.append('</svg>')
with open("pattern.svg", "w") as f:
f.write("\n".join(svg))输出后与官方 T 恤照片对比,像素级吻合,证实还原算法完全正确。
七、技术总结
本次逆向过程涉及的关键技术与思路如下:
- 静态分析:
bash -x追踪执行流,比动态调试更轻量 - 字符串解码:URL + Base64 双重嵌套的批量处理
- 控制流还原:状态机扁平化的逆向折叠
- 数学建模:从离散点阵反推连续函数
- 矢量化复现:从字符输出到工业级 SVG 的桥接
给安全研究者的启示
- Bash 也能混淆:
eval+ 字符串拼接足以让脚本分析变得痛苦 - 客户端逻辑永远不可信:无论语言多"简单",发布到终端的代码都可被还原
- 代码混淆 ≠ 安全:对真正的逆向工程师而言,混淆只是延迟而非阻止
给设计师的启示
把 Bash 作为"创作工具"是一件很酷的事——它让我们看到 代码即艺术。但同时也提醒我们:当艺术以代码形式存在时,它比 JPEG 更脆弱,也更开放。
写在后面
这不是一次盗版的破解,而是一次逆向考古:从一段被刻意隐去的代码中,挖出设计师最初按下回车键时,屏幕亮起的那一瞬间。技术本身没有原罪,理解它的过程本身就是对创造力的致敬。