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.


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": [

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) )
    quit_prompt(&v3, &v4);
    v1 = (__int16 *)&v3;
    if ( !(unsigned int)check_command((__int64)&v4, (__int64)&v3, 5LL) )
    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) )
      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?
  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 )
    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);
    while ( 1 )
      LOBYTE(v9) = v0;
      hFile = open_file(&v9, v1);
      v3 = hFile;
      if ( hFile >= 0 )
      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;
      *ptrBuffer++ ^= *(_BYTE *)ptrFile;
      ptrFile = (__int64 *)((char *)ptrFile + 1);
    while ( ptrBuffer != (unsigned __int8 *)&v12 );
  while ( (_BYTE)v0 != '{' );
  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;
  v6 = 0LL;
    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 = [

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('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)

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.


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

I am actively job-hunting and available
Interested? Feel free to reach