0
点赞
收藏
分享

微信扫一扫

JS逆向-国税增值税发票查询平台的漫漫过程

萧萧雨潇潇 2022-04-21 阅读 64
javascriptc#

一、2019年下半年,因为工作需要,有大量增值税发票需要查询真伪,而且是每张必查,当时还不太懂爬虫原理,就用“C#+大漠插件+全球鹰验证码识别"写了个类似于按键精灵那样的桌面应用程序,批量查询发票,用了近三个月,效果很满意。后来由于工作变动,不再需要查询,就没有去维护,就这样放弃了;

二、2021年底,有同事问我能不能做个批量查询工具,顺口就应承下来了。

1、花了近一个月的时间,学习《编辑原理》,开始感觉太抽象,就反复听,反复看书,整一个月,一有空,就听视频、看教材,似乎明白了一点点原理,但离理解还差太远。

   推荐视频:国防科技大学-编译原理(国家级精品课)高清流畅_哔哩哔哩_bilibiliicon-default.png?t=M3C8https://www.bilibili.com/video/BV11t411V74n?p=2&spm_id_from=pageDriver

2、花了近半个月的时间,学习babel插件。

   推荐学习资料:Babel 插件通关秘籍 - zxg_神说要有光 - 掘金小册 (juejin.cn)

                             JS逆向:AST还原极验混淆JS实战 (qq.com)

3、稍微懂了一点点编辑原理的皮毛,再加上了解了一点babel插件的用法,就不知天高地厚地开始了国税增值税发票查询平台的逆向过程。虽然结果还满意,但过程确实很艰辛,个中曲折就不说了。

  第一步:过无限debugger似乎不难,无需多说;

  第二步:拿到所有js源码文件。过了debugger,拿到js源码应该也不难

        

 

第三步:因为无法调试,只能先还原JS源码,用babel插件还原

const parser = require('@babel/parser')

const traverse = require('@babel/traverse').default

const t = require('@babel/types')

const generator = require('@babel/generator').default

const fs = require('fs')

var newAst = null;

let jscode = fs.readFileSync(__dirname + '/source/90a1c.js', {

    encoding: 'utf-8',

})

let ast = parser.parse(jscode);

hexUnicodeToString(ast);   //将所有十六进制编码与Unicode编码转为正常字符

addArrFuncToMemory(ast);   //将数组、公用函数添加到内存中

funcToStr(ast);           //解密函数实现

var totalObj = {};        //定义一个空对象,用于存放字符串花指令对象

ast = generatorObj(ast);  //将字符串花指令对象 写入到totalObj 对象中

changeJunkCode(ast);      //遍历花指令对象中的值 ,如果是字符串,则不变,如果不是,则            //在 totalObj 中对象中找到相应的值进行替换,直至为字符串

ast = generatorObj(ast);

changeLastJunkCode(ast);  //将对象引用中的字符串花指令转换为对象的字符串

 ast = generatorObj(ast);

 changeJunkFuncCode(ast);   //遍历花指令对象中的值 ,如果是字符串,则不变,如果不是,则在 totalObj 中对象中找到相应的函数进行替换,直至为字符串

 ast = generatorObj(ast);

 changeLastJunkFuncCode(ast);  //将对象引用中的函数花指令转换为对象的字符串

//removeJunkCode(ast);          //移除花指令

 //changeObjectAccessMode(ast);  //对象['属性'] 改为对象.属性

 //switchFlat(ast);              // 处理switch混淆

let { code } = generator(ast, opts = { jsescOption: { "minimal": true } });

fs.writeFile(__dirname + '/result/new_90a1c.js', code, (err) => { })

//将所有十六进制编码与Unicode编码转为正常字符

function hexUnicodeToString(ast) {

    traverse(ast, {

        StringLiteral(path) {

            var curNode = path.node

            delete curNode.extra

        },

        NumericLiteral(path) {

            var curNode = path.node

            delete curNode.extra

        },

        RegExpLiteral(path) {

            var curNode = path.node

            delete curNode.extra

        }

    })

}

//将数组、公用函数添加到内存中

function addArrFuncToMemory(ast) {

    newAst = parser.parse('');

    newAst.program.body.push(ast.program.body[0]);

    newAst.program.body.push(ast.program.body[1]);

    newAst.program.body.push(ast.program.body[2]);

    //newAst.program.body.push(ast.program.body[3]);

    // 把这四部分的代码转为字符串,由于存在格式化检测,需要指定选项,来压缩代码

    let stringArrFunc = generator(newAst, { compact: true }).code;

    // 将字符串形式的代码执行,这样就可以在 nodejs 中运行调用数组和函数了

    global.eval(stringArrFunc);

}

//解密函数实现

function funcToStr(ast) {

    traverse(ast, {

        VariableDeclarator(path) {

            if (t.isFunctionExpression(path.node.init)) {

                let funcName = path.node.id.name;

                if (funcName !== "_0x4a4a") { return; }

                let binding = path.scope.getBinding(funcName);

                if (binding && binding.referencePaths) {

                    let referencePaths = binding.referencePaths;

                    referencePaths.map(p => {

                        if (t.isCallExpression(p.parentPath)) {

                            let str = eval(p.parentPath.toString());

                            p.parentPath.replaceWith(t.stringLiteral(str));

                        }

                    })

                }

            }

        }

    })

}

//将字符串花指令对象 写入到totalObj 对象中

function generatorObj(ast) {

    traverse(ast, {

        VariableDeclarator(path) {

            //init 节点为 ObjectExpression 的时候,就是需要处理的对象

            if (t.isObjectExpression(path.node.init)) {

                //取出对象名

                let objName = path.node.id.name;

                //以对象名作为属性名在 totalObj 中创建对象

                objName && (totalObj[objName] = totalObj[objName] || {});

                //解析对象中的每一个属性,加入到新建的对象中去,注意属性值依然是 Node 类型

                totalObj[objName] && path.node.init.properties.map(function (p) {

                    totalObj[objName][p.key.value] = p.value;

                });

            };

        }

    });

    return ast;

}

//遍历花指令对象中的值 ,如果是字符串,则不变,如果不是,则在 totalObj 中对象中找到相应的值进行替换,直至为字符串

function changeJunkCode(ast) {

    traverse(ast, {

        VariableDeclarator(path) {

            if (t.isObjectExpression(path.node.init)) {

                path.node.init.properties.map(function (p) {

                    let realNode = findRealValue(p.value);

                    realNode && (p.value = realNode);

                });

            };

        }

    });

}

//一个递归转换函数

function findRealValue(node) {

    if (t.isMemberExpression(node)) {

        let objName = node.object.name;

        let propName = node.property.value;

        if (totalObj[objName][propName]) {

            return findRealValue(totalObj[objName][propName]);

        } else {

            return false;

        }

    } else {

        return node;

    }

}

//将对象引用中的字符串花指令转换为对象的字符串

function changeLastJunkCode(ast) {

    traverse(ast, {

        MemberExpression(path) {

            let objName = path.node.object.name;

            let propName = path.node.property.value;

            totalObj[objName] && t.isStringLiteral(totalObj[objName][propName]) &&

                path.replaceWith(totalObj[objName][propName]);

        }

    });

}

部分还原后的JS代码

$(document)["ready"](function () {
  $("#fpdm")["blur"](function () {
    if ("eyHee" !== "fRCTG") {
      retrycount = 0;
    } else {
      var _0x3d92c0 = new _0x439101();

      var _0x89b16f = _0x3d92c0["getTime"]();

      return _0x89b16f;
    }
  });
});

function yzmTime(_0x3dd72d) {
  if (yzmWait == 0) {
    $("#yzm_unuse_img")["hide"]();
    $("#yzm_img")["show"]();
    yzmWait = 60;
  } else {
    if (yzmWait == 2) {
      if ("dgdfx" !== "FOacK") {
        $("#yzm_unuse_img")["show"]();
        $("#yzm_img")["hide"]();
      } else {
        _0x36629d("24小时内验证码请求太频繁,请稍后再试!", "警告");

        _0x1eb024("#yzm_img")["hide"]();
      }
    }

    yzmWait--;
    setTimeout(function () {
      if ("FUDMu" === "hzdbb") {
        if (_0x1bd127 == 9) {
          _0x2ed1ec("系统繁忙,请稍后重试!", "提示");
        } else {
          _0x1f2455 = _0x3d3c5d + 1;

          _0x3f88c4();
        }
      } else {
        yzmTime(_0x3dd72d);
      }
    }, 1000);
  }
}

第四步: 挂上fiddler的AutoResponder ,就可以自由调试前端代码了

 

 

第五步:找到关键代码处理(验证码参数来源、查验提交参数的来源)

 第六步:把 js代码扣出来,形成一个独立的js文件,

    获取验证码参数中的 key9 flwq39   注意callback参数后面的时间戳,这里有个坑,处理不好,查询不了几张发票就让你出错

 获取发票信息参数中的key9  flwq39

 做到这一步,基本上就可以拿到发票信息了,剩下的,就是对结果进行判断了,主要是key1的值进行判断

 这里还有个小坑要踩一踩,原以为到这里就算结束了,事实上不是,发票信息结果里面还有玄机,仔细一看,购买方名称与销售方名称时不时要颠倒顺序,原因就在下面这段代码里。

 最后的成品:

 

 

我是编程菜鸟,刚学了一点皮毛,就在这里舞文弄墨,实是贻笑大方。第一次写这样的文章,见笔了。

举报

相关推荐

0 条评论