DigitalOverdose: Time

Reading time ~3 minutes

Great CTF with a lot of variety of challenges plus great infrastructure stability. Nice job team. This challenge I solved using a backup idea I had when my original solution wasn’t working. Details below.

Time - Reverse Engineering - 125 points

This challenge reads:

Perseverance Arrives at Mars!
You are now the master of time!

23 solves

With this challenge comes a file called time which is a Linux ELF binary:

$ file time
time: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically
linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3376dc1a92787bdc410ff6d2b627d1483812e448, 
for GNU/Linux 3.2.0, not stripped

A quick check with strings shows no low hanging fruit so further investigation is required. I fire up Ghidra and check out the main() function in the decompiler:

int main(void)

{
  int flagmatch;
  undefined aes_ctx [192];
  char *ciphertext;
  undefined8 key;
  undefined8 keytime;

  time_t timet;
  int i;
  
  keytime = 0;
  uStack48 = 0;
  uStack40 = 0;
  key = 0;
  uStack64 = 0;
  ciphertext = (char *)0x11283c52c7f437a8;
  uStack96 = 0xda92120d98ec7669;
  uStack88 = 0x7bd2fafa02bc48c8;
  uStack80 = 0x8fb0cf6982db479c;
  timet = time((time_t *)0x0);
  snprintf((char *)&keytime,0x11,"%li",timet);
  key = keytime;
  uStack64 = uStack48;
  AES_init_ctx_iv(aes_ctx,&key,iv);
  AES_CBC_decrypt_buffer(aes_ctx,&ciphertext,0x20);
  flagmatch = strncmp("DO{",(char *)&ciphertext,3);
  if (flagmatch == 0) {
    printf("Flag: ");
    for (i = 0; i < 0x1b; i = i + 1) {
      putchar((int)*(char *)((long)&ciphertext + (long)i));
    }
    putchar(L'\n');
  }
  else {
    puts("Decryption failed!");
  }
  return 0;
}

So this is nice and easy, it’s doing the following:

  • Getting the current time and using the string representation as a key.
  • Using a constant initialization vector (IV) and that a key made of the string representation of the time, decrypting a constant ciphertext.
  • If the Ciphertext begins with DO{ then the decryption succeeded and the flag is printed.

Then all we need to decrypt this is the components in the binary and to implement the algorithm in Python or something and count backwards until the payload decrypts?

Well that seems logical but after exhausting a large amount of the possible keyspace with a Python script it wasn’t working.

So out comes my backup solution.

First I wrote a quick shared object to replace the libc time() function. I need a way for it to take input from the environment though because I didn’t want to fiddle with my actual clock or anything. So I wrote this stub:

#include<time.h>
#include<stdlib.h>

time_t time(time_t *__timer) {
    return atoi(getenv("TIMETHING"));
}

It just gets a string from TIMETHING in the local environment and returns that as an integer to the callee.

Next I wrote a quick C program to test it actually works:

#include<stdio.h>
#include<stdlib.h>  
#include<time.h>  

int main(void) { 
  time_t ttime = time(0);   
  printf("%li",ttime);
}

Then I run a quick test to check this is doing what I want:

$ gcc tt.c -shared -o tt.so
$ gcc test.c -o test
$ ./test
1633947242
$ export TIMETHING=13371337
$ LD_PRELOAD=./tt.so ./test
13371337

Great. We can, by LD_PRELOAD force time() to be whatever we want. All we need to do now is run time backwards (by decrementing TIMETHING) until we get the flag.

To speed things up I re-read the clue Perseverance Arrives at Mars!. That was pretty useful because Perseverance landed on Mars in February 2021. So that seems like a good place to start. I wrote the following Python to wrap this all up:

import subprocess
import os

os.environ["LD_PRELOAD"] = "./tt.so"

twodays = 86400*2
perseverance = 1613606400  # 2021-02-18

for t in range(perseverance+twodays, 0, -1):
    os.environ["TIMETHING"] = str(t)
    out = subprocess.check_output("./time", env=os.environ)
    if b'Decryption failed' not in out:
        print("flag: %s" % out)

After about 5 minutes runtime (subprocess calls are slower than doing the attack directly against AES) we got:

$ ./solve.py 
flag: b'Flag: DO{V3ry_n1Ce_t1MInG5_!1}\x08\x08\x08\n'

I’m still not sure why my Python code did not solve it. I probably got the endianness of one of the components wrong or something but this backup method solved it while I was trying to get that working so that was nice to not need to debug the python anymore.

Interviewing in Tech: Security Engineer & Security Analyst

Landing a job as a security engineer or analyst at a tech company is a significant feat. It requires not only technical acumen but also s...… Continue reading

BSides Sydney 2023 Writeups

Published on November 24, 2023

DUCTF 2023 Writeups

Published on August 31, 2023