In this article I’ll be discussing the TSA Cyber Champion Competition, and the challenges I faced during the competition. especially the interesting RE challenges that I encountered.

alt text

Introduction

TSA Cyber Champion is a national competition held by Kominfo supported by CISCO

Chall Introduction

Given a x86_64 ELF binary, when we execute it, it will ask for the FLAG, and i think we need to input the correct flag to get the flag. If not the program will exit with some error message. So, we can start static analysis on the binary, and see what the program does.

__int64 __fastcall main(int a1, const char **a2, char **a3)
{
  printf("Enter the flag: ");
  __isoc99_scanf("%s", s);
  if ( strlen(s) == 27 )
  {
    ...
  }
  else
  {
    puts("Invalid flag length :(");
    return 1LL;
  }
}

We can see that the input length must be 27, and if it is not, the program will exit. Next, we need to see what the program does if the input length is correct.

if ( strlen(s) == 27 )
  {
    for ( i = 0; ; ++i )
    {
      v4 = i;
      if ( v4 >= strlen(s) )
        break;
      (*(&funcs_179C + i))(s[i]);
    }
    return 0LL;
  }
.data:00000000000080E0 funcs_179C      dq offset sub_17C4      ; DATA XREF: main+96↑o
int __fastcall sub_17C4(char a1)
{
  int result; // eax
  char *v2; // rax
  __int16 v3; // [rsp+16h] [rbp-Ah] BYREF
  unsigned int v4; // [rsp+18h] [rbp-8h]
  unsigned int v5; // [rsp+1Ch] [rbp-4h]

  v3 = 28587;
  v5 = 0;
  v4 = 0;
  while ( v5 <= 1 )
  {
    // do some operations
    *(&v3 + v5++) = ++v4;
  }
  result = a1;
  if ( v3 == a1 )
  {
    v2 = md5(&v3);
    return sub_1414(".c4ca4238a0b923820dcc509a6f75849b", v2);
  }
  return result;
}

From the above, the program will iterate through the input string, and call the function at funcs_179C and pointing to sub_17C4 with the current character as the first argument. The function at sub_17C4 will do some operation and check if the cur character is equal to the generated character. After that it will create md5 for the char. Next it will call the function at sub_1414 with the md5 hash as the first argument and the md5 hash of the current character as the second argument.

int __fastcall sub_1413(const char *a1, const char *md5_input_char)
{
  int result; // eax
  char *file_name; // rax
  char v4; // r12
  unsigned __int64 v5; // rbx
  size_t v6; // rax
  _DWORD v7[4]; // [rsp+10h] [rbp-E0h] BYREF
  __int64 v8; // [rsp+20h] [rbp-D0h]
  __off_t offset; // [rsp+28h] [rbp-C8h]
  size_t size; // [rsp+30h] [rbp-C0h]
  _BYTE buf[40]; // [rsp+50h] [rbp-A0h] BYREF
  __off_t v12; // [rsp+78h] [rbp-78h]
  unsigned __int16 v13; // [rsp+8Ch] [rbp-64h]
  unsigned __int16 v14; // [rsp+8Eh] [rbp-62h]
  void *addr; // [rsp+90h] [rbp-60h]
  unsigned __int64 v16; // [rsp+98h] [rbp-58h]
  size_t v17; // [rsp+A0h] [rbp-50h]
  unsigned __int64 v18; // [rsp+A8h] [rbp-48h]
  char *s1; // [rsp+B0h] [rbp-40h]
  void *ptr; // [rsp+B8h] [rbp-38h]
  __int64 v21; // [rsp+C0h] [rbp-30h]
  int fd; // [rsp+C8h] [rbp-28h]
  int j; // [rsp+CCh] [rbp-24h]
  size_t len; // [rsp+D0h] [rbp-20h]
  int i; // [rsp+DCh] [rbp-14h]

  result = open(dest, 0);
  fd = result;
  if ( result != -1 )
  {
    file_name = __xpg_basename(dest);
    v21 = sub_1356(file_name);
    read(fd, buf, 0x40uLL);                    
    lseek(fd, (v14 << 6) + v12, 0);            
    read(fd, v7, 0x40uLL);                     
    ptr = malloc(size);
    lseek(fd, offset, 0);
    read(fd, ptr, size);
    lseek(fd, v12, 0);
    for ( i = 0; i < v13; ++i )
    {
      read(fd, v7, 0x40uLL);
      s1 = ptr + v7[0];
      if ( !strcmp(s1, a1) )
      {
        v18 = v8 + v21;
        v17 = size;
        v16 = sysconf(30);
        addr = (v18 - v18 % v16);
        len = v18 % v16 + v17;
        if ( len % v16 )
          len += v16 - len % v16;
        mprotect(addr, len, 7);
        for ( j = 0; j < v17; ++j )
        {
          v4 = *(j + v18);
          v5 = j;
          v6 = strlen(md5_input_char);
          *(j + v18) = md5_input_char[v5 % v6] ^ v4;
        }
        mprotect(addr, len, 5);
        break;
      }
    }
    free(ptr);
    return close(fd);
  }
  return result;
}

Using GPT this is the point of this function:

  • Opens a file and processes its header.
  • Searches for a specific string (a1) within the file data.
  • If it finds the string, it calculates an address, changes memory permissions, and then XOR-encrypts the content at the address using the md5_input_char string.
  • After transforming the data, it resets memory protections, frees memory, and closes the file.

After that, we still confuse with this binary does, so we need to dynamically analyze the binary. Here im using gdb to debug this challenge.

alt text

Set breakpoint to the 0x0000000000001A12 cmp edx, eax on sub_17C4 function, and we can see that the program will compare the generated character with the current character. If it is equal, it will generate the md5 hash of the generated character.

Try to debug the program with ni then we can see that it return to the main function and pass to the next character on call rdx instruction. But the next function address is different from the previous function address. It try to access on offset 0x1a37 alt text alt text

Notice that the segment is the md5 hash of the previous parameter that used in the sub_1413 function. Remember that the summary of the sub_1413 function is to open a file, search for a specific string, and then XOR the content of the file with the md5 hash of the current character. So, we can conclude that the program will decrypt the segment in sub_1413 function.

Solution

The solution is to decrypt for each segment in the binary using the md5 hash of the correct character.

But the problem is i dont know how to reverse the correct character. So i try to make a solver using gdb api to solve this challenge. Here is the solver that i made:

import gdb


def disass():
    return gdb.execute('x/200i $rip', to_string=True)

global flag
flag = []

def find_and_break_on_cmp():
    disassembled_code = disass()
    rip = int(gdb.parse_and_eval('$rip'))
    
    # Iterate over the disassembled instructions to find 'cmp    edx,eax'
    for line in disassembled_code.split('\n'):
        if 'cmp    edx,eax' in line:
            # Extract the address from the line
            parts = line.split()
            address = int(parts[0].rstrip(':'), 16)
            print(f'Breakpoint at {hex(address)} (cmp    edx,eax) found')
            gdb.execute(f'break *{hex(address)}')
            gdb.execute('continue')
            rdx = int(gdb.parse_and_eval('$rdx'))
            gdb.execute(f'set $rax = {rdx}')
            flag.append(chr(rdx))
            print(gdb.parse_and_eval('$rax'))
            print(''.join(flag))
            # input('Continue?')
            return
    
    print('No cmp    edx,eax instruction found in the current disassembly range.')

# Clean up
gdb.execute('file ./chall')
rawinput = 'TSA{' + 'A' * (27 - 4)
gdb.execute('break-rva 0x179C')
gdb.execute(f'run <<< {rawinput}')
gdb.execute('continue')
gdb.execute('si')

for i in range(26):
    find_and_break_on_cmp() #S 1
    gdb.execute('continue')
    gdb.execute('si')

So, the solve will set the breakpoint on the cmp edx, eax instruction, and set the rax == rdx then append the character to the flag. After that, the program will continue to the next character and do the same thing until the flag is complete.

My solver kinda bad because of my skill-ish on gdb scripting, but it works and i got the flag thanks god alhamdulillah 😭🙏.