# 6.3.1 web HCTF2017 babycrack - [题目解析](#题目解析) - [解题流程](#解题流程) [下载文件](../src/writeup/6.3.1_web_hctf2017_babycrack) ## 题目解析 题目就不用多说了,很容易发现是 JavaScript 代码审计。 整个文件的变量名/函数名可以看作是混淆了的,分析一下整个文件的结构: ``` —— |- _0x180a,关键字的替换数组 |- 匿名函数,对数组元素进行重排 |- _0xa180,取出对应索引的数组元素 |- check,主要的分析函数 |- test,主要的运行函数 ``` 这道题结合浏览器进行动态调试,可以节省很多脑力。 首先是重排,这里不需要去深究到底逻辑原理,让引擎代替你去把数组重排好即可。结合程序员计算器和 CyberChef 分析更加方便。 ## 解题流程 这样我们可以直接进入 check 函数进行分析。 ``` —— |- _0x2e2f8d,又一次进行数组混淆,得到一个新数组 |- _0x50559f,获取 flag 的前四位,即 ‘hctf’ |- _0x5cea12,由 ‘hctf’ 生成一个基数 |- 这里有一个 debug 的事件,个人认为是阻止使用 F12 调试用的,所以可以直接删去 |- 匿名函数,对 _0x2e2f8d 这个数组再进行排列 |- _0x43c8d1,根据输入获取数组中相应值的函数 |- _0x1c3854,将输入的 ascii 码转化为 16 进制,再加上 ‘0x’ ``` 以上部分可以看成是准备部分,这一部分的难点在于多次处理了数组,在动态调试时,很多函数如果多次执行就会产生与原答案不同的数组结构,因此,每次执行都需要重新初始化。 ``` —— |- _0x76e1e8,以下划线分割输入,从后面分析可以得知 flag 一共有 5 段 |- _0x34f55b,这一段给出了第一个逆向的条件,结合下一句 if 条件。 ``` 单独来分析,其实最初我看掉了一个括号,结果弄混了符号优先级,导致觉得这个条件没有意义。 这个条件是说,**第一段的最后两个字符的 16 进制和 ‘{’ 的 16 进制异或后,对第一段的长度求余应该等于 5 **。 这里可以先进行如下猜测 第一段,已经有 ‘hctf{’ 了,这里正好去最后两位,先猜测第一段一共只有 7 位,这个猜测是后验的,先不细说。 ``` —— |- b2c ``` 理解这个函数极为重要,通过随机输入进行测试,输出结果有些眼熟,像是 base64 但不对,比对后确定是 base32 编码,知道这个就不用再去多解读它了。同时,这里也有一个 debug 需要删除 ``` —— |- e,第二个逆向条件 ``` 这一句是说,**第三段做 base32 编码,取等号前的部分,再进行 16 进制和 0x53a3f32 异或等于 0x4b7c0a73 ** ``` 计算 0x4b7c0a73^0x53a3f32=0x‭4E463541‬ ‭4E463541 => NF5A 16 进制转字符 NF5A => iz base32 解码 ``` 因此,flag 暂时如下 hctf{x\_x\_iz\_x\_x} ``` —— |- f,第三个逆向条件 ``` 这一句是说,第四段和第三段一样编码后,和 0x4b7c0a73 异或等于 0x4315332 ``` 计算 0x4315332^0x4b7c0a73=0x‭4F4D5941 4F4D5941 => OMYA OMYA => s0 ``` flag hctf{x\_x\_iz\_s0\_x} ``` —— |- n,f*e*第一段的长度(先不管) |- h,将输入字符串的每一个字符 ascii 码进行计算(*第二段长度) 后连接起来显示(字符到 ascii 码转换) |- j,将第二段以 ‘3’ 分割,又后面可以确定是分成了两部分 |- 第四个逆向条件 ``` 首先是,**分割的两部份长度相等,第一部分和第二部分 16 进制异或等于 0x1613 **,这个条件只能后验,也先不管。 ``` —— |- k,输入的 ascii 码*第二段的长度 |- l,第一部分逐字符 ascii 码*第二段长度等于 0x2f9b5072 ``` 首先,0x2f9b5072 == 798707826‬ ``` 798 707 826 正好分成三个,已知h是对应 ascii 码*常数, 所以假设第一部分有三个字符,那么就是变成了求解常数 也就是 798 707 826 的最大公约数 求解得常数为 7 字符 114 101 118 => rev ``` 所以,第二段一共有 7 个字符,前四个字符为 rev3,带入上面的后验条件 0x1613 ``` 0x726576^0x1613=0x‭727365 727365 => rse ``` flag hctf{?\_rev3rse\_iz\_s0\_?} ``` —— |- m,第五个逆向条件,第五段的前四位和第一段的长度有关 ``` 题目的 hint 提示,每一段都有意义,因此我们这里做个爆破,假设第一段的长度在 6-30 之间,我们可以算出 n,在用 n 去算第五段前四位。 ``` n = f*e*(6-30) 第五段前四位 = n % 0x2f9b5072 + 0x48a05362 ``` 代码: ``` import binascii for i in range(6,31): n = 0x4315332*0x4b7c0a73*i strings = n%0x2f9b5072 + 0x48a05362 print binascii.a2b_hex(str(hex(strings))[2:-1]) ``` 从结果中可以看到大多数字符都没有意义,除了 h4r 让人遐想联翩,可惜还是不全,但是结合已经分析出的 flag,猜测应该是 h4rd。 flag hctf{??\_rev3rse\_iz\_s0\_h4rd?} ``` —— |- _0x5a6d56,将输入重复指定次数组合 |- 第六个逆向条件和第七个逆向条件 ``` 1. 第五段的第六位重复两次不等于倒数第 5-8 位,这个条件也让人摸不着头脑。 2. 第五段倒数第 2 位等于第五段第五位加 1 3. 第五段第 7-8 位去掉 0x 等于第五段第 7 位的 ascii 码\*第五段长度\*5 4. 第五段第五位为 2,第五段 7-8 位等于第五段第 8 位重复两次 5. 结合 hint 由以上条件可以推出以下 flag ``` hctf{??_rev3ser_iz_s0_h4rd2?3??3333} ``` 先假设 2 和 3 之间没有数字了,这时 7-8 位还未知但是 7-8 位相同,这时的方程 ``` 而且在这里,由于直接把 0x 去掉,所以 x 的 16 进制一定全为数字 字符拼接 {hex(x)hex(x)} = ascii(x)*13*5 ``` 爆破代码: ```python import binascii for i in range(1,128): string1 = hex(i)[2:] try: if int(string1+string1) == i*13*5: print chr(i) except: continue ``` output: ``` e ``` 验证前面的后验条件可以确定如下 flag ``` hctf{??_rev3ser_iz_s0_h4rd23ee3333} ``` 只剩下最前面的两位,为了方便,利用题目提供的 sha256 结果,我就不回溯条件在判断,直接进行碰撞。 ```python import hashlib a = 'hctf{' b = '_rev3rse_iz_s0_h4rd23ee3333}' e1 = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k', 'l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'] e2 = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k', 'l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'] for i in e1: for j in e2: sh = hashlib.sha256() sh.update(a+i+j+b) if sh.hexdigest() == "d3f154b641251e319855a73b010309a168a12927f3873c97d2e5163ea5cbb443": print a+i+j+b ``` output: ``` hctf{j5_rev3rse_iz_s0_h4rd23ee3333} ```