Reverse engineering, Windows internals, x86 magic, low level programming, and everything else I feel like writing about.

CrySyS SecChallenge 2020: Master boot record

It’s a beginner-friendly challenge. Don’t worry if you are not a binary-ninja, I hope it’s a good point to start to learn reverse engineering. Just check the webpage and show me that, you understand the basics of how binaries work in OS level and in userland, too.

Sorry for linux3 name, nothing connected to linux… but I don’t really know what I am doing. Can you find my flag?

Categories

Reverse Engineering, C language, Stack Overflow

Files

  • linux3.iso (provided as downloadable in an online x86 emulator environment)
  • Unfortunately I cannot provide the full environment for this, as the server code wasn’t given

Solution

Let’s download the ISO and open it up in our favorite archiver. We can notice MAIN.FLP with a size of an MBR (512 bytes) pretty easily. Loading it into IDA as 16-bit 8086 code confirms our suspicion. After doing the usual chores of marking functions / strings and naming stuff, we can see this control flow graph:

MBR control flow graph

By following the edges into the keyboard reading part, we can easily deduce the following:

  • dl holds count of entered characters
  • cl is 0

By following the input character (al) from the reading interrupt, we see this happening:

  1. al is checked for \r. If true and dl = 54 the password is correct. This must be our length.

  2. al = al ^ 0x21 ^ 0xFF.

  3. al is checked against 0xFF. For this to be true al would need to be 0xDE, which is unlikely. This must be an always false test, therefore we ignore the true branch.

  4. al = bl = al ^ 0xFF + 5. Note how xoring by 0xFF cancels the previous xor with 0xFF.

  5. al = bEncryptedData[dl], previous value is trashed, therefore we only need to follow bl for char ^ 0x21 + 5 and al for bEncryptedData[count].

  6. Save ax to cx, test for 0x3E == 0x62, then restore ax. This is always false, so we ignore the other outgoing edge.

  7. al and bl is compared - jackpot! If this is true, we advance dl.

Based on this we can easily write a C++ program that simply decrypts bEncryptedData:

constexpr unsigned char bEncryptedData[55] = {
  0x6E, 0x6D, 0x74, 0x7A, 0x06, 0x6B, 0x53, 0x53, 0x4A, 0x06, 0x50, 0x53,
  0x48, 0x12, 0x06, 0x47, 0x4E, 0x49, 0x47, 0x4F, 0x06, 0x13, 0x56, 0x45,
  0x4B, 0x49, 0x57, 0x13, 0x4E, 0x15, 0x4A, 0x4A, 0x17, 0x54, 0x58, 0x16,
  0x59, 0x5A, 0x17, 0x5B, 0x15, 0x5A, 0x4E, 0x52, 0x16, 0x54, 0x4B, 0x57,
  0x5A, 0x58, 0x15, 0x54, 0x4B, 0x57, 0x00
};


int main()
{
  char data[55];
  for (auto i = 0; i < 55; ++i)
    data[i] = (char)((bEncryptedData[i] - 5) ^ 0x21);
  data[54] = 0;
  printf("%s", data);
  return 0;
}

Output:

HINT Good job, check /pages/h1dd3nr0ut3w1thl0ngstr1ngs

Visiting the page we’re greeted with the following:

//Use the pw GET parameter for flag
char in_pw[20];
char check_pw[20];
for (char* mypointer = check_pw; mypointer < check_pw + 20; mypointer++)
  *mypointer = (rand() % 255) + 1;
strcpy(in_pw, threadlocalhrq.req_body[i].value);
if(memcmp(check_pw, in_pw, 20) == 0)
  response=sdscat(response,"<p>cd20{REDACTED}</p>");
else
  response=sdscat(response,"<p>The pw is wrong!</p>");

This is a simple buffer overflow, a long enough in_pw will overwrite check_pw. After trying some lengths increasing from 40 I eventually got it right:

You try pw: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

cd20{REDACTED}