Knight’s secret Link to heading

This first challenge was not a typical PWN challenge but instead a python jail.

We connect to the remote server provided and see this:

KCTF205_PWN_KS_1

Tip
Using rlwrap with netcat gives you a much better terminal experience by enabling features like command history and line editing, which are not available by default in netcat.

Our goal will be to retrieve information contained in the CONFIG dictionary.

After quick search about pyjails, i found working payload on pyjailbreaker github repo

Using .__init__.__globals__ as a payload, i was able to see the content of CONFIG dictionary

KCTF205_PWN_KS_3

With these information, we can retrieve our flag using __init__.__globals__[CONFIG][KEY]

KCTF205_PWN_KS_4

FLAG : KCTF{c0ngRaT5_Kn1GHT_Y0U_g07_THE_secreT}

Knight bank Link to heading

For this challenge, we are given a connection to a remote server and file which is the executable ran by the remote server.

KCTF205_PWN_KB_1

It’s a pretty simple program that ask user to type the amount of money he want to withdraw.

KCTF205_PWN_KB_2

By doing some testing, we quickly found the solution for this challenge of the local program. KCTF205_PWN_KB_2

Since we are curious, lets see what exactly happens by opening up the binary in ghidra.

There is the main function:

undefined8 main(void)

{
  undefined8 main(void)

{
  int iVar1;
  undefined8 uVar2;
  uint withdraw_amount;
  uint balance;
  
  balance = 1000;
  puts("Welcome to the Knight Bank!");
  fflush(stdout);
  printf("Your current balance is: %u\n",(ulong)balance);
  fflush(stdout);
  printf("Enter the amount you want to withdraw: ");
  fflush(stdout);
  iVar1 = __isoc99_scanf(&DAT_004020a0,&withdraw_amount);
  if (iVar1 == 1) {
    if (withdraw_amount < 0xf4241) {
      balance = balance - withdraw_amount;
      printf("You withdrew %u. Your new balance is %u.\n",(ulong)withdraw_amount,(ulong)balance);
      fflush(stdout);
      if (balance < 0xf4241) {
        puts("Better luck next time!");
        fflush(stdout);
      }
      else {
        win_prize();
      }
      uVar2 = 0;
    }
    else {
      puts("Error: You cannot withdraw more than 1,000,000 at a time.");
      fflush(stdout);
      uVar2 = 1;
    }
  }
  else {
    puts("Invalid input. Exiting.");
    fflush(stdout);
    uVar2 = 1;
  }
  return uVar2;
}
}

The issue here is that the program uses unsigned integer variables, which can only hold non-negative numbers. However, the program doesn’t properly check the user’s input, which means someone could enter a value that causes the subtraction to result into negative value. But here’s the catch—since unsigned variables can’t store negative numbers, they don’t just break. Instead, they “wrap around” to the maximum value for that type.

For example, if we’re dealing with a 32-bit unsigned integer, the maximum value is 2^32=4294967296. So if the balance is 1000 and someone tries to withdraw 1001, the subtraction (1000 - 1001) would normally give -1. But with unsigned integers, it wraps around, and the result becomes 4294967295 (4294967296 - 1) instead of an error.

This creates an integer underflow vulnerability, where the program ends up with a huge, incorrect value instead of properly handling the situation.

KCTF205_PWN_KB_2

FLAG : KCTF{W0W_KNIGHT_y0U_ARE_R1cH_}