簡(jiǎn)介 :
題目地址 : https://s3.eu-central-1.amazonaws.com/dragonsector2-ctf-prod/starblind_96bbc884beb953bee0f120d0994d30d6073c53afd582f456586d7effa184dc25/starblind.html
分析 :
查看源碼發(fā)現(xiàn)有一段 base64 編碼的 js 代碼
直接點(diǎn)開(kāi)瀏覽器會(huì)自動(dòng)解碼 :
可以找到 , 這里是主要的密碼驗(yàn)證的地方 , 會(huì)將用戶(hù)輸入的密碼使用 CalcSha4() 這個(gè)函數(shù)計(jì)算哈希
然后再比較得到的哈希是否和已存在的哈希相同
再來(lái)看 CalcSha4() 這個(gè)函數(shù) :
var CalcSHA4 = function(block) {
let r = new Uint8Array(64);
for (let i = 0; i < block.length; i++) {
r[i] = block.charCodeAt(i); // 將用戶(hù)輸入復(fù)制到新的長(zhǎng)度 64 的數(shù)組中
}
for (let i = 32; i < 64; i++) {
r[i] = i * 48271; // 后 32 字節(jié)填充
}
// 定義異或函數(shù)
let xor = function (imm) {
for (let i = 0; i < 64; i++) {
r[i] ^= imm[i];
}
};
// 定義 perm 函數(shù)
let perm = function (imm) {
let n = new Uint8Array(64);
for (let i = 0; i < 512; i++) {
const dst_bit = i%8;
const dst_byte = i/8|0;
const sign = Math.sgn(imm[i]);
const idx = sign ? -imm[i] : imm[i];
const src_bit = idx%8;
const src_byte = idx/8|0;
let b = (r[src_byte] >> src_bit) & 1;
if (sign) { b ^= 1; }
n[dst_byte] |= b << dst_bit;
}
r = n;
};
/*
* 這里省略了 xor 函數(shù) 和 perm 函數(shù)
*/
// 將數(shù)組轉(zhuǎn)換成 16 進(jìn)制字符串
hexdigest = "";
for (let i = 0; i < 64; i++) {
let n = r[i].toString(16);
if (n.length < 2) {
n = "0" + n;
}
hexdigest += n;
}
return hexdigest;
}
這里需要我們重點(diǎn)關(guān)注的函數(shù)是 perm 函數(shù) , 單獨(dú)拿出來(lái)分析 :
let perm = function (imm) {
let n = new Uint8Array(64);
for (let i = 0; i < 512; i++) { // 64字節(jié) -> 512 位 , 也就是遍歷 64 個(gè)字符的每一個(gè)位
const dst_bit = i%8;
const dst_byte = i/8|0;
const sign = Math.sgn(imm[i]); // Math.sgn 返回這個(gè)數(shù)是否為負(fù)數(shù)
// Math.sgn = function(a) { return 1/a<0; };
const idx = sign ? -imm[i] : imm[i]; // 求出 imm 數(shù)組元素的絕對(duì)值
const src_bit = idx%8;
const src_byte = idx/8|0;
let b = (r[src_byte] >> src_bit) & 1; // 取出 r 的第 idx/8 個(gè)元素的第 idx%8 個(gè) bit
if (sign) { b ^= 1; } // 如果這里 imm 元素是負(fù)數(shù) , 將這個(gè) bit 與 1 異或
n[dst_byte] |= b << dst_bit; // 將異或后的 b 放置在 n 這個(gè)數(shù)組的第 i/8 個(gè)元素的第 i%8 個(gè) bit
}
r = n;
};
這里加密算法的流程是這樣 :
1. 加密之前保證明文長(zhǎng)度為 27 字節(jié)
2. 定義一個(gè) 64 字節(jié)的數(shù)組 , 初始值為 0
3. 將明文按照順序復(fù)制到這個(gè)數(shù)組中
4. 由于明文不足 64 字節(jié) , 因此從 32 位開(kāi)始 , 對(duì)明文進(jìn)行填充 , 具體填充的做法請(qǐng)見(jiàn)上面的代碼
5. 不斷地使用 xor 和 perm 函數(shù)對(duì)函數(shù)中定義的數(shù)組進(jìn)行處理 (大約總共五百多次)
5.1. xor() : xor 的話(huà)在異或一次就可以得到明文
5.2. perm() : 將明文 r(64個(gè)字節(jié)(元素)) 和加密向量 imm(512個(gè)元素) 進(jìn)行運(yùn)算得到密文 n
5.2.1. 循環(huán)計(jì)數(shù)器 i , 除以 8 得到目標(biāo)字節(jié)的索引
5.2.2. 循環(huán)計(jì)數(shù)器 i , 求余 8 得到目標(biāo)字節(jié)的 bit 的索引
5.2.3. imm的元素 , 判斷是否小于 0 , 小于 0 , 則本次循環(huán)中取得的 bit 要與 1 異或 , 大于則不進(jìn)行異或處理
5.2.4. imm的元素 , 將其除以 8 得到 src_byte , 源字節(jié)
5.2.5. imm的元素 , 將其求余 8 得到單個(gè)字節(jié)的 bit 數(shù)
5.2.6. 最后將 r 的 sct_byte 的 src_bit 移動(dòng)到 n 的 dst_byte 的 dst_bit 位置
5.2.7. 循環(huán) 512 次直到所有 bit 都被移動(dòng)
6. 處理結(jié)束 , 然后將這個(gè)數(shù)組轉(zhuǎn)換成 16 進(jìn)制字符串 , 并返回 , 得到明文的哈希
那么我們現(xiàn)在可以知道的是最終的哈希 , 以及最后一次 perm 函數(shù)中使用到的加密向量 imm
那我們就可以反推出最后一次 perm 函數(shù)執(zhí)行前明文 r 的值
然后就可以一直反推回去 , 就可以得到最終的明文 , 也就是 r 的前 27 字節(jié)
將 js 代碼進(jìn)行簡(jiǎn)單修改即可
這里有一個(gè)需要注意的技巧就是 , 這里有大量的 xor 函數(shù)和 perm 函數(shù)需要逆序執(zhí)行
這里給出兩種方法 :
1. 神馬大哥的方法 : 使用 sublime : Edit->Permute Lines->Reverse
2. linux下使用 tac 命令
最終的 js 代碼如下 :
let r = [
0x98, 0x3b, 0xb3, 0x5e, 0xd0, 0xa8, 0x00, 0xfc,
0xc8, 0x5d, 0x12, 0x80, 0x6d, 0xf9, 0x22, 0x53,
0x64, 0x71, 0x3b, 0xe5, 0x78, 0xba, 0x67, 0xf6,
0x5b, 0xc5, 0x08, 0xb7, 0x7f, 0x0c, 0x54, 0x87,
0x8e, 0xda, 0x18, 0xa5, 0xee, 0xd5, 0x0b, 0xac,
0x70, 0x5b, 0xdc, 0x7d, 0xb2, 0x05, 0x62, 0x32,
0x21, 0xe8, 0xff, 0xe3, 0x30, 0x48, 0x39, 0x55,
0xa2, 0x22, 0x16, 0x96, 0x07, 0x54, 0xa1, 0x22
];
// 定義 Math.sgn 函數(shù)
Math.sgn = function(a) { return 1/a<0; };
// 定義 xor 函數(shù)
let xor = function (imm) {
for (let i = 0; i < 64; i++) {
r[i] ^= imm[i];
}
};
// 定義 perm 函數(shù)
let perm = function (imm) {
let n = new Uint8Array(64);
for (let i = 0; i < 512; i++) {
const dst_bit = i%8;
const dst_byte = i/8|0;
const sign = Math.sgn(imm[i]);
const idx = sign ? -imm[i] : imm[i];
const src_bit = idx%8;
const src_byte = idx/8|0;
let b = (r[dst_byte] >> dst_bi) & 1; // 調(diào)換 dst 和 src
if (sign) { b ^= 1; }
n[src_byte] |= b << src_bitt;
}
r = n;
};
/*
* 這里是已經(jīng)逆向排序的 xor 和 perm 函數(shù)
*/
// 輸出 r
for(let i = 0; i < 64; i++){
console.log(r[i]);
}