Investigation_encoded_1

Forensics

WriteUp: https://github.com/Dvd848/CTFs/blob/master/2019_picoCTF/investigation_encoded_1.md

A Linux binary file and an output containing the encoded flag is provided.

The flag is encoded using bit shifting and values in two arrays.   Each letter in the alphabet is encoded into a unique sequence of bits and the number of bits is different. 

Executable code – using Ghidra to decompile the binary.

undefined8 main(void)

{
  long lVar1;
  size_t sVar2;
  undefined4 local_18;
  int local_14;
  FILE *flag_file;
 
  flag_file = fopen(“flag.txt”,”r”);
  if (flag_file == (FILE *)0x0) {
    fwrite(“./flag.txt not found\n”,1,0×15,stderr);
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  flag_size = 0;
  fseek(flag_file,0,2);
  lVar1 = ftell(flag_file);
  flag_size = (int)lVar1;
  fseek(flag_file,0,0);
  if (0xfffe < flag_size) {
    fwrite(“Error, file bigger that 65535\n”,1,0x1e,stderr);
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  flag = malloc((long)flag_size);   <- flag stored in memory
  sVar2 = fread(flag,1,(long)flag_size,flag_file);
  local_14 = (int)sVar2;
  if (local_14 < 1) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  local_18 = 0;
  flag_index = &local_18;  <- global variable keeps track of current character in flag
  output = fopen(“output”,”w”);
  buffChar = 0;  <- global variable holding current character
  remain = 7;      <- global variable to keep track of bits processed
  fclose(flag_file);
  encode();
  fclose(output);
  fwrite(“I\’m Done, check ./output\n”,1,0×19,stderr);
  return 0;
}

*———————————————————

void encode(void)

{
  char cVar1;
  int iVar2;
  int local_10;
  char local_9;
 
  while( true ) {
    if (flag_size <= *flag_index) {
      while (remain != 7) {
        save(0);
      }
      return;
    }
    cVar1 = isValid((int)*(char *)(*flag_index + flag));  <- check for lower case, upper case or space
    if (cVar1 != ‘\x01’) break;
    local_9 = lower();
    if (local_9 == ‘ ‘) {
      local_9 = ‘{‘;
    }
    local_10 = *(int *)(matrix + (long)(local_9 + -0x61) * 8 + 4);  <- start value from matrix array
    iVar2 = local_10 + *(int *)(matrix + (long)(local_9 + -0x61) * 8);  <- end value from matrix array
    while (local_10 < iVar2) {
      getValue();
      save();  
      local_10 = local_10 + 1;
    }
    *flag_index = *flag_index + 1;  <- move to the next character in the flag
  }
  fwrite(“Error, I don\’t know why I crashed\n”,1,0×22,stderr);
                    /* WARNING: Subroutine does not return */
  exit(1);
}

*——————————————————–

uint lower(byte param_1)

{
  uint uVar1;
 
  if (((char)param_1 < ‘A’) || (‘Z’ < (char)param_1)) {
    uVar1 = (uint)param_1;
  }
  else {
    uVar1 = param_1 + 0x20;
  }
  return uVar1;
}
*——————————————————————

uint getValue(int param_1)

{
  byte bVar1;
  int iVar2;
 
  iVar2 = param_1;
  if (param_1 < 0) {
    iVar2 = param_1 + 7;
  }
  bVar1 = (byte)(param_1 >> 0x37);
  return (int)(uint)(byte)secret[iVar2 >> 3] >>
         (7 – (((char)param_1 + (bVar1 >> 5) & 7) – (bVar1 >> 5)) & 0x1f) & 1;   <-return next bit value
}
*——————————————————————

void save(byte param_1)

{
  buffChar = buffChar | param_1;   <- add the current bit to the lsb of the current 8 bits
  if (remain == 0) {  <- all 8 bits of the current character complete
    remain = 7;
    fputc((int)(char)buffChar,output);  <-output the current char to the file
    buffChar = ‘\0’;
  }
  else {
    buffChar = buffChar * ‘\x02’;  <-shift the bit 1 position to the left
    remain = remain + -1;    <-move to the next bit position
  }
  return;
}

*——————————————————————-

undefined8 isValid(char param_1)

{
  undefined8 uVar1;
 
  if ((param_1 < ‘a’) || (‘z’ < param_1)) {
    if ((param_1 < ‘A’) || (‘Z’ < param_1)) {
      if (param_1 == ‘ ‘) {
        uVar1 = 1;   <- value to return if the character is a space
      }
      else {
        uVar1 = 0;
      }
    }
    else {
      uVar1 = 1;   <- value to return if the character is upper case
    }
  }
  else {
    uVar1 = 1;   <- value to return if the character is a lower case
  }
  return uVar1;
}

Contents of Output file – using a Hex editor

Arrays – from Ghidra

Secret[]:

 

Matrix[]:

 

Solution:

Create a c program to output the lookup table (dictionary) –

#include
#include
#include

// Definitions from Ghidra
typedef unsigned char    byte;
typedef unsigned int     uint;
typedef unsigned long    ulong;

// r2: pc 216 @ obj.matrix
const uint8_t matrix[] = {
  0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00,
  0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
  0x14, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x22, 0x00,
  0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x0c, 0x00,
  0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
  0x48, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x52, 0x00,
  0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
  0x0c, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x0c, 0x00,
  0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
  0x80, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x8a, 0x00,
  0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00,
  0x0e, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x10, 0x00,
  0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
  0xbe, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xc8, 0x00,
  0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00,
  0x0a, 0x00, 0x00, 0x00, 0xd6, 0x00, 0x00, 0x00, 0x0c, 0x00,
  0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
  0xec, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xf8, 0x00,
  0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x01, 0x00, 0x00,
  0x0e, 0x00, 0x00, 0x00, 0x16, 0x01, 0x00, 0x00, 0x04, 0x00,
  0x00, 0x00, 0x24, 0x01, 0x00, 0x00
};

const uint8_t secret[] = {
  0xb8, 0xea, 0x8e, 0xba, 0x3a, 0x88, 0xae, 0x8e, 0xe8, 0xaa,
  0x28, 0xbb, 0xb8, 0xeb, 0x8b, 0xa8, 0xee, 0x3a, 0x3b, 0xb8,
  0xbb, 0xa3, 0xba, 0xe2, 0xe8, 0xa8, 0xe2, 0xb8, 0xab, 0x8b,
  0xb8, 0xea, 0xe3, 0xae, 0xe3, 0xba, 0x80
};

// getValue is the same as the decompiled version.
ulong getValue(int param_1)
{
  byte bVar1;
  int iVar2;
 
  iVar2 = param_1;
  if (param_1 < 0) {
    iVar2 = param_1 + 7;
  }
  bVar1 = (byte)(param_1 >> 0x37);
  return (ulong)((int)(uint)(byte)secret[iVar2 >> 3] >>
                 (7 – (((char)param_1 + (bVar1 >> 5) & 7) – (bVar1 >> 5)) & 0x1f) & 1);
}

// Encode is the same as the decompiled version with fixed labels.
void encode(char c)
{
    int end;
    ulong uVar1;
    int current_index;
    
    printf(“%c: “, c);
    
    if (c == ‘ ‘)
    {
        c = ‘{‘;
    }

    current_index = *(int *)(matrix + (long)((int)c + -‘a’) * 8 + 4);
    end = current_index + *(int *)(matrix + (long)((int)c + -‘a’) * 8);
    while (current_index < end) {
        uVar1 = getValue(current_index);
        printf(“%d”, uVar1);
        current_index = current_index + 1;
    }
    printf(“\n”);
}

//The main function calls encode for lower case characters only
int main(int argc, char* argv[])
{
    char c;
    for (c = ‘a’; c <= ‘z’; c++)
    {
        encode(c);
    }
    encode(‘ ‘);
    return 0;
}

Output:

The letter substitutions for the binary is revealed-

└─$ ./dict   
a: 10111000
b: 111010101000
c: 11101011101000
d: 1110101000
e: 1000
f: 101011101000
g: 111011101000
h: 1010101000
i: 101000
j: 1011101110111000
k: 111010111000
l: 101110101000
m: 1110111000
n: 11101000
o: 11101110111000
p: 10111011101000
q: 1110111010111000
r: 1011101000
s: 10101000
t: 111000
u: 1010111000
v: 101010111000
w: 101110111000
x: 11101010111000
y: 1110101110111000
z: 11101110101000
 : 0000

Decode:

(i) Manual

Convert hex values in Output file to binary and do search and replace.

Hex: 8E 8E BA 3B B8 EA 23 A8 A3 B8 AB 8B A2 A8 BB B8 EB 8E A3 BA 8E A0

Binary: 10001110100011101011101000111011101110001110101000100011101010001010001110111000101010111000101110100010101010001011101110111000111010111000111010100011101110101000111010100000

Flag:

encodedimvrhrkdzd

(ii) Python Script

from pwn import *

p = process(“./dict”)
dict_output = p.recvall().rstrip()

encoding_dict = {}
for line in dict_output.split(“\n”):
    line = line.rstrip()
    char, encoding = line.split(“: “)
    encoding_dict[encoding] = char

with open(“output”, “rb”) as f:
    data = f.read()
    bin_data = bits_str(data)
    log.info(“Binary data:\n{}”.format(bin_data))
    res = “”
    while bin_data:
        for k in encoding_dict:
            if bin_data.startswith(k):
                res += encoding_dict[k]
                bin_data = bin_data[len(k):]
    log.success(“Output: {}”.format(res))

Flag:

encodedimvrhjkdzd