Last updated on January 27, 2024 am
记录JS逆向以及Html&Css逆向
UofTCTF-2023
这里是题目
第一步首先找到关键的函数,可以先找按钮的位置,再找按钮的触发事件
显然是这个checkPassword
,checkPassword
是被混淆了的
丢去这个网站可以解混淆,下面是混淆后的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| function checkPassword(password) { try { if (password.length !== 21) { return false; } if (password.slice(1, 2) !== (String.fromCodePoint + '')[parseInt((parseInt + '').charCodeAt(3), 16) - 147] || password[(parseInt(41, 6) >> 2) - 2] !== String.fromCodePoint(123) || password[4].charCodeAt(0) !== password[7].charCodeAt(0) + 72 || JSON.stringify(Array.from(password.slice(5, 7).split('').reverse().join(), _0x2d4d73 => _0x2d4d73.codePointAt(0)).map(_0x5b85c5 => _0x5b85c5 + 213)) !== JSON.stringify([285, 257, 297])) { return false; } let password_8_12_rev = password.slice(8, 12).split('').reverse(); try { for (let i = 0; i < 5; i++) { password_8_12_rev[i] = password_8_12_rev[i].charCodeAt(0) + i + getAdder(i); } } catch (_0x1fbd51) { password_8_12_rev = password_8_12_rev.map(_0x24cda7 => _0x24cda7 += _0x1fbd51.constructor.name.length - 4); } if (MD5(String.fromCodePoint(...password_8_12_rev)) !== '098f6bcd4621d373cade4e832627b4f6') { return false; } if (MD5(password.charCodeAt(12) + '') !== '812b4ba287f5ee0bc9d43bbf5bbe87fb') { return false; } password_8_12_rev = (password[8] + password[11]).split(''); password_8_12_rev.push(password_8_12_rev.shift()); if (password.substring(14, 16) !== String.fromCodePoint(...password_8_12_rev.map(_0x5b5ec8 => Number.isNaN(+_0x5b5ec8) ? _0x5b5ec8.charCodeAt(0) + 5 : 48)) || password[password[7] - password[10]] !== atob('dQ==') || password.indexOf(String.fromCharCode(117)) !== password[7] - password[17] || JSON.stringify(password.slice(2, 4).split('').map(_0x7bf0a6 => _0x7bf0a6.charCodeAt(0) ^ getAdder.name[password[7]].charCodeAt(0))) !== JSON.stringify([ 72, 90 ].map(_0x40ab0d => _0x40ab0d ^ String.fromCodePoint.name[password[17] - 1].charCodeAt(0)))) { return false; } if (String.fromCodePoint(...password.split('').filter((_0x5edfac, _0x2965d2) => _0x2965d2 > 15 && _0x2965d2 % 2 == 0).map(_0x2ffa6d => _0x2ffa6d.charCodeAt(0) ^ password.length + password[7])) !== atob('g5Go')) { return false; } if (password[password.length - 2] !== String.fromCharCode(Math.floor((({} + '').charCodeAt(0) + 9) / 3)) || password[1 + password[7]] !== giggity()[5]) { return false; } return true; } catch (_0x4d4983) { return false; } } function getAdder(_0x430c9d) { switch (_0x430c9d) { case 0: return 34; case 1: return 44; case 2: return 26; case 3: return 60; } return 101; } function giggity() { return giggity.caller.name; }
|
这里面有些变量作了重新命名,便于审计。
整体的验证流程都很简单,就是把flag给拆分开来进行判断,
比如首先验证长度为21
1 2 3
| if (password.length !== 21) { return false; }
|
然后逐步逐步验证
1 2 3 4 5 6
| if (password.slice(1, 2) !== (String.fromCodePoint + '')[parseInt((parseInt + '').charCodeAt(3), 16) - 147] || password[(parseInt(41, 6) >> 2) - 2] !== String.fromCodePoint(123) || password[4].charCodeAt(0) !== password[7].charCodeAt(0) + 72 || JSON.stringify(Array.from(password.slice(5, 7).split('').reverse().join(), _0x2d4d73 => _0x2d4d73.codePointAt(0)).map(_0x5b85c5 => _0x5b85c5 + 213)) !== JSON.stringify([285, 257, 297])) { return false; }
|
对于其中的一些语句可以直接使用控制台打印调试,整体都是这个思路,这里贴上官方wp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
| function checkPassword(_0x38d32a) { try { if (_0x38d32a.length !== 21) { return false; } if ( _0x38d32a.slice(1, 2) !== (String.fromCodePoint + "")[ parseInt((parseInt + "").charCodeAt(3), 16) - 147 ] || _0x38d32a[(parseInt(41, 6) >> 2) - 2] !== String.fromCodePoint(123) || _0x38d32a[4].charCodeAt(0) !== _0x38d32a[7].charCodeAt(0) + 72 || JSON.stringify( Array.from( _0x38d32a.slice(5, 7).split("").reverse().join(), (_0x2d4d73) => _0x2d4d73.codePointAt(0) ).map((_0x5b85c5) => _0x5b85c5 + 213) ) !== JSON.stringify([ 285, 257, 297, ]) ) { return false; } let _0x3c7a5c = _0x38d32a.slice(8, 12).split("").reverse();
try { for (let _0x396662 = 0; _0x396662 < 5; _0x396662++) { _0x3c7a5c[_0x396662] = _0x3c7a5c[_0x396662].charCodeAt(0) + _0x396662 + getAdder(_0x396662); } } catch (_0x1fbd51) { _0x3c7a5c = _0x3c7a5c.map( (_0x24cda7) => (_0x24cda7 += _0x1fbd51.constructor.name.length - 4) ); }
if ( MD5(String.fromCodePoint(..._0x3c7a5c)) !== "098f6bcd4621d373cade4e832627b4f6" ) { return false; }
if ( MD5(_0x38d32a.charCodeAt(12) + "") !== "812b4ba287f5ee0bc9d43bbf5bbe87fb" ) { return false; } _0x3c7a5c = (_0x38d32a[8] + _0x38d32a[11]).split(""); _0x3c7a5c.push(_0x3c7a5c.shift()); if ( _0x38d32a.substring(14, 16) !== String.fromCodePoint( ..._0x3c7a5c.map((_0x5b5ec8) => Number.isNaN(+_0x5b5ec8) ? _0x5b5ec8.charCodeAt(0) + 5 : 48 ) ) || _0x38d32a[_0x38d32a[7] - _0x38d32a[10]] !== atob("dQ==") || _0x38d32a.indexOf(String.fromCharCode(117)) !== _0x38d32a[7] - _0x38d32a[17] || JSON.stringify( _0x38d32a .slice(2, 4) .split("") .map( (_0x7bf0a6) => _0x7bf0a6.charCodeAt(0) ^ getAdder.name[_0x38d32a[7]].charCodeAt(0) ) ) !== JSON.stringify( [72, 90].map( (_0x40ab0d) => _0x40ab0d ^ String.fromCodePoint.name[_0x38d32a[17] - 1].charCodeAt(0) ) ) ) { return false; } if ( String.fromCodePoint( ..._0x38d32a .split("") .filter( (_0x5edfac, _0x2965d2) => _0x2965d2 > 15 && _0x2965d2 % 2 == 0 ) .map( (_0x2ffa6d) => _0x2ffa6d.charCodeAt(0) ^ (_0x38d32a.length + _0x38d32a[7]) ) ) !== atob( "g5Go" ) ) { return false; } if ( _0x38d32a[_0x38d32a.length - 2] !== String.fromCharCode(Math.floor((({} + "").charCodeAt(0) + 9) / 3)) || _0x38d32a[1 + _0x38d32a[7]] !== giggity()[5] ) { return false; } return true; } catch (_0x4d4983) { return false; } } function getAdder(_0x430c9d) { switch (_0x430c9d) { case 0: return 34; case 1: return 44; case 2: return 26; case 3: return 60; } return 101; } function giggity() { return giggity.caller.name; }
|
UofTCTF-2024
这里是题目
题目只有一个html文件,打开后是这个样子,大意是需要控制上面19个字节使得下面的灯全亮即可获得正确flag。
阅读html文件得知,这里实际上就是有css控制。
这里是控制每一个比特的样式,
这里是用鼠标点击作为控制,当点击latch_set
按钮时,这个按钮下的子元素latch_state
就往左移;当点击latch_reset
按钮时,这个按钮下的子元素latch_state
就往右移,
这里则是控制整一个字节的样式
这里则是控制LED灯的样式
最关键的则是控制LED的代码,这里可以这样理解
在wrapper
类中,若第一个byte
子元素里面的第七个latch
子元素的latch_reset
是active
,那么checker
的父元素的具有相同第二个类型元素的第一个子元素做操作。
结合html文件的树形结构会更好理解。即每一个byte
里面的每一个比特都控制一个checker
里面的一个checker_state
元素,我们就需要把所有的checker_state
都用操作translateX(-100%)
给挪走,这样就可以使得覆盖在原checker
上面的checker_state
消失,从而暴露出原来的checker
,那么就会点亮这个LED
这里就直接手动操作完所有的比特了
1 2 3 4 5 6 7 8 9 10
| flag = [0b01000011, 0b01110011, 0b01010011, 0b01011111, 0b01101100, 0b00110000, 0b01100111, 0b00110001, 0b01100011, 0b01011111, 0b01101001, 0b01110011, 0b01011111, 0b01100110, 0b01110101, 0b01101110, 0b01011111, 0b00110011, 0b01101000]
for i in flag: print(chr(i), end='')
|
JS总结
- map函数会自动捕捉上下文的变量,比如
arr.map(x=>x+1)
就会把arr
里面的元素作为变量进行相同运算
- 数字与字符相加会直接在后面添加
- 数组的
filter
方法使用箭头函数会有两种使用方式
arr.filter((v) => xxx),v是具体的值
arr.filter((v,index) => xxx),index是每一项的索引
CSS总结
选择子
1 2 3 4 5 6
| .latch { position: relative; display: inline-flex; width: 200px; height: 50px; }
|
这就是选择所有类latch
的元素,设置它们的样式
操作
1 2 3 4
| .latch__reset:active~.latch__state { transform: translateX(0%); transition: transform 0s; }
|
这里是选择latch_reset
元素,如果点击这个元素active
,那么选择这个元素后第一个子元素为latch_state
,设置它的样式
条件操作
1 2 3 4
| .wrapper:has(.byte:nth-child(1) .latch:nth-child(7) .latch__reset:active) .checker:nth-of-type(2) .checker__state:nth-child(1) { transform: translateX(0%); transition: transform 0s; }
|
在wrapper
类中,若第一个byte
子元素里面的第七个latch
子元素的latch_reset
是active
,那么checker
的父元素的具有相同第二个类型元素的第一个子元素做操作。