• 如果您想对本站表示支持,请随手点击一下广告即可~
  • 本站致力于提供原创、优秀的技术文章~
  • 有任何疑问或建议 均可以在站点右侧栏处 通过各种方式联系站长哦~
  • CTF – RootMe解题报告 [Web-Client : Javascript – Obfuscation 4]

    渗透测试 EXP 1405阅读 1评论

    挑战入口:Root-Me(https://www.root-me.org/en/Challenges/Web-Client/Javascript-Obfuscation-4)
      分类目录:Link to …(http://exp-blog.com/2019/01/02/pid-2597/11/)


    感慨

    这题是真的难,我前后花了一周时间。

    总的来说,题目只有两个似是而非的提示(解题出来之后才发现这两个提示其实很关键):

    • 提示一在题目说明:需要允许弹窗功能
    • 提示二在页面源码:凭耐心和直觉

    虽然如此,但其实题目隐藏了很多线索。因此要解题,首先需要知道作者究竟真正想隐藏的是什么。

    只有透过现象看本质,抽丝剥茧,方得真相。

    源码梳理

    开启挑战页面后,要求输入密码,反正不知道,随便输就好,一段法语提示说密码错误。

    提取页面源码,发现是一段混淆加密过的 Javascript 代码 :


    因为这段混淆代码真的很难看,我稍微替换了变量名和方法名,整理成如下代码:


    在这里我对 window.open() 的十六进制串做了解码,还原出来就是 width=300, height=20

    关于 window.open() 函数这里暂且先不管,后面会着重说明。

    salt 的十六进制串因为解码出来还是乱码,所以这里就先保持原样不动。

    整理后的代码已经大致可以了解逻辑功能了。

    但为了方便调试,我又用 python 重写了一遍,并加了注释:

    针对 python 的每个函数我做了一些单元测试,大致归纳出每个函数的作用(过程略,有兴趣的同学可以自己测试)。

    为了方便后面结合分析,我对这三份处理过的代码的每个函数名做了个映射关系表格,并附带了说明:

    混淆 JS 函数/变量 美化 JS 函数/变量 重构 Python 函数/变量 说明
    _(x, y) xor(x, y) xor(x, y) 异或运算(存在逆运算
    若 z = xor(x, y) , 则 y = xor(x, z)
    __(y) powsum1(y) powsum1(y) 指数求和(被 bit1 调用)
    y 取值范围为 [0, 8)],对应输出固定为
    0, 1, 3, 7, 15, 31, 63, 127
    ___(y) powsum2(y) powsum2(y) 指数求和(被 bit2 调用)
    y 取值范围为 [0, 8)],对应输出固定为
    0, 128, 192, 224, 240, 248, 252, 254
    ____(x, y) bit1(x, y) bit1(x, y) 执行一些位运算(此函数并没有被调用)
    甚至从某种意义上看 bit1 与 bit2 是等价
    其存在只是混淆视听,误认为是 bit2 的逆运算
    _____(x, y) bit2(x, y) bit2(x, y) 执行一些位运算(不存在逆运算
    当 x 固定的时候,无论 y 为何值
    结果只会是某 8 个固定 ASCII 码的其中之一
    且均有 bit2(x, y) == bit1(x, 8 – y)
    ______(x, y) bit(x, y) bit(x, y) 无任何作用
    此函数只是简单调用 bit2 而已
    _______(ð, key) encrypt(salt, key) encrypt(salt, key) 使用输入串 key 对盐 salt 循环加密
    得到加密串 pwd
    __________(þ) check(pwd) check(key, pwd) 检查加密串 pwd 每个字符的 ASCII 码之和
    是否等于 8932,若是在则弹出式窗口
    打印 pwd 的内容
    ð salt salt 盐(固定值,十六进制串)
    key key key 输入字符串(本题需要推导的最终 flag
    þ pwd pwd salt 与 key 加密得到的字符串
    若其 ASCII 码的校验和等于 8932
    则会触发弹窗事件并将其打印出来

    背景分析

    到这里为止,整个代码在做什么,以及出题人希望我们做什么,似乎已经是很清晰了。

    简单来说,就是 给定一个盐 salt ,然后要求输入一个密钥 key , 通过某种算法使用 saltkey 加密得到密码 pwd

    但是因为某种原因,密钥 key 遗失了, 密码 pwd 也忘记了。期望可以通过某种途径找回 key

    目前只知道的线索有:

    • 加密算法的逻辑
    • salt
    • 密码 pwd 的校验和

    初步分析

    首先需要明确的是,因为有无数种组合,所以要通过校验和反推密码 pwd 本身是不可能的。

    因此校验和只能作为我们是否找到了 key 的判断依据之一

    另外就算已知加密算法的逻辑,也无法写出对应的解密算法,究其原因就是 bit 函数是不可逆的。

    密码爆破

    通过前面分析,我凭第一直觉想到的解题方法就是密码爆破:不断枚举输入 key 值,然后测试其输出 pwd 值的校验和是否等于 8932 ,只要命中则认为找到了目标 key

    于是我选择了两种爆破方式:

    • 以 ASCII 表作为 key 的每个字符范围进行枚举,令 key 的长度依次为 1 个字符、2个字符、3个字符….
    • 枚举从 darkweb 统计出来的 Top 10000 的弱密码作为 key(密码表可在 SecList 下载)

    利用前面重构出来的 python 代码,可以很容易就实现爆破测试,这里就不贴爆破代码了,直接说结果。

    通过爆破我找到了两个符合条件的 key 值: +3yasmin

    但是很遗憾,这两个 key 值虽然可以使得 pwd 的校验和为 8932 ,但提交后发现并不是真正的 pwd

    看来事情并没有想象中简单。


    我认真地分析 encrypt(salt, key) 函数,又发现一件事情:

    key 的每个字符一直在通过 取模运算,循环 地对 salt 的每个字符依次执行某个操作(xorbit

    亦即前面得到的两个 key 值,只要通过循环,就可以衍生出更多符合校验和条件的 key 值,例如:

    +3+3+3+3+3+3yasminyasminyasminyasminyasminyasmin、……

    显然不可能有这么多正确答案,换言之爆破不是正确的解题思路

    进一步分析

    仔细观察 encrypt(salt, key) 函数的行为,与其说这是 saltkey 加密成 pwd 的过程,

    倒不如说这是通过 key 把密码 salt 还原成明文 pwd 的过程encrypt 实际上应该是一个解密函数

    据此我调整了一下这部分代码:

    • pwd => plaintext
    • salt => pwd
    • encrypt(salt, key) => decrypt(pwd, key)

    调整思路

    调整代码后,整段代码的功能就更切合正常思路了:

    有一密文 pwd , 需要找到其加密密钥 key 解密成明文 plaintext,若解密成功则把 plaintext 输出到一个弹窗

    为什么是弹窗

    在这里,我注意到一个问题,而且整个挑战也只有这个唯一提示:

    NB : You will have to enable popups in order to solve this challenge!

    就是说我要开启浏览器弹窗功能才能解决这个挑战。

    为什么不用 alert ?为什么是弹窗?为什么弹窗会成为提示要点?


    我把代码重构成 python 时,直观上认为弹窗只不过是输出结果的方式而已,就一直忽略了弹窗函数的作用。

    但既然提示指出了弹窗的必要性,可能在其中会隐含了什么。

    在这段弹窗代码中,用到了两个 js 函数,从 W3School 查到其 API 说明如下:

    其中 window.open 的参数说明为 :
       ○ URL:可选,声明了要在新窗口中显示的页面的 URL。
           如果省略了这个参数,或者它的值是空字符串,那么新窗口就不会显示任何页面内容
       ○ name:可选,声明了新窗口的名称
       ○ features:可选,声明了新窗口要显示的标准浏览器的特征
       ○ replace:可选,本题代码中并没有使用这个参数


    为了测试弹窗的效果,我把前面爆破得到的两个 key+3yasmin 分别输入到挑战页面。

    因为这两个值校验和均为 8932 ,所以必定会触发弹窗。

    使用浏览器的开发者工具查看弹窗源码,我发现几件很有意思的事情:

    • 弹窗显示了意义不明的乱码内容,而且为了显示乱码内容,弹窗自动添加了 <html> 等标签(因为使用 python 运行时,并没有输出这些标签,而且 window.open 的 URL 为空,即页面本身不会有任何内容,所以我肯定这些标签是弹窗自动加的,与 window.opendocument.write 无关)
    • 不同的 key 值得到输出到弹窗的内容不同(意味着很可能有一个 key 会使得内容变成明文)
    • key 值为 +3 时,不难发现乱码中有一对闭合标签 <o€_Â=>

    尤其是最后一点,乱码的闭合标签 ,给了我很大的启示:


    大胆猜测

    既然 window.open 开启了一个完全空白的窗口, document.write 又往里面写 plaintext

    那会不会 plaintext 的内容就是页面源码?

    而且会不会 plaintext 就是包含完整 html 标签的页面源码?

    虽然还不知道 plaintext 的全部内容,但如果是包含完整 html 标签的页面源码,那第一个字符很有可能就是 <

    更甚者, plaintext 前几个字符很有可能就是 <html>

    验证猜测

    虽然是全凭直觉的猜测,但要验证它也并不是什么复杂的事情。

    验证的思路很简单,逐个字符验证就可以了:

    现在已知密文 pwd 的每一个字符和 decrypt 的代码逻辑,又知道 plaintext 的前几个字符(虽然只是猜测),

    那么只需要逆向计算 key 的每一个字符,只要都不是乱码字符,就说明猜测正确,同时也能推算出 key 的值。

    逆推

    decrypt 逻辑可知:

    plaintext[0] = xor( pwd[0], key[0] ) ,现已知 pwd[0] = '\x71'plaintext[0] = '<'

    因为 xor 的逆运算就是其自身,因此有 key[0] = xor( pwd[0], plaintext[0] ) = 'M'


    又从 decrypt 逻辑可知:

    第 i 个字符使用 xor 还是 bit 方法,取决于 plaintext[i-1] 的 ASCII 码是奇是偶,

    因为 plaintext[0] = '<' 其 ASCII 码是偶数, 所以 plaintext[1] 依旧使用 xor 方法逆推,

    已知 pwd[1] = '\x11'plaintext[1] = 'h',因此有 key[1] = xor( pwd[1], plaintext[1] ) = 'y'


    根据上述规律,整理出前几个字符的逆推表如下:

    字符索引 : i 0 1 2 3 4 5 6 ……
    pwd[i] \x71 \x11 \x24 \x59 \x8d \x6d \x71 ……
    plaintext[i-1] ……
    plaintext[i] < h t m l > < ……
    逆运算 xor xor xor xor bit(无逆运算) xor xor ……
    key[i] M y P 4 ?(未知) S M ……

    不难注意到,pwd[0] == pwd[6] == '\x71' ,因此很可能也有 plaintext[0] == plaintext[6] == '<'

    反推得到 key[0] == key[6] == 'M' ,也就是说,key 的字符开始重复了。

    通过前面做爆破时的思路可以,这可能就是 key 的取模循环点,换言之 key 应该只有 6 个字符:MyP4?S


    Bingo

    现在的问题在于,第 4 个字符 ? 是什么。

    因为从代码可知 bit 是不存在逆运算的,亦即虽然我们已知 pwd[4] == '\x8d'

    plaintext[4] = bit( pwd[4], key[4] ) = 'l' ,但是从算术上无法逆推 key[4] 值。


    既然无法逆推,那么顺序爆破就好了:

    通过枚举 key[4] 的 ASCII 码值(也就256次),记录所有使得 bit( pwd[4], key[4] ) = 'l'key[4] 值,

    于是我得到了这 12 个字符均是满足条件的: # + 3 ; C K S [ c k s {

    逐个代入到 ? 在挑战页面校验,最终发现只有 MyP4sS 会使得弹窗内容完全变成明文,即是真正的 key,完成挑战。

    (其实 ? 不用爆破也能猜到不是 s 就是 S,因为很明显 MyP4sS 就是 MyPASS 的变体)


    在最后,我在 python 代码输入 key = MyP4sS ,还原出真正的 plaintext 内容为:

    <html><head><title>Victoire!</title></head><body>Vous pouvez entrer ce mot de passe!</body></html>

    说明前面的猜测是正确。


    转载请注明:EXP 技术分享博客 » CTF – RootMe解题报告 [Web-Client : Javascript – Obfuscation 4]

    喜欢 (4) 分享 (0)
    发表我的评论
    取消评论

    表情

    Hi,您需要填写昵称和邮箱!

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址
    (1)个小伙伴在吐槽
    1. 666分析很详细,谷歌一直找不到答案
      喵星狗2019-01-18 08:02 回复