TheLeopard65
Published on

TUCTF-2024 Reverse Challenges Writeup

AUTHORS
  • avatar
    NAME
    Yasir Mehmood
    TWITTER

REVERSE ENGINEERING

  1. MYSTERY-BOX
  2. SIMPLE-LOGIN
  3. CUSTOM-IMAGE-GENERATOR

1. MYSTERY-BOX

mystery_box_challenge.png

  1. I executed the it asked to either play a game or to exit!

mystery_box_initial.png

  1. I choose to play a game and it asked for a secret code.
  2. I enter some random words and it said “Incorrect secret code”.
  3. I opened up the file in Ghidra.
  4. Analyzed the file a bit.
  5. Found the Secret in the Assembly Section.

mystery_box_ghidra.png

  1. Submitted the secret code. Got the Flag.
  2. Submitted the Flag. Got the points.

mystery_box_solve.png

FLAG: TUCTF{Banana_Socks}

2. SIMPLE-LOGIN

simple_login_challenge.png

  1. The first time i downloaded the file. it didn’t execute (neither in Windows nor in WSL2 Kali Linux).
  2. So, I opened up the executable in Ghidra.
  3. And Started to figure out the functions as windows executables in Ghidra are same as stripped ELF in Ghidra.
  4. After wondering around a bit i figured, the function the entry() points to is for something else but the function we actually need to go to is inside the that function pointed to entry(). I renamed that function to Conversations() as it was just conversations.

/* WARNING: Function: _guard_dispatch_icall replaced with injection: guard_dispatch_icall */

int main(void){
  bool bVar1;
  undefined8 startup-lock_return_code;
  code **ppcVar2;
  ulonglong uVar3;
  longlong *plVar4;
  undefined8 uVar5;
  undefined8 *puVar6;
  uint *puVar7;
  ulonglong uVar8;
  int Final_Return_code;
  undefined8 unaff_RBX;
  undefined8 in_R9;

  Final_Return_code = (int)unaff_RBX;
  startup-lock_return_code = FUN_140001c1c(1);
  if ((char)startup-lock_return_code == '\0') {
    FUN_140001f38(7);
  } else {
    bVar1 = false;
    startup-lock_return_code = __scrt_acquire_startup_lock();
    Final_Return_code =
         (int)CONCAT71((int7)((ulonglong)unaff_RBX >> 8),(char)startup-lock_return_code);
    if (DAT_140004830 != 1) {
      if (DAT_140004830 == 0) {
        DAT_140004830 = 1;
        Final_Return_code = _initterm_e(&DAT_140003210,&DAT_140003228);
        if (Final_Return_code != 0) {
          return 0xff;
        }
        _initterm(&DAT_1400031f8,&DAT_140003208);
        DAT_140004830 = 2;
      } else {
        bVar1 = true;
      }
      __scrt_release_startup_lock((char)startup-lock_return_code);
      ppcVar2 = (code **)FUN_140001f20();
      if ((*ppcVar2 != (code *)0x0) &&
         (uVar3 = FUN_140001ce4((longlong)ppcVar2), (char)uVar3 != '\0')) {
        (**ppcVar2)(0,2);
      }
      plVar4 = (longlong *)FUN_140001f28();
      if ((*plVar4 != 0) && (uVar3 = FUN_140001ce4((longlong)plVar4), (char)uVar3 != '\0')) {
        _register_thread_local_exe_atexit_callback(*plVar4);
      }
      uVar5 = _get_initial_narrow_environment();
      puVar6 = (undefined8 *)__p___argv();
      startup-lock_return_code = *puVar6;
      puVar7 = (uint *)__p___argc();
      uVar8 = (ulonglong)*puVar7;
      Final_Return_code = Conversations(uVar8,startup-lock_return_code,uVar5,in_R9);                 // This line contains call to `Conversations()`
      uVar3 = some_final_checker();
      if ((char)uVar3 != '\0') {
        if (!bVar1) {
          _cexit();
        }
        __scrt_uninitialize_crt(CONCAT71((int7)(uVar8 >> 8),1),'\0');
        return Final_Return_code;
      }
      goto LAB_140001a40;
    }
  }
  FUN_140001f38(7);
LAB_140001a40:
                    /* WARNING: Subroutine does not return */
  exit(Final_Return_code);
}

  1. Here is the code for Conversations function. Inside this function i found 4 more functions called inside of it.
    1. print(): This is the custom function to print something.
    2. input_taker(): This is the custom function to Take input from the user.
    3. Comparer(): This function lead me the Username of the Admin "TheSuperSecureAdminWhoseSecretWillNeverBeGotten”
    4. First_Password_checker(): This function checks the first 5 char/bytes of the input against a predefined password.


void Conversations(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4){
  bool correct_bool;
  int iVar1;
  undefined7 answer;
  char *buffer_pointer;
  undefined *password_pointer;
  undefined storage_code [32];
  undefined *password_input_memory;
  undefined password_buffer [32];
  char input_buffer [56];
  ulonglong storage_fixer;

  storage_fixer = storage ^ (ulonglong)storage_code;
  print(s_Welcome_to_my_super_secure_login_140004040,param_2,param_3,param_4);
  print(s_If_you_are_so_confident,_you_may_140004090,param_2,param_3,param_4);
  buffer_pointer = input_buffer;
  input_taker(input_space_of_56,buffer_pointer,param_3,param_4);
  correct_bool = comparer(input_buffer);
  if ((int)CONCAT71(answer,correct_bool) == 0) {
    print(s_My_defenses_are_impenetrable._140004120,buffer_pointer,param_3,param_4);
  } else {
    password_input_memory = (undefined *)malloc(0x26);
    print(s_Okay_okay_fine,_but_what_is_it_y_140004140,buffer_pointer,param_3,param_4);
    input_taker(input_space_of-32,password_buffer,param_3,param_4);
    password_pointer = password_input_memory;
    iVar1 = first_password_checker((longlong)password_buffer,password_input_memory);
    if (iVar1 != 0) {
      print(s_I_can't_believe_you_were_able_to_140004190,password_pointer,param_3,param_4);
      print(password_input_memory,password_pointer,param_3,param_4);
    }
  }
  main_checker(storage_fixer ^ (ulonglong)storage_code);
  return;
}


bool comparer(char *param_1){
  int ret_val;
  ret_val = strcmp(param_1,s_TheSuperSecureAdminWhoseSecretWi_140004000);
  return ret_val == 0;
}

  1. The next Functions to move to would be First_Password_Checker(). This function checked if the first 5 characters of the input were ”ruint” if yes then it sat the first 5 characters of the Final-flag to TUCTF . It then called the Second_Password_checker() Function. Here is the Code for the First_Password_Checker() Function:

void first_password_checker(longlong input,undefined *final_flag){
  longlong i;
  char *ruint_string;
  char *buffer-pointer;
  undefined storage_code [32];
  int j;
  char buffer-12-char [12];
  ulonglong storage_lock;

  storage_lock = storage ^ (ulonglong)storage_code;
  ruint_string = ::ruint_string;
  buffer-pointer = buffer-12-char;
  for (i = 6; i != 0; i = i + -1) {
    *buffer-pointer = *ruint_string;
    ruint_string = ruint_string + 1;
    buffer-pointer = buffer-pointer + 1;
  }
  for (j = 0; j < 5; j = j + 1) {
    if (buffer-12-char[j] != *(char *)(input + j)) goto LAB_1400014fe;
  }
  *final_flag = 'T';
  final_flag[1] = 'U';
  final_flag[2] = 'C';
  final_flag[3] = 'T';
  final_flag[4] = 'F';
  Second_password_checker((char *)input,final_flag);
LAB_1400014fe:
  main_checker(storage_lock ^ (ulonglong)storage_code);
  return;
}

  1. The Second_Password_Checker() Function simply checked if after XORing the character in range from 6th to 13th with 2, will they be equal to some special variables, if yes then all good and move on to the next function which is Third_Password_checker() or if not move on to the main checker and exit the program. At this point my flag was TUCTF{running_. Here is the code for the Function for Second_Password_Checker():

void Second_password_checker(char *input,char *final_flag){
  bool XOR-return-code;
  undefined7 extraout_var;
  undefined storage_code [32];
  int i;
  int j;
  byte key_A [4];
  undefined local_1c;
  undefined local_1b;
  undefined local_1a;
  undefined local_19;
  byte local_18 [4];
  undefined local_14;
  undefined local_13;
  undefined local_12;
  undefined local_11;
  ulonglong storage_lock;

  storage_lock = storage ^ (ulonglong)storage_code;
  key_A[0] = 'Z';
  key_A[1] = ']';
  key_A[2] = 'A';
  key_A[3] = 'W';
  local_1c = 'B';
  local_1b = '@';
  local_1a = 'W';
  local_19 = 'A';
  local_18[0] = 26;
  local_18[1] = 26;
  local_18[2] = 29;
  local_18[3] = 11;
  local_14 = 25;
  local_13 = 28;
  local_12 = 2;
  local_11 = 44;
  for (i = 5; i < 13; i = i + 1) {
    j = i + -5;
    XOR-return-code = XOR_with_2_checker(input[i],key_A[j]);
    if ((int)CONCAT71(extraout_var,XOR-return-code) == 0) goto LAB_140001405;
    final_flag[i] = input[i] ^ local_18[j];
  }
  third_password_checker(input,final_flag);
LAB_140001405:
  main_checker(storage_lock ^ (ulonglong)storage_code);
  return;
}


bool XOR_with_2_checker(byte input,byte result){
  return (input ^ 0x32) == result;
}

  1. The Third_Password_Checker() Function was the trickiest of the three. It first performed an shifting operation on the user’s input. after that it checked if the resulting character is lowercase or not. if lowercase save the shifted char into the Final_flag if not lowercase, save the character itself in the Final_flag. It consisted of two sub-functions:
    1. Shifting_char_check(): This function performed a shifting operation on the user’s input to check if it matches some predetermined function.
    2. shift_if_lower_case_return_ascii_if_other(): This function checks if the shifted char is a lowercase letter or if it is something else. based on that result it changes the value in the Final_flag variable. Her is the code for the Third_password_checker() function:

void third_password_checker(char *input,char *final_flag){
  bool change-ret;
  undefined7 extraout_var;
  undefined auStack_78 [32];
  int char_counter;
  int i;
  int first_shift_ret;
  int secod_shift-ret;
  longlong value_holder;
  char key_A [4];
  undefined local_3c;
  undefined local_3b;
  undefined local_3a;
  undefined local_39;
  undefined local_38;
  undefined local_37;
  undefined local_36;
  undefined local_35;
  undefined local_34;
  char key_14-to-17 [4];
  undefined local_2c;
  undefined local_2b;
  undefined local_2a;
  undefined local_29;
  undefined local_28;
  undefined local_27;
  undefined local_26;
  undefined local_25;
  undefined local_24;
  undefined local_23;
  undefined local_22;
  undefined local_21;
  undefined local_20;
  undefined local_1f;
  undefined local_1e;
  undefined local_1d;
  undefined local_1c;
  undefined local_1b;
  undefined local_1a;
  undefined local_19;
  undefined local_18;
  undefined local_17;
  ulonglong local_10;

  local_10 = storage ^ (ulonglong)auStack_78;
  key_A[0] = 'r';
  key_A[1] = 'e';
  key_A[2] = 'i';
  key_A[3] = 'n';
  local_3c = 'g';
  local_3b = 'v';
  local_3a = 'b';
  local_39 = 'a';
  local_38 = 'o';
  local_37 = 'n';
  local_36 = 'e';
  local_35 = 't';
  local_34 = 'r';
  key_14-to-17[0] = 'g';
  key_14-to-17[1] = 'u';
  key_14-to-17[2] = 'e';
  key_14-to-17[3] = 'b';
  local_2c = 'h';
  local_2b = 't';
  local_2a = 'u';
  local_29 = '_';
  local_28 = 'g';
  local_27 = 'u';
  local_26 = 'r';
  local_25 = '_';
  local_24 = 'f';
  local_23 = 'h';
  local_22 = 'o';
  local_21 = 'e';
  local_20 = 'b';
  local_1f = 'h';
  local_1e = 'g';
  local_1d = 'v';
  local_1c = 'a';
  local_1b = 'r';
  local_1a = 'f';
  local_19 = '!';
  local_18 = '!';
  local_17 = '}';
  char_counter = 1;
  for (i = 1; i < 26; i = i + 2) {
    value_holder = (longlong)(i % 13);
    change-ret = shifting_char_check(input[i % 13 + 13],key_A[value_holder]);
    if ((int)CONCAT71(extraout_var,change-ret) == 0) break;
    first_shift_ret = shift_if_lower_case_return_ascii_if_other(key_14-to-17[char_counter % 26]);
    final_flag[char_counter % 26 + 13] = (char)first_shift_ret;
    char_counter = char_counter + 1;
    secod_shift-ret = shift_if_lower_case_return_ascii_if_other(key_14-to-17[char_counter % 26]);
    final_flag[char_counter % 26 + 13] = (char)secod_shift-ret;
    char_counter = char_counter + 1;
  }
  main_checker(local_10 ^ (ulonglong)auStack_78);
  return;
}


bool shifting_char_check(char input_char,char key_char){
  return (input_char + -84) % 26 + 97 == (int)key_char;
}


int shift_if_lower_case_return_ascii_if_other(char character){
  int return_value;
  if ((character < 'a') || ('z' < character)) {
    return_value = (int)character;
  } else {
    return_value = (character + -84) % 26 + 97;
  }
  return return_value;
}

  1. After solving through all that rubbish and banging my head into the wall for 3-4 hours. I got the flag.
FLAG: TUCTF{running_through_the_subroutines!!}

3. CUSTOM IMAGE GENERATOR

custom_image_generator_challenge.png

  1. They provided us with 2 files (one is the file encoder timg.py and the other is the encoded file flag.timg.
  2. We can view the file via eog or Windows photo. and event the timg file viewing function in the timg.py is incomplete.
  3. Here is the code for timg.py:
from PIL import Image
import numpy as np
import crc8

def main():
    inp = input("""
                Welcome to the TU Image Program
                It can convert images to TIMGs
                It will also display TIGMs

                [1] Convert Image to TIMG
                [2] Display TIMG

                """)
    match inp:
        case "1":
            conv()
        case "2":
            display() #TODO: Add
            '''
            # Textual troll face. Unicode encoding failed in Notion.
						 '''
        case _:
            return 0
    return 0

def conv():
    file = input("Enter the path to you image you want converted to a TIMG file:\n")
    out = input("Enter the path youd like to write the TIMG to:\n")
    img = Image.open(file)
    w,h = img.size
    write = [b'\x54',b'\x49',b'\x4D',b'\x47',b'\x00',b'\x01',b'\x00',b'\x02']
    for x in w.to_bytes(4):
        write.append(x.to_bytes(1))
    for y in h.to_bytes(4):
        write.append(y.to_bytes(1))
    write.append(b'\x52')
    write.append(b'\x55')
    write.append(b'\x42')
    write.append(b'\x59')

    for i in range(h):
        dat = [b'\x44',b'\x41',b'\x54',b'\x52']
        for j in range(w):
            dat.append(img.getpixel([j,i])[0].to_bytes(1))
        dat.append(getCheck(dat[4:]))
        for wa in dat:
            write.append(wa)

    for i in range(h):
        dat = [b'\x44',b'\x41',b'\x54',b'\x47']
        for j in range(w):
            dat.append(img.getpixel([j,i])[1].to_bytes(1))
        dat.append(getCheck(dat[4:]))
        for wa in dat:
            write.append(wa)

    for i in range(h):
        dat = [b'\x44',b'\x41',b'\x54',b'\x42'  ]
        for j in range(w):
            print(img.getpixel([j,i])[2].to_bytes(1))
            dat.append(img.getpixel([j,i])[2].to_bytes(1))
        print(dat)
        dat.append(getCheck(dat[4:]))
        for wa in dat:
            write.append(wa)

    write.append(b'\x44')
    write.append(b'\x41')
    write.append(b'\x54')
    write.append(b'\x45')

    with open(out,"ab") as f:
        for b in write:
            f.write(b)

    return 0

def getCheck(datr):
    dat = ''
    for w in datr:
        dat+=chr(int.from_bytes(w))
    print(datr      )
    print(dat.encode())
    return int.to_bytes(int(crc8.crc8(dat.encode()).hexdigest(),base=16),1)

if __name__=='__main__':
    main()

  • Here is the information about the flag.timg:
(timg-solve) ((timg-solve) kali@LEOPARD-PC)-[~/CTF-Events/TUCTF-2025/REV]$ ll flag.timg
-rw-r--r-- 1 kali kali 2336424 Dec  1 03:17 flag.timg
(timg-solve) ((timg-solve) kali@LEOPARD-PC)-[~/CTF-Events/TUCTF-2025/REV]$ file flag.timg
flag.timg: data
(timg-solve) ((timg-solve) kali@LEOPARD-PC)-[~/CTF-Events/TUCTF-2025/REV]$ exiftool flag.timg
ExifTool Version Number         : 13.00
File Name                       : flag.timg
Directory                       : .
File Size                       : 2.3 MB
File Modification Date/Time     : 2024:12:01 03:17:33+05:00
File Access Date/Time           : 2025:01:26 11:13:59+05:00
File Inode Change Date/Time     : 2025:01:26 11:13:13+05:00
File Permissions                : -rw-r--r--
Error                           : Unknown file type
(timg-solve) ((timg-solve) kali@LEOPARD-PC)-[~/CTF-Events/TUCTF-2025/REV]$

  1. After fiddling around a bit i wrote the following script to decode the file into either PNG or JPG:
from PIL import Image
import struct

def read_timg(file_path):
    with open(file_path, 'rb') as f:
        header = f.read(8)
        if header[:4] != b'TIMG':
            raise ValueError("Not a valid TIMG file")

        width = struct.unpack('>I', f.read(4))[0]
        height = struct.unpack('>I', f.read(4))[0]
        f.read(3)
        img = Image.new('RGB', (width, height))
        for i in range(height):
            # Read Red channel
            f.read(4)  # Skip 'DATA' marker
            red_data = f.read(width)
            # Read Green channel
            f.read(4)  # Skip 'DATA' marker
            green_data = f.read(width)
            # Read Blue channel
            f.read(4)  # Skip 'DATA' marker
            blue_data = f.read(width)

            # Set pixel values
            for j in range(width):
                r = red_data[j]
                g = green_data[j]
                b = blue_data[j]
                img.putpixel((j, i), (r, g, b))
        return img

def save_image(img, output_path):
    img.save(output_path)

def main():
    input_path = input("Enter the path to the .timg file:\n")
    output_path = input("Enter the path to save the output image (PNG/JPG):\n")
    img = read_timg(input_path)
    save_image(img, output_path)
    print(f"Image saved to {output_path}")

if __name__ == '__main__':
    main()

  1. After using the script you will get an image like this:

custom_image_generator_solve.jpg

  1. I squinted my eyes and got the flag.
FLAG: TUCTF{f1le_structur3_r3v3rs1ng_1s_fun}