Comment on page

investigation_encoded_2

Problem

We have recovered a binary and 1 file: image01. See what you can make of it. Its also found in /problems/investigation-encoded-2_6_74ebdbfd3962c221df51c8ce5141b275 on the shell server. NOTE: The flag is not in the normal picoCTF{XXX} format.

Solution

  1. 1.
    Running the binary produces: Error: file ./flag.txt not found
  2. 2.
    Lets create a flag with echo picoctf > flag.txt and try running again which just causes a Segmentation fault
  3. 3.
    Decompile the binary file using Ghidra (cheat sheet):
    undefined8 main(void)
    {
    long lVar1;
    size_t sVar2;
    undefined4 local_18;
    int local_14;
    FILE *local_10;
    badChars = '\0';
    local_10 = fopen("flag.txt","r");
    if (local_10 == (FILE *)0x0) {
    fwrite("Error: file ./flag.txt not found\n",1,0x21,stderr);
    /* WARNING: Subroutine does not return */
    exit(1);
    }
    flag_size = 0;
    fseek(local_10,0,2);
    lVar1 = ftell(local_10);
    flag_size = (int)lVar1;
    fseek(local_10,0,0);
    login();
    if (0xfffe < flag_size) {
    fwrite("Error, file bigger than 65535\n",1,0x1e,stderr);
    /* WARNING: Subroutine does not return */
    exit(1);
    }
    flag = malloc((long)flag_size);
    sVar2 = fread(flag,1,(long)flag_size,local_10);
    local_14 = (int)sVar2;
    if (local_14 < 1) {
    /* WARNING: Subroutine does not return */
    exit(0);
    }
    local_18 = 0;
    flag_index = &local_18;
    output = fopen("output","w");
    buffChar = 0;
    remain = 7;
    fclose(local_10);
    encode();
    fclose(output);
    if (badChars == '\x01') {
    fwrite("Invalid Characters in flag.txt\n./output is corrupted\n",1,0x35,stderr);
    }
    else {
    fwrite("I\'m Done, check file ./output\n",1,0x1e,stderr);
    }
    return 0;
    }
  4. 4.
    encode() function decompiled:
    void encode(void)
    {
    byte bVar1;
    int iVar2;
    int local_10;
    char local_9;
    while (*flag_index < flag_size) {
    local_9 = lower((ulong)(uint)(int)*(char *)(*flag_index + flag));
    if (local_9 == ' ') {
    local_9 = -0x7b;
    }
    else {
    if (('/' < local_9) && (local_9 < ':')) {
    local_9 = local_9 + 'K';
    }
    }
    local_9 = local_9 + -0x61;
    if ((local_9 < '\0') || ('$' < local_9)) {
    badChars = 1;
    }
    if (local_9 != '$') {
    iVar2 = ((int)local_9 + 0x12) % 0x24;
    bVar1 = (byte)(iVar2 >> 0x1f);
    local_9 = ((byte)iVar2 ^ bVar1) - bVar1;
    }
    local_10 = *(int *)(indexTable + (long)(int)local_9 * 4);
    iVar2 = *(int *)(indexTable + (long)((int)local_9 + 1) * 4);
    while (local_10 < iVar2) {
    getValue();
    save();
    local_10 = local_10 + 1;
    }
    *flag_index = *flag_index + 1;
    }
    while (remain != 7) {
    save(0);
    }
    return;
    }
  5. 5.
    getValue() function decompiled:
    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);
    }
  6. 6.
    save() function decompiled:
    void save(byte param_1)
    {
    buffChar = buffChar | param_1;
    if (remain == 0) {
    remain = 7;
    fputc((int)(char)buffChar,output);
    buffChar = '\0';
    }
    else {
    buffChar = buffChar * '\x02';
    remain = remain + -1;
    }
    return;
    }
  7. 7.
    Differences from previous challenge, investigation_encoded_1:
    • The matrix array was replaced with indexTable
    • The digits 0-9 (inclusive) are valid for input (the flag can include numbers now). The while loop through the flag adds K (75) if the current character is between '\' and '$', which are the ascii values for the digits 0-9. Adding K is important because a (97) is subtracted from each character, regardless whether its a number or not. 48 is the ascii value for 0. Adding 75 yields 123 and subtracting the 97 gives 26. This means that from 0-25 are the letters a-z and then starting at 26 are the numbers 0-9. Since 97 is always subtracted, only a-z, {, |, }, ~, and any exceptions (0-9 and space) are accepted. However, the characters {, |, }, ~ would get the same encoding as 0, 1, 2, and 3, so they can be ignored.
    • The program performs some kind of manipulation on the characters before using them as array indices:
      iVar2 = ((int)local_9 + 0x12) % 0x24;
      bVar1 = (byte)(iVar2 >> 0x1f);
      local_9 = ((byte)iVar2 ^ bVar1) - bVar1;
    • There is a login function which will crash the program (This was the cause of the Segmentation fault earlier). It can be skipped over during debugging.
  8. 8.
    Get indexTable and secret using radare2:
    [0x00000ae0]> bf obj.secret
    [0x00000ae0]> pc @ obj.secret
    #define _BUFFER_SIZE 71
    const uint8_t buffer[_BUFFER_SIZE] = {
    0x8b, 0xaa, 0x2e, 0xee, 0xe8, 0xbb, 0xae, 0x8e, 0xbb, 0xae,
    0x3a, 0xee, 0x8e, 0xee, 0xa8, 0xee, 0xae, 0xe3, 0xaa, 0xe3,
    0xae, 0xbb, 0x8b, 0xae, 0xb8, 0xea, 0xae, 0x2e, 0xba, 0x2e,
    0xae, 0x8a, 0xee, 0xa3, 0xab, 0xa3, 0xbb, 0xbb, 0x8b, 0xbb,
    0xb8, 0xae, 0xee, 0x2a, 0xee, 0x2e, 0x2a, 0xb8, 0xaa, 0x8e,
    0xaa, 0x3b, 0xaa, 0x3b, 0xba, 0x8e, 0xa8, 0xeb, 0xa3, 0xa8,
    0xaa, 0x28, 0xbb, 0xb8, 0xae, 0x2a, 0xe2, 0xee, 0x3a, 0xb8,
    0x00
    };
    [0x00000ae0]> bf obj.indexTable
    [0x00000ae0]> pcw @ obj.indexTable
    #define _BUFFER_SIZE 38
    const uint32_t buffer[_BUFFER_SIZE] = {
    0x00000000U, 0x00000004U, 0x00000012U, 0x00000028U, 0x0000003cU,
    0x00000052U, 0x00000064U, 0x00000078U, 0x0000008eU, 0x0000009eU,
    0x000000b4U, 0x000000c8U, 0x000000daU, 0x000000eaU, 0x000000fcU,
    0x0000010eU, 0x0000011eU, 0x00000134U, 0x00000148U, 0x0000015aU,
    0x0000016aU, 0x00000172U, 0x00000180U, 0x0000018cU, 0x0000019aU,
    0x000001aaU, 0x000001bcU, 0x000001c8U, 0x000001d6U, 0x000001e0U,
    0x000001eaU, 0x000001f0U, 0x00000200U, 0x0000020aU, 0x00000216U,
    0x00000222U, 0x00000230U, 0x00000234U
    };
    [0x00000ae0]> pc @ obj.indexTable
    #define _BUFFER_SIZE 152
    const uint8_t buffer[_BUFFER_SIZE] = {
    0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x12, 0x00,
    0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
    0x52, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x78, 0x00,
    0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00,
    0xb4, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00, 0xda, 0x00,
    0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00,
    0x0e, 0x01, 0x00, 0x00, 0x1e, 0x01, 0x00, 0x00, 0x34, 0x01,
    0x00, 0x00, 0x48, 0x01, 0x00, 0x00, 0x5a, 0x01, 0x00, 0x00,
    0x6a, 0x01, 0x00, 0x00, 0x72, 0x01, 0x00, 0x00, 0x80, 0x01,
    0x00, 0x00, 0x8c, 0x01, 0x00, 0x00, 0x9a, 0x01, 0x00, 0x00,
    0xaa, 0x01, 0x00, 0x00, 0xbc, 0x01, 0x00, 0x00, 0xc8, 0x01,
    0x00, 0x00, 0xd6, 0x01, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,
    0xea, 0x01, 0x00, 0x00, 0xf0, 0x01, 0x00, 0x00, 0x00, 0x02,
    0x00, 0x00, 0x0a, 0x02, 0x00, 0x00, 0x16, 0x02, 0x00, 0x00,
    0x22, 0x02, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, 0x34, 0x02,
    0x00, 0x00
    };
  9. 9.
    Find the sequences that correspond with each letter using the decode.py script:
    a: 101011101110111000
    b: 1010101110111000
    c: 10111000
    d: 10101010111000
    e: 101010101000
    f: 11101010101000
    g: 1110111010101000
    h: 111011101110101000
    i: 111010101000
    j: 11101011101000
    k: 1110101000
    l: 1010101000
    m: 101000
    n: 1011101110111000
    o: 1010111000
    p: 101010111000
    q: 101110111000
    r: 11101010111000
    s: 1000
    t: 10111010101000
    u: 1011101110111011101000
    v: 10111011101011101000
    w: 1110101110111010111000
    x: 111010111011101000
    y: 11101110111010101000
    z: 1110111010101110111000
    0: 1110101010111000
    1: 1110101110101110111000
    2: 10111010111010111000
    3: 111010101010111000
    4: 1011101011101000
    5: 101110101011101000
    6: 101011101110101000
    7: 1110101011101000
    8: 1110111011101110111000
    9: 10111011101110111000
    Python dictionary: {'101011101110111000': 'a', '1010101110111000': 'b', '10111000': 'c', '10101010111000': 'd', '101010101000': 'e', '11101010101000': 'f', '1110111010101000': 'g', '111011101110101000': 'h', '111010101000': 'i', '11101011101000': 'j', '1110101000': 'k', '1010101000': 'l', '101000': 'm', '1011101110111000': 'n', '1010111000': 'o', '101010111000': 'p', '101110111000': 'q', '11101010111000': 'r', '1000': 's', '10111010101000': 't', '1011101110111011101000': 'u', '10111011101011101000': 'v', '1110101110111010111000': 'w', '111010111011101000': 'x', '11101110111010101000': 'y', '1110111010101110111000': 'z', '1110101010111000': '0', '1110101110101110111000': '1', '10111010111010111000': '2', '111010101010111000': '3', '1011101011101000': '4', '101110101011101000': '5', '101011101110101000': '6', '1110101011101000': '7', '1110111011101110111000': '8', '10111011101110111000': '9'}
    Unlike the previous script in investigation_encoded_1, this script makes use of the C word (4 byte) representation of the matrix/indexTable variable instead of the 1 byte representation. To handle this, the * 4 was removed from index and end. The * 4 effectively selected every 4th value, so removing that and only keeping every 4th value is an alternative way to solve this challenge instead of manually updating the 1 byte representation of indexTable as was done in the previous challenge.
  10. 10.
    Run solve.py to get Flag: t1m3f1i3500000000000098a9a51. The solve.py script is the exact same as in investigation_encoded_1, except the decoded_dict and flag_encoded were replaced.

Flag

t1m3f1i3500000000000098a9a51