Etape 1 : reconnaissance du terrain.
Il s’agit d’un challenge plutôt de type pawn au final selon moi.
On se connecte en SSH comme proposé et on voit centré sur l’écran le message suivant :
┌ FCSC ACCESS TERMINAL ────────────────────────────────────────┐
│You've been waiting 7 seconds for a flag to appear... │
│Maybe your TTY is too small? 🦕 │
│ │
│uint16_t X = 80, Y = 24, t = 7 │
└ Press q to exit ─────────────────────────────────────────────┘
OK, cela semble donc tourner autour de dimensions d’écran…
Etape 2 : Analyse.
Comme proposé, on va augmenter la taille de notre TTY. Mais je suppose que l’auteur du challenge voudra une taille d’écran grande bien plus grande que ce qu’un terminal peut montrer, même un xterm avec la police unreadable.
Donc on va plutôt partir sur la façon dont SSH fonctionne et comment un redimensionnement de fenêtre de terminal est transmis via SSH au PTY en face.
Du côté du client, un redimensionnement de fenêtre se traduit par un
envoi du signal SIGWINCH au client SSH qui peut récupérer la nouvelle
dimension via un appel ioctl(). Le protocole SSH prévoit d’envoyer au
serveur en face un message contenant les nouvelles dimensions de la
fenêtre.
Ces informations se trouvent assez facilement en quelques recherches sur GOOGLE. Par exemple cela remonte les threads de discussion suivants :
- https://www.ietf.org/proceedings/65/RFCs/rfc4254.txt
- https://unix.stackexchange.com/questions/207782/how-are-terminal-length-and-width-forwarded-over-ssh-and-telnet
- https://stackoverflow.com/questions/19157202/how-do-terminal-size-changes-get-sent-to-command-line-applications-though-ssh-or
Mon approche est donc la suivante : je vais recompiler un client SSH depuis les sources de OPENSSH et je vais hardcoder dedans des valeurs arbitraires de dimension de fenêtre pour voir comment se comporte le truc en face.
Je prends le code source de la même version que mon poste DEBIAN mais cela n’a pas beaucoup d’importance je pense. Bref je prends le code source suivant : https://github.com/openssh/openssh-portable/releases/tag/V_10_0_P2
Je patche le code pour mettre une taille à 256 x 256.
--- clientloop.c 2026-07-27 21:01:44.902073921 +0200
+++ clientloop.c 2026-07-29 22:35:02.988680810 +0200
@@ -2700,6 +2700,18 @@
/* Store window size in the packet. */
if (ioctl(in_fd, TIOCGWINSZ, &ws) == -1)
memset(&ws, 0, sizeof(ws));
+//
+// struct winsize {
+// unsigned short ws_row; /* rows, in characters */
+// unsigned short ws_col; /* columns, in character */
+// unsigned short ws_xpixel; /* horizontal size, pixels */
+// unsigned short ws_ypixel; /* vertical size, pixels */
+// };
+//
+ws.ws_col = 256 ;
+ws.ws_row = 256 ;
+ws.ws_xpixel = ws.ws_col ; // quick and dirty
+ws.ws_ypixel = ws.ws_row ; // quick and dirty
channel_request_start(ssh, id, "pty-req", 1);
client_expect_confirm(ssh, id, "PTY allocation", CONFIRM_TTY);
Cela ne change rien : fenêtre trop petite.
Je patche le code initialement pour mettre une taille à 512 x 512.
--- clientloop.c 2026-07-27 21:01:44.902073921 +0200
+++ clientloop.c 2026-07-29 22:35:02.988680810 +0200
@@ -2700,6 +2700,18 @@
/* Store window size in the packet. */
if (ioctl(in_fd, TIOCGWINSZ, &ws) == -1)
memset(&ws, 0, sizeof(ws));
+//
+// struct winsize {
+// unsigned short ws_row; /* rows, in characters */
+// unsigned short ws_col; /* columns, in character */
+// unsigned short ws_xpixel; /* horizontal size, pixels */
+// unsigned short ws_ypixel; /* vertical size, pixels */
+// };
+//
+ws.ws_col = 512 ;
+ws.ws_row = 512 ;
+ws.ws_xpixel = ws.ws_col ; // quick and dirty
+ws.ws_ypixel = ws.ws_row ; // quick and dirty
channel_request_start(ssh, id, "pty-req", 1);
client_expect_confirm(ssh, id, "PTY allocation", CONFIRM_TTY);
Ah cela mord. Cette fois-ci, on a un autre affichage :
┌ FCSC ACCESS TERMINAL ────────────────────────────────────────┐
│So much screen space, you seem serious about getting the flag!│
│Pass the following C condition to print the flag: │
│X + Y + t == 42 && X > 300 && Y > 200 │
│uint16_t X = 512, Y = 512, t = 0 │
└ Press q to exit ─────────────────────────────────────────────┘
Etape 3 : Résolution.
L’affichage propose un mini challenge à résoudre.
X + Y + t == 42 && X > 300 && Y > 200 │
uint16_t X
uint16_t Y
uint16_t t
C’est assez facile à résoudre : il suffit que l’addition fasse un overflow sur 16 bits et cela fonctionne.
Par exemple, si X = 0x8000, Y = 0x8000, t = 42 alors on a X + Y + t = 0x1002a et 0x1002a & 0xffff = 0x2a = 42.
Donc je repatche le code du client en changeant les dimensions à 0x8000 x 0x8000 et il suffira d’attendre 42 secondes pour voir ce qui se passe (on s’attend à ce que le flag s’affiche).
--- clientloop.c 2026-07-27 21:01:44.902073921 +0200
+++ clientloop.c 2026-07-29 22:35:02.988680810 +0200
@@ -2700,6 +2700,18 @@
/* Store window size in the packet. */
if (ioctl(in_fd, TIOCGWINSZ, &ws) == -1)
memset(&ws, 0, sizeof(ws));
+//
+// struct winsize {
+// unsigned short ws_row; /* rows, in characters */
+// unsigned short ws_col; /* columns, in character */
+// unsigned short ws_xpixel; /* horizontal size, pixels */
+// unsigned short ws_ypixel; /* vertical size, pixels */
+// };
+//
+ws.ws_col = 0x8000 ;
+ws.ws_row = 0x8000 ;
+ws.ws_xpixel = ws.ws_col ; // quick and dirty
+ws.ws_ypixel = ws.ws_row ; // quick and dirty
channel_request_start(ssh, id, "pty-req", 1);
client_expect_confirm(ssh, id, "PTY allocation", CONFIRM_TTY);
On lance le programme, on attend 42 secondes (c’est long) et on voit le flag s’afficher :
Le flag doit donc être :
FCSC{2b1150bfd7ad93f72a184764bcae0f51521fcaf796b0997288}
Bingo. Ce flag se valide.
QED.
🐒