优衣库 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

从输出可以判断:

  1. 脚本使用 ANSI 转义序列清屏(\033[2J\033[H),配合 与空格构造像素点;
  2. 颜色仅有一种(绿色 \033[32m),说明原图是单色点阵图
  3. 输出尺寸约为 64×96 的点阵,接近 T 恤印花区域的纵横比。

二、第一层混淆:变量名压缩

原始脚本的变量命名采用了 abc…的极简风格,所有函数名也被替换为 f1f2 这种无意义标识符。这本身不算强混淆,但配合下一层的 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 的桥接

给安全研究者的启示

  1. Bash 也能混淆eval + 字符串拼接足以让脚本分析变得痛苦
  2. 客户端逻辑永远不可信:无论语言多"简单",发布到终端的代码都可被还原
  3. 代码混淆 ≠ 安全:对真正的逆向工程师而言,混淆只是延迟而非阻止

给设计师的启示

把 Bash 作为"创作工具"是一件很酷的事——它让我们看到 代码即艺术。但同时也提醒我们:当艺术以代码形式存在时,它比 JPEG 更脆弱,也更开放


写在后面

这不是一次盗版的破解,而是一次逆向考古:从一段被刻意隐去的代码中,挖出设计师最初按下回车键时,屏幕亮起的那一瞬间。技术本身没有原罪,理解它的过程本身就是对创造力的致敬。