2020riftCTF
Contents
0x0 概述
近期在riftCTF上有一道逆向题,涉及到web前端反调试。 题目如下
0x1 开始
下载四个附件后,用游览器打开crackme.html
输入字符串后会有两个结果
- 字符串与flag一致,提示correct password
- 否则显示wrong password
除了crackme.js以外的文件,比较简单,没有一些相关信息,直接看crackme.js
大致源码分析了下,基本上比对flag的代码集中在crackme.js 核心代码在于
|
|
通过base64解码,将引号内容还原成可阅读的代码文件
|
|
主要password比对的操作集中在check函数。
0x2 解题思路
- 静态分析 静态分析是一种方式,但对于新手来说,有一部分参数和字段的转化需要花很长时间来理清
后续
可以考虑能否简化过程
动态分析 F12打开开发者工具,web直接中断,无法调试。
查阅了相关资料,发现前端有反调试机制,使得我们无法通过下断,单步步进,对程序就行调试。我们需要绕过这部分代码。
- 动静结合 因为程序代码所有的工作空间都在一起,所以可用通过console打印结合静态分析,解读代码获取正确的password
0x3 解题过程
将我们解码的文件覆盖原有的crackme.js
- 动静结合 刷新页面,F12进入开发者工具。在console里输入check(‘123456789’),可以看到虽然程序处于暂停状态,实际内部代码我们是可以调用的。
开始对check函数逐行就行分析,可以看到,如果我们进入任意带return ![]的if语句中,check函数返回的都是false,所以我们要让函数中这样的的if条件均为false- 举例说明
1 2 3 4 5
if (_0x4825a8[0x24][_0x22fc(_0x1542('0x11'))]() % 0x5 != 0x0 && _0x4825a8[0x24]['charCodeAt']() - _0x4825a8[0x7][_0x1542('0x8')]() != 0x2 || _0x4825a8[0x24][_0x22fc('0x3')]() != eval(atob(_0x22fc(_0x1542('0x1a'))))) { return ![]; }
我们可以将一些内容用console打印出来,例如
所以上面的if语句可以简化为1 2 3 4
if(_0x4825a8[36].charCodeAt()%5!=0&&_0x4825a8[36].charCodeAt()-_0x4825a8[7].charCodeAt()!=2||_0x4825a8[36].charCodeAt()!=125 ) { return false; }
由此可见,_0x4825a8[36]=125 ,_0x4825a8[7]=123
依次完成check函数的混淆还原成简化的函数如下:
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
var check = function (flag) { var _0x873b25 = "KCgoMSA8PCAyKSAqIDEwICsgOSkgLyAoKDEgPDwgMykgLSAxKSkgKiAoMSA8PCAxKQ=="; if (flag.indexOf('_') !=14) { return false; } if (flag[36].charCodeAt() % 5 != 0 && flag[36].charCodeAt() - flag[7].charCodeAt() != 2 || flag[36].charCodeAt() != 125) { return false; } if (flag[8] == flag[35]) { if (flag[9] != flag[34]) { return false; } if (flag[9] != '-') { return false; } if (flag[34] != flag[35]) { return false; } var _0x873b25 = flag[34].charCodeAt(); if (_0x873b25 % 9 != 0) { return false; } if (_0x873b25 % 5 != 0) { return false; } if (_0x873b25 % 40 != 5) { return false; } } else { return false; } if (flag[10]) { _0x873b25 = flag[10].charCodeAt(); if (_0x873b25 - flag[8].charCodeAt() * 3 + 14 != 0) { return false; } } if (Math.pow(flag[11].charCodeAt() - 48, 4) != 0) { return false; } if (flag[12] != 'u' && flag[21] != 'w') { return false; } if (flag[13].charCodeAt() % 6 == 0) { var _0x1ed1fd = [1]; for (var i = 1; i < 5; i++) { var _0x476c4e = 1; for (var j = 1; j <= i; j++) { _0x476c4e = _0x476c4e * Math.pow(j, j); } _0x1ed1fd[i] = _0x476c4e; } if (_0x1ed1fd[0] + _0x1ed1fd[1] + _0x1ed1fd[2] + _0x1ed1fd[3] != flag[13].charCodeAt()) { return false; } } else { return false; } if ((flag[14].charCodeAt() ^ flag[20].charCodeAt() ^ flag[24].charCodeAt() ^ flag[31].charCodeAt()) != 0 || flag[24].charCodeAt() != flag[26].charCodeAt() - 19) { return false; } if (flag[15] != 'f' || flag[16].charCodeAt() - 48 != 1) { return false; } if (flag[17].charCodeAt() % 6 == 0) { var _0x1ed1fd = [1]; for (var i = 1; i < 5; i++) { var _0x476c4e = 1; for (var j = 1; j <= i; j++) { _0x476c4e = _0x476c4e * Math.pow(j, j); } _0x1ed1fd[i] = _0x476c4e; } if (_0x1ed1fd[0] + _0x1ed1fd[1] + _0x1ed1fd[2] + _0x1ed1fd[3] != flag[17].charCodeAt()) { return false; } } else { return false; } if (flag[17].charCodeAt() + 0x1 != flag[18].charCodeAt()) { return false; } if (flag[17].charCodeAt() + 0x2 != flag[19].charCodeAt()) { return false; } if (flag[21] != 'w' || flag[22].charCodeAt() - 48 != 3) { return false; } if (flag[23] != 'b' || flag[25] != 'c') { return false; } if (flag[26].charCodeAt() % 0x6 == 0x0) { var _0x1ed1fd = [1]; for (var i = 1; i < 5; i++) { var _0x476c4e = 1; for (var j = 1; j <= i; j++) { _0x476c4e = _0x476c4e * Math.pow(j, j); } _0x1ed1fd[i] = _0x476c4e; } if (_0x1ed1fd[0] + _0x1ed1fd[1] + _0x1ed1fd[2] + _0x1ed1fd[3] != flag[26].charCodeAt()) { return false; } } else { return false; } if (flag[27].charCodeAt() - 48 != 4) { return false; } if ((flag[29].charCodeAt() ^ 32) != 'K' .charCodeAt()) { return false; } if (flag[29] != 'm' || flag[30] != 'e' || flag[32] != 'X' || flag[33] != 'O') { return false; } if (flag[0] != 'r' && flag[1] != 'i' && flag[2] != 'f' && flag[3] != 't' && flag[4] != 'C' && flag[5] != 'T' && flag[6] != 'F') { return false; } return true; };
- 反反调试
查阅了一些资料,前端反调试其实挺简单的。就是在代码中插入一些debugable,需要将这些标记删除即可。 大致游览了下,整体代码中有多处出现debugable相关代码 比如 直接将类似的函数删除之后,就能去除反调试限制。
如何确认是否可删除,是否会影响程序运行,需要逐步测试。
剔除debugable后的代码
通过以上方法,最终获得flag
flag
riftCTF{–y0ur_f1rst_w3b_cr4kme_XO–}
Author Zhe
LastMod 2020-03-23