# S4ur0n Christmas CTF 2024 Writeup ![GfcBhssWEAE5Y3S](https://hackmd.io/_uploads/HJNPJgI8Jl.jpg) **Autores:** - Juan Wilches (MadLies) - Alvaro Torres (4LV4R0T) Estaremos haciendo revisión de los retos del CTF de Navidad de S4ur0n 2024, explicando paso por paso como llegamos a resolverlo por parte del equipo Uqbar. ### Reto #1: Tragamonedas Para el primer reto del CTF nos encontramos con un clásico juego tragamonedas, en donde ingresamos el número de cookies y la apuesta. Aquí el truco estaba simplemente en ingresar "bastantes" cookies, que para este caso serían las monedas, y eventualmente ganaríamos, para darnos el link a la siguiente fase. ![2025-01-03_21-50](https://hackmd.io/_uploads/rkIaX7UIkg.png) ![2025-01-03_21-50](https://hackmd.io/_uploads/H1UJ4m8Lyx.png) ![image](https://hackmd.io/_uploads/B1fDEmIIke.png) ### Reto #2: Clave privada En este reto se nos da la siguiente imagen: ![2025-01-03_22-07](https://hackmd.io/_uploads/SkId8QLIJe.png) Lo interesante está en lo que muestra el compu de Santa, que parece ser una clave privada ssh, y en el gestor de ventanas se ve el comando para usarla, revelando host y usuario: ![image](https://hackmd.io/_uploads/H1LSDmI8kl.png) ![image](https://hackmd.io/_uploads/HyNeOQ8Iyx.png) Para desgracia de nuestros ojos, tuvimos que copiar la clave privada letra por letra, tarea complicada dada la similitud de algunos caracteres (1-l, k-K, 5-s, etc). Luego de copiar toda la clave que se nos es mostrada en la imagen, hicimos un par de revisiones corrigiendo caracteres erroneos, pero al intentar usar la clave no funcionaba debido a la longitud y chequeo de la misma, por lo que todo indicaba que la clave estaba incompleta, y donde se ven puntos y un par de mensajes, debería de estar el resto de la clave. Después de buscar por internet retos similares o documentación al respecto, me encontré con una máquina de Hack The Box, [Response](https://app.hackthebox.com/machines/468), Linux de dificultad Insane donde toca hacer algo similar, nos dan una parte de la clave privada, con la diferencia que en ese reto se tiene la clave pública. Sin embargo la teoría es la misma y eso me fue muy útil para buscar retos similares y llegué a este par de artículos donde nuestra situación es la misma: * https://blog.cryptohack.org/twitter-secrets * https://c15c01337.medium.com/the-art-of-cryptographic-recovery-decrypting-with-an-incomplete-private-key-deddb704834b Ya teniendo estos recursos, primero replicamos la resolución del reto mostrado en el artículo para tener un mayor entendimiento de la solución, y luego hicimos los cálculos de offset, creamos un collab para ir sacando los valores numéricos que requería el script y lo ejecutamos, para de esta forma obtener toda la clave privada en texto claro. ~~~python from Crypto.Util.number import isPrime e = 65537 q = 0xe781ed2c4a257b1a0c39ba0ccdf00ed81ab6c5121e758b81154cfb0f2f8d46cf0365f5a4e2e86f7ab602706c2ac48bc84a8f3e726dc58fa90965eb19c51d63c4f7010bb8aab746d5c7e785c3bdbebe090f5d952c32a0734565b86e965e69d4c696834894a56b11240b8a1bd6e4822e6555c174f291bfb514df002ef67b82ab195f1fb67880c0f10e3288909621d87d730f14078639034ff90cb887cf0543c88552236515cb0948b06edfd7232a2cb9e37a5cb019df4cf517e1035be592ad3100da643e37f5b2525e8c0f2e77150b3a1bc007e0fc81ec5933713bad691e52100019e03420aad2895abbc3d059c086a20de025f840c1e98c7460bd8e4b5b5b774f dp = 0x91b5b9d3287055ad70d4c424ce308730294f351fb330bacad6adfcce9edee9b5b5785ee317cb11c248e225d008103279b13259e8049cef0ea34551a7b94b8cffc8b166dc10b068fc35109ec977ef46448e1338f56a0d9b00bcdfbe5a38cbca4713c5cd44fe07adf89afa545fa5e03eb04c54e7c9c7d592b15d78f8d77a77b3722fb3d956d16c90096fa436eea5181b2e8f831b6d9d2c7d8295f10eba53a9e4488fffd79d62ddbba8643ca1ac6f690b6fa24bafad202d09fff30aa3cb7e3fdbdc23f8392a9230e622dde262cfb83869b678dc7537a4166bc88c175b5a2cbec2a51dc4edd4211ab9fa6394b183ab012a971ba812888b873d1a41555d758d25bc05 for kp in range(3, e): p_mul = dp * e - 1 if p_mul % kp == 0: p = (p_mul // kp) + 1 if isPrime(p): print(f"Possible p: {p}") ~~~ ~~~python print(hex(31861992753927687898015867225364936717156122966871452158522022273441904906356555122255888752621540354459950502936284034402701637705665618072596224073750243871532896158590170890907702915557375271038451218070882681515634038395863197355657943957691079570303905905298993158975724654680441038860926955805887867091530086877315923850168728153134807354712937179334856558580340184471207680904031791394233409506507313707576211646863015977628818121361148167068816060288286899686931024995108453066694846917717849481656813578194027780500042029177544711527488042307614920323686161657450545442712185236407441996627715979725636279181)) ~~~ ~~~python N_upper_bits = 0xe43f7b14d127b45f30ae85b1353bb1f7e1a5775f3bc164173fdf3b062e8fb0b321cb670093418384fcd24c156758e01f79dd05cfed4effe1659ea8dc6abbe6b2e6bf5e67aae7ac525e0d3241ab2a65a7bbf9405a8cc54819d25d09748b6432daeca4585851964fefa0256482e7527e81b9c23723e5692c7c26b4c60c4065b261d1cae5098cd03b85bebb4939cf2c16f380f0dfdfe683b05380a40f208072abec5707914d54f7fe8ace3e38a85c722598d46f86a3386e274e7c108d57b8e440ead56305ad0426b6e4526d5b363ac78fe1fb14c3809606648e9c6de93581ecc6c99dfef3ba08e931f6710692ee524de49aa898c9a833524c48aac5 p = 0xfc65460e8231346a993739c99323f8c7414be9c5b7c7beda15d55b158fd0195d2f850bf0520c5d997b88a97ebcbd10bee2ed03c1fbfcbffed32c69b8f60abf2b178c36a07a6aee60cfba85ffb50908c64e86fac6261d403187251efb77b6ffee72e2032c05e799dbea57442edd52880b9aad5d6d05860c976ddd8c6c25888fe3c3fbddba11ca7d33c0ade8c8bd8dd421e05be0cc8f7c36501f3412133eebaaca0e092f985bb7898d2641a1ba07f41c576e69d2d2171663ff773b48d6dadb174bcd03207d70ffc3f10195c2f13c7f685221dd4da816aad1b48e478ad1c45e6cde1fd42006614dd6d40ce75baa197cbb88d249e36feba54f512676a179ae5a9b8d q = 0xe781ed2c4a257b1a0c39ba0ccdf00ed81ab6c5121e758b81154cfb0f2f8d46cf0365f5a4e2e86f7ab602706c2ac48bc84a8f3e726dc58fa90965eb19c51d63c4f7010bb8aab746d5c7e785c3bdbebe090f5d952c32a0734565b86e965e69d4c696834894a56b11240b8a1bd6e4822e6555c174f291bfb514df002ef67b82ab195f1fb67880c0f10e3288909621d87d730f14078639034ff90cb887cf0543c88552236515cb0948b06edfd7232a2cb9e37a5cb019df4cf517e1035be592ad3100da643e37f5b2525e8c0f2e77150b3a1bc007e0fc81ec5933713bad691e52100019e03420aad2895abbc3d059c086a20de025f840c1e98c7460bd8e4b5b5b774f N = p*q assert hex(N).startswith(hex(N_upper_bits)) print(N) ~~~ ~~~python from Crypto.Util.number import isPrime from Crypto.PublicKey import RSA # Data recovered from the redacted PEM N_upper_bits = 0xe43f7b14d127b45f30ae85b1353bb1f7e1a5775f3bc164173fdf3b062e8fb0b321cb670093418384fcd24c156758e01f79dd05cfed4effe1659ea8dc6abbe6b2e6bf5e67aae7ac525e0d3241ab2a65a7bbf9405 e = 65537 # assumption p_lower_bits = 0xd1c45e6cde1fd42006614dd6d40ce75baa197cbb88d249e36feba54f512676a179ae5a9b8d q = 0xe781ed2c4a257b1a0c39ba0ccdf00ed81ab6c5121e758b81154cfb0f2f8d46cf0365f5a4e2e86f7ab602706c2ac48bc84a8f3e726dc58fa90965eb19c51d63c4f7010bb8aab746d5c7e785c3bdbebe090f5d952c32a0734565b86e965e69d4c696834894a56b11240b8a1bd6e4822e6555c174f291bfb514df002ef67b82ab195f1fb67880c0f10e3288909621d87d730f14078639034ff90cb887cf0543c88552236515cb0948b06edfd7232a2cb9e37a5cb019df4cf517e1035be592ad3100da643e37f5b2525e8c0f2e77150b3a1bc007e0fc81ec5933713bad691e52100019e03420aad2895abbc3d059c086a20de025f840c1e98c7460bd8e4b5b5b774f dp = 0x91b5b9d3287055ad70d4c424ce308730294f351fb330bacad6adfcce9edee9b5b5785ee317cb11c248e225d008103279b13259e8049cef0ea34551a7b94b8cffc8b166dc10b068fc35109ec977ef46448e1338f56a0d9b00bcdfbe5a38cbca4713c5cd44fe07adf89afa545fa5e03eb04c54e7c9c7d592b15d78f8d77a77b3722fb3d956d16c90096fa436eea5181b2e8f831b6d9d2c7d8295f10eba53a9e4488fffd79d62ddbba8643ca1ac6f690b6fa24bafad202d09fff30aa3cb7e3fdbdc23f8392a9230e622dde262cfb83869b678dc7537a4166bc88c175b5a2cbec2a51dc4edd4211ab9fa6394b183ab012a971ba812888b873d1a41555d758d25bc05 dq_upper_bits = 0xb1baac63291537399b132243 # derived info N = 0xe43f7b14d127b45f30ae85b1353bb1f7e1a5775f3bc164173fdf3b062e8fb0b321cb670093418384fcd24c156758e01f79dd05cfed4effe1659ea8dc6abbe6b2e6bf5e67aae7ac525e0d3241ab2a65a7bbf9405a8cc54819d25d09748b6432daeca4585851964fefa0256482e7527e81b9c23723e5692c7c26b4c60c4065b261d1cae5098cd03b85bebb4939cf2c16f380f0dfdfe683b05380a40f208072abec5707914d54f7fe8ace3e38a85c722598d46f86a3386e274e7c108d57b8e440ead56305ad0426b6e4526d5b363ac78fe1fb14c3809606648e9c6de93581ecc6c99dfef3ba08e931f6710692ee524de49aa898c9a833524c48aac56f66a220d558d0d4312fbd03ef7d128640cd392370887460c17d031aa423a467ac326a3de0bd8bb08cea6b21cea54f280cdcf0ccee124218985cb6339149ca74e89a609a98ff89c152e77858fe621adce7e3b5b52aedba879640addfd4dce5bd66f70225de2ba463495c994031cb5b1a9e716dfd27978eae6b4b0da2f82f7bdc97d0aef86d66d6e067bb9175b84588dbecfc2f08ec4f3493aa72d176f628041838e66b5ae26c4cdcedef975d419599d12038f195d279c69479b1e8a47c376b087bfae32bd144864de6345fd64bf3ec380ac551b55838dff8836f23843ebfee88fa7c2a07216aa9078d29b21601c21dd9c85ae210c893d2d010c0f36622f7b8d35c6856638b83 p = 0xfc65460e8231346a993739c99323f8c7414be9c5b7c7beda15d55b158fd0195d2f850bf0520c5d997b88a97ebcbd10bee2ed03c1fbfcbffed32c69b8f60abf2b178c36a07a6aee60cfba85ffb50908c64e86fac6261d403187251efb77b6ffee72e2032c05e799dbea57442edd52880b9aad5d6d05860c976ddd8c6c25888fe3c3fbddba11ca7d33c0ade8c8bd8dd421e05be0cc8f7c36501f3412133eebaaca0e092f985bb7898d2641a1ba07f41c576e69d2d2171663ff773b48d6dadb174bcd03207d70ffc3f10195c2f13c7f685221dd4da816aad1b48e478ad1c45e6cde1fd42006614dd6d40ce75baa197cbb88d249e36feba54f512676a179ae5a9b8d phi = (p-1)*(q-1) d = pow(e,-1,phi) # We have found the two prime factors of the modulus assert isPrime(p) and isPrime(q) and p*q == N # Our private exponent matches that from dp recovered assert d % (p-1) == dp # The top bits of the Modulus match those recovered assert hex(N).startswith(hex(N_upper_bits)) # The prime p matches the low bits assert hex(p).endswith(hex(p_lower_bits)[2:]) # The derived dq matches the recovered upper bits of dq assert hex(d % (q-1)).startswith(hex(dq_upper_bits)) key = RSA.construct((N,e,d,p,q)) pem = key.export_key('PEM') print(pem.decode()) ~~~ Y con este último script obtuvimos la llave privada: ~~~ -----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEA5D97FNEntF8wroWxNTux9+Gld187wWQXP987Bi6PsLMhy2cA k0GDhPzSTBVnWOAfed0Fz+1O/+Flnqjcarvmsua/Xmeq56xSXg0yQasqZae7+UBa jMVIGdJdCXSLZDLa7KRYWFGWT++gJWSC51J+gbnCNyPlaSx8JrTGDEBlsmHRyuUJ jNA7hb67STnPLBbzgPDf3+aDsFOApA8ggHKr7FcHkU1U9/6Kzj44qFxyJZjUb4aj OG4nTnwQjVe45EDq1WMFrQQmtuRSbVs2OseP4fsUw4CWBmSOnG3pNYHsxsmd/vO6 COkx9nEGku5STeSaqJjJqDNSTEiqxW9moiDVWNDUMS+9A+99EoZAzTkjcIh0YMF9 AxqkI6RnrDJqPeC9i7CM6mshzqVPKAzc8MzuEkIYmFy2M5FJynTommCamP+JwVLn eFj+Yhrc5+O1tSrtuoeWQK3f1NzlvWb3AiXeK6RjSVyZQDHLWxqecW39J5eOrmtL DaL4L3vcl9Cu+G1m1uBnu5F1uEWI2+z8LwjsTzSTqnLRdvYoBBg45mta4mxM3O3v l11BlZnRIDjxldJ5xpR5seikfDdrCHv64yvRRIZN5jRf1kvz7DgKxVG1WDjf+INv I4Q+v+6I+nwqByFqqQeNKbIWAcId2cha4hDIk9LQEMDzZiL3uNNcaFZji4MCAwEA AQKCAgEA1Ar1eT1luXfFbhzdqCqxBywl4GQky2EFCF2GJBQVgX6pIqGqMyNl36JQ bEZmIHb2RuxCfgxkm+r10RPm0XGGvSUJG9cLOvcn/iAcVE2DsbTGOKTEeoq8lOCN dj9DT+6+26FCQapqDhD7okFiKyzEQhgkib1bXv3oyLygULlywOmHUQq+eIbrBTFQ JJMEGF2qElu0X/ly1dh9Zex3sVzWw1WGvkItccaThU7gq+hWUv9MO9/EuqP6+Drh 1a1tIv/8Kgk4OKfmn3o16UoXczv6O2Jaw9UtivrYUhL52K+/HF4p3bTnW2fo9p3C EbY92AdMdtyaWxxylFPd8lWv72a5S2wIL1XiUdFgJVbojVnj1v7VmG+R68AnrMqc 03kXCkHmxaWf76XI4e/CpxA8Ms46Dw8yvgfqHD1xBeK8j3jwgSWYu6G11zDKaxYb FG0UQB4G5uC89wC4GHb3Yl5AU0jKpdkPpQ+7VjMnuf4YM0dYuuW5ljOma+vkvLJo KrhDT9zJvixYqKjJV8FEwNRLjLPvMqUQt0OzkMvpJsI/lEgfXbZtXNdOqCNsfUS9 OqlNkIzWipDRdMu+H7eoPMMuT3OBx2TYnYAkTLNY5FOb+H4zL3hJ2gOntrDRAoYX v4ONlrx/ityx1ff/3f9V31t2rxBoGIh/WEyp1Xla9CZ//aU6cyECggEBAPxlRg6C MTRqmTc5yZMj+MdBS+nFt8e+2hXVWxWP0BldL4UL8FIMXZl7iKl+vL0QvuLtA8H7 /L/+0yxpuPYKvysXjDagemruYM+6hf+1CQjGTob6xiYdQDGHJR77d7b/7nLiAywF 55nb6ldELt1SiAuarV1tBYYMl23djGwliI/jw/vduhHKfTPArejIvY3UIeBb4MyP fDZQHzQSEz7rqsoOCS+YW7eJjSZBoboH9BxXbmnS0hcWY/93O0jW2tsXS80DIH1w /8PxAZXC8Tx/aFIh3U2oFqrRtI5HitHEXmzeH9QgBmFN1tQM51uqGXy7iNJJ42/r pU9RJnahea5am40CggEBAOeB7SxKJXsaDDm6DM3wDtgatsUSHnWLgRVM+w8vjUbP A2X1pOLob3q2AnBsKsSLyEqPPnJtxY+pCWXrGcUdY8T3AQu4qrdG1cfnhcO9vr4J D12VLDKgc0VluG6WXmnUxpaDSJSlaxEkC4ob1uSCLmVVwXTykb+1FN8ALvZ7gqsZ Xx+2eIDA8Q4yiJCWIdh9cw8UB4Y5A0/5DLiHzwVDyIVSI2UVywlIsG7f1yMqLLnj elywGd9M9RfhA1vlkq0xANpkPjf1slJejA8udxULOhvAB+D8gexZM3E7rWkeUhAA GeA0IKrSiVq7w9BZwIaiDeAl+EDB6Yx0YL2OS1tbd08CggEBAJG1udMocFWtcNTE JM4whzApTzUfszC6ytat/M6e3um1tXhe4xfLEcJI4iXQCBAyebEyWegEnO8Oo0VR p7lLjP/IsWbcELBo/DUQnsl370ZEjhM49WoNmwC8375aOMvKRxPFzUT+B634mvpU X6XgPrBMVOfJx9WSsV14+Nd6d7NyL7PZVtFskAlvpDbupRgbLo+DG22dLH2ClfEO ulOp5EiP/9edYt27qGQ8oaxvaQtvokuvrSAtCf/zCqPLfj/b3CP4OSqSMOYi3eJi z7g4abZ43HU3pBZryIwXW1osvsKlHcTt1CEaufpjlLGDqwEqlxuoEoiLhz0aQVVd dY0lvAUCggEBALG6rGMpFTc5mxMiQzxCxJKhh5kpvNqO2+2HaOKSpgorWTeIayqM OTFi0+KNGBRGH+ElsVJV9arBoeZtpB4Q3wxSeKoP/nev2OWcV7QbUnlAKVy17fV7 +qLXYcz8gcULxd29MhZ0HAtPudAwaTyKuKWxPVDT/JLJqRk+Yc92qK1EUCPfiQmH lkhJAVDHAXrbbF6yCMjBskpOL7bnBEbNb/7yPRwYrAQXmuOz0s07TpTzD3hi9anZ wfuwEk0VpRJzIW2IMb/yTxEvZqUtDdzI/rZZKXNPR0s0e+q9XvbpgSSpfzQBsOaT tUFEDyNAFC8H8FEZtUm51NuwaKh9ulqLkL8CggEBAINxIsHvYOPaMchRz2vwBhvj SWFWxrWUYmUoV7No5EbJhpQl4CEN5FX6oS6yj4X+5aKT3P/W5WdQ0dph/K4vhrD2 ChbmXZ/y63xghCUjAU8rEfTIsu/6u36qxf7D4UAHwHPvB5Jx0to7HhkRFfXw1f16 NEZe+vhMIFRFvaipj95UNHuMnbmuaTBFxmgfRsbsg9AQorr5Ky/SAKROrXUfbyx4 wLerPPfMvrv3EmP8UJtSmXNkncStfBrzgOY0yZUMmh5ML8wK5YfrlQ0nc769okCl VtqnfOK0g+1YGqdX1m5dxlqTVYGAQLQboLyhJm9xnoLDfqlHuQ+1eJhGyaZ5qI8= -----END RSA PRIVATE KEY----- ~~~ Luego de dar los permisos podemos intentar conectarnos por `ssh`, pero salta el siguiente error: ```go chmod 600 id_rsa ssh -i id_rsa santa@xmas2024.s4ur0n.com This service allows sftp connections only. Connection to xmas2024.s4ur0n.com closed. ``` Por lo que intentamos loguearnos por `sftp` y obtenemos los archivos del siguiente reto. ### Reto #3: Fax Dentro del servidor se encuentran 2 archivos que son bastante interesantes: ```go .rw-rw-r-- kali kali 681 B Sun Jan 5 17:07:15 2025  grinch .rw-rw-r-- kali kali 415 B Sun Jan 5 17:07:21 2025  README.unknown ``` Dentro del readme hay el siguiente contenido: ``` The Grinch intercepted a message between s4ur0n and Santa, obtaining a file, but we don't know what it contains or how to open it... We know they used analog and old-school communications, but not much more... In 1964, Xerox Corporation introduced (and patented) what many consider to be the first commercialized version of the original machine used to send messages between Santa and s4ur0n under the name LDX... ``` Y al revisar el tipo de archivo del archivo `grinch` se ve lo siguiente: ``` file grinch grinch: raw G3 (Group 3) FAX ``` Por lo que podemos asumir que el otro archivo es un mensaje antiguo de fax, y después de investigar se puede abrir con el comando `viewfax`, se puede usar el siguiente comando: ```go viewfax grinch ``` Con el que podemos visualizar el contenido y la **URL** al siguiente reto: ![image](https://hackmd.io/_uploads/rJyrPK_Lyx.png) ### Reto #4: Gameboy Al obtener los archivos de este reto, nos encontramos con un binario que al hacerle un `file` nos damos cuenta que se trata de un juego para GameBoy! ![image](https://hackmd.io/_uploads/B16QmEILyg.png) Usamos el emulador para Gameboy [Visualboy-advance](https://visualboyadvance.org/), el cual trae su propio debugger y otras herramientas interesantes para examinar juegos de Gameboy (aparte de emularlos y jugar claramente). Al ejecutarlo nos topamos con un simpático juego de pong. ![image](https://hackmd.io/_uploads/S1azBEILJg.png) Ya sea que ganemos o perdamos mostrará el mismo mensaje en la pantalla. ![image](https://hackmd.io/_uploads/SyxgLBVIIye.png) Luego de gastar tiempo considerable aprendiendo a como reversear un juego para el gameboy, instalando extensiones para el **Ghidra** por ejemplo, nos dimos cuenta luego de un tiempo que esto era inútil. >**Nota:** Entre otras cosas, el juego cuenta con ciertas configuraciones para que no sea tan fácil analizarlo; esto debido a que tiene un peso demasiado grande para ser un juego normal, por lo que eso ya es una pista que puede llamar nuestra atención. La respuesta para pasar al siguiente reto era simplemente ver contenido oculto en el juego usando herramientas como `binwalk`. ![image](https://hackmd.io/_uploads/Hy3HIV88yl.png) Como se puede observar, hay un gif oculto (algo curioso por cierto): ![image](https://hackmd.io/_uploads/B1YTLNLLkx.gif) Al escanear el código QR, nos dan las instrucciónes para conectarnos por SSH y pasar al siguiente reto. ```go Connect by ssh (user: level6, private key from #2) ``` ### Reto #6: Tarjetas perforadas Al conectarnos por `ssh` al servidor con el comando ```bash ssh -i id_rsa level6@xmas2024.s4ur0n.com ``` Se pueden ver múltiples archivos que debemos lograr sacar del servidor (Un pequeño reto, debido a que el servidor no contaba con ninguno binario que nos pudiera ser de utilidad). ```python Linux xmas2024 6.1.0-28-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.119-1 (2024-11-22) x86_64 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⠟⠋⠉⠻⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⠟⠁⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⠟⠀⠀⠀⠀⠀⢤⠀⠀⠀⠀⠀⠙⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⠟⠁⠀⠀⠀⠀⠀⠀⠸⣆⠀⠀⠀⠀⠀⠈⠻⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⢠⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣧⣀⣀⣀⡀⠀⠀⠈⠛⢿⣆⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⢰⡟⠉⠉⠽⠿⢧⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠙⠻⠿⢻⣿⠙⢷⣄⠀⠀⠈⢿⣆⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⢸⡿⣇⠀⠀⠀⠀⠉⠉⠉⠙⢷⣤⣤⣤⣄⠀⠀⠀⠀⠀⠀⢿⣇⣀⠻⣆⠀⠀⢸⣿⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⣴⠿⢾⣂⠀⠀⠀⠀⠀⠀⠺⣧⣄⠀⠀⠉⢻⡶⠚⠛⠳⣦⡞⠋⠙⡿⢻⡆⠀⢸⣇⣀⣀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠻⣦⡀⠀⢱⠀⠀⠀⠀⠀⠀⡼⠋⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠤⠖⠷⢾⣧⡀⡿⠙⠿⣿⣟⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣳⡾⠳⢦⣤⣤⣠⡖⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⠐⣤⣸⡟⠻⢧⠀⠀⠀⡙⢷⣄ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⡟⠁⠀⠀⠀⠀⠹⣷⣤⣤⣤⣤⣼⣄⠀⠀⢳⣄⠀⠘⣷⣤⣼⠿⣧⡇⠀⠀⠀⠀⢹⣦⣿ ⠀⠀⠀⠀⢠⣤⣤⣤⣴⡟⠀⠀⠀⠀⠀⠀⠈⠳⡌⠉⠀⠀⣠⠽⠛⠛⠛⠉⠛⠛⢻⣿⡀⠀⠛⢿⡄⠀⢸⠀⢸⡿⠀ ⠀⠀⣠⣴⡿⣟⠛⠛⠛⢁⠀⠲⣦⣠⣀⠀⠀⠀⠘⢄⢠⠞⠁⠀⢀⣤⣶⣤⣤⠄⠀⣿⣧⣶⣶⣈⣻⣶⡿⣷⡾⠃⠀ ⠀⢈⣿⣿⠋⣁⣤⣤⣄⡉⠲⣄⠙⣿⠿⣷⣿⣦⣄⠀⢻⠀⠀⣠⣿⡿⢿⡉⢯⠉⢠⣟⣉⠀⠼⣿⣟⠁⠀⠀⠀⠀⠀ ⢠⣾⣿⢋⡄⠙⢹⡇⠈⠻⡀⠀⠙⠻⣤⡸⣿⠟⢻⣶⡼⣦⢾⡟⣿⢟⣤⠷⠒⠛⠋⠙⢿⡳⡄⠙⣿⣷⡄⠀⠀⠀⠀ ⠿⣿⣏⣾⠀⡄⠸⣇⠀⠀⠁⠀⠤⡀⠈⠳⣤⠴⡿⠾⠛⠳⢬⣷⡴⠏⠀⠀⠀⠀⠀⠀⢸⡇⠀⠀⢹⣟⠁⠀⠀⠀⠀ ⠀⢻⣿⣿⠀⣧⠀⠹⣦⡀⠀⠀⠀⠀⠑⠤⠌⠉⠓⠒⢶⠒⠒⠛⠀⠀⠁⠀⠀⠀⠀⠀⣼⠃⠀⠀⣼⣿⠀⠀⠀⠀⠀ ⠀⠀⠉⢹⣧⣿⣧⠀⠈⠛⢦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⢀⣠⡾⠃⠀⣴⣿⠟⠃⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠉⠛⠻⣷⣄⠀⠀⠈⠛⠶⣤⣀⠀⠀⠀⠀⠀⣿⠀⠀⠀⢀⣠⡴⠞⠋⠁⠀⣠⣾⠿⠋⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠈⠻⢷⣦⣄⡀⠀⠈⠙⠳⢦⣀⠀⠀⡏⢀⣴⠞⠋⠁⠀⠀⣀⣤⡾⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⢷⣦⣄⡀⠀⠈⠳⣤⡾⠋⠀⣀⣤⣴⠾⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⢷⣦⣄⣉⣠⣴⠾⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ You are in my house... Merry XMAS!!! Last login: Fri Jan 3 23:44:53 2025 from 81.36.144.29 -bash-5.2$ ls 13 a1 archivo.bin ce1 13d5c3ed-1337-4cde-a061-9a429344a793.png a1277b71-1337-434f-abe2-6cfa8dda6d40.png archivo_hex.txt ce18234d-1337-435d-a60a-e82c1f5f65a6.png 29 ae bd cec8906e-1337-4d9b-b6c9-9563b9b963de.png 296dd470-1337-485d-8481-afcff05720bb.png ae38bba9-1337-4546-a288-1efaec2c9add.png bd6ab787-1337-4586-8745-7d06d7d58f79.png f73ce5e6-1337-4091-81c6-be4ab4f456d2.png ``` Por lo que la opción más simple fue hacer un script de python para que los extrajera: ```python import paramiko import sys # Configuración SSH_HOST = 'xmas2024.s4ur0n.com' SSH_PORT = 22 SSH_USER = 'level6' SSH_KEY_PATH = './id_rsa' REMOTE_FILE = '/home/level6/imagen.png' LOCAL_FILE = './imagen1.png' def ssh_xxd_transfer(): try: # Conectar al servidor SSH usando clave privada client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(SSH_HOST, port=SSH_PORT, username=SSH_USER, key_filename=SSH_KEY_PATH) print('[+] Conexión SSH establecida.') # Convertir archivo a xxd en el servidor stdin, stdout, stderr = client.exec_command(f'xxd {REMOTE_FILE}') xxd_data = stdout.read().decode('utf-8') if stderr.channel.recv_exit_status() != 0: print('[-] Error al ejecutar xxd en el archivo remoto.') print(stderr.read().decode('utf-8')) return print('[+] Archivo convertido con xxd. Transferencia iniciada...') # Guardar el contenido xxd en un archivo local with open('archivo.xxd', 'w') as f: f.write(xxd_data) print('[+] Archivo xxd recibido y guardado localmente.') # Reconstruir el archivo con xxd inverso import subprocess subprocess.run(['xxd', '-r', 'archivo.xxd', LOCAL_FILE]) print(f'[+] Archivo reconstruido exitosamente en: {LOCAL_FILE}') except Exception as e: print(f'[-] Error: {e}') finally: client.close() print('[+] Conexión SSH cerrada.') if __name__ == '__main__': ssh_xxd_transfer() ``` Y después de un copiar y pegar nombres hemos conseguido todos los archivos. Al analizar las imagenes observamos que son tarjetas perforadas, y al buscar por internet nos encontramos con la siguiente herramienta para leerlas. ![Imagen de WhatsApp 2025-01-06 a las 11.36.26_c7cd375d](https://hackmd.io/_uploads/r19c8FKUJx.jpg) https://www.masswerk.at/cardreader/ Sin embargo no nos quiso funcionar, por lo que tuvimos que optar por la opción más `dummy` leerlas manualmente. Por lo que el proceso ha sido el siguiente: - Obtener que significa cada combinación de agujeros. - Luego buscar las combinaciones que tenemos e irlo decodificando. Hemos usado el siguiente video como guia para entenderlo un poco mejor: https://www.youtube.com/watch?v=ynAbZUQqo6g Luego de extraer los mensajes se puede ver algo interesante: ```python procedure division. identification division. display "xmas2024.s4ur0n.com/l3v" display "next topic is revesing" program-id. level6. display "download it at https://" stop run. display "3l7xmas24ctfr3v". ``` Si unimos todos los que tienen el prefijo **display** se puede obtener la **url** al siguiente nivel: ```ruby download it at https://xmas2024.s4ur0n.com/l3v3l7xmas24ctfr3v ``` ### Reto #7: Reversing Password Windows Al revisar la `url` obtenemos un binario de windows, el cual asumimos que hay que reversear para poder pasar al siguiente nivel. Al ejecutarlo y luego de esperar un rato solicita que se le ingrese una contraseña: ![image](https://hackmd.io/_uploads/By_Ba2uL1e.png) #### Aquí empieza el infierno Por lo que intentamos reversarlo con `ida` y vemos que el binario se encuentra `stripped`, por lo que tendremos que encontrar cual es la función que contiene toda la lógica. Pero al revisarlo contiene demasiadas funciones (2503). ![image](https://hackmd.io/_uploads/rJ7tGTdIkg.png) Por lo que para descubrirlo decidí utilizar `x64dbg` y ver en que función se detiene a la hora de consultar la contraseña. Pero ahí tenemos el primer reto el ida no puede decompilarlo (Probablemente por que uso el free). > **Nota:** Al intentar abrirlo con ghidra tampoco fue muy fructifero, cuando entramos a la función que contiene la lógica crasheo mi **VM** ![image](https://hackmd.io/_uploads/BJyv4auIkg.png) Por lo que podemos ir a la función que contiene esa dirrección de memoria y ver la lógica que allí se encuentra, en este caso es `sub_45B0C0`. Por lo que en ese punto solo quedaba leer directamente el código `asm`, pero al revisar el grafo se ve que es una función un tanto pesada :\( : ![image](https://hackmd.io/_uploads/H1Jj4TdLke.png) Luego de muchos intentos hemos obtenido la contraseña para este reto y poder continuar, por lo que al ingresar la clave `H4rdRev3rs1ngW1thMB4sANDMerryXmas4u`: ![Imagen de WhatsApp 2025-01-03 a las 14.01.49_8147129f](https://hackmd.io/_uploads/HJPyD5_81g.jpg) ### Reto #8: Video Nos podemos conectar con el siguiente comando con la credencial del reto anterior: ```go ssh level8@xmas2024.s4ur0n.com ``` Y allí conseguimos un `README` con el siguiente mensaje: ```t Welcome home again... but Santa hasn't left any gifts yet. It's time to fucj around a bit more before we get to them... Level 9 is localted at https://xmas2024.s4ur0n.com/ea6a958571c0450486dd1c842326fefe1a4fc03c43c60efcbbae72966c91e8cf ``` En donde se puede ver una web que carga un video, sin embargo en el mismo hay un mensaje que dice que no hay nada interesante. ![Imagen de WhatsApp 2025-01-03 a las 14.21.44_0186b37c](https://hackmd.io/_uploads/BJTFuquLyl.jpg) Al revisar el código fuente de la `web`, se ve que este video se encuentra alojado en la carpeta `assets`, sobre la cual tenemos permisos de lectura por lo que podemos ver 3 archivos que llaman nuestra atención ![image](https://hackmd.io/_uploads/HJ1l8_YIyx.png) Y al leerlos se puede ver nueva información que podemos utilizar para conectarnos por ssh ### Reto #9: Audio BBS Dentro del servidor `ssh` se ve el siguiente archivo: ```txt Did you remember old BBS's time? Connect to https://xmas2024.s4ur0n.com/38d448ea401fbe9be522afd88e8b3cfd.html and enjoy it! ``` Y dentro del servidor se puede ver una web que nos permite descargar un `wav`. ![Imagen de WhatsApp 2025-01-06 a las 10.29.21_7131749a](https://hackmd.io/_uploads/SJm1wOKUJx.jpg) Al investigar un poco sobre `BBS` he encontrado lo siguiente: > Un Bulletin Board System o BBS (en español Sistema de Tablón de anuncios) es un software que fue popular en las décadas de 1980 y 1990 en Estados Unidos para redes de ordenadores, ya que permitía a los usuarios participantes conectarse a través de una línea telefónica a una red donde podían ver y consultar distintas informaciones que publicaban usuarios participantes. Actualmente, es de fácil acceso por medio de telnet a través de internet a servidores y realizar funciones como descarga de software, datos, lectura de noticias y boletines, intercambio de mensajes con otros usuarios a través del correo2​, juego en línea, etc. Sin embargo un dato bastante interesante, es que en esa epoca esto se relacionaba bastante con los modems, por lo que podrían ser la señal de la misma así que intentamos decodificarla. Como no sabia cual era la frecuencia con la que trabajaba el modem lo que hice fue hacer un pequeño script que le aplicara fuerza bruta. Con el que descubrí que la tasa de recepción eran 300. Por lo que obtuve lo siguiente: ```python minimodem --rx 300 -f santa.wav ### CARRIER 300 @ 1250.0 Hz ### SG8hIEhvISBIbyEgQ29uZ3JhdHMgbXkgZnJpZW5kISBJIHdpbGwgaWdub3JlIHlvdXIgR29vZ2xlIGFuZCBQb3JuaHViIGhpc3Rvcnkgd2hlbiByZWFkaW5nIHlvdXIgbGV0dGVyLiBJJ20gdmVyeSBidXN5IHJpZ2h0IG5vdyBiZWNhdXNlIEkgaGF2ZSB0byBmZWVkIFJ1ZG9scGgsIERvbm5lciwgQmxpdHplbiwgVml4ZW4sIEN1cGlkLCBDb21ldCwgRGFzaGVyLCBEYW5jZXIgYW5kIFByYW5jZXIuIEl0IG9ubHkgcmVtYWlucyBmb3IgbWUgdG8gY29udmV5IG15IGNvbmdyYXR1bGF0aW9ucyBhbmQgd2lzaCB5b3UgZXZlcnkgc3VjY2VzcyBmb3IgeW91ciBuZXh0IGxldmVsIHRoYXQgaXMgYXQgaHR0cHM6Ly94bWFzMjAyNC5zNHVyMG4uY29tL2xldmVsMTAvZjc1MTcwYzM0MTJmMGRkMjYxMDNjMGNjNGY0MjhlMDY3MTc1MjgzNi5odG1s ### NOCARRIER ndata=557 confidence=2.355 ampl=0.993 bps=300.00 (rate perfect) ### ``` Y al decodearlo se consigue acceso al nivel 10: ![image](https://hackmd.io/_uploads/SkEmKdFIJx.png) ### Nivel 10: Unicode Java Injection Al ingresar a la web se nos solicita que ingresemos un código en java que logre hacer un bypass de los comentarios con el fin de que se ejecute: ```java System.out.println("BLUE or RED pill?"); ``` ![Imagen de WhatsApp 2025-01-06 a las 10.40.45_8179166e](https://hackmd.io/_uploads/ByIqFuFIJl.jpg) Por lo que al investigar un poco sobre como funciona el procesamiento antes de llegar al análisis sintactico de `java` he encontrado un poco sobre el ataque `unicode normalization` que puede ser aplicado en este caso: - https://hacktricks.boitatech.com.br/pentesting-web/unicode-normalization-vulnerability - https://portswigger.net/research/hiding-payloads-in-java-source-code-strings Ahora con esto en mente tenemos que tener en cuaneta que el payload que podría interesarnos es el que cierre los comentarios por que podríamos enviar los siguiente: ```java */System.out.println("BLUE or RED pill?");/* ``` Lo que en formato `unicode` sería lo siguiente: ```unicode \u002a\u002f\u0053\u0079\u0073\u0074\u0065\u006d\u002e\u006f\u0075\u0074\u002e\u0070\u0072\u0069\u006e\u0074\u006c\u006e\u0028\u0022\u0042\u004c\u0055\u0045\u0020\u006f\u0072\u0020\u0052\u0045\u0044\u0020\u0070\u0069\u006c\u006c\u003f\u0022\u0029\u003b\u002f\u002a ``` Lo cual nos da la `url` para conseguir el reto final ```ruby https://xmas2024.s4ur0n.com/level5/xmas2024-vm.zip ``` ### Reto #5 Y Final: Reversing vCPU Curiosamente el reto final es el 5 (Debido a que estamos en el año **2025** xd). El archivo lo conseguimos de la `url` entregada en el reto anterior, dentro del zip se encuentran los siguientes archivos: - Un binario que es el reto. - Un archivo encriptado que parece contener las respuestas del reto. - Un checksum para validar los archivos. ```bash ls vm ctf.hex checksums.txt -al .rw-r--r-- kali kali 285 B Mon Dec 23 06:18:02 2024  checksums.txt .rwxr-xr-x kali kali 67 B Mon Dec 23 03:31:06 2024  ctf.hex .rwxr-xr-x kali kali 666 KB Mon Dec 23 06:15:40 2024  vm ``` Podemos ver las carácteristicas del binario para ver si tiene algo interesante ```c file vm vm: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, BuildID[sha1]=e0513250fd4a9779d97817c951695e770efe950c, for GNU/Linux 3.2.0, strippep ``` De este punto lo más importante es que es stripped, por lo que las firmas de las funciones no se mantendrán. Al ejecutar se puede ver la siguiente información: ```python | -+- A /=\ /\ /\ ___ _ __ _ __ __ __ i/ O \i / \/ \ / _ \| '__|| '__|\ \ / / /=====\ / /\ /\ \| __/| | | | \ \/ / / i \ \ \ \/ / / \___/|_| |_| \ / i/ O * O \i / / /=========\ __ __ /_/ _ / * * \ \ \/ / /\ /\ __ _ ____ | | i/ O i O \i \ / __ / \/ \ / _` |/ ___\ |_| /=============\ / \ |__| / /\ /\ \| (_| |\___ \ _ / O i O \ /_/\_\ \ \ \/ / / \__,_|\____/ |_| i/ * O O * \i /=================\ |___| XMAS CTF by @NN2ed_s4ur0n (https://cs3group.com) VM description: [+] Flat memory model [+] Little-endian [+] Code <= 4096 bytes [+] Stack size <= 256 bytes [+] Custom VM's instruction set (CISC) [+] Four general registers (32 bits) [+] IP register [+] SP register Usage: ./vm file[.ext] your_password Example: ./vm ctf.hex your_password ``` Dentro del archivo `ctf.hex` podemos ver las posibles respuestas del binario. ```c cat ctf.hex -A -np S␀c\xABWell·done!!!␍␊ ␀Ops!␍␊ ``` Por lo que al ejecutarlo podemos ver la siguiente salida: ```python ./vm ctf.hex aaaaaaaaa | -+- A /=\ /\ /\ ___ _ __ _ __ __ __ i/ O \i / \/ \ / _ \| '__|| '__|\ \ / / /=====\ / /\ /\ \| __/| | | | \ \/ / / i \ \ \ \/ / / \___/|_| |_| \ / i/ O * O \i / / /=========\ __ __ /_/ _ / * * \ \ \/ / /\ /\ __ _ ____ | | i/ O i O \i \ / __ / \/ \ / _` |/ ___\ |_| /=============\ / \ |__| / /\ /\ \| (_| |\___ \ _ / O i O \ /_/\_\ \ \ \/ / / \__,_|\____/ |_| i/ * O O * \i /=================\ |___| XMAS CTF by @NN2ed_s4ur0n (https://cs3group.com) Trying password... aaaaaaaaa. If it is ok, then try https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('aaaaaaaaa') 2024 XMAS CTF VM result... Ops! ``` Así que vamos a asumir que cuando funciona dice `Well done` y cuando la contraseña es incorrecta dice `Ops`. Por lo que ahora nos vemos en la necesidad de reversar el binario para entender que sucede, pero como necesitamos encontrar el main nos podemos dirigir a la función `start` y ver desde allí a cuales llama: ![image](https://hackmd.io/_uploads/SkPkva_Ikx.png) Como `sub_804A0B4` se ve llamativo nos dirigimos a ella y ahí encontramos la lógica principal de la aplicación, luego de entender el conportamiento se le pueden dar algunos nombres a las funciones y los argumentos: ```c int __cdecl sub_804A0B4(int NumeroArgumentos, _DWORD *argumentos) { int v3; // eax unsigned int largoArchivo; // [esp+0h] [ebp-30h] int v5; // [esp+4h] [ebp-2Ch] int ArregloCreado; // [esp+8h] [ebp-28h] int arregloConValoresXor; // [esp+Ch] [ebp-24h] int valorXoreado; // [esp+10h] [ebp-20h] int v9; // [esp+14h] [ebp-1Ch] banner(); if ( NumeroArgumentos == 3 ) { v9 = leerArgumento(argumentos[2]); if ( revisarLargo(v9) <= 7u || revisarLargo(v9) > 0x20u ) { ImprimirError("2024 XMAS CTF VM result: Your password length is incorrect"); exit(-1); } ImprimirData( "Trying password... %s.\n" "If it is ok, then try https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('%s')\n" "\n" "2024 XMAS CTF VM result... ", v9, v9); valorXoreado = xorEntreLetras(v9); arregloConValoresXor = crearVector(4608); ArregloCreado = crearVector(14); *(_WORD *)(ArregloCreado + 10) = 0; *(_WORD *)(ArregloCreado + 12) = 256; *(_BYTE *)(ArregloCreado + 8) = 0; v3 = leerArgumento(argumentos[1]); v5 = leerContenidoArchivo(v3, "rb"); if ( v5 ) { sub_805C740(v5, 0, 2); largoArchivo = LargoArchivo(v5); if ( largoArchivo <= 0x1000 ) { sub_805C820(v5); sub_8058F40(arregloConValoresXor, 1, largoArchivo, v5); sub_8058AB0(v5); *(_WORD *)(arregloConValoresXor + 4) = HIWORD(valorXoreado); *(_WORD *)(arregloConValoresXor + 16) = valorXoreado; LogicaAplicacion(arregloConValoresXor, ArregloCreado); sub_8059710(10); return 0; } else { ImprimirError("*ERROR*\nCode and data is larger than the storage available into the XMAS VM"); return -3; } } else { ImprimirError("*ERROR*\nFile not found!"); return -2; } } else { ImprimirEspecificaciones(); ImprimirData( "Usage:\n\t%s file[.ext] your_password\n\nExample:\n\t%s ctf.hex your_password\n\n", *argumentos, *argumentos); return -1; } } ``` Revisamos la función `xorEntreLetras(password)` debido a que esta es una función clave para el comportamiendo del programa: ```c int __cdecl xorEntreLetras(_BYTE *a1) { return (char)(a1[3] ^ a1[7]) + ((char)(a1[2] ^ a1[6]) << 8) + ((char)(a1[1] ^ a1[5]) << 16) + ((char)(*a1 ^ a1[4]) << 24); } ``` Lo que hace es dividir la palabra por la mitad y hacer `xor` entre los elementos que están en la misma posición, por ejemplo para la palabra `aaaabcde`: ```python a a a a xor b c d e --------------------------------- 0x03 0x02 0x05 0x04 ``` Por lo que tenemos que encontrar las parejas que nos generen los valores esparados. Luego el siguiente paso es entender lo que sucede en la función `LogicaAplicacion`. Sin embargo esto fue una pared algo complicada. Pero de este punto he podido rascar lo siguiente: > En este punto realmente estoy algo confundido respecto a el comportamiento y no podría estar totalmente seguro de la conjetura pero llegué a la siguiente deducción al ver el código: Si se está bloqueando la `c` y la `S`, podría que el código esté permitendo que se tengan las letras opuestas es decir `C` y `s`. ```c if ( v16 > 'c' ) goto LABEL_52; if ( v16 > 'S' ) goto LABEL_52; LABEL_52: result = sub_8059360("\n=== 2024 XMAS CTF Error! Exception found! ===\nExiting VM...\n"); v17 = 1; ``` Asumiendo que en la contraseña solo hay letras mayusculas y minusculas el diccionario sería `a-zA-Z`, y como ya sabemos que las operaciones de la función XOR nos debe dar ` 0x2e 0x2e 0x31 0x26`, pensaremos en ello como nuestro objetivo; pero intentando obtener una cadena que sea relacionada con el reto. Eso ha sido clave para poder hallar la contraseña, esto debido a que hace que no tengamos que entender totalmente el programa y podamos obtener la contraseña apartir de mátematicas y lógica: ```python a a a a xor O O P G --------------------------------- 0x2e 0x2e 0x31 0x26 ``` Por lo que ya sabemos cuanto tienen que dar nuestras operaciones y si contamos con que sabemos que la contraseña debe tener `s` y `C`. Tenemos un punto de partida para reducir la busqueda por fuerza bruta. En ese caso he creado un script para que encuentre las combinaciones posibles que pueden generar estos valores mediante el uso de python: ```python def find_filtered_2e_combinations(target): chars = [chr(x) for x in range(ord('A'), ord('z')+1)] combinations = [] print("Buscando combinaciones para 0x2E que contengan 'C' o 's':") print("=" * 60) print("| Char 1 | Char 2 | Hex 1 | Hex 2 | Operation | Contains |") print("=" * 60) for char1 in chars: if not char1.isalpha(): continue for char2 in chars: if not char2.isalpha(): continue if ord(char1) ^ ord(char2) == target: # Solo guardamos combinaciones que contengan 'C' o 's' if 'C' in (char1, char2) or 's' in (char1, char2): combinations.append((char1, char2)) contains = [] if 'C' in (char1, char2): contains.append('C') if 's' in (char1, char2): contains.append('s') contains_str = ','.join(contains) print(f"| {char1:^6} | {char2:^6} | 0x{ord(char1):02X} | 0x{ord(char2):02X} | {ord(char1):02X} ^ {ord(char2):02X} = {target:02X} | {contains_str:^8} |") return combinations # Ejecutar el análisis combinations = find_filtered_2e_combinations(0x2e) combinations = find_filtered_2e_combinations(0x31) combinations = find_filtered_2e_combinations(0x26) ``` Con ese código se obtiene la siguiente salida: ```python Buscando combinaciones para 0x2E que contengan 'C' o 's': ============================================================ | Char 1 | Char 2 | Hex 1 | Hex 2 | Operation | Contains | ============================================================ | C | m | 0x43 | 0x6D | 43 ^ 6D = 2E | C | | m | C | 0x6D | 0x43 | 6D ^ 43 = 2E | C | Buscando combinaciones para 0x2E que contengan 'C' o 's': ============================================================ | Char 1 | Char 2 | Hex 1 | Hex 2 | Operation | Contains | ============================================================ | B | s | 0x42 | 0x73 | 42 ^ 73 = 31 | s | | C | r | 0x43 | 0x72 | 43 ^ 72 = 31 | C | | r | C | 0x72 | 0x43 | 72 ^ 43 = 31 | C | | s | B | 0x73 | 0x42 | 73 ^ 42 = 31 | s | Buscando combinaciones para 0x2E que contengan 'C' o 's': ============================================================ | Char 1 | Char 2 | Hex 1 | Hex 2 | Operation | Contains | ============================================================ | C | e | 0x43 | 0x65 | 43 ^ 65 = 26 | C | | U | s | 0x55 | 0x73 | 55 ^ 73 = 26 | s | | e | C | 0x65 | 0x43 | 65 ^ 43 = 26 | C | | s | U | 0x73 | 0x55 | 73 ^ 55 = 26 | s | ``` Por lo que ahora sabemos que valores pueden estar en cada posición, y con eso en mente se pueden crear las opciones para intentar pensar en cual puede ser la cadena valida: ```python from itertools import product def generate_dictionary(): # Posibles valores para las posiciones 0,4 y 1,5 pos_0_4_1_5 = [('C','m'), ('m','C')] # Posibles valores para las posiciones 2,6 pos_2_6 = [('B','s'), ('s','B')] pos_3_7 = [('s','U'), ('U','s')] # Caracter para rellenar otras posiciones filler = '-' # Generar todas las combinaciones posibles for (p0, p4), (p1, p5), (p2, p6) in product(pos_0_4_1_5, pos_0_4_1_5, pos_2_6): # Crear y mostrar cada palabra de 8 caracteres word = [p0 , filler , p2, filler, p4, filler , p6, filler] print(''.join(word)) for (p0, p4), (p1, p5), (p2, p6) in product(pos_0_4_1_5, pos_0_4_1_5, pos_2_6): # Crear y mostrar cada palabra de 8 caracteres word = [filler , p1 , p2, filler, filler , p5 , p6, filler] print(''.join(word)) for (p0, p4), (p1, p5), (p3, p7) in product(pos_0_4_1_5, pos_0_4_1_5, pos_3_7): # Crear y mostrar cada palabra de 8 caracteres word = [p0 , filler , filler , p3, p4, filler , filler , p7] print(''.join(word)) for (p0, p4), (p1, p5), (p3, p7) in product(pos_0_4_1_5, pos_0_4_1_5, pos_3_7): # Crear y mostrar cada palabra de 8 caracteres word = [filler , p1 , filler, p3, filler, p5 , filler, p7] print(''.join(word)) # Posibles valores para las posiciones 2,6 pos_2_6 = [('C','r'), ('r','C')] pos_3_7 = [('s','U'), ('U','s')] # Caracter para rellenar otras posiciones filler = '-' for (p0, p4), (p2,p6), (p3, p7) in product(pos_0_4_1_5, pos_2_6, pos_3_7): # Crear y mostrar cada palabra de 8 caracteres word = [filler , filler ,p2, p3, filler, filler , p6, p7] print(''.join(word)) pos_2_6 = [('B','s'), ('s','B')] pos_3_7 = [('C','e'), ('e','C')] # Caracter para rellenar otras posiciones filler = '-' for (p0, p4), (p2,p6), (p3, p7) in product(pos_0_4_1_5, pos_2_6, pos_3_7): # Crear y mostrar cada palabra de 8 caracteres word = [filler , filler ,p2, p3, filler, filler , p6, p7] print(''.join(word)) # Ejecutar el generador generate_dictionary() ``` El diccionario de palabras es el siguiente, es importante tener en cuenta que los espacios en donde hay "-" no sabemos que letras pueden ser: ```python C-B-m-s- C-s-m-B- C-B-m-s- C-s-m-B- m-B-C-s- m-s-C-B- m-B-C-s- m-s-C-B- -CB--ms- -Cs--mB- -mB--Cs- -ms--CB- -CB--ms- -Cs--mB- -mB--Cs- -ms--CB- C--sm--U C--Um--s C--sm--U C--Um--s m--sC--U m--UC--s m--sC--U m--UC--s -C-s-m-U -C-U-m-s -m-s-C-U -m-U-C-s -C-s-m-U -C-U-m-s -m-s-C-U -m-U-C-s --Cs--rU --CU--rs --rs--CU --rU--Cs --Cs--rU --CU--rs --rs--CU --rU--Cs --BC--se --Be--sC --sC--Be --se--BC --BC--se --Be--sC --sC--Be --se--BC ``` Con estas he empezado a realizar fuerza bruta para ver si alguna funcionaba. Sin embargo mientras analizaba las cadenas me he puesto a leerlas una por una a ver si dentro de alguna se me puede llegar a ocurrir una contraseña valida y apta para el reto. por lo que eliminé las que a mi parecer no tenián lógica debido a la posición de las letras. Por lo que finalmente he terminado con las siguientes: ```python C-B-m-s- C-s-m-B- C-B-m-s- C-s-m-B- m-B-C-s- m-s-C-B- m-B-C-s- m-s-C-B- -CB--ms- -Cs--mB- -CB--ms- -Cs--mB- -mB--Cs- -ms--CB- C--sm--U C--Um--s C--sm--U C--Um--s m--sC--U m--UC--s m--sC--U m--UC--s -C-s-m-U -C-U-m-s -m-s-C-U -m-U-C-s -C-s-m-U -C-U-m-s -m-s-C-U -m-U-C-s --Cs--rU --CU--rs --rs--CU --rU--Cs --Cs--rU --CU--rs --rs--CU --rU--Cs --BC--se --sC--Be --BC--se --sC--Be ``` Que aunque no han sido muchas, mientras las leí ví una que particularmente llamó mi atención: ```python -C-U-m-s ``` Esto debido a que si lo pensamos, la palabra `Xmas` ha estado presente durante toda la competencia. Y el reto era una `vCPU`, por lo que he hecho sinapsis, la sabiduría vino a mí y se me ocurrío que ahí podría estar la contraseña. ![image](https://hackmd.io/_uploads/ByhLfR_LJg.png) > Necesito saber como era la forma correcta de hacerlo. He probado que valor obtenía de la conjetura: ![image](https://hackmd.io/_uploads/SJgRfCuIkg.png) Y como podrás imaginar casi me desmayo cunado ví que cumplía la condición. ![image](https://hackmd.io/_uploads/HyA77C_8Jl.png) > Y aunque el reto estaba casi terminado olvidé algo importante Como la contraseña es trabajada como 2 palabras eso significa que una opción valida también sería `vCPUXmas` y su opuesta `XmasvCPU`, detalle que no tuve en cuenta por un buen tiempo. Finalmente como no necesariamente esa es la variante correcta ya que se pueden invertir las letras y las mayusculas por minusculas he decidido crear las variantes de las 2 palabras y probarlas: ```python import itertools # Palabra inicial word1 = "xmasVCPU" word2 = "VCPUxmas" # Generar todas las combinaciones de mayúsculas y minúsculas def generate_case_variants(word): # Para cada letra, crear una lista con sus variantes (mayúscula y minúscula) cases = [(char.lower(), char.upper()) for char in word] # Generar el producto cartesiano de las variantes combinations = itertools.product(*cases) # Convertir cada combinación en una palabra return ["".join(comb) for comb in combinations] # Obtener las variantes variants1 = generate_case_variants(word1) variants2 = generate_case_variants(word2) # Guardar en un archivo output_file = "case_variants1.txt" with open(output_file, "w") as f: for variant in variants1: f.write(variant + "\n") output_file = "case_variants2.txt" with open(output_file, "w") as f: for variant in variants2: f.write(variant + "\n") print(f"Se generaron {len(variants2)*2} variantes y se guardaron en el archivo {output_file}.") ``` Y luego hice un script que lanzara fuerza bruta al servidor: ```python import itertools import subprocess # Palabra inicial word = "vcpuxmas" # Generar todas las combinaciones de mayúsculas y minúsculas def generate_case_variants(word): cases = [(char.lower(), char.upper()) for char in word] combinations = itertools.product(*cases) return ["".join(comb) for comb in combinations] # Variantes de la palabra variants = generate_case_variants(word) # Archivo de salida output_file = "command_outputs.txt" # Ejecutar el comando con cada variante with open(output_file, "w") as f: for variant in variants: try: # Ejecutar el comando result = subprocess.run( ['./vm', 'ctf.hex', variant], # Comando y argumentos capture_output=True, text=True, timeout=2 # Tiempo máximo de espera en segundos ) # Escribir el resultado en el archivo f.write(f"Output for word '{variant}':\n") f.write(result.stdout + "\n") f.write(result.stderr + "\n") f.write("=" * 40 + "\n") # Separador para cada resultado except subprocess.TimeoutExpired: # Manejar el caso en que el comando tarde demasiado f.write(f"Timeout for word '{variant}'\n") f.write("=" * 40 + "\n") print(f"Se guardaron los resultados de las ejecuciones en el archivo {output_file}.") ``` Luego metemos las salidas de las 2 variantes en el mismo archivo y lanzamos el siguiente oneliner para extraer las opciones validas: ```bash cat command_outputsFinal.txt | grep done -B 3 | grep "http" | grep C | awk 'NF{print $NF}' https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('vCpuXmAS') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('vCpUXmAs') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('vCPuXmaS') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('vCPUXmas') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('VCpuxmAS') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('VCpUxmAs') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('VCPuxmaS') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('VCPUxmas') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('xmasVCPU') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('xmaSVCPu') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('xmAsVCpU') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('xmASVCpu') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('XmasvCPU') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('XmaSvCPu') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('XmAsvCpU') https://xmas2024.s4ur0n.com/level5/verify-xmas.php?pwd=sha256('XmASvCpu') ``` Y tenemos los siguientes casos: ##### Cuando la opción era incorrecta ```bash echo -n "VCPUxmas" | sha256sum c4e8b535247ca478a97d7bcab1cc9a788429b9bf3a375f32648cc1dc27c42fcc - ``` Con un url invalida: ```python https://xmas2024.s4ur0n.com/hof2024/level5/verify-xmas.php?pwd=c4e8b535247ca478a97d7bcab1cc9a788429b9bf3a375f32648cc1dc27c42fcc ``` Nos lleva a la siguiente web en donde la ruta es el `sha-256` de `looser`: ![image](https://hackmd.io/_uploads/SJ_C4YtL1l.png) ##### Cuando la opción era correcta ```bash echo -n "vCPUXmas" | sha256sum 08bdbd247ddf8b8ce6b5a4f85178ba323c50cb075cfb1620a2ecfd09043ae926 - ``` Y tenemos la siguiente url: ```python https://xmas2024.s4ur0n.com/hof2024/level5/verify-xmas.php?pwd=08bdbd247ddf8b8ce6b5a4f85178ba323c50cb075cfb1620a2ecfd09043ae926 ``` Con eso hemos logrado completar el reto de navidad llegando a la web final: ![Imagen de WhatsApp 2025-01-05 a las 20.37.26_70b74208](https://hackmd.io/_uploads/rkIJV3uUyx.jpg) Y luego de mucho esfuerzo hemos quedado en el tercer puesto del **CTF de navidad de S4ur0n Edición 2024** ![Imagen de WhatsApp 2025-01-05 a las 20.39.29_0f7996e6](https://hackmd.io/_uploads/BySvNnO8yl.jpg)