UniqloのTシャツに印刷された難読化bashスクリプトを解読する
(tris.sherliker.net)- Uniqlo店舗で販売されたAkamaiデザインのTシャツには、
base64 --decodeの結果をevalで実行する難読化されたbashコードが印刷されていたが、実際にはPeace for Allキャンペーン向けのイースターエッグだった - Base64でエンコードされたHere stringを展開すると、ターミナルで
♥PEACE♥FOR♥ALL♥の文言をサイン波アニメーションで繰り返し表示するスクリプトが現れる - Base64には誤り訂正がないため転記が難しく、文字列のパディングや引用符・波括弧が合っているかを確認したうえで、Android OCR、Tesseract、Claudeの結果を比較して整理した
- デコードされたスクリプトは
tput、bc、256色ANSIエスケープを組み合わせ、ターミナルサイズごとの文字位置と色を計算し、CTRL+Cで終了するとカーソルを復元する - Akamaiは背面の実際のコードをLinuxへの参照として説明しており、フォントは当初推定されたConsolasではなくRoboto Monoに訂正された
Tシャツ背面の実行可能なコード
- Uniqlo x Akamaiの
Peace for AllTシャツの前面には波括弧の中のハートがあり、背面には大きな英数字ブロックが印刷されている - 近くで見ると、そのブロックはshebangで始まるbashコードで、Base64エンコードされたHere stringを
base64 --decodeで展開してevalに渡す構造になっている - 見た目には「ウイルス配布の手法」のように見えるかもしれないが、実際の目的はイースターエッグのメッセージを表示することだった
- このTシャツは、AkamaiがUnqiloのPeace for All campaignを支援してデザインした製品である
OCRと転記の過程
- Base64には誤り訂正機能がないため、印刷された文字列を実行可能な形へ移すには完全な転記が必要だった
- 文字列は想定されるパディングで終わっており、引用符や波括弧も対応していたため、完全なコードである可能性が高かった
- 転記には3つの方法を併用した
- Androidのcircle-to-search内蔵OCR
- Tesseractといくつかのオプションおよび調整
- ClaudeによるOCRと位置表の作成
- 3つの結果をdiffで比較して不一致箇所を見つけ、残りの部分は手作業で整理した
- 同じUniqlo x Akamaiラインの別のTシャツデザインは、より広いコードブロックから切り取られたようで、importsが途中で切れ、
returnもretuで終わっていてコンパイル不能な状態だった
デコードされたbashスクリプトの動作
- Base64をデコードすると、コメントが丁寧に付けられたbashイースターエッグが現れる
- スクリプト冒頭には英語と日本語の祝福メッセージが含まれている
Congratulations! You found the easter egg! ❤️おめでとうございます!隠されたサプライズを見つけました!❤️
- アニメーション対象の文字列は
♥PEACE♥FOR♥ALL♥PEACE♥FOR♥ALL...という形で繰り返される - ターミナルサイズは
tput cols、tput linesで取得し、文字列長はbashの${#text}で計算する - カーソルは
tput civisで隠し、SIGINTを受けるとtput cnorm; exitで元に戻す
アニメーションの実装方法
- ループは
for (( t=0; ; t+=1 ))形式の無限ループとして動き続ける - 各反復で、現在出力する文字を
textから1文字ずつ取り出す - 位置計算は
bc -lでサイン値を求めて処理するangle = t * 0.2sine_value = s(angle)x = (cols / 2) + (cols / 4) * sine_value
- 計算された
xはターミナル範囲を外れないよう、0以上cols - 1以下に補正される - 色は256色パレットでcyanに相当する12とorangeに相当する208の間のグラデーションとして計算される
- 出力はANSIエスケープと
tput cupを使って指定位置に色付き文字を描き、改行で下へ進む方式になっている - その結果、
Peace for Allキャンペーンのメッセージがターミナル上で連続するサイン波ループとして現れる
フォントとAkamaiのデザイン説明
- フォントについての当初の推定はConsolasだったが、Hacker Newsユーザーralphinusの確認を受けてRoboto Monoに訂正された
- 誤った推定の根拠としては、浅く斜線が入った
0、セリフのない1、BDygや数字2の丸い曲線などが挙げられていた - AkamaiはTシャツ発売時のプレスリリースを通じてデザインメッセージを説明している
- 明るい黄褐色は初期インターネット時代のコンピューターの「beige box」プラスチック筐体を指し、前面のハートはインターネットが世界中で善い目的に使われてきたことを表している
- 背面の実際のコードはLinuxへの参照であり、Akamaiはこれを「インターネットのオープンソース言語」であり、より安全でつながった世界へ向かう共通言語だと位置づけている
- 同じTシャツに関心を持った別の事例としてWen Chuan Leeの記事があり、その記事はさらに別の関連投稿へとつながっている
1件のコメント
Hacker Newsのコメント
「Uniqlo x Akamaiが同じラインで売っている別のシャツは、コードが明らかに未完成のデザインになっている」
シャツが不具合を起こして返品しなければならないと想像すると笑える
「なぜ返品されるのですか、サイズが合わなかったのですか、それともお気に召しませんでしたか?」
「いえ、37行目に構文エラーがあって実行できないんです。それに、街の人たちに私が危険なBashスクリプトを書いていると宣伝していると思われそうで心配でして」
こういうものが好きなら、Martin Kleppeの作品も気に入るかもしれない。たとえばQuine Clockがある
https://aem1k.com/qlock/
数年前に、難読化されていない形にリバースエンジニアリングしたものもある
https://gist.github.com/olooney/a89db3932b089925b71b68d7e9f2...
そのほかにも素晴らしいASCIIビジュアライゼーションをたくさん作っている
https://aem1k.com/
とても印象的ではあるけれど、それでも
フォントはConsolasではなくRoboto Monoだ
もっと奇妙なのは、フォント自体はちゃんとした等幅フォントなのに、シャツの組版はそうなっていないことだ。カーニングが入っていて、特に「Iy」の組み合わせで目立つし、「i」のような細い文字は横幅をあまり取っていないように見える。推測するに、InDesignの「optical kerning」のようなツールで組版したのだろう
等幅フォントを使う理由は、グリッドにきれいに収まるからだ。でもそのグリッドの中でも文字を少し左や右に動かす余地はあるし、そうすればもっと読みやすい等幅フォントになるかもしれない。もちろん、文字が左に寄るたびに右側の空きが大きくなって、ひどい結果になる可能性も同じくらい高い
このシャツは本当に良い。実際のデザイナーが制作過程を説明している良い動画がある。意図的にOCRしにくくした工程も含まれている: https://youtu.be/jocGLiecpjU?t=526
https://archive.org/details/1983-01-compute-magazine/page/96...
Samsung S24Uでホーム画面の「circle to search」ボタンを長押ししてAIツールのインターフェースを出し、テキストを長押しして一度に全部コピーした。正式名称は分からないが、2秒くらいだった
これをOCRするのは悪夢で、自分を優秀なOCR/ビジョンモデルだと主張するものにとって良いベンチマークになる
ただ、最新のClaudeやGPTのような優れたビジョンモデルを組み込んだエージェント型ツールに画像を渡して、行ごとに分割し、各行を別々にOCRさせればかなり簡単にいけそうだ
難読化前のこのスクリプト自体がLLMで書かれたのかも気になる。コメントがかなり多そうだし、この場合はそれでも別に構わない :)
祝福メッセージが英語と日本語の両方で入っていて、ハートの絵文字も1つある。青緑からオレンジへ移るグラデーションにしたかった意図はあったようだが、その範囲はANSIブロックで始まり、256色ブロックの途中で終わっており、256色のターミナルカラーはそもそもグラデーション状に並んでいない。それにループの終わりにsleepがないが、LLMなら防御的に追加していそうだ
文字は簡単に分離できるし、読取器を混乱させるような構造的背景もない。たとえばヨーグルトのアルミ蓋に印字された賞味期限のようなものとは違う。結果を乱すような、ほとんど文字に見えるテクスチャもないし、フォントもきれいで標準的だ
今は名前を思い出せない。このところ気を散らすものが多すぎた
不思議なことに、hashbang部分まで含めて全文字列をコピーしようとすると、ずっと精度が悪くなる。内部で何をしていて、どうしてこんなにうまくいくのか気になる
base64エンコードを難読化と呼ぶのか
いけてるJS逆難読化テクニックを期待していたのに、よく言っても言葉にできないほどがっかりで、一日が台無しになった
うわ、店であのTシャツを見たとき、彼女に「このスクリプトが実際に動くはずない、ただの飾りだろう」と言っていた。もっと深掘りすべきだった
いいね。スクリプトについて唯一惜しいと思ったのは、ループの中に
sleep 0.1
を入れておけばよかったかも、という点。そうすれば端末に出力されたときに実際に読める。最近の端末はスクロールが速すぎて、メッセージが流れていく様子を見られない
10Hz更新 くらいまで遅くするとかなり見やすい
投稿者がいろいろなOCRツールを試して結果がばらついたあと、いちばん良かった出力を延々と手作業で直している様子が面白かった。最初から自分で打ち込めばいいのに、と思った
スマホのGoogle Lensで使うGoogle OCRもかなりうまく抽出できたけど、100%正確ではなかった。それでも手で打ち写すより、修正にかかる時間のほうがずっと少なかったはず。投稿者が何を使ったのかは分からないけど、OCRの試行結果を共有してくれたらよかったのにと思う。たぶんシンプルなOCRツールを試したんじゃないかな
自分でも当然OCRを選ぶと思う。次に Bash Tシャツ に遭遇したときに備えないといけないから
昔の同僚が Goプログラム の入ったシャツを持っていて、いつもかなりかっこいいと思っていた
[0] https://github.com/GL-Kageyama/UNIQLO_Akamai_T-shirt_Code