Flare-On 8 - Antioch
To solve this challenge, you’ll need to …AAARGH
This challenge was really AAARGH for me. In retrospect I wonder why I didn't see what I was supposed to see and due to that lost almost a week on this fairly easy challenge. But let's start from the beginning.
We are given a tar file, which contains, a lot of (possibly) randomly names folders, a13ffcf46cf41480e7f15c7f3c6b862b799bbe61e7d5909150d8a43bd3b6c039.json
, manifest.json
and repositories
files. Each folder contains json
, layer.tar
and VERSION
. Inspecting the files it become obvious that the whole antioch.tar
is a docker archive which we can import.
λ docker load --input chall\antioch.tar
Loaded image: antioch:latest
And then we can run it
λ docker run -it antioch
AntiochOS, version 1.32 (build 1975)
Type help for help
>
typing help doesn't give us much and it looks like nothing much going on in the program. Let's get back to the tar archive. Inspecting the layer.tar
in each folder reveals that they contain a pattern of files named with the lower letters from a
to z
(i.e. a.dat
) but not all the files are in all the folders. One layer.tar
was different, in the folder 7016b68f19aed3bb67ac4bf310defd3f7e0f7dd3ce544177c506d795f0b2acf3
there was a single file AntiochOS
and it was an ELF. Time for Ghidra.
There's not that many methods and the entry
is quite simple. It prints the header, reads the input and compares it with one of the 3 choices: help
, consult
or approach
. Let's focus on the latter one. Going in the approach
, we can see it prints the approach the gorge
, ask for your name, calculated crc32
of it, then check the table of valid crc32
for names. If it doesn't find one, it prints aaaargh
. If it does, asks for the quest and color. If the quest crc32
matches the value for the quest associated with the name, it continues to operate. Otherwise, again we get an aaaargh
.
i = 0;
new_line = 10;
len = decode_approach_the_gorge();
print(1,len,0x25);
setuid(1);
what_is_your_name(local_b8);
print(1,local_b8,0x13);
len = read_input(0,buf,0x80);
decoded_name = crc32(buf,len);
crc32val = 0xb59395a9;
pos = &correct_vals;
while (crc32val != decoded_name) {
uVar2 = (int)i + 1;
i = (ulong)uVar2;
if (uVar2 == 0x1e) goto aaaargh;
crc32val = *pos;
pos = pos + 3;
}
what_is_your_quest(local_b8);
print(1,local_b8,0x14);
lVar3 = read_input(0,buf,0x80);
if (1 < lVar3) {
what_is_your_color(local_b8);
print(1,local_b8,0x1d);
len = read_input(0,buf,0x80);
crc32val = crc32(buf,len);
if (((&colors)[i * 3] == crc32val) && ('\x00' < *(char *)(&idx + i * 3))) {
to_int((int)*(char *)(&idx + i * 3),buf);
len = Right_off_you_go.();
print(1,len,0x14);
For the consult
method, it prints the header (consult the book of armaments!
), prepares the buffer, prepares the file name to be a.dat
and opens the file. Next it reads the content and goes to the next file b.dat
. This continues until we reach the end of the alphabet. Having that, there's a big buffer with .
prepared and we use the data we read. The last steps are:
- replace some characters in the buffer with characters like
\)(/|_
- print the resulting ascii art
So to get the flag, we need to: have the missing layers (those ones with files a.dat
through z.dat
) inside the docker, know the valid names and matching colors. Run everything and get the flag.
My initial approach was to bruteforce, the names and crc32
to find valid ones and it kind worked. Colors were short and quite quickly appeared in the list. Names were difficult but soon enough got one and I could test the program.
2021/03_antioch/solution via 🐍 v2.7.18
❯ python3 solve.py
Color: Red - EF185643, idx: 13
Color: Tan - 379549C9, idx: 21
Color: Blue - 3F8468C8, idx: 1
Color: Gold - EF3DE1E8, idx: 16
Color: Gray - BAFA91E5, idx: 11
Color: Mint - C9A060AA, idx: 4
Color: Pink - 9C8A3D07, idx: 19
Color: Teal - 67C1F585, idx: 29
Name: Zoot - 3FC91ED3, idx: 21
So the one matching pair would be Zoot
and Tan
, and as we seen from the code - the quest doesn't matter. Let's see this in action:
Now we have some problems, we do not have the files from the other layers in the correct order and we don't know the order. The order could be give by the number with the message "Right. Off you go #8" but how to map that to layers?
That was my problem for a long time - too long. But there is a link, the names are in the json
files. If we open one we can clearly see the name Zoot
as an author.
{"architecture":"amd64","author":"Zoot","config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":null,"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Hostname":"","Image":"","Labels":null,"OnBuild":null,"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"container_config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","#(nop) ADD multi:6193592bdd67ab3993428a5b2167f66b47fdc0f62d83e0e0cbdb751d74642801 in / "],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Hostname":"","Image":"","Labels":null,"OnBuild":null,"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"created":"1975-04-03T12:00:00.000000000Z","docker_version":"20.10.2","id":"b5f502d32c018d6b2ee6a61f30306f9b46dad823ba503eea5b403951209fd59b","os":"linux"}
so the layer, should be 8th. With that knowledge we could calculate the remaining crc32
but also we don't need that. We only need to apply dat
files on top of each other in correct order defined by the binary. The following script does that
import binascii
import string
import itertools
import json
import os
def crc32(inp):
return binascii.crc32(inp)
names = ["Dragon of Angnor",
"Roger the Shrubber",
"Dinky",
"Dennis the Peasant",
"Sir Ector",
"A Famous Historian",
"Tim the Enchanter",
"Sir Gawain",
"Trojan Rabbit",
"Sir Robin",
"Green Knight",
"Sir Bedevere",
"Squire Concorde",
"Sir Not-Appearing-in-this-Film",
"Legendary Black Beast of Argh",
"Sir Gallahad",
"Lady of the Lake",
"Zoot",
"Miss Islington",
"Chicken of Bristol",
"Rabbit of Caerbannog",
"Black Knight",
"Prince Herbert",
"Brother Maynard",
"King Arthur",
"Sir Bors",
"Squire Patsy",
"Bridge Keeper",
"Inspector End Of Film",
"Sir Lancelot"]
valid_names = [0xb59395a9, 0x5EFDD04B, 0xECED85D0, 0xD8549214, 0x2C2F024D, 0x18A5232, 0x72B88A33, 0x674404E2, 0x307A73B5, 0x13468704, 0x94F6471B, 0xEDA1CF75,
0xBBAC124D, 0xF707E4C3, 0xD702596F, 0x86A10848, 0xD640531C, 0x7B665DB3, 0xab1321cc, 0x4f6066d8, 0x256047ca, 0x3fc91ed3, 0xa424afe4, 0x550901da,
0x10a29e2d, 0x56cbc85f, 0x80dfe3a6, 0xe657d4e1, 0x2ba1e1d4, 0x7d33089b]
nums = [14,18,2,29,12,13,20,11,28,21,5,24,25,7,10,1,19,3,4,17,9,8,27,16,22,15,30,23,26,6]
for root,dirs,files in os.walk(".", topdown=False):
for f in files:
name = os.path.join(root,f)
if not 'json' in name:
continue
json_data = json.load(open(name,'rb'))
if "author" not in json_data:
continue
author = json_data["author"]
idx = valid_names.index(crc32(str.encode(author+'\n')))
print(f'{author} - {nums[idx]}')
last_dir = os.path.basename(os.path.normpath(root))
print(f'renaming {root} to {root.replace(last_dir, str(nums[idx]))}')
os.rename(root, root.replace(last_dir, str(nums[idx])))
import glob
import shutil
for i in range(1,31):
for file in glob.glob(f'./from_backup/{i}/*.dat'):
print(file)
shutil.copy(file, r'./docker')
Running it, and having the extracted files from all the layers in separate folders (1
- 30
) will correctly apply them on top of each other. What is left is to run the binary providing one correct pair.
And we get flag: Five-Is-Right-Out@flare-on.com