某象验证码AC参数JS逆向上(目前418版本)

注意,本文只提供学习的思路,严禁违反法律以及破坏信息系统等行为,本文只提供思路
本文的验证码网址如下,使用base64解码获得
aHR0cHM6Ly93d3cuZGluZ3hpYW5nLWluYy5jb20vYnVzaW5lc3MvY2FwdGNoYQ==

在这里插入图片描述

本文以滑块验证码举例,刷新验证码,打开请求
在这里插入图片描述
发现是a接口传回验证码图片相应参数,滑动任意参数,查找校验请求
在这里插入图片描述
发现是v1接口,其他参数在发送验证码接口,也就是a接口中可以找到,最终确定逆向的是ac参数
在这里插入图片描述
我们跟栈进去看看,在哪生成
在这里插入图片描述
经过一番查找后,是由hn.getUA()获取,那么getUA这个方法就是我们要针对的目标
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/653a34163571475695b1e5afc31dc390.png

跟进去之后,发现是混淆之后的,并且这里也只是单纯的return,说明,在前面就已经计算完成了,我们继续查找最初生成的位置
在这里插入图片描述
最终在这个位置发现刚开始生成的位置,传进去的n是之前发送验证码的sid值
在这里插入图片描述
但是这里的418值很短,所以还存在其他计算校验的地方,基本都是在同一个js文件里面,这里我们先进行反混淆
在这里插入图片描述

大致看了一下,混淆的形式不算多,有switch流程平坦化、变量标识符混淆、join字符串拼接混淆等,我们来一一针对

1、变量名混淆,变量名混淆其实就是自执行函数最后的大数组传入对应的形参,然后解码即可
结尾的大数组
这里,虽然网站的js会变化,但我为了方便,我暂时先写死

let vmpvar = ['n', 'r', 'e']
let vmpli = [["72,", "str", ",", "]] // 此为大数组,因为篇幅省略

let devmpmember = {
    MemberExpression(path) {
        // 当成员变量中不为数字时,则跳过
        if (!types.isNumericLiteral(path.node.property)) {
            return
        }
        if (types.isIdentifier(path.node.object)) {
            vmpindex = vmpvar.indexOf(path.node.object.name)
            if (vmpindex != -1) {
                values = vmpli[vmpindex]
                k = path.node.property.value
                // 替换为对应元素
                path.replaceWith(types.valueToNode(values[k]))
                de_vmpmembernum += 1
            }
        }
    }
}
traverse(ast, devmpmember)

还原之后,对应的变量都有相应的字符串赋值了
还原前
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/15316884c5324808a8c143f28e0cec5e.png
还原后
在这里插入图片描述

2、还原switch流程平坦化

还原switch混淆,需要先进行大数组字符串解混淆,因为这里需要获取switch中循环数值

let deWhile2 = {
    SwitchStatement(path) {
        // 如果父节点不是for循环,则跳过
        if (!types.isForStatement(path.parentPath.parentPath)) {
            return
        }
        // 获取switch的条件
        let switchvar = path.node.discriminant
        // 判断是否为成员变量
        if (!types.isMemberExpression(switchvar)) {
            return
        }
        // 获取调用的成员变量
        let membervar = switchvar.object.name
        // 获取调用的自增变量
        let updatevar = switchvar.property.argument.name
        // 获取for循环的条件
        // 获取调用的成员
        member = null
        let binding = path.scope.getBinding(membervar)
        binding && binding.scope.traverse(binding.scope.block, {
            VariableDeclarator(path2){
                if(path2.node.id.name == membervar){
                    try{
                        member = eval(generator(path2.node.init).code)
                        path2.stop()
                    }catch(e){
                        debugger
                    }

                }
            }
        })
        // 将case的条件转换成对象字面量
        inc = 0 // 暂时写死,因为目前都是0
        statecases = path.node.cases
        cases = {}
        for(let i = 0; i < statecases.length; i++) {
            cases[statecases[i].test.value] = statecases[i].consequent
        }
        // 循环member变量
        for(let i = 0; i < member.length; i++) {
            // 获取对应的case语句
            case_num = member[inc++]
            // 根据case_num以及case对应的条件获取对应的语句
            let casebody = cases[case_num]
            // 插入到当前while语句前面
            for(let j = 0; j < casebody.length; j++) {
                // 如果是break或者continue语句不插入
                if (types.isBreakStatement(casebody[j]) || types.isContinueStatement(casebody[j])) {
                    continue
                }
                path.insertBefore(casebody[j])
            }

        }

        // 删除while语句
        path.remove()
        de_whilenum2 += 1

    }
}
traverse(ast, deWhile2)

使用代码还原前
在这里插入图片描述
还原后
在这里插入图片描述
3、变量标识符混淆

因为之前定义了很多的变量,并在代码结构体中引用这些变量,导致变量非常的多,即使我们还原了字符串大数组后,阅读依然困难,所以这一步我们需要将引用的变量直接替换成之前已经定义好的值

let deiden = {
    Identifier(path){
        // 如果父类是VariableDeclarator,则跳过
        if(types.isVariableDeclarator(path.parentPath)){
            return
        }
        // 如果名称在vmpvar中,则跳过
        // if (vmpvar.indexOf(path.node.name) != -1) {
        //     return
        // }
        // 如果名称的父节点不是是ArrayExpression, 则跳过
        if (!types.isArrayExpression(path.parentPath)) {
            return
        }
        // 查找变量定义的位置
        identname = path.node.name
        binding = path.scope.getBinding(identname)
        binding && binding.scope.traverse(binding.scope.block, {
            VariableDeclarator(path2) {
                // 如果变量定义的父节点是for循环,则跳过
                if (types.isForStatement(path2.parentPath.parentPath)) {
                    return
                }
                // 如果定义的变量名和当前标识符名相同
                if(path2.node.id.name == identname){
                    // 当节点值不是字符串也不是数字时,则跳过
                    if (!types.isStringLiteral(path2.node.init) && !types.isNumericLiteral(path2.node.init)) {
                        return
                    }
                    try {
                        path.replaceWith(types.valueToNode(path2.node.init.value))
                        unieconsole.log(identname, path2 + "")
                        idennum += 1
                        path2.stop()
                        path.skip()
                    }catch(e){
                        //unieconsole.log(identname, path2 + "", "还原失败")
                    }

                }
            }
        })
    }
}
traverse(ast, deiden)

还原前
在这里插入图片描述
还原后
在这里插入图片描述
除此之外,还涉及到其他混淆,例如return表达式混淆,十六进制等,最基本的混淆我不再一一赘述,大致原理都差不多,下面展示一下,最终还原的结果
在这里插入图片描述
可以看到,还原出来,阅读非常的舒适、这样非常利于解决AC的值的时候,具体步骤,将在下一篇介绍

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码王吴彦祖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值