Information Assurance Student Group

The Information Assurance Student Group (IASG) at Iowa State University is dedicated to cyber security and holds weekly meetings for students interested in the Cyber Security field. Join our discord!

About IASG Available Equipment Puzzlers Meetings Projects CDC Help

Hex and Base64 Encoding Puzzler

This puzzler focused on two forms of encoding for arbitrary binary data: Hex encoding and Base64.

The supplied file was hex-encoded. Decoding it yielded three 16-byte chunks. The first two chunks were ASCII consisting of Base64-encoded text - the first 2/3 of the flag.

The third chunk, after decoding the hex, was not ASCII but rather binary garbage. Solving this part was the hardest. The third part of the flag could be found by XOR-ing the first two encoded parts with the third part.

There was a small hint: the last byte was a newline. Most Unix text files end with a newline, and parts 1 and 2 end with the same character (‘=’). This means that the last character of the third part is unchanged by the XOR operation.

Tips and Techniques

The supporting-files tarball has the creation script and a solve script which show several different techniques for working on this sort of challenge.

Shell Utilities

Head and Tail

The shell contains a number of utilities that cover some of the basic operations of text or binary files. head and tail are very useful for splitting files. The create.sh script uses head and tail for this purpose.

head -c 10 flag.txt : Select the first 10 characters of the file
tail -c +22 flag.txt : Biginning with the 11th character, select the rest of the file
tail -c +11 flag.txt | head -c 11 : Combining the first two, this selects 11 characters from the file, beginning with the 11th character.

The normal use for head and tail is of course separating based on lines. Replacing the -c flag with -n causes the utilities to work on lines, rather than characters.

xxd

xxd without any arguments makes a nice hex dump of a file, showing offsets, formatted hex, and ASCII. However, it can also be used encode and decode plain hex dumps.

xxd -p file simply creates a plain hex encoding of the file
xxd -r -p file.hex decodes plain hex
xxd -p file | xxd -r -p encodes then decodes the file - full cycle

Misc. Tools

There are other tools for various encodings. base64 is used in this puzzler. rot13 and caesar show up from time to time, to perform Caesar ciphers.

Scripting language

While using shell utilities is a quick and basic way to take a look at encoded text, it lacks power and customizability. At that point, it becomes necessary to drop to a real scripting language.

The create.sh script is a good example of this. While most of the operations were done using shell utilities, there isn’t a good way to XOR two strings in a shell, so that part is kicked off to a Ruby script. Additionally, while it is possible, some operations are fairly arcane in the shell. For example, array slicing is much nicer than splitting with head and tail.

Scripting Language Tricks

The solve.rb script was written entirely in Ruby. It is fairly easy to read, but these are some of the tricks used in the script.

Decoding Hex

hex.scan(/../).map{|c| c.hex} Ruby doesn’t have a standard function to encode or decode hex, so decoding the initial file requires scanning two chars at a time and decoding those two chars as a hexadecimal number. This yields an array of numbers, which is the byte array representing the decoded file.

Other languages have convenience functions to make hex encoding and decoding very easy.

Splitting files

Once the file is represented as either a char array or a byte array (in the case of solve.rb, it is a byte array), array slicing makes splitting it into parts very easy.

` p1 = puzzler[0..15] p2 = puzzler[16..31] p3 = puzzler[32..47] `

Most languages support array slicing (or string slicing) of some sort.

Base64 and other encodings

This is just a matter of using the right language features/libraries for the encoding in question. Base64 is very easy in Ruby, and since it is ubiquitous, most languages should support it as well.

Base64.decode64(str)

Zipping and Mapping

map and zip are two higher-order functions common in high-level languages.

The map operation is used to convert an array by performing the same operation on every element of the array. For example, it could be used to add 1 to every number in an array of ints.

[1, 2, 3].map{|e| e + 1} yields the array [2, 3, 4]

The zip operation takes two arrays of equal length “zips” them together, yielding an array of pairs.

[1, 2, 3].zip([4, 5, 6]) produces [[1, 4], [2, 5], [3, 6]].

The general form of “zipping” arrays can be extended beyond two arrays yielding pairs to three arrays yielding triples, up to any number of arrays yielding N-tuples.

Combining “zipping” and “mapping” is how XOR-ing one or more strings together is done.

plain3 = p3.zip(p1, p2).map{|b1, b2, b3| b1 ^ b2 ^ b3}.pack('C*')

This takes the p3 array, zips it with p1 and p2 to get an array of triples. It will iterate through the array of triples, XORing all three bytes together to build a final array of bytes.

pack('C*') is just Ruby magic to turn a byte array into a string.

All of these operations are possible in Python, and probably other languages as well.

Interactivity

Interactivity isn’t just for the shell. Ruby (and Python) have interactive interpreters available. You can test it out by running irb and then trying out the solve.rb script one line at a time - or even breaking the larger one-liners into multiple commands to see how these basic data-manipulation methods can be used.

Other Tools

CyberChef is a tool made for solving this sort of challenge. It has blocks for most of these operations (encoding, hex, string XOR…). It also gives immediate feedback as the recipe is modified.