Cette épreuve est constituée d'un exécutable livré avec SDL.dll
Quand on lance l'exécutable, on nous propose de jouer à un clone du jeu Snake :
En reversant l'application avec IDA, on localise le main en 0x402A98 grâce aux appels de l'API de SDL servant à initialiser la librairie ainsi que la fenêtre SDL :
SDL_Init(33); v9 = SDL_SetVideoMode(336, 240, 0, 1073741825); SDL_WM_SetCaption("Special Pwnium Game", 0);
On trouve plus loin dans le main la boucle de jeu :
while (1) { if (!SDL_PollEvent(&v11)) { // Affichage [...] } if (v11 == 12) goto LABEL_17; if (v11 == 2) break; LABEL_6: // Gestion des inputs sub_4074EC(v4, (int)&v11); }
On peut se référer au code source de SDL pour comprendre les codes renvoyés par SDL_PollEvent :
SDL_KEYDOWN = 2 SDL_QUIT = 12
Dans la fonction 0x4074EC utilisée pour gérer les inputs, des opérations sont effectuées en fonction de l'input choisi. Beaucoup de code est utilisé pour faire avancer notre snake, mais on remarque aussi que selon l'input, on écrit dans une structure de 4000 octets allouée en début de fonction.
switch (v12) // key { [...] case 276: *(_BYTE *)(*(_DWORD *)(a1 + 16) + 54016) |= 4u; break; case 275: *(_BYTE *)(*(_DWORD *)(a1 + 16) + 54016) |= 8u; break; [...] } [...] v3 = *(_BYTE *)(*(_DWORD *)(a1 + 16) + 54016); if ( (_BYTE)v3 != -1 ) { v4 = dword_414024; *(_DWORD *)(dword_414024 + 4 * dword_414020) = v3; v5 = dword_414020 + 1; dword_414020 = v5; dword_414BE0 = 0; }
Juste à la suite, on remarque une cascade de if imbriqué qui lit notre structure. En connaissant l'état de la structure attendue, on peut reconstruire le tableau de bytes qui est écrit :
if ( v5 > 0 ) { v7 = v4 + 28; v6 = 0; do { if ( *(_DWORD *)(v7 - 28) == 254 ) { if ( *(_DWORD *)(v7 - 24) == 254 ) { if ( *(_DWORD *)(v7 - 20) == 251 ) { if ( *(_DWORD *)(v7 - 16) == 254 ) { if ( *(_DWORD *)(v7 - 12) == 253 ) { if ( *(_DWORD *)(v7 - 8) == 247 ) { if ( *(_DWORD *)(v7 - 4) == 253 ) { if ( *(_DWORD *)v7 == 251 ) { if ( *(_DWORD *)(v7 + 4) == 247 ) { *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15108) = 71; // 71 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15109) = *(_BYTE *)(v7 - 24) + 81; // 254 + 81 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15110) = 12048 / *(_DWORD *)(v7 - 20); // 12048 / 251 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15111) = *(_DWORD *)(v7 - 8) ^ 0xB3; // 247 ^ 0xB3 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15112) = 23845 / *(_DWORD *)v7; // 23845 / 251 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15113) = *(_BYTE *)(v7 + 4) + 83; // 253 + 83 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15114) = 48; // 48 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15115) = *(_BYTE *)(v7 - 4) + 64 - *(_BYTE *)v7; // 253 + 64 - 251 *(_BYTE *)(*(_DWORD *)(a1 + 16) + 15116) = *(_BYTE *)(v7 + 4) ^ 0xD6; // 247 ^ 0xD6 } } } } } } } } } ++v6; dword_414BE0 = v6; v7 += 4; } while ( v5 != v6 ); }
On peut écrire un bout de code pour le décodage :
unsigned char buffer[1024] = {0}; buffer[0] = 71; // 71 buffer[1] = 254 + 81; // 254 + 81 buffer[2] = 12048 / 251; // 12048 / 251 buffer[3] = 247 ^ 0xB3; // 247 ^ 0xB3 buffer[4] = 23845 / 251; // 23845 / 251 buffer[5] = 253 + 83; // 253 + 83 buffer[6] = 48; // 48 buffer[7] = 253 + 64 - 251; // 253 + 64 - 251 buffer[8] = 247 ^ 0xD6; // 247 ^ 0xD6 printf("buffer = %s\n", buffer);
Qui nous donne :
buffer = GO0D_P0B!
Ce flag ne validant pas (?), on reconnaît néanmoins la similitude avec le flag GO0D_J0B! qui lui permet de valider l'épreuve.