Decoding the obfuscated bash script on a Uniqlo t-shirt

6 hours ago (tris.sherliker.net)

"Uniqlo x Akamai sells another design of shirt in the same range which is plainly incomplete"

Imagine having to return a t-shirt because that malfunction!

— I don't understand why are you returning this, was the size wrong or you didn't like it?

— No, there is a syntax error at line 37 that makes it impossible to run, and I'm concerned people on the street may think I promote unsafe bash scripting.

The font is Roboto Mono, not Consolas.

There's something else a lot stranger going on, though. It is a proper monospace font, but the typesetting on the shirt is not. There's some kerning going on (I noticed it especially in the 'Iy' pair), and also it appears that narrower characters such as 'i' take less horizontal space. If I had to guess, I would say that it was set with a tool such as "optical kerning" in InDesign.

  • Has anyone ever made a monospace font with dynamic kerning? Which is a silly thing I never thought of until I read the above comment. This sounds nonsensical at first glance(and it may be) but hear me out.

    We use monospace fonts for a reason, they stack in a grid nicely. But within the confines of that grid there is room to shift a character left or right a bit which may lead to a nicer to read monospace. (it is equally likely to lead to a hideous mess, every time a letter would shift left it would leave a larger space right)

I love this shirt! Here's a nice video from the actual designer about the process of making this shirt (including intentionally making it hard to OCR): https://youtu.be/jocGLiecpjU?t=526

OCRing this is a nightmare and is a good benchmark to any self-proclaimed good OCR/vision model.

I think though it could likely be easily OCR'd if you give the image to any decent agentic harness with a good vision model, e.g. newest Claude/GPT ones, and tell them to split the image per lines, and then just OCR each line individually.

I wonder if the script itself was written by an LLM before obfuscation? There seem to be a lot of comments in it, but in this case it's still ok :)

  • I don't think it was written by an LLM, some things stand out:

    The congratulations text is both in English and Japanese. Contains a single heart emoji.

    There was an intention to have a cyan to orange gradient, but the range starts in an ANSI block, ends halfway through the 256 color block and 256 terminal colors are not arranged like a gradient at all.

    There's no sleep at the end of the loop where I feel like an LLM would add that defensively.

  • > OCRing this is a nightmare and is a good benchmark to any self-proclaimed good OCR/vision model.

    It's not that difficult, our industrial OCR model read it correctly on its first attempt with default parameters. The characters are easily separable, there is no structured background (think expiration dates on yogurt aluminum lids) that confuses the reader, there is no almost-text-like texture anywhere that would clutter the result. The font is also nice and standard.

  • The last time Internet people were obsessed with OCRing some base64 was a few months ago when the DoJ released tons of emails from some guy who died, but they were released as rasterized PDFs.

    Can't remember his name now, there's been so many distractions...

  • Safari's copy-text-from-image feature manages the entire base64 part of the string, except for the first character (I instead of a T). Weirdly, it gets much worse performance if you try to copy the entire string, including the hashbang part.

    I wonder what it's doing under the hood to get such good performance?

  • I gave the photo to Opus 4.8 and it reconstructed the same script in one shot. Although it did say it had to correct some parts of it based on context where it suspected OCR mistakes.

  • > I wonder if the script itself was written by an LLM before obfuscation?

    From the prototype shown here [0], and the way they talk about their process, I sincerely doubt it. Especially as they mention trying to make it hard for AI to handle the output.

    [0] https://youtu.be/jocGLiecpjU?t=567

    • I watched that whole video link - thank you for that - and he doesn't really say. In fact, he spends much more time on the beige color harkening to computer case plastics of the 80s & 90s.

      The AI not handling the output relates to the final base64 output on the T-shirt (which other comments in this thread mention manually keying in or TFA discusses in the context of OCR). So, that is just not relevant to the question.

      What made me start to wonder, personally, was that the output seems identical if you use "♥PEACE♥FOR♥ALL" instead of the version with internal repeats. IF there is any point to that "manual expansion of the cycles", IMO that deserves a comment much more so than "# Calculate length of text; text_length=".

      Also, that `echo -n ...` followed by `echo ""` instead of just plain `echo` in the first place seems like the kind of copy-pasta code LLMs generate. Then again, regular devs also write pretty bad copy-pasta code.

      There is also this the weirdly "broken down" calculation with 3 `bc` invocations not 1 as if it was translated from a language with more arithmetic/special function power than bash.

      There is also the color scale stuff done in the loop instead of outside (except the one color=$(..)) which seems very unnatural and also very like machine translation.

      Also, at least for me, on my bash-5.3.15(1), `char="${text:t % text_length:1}"` does not work to slice out the multi-byte UTF8 heart symbols, but it sure does look like the kind of thing an LLM would do translating from a python3 script (such as something like https://news.ycombinator.com/item?id=48830669) into bash.

      Another thing is, as others here have observed, there is nothing "gradual" about the xterm-256 color cube. So, "gradient" is a misnomer and exactly the kind of weird things LLMs do when they cobble text together.

      Finally, all the tput stuff the script does instead of just "print x spaces" really smells like a human description of the side scroll in the video game graphic he shows inspired him somehow LLM-corrupted/complexified into the vertical scroll terminals do.

      None of this is conclusive, but the video mentions 2023..2025 as when he did it and given that he was a designer and his concerns more visual than code-oriented, I'd have to say I disagree with your sincere doubt and I do strongly suspect the decoded script was very likely LLM-circa2024-generated, possibly with light post-edits by hand.

      4 replies →

  • >I wonder if the script itself was written by an LLM before obfuscation?

    I seem to recall seeing an Akamai-branded base64'd shell script on a white shirt pre-2021(?), so unless they've changed the code since then, I doubt it...

  • Definitely LLM. No humans write that many comments.

Neat. My only critique of the script is that I would have added a

  sleep 0.1 

in the loop so that as this prints in a terminal it is actually readable; any modern terminal will scroll so fast you can't see the message in flight.

Slowing it to a 10hz refresh makes it look great.

Oh wow I saw that tshirt at the store and said to my girlfriend "no way that script is functional, probably just for show". I should have persevered.

This reminds me of a T-shirt I once saw that read:

          perl -e '
     "$a="etbjxntqrdke";
  $a=~s/(.)/chr(ord($1)+1)/eg;
        print "$a\n;"'

It's cursing. Don't run it if it might offend you.

Upon seeing this, I decided to golf and came up with a shorter version:

  perl -e "print chr 1+ ord for split //,'etbjxntqrdke'"

I thought it was funny that the author used a variety of OCR tools with mixed success before spending a lot of time manually fixing up the output from the best one, rather than just typing it in

  • Feels like my experienced reality of task automation in corporate environments. We routinely have engineers spend 40+ hours automating tasks that an entry level person can do manually in 10 minutes and only need to be done weekly. Automation at all costs seems to be the future

    • It’s certainly not a new phenomenon. I appreciate this XKCD [1], with a chart of “How long can you work on making a routine task more efficient before you’re spending more time than you save”

      It’s not the final word, since automation has other benefits: documenting the procedure’s steps, reducing human errors, increasing consistency, etc.

      1: https://xkcd.com/1205/

  • That was also my thought… but I grew up mashing rubber keys for hours copying “games” out of magazines and books! Then hours after fixing all the typos!

    • I spent hours typing 6502 assembly. It went a lot better when someone dictated: LDA, STA, BEQ, LDY, STY...

  • I ran it through paddle paddle OCR and it flawlessly did it. Google's OCR through my phone's Google lens had also worked at getting a very good extraction but not 100% correct. Definitely would spend less time fixing it than hand copying.

    IDK what the author was using but I feel like he could have shared how his OCR attempt went, but I am thinking he tried some naive OCR tools.

    • Author here - that's a good idea actually, it shouldn't be too hard to compare the various attempts. The tools I used were whatever my Android built-in is (likely Google Gemini, but I can't tell whether this is something Samsung has replaced in OneUI); tesseract; tesseract with various tweaks and charsrt restrictions; Claude; and finally, manual fixes based on disagreements between all the previous.

  • Took me almost 2 minutes for 4 lines (and I missed a character in one of them!). I would opt for OCR too, obviously so I'm prepared for the next bash t-shirt I'd come across...

    • I think this is a case where two people can successfully complete the task manually faster than one attempting to automate it. Get a ruler, read five centimetres of characters to your colleague, have them type it in as you go, then repeat that five centimetres back to you. Correct as you go. Format your string with the same line-breaks as the t-shirt, and remove them at the end, so you can be sure you've got the correct length on each row. Trial-and-error adjust the five-cm distance depending on your success rate as you go along

      All in, you should have a non-corrupted string in 10-15 min.

  • Gemini3.5 Flash didn't have a problem OCR'ing and base64 decoding it, despite the OCR step having errors, it just fixed them in the base64 decoding step.

  • I'm guilty of this, but for me this kind of thing is optimizing over annoyance rather than time.

I once wrote a tool that helps with finding mistakes in OCR'd fixed width text, https://blog.qiqitori.com/2023/03/ocring-hex-dumps-or-other-...

Basically it just clusters same characters and asks the human to find the problems, which is easy when you're looking at a series of pictures like ssssss5sss.

The UI is kinda least-effort. Should ask a modern AI agent to make it look nice and intuitive, sometime maybe.

Huh! I was sure the copy-text-from-image feature in MacOS would handle this flawlessly. But the best run I managed produced the following:

    base64: stdin: (null): error decoding base64 input stream
    #!/bin/bash
    
    # Congratulations! You found thu eastur ugg!#B��O��
    # おめでとう��M�ぇM�す!隣C��わM�サ�#ライ����見でM�������!O��
    
    # Define thu tuxt to anima|e
    text="♥PEACE♥FOR♥ALOB��PEACE♵FOR♵ALL♵PEACE♥FOR♥ALL♥PEACE♥FOR♥ALL♥PEACE♵FOR♵ALL♥"
    
    # Get termb�al dmmensions
    cols=$(tput cols)
    linus=$(tput lines)

while base64 can be considered obfuscation in this context and its inverse as decoding I can't help but feel this title is overselling and catering to a rather cyber-cheesy marketing campaign at that.

  • Yeah, its a bit of a cheat. The best obfuscated C programs have the source looking like a Christmas tree (or something) and then play an xmas song (or whatever)

    • the base64 thing they did feels like a cheap version of that green-obscure-symbols-raining-on-a-terminal animation in The Matrix. should have gone with "Hack the Planet" instead ...

      1 reply →

Super cool, especially that the code is annotated!

In case the author is reading: The decorative feather images are between 2MB to almost 5MB in size. Compression might be in order to save users time and bandwidth, and make the site look less broken while the images are partially loaded :)

> I guess Uniqlo is run through Windows though: one thing that struck me was the font, which I’m almost certain is Consolas,

Surely this would use whatever font the virtual terminal profile was set to? I don’t know of any method to choose a virtual terminal font from bash and don’t see any code that addresses it?

how is it obfuscated? It's literally written as plain black monospace text on a white background.

Pretty sure any AI can solve it in 20 seconds.

  • It’s encoded not obfuscated. It’s even commented, which is the opposite of obfuscated. Plus it’s not really an Easter egg that was found: it is literally printed in a shirt. Easter eggs are supposed to be hidden and either only found by insider knowledge or deep investigation. This was neither.

Great post! It's interesting, detailed but concise, and well-written. Also, I appreciate the "no cookies or tracking" and attractive, functional and performant site design.

> Interesting. I told my wife "that’s basically how people ship viruses’ and bought it.

It’s a movie plot.

For a non English locale that use comma instead of dot for decimals (in my case, Spanish), this script is partially crashing. Run using something like `chmod +x shirt.sh; LC_NUMERIC=C ./shirt.sh`.

I don't know... I prefer unobfuscated text that you can immediately grok. The other day I saw this on a T-shirt:

> May the m×s/t² be with you

For anyone that cares, this is a slightly less stupid Python version:

    #!/usr/bin/env python3
    from os   import environ; E = environ.get
    from math import sin
    from time import sleep
    text = "♥PEACE♥FOR♥ALL" # The text to sine-scroll animate
    nText  = len(text)      # Number of utf8 chars
    freq   = 0.2            # Frequency scaling factor
    color0 = 12             # xt256 Color cube segment 12..<208
    color1 = 208; nColor = color1 - color0
    (w, h) = (int(E("COLUMNS", 80)), int(E("LINES", 24)))
    t = 0
    while True:
        x = (w/2) + (w/4)*sin(t*freq)           # x pos via sine value
        x = max(0, min(w - 1, int(x + 0.5)))    # bound to tty width
        color = color0 + ((nColor*t)//h)%nColor # cycle colors
        ch = text[t%nText]  # Get char & Use xterm-256 color escs
        print("%*s\033[38;5;%sm%s\033[m\n" % (x, "", color, ch))
        t += 1
        sleep(0.1)   # original used bc shell outs to rate-limit

As mentioned in https://news.ycombinator.com/item?id=48830634 , the heart symbols did not otherwise even work for my bash and some have commented on liking the screen saver.

On one hand it's nice how it's clean and commented, but on the other hand some golfing could have made the encoded block a lot more reasonable to actually manually enter.

Nice!

Might have to do something like that for a verse on the next Carolina Code Conference shirt. Been trying to figure out a good way to pull in cybersecurity.

Well at least they're not instructing consumers to run curl | bash.

That's better than half the tech howtos out there.

The real threat model here isn't the base64 payload, it's Uniqlo turning a T-shirt into a QR code that requires a human OCR pipeline to redeem.

what if it contained a zero day for tesseract and the script you thought you got is just a throwaway

Cool! I bought one a few months ago as soon as I spotted it at a Uniqlo store, and later ordered a larger size online—I really love wearing them. But it never occurred to me to look into the story behind them.

Brilliant marketing when you can get people to pay to walk around advertising with your logo!!

Base64 without error correction turns the t-shirt itself into a lossy transport layer, so the OCR/transcription step becomes the actual challenge.

P ./cool.sh: line 31: bc: command not found ./cool.sh: line 34: bc: command not found ./cool.sh: line 37: bc: command not found E ./cool.sh: line 31: bc: command not found ./cool.sh: line 34: bc: command not found ./cool.sh: line 37: bc: command not found

Very wow. Shame they assumed everyone has "bc"...

> I ran OCR in a few ways: First, using the built-in OCR of the circle-to-search feature on Android, which is often very good. Second, by using Tesseract with a few options and tweaks. And third by running it through Claude. After diffing the three to look for mismatches and getting Claude to output a table of locations for quick scanning, it became trivial but time-consuimg to tidy up the remainder

I bet 10$ I'd spend less time typing it from the t-shirt. And I wouldn't boil two kettles of water in the process.

But hey, AI makes you 10x more productive, I suppose

Why does the shirt have an obfuscated bash script on the back?