0x0 背景
在某个站发现了一套输入密码然后跳转url的方式
扒了下发现全程运算是在本地js的,与后台服务器无通信
来看一下原始代码
function _HpbChkPwd(keyin,escEncrypted,defaultUrl,target)
{
var encrypted = unescape(escEncrypted);
var indexbase = " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
var passlen = keyin.length;
var enqlen = encrypted.length;
var decrypted = "";
var decryptedPassword = "";
var decryptedPath = "";
var targetUrl = "";
var i, j, k, chr1, chr2, nbase;
var needPassword = "%u30D1%u30B9%u30EF%u30FC%u30C9%u3092%u5165%u529B%u3057%u3066%u304F%u3060%u3055%u3044%u3002";
var badPassword = "%u30D1%u30B9%u30EF%u30FC%u30C9%u304C%u6B63%u3057%u304F%u3042%u308A%u307E%u305B%u3093%u3002";
if (passlen <= 0)
{
alert(unescape(needPassword));
return ;
}
for (i = 0, j = passlen - 1, k = 0 ; i < enqlen ; i++, j--, k=0)
{
if (j < 0)
{
j = passlen - 1;
}
chr1 = indexbase.indexOf(keyin.charAt(j));
chr2 = indexbase.indexOf(encrypted.charAt(i));
if (chr2 < (chr1 + j))
{
nbase = (chr1 + j - chr2) / 0x5f;
k += (0x5f * Math.ceil(nbase));
}
k += (chr2 - chr1 - j);
decrypted += indexbase.charAt(k);
}
decryptedPassword = decrypted.substring(decrypted.length - passlen, decrypted.length);
if (keyin == decryptedPassword)
{
decryptedPath = decrypted.substring(0, decrypted.length - passlen);
passlen = decryptedPath.length;
for (i = 0 ; i < passlen ; i++)
{
chr1 = decryptedPath.charAt(i);
if (chr1 == "%")
{
chr2 = decryptedPath.substring(i, i+6);
targetUrl += chr2;
i += 11;
}
else
targetUrl += chr1;
}
if ((typeof(opener.closed) != "unknown") && ! opener.closed)
opener.location.href = targetUrl;
window.close();
}
else
{
if (defaultUrl.length)
{
if ((typeof(opener.closed) != "unknown") && ! opener.closed)
opener.location.href = defaultUrl;
}
else
{
alert(unescape(badPassword));
}
window.close();
}
0x1 分析
从上述源码中可以推论出如下
- 密钥的加密结果会附在密文后:
for (i = 0, j = passlen - 1, k = 0 ; i < enqlen ; i++, j--, k=0)
{
if (j < 0)
{
j = passlen - 1;
}
chr1 = indexbase.indexOf(keyin.charAt(j));
chr2 = indexbase.indexOf(encrypted.charAt(i));
if (chr2 < (chr1 + j))
{
nbase = (chr1 + j - chr2) / 0x5f;
k += (0x5f * Math.ceil(nbase));
}
k += (chr2 - chr1 - j);
decrypted += indexbase.charAt(k);
}
decryptedPassword = decrypted.substring(decrypted.length - passlen, decrypted.length);
if (keyin == decryptedPassword)
{
decryptedPath = decrypted.substring(0, decrypted.length - passlen);
对于输入的密码 Keyin[],逆序平铺在每个加密字节上,即明文10位,密钥5位,密文=PPPPPPPPPPKKKKK
-
解密方法如下,明文正序,密钥循环逆序
密文Byte[] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 密钥 4 3 2 1 0 4 3 2 1 0 4 3 2 1 0 明文 ? ? ? ? ? ? ? ? ? ? 密钥[0] 密钥[1] 密钥[2] 密钥[3] 密钥[4] 可列部分等式:
密文[10]+4+密钥[4]=密钥[0]
密文[11]+3+密钥[3]=密钥[1]
密文[12]+2+密钥[2]=密钥[2]
密文[13]+1+密钥[1]=密钥[3]
密文[14]+0+密钥[0]=密钥[4]
这里相加后是对照第一部分的IndexBase,当成一个Base95的ASCII表
-
第二步之后已经可以获得若干位密钥,接下来进入下一步破解
解密出的明文为一个跳转url,在大多数情况下为.htm或.html结尾,因此可以额外列出等式
密文Byte[] 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 密钥 4 3 2 1 0 4 3 2 1 0 4 3 2 1 0 明文 ? ? ? ? ? . h t m l 密钥[0] 密钥[1] 密钥[2] 密钥[3] 密钥[4]
或者,对于一个很长的明文,可以猜测前缀为http://
密文Byte[] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
密钥 | 4 | 3 | 2 | 1 | 0 | 4 | 3 | 2 | 1 | 0 | 4 | 3 | 2 | 1 | 0 |
明文 | h | t | t | p | : | ? | ? | ? | ? | ? | 密钥[0] | 密钥[1] | 密钥[2] | 密钥[3] | 密钥[4] |
由此,可以列出其他等式
密文[5]+4+密钥[4]="."
...以此类推
0x2 解密
function start(n,escEncrypted)
{
var encrypted = unescape(escEncrypted);
var indexbase = " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
var str = ".html";
var arr=[]
var arr2=[]
for(i=0;i
核心代码差不多就这样,因为密钥长度不确定,需要当做参数输入,也可以自动从4开始进行试探
0x4 遗留问题
因为是通过猜测和取巧的方式进行破解,偶尔会出现长密码下几个字符的公式互相重合
此时再上暴力破解即可
0X5 参考资料
https://github.com/s-horiguchi/HPBPwdBreaker
http://www.usamimi.info/~geko/arch_web/03_memo/011_hpb/index.html