0

I'm trying to understand the following snippet code from a CTF binary. It is part of an ELF 32-bit and I decompiled it with Ghidra.

undefined4 check_serial(char *code)

{
  size_t sVar1;
  undefined4 uVar2;
  int in_GS_OFFSET;
  int iterator;
  byte correct_code [16];
  int local_10;

  local_10 = *(int *)(in_GS_OFFSET + 0x14);
  correct_code[0] = 0x27;
  correct_code[1] = 0x31;
  correct_code[2] = 0x20;
  correct_code[3] = 0x1c;
  correct_code[4] = 0x1a;
  correct_code[5] = 8;
  correct_code[6] = 4;
  correct_code[7] = 0x18;
  correct_code[8] = 0x5c;
  correct_code[9] = 0;
  correct_code[10] = 0x31;
  correct_code[11] = 0x18;
  correct_code[12] = 0x44;
  correct_code[13] = 3;
  correct_code[14] = 0x17;
  correct_code[15] = 0xe;
  sVar1 = strlen(code);
  if (sVar1 == 0x10) {
    iterator = 0;
    while (iterator < 0x10) {
      if ((uint)(byte)((char)iterator + 100U ^ correct_code[iterator]) != (int)code[iterator]) {
        uVar2 = 0;
        goto LAB_08048aca;
      }
      iterator = iterator + 1;
    }
    uVar2 = 1;
  }
  else {
    uVar2 = 0;
  }

I've already renamed some variables to make the code more clear. I have to find the correct serial that will give me the flag. However, I don't understand the following instruction:

if ((uint)(byte)((char)iterator + 100U ^ correct_code[iterator]) != (int)code[iterator])

In the first iteration of the loop, it takes the iterator (which is 0) and adds 100 to it. This makes a total of 100 and performs a XOR operation with correct_code[0] which is 0x27 or 39 in decimal. The result of the XOR between 100 and 39 is 43.

If I perform all the iterations of the loop, I get the following serial:

43 84 70 123 114 97 110 115 48 109 95 119 52 114 101 125

However, the binary says this is not the correct serial. I tried with and without spaces.

What I am not understanding about this code snippet?

1

Hehe, you're definitely close and on the right track; however, when you say this:

However, the binary says this is not the correct serial. I tried with and without spaces.

This tells me that you're a tad confused and entering it wrong. I have two things:

  1. Reminder, letters are seen as numbers when you're at the low level.
  2. You're mixing hexadecimal with decimal which will throw your answer off

If you print your values as characters, you may see something pretty interesting...

Also, I'm going to break it down a little bit so you can see the big picture juuuust in case you're new to this and would like to learn something new today...Lets focus on the if statement. It's saying that if the length of your code isn't 0x10, or 16, it will assign uVar2 to 0. If it is the length, it'll be assigned to 1. This tells me that undefined4 check_serial(char *code) is actually a boolean function and can be rewritten to bool check_serial(char *code).

Now if the length of your serial is 0x10, it'll go to this code block (cleaned up a bit):

if (codeLen == 0x10) 
{
    iterator = 0;
    while (iterator < 0x10) 
    {
        if (iterator + 100U ^ correct_code[iterator]) != code[iterator]) 
        {
            return false;
        }
        iterator++;
    }
}

If the operation inside that if statement doesn't match, it'll return false. You can get an idea of what it's checking if you get all the values inside correct_code and do that operation yourself.

byte test[] = {
    0x27, 0x31, 0x20, 0x1c, 0x1a, 0x8, 0x4, 0x18, 0x5c, 0x0, 0x31, 0x18, 0x44, 0x3, 0x17, 0xe
};

int main()
{
    int i = 0;
    const int thing= 100;

    // These are your values in decimal
    while (i < 0x10)
    {
        std::cout << (i + thing ^ test[i]) << " ";
        i++;
    }
    std::cout << std::endl;

    i = 0;
    // These are your values in hexadecimal
    while (i < 0x10)
    {
        std::cout << std::hex << (i + thing ^ test[i]) << " ";
        i++;
    }
    std::cout << std::endl;

    i = 0;
    // Lastly, your values as alphanumeric characters
    while (i < 0x10)
    {
        std::cout << char(i + thing ^ test[i]);
        i++;
    }

    return 0;
}

This will get you this:

Release>ConsoleApplication6.exe
67 84 70 123 114 97 110 115 48 109 95 119 52 114 101 125
43 54 46 7b 72 61 6e 73 30 6d 5f 77 34 72 65 7d
CTF{rans0m_w4re}

Anyhow, hope this clears up what you were stuck on! I would recommend going over it again and trying it out on a different ctf binary or something on crackme/rootme/whatever.

Lastly, here is how I would clean up this code block:

bool check_serial(char *code)
{
    int iterator = 0;
    byte correct_code[16] = {
        0x27, 0x31, 0x20, 0x1c, 0x1a, 0x8, 0x4, 0x18, 
        0x5c, 0x0, 0x31, 0x18, 0x44, 0x3, 0x17, 0xe
    };

    codeLen = strlen(code);
    if (codeLen == 0x10) 
    {
        while (iterator < 0x10) 
        {
            if ((iterator + 100U ^ correct_code[iterator]) != code[iterator]) 
            {
                return false;
            }
            iterator++;
        }
        return true;
    }
    return false;
}
| improve this answer | |
  • Thanks for your answer! It was very well explained. I'm just not getting the part were the numbers are converted to the final string. Why is 67, 84, 70, etc. equal CTF{rans0m_w4re}? – Raptor Feb 21 at 10:15
  • Sure thing. Those numbers are the decimal representation of your flag. When you're viewing anything in a debugger/disassembler, you'll see everything in numbers. For example, AAAA will be 0x41 0x41 0x41 0x41. BBBB will be 66 66 66 66, if that makes sense – Jon Feb 21 at 11:55
  • But there are more numbers in your ConsoleApplication6.exe output than letters of the code. I do get what you mean about the letters in disassembler. – Raptor Feb 23 at 22:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.