整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

是时候为你的C#程序进行代码混淆

是时候为你的C#程序进行代码混淆

不管是C#还是Java,都是可以通过反编译工具,进行反编译后查看源码,这个源码虽然不是真正意义的源码,但和真正源码的相差不是很大.如果是单纯的Web还好一些.因为只需要部署到服务器上,也很少人能看到部署文件.所以相对要好一些.如果是C#做客户端的话,是需要安装到客户机上的.所以还是需要对程序做一下处理,对程序代码进行代码混淆.

这里主要使用Obfuscator这个工具,Obfuscator是源码开源.且也是国人Lex Li(已经出国)所写.

在VS中如何使用Obfuscator

1.在Nuget中,搜索Obfuscator,并进行安装

在Nuget浏览器中,搜索Obfuscator

2.创建obfuscar.xml,并设置该文件为较新复制

在属性中,设置文件较新复制

3. 在obfuscar.xml中指定配置信息

<?xml version="1.0" encoding="utf-8" ?>
<Obfuscator>
  <!--输入路径-->
  <Var name="InPath" value="." />
  <!--输出路径:加密混淆过的路径-->
  <Var name="OutPath" value=".\Obfuscator_Output" />
  <!--混淆代码的参数-->
  <Var name="ReuseNames" value="false" />
  <Var name="HideStrings" value="false" />
  <Var name="KeepPublicApi" value="false" />
  <Var name="HidePrivateApi" value="true" />
  <!--要混淆的模块-->
  <Module file="$(InPath)\ConsoleApp2.exe" />
</Obfuscator>
参数信息,可以看这里: https://docs.obfuscar.com/getting-started/configuration.html

4.在程序生成之后调用Obfuscar.通过VS的生成事件实现.

在VS中生成后事件,调用Obfusacr 并加载obfuscar.xml

5.在Obfuscator_Output目录,通过反编译工具JustDecompilte查看混淆过的程序.

使用反编译工具JustDecompilte查看混淆过的代码

在命令行中使用Obfuscator

Obfuscator可以不在VS中直接使用,Obfuscator是一个单纯的代码混淆工具. 上边说过Obfuscator代码是开源的.

在GitHub项目地址: git@github.com:obfuscar/obfuscar.git
在Gitee项目地址: https://gitee.com/junweihuang_admin/obfuscar.git

因为Gitee速度要快于GitHub,如果Gitee上有的话,还是优先使用Gitee. 如果Gitee上面没有的话,就得去Github上.打开Obfuscar.sln解决方案.进行编译.发现是无法编译成功的.发现Obfuscator依赖Baml项目中.

在Baml项目中,发现这几个不存在的.

发现Baml项目中,有4个文件不存在

在Obfuscator项目中的Obfuscator.cs发现ILSpy.BamlDecompiler.Baml命名:

发现了ILSpy.BamlDecompiler.Baml这个命名空间

便怀疑是不是缺的这几个文件在IlSpy中呀!于是便把IlSpy源码下载到本地,最终找到了这几个文件.将这几个文件拷贝到Baml项目中,尝试编译,果真是编译成功了.

生成后的目录:

Obfuscar代码混淆工具,只需要将要混淆的程序放入Input路径下,在obfuscar.xml修改指定,双击run.bat就可以了

Obfuscar.Console.exe使用:

  1. 将要混淆的程序集放入Input目录中
  2. 修改obfuscar.xml指定要混淆的程序集(exe或dll)
  3. 双击run.bat

文分享的是一个动画效果源码,演示JS代码是如何被加密的。

先看效果:

一般我们在进行JS加密时,提交原始代码,紧接着就直接得到了加密代码,混淆加密过程是黑盒状态,是不被我们知道的。

这个动画,用慢放的效果,逐帧演示了JS代码在进行混淆加密时发生的变化。

比如:变量名变短、回车换行消失、空格删除、消除注释、函数调用变成自执行函数。

注:动画演示的是JShaman专业版使用部分功能对JS代码进行混淆加密的效果。

下面给出这个动画完整源码,保存为html文件即可使用:

<h2>动画演示:JShaman是怎样对JS代码混淆加密的</h2>
<button onclick="mini_change();">开始</button><br>
<br>
<textarea id="js_code" style="width:800px; height:200px;font-size:19px;">
function get_copyright(){
    var domain="JShaman专注于JS代码混淆加密";
    var from_year=2017;
    var the_copyright="(c)" + from_year + "-" + (new Date).getFullYear() + "," + domain;
    return the_copyright;
}
//输出信息
console.log(get_copyright());
</textarea>
<br>
注:演示的是JShaman专业版部分效果
<script>

    var index=0;
    var js_code_textarea=document.getElementById("js_code");
    var change_matrix=[
        ["\n",""],
        ["    ",""],
        ["var domain","var _"],
        ["\n",""],
        ["    ",""],
        ["var from_year","var _2"],
        ["\n",""],
        ["    ",""],
        ["var the_copyright","var _3"],
        ["from_year","_2"],
        ["\n",""],
        ["    ",""],
        ["\n",""],
        ["the_copyright","_3"],
        ["domain","_"],
        ["\n",""],
        ["//输出信息",""],
        ["\n",""],
        ["function get_copyright(){",""],
        ["}",""],
        ["get_copyright()","function(){"+`var _="JShaman专注于JS代码混淆加密";var _2=2017;var _3="(c)" + _2 + "-" + (new Date).getFullYear() + "," + _;return _3;`+"}()"],
        [`var _="JShaman专注于JS代码混淆加密";var _2=2017;var _3="(c)" + _2 + "-" + (new Date).getFullYear() + "," + _;return _3;`,""],
    ]
    
    function mini_change(){
        js_code_textarea.value=js_code_textarea.value.replace(change_matrix[index][0], change_matrix[index][1]);
        index++;
        console.log(index[0]);

        if(index >change_matrix.length-1){
            console.log("执行");
            eval(js_code_textarea.value);
            alert("演示完成");
            return;
        }else{
            setTimeout(mini_change,1000);
        }
    }
</script> 

这段代码中,应用了一些不错的JS技巧,参考修改,也可以用来做其它文本演示动画。

们已经学到很多反爬机制以及相应的反反爬策略。使用那些手段,其实已经完全可以完成绝大多数的爬虫任务。但是,还是有极个别的情况下,会出现诸如 JS 加密和 JS 混淆之类的高深反爬机制。

如果不幸遇到这种反爬机制,一个明智之举是给站长点个赞,然后恭恭敬敬选择放弃,去别的地方找数据。

当然,还是那句话,我们可以选择不爬,但是对付 JS 加密和 JS 混淆的方法却不可以不会。

这里就以中国空气质量在线检测平台为例,介绍 JS 加密和 JS 混淆的实现和破解方法。

要爬取的网站:https://www.aqistudy.cn/html/city_detail.html

这个网站正在升级,所以页面无法正常显示。这也意味着这个网站本身的 JS 解密是有问题的(如果没问题就能显示了),所以最后我们并不能完全解析出数据来。虽然如此,这个网站仍然是学习 JS 加密和 JS 混淆的相当不错的平台。

闲话少说,开始干活!

首先浏览器打开网页,并打开调试台的抓包工具。修改查询条件(城市的名称 + 时间范围),然后点击查询按钮,捕获点击按钮后发起请求对应的数据包。点击查询按钮后,并没有刷新页面,显然发起的是 ajax 请求。该请求就会将指定查询条件对应的数据加载到当前页面中(我们要爬取的数据就是该 ajax 请求请求到的数据)。

分析捕获到的数据包

  • 提取出请求的 url:https://www.aqistudy.cn/apinew/aqistudyapi.php
  • 请求方式:post
  • 请求参数:d: 动态变化一组数据(且加密)
  • 响应数据:是加密的密文数据

该数据包请求到的是密文数据,为何在前台页面显示的却是原文数据呢?

原来,在请求请求到密文数据后,前台接受到密文数据后使用指定的解密操作(JS 函数)对密文数据进行了解密操作,然后将原文数据显示在了前台页面。

接下来的工作流程:

首先先处理动态变化的请求参数,动态获取该参数的话,就可以携带该参数进行请求发送,将请求到的密文数据捕获到。

  • 将捕获到的密文数据找到对应的解密函数对其进行解密即可。
  • 【重点】需要找到点击查询按钮后对应的 ajax 请求代码,从这组代码中就可以破解动态变化的请求参数和加密的响应数据对应的相关操作。
  • 找 ajax 请求对应的代码,分析代码获取参数 d 的生成方式和加密的响应数据的解密操作。直接在页面中,并没有办法直接找到发送 ajax 请求的函数的,因为它以及被封装到别的文件中了。我们可以基于火狐浏览器定位查询按钮绑定的点击事件。
  • 抽丝剥茧,首先从 getData 函数实现中找寻 ajax 请求对应的代码。在该函数的实现中没有找到 ajax 代码,但是发现了另外两个函数的调用,getAQIData()getWeatherData()。ajax 代码一定是存在于这两个函数实现内部。

    另外,这里记住一个参数,type==’HOUR‘,它的含义是查询时间是以小时为单位。这个参数我们后来会用到。

    接下来我们就去分析 getAQIData()getWeatherData(),争取能够找到 ajax 代码。

    我们找到这两个函数的定义位置,还是没有找到 ajax 请求代码。不过我们却发现它们同时调用了另外一个函数,getServerData(method,param,func,0.5)。它的参数的值可以为:

    • method 可以是 ‘GETCITYWEATHER’ 或者 ‘GETDETAIL’
    • params 的值是 {city, type, startTime, endTime},也就是查询条件
    • func 是一个匿名函数,看样子是在处理数据。

    下一步当然就要找 getServerData 函数了,看看那个函数里面有没有我们一致想要的发送 ajax 请求的代码。

    我们尝试着在页面中搜索,却找不到这个函数。很显然,它是被封装到其他 js 文件中了。这时,我们可以基于抓包工具做全局搜索。

    好消息是,我们顺利找到了 getServerData 函数!坏消息是,这货长得一点也不像是函数。

    这是因为,这段 JS 函数代码被加密的。这种加密的方式,我们称为 JS 混淆。

    JS 混淆,也就是对核心的 JS 代码进行加密。

    JS 反混淆,则是对 JS 加密代码进行解密。

    接下来我们要做的,就是 JS 反混淆,让这段我们看不懂的东西,显现出庐山真面目。

    我们用的方法十分简单粗暴,也就是暴力破解。使用这个网站就可以实现对 JS 混淆的暴力破解:https://www.bm8.com.cn/jsConfusion/

    将 getServerData 函数所在的那一整行代码都复制过来,粘贴到这个网址的文本输入框中,然后点击 开始格式化 即可:

    终于,我们看到了 getServerData 的代码,并且在其中发现了发送 ajax 的请求:

    function getServerData(method, object, callback, period) {
        const key=hex_md5(method + JSON.stringify(object));
        const data=getDataFromLocalStorage(key, period);
        if (!data) {
            var param=getParam(method, object);
            $.ajax({
                url: '../apinew/aqistudyapi.php',
                data: {
                    d: param
                },
                type: "post",
                success: function (data) {
                    data=decodeData(data);
                    obj=JSON.parse(data);
                    if (obj.success) {
                        if (period > 0) {
                            obj.result.time=new Date().getTime();
                            localStorageUtil.save(key, obj.result)
                        }
                        callback(obj.result)
                    } else {
                        console.log(obj.errcode, obj.errmsg)
                    }
                }
            })
        } else {
            callback(data)
        }
    }

    从这段代码中,我们不难得出下面这几个信息:

    • ajax 请求成功后获得到的 data 是加密的响应数据(就是我们最开始通过抓包工具看到的那一串神秘的相应字符串),通过 decodeData(data) 函数,可以将加密的数据解密成我们需要的明文数据。
    • 发送请求时携带的参数,也就是 d 对应的值 param 是通过 getParam(method, object) 函数返回动的态变化的请求参数。这两个参数我们前面也分析过:参数 method 可以是 ‘GETCITYWEATHER’ 或者 ‘GETDETAIL’参数 object 则为 {city, type, startTime, endTime},是我们的查询条件我们当然还可以继续最终下去,刨根问题找到它们究竟是通过什么方式进行加密和解密的。然后,使用 Python 代码,重复这个加密和解密的过程,完成请求数据的生成和响应数据的解析过程。

    但是我们并不打算这么做。因为再继续深挖下去,难度将会陡然增加。此时我们已经很疲惫了,如果继续下去恐怕要疯掉。而且,JavaScript 和 Python 毕竟是两种语言,它们之间的方法和各种包都不相同。JavaScript 能实现的,Python 未必能够轻松完成。所以重新写一个加密和解密的脚本,并不是明智之举。

    更好的解决方案是,我们提供请求的明文数据,通过网站自己的 JS 代码进行加密,得到加密的请求参数。使用这个参数,我们发送请求给服务端。拿到加密的响应数据后,再通过网站的 JS 代码进行解密。

    也就是说,我们接下来需要做的就是要调用两个 JS 函数 decodeData 和 getParam,并拿到返回结果即可。

    现在的问题是,在 Python 程序中如何调用 JS 函数呢?

    这就涉及到一个新的概念:JS 逆向。JS 逆向,也就是在 Python 中调用 JS 函数代码。

    能够实现 JS 逆向的方式有两种:

    1. 手动将 JS 函数改写称为 Python 函数并执行。
    2. 这种方法我刚刚谈过了,并不现实。因为 JS 能实现的,Python 未必能够轻易实现。而且毕竟还要重写函数,比较麻烦。
    3. 使用固定模块,实现自动逆向(推荐)。
    4. 一个很好用的实现 JS 逆向的 Python 库 是 PyExecJS。
    5. PyExecJS 库用来实现模拟 JavaScript 代码执行获取动态加密的请求参数,然后再将加密的响应数据带入 decodeData 进行解密即可。
    6. PyExecJS 需要在本机安装好 nodejs 的环境。
    7. PyExecJS 的安装:
    pip install PyExecJS

    接下来,我们就可以生成加密的请求数据了。

    首先,把我们解析出来的那串代码保存到本地,比如名为 code.js 的文件中。在里面我们补充一个函数,比如名字叫 getPostParamCode,用来发起我们的数据请求。之所以这样做是因为使用 PyExecJS 调用 JS 函数时,传入的参数只能是字符串。而 getParam 方法的参数需要用到 JS 的自定义对象。

    我们只需在 code.js 中加上下面的代码即可:

    function getPostParamCode(method, type, city, start_time, end_time) {
        var param={};
        param.type=type;
        param.city=city;
        param.start_time=start_time;
        param.end_time=end_time;
        return getParam(method, param)
    }

    然后,使用 PyExecJS 调用里面的 getParam 方法,将我们的请求数据加密:

    # 模拟执行decodeData的js函数对加密响应数据进行解密
    import execjs
    import requests
    
    node=execjs.get()
    
    # 请求参数
    method='GETCITYWEATHER'
    type='HOUR'
    city='北京'
    start_time='2020-03-20 00:00:00'
    end_time='2020-03-25 00:00:00'
    
    # 编译js代码
    file='code.js'    # js代码的路径
    ctx=node.compile(open(file, encoding='utf-8').read())
    
    # 将请求数据加密
    encode_js=f'getPostParamCode("{method}", "{type}", "{city}", "{start_time}", "{end_time}")'
    params=ctx.eval(encode_js)
    
    # 使用加密的参数,发起post请求
    url='https://www.aqistudy.cn/apinew/aqistudyapi.php'
    response_text=requests.post(url, data={'d': params}).text
    
    # 将响应数据解密
    decode_js=f'decodeData("{response_text}")'
    decrypted_data=ctx.eval(decode_js)    # 如果顺利,返回的将是解密后的原文数据
    print(decrypted_data)    # 执行会报错:目前页面中没有数据。解密函数只是针对页面中原始的数据进行解密。

    自此,我们完成了 JS 加密和 JS 混淆的处理。这里我们总结一下这几个概念:

    • JS 加密,也就是通过 JS 代码,将数据进行加密处理,将明文数据变成密文数据。如果不能将其解密,密文数据将毫无用处。
    • JS 解密:通过 JS 代码,将加密的数据解密,也就是将密文数据解析成明文数据。JS 解密是 JS 加密的逆过程。
    • JS 混淆:将 JS 代码(比如 JS 函数)本身进行加密。
    • JS 反混淆:将加密了的 JS 代码解密成常规的 JS 代码。通常直接使用暴力破解即可。
    • JS 逆向(重要):通过 Python 代码调用 JS 的函数。

    附,ajax 请求的各个数据的含义: