CS2110 Reverse Engineering

This is not an official NUS module. It is an event organized and hosted by NUS Greyhats

crackme1

int __fastcall main(int argc, const char **argv, const char **envp)
{
  unsigned int seed; // [rsp+0h] [rbp-10h] BYREF
  int v5; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v6; // [rsp+8h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  printf("Whats the secret key?\n>> ");
  __isoc99_scanf("%d", &seed);
  v5 = do_stuff(&seed);
  srand(seed);
  puts("Here's your flag. If you got the right key, the flag would output correctly!");
  get_flag();
  if ( !v5 )
    puts("You did not get the right key, unfortunately. Try again!");
  return 0;
}

The program prompts the user for an integer, performs some transformations on it via do_stuff(), then uses the value as the seed to srand(). The flag is printed using get_flag(), which relies on rand().

If the original input does not satisfies the condition inside do_stuff(), the program prints a fail message.

Zooming into the function (do_stuff()):

Working the math backwards gives: 67.24979457682826

└─$ grey{cr4ck3d_an0th3r_one,nice!}

crackme

The program checks for the following condition: 13 * input == -559155996

But you can’t simply divide -559155996 by 13 because this value is stored in a 32-bit DWORD, and the number -559155996 doesn’t represent a true mathematical negative in this context. It’s actually the result of signed integer overflow.

To solve it properly, we must reinterpret -559155996 as an unsigned 32-bit value. Once we convert it to its unsigned form, then we can divide that value by 13 to recover the correct input.

└─$ grey{cr4ck3d_an0th3r_one,nice!}

puzzle

The program runs a Wordle game by selecting a random word from an encoded word list, decoding it, and then checking the player’s input against it.

  • func1: selects a random word from the list and deciphers it

  • func2 / func3: validate user input

  • func4: prints invalid

  • func5: generates the flag when the guess is correct

We can set a breakpoint right after the routine computes the target word and inspect the stack for the recovered word, then simply entered that value.

└─$ grey{cr4ck3d_an0th3r_one,nice!}

antidbg

The programs runs a password checker that uses the password as the key to decrypt the encrypted flag. The checker() function handles the validation of the password and this function is mapped dynamically at runtime.

Additionally, the binary includes anti-debugging checks that print "huh?" if a debugger is detected.

To bypass the debugger check, I simply patched the Zero Flag after the cmp instruction to 1.

Now we can obtain the mapped checker() function by tracing the address returned from the mapping routine.

└─$ grey{ur3_qu1t3_g00d_4h}

bomb

The program expects three integers, and checks each one against a different function:

  • Layer 1 : always return 227

  • Layer 2 : Fibonacci with n = 21

  • Layer 3 : Sieve of Eratosthenes with n = 1337

└─$ grey{k33p_t41k1ng_4nd_n0b0dy_expl0d35!}

packed

The program consists of two layers:

  1. Mantle Layer : Memory-mapped blob thats XORed against a 4-byte key

  2. Inner Layer : Flag checker

From the hint in the challenge, we can easily derive the XOR key 2110, which decrypts the memory‑mapped blob into a valid ELF.

After entering the key, we can set a breakpoint at ((void (*)(void))(addr + 4352))() to step into the decrypted code and inspect the Inner Layer logic. This inner function turns out to be a simple flag checker that validates the input by comparing it against a permuted version of a hard‑coded string. Reversing the permutation get us the flag:

└─$ grey{unp4ck1ng_grandmaster!}

Last updated