Flash CTF – Flag Appraisal

Reversing the Binary

Decompiling the binary, we see that the main function looks fairly simple.

bool FUN_0010128e(void)

{
  int iVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  char local_78 [104];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Welcome to my Pawn Shop, go ahead and show me the flag you want appraised: ");
  fgets(local_78,100,stdin);
  sVar2 = strcspn(local_78,"\n");
  local_78[sVar2] = '\0';
  sVar2 = strlen(local_78);
  FUN_00101199(local_78,sVar2 & 0xffffffff);
  iVar1 = strncmp(local_78,&DAT_00102058,0x25);
  if (iVar1 != 0) {
    puts("Unfortunately, your flag here looks to be a counterfeit.");
  }
  else {
    puts("Well good news, your flag looks to be authentic! Best I can do is $2.");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return iVar1 != 0;
}

All the function appears to do is take user input, run it through some hash or mangle function, and then compare it against a static value at DAT_00102058. It also indicates that it is checking exactly 37 bytes, so that will likely be the length of the final mangled value, and may be the length of the flag.

Looking at FUN_00101199, which we believe is a mangle function, shows the main meat of the challenge:

void FUN_00101199(long param_1,uint param_2)

{
  uint local_10;
  int local_c;
  
  local_10 = 0;
  for (local_c = 0; local_c < (int)(param_2 - 1); local_c = local_c + 2) {
    local_10 = local_10 * 0x21 ^
               *(char *)(param_1 + (long)local_c + 1) * 0x1fd ^ *(char *)(param_1 + local_c) * 0x101
    ;
    *(char *)(param_1 + local_c) = (char)local_10;
    *(char *)(param_1 + (long)local_c + 1) = (char)(local_10 >> 8);
  }
  if ((param_2 & 1) != 0) {
    *(byte *)(param_1 + (long)(int)param_2 + -1) =
         (char)local_10 * '!' ^ *(byte *)(param_1 + (long)(int)param_2 + -1);
  }
  return;
}

This function appears to go through the input and manipulates it two bytes at a time, along with some intermediate state value to prevent repeat two byte sequences from giving the same result. This is hard to read as is, so some cleaning up of placeholder variable names and refactoring into native C later, we’re left with the original code:

void mangle(char *input, int length) {
    unsigned int state = 0;
    for (int i = 0; i < length - 1; i += 2) {
        state = (input[i] * 257) ^ (input[i + 1] * 509) ^ (state * 33);
        input[i] = state & 0xff;
        input[i + 1] = (state >> 8) & 0xff;
    }
    if (length % 2 != 0) {
        state = (input[length - 1] * 257) ^ (state * 33);
        input[length - 1] = state & 0xff;
    }
}

Solve Script

Writing a quick script to reverse this function, and feeding it the 37 static bytes that are being compared against in the main program execution solves the challenge:

def unmangle(mangled, length):
    state = 0
    unmangled = ['\x00'] * length  # Initialize unmangled array with null characters

    # Process pairs of characters
    for i in range(0, length - 1, 2):
        current_state = (mangled[i] | (mangled[i + 1] << 8))  # Combine two bytes into one state
        original_state = current_state

        # Reverse the state calculation
        for j in range(256):
            for k in range(256):
                test_state = (j * 257) ^ (k * 509) ^ (state * 33)
                if test_state & 0xffff == original_state:
                    unmangled[i] = chr(j)
                    unmangled[i + 1] = chr(k)
                    break
            else:
                continue
            break

        # Update state for next iteration
        state = original_state
        print(original_state)

    # Handle the last character if the length is odd
    if length % 2 != 0:
        last_char = mangled[length - 1]
        for j in range(256):
            test_state = (j * 257) ^ (state * 33)
            if test_state & 0xff == last_char:
                unmangled[length - 1] = chr(j)
                break

    return ''.join(unmangled)

mangled = [0x9c, 0x85, 0xb5, 0x8d, 0x12, 0xa0, 0x9b, 0x10, 0xe8, 0x1f, 0x2b, 0xb3, 0xdb, 0x4a, 0x87, 0x1e, 0x39, 0xbd, 0x03, 0x32, 0xc6, 0xd0, 0x82, 0xdb, 0xcd, 0x46, 0x82, 0xa1, 0x6d, 0x09, 0x80, 0xe5, 0x6c, 0x7f, 0x6c, 0x82, 0x91]
length = len(mangled)
unmangled = unmangle(mangled, length)
print("Unmangled:", unmangled)

Flag: MetaCTF{c0un73rf3171ng_7h3_p4wn_$h0p}