Circle City Con 2020 kids-these-days writeup Note: this file needs to be viewed in UTF-8 for the emoji to display properly. If you see junk instead, find the text encoding menu item in your browser and choose UTF-8 or Unicode. ######################################## Challenge Definition We were initially provided with a bunch of emoji in the Discord channel and no further information. Copy/pasting the emoji resulted in the following string: :classical_building::u6708::ticket::bed::boy::nerd::mouse::calling::green_heart::nut_and_bolt::star2::regional_indicator_p::shirt::orange_book::regional_indicator_k::handshake::fork_knife_plate::motorboat::cool::cloud_tornado::gem::slight_frown::sweat::cloud_lightning::womans_clothes::slight_smile::cow::regional_indicator_p::police_officer::crayon::sweet_potato::pig::family::knife::mortar_board::clock130: Presumably this contained a flag encoded into it somehow. ######################################## UTF-8 and Emoji History Since computers only deal naively with numbers, representing text requires the use of a look-up table to assign a number to each letter. This is called a character encoding. Since the 1960s, ASCII has been the most broadly-used character encoding for representing unaccented roman-alphabet text. (Others are still sometimes used, such as EBCDIC on IBM mainframes.) ASCII was designed for use in English, and the original 7-bit version lacked even the accented characters needed for other roman-alphabet languages. By the 1980s, various ASCII extensions (such as ISO-8859-1 aka ISO-Latin-1) used all 8 bits of each byte to include accented characters for European languages. Most non-Roman-alphabet languages had several mutually-incompatible character encodings, such as JIS, Shift-JIS, and EUC-JP for Japanese text. These usually used a mixture of 8-bit (one-byte) and 16-bit (two-byte) characters to hold their larger number of symbols. In the 1990s, the Unicode family of character encodings was created to simplify this chaos by being big enough to hold all the letters for all the languages and then some. UTF-8 is the most commonly used Unicode encoding; it uses a variable number of bytes, from 1 to 4 per character. This means that virtually every character needed in any human language is included, with plenty of extra space. By the early 2000s, some of that extra space was being used to include pictures -- these are emoji. Several batches have been released over time, as often as once per year. This will come up later. In Unicode, every symbol (include emoji) has a canonical identifier called a code point. This is often written in the form U+ such as U+1F525 for the fire emoji. This code point is then turned into a multi-byte representation in UTF-8 using a somewhat complicated formula visible here: https://en.wikipedia.org/wiki/UTF-8#Description Some emoji have multiple code point representations, such as the "Monthly Amount" / "Ideograph-6708" emoji, which is U+1F237 and also U+FE0F. These will appear similar when viewed, but are stored as very different byte sequences. This will also come up later. Not all programs deal gracefully with UTF-8 multibyte characters and emoji, so some common applications like Slack and Discord handle them through "shortcodes". A shortcode is typically a short description of the emoji surrounded by colons, such as :fire:. This is the beginning of one of our difficulties. ######################################## Beginnings and Failures Copying out of Discord gave a string of shortcodes, as presented above. Since there wasn't much repetition in the challenge prompt, I guessed it wasn't a simple substitution cipher where one letter was represented by a unique emoji. To turn the shortcodes back into "real" UTF-8 emoji, I first tried typing them in. I'm using Ubuntu Linux, so the default graphical text editor allows entering emoji by right-clicking and choosing "Insert Emoji" from the menu. I did that over and over, typing in the short code to search the emoji picker. Some were not available, so I searched for the shortcode on the internet and copy/pasted what I found. Eventually I came up with this: 🏛ī¸đŸˆˇī¸đŸŽĢī¸đŸ›ī¸đŸ‘Ļī¸đŸ¤“ī¸đŸ­ī¸đŸ“˛đŸ’šī¸đŸ”Šī¸đŸŒŸđŸ‡ĩ👕ī¸đŸ“™ī¸đŸ‡°đŸ¤ī¸đŸŊī¸đŸ›Ĩī¸đŸ†’ī¸đŸŒĒī¸đŸ’Žī¸đŸ™đŸ˜“đŸŒŠī¸đŸ‘šī¸đŸ™‚đŸ‡ĩ👮🖍ī¸đŸ ī¸đŸˇđŸ‘ĒđŸ”Ē🎓🕜 In case that got mangled, here it is in Base64: 8J+Pm++4j/CfiLfvuI/wn46r77iP8J+bj++4j/CfkabvuI/wn6ST77iP8J+Qre+4j/Cfk7Lwn5Ka 77iP8J+Uqe+4j/CfjJ/wn4e18J+Rle+4j/Cfk5nvuI/wn4ew8J+kne+4j/Cfjb3vuI/wn5ul77iP 8J+Gku+4j/CfjKrvuI/wn5KO77iP8J+ZgfCfmJPwn4yp77iP8J+Rmu+4j/CfmYLwn4e18J+RrvCf lo3vuI/wn42g77iP8J+Qt/Cfkarwn5Sq8J+Ok/CflZwK An initial guess was that maybe this was some sort of computer program that would output the flag when run. Web searching revealed https://emojicode.org as the home page for such a language. I downloaded the source code and compiled it, but the emoji string was not a valid emojicode program. Further research revealed codemoji, a fun "encryption" project from Mozilla that was released in 2016. It's described at https://codemoji.org but isn't online anymore. I found the code on github at https://github.com/mozilla/codemoji and spent an hour or two getting the thing to build (it's a complicated node.js application). It's on my website if you want to play with it: https://www.tinkerfairy.net/codemoji/ Codemoji doesn't have a simple decryption UI, but playing with it a bit revealed how to do so: * The URL base is https://www.tinkerfairy.net/codemoji/share.html?data= * The data argument is a JSON blob like this. It won't work without a key: {"message":"emoji-goes-here","key":"one-emoji-here"} * Base64 encode the JSON blob, URLEncode the Base64, and then append it to the URL base So, I created the following link: https://www.tinkerfairy.net/codemoji/share.html?data=eyJtZXNzYWdlIjoi8J%2BPm%2B%2B4j%2FCfiLfvuI%2Fwn46r77iP8J%2Bbj%2B%2B4j%2FCfkabvuI%2Fwn6ST77iP8J%2BQre%2B4j%2FCfk7Lwn5Ka77iP8J%2BUqe%2B4j%2FCfjJ%2Fwn4e18J%2BRle%2B4j%2FCfk5nvuI%2Fwn4ew8J%2Bkne%2B4j%2FCfjb3vuI%2Fwn5ul77iP8J%2BGku%2B4j%2FCfjKrvuI%2Fwn5KO77iP8J%2BZgfCfmJPwn4yp77iP8J%2BRmu%2B4j%2FCfmYLwn4e18J%2BRrvCflo3vuI%2Fwn42g77iP8J%2BQt%2FCfkarwn5Sq8J%2BOk%2FCflZwiLCJrZXkiOiLwn4%2Bb77iPIn0K I clicked the link, found that the emoji string couldn't be decrypted cleanly, and felt sad. :-( At this point, I had a couple next ideas to check: maybe codemoji needed to be built with newer dependencies to support more new emoji, or maybe it was some kind of classical cipher like Vigenere, but with a letter-to-emoji translation layered on top. I was pretty frustrated and moved on to other problems instead of following up on these leads. ######################################## Trying Again and Succeeding During the CTF wrapup, it was mentioned that the challenge was created using a command line tool. Basically, you can do like: echo "CCC{The Flag goes here}" | tool and you get the challenge prompt out of it. I started searching for such a tool and found ecoji on github: https://github.com/keith-turner/ecoji After building ecoji, I was again disappointed. Pasting the emoji string above into ecoji -d gives an "Invalid rune" error. No love. It seemed like it was probably the right tool though, so instead I tried testing it forwards, like this: $ echo "CCC{" | ./ecoji 🏛🈷đŸŽĢ🚆 Note that the first three emoji of this output look very similar to, but not quite the same as, the first three from my challenge string. This seemed to prove that ecoji was the correct tool, but something was wrong with my emoji. Using the hex dumper xxd verified that pretty quickly: Mine : f09f 8f9b efb8 8ff0 9f88 b7ef b88f f09f Ecoji: f09f 8f9b f09f 88b7 f09f 8eab f09f 9a86 I had the right emoji, represented wrong! I tried recreating the emoji string again, copying and pasting from https://emojipedia.org but was met with repeated failure. I tried brute forcing the flag by building up candidate strings one character at a time and running them through ecoji until the emoji looked right, but that was awful and time-consuming. Since the issue was turning the shortcodes into the proper UTF-8 codepoints manually, I looked for code to do it for me. https://github.com/mwunsch/rumoji claims to do it, so I installed that Ruby gem and tried it, giving the following results: 🏛🈷đŸŽĢ🛏đŸ‘Ļ🤓🐭📲💚🔩🌟:regional_indicator_p:👕📙:regional_indicator_k::handshake:đŸŊ:motorboat:🆒:cloud_tornado:💎:slight_frown:😓:cloud_lightning:👚:slight_smile:🐮:regional_indicator_p::police_officer::crayon:🍠🐷đŸ‘Ē:knife:🎓🕜 Not all of the shortcodes were supported by rumoji, but many of them were. I was disappointed but undeterred, so I worked through this new ciphertext one four-emoji chunk at a time. Rumoji-created emoji always seemed to be the ones required by ecoji, and I filled in the unsupported ones by copy/pasting from different websites until I got one that avoided the "invalid rune" error. Generally, https://emojis.wiki was the most compatible with ecoji, but it also didn't support all of them. Fortunately, https://emojipedia.org was able to correctly supply the rest. Eventually I got the following UNIX command, which reveals the flag: echo "🏛🈷đŸŽĢ🛏đŸ‘Ļ🤓🐭📲💚🔩🌟đŸ‡ĩ👕📙🇰🤝đŸŊđŸ›Ĩ🆒đŸŒĒ💎🙁😓🌩👚🙂🐮đŸ‡ĩ👮🖍🍠🐷đŸ‘ĒđŸ”Ē🎓🕜" | ./ecoji -d In case that got mangled, here it is in Base64: ZWNobyAi8J+Pm/CfiLfwn46r8J+bj/Cfkabwn6ST8J+QrfCfk7Lwn5Ka8J+UqfCfjJ/wn4e18J+R lfCfk5nwn4ew8J+knfCfjb3wn5ul8J+GkvCfjKrwn5KO8J+ZgfCfmJPwn4yp8J+RmvCfmYLwn5Cu 8J+HtfCfka7wn5aN8J+NoPCfkLfwn5Gq8J+UqvCfjpPwn5WcIiB8IC4vZWNvamkgLWQK If you're curious, go ahead and paste that emoji string through a hex dumper and compare it to the initial one to see the differences. ######################################## Gr33tz Thanks to Lintile and all the merry hackers who wrote the challenges and ran the Circle City Con 2020 CTF. This was super fun! Shout Out to @CTF_Circle, my favorite place on the internet. We run a tight ship; every deckhand has a five-year plan and an icepick. Congratulations to @iancoldwater for winning with style and grace. You're an inspiration and a chaotic goose. Honk!