HackTheBox是一个在线的渗透测试平台,我们可以通过它锻炼自己的渗透测试水平,并与安全领域的数千名人员交流想法和方法。有意思的是,HackTheBox的注册过程就是一道简单的类似于CTF谜题。
邀请码获取
HackTheBox注册地址:https://www.hackthebox.eu/invite
进入HackTheBox的注册页面后,发现需要输入邀请码,但这里的邀请码并不是通过向网站管理员发送注册申请的邮件获得的,而是需要进行解谜获得。
查看浏览器的控制台信息也可以证实这一点,控制台里给出提示信息 KEEP CALM AND HACK THIS BOX
查看页面渲染时加载的资源,可以发现一个有趣的js文件 inviteapi.min.js
inviteapi.min.js
的代码看起来很杂乱,我最初也是硬着头皮在读这段代码,之后才了解到这段代码其实是被 JS Packer
工具所混淆的结果。
- 解密前:
eval
(
function(p,a,c,k,e,d)
{
e=function(c){return c.toString(36)};
if(!''.replace(/^/,String))
{
while(c--)
{
d[c.toString(a)]=k[c]||c.toString(a)
}
k=[function(e){return d[e]}];
e=function(){return'\\w+'};
c=1
};
while(c--)
{
if(k[c])
{
p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])
}
}
return p
}
('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}',24,24,'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'),0,{})
)
- 解密后:
function verifyInviteCode(code) {
var formData = {
"code": code
};
$.ajax({
type: "POST",
dataType: "json",
data: formData,
url: '/api/invite/verify',
success: function (response) {
console.log(response)
}, error: function (response) {
console.log(response)
}
})
}
function makeInviteCode() {
$.ajax({
type: "POST",
dataType: "json",
url: '/api/invite/how/to/generate',
success: function (response) {
console.log(response)
}, error: function (response) {
console.log(response)
}
})
}
可以看到这里有两个函数 makeInviteCode
和 verifyInviteCode
,一个是生成邀请码、一个是验证邀请码,这里是通过AJAX的方式分别向 /api/invite/how/to/generate
和 /api/invite/verify
发送 JSON
数据。但如果检查加载页面时与网站的交互数据,其实我们并没有向这两个URL地址发送请求,既然这样不妨在浏览器控制台输入 makeInviteCode
主动发起请求。
解密上面一串由BASE64加载的信息得到:
In order to generate the invite code, make a POST request to /api/invite/generate
按其所说,得到下面的结果
再对这一串 code
进行解密,得到 GNHML-SUAKC-MVKCK-TLANJ-HPUBI
,这就是邀请码了。
JS Packer
上面那一串复杂的JS代码对于经验丰富的程序员来说秒杀,但是对于像我这样的菜鸟就要思考一段时间了,其实如果对JS Packer有所了解这很容易发现一个特征
这里提到了一款JS混淆工具——JS Packer,其实它并不是专门的混淆工具,而是一款js压缩工具。
其官网地址为: http://dean.edwards.name/packer/
比如,输入:
alter("hello, world");
输出:
eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String)){while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('0("1, 2");',3,3,'alter|hello|world'.split('|'),0,{}))
格式化后:
eval(function (p, a, c, k, e, r) {
e = String;
if (!''.replace(/^/, String)) {
while (c--) r[c] = k[c] || c;
k = [function (e) {
return r[e]
}];
e = function () {
return '\\w+'
};
c = 1
}
;
while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
return p
}('0("1, 2");', 3, 3, 'alter|hello|world'.split('|'), 0, {}))
下面是对解压缩算法的一个简单解读,有兴趣的同学可以做更多的了解:
// p 将原始内容中所有单词替换为字典下标后的压缩内容
// a 词典大小,暂时用不到
// c 词典大小,在解压时用来关联压缩内容和词典
// k 词典
// e 在解压时,当replace第二个参数支持function时,为\\w+,否则为与下标对应的字符串
// r 当加速解压时用来保存词典
eval(function(p, a, c, k, e, r) {
e = String;
// 检测当前的浏览器是否支持replace(regex, function),如果支持的话就能够加快解压速度
// 如果不支持的话可以把这一块直接忽略掉
if (!''.replace(/^/, String)) {
// 把被压缩的单词拷贝一份,因为k还有别的用处
while (c--) r[c] = k[c] || c;
// k[0]后面用来对每个匹配到的下标寻找替换字符串
k = [function(e) {
return r[e]
}];
// 用来分割原始内容
e = function() {
return '\\w+'
};
// 加速解压的时候,相当于把while变成了if
c = 1
};
// 使用词典将压缩后的下标代码扩展,如果没有上面的加速的话,c等于词典单词数,要一个一个替换了
// 如果支持replace(string, function)的话,会将匹配到的每一个数字都传递给k[c]来得到其应该被替换为的字符串
while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
// 完成解压
return p
} ('0.1("2");0.1("2");0.1("3");', 4, 4, 'console|log|aaaaa|bbbb'.split('|'), 0, {}))