whip1ash

hctf babycrack writeup -- 第一次做js的逆向

2017-11-18

hctf babycrack writeup – 第一次做js的逆向

菜鸡写的超详细writeup
emmm…..
原来也没接触过js逆向 这也是第一次做 比较菜 大佬勿喷…
ps:写的比较乱 参考的时候先搜索原本函数 然后再进行变形
我这里是通过chrome的debug去打断点分析和terminal对变量做一些操作去推测并验证
把分析出来的已知函数用起个名然后全局替换

一块块拆出来吧 以下顺序为函数调用顺序

1
2
3
4
5
6
7
8
(function(_0xd4b7d6, _0xad25ab) {
var _0x5e3956 = function(_0x1661d3) {
while (--_0x1661d3) {
_0xd4b7d6['push'](_0xd4b7d6['shift']());
}
};
_0x5e3956(++_0xad25ab);
}(_0x180a, 0x1a2));

优化一下

1
2
3
4
5
6
7
8
(function(function_stack, param2) {
var push_shift = function(param) {
while (--param) {
function_stack['push'](function_stack['shift']());
}
};
push_shift(++param2);
}(function_stack, 0x1a2));


由此可以看出,通过array_name['function_name']()可以调用数组的自带方法,等价于array_name.function_name()
这里function_stack.push(function_stack.shift()) 0x1a2次
刚开始感觉这里没啥用 其实是把array的第一个元素给放到最后一个 一共做了0x1a2次
最后这个数组长这个样

1
2
3
4
window['onload'] = function() {
setInterval(_0xa180('0x38'), 0x32);
test();
};

给_0xa180起个名叫exec_function,去上面看看这个函数干啥了

1
2
3
4
5
var _0xa180 = function(_0x5c351c, _0x2046d8) {
_0x5c351c = _0x5c351c - 0x0;
var _0x26f3b3 = function_stack[_0x5c351c];
return _0x26f3b3;
};

很明显了,是把函数名从function_stack里拿出来

1
2
3
4
5
var exec_function = function(position, _0x2046d8) {
position = position - 0x0;
var function_name = function_stack[position];
return function_name;
};

这块也很明显了,每50ms调用变形后的function_stack[0x38]就是window.console.clear();window.console.log('Welcome to HCTF :>')'
然后再来看调用的test()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
function test() {
var _0x5bf136 = document[exec_function('0x32')](exec_function('0x33'))['value'];
if (_0x5bf136 == '') {
console[exec_function('0x34')](exec_function('0x35'));
return ![];
}
var _0x4d0e29 = check(_0x5bf136);
if (_0x4d0e29) {
alert(exec_function('0x36'));
} else {
alert(exec_function('0x37'));
}
}

还原回去

1
2
3
4
5
6
7
8
9
10
11
12
13
function test() {
var value = document.getElementById(message)['value'];
if (value == '') {
console.log('Welcom to HCTF :>');
return ![];
}
var status = check(value);
if (status) {
alert("Congratulations! you got it!");
} else {
alert("Sorry, you are wrong...");
}
}

应该到了核心的部分 check函数 这个函数比较复杂 一点点拆开看吧

1
2
3
var _0x2e2f8d = ['code', exec_function('0x0'), exec_function('0x1'), exec_function('0x2'), 'invalidMonetizationCode', exec_function('0x3'), exec_function('0x4'), exec_function('0x5'), charAt, exec_function('0x7'), exec_function('0x8'), exec_function('0x9'), exec_function('0xa'), exec_function('0xb'), exec_function('0xc'), exec_function('0xd'), exec_function('0xe'), exec_function('0xf'), exec_function('0x10'), exec_function('0x11'), 'url', exec_function('0x12'), exec_function('0x13'), exec_function('0x14'), exec_function('0x15'), exec_function('0x16'), exec_function('0x17'), exec_function('0x18'), 'tabs', exec_function('0x19'), exec_function('0x1a'), exec_function('0x1b'), exec_function('0x1c'), exec_function('0x1d'), 'replace', exec_function('0x1e'), exec_function('0x1f'), 'includes', exec_function('0x20'), 'length', exec_function('0x21'), exec_function('0x22'), exec_function('0x23'), exec_function('0x24'), exec_function('0x25'), exec_function('0x26'), exec_function('0x27'), exec_function('0x28'), exec_function('0x29'), 'toString', exec_function('0x2a'), 'split'];
var _0x50559f = value[_0x2e2f8d[0x5]](0x0, 0x4);
var _0x5cea12 = parseInt(btoa(_0x50559f), 0x20);

首先通过exec_function()把函数名都拿出来,然后再还原一下

1
2
3
4
5
6
7
8
var check_func_stack = ['code', 'version', 'error', 'download', 'invalidMonetizationCode', 'substring', 'push', 'Function', 'charAt', 'idle', 'pyW5F1U43VI', 'init', 'https://the-extension', 'local', 'storage', 'eval', 'then', 'get', 'getTime', 'setUTCHours', 'url', 'origin', 'set', 'GET', 'loading', 'status', 'removeListener', 'onUpdated', 'tabs', 'callee', 'addListener', 'onMessage', 'runtime', 'executeScript', 'replace', 'data', 'test', 'includes', 'http://', 'length', 'Url error', 'query', 'filter', 'active', 'floor', 'random', 'charCodeAt', 'fromCharCode', 'parse', 'toString', 'substr', 'split'];

//取5位;
var sub_value = value.substring(0x0, 0x4);

//btoa() base64;
//parseInt()将一个字符串解析成整数;
var parse_int = parseInt(btoa(sub_value), 0x20);

然后有一个eval(),这个eval()让我花了大概有两个小时,我发现这个eval好像没有什么用处,正则什么都没过滤
这个eval好像是防debug的 一直在debuger里面出不来 我给打掉了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
eval(function(_0x200db2, _0x177f13, _0x46da6f, _0x802d91, _0x2d59cf, _0x2829f2) {
_0x2d59cf = function(_0x4be75f) {
return _0x4be75f['toString'](_0x177f13);
}
;
if (!''['replace'](/^/, String)) {
while (_0x46da6f--)
_0x2829f2[_0x2d59cf(_0x46da6f)] = _0x802d91[_0x46da6f] || _0x2d59cf(_0x46da6f);
_0x802d91 = [function(_0x5e8f1a) {
return _0x2829f2[_0x5e8f1a];
}
];
_0x2d59cf = function() {
return exec_function('0x2b');
}
;
_0x46da6f = 0x1;
}
;while (_0x46da6f--)
if (_0x802d91[_0x46da6f])
_0x200db2 = _0x200db2[exec_function('0x2c')](new RegExp('\x5cb' + _0x2d59cf(_0x46da6f) + '\x5cb','g'), _0x802d91[_0x46da6f]);
return _0x200db2;
}(exec_function('0x2d'), 0x11, 0x11, exec_function('0x2e')['split']('|'), 0x0, {}));

这里的过程写注释里了 一般遇到function(){}()这种形式,我都是从后面的传参开始看,所以这个函数调用顺序应该是从后面真的调用开始

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
//注意 这里为了好看我把参数位置换了一下
//_0x177f13 = 0x11 _0x46da6f = 0x11 _0x2d59cf=0x0 _0x2829f2 = {}
eval(function(_0x200db2, _0x802d91, _0x177f13 , _0x46da6f , _0x2d59c, _0x2829f2) {
toString_function = function(str) {
//toString(0x11)
//String 类型
return str.toString(_0x177f13);
};

//这个if恒为真 在下面把这块分块还原了一下 在注释里
// if (!''['replace'](/^/, String)) {
// while (_0x46da6f--)
// _0x2829f2[_0x2d59cf(_0x46da6f)] = _0x802d91[_0x46da6f] || _0x2d59cf(_0x46da6f);
// _0x802d91 = [function(_0x5e8f1a) {
// return _0x2829f2[_0x5e8f1a];
// }
// ];
// _0x2d59cf = function() {
// return exec_function('0x2b');
// }
// ;
// _0x46da6f = 0x1;
// }

//while(0x11) 感觉这里没用 先跳
while (_0x46da6f--)
//toString_funcrion(0x11)=10
//{}.toString_function(0x11) = split_unknow_str2[0x11] || toString_function(0x11);
_0x2829f2[toString_function(_0x46da6f)] = _0x802d91[_0x46da6f] || toString_function(_0x46da6f);


// split_unknow_str2 = [function(param){
// return {}[param]
// }];
_0x802d91 = [function(param) {
return _0x2829f2[param];
}];

return_a_w = function() {
return "\w+";
};
_0x46da6f = 0x1;

//这个while只执行一次 if在这个while里为真 其实就是个正则了 通过我在console的测试 这里什么都没滤掉 \x5c 就是 \
while (_0x46da6f--)
if (_0x802d91[_0x46da6f])
_0x200db2 = _0x200db2['replace'](new RegExp('\x5cb' + '\w+' + '\x5cb','g'), _0x802d91[_0x46da6f]);
return _0x200db2;
}(unknow_str1, split_unknow_str2, 0x11, 0x11, 0x0, '{}'));

// var unknow_str1 = "(3(){"+"(3 a(){7{(3 b(2){9((''+(2/2)).5!==1||2%g===0){(3(){}).8('4')()}c{4}b(++2)})(0)}d(e){f(a,6)}})()})();";
// var unknow_str2 = '||i|function|debugger|length|5000|try|constructor|if|||else|catch||setTimeout|20';

//split_unknow_str2 = unknow_str.split('|');

// var split_unknow_str2 = ["", "", "i", "function", "debugger", "length", "5000", "try", "constructor", "if", "", "", "else", "catch", "", "setTimeout", "20"];

//由此感觉上面整段函数没有什么用

233333 在上面那个eval浪费了不少时间 好像是个防debug的 一直在debuger里出不来…..
于是我把他注释掉了
往下走

1
2
3
4
5
6
7
8
(function(_0x3291b7, _0xced890) {
var _0xaed809 = function(_0x3aba26) {
while (--_0x3aba26) {
_0x3291b7[exec_function('0x4')](_0x3291b7['shift']());
}
};
_0xaed809(++_0xced890);
}(_0x2e2f8d, _0x5cea12 % 0x7b));

还原回去可以比较简单的看出来,这又是一个打乱函数栈的操作

1
2
3
4
5
6
7
8
(function(check_func_stack, param) {
var random_positon = function(count_num) {
while (--count_num) {
check_func_stack.push(check_func_stack.shift());
}
};
random_positon(++param);
}(check_func_stack, parse_int % 0x7b));

但是这里循环次数是个变量… 所以不可预测了 往后看吧
ps:parse_int 在上面有赋值

1
2
3
4
5
6
7
8
9
10
11
12
var _0x43c8d1 = function(_0x3120e0) {
var _0x3120e0 = parseInt(_0x3120e0, 0x10);
var _0x3a882f = _0x2e2f8d[_0x3120e0];
return _0x3a882f;
};
var _0x1c3854 = function(_0x52ba71) {
var _0x52b956 = '0x';
for (var _0x59c050 = 0x0; _0x59c050 < _0x52ba71[_0x43c8d1(0x8)]; _0x59c050++) {
_0x52b956 += _0x52ba71[_0x43c8d1('f')](_0x59c050)[_0x43c8d1(0xc)](0x10);
}
return _0x52b956;
};

定义两个函数 有一个获取函数名的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var get_func_name = function(func_pos) {
var func_pos = parseInt(func_pos, 0x10);
var function_name = check_func_stack[func_pos];
return function_name;
};


var _0x1c3854 = function(param) {
var _0x52b956 = '0x';
for (var i=0; i < param[get_func_name(0x8)]; i++) {
_0x52b956 += param[get_func_name('f')](i)[get_func_name(0xc)](0x10);
}
return _0x52b956;
};

等后面调用了再回来细看,往后走

1
var _0x76e1e8 = value[get_func_name(0xe)]('_');

我在上面这一句打了个断点 随便输入了个值 发现直接跳到了exception 所以推测刚才那个随机化的函数并不是让函数栈完全随机化 而是我们输入的value的前五个字符在经过处理后要等于一个或一些特定的值 让函数栈处于一个特殊的状态 才能继续执行而不是不到exception
所以可以确定这么几个条件
字符串自带方法
最少只需要一个函数

断网了 明天再做

看看check_func_stack里有没有合适的函数
结合下面的对_0x76e1e8的调用

1
2
3
4
var _0x34f55b = (_0x1c3854(_0x76e1e8[0x0][get_func_name(0xd)](-0x2, 0x2)) ^ _0x1c3854(_0x76e1e8[0x0][get_func_name(0xd)](0x4, 0x1))) % _0x76e1e8[0x0][get_func_name(0x8)] == 0x5;
if (!_0x34f55b) {
return ![];
}

挺像数组的 所以猜测这里调用的是split函数
跑了一下 没有跳进exception 基本确定这里就是split
既然定位出来了 这里只需要让get_func_name(0xe)返回split就行了
结合上面函数的定义看get_func_name 的这一句

1
var func_pos = parseInt(func_pos, 0x10);

在下面我们传进去的是0xe 这里有一个小坑 js在执行parseInt的时候 首先将0xe转换成10进制的14 然后再进行16进制的转换 所以这里结果是一个20
所以我们要生成一些字符串符合这个规则 可以结合源代码和上面分析一下他的这个打乱函数栈的函数 然后模拟这个过程 生成一些随机字符串就可以跳过这个exception
下面贴出我瞎写的python

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
# coding: utf-8
import random
import string
import base64
# base64中32进制不支持的字符
block_str = ['w','x','y','z','W','X','Y','Z','+','/','=']

while 1:
random_str = ''.join(random.sample(string.ascii_letters+string.digits,4))
# random_str = 'test'
base64_str = base64.b64encode(random_str)

try:
parse_int = int(base64_str,32)
except ValueError,e:
# js的parseInt()把不符合要求的字符串都给截掉了 python的Int()却没有 只是傻傻的抛出一个exception 所以这里处理一下
for bl in block_str:
pos = base64_str.find(bl)
if not pos == -1 :
base64_str = base64_str[:pos]

if not base64_str.strip()=='':
parse_int = int(base64_str,32)
else:
continue


if (parse_int % 0x7b) == 31 or ((parse_int%0x7b)%52) == 31 :
print random_str

然后我们在分析这个做了一大坨计算的地方

1
var _0x34f55b = (_0x1c3854(_0x76e1e8[0x0][get_func_name(0xd)](-0x2, 0x2)) ^ _0x1c3854(_0x76e1e8[0x0][get_func_name(0xd)](0x4, 0x1))) % _0x76e1e8[0x0][get_func_name(0x8)] == 0x5;

通过正常的函数栈替换一下

这一块的意思就显而易见了 把_0x1c3854这个函数一起还原回来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//_0x1c3854
var string2hex = function(param) {
var hex_num = '0x';
for (var i=0; i < param.length; i++) {
//把字符串每一位的unicode编码转化成16进制的字符串 并拼接
hex_num += param.charCodeAt(i).toString(0x10);
}
return hex_num;
};

//我看下面的调用有点像数组的索引 所以我在这猜这个函数是split
//_0x76e1e8
var split_value = value.split('_');

//debug了一次 没有跳exception 说明这个序列是对的 这里看看它干啥了 让这成个true
//_0x34f55b
var cor_flag = (string2hex(split_value[0].substr(-0x2, 0x2)) ^ string2hex(split_value[0].substr(0x4, 0x1))) % split_value[0].length == 0x5;

if (!cor_flag) {
return ![];
}

这里有点傻了 在text输入框的时候有一个提示 hctf{xxxxxxxxxxx}
这一段代码在用’‘分割了后取了最后两位
由此可以猜前几位是这样的
`hctf{xx
`

给刚才那个python添加点东西,跑一下

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
# coding: utf-8
import random
import string
import base64
import math
import binascii

# base64中32进制不支持的字符
block_str = ['w','x','y','z','W','X','Y','Z','+','/','=']

def string2hex(string0):
hex_num = '0x'
for i in range(len(string0)):
hex_num += binascii.b2a_hex(string0[i])

try:
hex_num = int(hex_num,0x10)
except ValueError :
pass
# print random_str


return hex_num

while 1:
random_str_1 = ''.join(random.sample(string.punctuation+string.digits+string.letters,2))
random_str = 'hctf{'+random_str_1

sub_random_str = random_str[:4:]
base64_str = base64.b64encode(sub_random_str)

try:
parse_int = int(base64_str,32)
except ValueError,e:
# js的parseInt()把不符合要求的字符串都给截掉了 python的Int()却没有 只是傻傻的抛出一个exception 所以这里处理一下
for bl in block_str:
pos = base64_str.find(bl)
if not pos == -1 :
base64_str = base64_str[:pos]

if not base64_str.strip()=='':
parse_int = int(base64_str,32)
else:
continue


split_value = random_str.split('_')

try:
cor_flag = ((string2hex(split_value[0][-2::]) ^ string2hex(split_value[0][4])) % len(split_value[0]) == 5)
except IndexError:
continue

if cor_flag:
print random_str

发现这里不是唯一解,这里随便选一个
hctf{95_
下面才真正到答题的时候

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
b2c = function(_0x3f9bc5) {
var _0x3c3bd8 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var _0x4dc510 = [];
var _0x4a199f = Math[exec_function('0x25')](_0x3f9bc5[_0x43c8d1(0x8)] / 0x5);
var _0x4ee491 = _0x3f9bc5[_0x43c8d1(0x8)] % 0x5;
if (_0x4ee491 != 0x0) {
for (var _0x1e1753 = 0x0; _0x1e1753 < 0x5 - _0x4ee491; _0x1e1753++) {
_0x3f9bc5 += '';
}
_0x4a199f += 0x1;
}
for (_0x1e1753 = 0x0; _0x1e1753 < _0x4a199f; _0x1e1753++) {
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5) >> 0x3));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5) & 0x7) << 0x2 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) >> 0x6));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) & 0x3f) >> 0x1));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) & 0x1) << 0x4 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x2) >> 0x4));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x2) & 0xf) << 0x1 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) >> 0x7));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) & 0x7f) >> 0x2));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) & 0x3) << 0x3 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x4) >> 0x5));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x4) & 0x1f));
}
var _0x545c12 = 0x0;
if (_0x4ee491 == 0x1)
_0x545c12 = 0x6;
else if (_0x4ee491 == 0x2)
_0x545c12 = 0x4;
else if (_0x4ee491 == 0x3)
_0x545c12 = 0x3;
else if (_0x4ee491 == 0x4)
_0x545c12 = 0x1;
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++)
_0x4dc510[exec_function('0x2f')]();
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++)
_0x4dc510[_0x43c8d1('1b')]('=');
(function() {
(function _0x3c3bd8() {
try {
(function _0x4dc510(_0x460a91) {
if (('' + _0x460a91 / _0x460a91)[exec_function('0x30')] !== 0x1 || _0x460a91 % 0x14 === 0x0) {
(function() {}
['constructor']('debugger')());
} else {
debugger ;
}
_0x4dc510(++_0x460a91);
}(0x0));
} catch (_0x30f185) {
setTimeout(_0x3c3bd8, 0x1388);
}
}());
}());
return _0x4dc510[exec_function('0x31')]('');
}
;
e = _0x1c3854(b2c(_0x76e1e8[0x2])[_0x43c8d1(0xe)]('=')[0x0]) ^ 0x53a3f32;
if (e != 0x4b7c0a73) {
return ![];
}
f = _0x1c3854(b2c(_0x76e1e8[0x3])[_0x43c8d1(0xe)]('=')[0x0]) ^ e;
if (f != 0x4315332) {
return ![];
}

通过函数栈把函数名还原回去 并且把中间的debugger给扣掉

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
b2c = function(param_str) {
var base_str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var b2c_fun_stack = [];
var random_len_num = Math['floor'](param_str.length() / 0x5);
var len_con = param_str.length() % 0x5;
//使其长度为5的倍数 不是空格来凑
if (len_con != 0) {
for (var i = 0; i < 0x5 - len_con; i++) {
param_str += '';
}
random_len_num += 1;
}

for (i = 0; i < random_len_num; i++) {
b2c_fun_stack.push(base_str.charAt(param_str.charCodeAt(i * 0x5) >> 0x3));
b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5) & 0x7) << 0x2 | param_str.charCodeAt(i * 0x5 + 1) >> 0x6));
b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 1) & 0x3f) >> 1));


b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 1) & 1) << 4 | param_str.charCodeAt(i * 0x5 + 0x2) >> 4));


b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 0x2) & 0xf) << 1 | param_str.charCodeAt(i * 0x5 + 0x3) >> 0x7));

b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 0x3) & 0x7f) >> 0x2));
b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 0x3) & 0x3) << 0x3 | param_str.charCodeAt(i * 0x5 + 4) >> 0x5));
b2c_fun_stack.push(base_str.charAt(param_str.charCodeAt(i * 0x5 + 4) & 1f));
}

var _0x545c12 = 0;
if (len_con == 1)
_0x545c12 = 0x6;
else if (len_con == 0x2)
_0x545c12 = 4;
else if (len_con == 0x3)
_0x545c12 = 0x3;
else if (len_con == 4)
_0x545c12 = 1;

for (i = 0; i < _0x545c12; i++)
b2c_fun_stack['pop']();
un_stack
for (i = 0; i < _0x545c12; i++)
b2c_fun_stack.push('=');

//放debug 不知道什么时候能跑出来
// (function() {
// (function _0x3c3bd8() {
// try {
// (function _4dc510(_460a91) {
// if (('' + _460a91 / _460a91).length() !== 1 || _460a91 % 14 === 0) {
// (function() {}
// ['constructor']('debugger')());
// } else {
// debugger ;
// }
// _4dc510(++_460a91);
// }(0));
// } catch (_0x30f185) {
// setTimeout(_0x3c3bd8, 1388);
// }
// }());
// }());
return b2c_fun_stack['join']('');
};

e = string2hex(b2c(split_value[2]).split('=')[0]) ^ 0x53a3f32;
if (e != 4b7c0a73) {
return ![];
}
f = string2hex(b2c(split_value[3]).split('=')[0]) ^ e;

if (f != 4315332) {
return ![];
}

这里明显有唯一解,我在这一步彻底卡死了,用python跑了一下午也没跑出来
可以在这分析一下条件,这里这个异或给逆运算一下,发现string2hex(b2c(split_value[2]).split('=')[0])的返回值必须为0x4e463541也就是b2c(split_value[2]).split('=')[0]的返回值为NF5A
由于我是用python跑的,python在数据处理上跟js存在一定的差异,好几个地方抛出了exception,当时感觉不影响直接就给continue了,从而导致我在python里测试当splie.value为6位的时候,返回值是4位…….
然后想用js去爆破,一跑就给浏览器跑死了……
后来用python去执行js 用了个execjs这个模块 就是慢了点

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
# coding: utf-8
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import execjs
import random
import string


def get_js():
f = open("random_str.js", 'r')
line = f.readline()
htmlstr = ''
while line:
htmlstr = htmlstr + line
line = f.readline()
return htmlstr

js_file_content = get_js()
ctx = execjs.compile(js_file_content)


while 1:
burp = ''.join(random.sample(string.digits+string.letters,6))
b2c_string = ctx.call('b2c',burp)
print "%s ----------------- %s" % (b2c_string,burp)
e = b2c_string.split('=')[0]
e = ctx.call('string2hex',e)
e = e ^ 0x53a3f32
if e== 0x4b7c0a73:
print 'result: %s ------------ %s'%(b2c_string,burp)
break

random_str.js

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
function b2c (param_str) {
var base_str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var b2c_fun_stack = [];
var random_len_num = Math['floor'](param_str.length / 0x5);
var len_con = param_str.length % 0x5;
//使其长度为5的倍数 不是空格来凑
if (len_con != 0) {
for (var i = 0; i < 0x5 - len_con; i++) {
param_str += '';
}
random_len_num += 1;
}

for (i = 0; i < random_len_num; i++) {
b2c_fun_stack.push(base_str.charAt(param_str.charCodeAt(i * 0x5) >> 0x3));
b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5) & 0x7) << 0x2 | param_str.charCodeAt(i * 0x5 + 1) >> 0x6));
b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 1) & 0x3f) >> 1));


b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 1) & 1) << 4 | param_str.charCodeAt(i * 0x5 + 0x2) >> 4));


b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 0x2) & 0xf) << 1 | param_str.charCodeAt(i * 0x5 + 0x3) >> 0x7));

b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 0x3) & 0x7f) >> 0x2));
b2c_fun_stack.push(base_str.charAt((param_str.charCodeAt(i * 0x5 + 0x3) & 0x3) << 0x3 | param_str.charCodeAt(i * 0x5 + 4) >> 0x5));
b2c_fun_stack.push(base_str.charAt(param_str.charCodeAt(i * 0x5 + 4) & 0x1f));
}

var _0x545c12 = 0;
if (len_con == 1)
_0x545c12 = 0x6;
else if (len_con == 0x2)
_0x545c12 = 4;
else if (len_con == 0x3)
_0x545c12 = 0x3;
else if (len_con == 4)
_0x545c12 = 1;

for (i = 0; i < _0x545c12; i++)
b2c_fun
_stack['pop']();

for (i = 0; i < _0x545c12; i++)
b2c_fun_stack.push('=');

return b2c_fun_stack['join']('');
};

var string2hex = function(str) {
var hex_num = '0x';
for (var i=0; i < str.length; i++) {
//把字符串每一位的unicode编码转化成16进制的字符串 并拼接
hex_num += str.charCodeAt(i).toString(0x10);
}
hex_num = parseInt(hex_num,0x10);
return hex_num;
};

跑出来这一个是iz
其实这是个base32……
直接解就能解出来…….
第二个部分就同理了
出来是s0

下面这一部分就是在确定split_value[1]了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
n = f * e * _0x76e1e8[0x0][_0x43c8d1(0x8)];
h = function(_0x4c466e, _0x28871) {
var _0x3ea581 = '';
for (var _0x2fbf7a = 0x0; _0x2fbf7a < _0x4c466e[_0x43c8d1(0x8)]; _0x2fbf7a++) {
_0x3ea581 += _0x28871(_0x4c466e[_0x2fbf7a]);
}
return _0x3ea581;
}
;
j = _0x76e1e8[0x1][_0x43c8d1(0xe)]('3');
if (j[0x0][_0x43c8d1(0x8)] != j[0x1][_0x43c8d1(0x8)] || (_0x1c3854(j[0x0]) ^ _0x1c3854(j[0x1])) != 0x1613) {
return ![];
}
k = _0xffcc52=>_0xffcc52[_0x43c8d1('f')]() * _0x76e1e8[0x1][_0x43c8d1(0x8)];
l = h(j[0x0], k);
if (l != 0x2f9b5072) {
return ![];
}

把已知条件带进去再猜一猜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sum = 0x4315332 * 0x4b7c0a73 * split_value[0].length;

h = function(param_str, func_name) {
var str = '';
for (var i = 0; i < param_str.length(); i++) {
str += func_name(param_str[i]);
}
return str;
};

j = split_value[1].split('3');

if (j[0].length() != j[1].length() || (string2hex(j[0]) ^ string2hex(j[1])) != 1613) {
return ![];
}

k = _0xffcc52=>_0xffcc52.charCodeAt() * split_value[1].length();
//用k函数去去处理 每一位
l = h(j[0], k);
if (l != 0x2f9b5072) {
return ![];
}

这里有两个判断,第一个判断,split_value[1]被3分割,左右两边长度相等.这两部分异或等于1613.这个k函数是es6的函数定义方法.所以l就是使用k函数去处理j[0]的每一位,并拼接.处理后要等于0x2f9b5072.
这里有一个简单的逻辑可以定位出来split_value[1]的长度.
这个l是几个数字拼接起来的字符串,从十六进制上看,l可以拆成4位,如果这样拆的话,那么split_value[1]的长度就是9位,那么两位两位拆开除以9的话可以发现值非常小,都是特殊字符.所以这里把这个值转化成十进制,发现为798707826三位三位拆开并除以7,就是
114 101 118查ascii,就是rev.然后根据条件,异或,拆开,对照ascii转换,发现另外三位是rse
所以split_value[1]是rev3rse.

快结束了,现在已经能确定 hctf{xx_rev3rse_iz_s0_xxxxxxxxxx
根据提示应该只有最后一部分了

1
2
3
4
5
6
7
8
9
10
11
12
13
m = _0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x0, 0x4)) - 0x48a05362 == n % l;
function _0x5a6d56(_0x5a25ab, _0x4a4483) {
var _0x55b09f = '';
for (var _0x508ace = 0x0; _0x508ace < _0x4a4483; _0x508ace++) {
_0x55b09f += _0x5a25ab;
}
return _0x55b09f;
}
if (!m || _0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x5, 0x1), 0x2) == _0x76e1e8[0x4][_0x43c8d1(0xd)](-0x5, 0x4) || _0x76e1e8[0x4][_0x43c8d1(0xd)](-0x2, 0x1) - _0x76e1e8[0x4][_0x43c8d1(0xd)](0x4, 0x1) != 0x1) {
return ![];
}
o = _0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6, 0x2))[_0x43c8d1(0xd)](0x2) == _0x76e1e8[0x4][_0x43c8d1(0xd)](0x6, 0x1)[_0x43c8d1('f')]() * _0x76e1e8[0x4][_0x43c8d1(0x8)] * 0x5;
return o && _0x76e1e8[0x4][_0x43c8d1(0xd)](0x4, 0x1) == 0x2 && _0x76e1e8[0x4][_0x43c8d1(0xd)](0x6, 0x2) == _0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x7, 0x1), 0x2);

转换+看函数作用+改名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//sum % l == 0x8A7600E4B62483A % 0x2f9b5072 == 0x1f941f3c
//这里解出来 'h4r\x9e'
m = string2hex(split_value[4].substr(0, 4)) - 48a05362 == sum % l;
//循环拼接字符串
function _0x5a6d56(param_str, count) {
var str0 = '';
for (var i = 0; i < count; i++) {
str0 += param_str;
}
return str0;
}


if (!m || _0x5a6d56(split_value[4]['substr'](0x5, 1), 0x2) == split_value[4]['substr'](-0x5, 4) || split_value[4]['substr'](-0x2, 1) - split_value[4]['substr'](4, 1) != 1) {
return ![];
}



o = string2hex(split_value[4]['substr'](0x6, 0x2))['substr'](0x2) == split_value[4]['substr'](0x6, 1).charCodeAt() * split_value[4].length * 0x5;

p = split_value[4]['substr'](4, 1) == 0x2 && split_value[4]['substr'](0x6, 0x2) == _0x5a6d56(split_value[4]['substr'](0x7, 1), 0x2);

return o && p

第一句这个,我们逆运算一下,就length一个变量,从6开始试一试就行了,试到7你就会发现解出来h4r\x9e

binascii.a2b_hex( hex(((0x4315332 * 0x4b7c0a73 * length)%0x2f9b5072)+0x48a05362)[2:] )

这里不知道为啥最后会出来个\x9e,

不管了,往后看,后面几个判断我们可以发现有几个条件

1
2
3
4
5
1. 第六位重复拼接两次 不等于 从倒数第五位开始取,取4位
2. 倒数第2位-第5位=1
3. 第7位取2位,字符串转换成16进制的字符串并剔除前两个字符,也就是0x 等于 第七位的ascii值 * split_value[4]的长度 * 5
4. 第五位是2
5. 第七位取两位 = 第8位循环两次 , 说明第7位又第8位相同

仔细读题,发现第3个条件可以爆破,于是爆破之

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# coding: utf-8
import string
import random
import binascii

b=0
while 1:
burp = ''.join(random.sample(string.letters+string.digits,1))

print burp
for length in range(1,20):
a = (binascii.b2a_hex(burp+burp) == str(ord(burp) * length * 5))
if a:
print 'result '+burp+' length '+str(length)
b = 1
break
if b:
break

就发现结果是e 一共13位
综合原来的flag,现在我们已经知道为 hctf{??_rev3rse_iz_s0_h4r?23ee3333}
现在可以通过hash爆破了

1
2
3
4
5
6
7
8
9
10
11
12
13
import hashlib
flag_1="hctf{"
flag_2 = "_rev3rse_iz_s0_h4r"
flag_3 = "23ee3333}"
for i in xrange(0,128):
for j in xrange(0,128):
for k in xrange(0,128):
string0=chr(i)+chr(j)
string1=chr(k)
flag_hash = hashlib.sha256()
flag_hash.update(flag_1+string0+flag_2+string1+flag_3)
if flag_hash.hexdigest() == "d3f154b641251e319855a73b010309a168a12927f3873c97d2e5163ea5cbb443":
print flag_1+string0+flag_2+string1+flag_3

跑一下就ok了
完….

扫描二维码,分享此文章