Intro
I am not going to lie, this one was one of the most frustrating ones. It has so many layers one after another that is why I gave up so many times. Let’s start.
Write-Up
When we unpack the file, we see a single file named antioch.tar
Let’s unpack this one once again. We get many folders inside the archive. Let’s check what is inside those folders.
-rw-r--r--@ 1 mustafa staff 3B Jul 20 07:18 VERSION
-rw-r--r--@ 1 mustafa staff 989B Jul 23 06:25 json
-rw-r--r--@ 1 mustafa staff 87K Jul 20 07:18 layer.tar
When we check the JSON file we see
{"architecture":"amd64","author":"Roger the Shrubber","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:a08ee5be09d5524b13fb93561f476c5975fbd1f54e526380a25253d0c6a5a425 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":"1c5d28d6564aed0316526e8bb2d79a436b45530d2493967c8083fea2b2e518ce","os":"linux"}
This is a docker image archive. We can easily load this docker image with the following command
docker load < antioch.tar
Let’s inspect the image
docker image inspect antioch
We will get the following output
"Cmd": [
"/AntiochOS"
],
Let’s run this image.
docker exec -it <YOURCONTAINERID> /AntiochOS
We’re greeted with the following text.
AntiochOS, version 1.32 (build 1975)
Type help for help
We can get the AntiochOS binary either from the docker image layer or you can use
docker cp YOUR_CONTAINER_NAME:/AntiochOS ~/Desktop/
Let’s disassemble this file.
__int64 start()
{
void *v0; // rsi
__int16 *v1; // rsi
__int64 v3; // [rsp-20h] [rbp-B8h]
__int16 v4; // [rsp+0h] [rbp-98h]
__asm { endbr64 }
v0 = getHeader();
print_str(1LL, v0, 37LL);
help_prompt(&v4, v0);
print_str(1LL, &v4, 19LL);
while ( 1 )
{
v4 = ' >';
print_str(1LL, &v4, 2LL);
v1 = &v4;
if ( !read_file(0, (__int64)&v4, 128LL) )
break;
quit_prompt(&v3, &v4);
v1 = (__int16 *)&v3;
if ( !(unsigned int)check_command((__int64)&v4, (__int64)&v3, 5LL) )
break;
help_command(&v3, &v3);
if ( (unsigned int)check_command((__int64)&v4, (__int64)&v3, 5LL) )
{
consult_command(&v3, &v3);
if ( (unsigned int)check_command((__int64)&v4, (__int64)&v3, 8LL) )
{
approach_command(&v3, &v3);
if ( !(unsigned int)check_command((__int64)&v4, (__int64)&v3, 9LL) )
show_approach();
}
else
{
sub_401460();
}
}
else
{
sub_401420(&v4, &v3);
}
}
return sub_401A90(0LL, v1);
}
This binary accepts the following commands,
- help
- quit
- approach
- consult
When we type approach it jumps to show_approach
proc and asks these questions
Approach the Gorge of Eternal Peril!
What is your name?
What is your quest?
What is your favorite color?
Let’s check how these are generated and checked
__int64 show_approach()
{
__int64 v0; // rbx
void *v1; // rax
__int64 v2; // rsi
int v3; // eax
int *v4; // rdx
unsigned int v5; // ecx
int v6; // eax
int v7; // er8
int *v8; // rax
char v9; // al
void *v10; // rax
char v12; // [rsp+Fh] [rbp-B9h]
char v13; // [rsp+10h] [rbp-B8h]
char v14; // [rsp+30h] [rbp-98h]
v0 = 0LL;
v12 = 10;
v1 = sub_401260(); // Generate the header
print_str(1LL, v1, 37LL);
sub_401A50(); // Generate What's your name?
ask_name(&v13);
print_str(1LL, &v13, 19LL);
v2 = read_file(0, (__int64)&v14, 128LL);
v3 = crc32(&v14, v2);
v4 = dword_40200C;
v5 = 0xB59395A9;
while ( v5 != v3 )
{
v0 = (unsigned int)(v0 + 1);
if ( (_DWORD)v0 == 30 )
return print_str(1LL, "...AAARGH\n\n", 11LL);
v5 = *v4;
v4 += 3;
}
ask_quest(&v13, v2, v4);
print_str(1LL, &v13, 20LL);
if ( read_file(0, (__int64)&v14, 128LL) > 1 )
{
ask_color(&v13);
print_str(1LL, &v13, 29LL);
v6 = read_file(0, (__int64)&v14, 128LL);
v7 = crc32(&v14, v6);
v8 = &dword_402000[3 * v0];
if ( v8[1] == v7 )
{
v9 = *((_BYTE *)v8 + 8);
if ( v9 > 0 )
{
sub_401AF0(v9, &v14);
v10 = right_off();
print_str(1LL, v10, 20LL);
print_str(1LL, &v14, strlen(&v14));
return print_str(1LL, &v12, 1LL);
}
}
}
return print_str(1LL, "...AAARGH\n\n", 11LL);
}
First, it calculates the CRC32 hash of the name and it compares with some predefined values at dword_40200C
. This one was puzzling. How could we guess the name? Thanks to one tip, I have realized that this is a dialogue from a movie called Monty Python and the Holy Grail This movie had a dialog something like below
- Bridgekeeper: What is your name?
- Lancelot: My name is Sir Lancelot of Camelot.
- Bridgekeeper: What is your quest?
- Lancelot: To seek the Holy Grail.
- Bridgekeeper: What is your favourite colour?
- Lancelot: Blue.
- Bridgekeeper: Right. Off you go.
So if we try this on the /AntiochOS file we get the following result
Approach the Gorge of Eternal Peril!
What is your name? Sir Lancelot
What is your quest? To seek the Holy Grail
What is your favorite color? Blue
Right. Off you go. #18
Let’s try to understand what is going on. First, it asks the name and compares the hash of the name to predefined values. If the hash is right it asks the quest. However, the content of the quest is not checked at all. Anything with length 1 or more will pass the check. Next, it asks the color and calculates the CRC32 of the color, and compares predefined CRC32 next to the hash of the name. If both values are correct, it prints the next value. For example, for Sir Lancelot, the name CRC32 is 5EFDD04Bh and Blue CRC32 is 3F8468C8h so it prints 18(12h)
.rodata:000000000040200C dword_40200C dd 5EFDD04Bh
.rodata:000000000040200C dd 3F8468C8h
.rodata:000000000040200C dd 12h
How we are going to know all those names and colors. What is the importance of these numbers? Let’s take a break and analyze the next command, consult.
When we type consult, it just prints, vvv… Let’s see the reason for this.
__int64 sub_401460()
{
signed int v0; // er14
char *v1; // rsi
signed int hFile; // eax
unsigned int v3; // er13
unsigned __int8 *ptrBuffer; // rax
__int64 *ptrFile; // rdx
__int64 v6; // rax
unsigned __int8 v7; // dl
__int64 v9; // [rsp+8h] [rbp-2030h]
__int64 file_content; // [rsp+10h] [rbp-2028h]
unsigned __int8 buffer[4096]; // [rsp+1010h] [rbp-1028h]
char v12; // [rsp+2010h] [rbp-28h]
v0 = 'a';
v9 = 'tad..';
memset(buffer, 0, sizeof(buffer));
v1 = (char *)consult_header();
print_str(1LL, v1, 31LL);
sub_401A50();
do
{
while ( 1 )
{
LOBYTE(v9) = v0;
hFile = open_file(&v9, v1);
v3 = hFile;
if ( hFile >= 0 )
break;
if ( (_BYTE)++v0 == '{' )
goto LABEL_7;
}
read_file(hFile, (__int64)&file_content, 4096LL);
close_file(v3, &file_content);
ptrBuffer = buffer;
ptrFile = &file_content;
v1 = &v12;
do
{
*ptrBuffer++ ^= *(_BYTE *)ptrFile;
ptrFile = (__int64 *)((char *)ptrFile + 1);
}
while ( ptrBuffer != (unsigned __int8 *)&v12 );
++v0;
}
while ( (_BYTE)v0 != '{' );
LABEL_7:
if ( !aV[0] )
{
*(__m128i *)aV = _mm_load_si128((const __m128i *)&xmmword_402240);
*(_OWORD *)&aV[16] = *(_OWORD *)aV;
*(_OWORD *)&aV[32] = *(_OWORD *)aV;
*(_OWORD *)&aV[48] = *(_OWORD *)aV;
*(_OWORD *)&aV[64] = *(_OWORD *)aV;
*(_OWORD *)&aV[80] = *(_OWORD *)aV;
*(_OWORD *)&aV[96] = *(_OWORD *)aV;
*(_OWORD *)&aV[112] = *(_OWORD *)aV;
*(_OWORD *)&aV[128] = *(_OWORD *)aV;
*(_OWORD *)&aV[144] = *(_OWORD *)aV;
*(_OWORD *)&aV[160] = *(_OWORD *)aV;
*(_OWORD *)&aV[176] = *(_OWORD *)aV;
*(_OWORD *)&aV[192] = *(_OWORD *)aV;
*(_OWORD *)&aV[208] = *(_OWORD *)aV;
*(_OWORD *)&aV[224] = *(_OWORD *)aV;
*(_OWORD *)&aV[240] = *(_OWORD *)aV;
sub_401000(aV);
}
v6 = 0LL;
do
{
v7 = 10;
if ( (v6 & 0xF) != 15 )
v7 = aV[buffer[v6]];
buffer[v6++] = v7;
}
while ( v6 != 4096 );
return print_str(1LL, buffer, 4096LL);
It reads a.dat to z.dat and creates some kind of hash and prints the result. However, our image doesn’t have those dat files. It only has one file. Let’s go back to the beginning. We saw many folders inside the tar archive and each one had JSON file. If we analyze the JSON file again we see an author
field! The number we see shows the number for those authors.
Docker images have many layers and those number shows the order of files or layers. If we unpack each folder one by one and copy those files according to their order, we will get our image. Let’s write a script to create our image.
import glob
import json
import binascii
name_hashes = [
0x0B59395A9,0x1BB5AB29,0x0E,
0x5EFDD04B,0x3F8468C8,0x12,
0x0ECED85D0,0x82D23D48,0x2,
0x0D8549214,0x472EE5,0x1D,
0x2C2F024D,0x0C9A060AA,0x0C,
0x18A5232,0x24D235,0x0D,
0x72B88A33,0x81576613,0x14,
0x674404E2,0x5169E129,0x0B,
0x307A73B5,0x0E560E13E,0x1C,
0x13468704,0x2358E4A9,0x15,
0x94F6471B,0x0D6341A53,0x5,
0x0EDA1CF75,0x0BAFA91E5,0x18,
0x0BBAC124D,0x0A697641D,0x19,
0x0F707E4C3,0x0EF185643,0x7,
0x0D702596F,0x79C28915,0x0A,
0x86A10848,0x59108FDC,0x1,
0x0D640531C,0x0EF3DE1E8,0x13,
0x7B665DB3,0x0A3A903B0,0x3,
0x0AB1321CC,0x0EEEDEAD7,0x4,
0x4F6066D8,0x9C8A3D07,0x11,
0x256047CA,0x4085BE9E,0x9,
0x3FC91ED3,0x379549C9,0x8,
0x0A424AFE4,0x0EF871347,0x1B,
0x550901DA,0x1FCEC6B,0x10,
0x10A29E2D,0x0E76056AA,0x16,
0x56CBC85F,0x356F1A68,0x0F,
0x80DFE3A6,0x9D0AB536,0x1E,
0x0E657D4E1,0x0B4E9FD30,0x17,
0x2BA1E1D4,0x0BE66D918,0x1A,
0x7D33089B,0x67C1F585,0x6
]
name_map = {}
for filename in glob.iglob('./antioch/' + '**/json', recursive=True):
f = open(filename)
data = json.load(f)
if "author" in data:
author_id = data["id"]
author = data['author']
print(author)
print(author_id)
print('File %s ' % filename)
byte_array = bytes(author + "\n", 'utf-8')
hash = binascii.crc32(byte_array) #calculate crc32 of name by appending "\n"
index = name_hashes.index(hash)
order = name_hashes[index+2]
name_map[order] = author_id
print('Order %d' % order)
print("----------")
f.close()
dictionary_items = name_map.items()
sorted_items = sorted(dictionary_items)
for item in sorted_items:
source_dir = "./antioch/" + item[1] + '/layer'
print("COPY %s/*.dat ./" % source_dir )
So if we create a docker file with the above script, we will end up with something like below
FROM alpine:latest
COPY ./chroots/antioch-latest/AntiochOS ./AntiochOS
COPY ./antioch/b75ea3e81881c5d36261f64d467c7eb87cd694c85dd15df946601330f36763a4/layer/*.dat ./
COPY ./antioch/ea12384be264c32ec1db0986247a8d4b2231bf017742313c01b05a7e431d9c26/layer/*.dat ./
COPY ./antioch/4c33f90f25ea2ab1352efb77794ecc424883181cf8e6644946255738ac9f5dbd/layer/*.dat ./
COPY ./antioch/09e6fff53d6496d170aaa9bc88bd39e17c8e5c13ee9066935b089ab0312635ef/layer/*.dat ./
COPY ./antioch/e5254dec4c7d10c15e16b41994ca3cf0c5e2b2a56c9d4dc2ef053eeff24333ff/layer/*.dat ./
COPY ./antioch/7d643931f34d73776e9169551798e1c4ca3b4c37b730143e88171292dbe99264/layer/*.dat ./
COPY ./antioch/754ee87063ee108c1f939cd3a28980a03b700f3c3967df8058831edad2743fd7/layer/*.dat ./
COPY ./antioch/b5f502d32c018d6b2ee6a61f30306f9b46dad823ba503eea5b403951209fd59b/layer/*.dat ./
COPY ./antioch/81f28623cca429f9914e21790722d0351737f8ad3e823619a4f7019be72e2195/layer/*.dat ./
COPY ./antioch/76531a907cdecf03c8ac404d91cbcabd438a226161e621fab103a920600372a8/layer/*.dat ./
COPY ./antioch/6b4e128697aa0459a6caba2088f6f77efaaf29d407ec6b58939c9bc7814688ad/layer/*.dat ./
COPY ./antioch/bfefc1bdf8b980a525f58da1550b56daa67bae66b56e49b993fff139faa1472c/layer/*.dat ./
COPY ./antioch/1c5d28d6564aed0316526e8bb2d79a436b45530d2493967c8083fea2b2e518ce/layer/*.dat ./
COPY ./antioch/f9621328166de01de73b4044edb9030b3ad3d5dbc61c0b79e26f177e9123d184/layer/*.dat ./
COPY ./antioch/58da659c7d1c5a0c3447cb97cd6ffb12027c734bfba32de8b9b362475fe92fae/layer/*.dat ./
COPY ./antioch/9a31bad171ad7e8009fba41193d339271fc51f992b8d574c501cae1bfa6c3fe2/layer/*.dat ./
COPY ./antioch/49fb821d2bf6d6841ac7cf5005a6f18c4c76f417ac8a53d9e6b48154b5aa1e76/layer/*.dat ./
COPY ./antioch/fd8bf3c084c5dd42159f9654475f5861add943905d0ad1d3672f39e014757470/layer/*.dat ./
COPY ./antioch/a435765bcd8745561460979b270878a3e7c729fae46d9e878f4c2d42e5096a44/layer/*.dat ./
COPY ./antioch/cd27ad9a438a7eef05f5b5d99e2454225693e63aba29ce8553800fed23575040/layer/*.dat ./
COPY ./antioch/8e11477e79016a17e5cde00abc06523856a7db9104c0234803d30a81c50d2b71/layer/*.dat ./
COPY ./antioch/e1a9333f9eccfeae42acec6ac459b9025fe6097c065ffeefe5210867e1e2317d/layer/*.dat ./
COPY ./antioch/e6c2557dc0ff4173baee856cbc5641d5b19706ddb4368556fcdb046f36efd2e2/layer/*.dat ./
COPY ./antioch/fadf53f0ae11908b89dffc3123e662d31176b0bb047182bfec51845d1e81beb9/layer/*.dat ./
COPY ./antioch/303dfd1f7447a80322cc8a8677941da7116fbf0cea56e7d36a4f563c6f22e867/layer/*.dat ./
COPY ./antioch/f2ebdc667cbafc2725421d3c02babc957da2370fbd019a9e1993d8b0409f86dd/layer/*.dat ./
COPY ./antioch/2b363180ec5d5862b2a348db3069b51d79d4e7a277d5cf5e4afe2a54fc04730e/layer/*.dat ./
COPY ./antioch/25e171d6ac47c26159b26cd192a90d5d37e733eb16e68d3579df364908db30f2/layer/*.dat ./
COPY ./antioch/cfd7ddb31ce44bb24b373645876ac7ea372da1f3f31758f2321cc8f5b29884fb/layer/*.dat ./
COPY ./antioch/a2de31788db95838a986271665b958ac888d78559aa07e55d2a98fc3baecf6e6/layer/*.dat ./
Run the consult and you will get the flag. Five-Is-Right-Out@flare-on.com
Tips:
There is an easy way to debug this file with gdb.
- First, we create a docker image with basic alpine and the following packages.
gdb build-base nasm gcc
- Go to the folder with your files and build your image with
docker build -t flareon .
-
Start your docker container.
docker run --rm -it --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 23946:23946 flareon sh
- Start your gdb server
gdbserver localhost:23946 AntiochOS
- Attach your debugger to localhost:23946 and debug the file.
Flare-On 2021 Write-ups