Android逆向实战HOOK解析(frida篇)

集合中的properties类似是键值对的数据类型,通过用于获取文件输入输出流

import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.util.Properties;  
  
public class Hello {    
    public static void main(String[] args) {     
        String currentWorkingDir = System.getProperty("user.dir");//获取本地工作区path    
        String filePath = currentWorkingDir + File.separator + "demo.properties";//添加'\'以及文件名
        System.out.println(filePath);  
        Properties properties = new Properties();  
        //创建相应的对象
        try (FileInputStream fis = new FileInputStream(filePath)) { //通过路径去获取路径去获取程序的输入流 
            properties.load(fis);  //通过对象去读取加载程序的输入流
            String className = properties.getProperty("class");  
            String methodName = properties.getProperty("method");  
            System.out.println(className + " " + methodName);  
        } catch (IOException e) {  
            System.err.println("无法读取属性文件: " + e.getMessage());  
        }  
    }  
}

这里利用反射我们将原本利用转载class类名和函数的文件进行载入,通过读取类名以及函数发方法名进行直接的执行函数方法

import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;  
import java.util.Properties;  
public class Hello {  
    public static void main(String[] args) {  
        String currentWorkingDir = System.getProperty("user.dir");  
        String filePath = currentWorkingDir + File.separator + "demo.properties";  
        System.out.println(filePath);  
        Properties properties = new Properties();  
        try (FileInputStream fis = new FileInputStream(filePath)) {  
            properties.load(fis);  
            String className = properties.getProperty("class");  
            String methodName = properties.getProperty("method");  
            System.out.println(className + " " + methodName);  
            Class<?> clazz = Class.forName(className);  
            Method method = clazz.getMethod(methodName);  
            // 因为是静态方法,所以第一个参数为null,第二个参数是方法的参数数组,这里为空数组  
            method.invoke(null, (Object[]) new Object[0]);  
        } catch (IOException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {  
            System.err.println("无法读取属性文件或调用方法: " + e.getMessage());  
        }  
    }  
}  
class chen_chen_chen {  
    public static void test0() {  
        System.out.println("Hello, Chen_chen_chen!");  
    }  
    public static void test1() {  
        System.out.println("Hello, test1");  
    }  
}

demo.properties:

class=chen_chen_chen
method=test0  or  method=test1

class类对象的获取

Class<?> clazz= Class.forName(className);//通过路径获取类
Class<?> class1=chen_chen_chen.class;//通过类名去获取类
Class<?> class2=new chen_chen_chen().getClass();//通过对象去获取类
下面两个基本没必要
Class<?> class3=chen_chen_chen.class.getClassLoader().loadClass(className);//通过类名去利用ClassLoader的loadClass获取类 
Class<?> class4=newchen_chen_chen().getClass().getClassLoader().loadClass(className);

class不同类的获取

Class<?> clazz= Class.forName(class$子类(或者是数字,应该于class中的第几个内部class)(内部类));//获取class内部类

通过反射对于属性进行获取数据

            Field nameField =  chen_chen_chenClass.getField("name");
            String name =(String)nameField.get(o2);
            System.out.println("name = " + name);
            Field ageField = chen_chen_chenClass.getField("age");
            int age =(int)ageField.get(o2);
            System.out.println("age = "+age);

反射调用方法:

            Method method =  chen_chen_chenClass.getMethod("test", String.class);//函数名以及类类型
            String string = (String)method.invoke(o2,"nihao_work");//传参
            System.out.println(string);

反射获取父类

            Class<?>[] Use =  chen_chen_chenClass.getInterfaces();
            System.out.println(Use.length);
            System.out.println(Use[0]);

反射遍历类的属性/同理将Fields修改为Method就可以遍历类的方

            Class<?> chen_chen_chenClass = Class.forName("chen_chen_chen");  
            Field [] fields = chen_chen_chenClass.getDeclaredFields();
            for(Field field:fields){
                System.out.println(field);
            }

利用匿名类去重写OnClick方法实现button控件的使用(获取并取得控件对象的方法)

findViewById同样可以获得控件对象

  	    binding = ActivityMainBinding.inflate(getLayoutInflater());//创建绑定实例
        setContentView(binding.getRoot());//利用视图来获取创建获取布局的根视图
        Button button1 = binding.button1;//面对对应的视图创建相应的对象
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("nihao","123456");
            }
        });
==================================
重写onclick类
button1.setOnClickListener(new MyClick()); //申请一个类实现我们点击触发的事件
class MyClick implements View.OnClickListener {
    @Override
    public void onClick(View view) {
        Log.d("xiaojianbnag", "onClick");
    }
}
===================================
在本类中去实现我们的鼠标点击的事件处理
  button1.setOnClickListener(this)//由于是在本类中,直接申请本类
  public void onClick(View view) {
        switch (view.getId()) {
            case R.id.button1:
                Log.d("xiaojianbnag", "onClick");
                break;
        }
    }

Toast土司提示框

Toast.makeText(MainActivity.this,"按钮点击",Toast.LENGTH_SHORT).show();

as中的字符串通常不会直接应用,而是通过在res——>valuse里面去获取值。

对于这样的数据,我们可以利用jeb在values——>public.xml里面去找到对应的值,之后找到了name之后利用name——>在strings.xml中去找到string

build.gradle

会记录该程序的执行的相关SDK版本,获取是相关SDK能够接受的最大或最小版本,以及相关的链接的libc库等相关的信息

plugins {
    alias(libs.plugins.android.application)
}

android {
    namespace = "com.chen.demo"
    compileSdk = 34    

    defaultConfig {
        applicationId = "com.chen.demo"
        minSdk = 24   //版本
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags += "-std=c++11"  //使用的cpp
            }
        }
    }
    buildTypes {
        release {
            isMinifyEnabled = false  //是否使用混淆
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    externalNativeBuild {
        cmake {
            path = file("src/main/cpp/CMakeLists.txt")
            version = "3.22.1"
        }
    }
    buildFeatures {
        viewBinding = true
    }
}
dependencies {
//链接的库
    implementation(libs.appcompat)
    implementation(libs.material)
    implementation(libs.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.ext.junit)
    androidTestImplementation(libs.espresso.core)
}

AndroidManifest

会包含相应的Android程序的相关信息,比如入口函数的名称,所使用的主题,或者所使用的图标....

假如这里设置了 android:name=".Myapplicaton" 那么函数的执行情况就是先Myapplicaton再MainActivity

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" >

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Demo"
        tools:targetApi="31"
        android:name=".Myapplicaton"
        >
        <activity
            android:name=".MainActivity"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

遍历HashMap

import java.util.HashMap;
import java.util.Set;
public class one11 {
    public static void main(String[] args) {
        HashMap<Object , Object> objectHashMap = new HashMap<Object, Object>();
        objectHashMap.put("key1", "value1");
        objectHashMap.put("key2", "value2");
        objectHashMap.put("key3", "value3");
        objectHashMap.put("key4", "value4");
        Set<Object> set = objectHashMap.keySet();//通过set去获取键对象存入列表中
        for (Object key : set) {
            System.out.println("Key: " + key + ", Value: " + objectHashMap.get(key));
        }
    }
} 
#
Key: key1, Value: value1
Key: key2, Value: value2
Key: key3, Value: value3
Key: key4, Value: value4

通过包名去获取程序的方法中遇到构造函数以及主动调用

    var bBase64 = Java.use("android.util.Base64")//这里获取了程序中的Base64类,最后主动调用并输出
	var DESKeySpec1 = Java.use("javax.crypto.spec.DESKeySpec")
    DESKeySpec1.$init.overload('[B').implementation=function(a){//通过$init 来调用构造函数
        console.log("DESKeySpec1 arg:",bBase64.encodeToString(a,0));
        this.$init(a);
    }

对于我们的Hook过程,我们可以通过主动调用去判断是否有重写错位frida过程中的方法

    var requestUtil = Java.use("com.dodonew.online.http.RequestUtil");
    requestUtil.encodeDesMap.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(a,b,c){
        console.log("encodeDesMap arg1:",a);
        console.log("encodeDesMap arg2:",b);
        console.log("encodeDesMap arg3:",c);
        var result = this.encodeDesMap(a,b,c);
        console.log("encodeDesMap result:",result);
        return result;
    }
    requestUtil.encodeDesMap("nihao", "123456","7891011");//直接调用,看是否有参数值被打印

通过HOOK方法来实现关键函数定位

利用Log.getStackTraceString()方法来打印堆栈

正常在安卓端可以通过LogOutPut(Log.getStackTraceString(new Throwable()))来打印堆栈,我们通过frida利用js来实现注入

    function showstack(){
        console.log(
            Java.use("android.util.Log").getStackTraceString(
                Java.use("java.lang.Throwable").$new()
                )
            );
    }//同理Log.getStackTraceString(new Throwable())
    var hashMap = Java.use("java.util.HashMap");//HOOK系统函数HashMap去实现打印
    hashMap.put.implementation = function(key, value) {
        if(key.equals("username")||key.equals("userPwd"))//条件判断
        {
            showstack();
            console.log("key " + key + ", value: " + value);
        }
        return this.put(key, value);
    }
------------------------------------------------------------------------------
------------------------------------------------------------------------------
同理去利用HOOKArrayList
    var arrayList = Java.use("java.util.ArrayList");
    arrayList.add.overload('int', 'java.lang.Object').implementation=function(key, value) {
        // showstack()
        console.log(".add.overload('int', 'java.lang.Object') key " + key + ", value: " + value);
        return this.add(key, value);
    }
    arrayList.add.overload('java.lang.Object').implementation=function(key) {
        // showstack()
        console.log("arg:.overload('java.lang.Object')" + key);
        return this.add(key);
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
利用isEmpty()方法来实现关键函数定位
    var textUtils = Java.use("android.text.TextUtils")
    textUtils.isEmpty.overload('java.lang.CharSequence').implementation=function(a) {
        console.log("arg:.implementation:" + a);
        return this.isEmpty(a);
------------------------------------------------------------------------------
------------------------------------------------------------------------------
利用collections.sort()来实现关键函数定位
    var collections = Java.use("java.util.Collections")
    collections.sort.overload('java.util.List').implementation=function(a){
        var arrayList = Java.cast(a,Java.use("java.util.ArrayList"));//向下转型
        console.log("arg:.implementation:" + arrayList.toString());
        return this.sort(a);
    }
------------------------------------------------------------------------------
------------------------------------------------------------------------------
利用JSONObject来实现
    var JSONObject = Java.use("org.json.JSONObject");
    JSONObject.put.overload('java.lang.String', 'java.lang.Object').implementation=function(a,b){
        console.log("JSONObject.put:" + a );
        console.log("arg:.implementation:" +b);
        return this.put(a,b);
    }
    JSONObject.getString.overload('java.lang.String').implementation=function(a){
        console.log("JSONObject.getString:" + a);
        return this.getString(a);
    }
------------------------------------------------------------------------------
------------------------------------------------------------------------------
     土司Toast
	var Toast = Java.use("android.widget.Toast");
    Toast.show.implementation=function(){
        console.log("Toast.show");
        return this.show;
    
    Base64.encodeToString方法
------------------------------------------------------------------------------
------------------------------------------------------------------------------      
    var base64 = Java.use("android.util.Base64");
    base64.encodeToString.overload('[B', 'int').implementaiton = function(a,b){
        showstack();
        console.log("Base64.encode",JSON.stringify(a));
        var result =this.encodeToString(a,b);
        console.log("result:"+result);
        return result;
     
------------------------------------------------------------------------------
------------------------------------------------------------------------------       
getBytes
        str.getBytes.overload('java.lang.String').implementation=function(a){
         showstack();
        var result = this.getBytes(a);
        var newStr = str.$new(result);
        console.log(newStr);
        

系统源码对于字符串构造函数的处理方式

当我们想要通过字符串来实现关键函数的定位时,我们就需要去HOOK字符串的构造函数,了解字符串是在什么时候进行构造和初始化的

查看一下系统源码对于字符串的处理:

public String() {
        throw new RuntimeException("Stub!");
    }

    public String(@RecentlyNonNull String original) {
        throw new RuntimeException("Stub!");
    }

    public String(char[] value) {
        throw new RuntimeException("Stub!");
    }

    public String(char[] value, int offset, int count) {
        throw new RuntimeException("Stub!");
    }

    public String(int[] codePoints, int offset, int count) {
        throw new RuntimeException("Stub!");
    }

    /** @deprecated */
    @Deprecated
    public String(byte[] ascii, int hibyte, int offset, int count) {
        throw new RuntimeException("Stub!");
    }//还有很多重载的string函数

我们就不去一一例举完String函数了,我们可以看到的是所以的string函数都是直接抛出了一个错误

其实字符串是通过StringFactory()函数来实现字符串构造的

利用StringFactory()方法实现HOOK

    var Stringfactory = Java.use("java.lang.StringFactory");
    Stringfactory.newStringFromString.overload('java.lang.String').implementation = function(a){
        var result = this.newStringFromString(a);
        console.log("Stringfactory.newStringFromString:"+result);
        return result;
    }
    Stringfactory.newStringFromChars.overload('[C').implementation= function(a){
        showstack();
        var result = this.newStringFromChars(a);
        console.log("Stringfactory.newStringFromChars:"+result);
        return result;
    }

关于字符串拼接的底层原理

我们在去利用字符串拼接来实现关键代码HOOK的时候,我们要去了解字符串拼接底层的实现过程,才能去真正的理解我们需要HOOK的函数

    static String simpleConcat(Object first, Object second) {
        String s1 = stringOf(first);
        String s2 = stringOf(second);
        if (s1.isEmpty()) {//检查两个字符串是否为空
            // newly created string required, see JLS 15.18.1
            return new String(s2);
        }
        if (s2.isEmpty()) {
            // newly created string required, see JLS 15.18.1
            return new String(s1);
        }
        // start "mixing" in length and coder or arguments, order is not
        // important
        long indexCoder = mix(initialCoder(), s1);
        indexCoder = mix(indexCoder, s2);
        //这两步是根据两个字符串的长度相加得到要创建newArray的大小,所以其实拼接的时候还是按照数组来实现的
        byte[] buf = newArray(indexCoder);
        // prepend each argument in reverse order, since we prepending
        // from the end of the byte array
        indexCoder = prepend(indexCoder, buf, s2);
        indexCoder = prepend(indexCoder, buf, s1);
        //这两步将申请的array逐步的按照每个字符串的大小进行赋值
        return newString(buf, indexCoder);
        //最后调用newString来实现真正的构造字符串

我们可以看到的是,其实底层是利用数组array来拼接,最后调用出newString来实现真正的新字符串的创建

利用这一点,我们就可以去HOOK newString()方法来实现通过字符串拼接过程中的HOOK代码,其中拼接过程中也会调用StringBuilder、StringBuffer方法,所以HOOK这两个系统函数也同样可以。

    var sbuilder = Java.use("java.lang.StringBuilder");
    sbuilder.toString.implementation = function()
    {
        var result = this.toString();
        if(result.indexOf("Encrypt")!=-1)
        {
            showstack();
        }
        console.log("StringBuilder.toString:"+result);
        return result;
    }
    var sbuffer = Java.use("java.lang.StringBuffer");
    sbuffer.toString.implementation = function()
    {
        var result = this.toString();
        if(result.indexOf("Encrypt")!=-1)
        {
            showstack();
        }
        console.log("StringBuffer.toString:"+result);
        return result;
    }
/*StringBuilder.toString:{"Encrypt":"NIszaqFPos1vd0pFqKlB42Np5itPxaNH\/\/FDsRnlBfgL4lcVxjXii\/UNcdXYMk0E1xRbP6wYR3bc\nqUsCY5VtJheBR6Vch9pVChB27DkdOh5kJ\/BCfnDkinSNzhb+4F5XRxXIiouW0HYNPUY8PfugWoiG\nJJLaSkJd0xTfYWoFaaoi2D4CDfqfwEgtz0EHpAWAAxWMUaUS\/6ZZ4IB\/NAR7CiHHriMnUXZR\n"}
StringBuilder.toString:java.lang.StringBuilder.toString(Native Method)
StringBuilder.toString:	at java.lang.StringBuilder.toString(Native Method)
StringBuilder.toString:com.dodonew.online.http.JsonRequest.addRequestMap(JsonRequest.java:117)
StringBuilder.toString:	at com.dodonew.online.http.JsonRequest.addRequestMap(JsonRequest.java:117)
StringBuilder.toString:com.dodonew.online.ui.LoginActivity.requestNetwork(LoginActivity.java:161)
StringBuilder.toString:	at com.dodonew.online.ui.LoginActivity.requestNetwork(LoginActivity.java:161)
StringBuilder.toString:com.dodonew.online.ui.LoginActivity.login(LoginActivity.java:134)
StringBuilder.toString:	at com.dodonew.online.ui.LoginActivity.login(LoginActivity.java:134)
StringBuilder.toString:com.dodonew.online.ui.LoginActivity.onClick(LoginActivity.java:103)
StringBuilder.toString:	at com.dodonew.online.ui.LoginActivity.onClick(LoginActivity.java:103)
StringBuilder.toString:android.view.View.performClick(View.java:7140)
StringBuilder.toString:	at android.view.View.performClick(View.java:7140)
StringBuilder.toString:android.view.View.performClickInternal(View.java:7117)
StringBuilder.toString:	at android.view.View.performClickInternal(View.java:7117)
StringBuilder.toString:android.view.View.access$3500(View.java:801)
StringBuilder.toString:	at android.view.View.access$3500(View.java:801)
StringBuilder.toString:android.view.View$PerformClick.run(View.java:27351)
StringBuilder.toString:	at android.view.View$PerformClick.run(View.java:27351)
StringBuilder.toString:android.os.Handler.handleCallback(Handler.java:883)
StringBuilder.toString:	at android.os.Handler.handleCallback(Handler.java:883)
StringBuilder.toString:android.os.Handler.dispatchMessage(Handler.java:100)
StringBuilder.toString:	at android.os.Handler.dispatchMessage(Handler.java:100)
StringBuilder.toString:android.os.Looper.loop(Looper.java:214)
StringBuilder.toString:	at android.os.Looper.loop(Looper.java:214)
StringBuilder.toString:android.app.ActivityThread.main(ActivityThread.java:7356)
StringBuilder.toString:	at android.app.ActivityThread.main(ActivityThread.java:7356)
StringBuilder.toString:java.lang.reflect.Method.invoke(Native Method)
StringBuilder.toString:	at java.lang.reflect.Method.invoke(Native Method)
StringBuilder.toString:com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
StringBuilder.toString:	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
StringBuilder.toString:com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
StringBuilder.toString:	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
StringBuffer.toString:java.lang.Throwable
*/

利用enumerateLoadedClassesSync来枚举出已经加载之后的类名

    var classArr = Java.enumerateLoadedClassesSync();
    for(var i=0; i<classArr.length; i++) 
    {
        console.log("classname",classArr[i]);
    }//程序中app的类可能会改变,我们要通过枚举之后来判断

通过枚举出来的类,我们找到了控制findViewById的参数控件的类"android.support.v7.app.AppCompatActivity"通过hook进一步来实现控件ID的输出,最后比对我们需要的控件ID,来实现showstack()

    var appCompatActivity=Java.use("android.support.v7.app.AppCompatActivity");
    console.log(appCompatActivity);
    appCompatActivity.findViewById.implementation=function(a)
    {
        console.log("findViewB yId:"+a); 
        if(a==btn_login_id)
        {
            showstack();
            console.log("findViewById btn_login_id------stack()");
        }
        return this.findViewById(a);
    }

这里我们需要注意:我们假如去hook控件,要使用frida开启 -f 来自启动app,而不是我们来打开,因为控件在我们程序启动出现界面之前就完成了初始化和操作,所以要使用frida来自启动程序

 frida -U -f com.dodonew.online  -l .\test1.js
  -f FILE, --file=FILE  spawn FILE   
  -F, --attach-frontmost            //要取消-F 选择-f 
                        attach to frontmost application

setOnClickListener()方法用于实现点击事件的处理。

 button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("nihao","123456");
            }//对于按钮button1来进行事件处理

所以我们可以Hook这个setOnClickListener()方法来实现我们的过程。

函数参数的修改以及返回值的修改

    var Money = Java.use("com.xiaojianbang.hook.Money")
    var str =Java.use("java.lang.String")
    Money.getInfo.implementation=function(){
        var result = this.getInfo();
        console.log("Money.getInfo.implementation",result);
        //return result
        //return "chen_chen_chen";
        return str.$new("chen_chen_chen");//这里本来应该返回result,但是我们修改了返回值,并且由于我们应该返回的是java类型的字符串,所以调用了java的类包来实现的。 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    var Money = Java.use("com.xiaojianbang.hook.Money");
    var Wallet = Java.use("com.xiaojianbang.hook.Wallet");
    Wallet.deposit.implementation=function(a){
        console.log("Wallet.deposit.implementation",a.getInfo());
        return this.deposit(Money.$new("美元",200));
    }//同理

对于可能遇到自定义的类的参数,由java转为了js之后的打印问题

由于可能是自定义的类,对于方法没有进行重写toString()方法,从而导致打印不出对于的参数,这里以map类型的HashMap为例,让我们能够得到正确的输出结果。

    var StringBuild=Java.use("java.lang.StringBuilder");
    var Utils= Java.use("com.xiaojianbang.hook.Utils");
    Utils.shufferMap.implementation=function(a){
        // console.log("Utils.shufferMap.implementation",a.toString());
        var set = a.keySet();
        var it =set.iterator();
        var result = StringBuild.$new();
        while(it.hasNext()) {
            var key = it.next();
            var value = a.get(key);
            result.append(value);
        }
        console.log("Utils.shufferMap.implementation",result.toString());
        return this.shufferMap(a);} 
    }

面对于多个参数和多个重载函数的时候,我们应该怎么去做到一次性进行Hook,就可以直接得到结果

    var Utils = Java.use("com.xiaojianbang.hook.Utils");    
    var overloadarr = Utils.getCalc.overloads;//获取overload的数组
    for (var i=0; i< overloadarr.length; i++)//遍历数组
    {
        overloadarr[i].implementation=function()
        {
            var params="";
            for(var j=0;j<arguments.length;j++)//利用js中的arguments方法获取参数
            {
                params += "arg"+j+":"+arguments[j]+"\n";
            };
            console.log("Utils.getCalc.overloads["+i+"]-----",params);
            return this.getCalc.apply(this,arguments)//利用apply()方法来实现call原函数 apply(object,参数数组)
        }
    }

遍历多个属于同一个类的成员中的方法

    var Money =Java.use("com.xiaojianbang.hook.Money")
    Money.$new("日元",500000);//主动调用
    Java.choose("com.xiaojianbang.hook.Money",  //获取创建了Money类中的所有成员
        {
            onMatch: function(obj)
            {
                console.log(obj.getInfo());//调用getInfo()
            },
            onComplete: function()
            {
                console.log("done");//匹配完成之后的输出
            }
        }
    )

赋值和修改类的字段

//static静态变量修改--直接获取class可以直接访问
    var Money =Java.use("com.xiaojianbang.hook.Money");
    console.log("Money.flag1",Money.flag.value);
    Money.flag.value = "日元";
    console.log("Money.flag2",Money.flag.value);
-------------------------------------------------------------------
-------------------------------------------------------------------
//通过利用Java.choose去实现获取类成员进行修改
    var Money =Java.use("com.xiaojianbang.hook.Money");
    Java.choose("com.xiaojianbang.hook.Money",{
        onMatch: function(obj)
        {
            console.log("money.currency:"+obj.currency.value);
        },
        onComplete:function()
        {
            console.log("done");
        }
    })
    Java.choose("com.xiaojianbang.hook.BankCard",{
        onMatch: function(obj)
        {
            console.log("BankCard.currency:"+obj._accountName.value);//这里类中的方法名和变量名相同,要加上'_'
        },
        onComplete:function()
        {
            console.log("done");
        }
    })

匿名内部类的Hook

    var MainActivity$1 = Java.use("com.xiaojianbang.app.MainActivity$1")
    MainActivity$1.getInfo.implementation = function() {
        var result = this.getInfo();
        console.log("MainActivity$1.getInfo.implementation",result);
        return result;
    }

主动调用AndriodStdio里面的base64.decode()解密base64字符串,实现混淆函数HOOK

    var base64 = Java.use("android.util.Base64");
    var Money = Java.use("com.xiaojianbang.hook.Money");
    var String = Java.use("java.lang.String");
    console.log(base64.decode("Z2V0SW5mbw==",0));//由于decode之后的结果是 byte[]类型,需要进行转置为String 带入Money[]
    console.log(String.$new(base64.decode("Z2V0SW5mbw==",0)))
    Money[String.$new(base64.decode("Z2V0SW5mbw==",0))].implementation = function() {//通过Money[方法变量]方式访问 同理为Money.getInfo()
        var result = this.getInfo()
        console.log("Money.getInfo.implementation",result);
        return result;
    }

dex混淆保护,通过对于原始dex进行读取,然后进行加密获取新的dex文件,实现混淆保护

try {
            File oldDexFile = new File("/data/data/com.xiaojianbang.testdex/classes.dex");
            File newDexFile = new File("/data/data/com.xiaojianbang.testdex/new_classes.dex");
            Log.d("xiaojianbang", "从assets中读取classes.dex...");
            InputStream ins = getApplicationContext().getAssets().open("classes.dex");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] bytes = new byte[1024];
            int index;
            while ((index = ins.read(bytes)) != -1)
                baos.write(bytes, 0, index);
            Log.d("xiaojianbang", "开始写出classes.dex到" + oldDexFile.toString() + "...");

            FileOutputStream fos = new FileOutputStream(oldDexFile);
            fos.write(baos.toByteArray());
            ins.close();
            fos.close();
            baos.close();
---------------------------------------------------------------------------------------------------
   //以上是读取dex,设置读取完成之后的存储位置
    
            Log.d("xiaojianbang", "开始用dexlib2加载classes.dex...");
            Opcodes opcodes = Opcodes.forApi(29);
            DexFileFactory.writeDexFile(oldDexFile.toString(), DexFileFactory.loadDexFile(oldDexFile, opcodes));//加载DEX文件,并将其转换为内部表示形式,以便进行进一步操作
            DexBackedDexFile dexBackedDexFile = DexFileFactory.loadDexFile(oldDexFile, opcodes);//用于调试或验证DEX文件是否能被正确加载和写入
            Log.d("xiaojianbang", "dexFile: " + dexBackedDexFile.getClass().getName());

            DexRewriter rewriter = new DexRewriter(new RewriterModule() {
                @Override
                public Rewriter<ClassDef> getClassDefRewriter(Rewriters rewriters) {
                //传递一个匿名RewriterModule实例来定义重写逻辑
                    return new ClassDefRewriter(rewriters) {
                        @Override
                        public ClassDef rewrite(ClassDef classDef) {
                            String className = classDef.getType();
                            if (className.contains("com/xiaojianbang")) {
                                //检查类名是否包含特定字符串(如"com/xiaojianbang")
                                return new RewrittenClassDef(classDef) {
                                    @Override
                                    public String getType() {//重写 getType()
                                        String class_fullName = classDef.getType();
                                        int start = class_fullName.lastIndexOf("/");
                                        int end = class_fullName.lastIndexOf("$");
                                        int length = class_fullName.length();
                                        if (end < 0) {
                                            if (class_fullName.contains(";")) {
                                                end = length - 1;
                                            } else {
                                                end = length;
                                            }
                                        }
                                        String class_name = class_fullName.substring(start + 1, end);
                                        String class_name_enc = Base64.encodeToString(class_name.getBytes(), 0);
                                        Log.d("xiaojianbang", class_name + " " + class_name_enc);
                                        return class_fullName.replace(class_name, class_name_enc);
                                    }

                                    @Override
                                    public String getSourceFile() {
                                        return null;
                                    }
                                };
                            }
                            return super.rewrite(classDef);
                        }
                    };
                }

                @Override
                public Rewriter<Field> getFieldRewriter(Rewriters rewriters) {
                    return new FieldRewriter(rewriters) {
                        @Override
                        public Field rewrite(Field field) {
                            String className = field.getDefiningClass();
                            if (className.contains("com/xiaojianbang")) {
                                return new RewrittenField(field) {
                                    @Override
                                    public String getName() {
                                        return Base64.encodeToString(field.getName().getBytes(), 0);
                                    }
                                };
                            }
                            return super.rewrite(field);
                        }
                    };
                }
                @Override
                public Rewriter<Method> getMethodRewriter(Rewriters rewriters) {
                    return new MethodRewriter(rewriters) {
                        @Override
                        public Method rewrite(Method value) {
                            String className = value.getDefiningClass();
                            if (className.contains("com/xiaojianbang")) {
                                return new RewrittenMethod(value) {
                                    @Override
                                    public String getName() {
                                        String methodName = Base64.encodeToString(value.getName().getBytes(), 0);
                                        Log.d("xiaojianbang", "methodName: " + methodName);
                                        return methodName;
                                    }
                                };
                            }
                            return super.rewrite(value);
                        }
                    };
                }
            });
----------------------------------------------------------------------------------------------------
//以上是通过对于dex文件中的类名方法名进行对比,假如有"com/xiaojianbang"字段的就去获取并进行base64
            DexFile rewriteDexFile = new DexFileRewriter(rewriter).rewrite(dexBackedDexFile);
            DexPool dexPool = new DexPool(rewriteDexFile.getOpcodes());
//rewrite方法会根据rewriter中定义的规则修改DEX文件的内容,并返回一个新的DexFile对象,其中包含修改后的DEX数据。
            for (ClassDef classDef : rewriteDexFile.getClasses()) {
                dexPool.internClass(classDef);
            }
            //dexPool.internClass(buildClasses());
//创建了一个DexPool对象,它用于管理DEX文件中的类定义。通过遍历rewriteDexFile中的所有类定义(ClassDef对象),并使用dexPool.internClass(classDef)方法将它们注册到DexPool中。
            dexPool.writeTo(new FileDataStore(newDexFile));
            //DexFileFactory.writeDexFile(newDexFile.toString(), rewriteDexFile);
            Log.d("xiaojianbang", "new_classes.dex生成完毕,保存路径为:" + newDexFile.toString());

        } catch (IOException e) {
            e.printStackTrace();
        }

利用反射来实现获取类中的属性

var Wallet = Java.use("com.xiaojianbang.hook.Wallet");
    console.log("methods:");
    var methods =Wallet.class.getDeclaredMethods();//获取已经声明的方法
    for (var i = 0; i < methods.length; i++) {
        console.log(methods[i].getName());
    }
    console.log("----------------------------------------------------------------");
    var Constructor = Wallet.class.getDeclaredConstructors();//获取已经声明的构造器
    console.log("Constructor:");
    for (var i = 0; i < Constructor.length; i++) {
        console.log(Constructor[i].getName());
    }
    console.log("----------------------------------------------------------------");
    var Fields = Wallet.class.getDeclaredFields();//获取已经声明的字段(变量)
    console.log("Fields:");
    for (var i = 0; i < Fields.length; i++) {
        console.log(Fields[i].getName());
    }
    console.log("----------------------------------------------------------------");
    var Classes = Wallet.class.getDeclaredClasses();//获取已经声明的内部类
    console.log("Classes:");
    for (var i = 0; i < Classes.length; i++) {
        console.log(Classes[i].getName());
        var Wallet$InnerStructure = Classes[i].getDeclaredFields();//获取已经声明的内部类的内部属性
        console.log("----------------------------------------------------------------");
        console.log("Wallet$InnerStructure:");
        for (var j = 0; j < Wallet$InnerStructure.length; j++) {
            console.log("Wallet$InnerStructure" + Wallet$InnerStructure[j].getName());
        }
    }

结合上面的过程,我们再去HOOK每个方法的重载函数

    var Utils = Java.use("com.xiaojianbang.hook.Utils");
    console.log("methods:");
    var methods =Utils.class.getDeclaredMethods();
    for (var i = 0; i < methods.length; i++) {
        console.log(methods[i].getName());
        let methodsname = methods[i].getName();
        Hook_method_overload(methodsname);
    }

    function Hook_method_overload(methods_name) {
    {
        var overloadsarr = Utils[methods_name].overloads;
        console.log("methods_name:",methods_name);
        for (var i = 0; i < overloadsarr.length; i++) 
        {
            overloadsarr[i].implementation = function()  
            {
                console.log("Utils.getCalc.overloads["+i+"]-----");//打印重载方法名和参数
                var params="";
                for(var j=0;j<arguments.length;j++)//利用js中的arguments方法获取参数
                {
                    params += "arg"+j+":"+arguments[j]+"\n";
                };
                console.log("Utils.getCalc.overloads["+i+"]-----",params);
                return this[methods_name].apply(this,arguments)//利用apply()方法来实现call原函数 apply(object,参数数组)
            }
        }
    }
}

Java的dex文件注入

我们通过Android studio去创新自己的APK文件,里面包含了想要实现的类,方法......,利用frida注入,从而实现我们利用方法使用

package com.chen.myapplication;

import android.util.Log;

public class Test {
    public static void test()
    {
        Log.d("tag","test!!!!!!!!!!!!注入");
    }
}

JS注入代码

Java.openClassFile("/data/local/tmp/Testclasses.dex").load();//这里的先要将自己生成的APK文件中的dex文件push到手机端去
var Test=java.use("com.chen.myapplication.Test");
//Test.test.implementation=function()
//{
//	return this.test;
//}
Test.test();

数据类型由Map转为HashMap的过程以及Java.cast的使用方法

    var Utils = Java.use("com.xiaojianbang.hook.Utils");
    Utils.shufferMap2.implementation=function(a){
        console.log("Utils.shufferMap2.implementation",a);
        console.log("Utils.shufferMap2.implementation",a.toString());
        var result = Java.cast(a,Java.use("java.util.HashMap"));
        console.log(result);
        return this.shufferMap2(a);
    }

通过js来创建数组的过程传递给JAVA程序

    var Utils = Java.use("com.xiaojianbang.hook.Utils");
    var result =Utils.myPrint(["chen_chen_chen","xiaojianbang","ahhaha"]);
    console.log(result);
    var array =Java.array(
        "Ljava.lang.String;",
        ["chen_chen_chen","xiaojianbang","ahhaha"]
        );
    var result1 = Utils.myPrint(array);
    console.log(result1);

注意:

/*    public static String myPrint(Object... objArr) {
        StringBuilder sb = new StringBuilder();
        for (Object obj : objArr) {
            sb.append(obj);
            sb.append("|");
        }
        return sb.toString();
    }
 */  
这里假如要HOOK或者是创建的函数是Object类型的,字符串是可以直接进行传值的,但是int或者是bool类型的数据是不可以直接传值的,我们这里可以看到的是传入的是Object类型,也就是所有的对象都可以进行传入,但是失败。

原因是我们传入的数据其实在java底层是通过创建相应的方法进行传值的,比如 int ——>Integer bool ——>Bool

所以我们也要进行数据的封装,最后达到应有的效果

    var Utils = Java.use("com.xiaojianbang.hook.Utils");
    var   
result=Utils.myPrint([
"chen_chen_chen",
Java.use("java.lang.Integer").$new(100),
Java.use("java.lang.Boolean").$new(false)
]);
    console.log(result);

===================================================================================
创建一个Array来进行传值
    var ArrayList = Java.use("java.util.ArrayList");
    var arrry = ArrayList.$new();
    arrry.add("chen_chen_chen");
    arrry.add(Java.use("java.lang.Integer").$new(100));
    arrry.add(Java.use("java.lang.Boolean").$new(false));
    console.log(arrry)
    var Utils = Java.use("com.xiaojianbang.hook.Utils");
    var result =Utils.myPrint(arrry);
    console.log(result);

ClassLoader不同对于同一个类加载之后的class是不同的,我们可能可以按照对应了的包名来进行搜索,但是结果可能是不对的,这时候我们可以去枚举相应的类加载器来查看相应的加载过程

    Java.enumerateClassLoaders(
    {
    onMatch:function(loader)
    {
        try
        {
            console.log(loader);
            Java.classFactory.loader = loader;
            var dynamic = Java.use("com.xiaojianbang.app.Dynamic")
            console.log("找到了!!!!!!!!",dynamic);
            dynamic.sayHello.implementation = function(){
                console.log("Hook dynamic is run!!!!!!")
                return "chen_chen_chen_chen"
            }
        }
        catch(e)
        {
            console.log("False")
        };
    },
    onComplete:function(){
    console.log("enumerateClassLoaders completed");
    }
});
/*
dalvik.system.PathClassLoader[DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /vendor/lib64, /system/product/lib64, /system/lib64, /vendor/lib64, /system/product/lib64]]]
False
java.lang.BootClassLoader@58f6796
False
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.xiaojianbang.app-qX4fZHwYEPhfLXNw8TFC2g==/base.apk"],nativeLibraryDirectories=[/data/app/com.xiaojianbang.app-qX4fZHwYEPhfLXNw8TFC2g==/lib/arm64, /data/app/com.xiaojianbang.app-qX4fZHwYEPhfLXNw8TFC2g==/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64, /system/product/lib64]]]
False
dalvik.system.DexClassLoader[DexPathList[[dex file "/data/user/0/com.xiaojianbang.app/app_shell/xiaojianbang.dex"],nativeLibraryDirectories=[/data/app/com.xiaojianbang.app-qX4fZHwYEPhfLXNw8TFC2g==/lib/arm64, /system/lib64, /vendor/lib64, /system/product/lib64]]]
找到了!!!!!!!! <class: com.xiaojianbang.app.Dynamic>
enumerateClassLoaders completed
*/

接口函数的HOOK

由于我们知道,可能会发现某个可疑或者是包含我们关键参数的接口,里面存在着我们希望定位到关键函数的成员属性,所以我们回去时不时的HOOK接口,从而定位代码(由于可能存在对应的混淆点,比如方法名全为a,b,c,d或者一些难以通过交叉引用得到的保护方法)我们就需要HOOK接口函数

    var classes = Java.enumerateLoadedClassesSync();
    for(const index in classes)
    {
        let className = classes[index];
        if(className.indexOf("com.xiaojianbang")==-1)continue;
        console.log("com.xiaojianbang's classname",className);
        //打印已经加载的class同时满足类名中包含"com.xiaojianbang"
        let clazz = Java.use(className);
        let resultArr = clazz.class.getInterfaces();//获取接口
        for(let i=0;i<resultArr.length;i++)
        {
            if(resultArr[i].toString().indexOf("com.xiaojianbang.app.TestRegisterClass")==-1)//比对接口是否是包含该接口
            {
                console.log(className,resultArr);    
            } 
        }
    }
/*
com.xiaojianbang's classname com.xiaojianbang.app.InterfaceDemo
com.xiaojianbang's classname com.xiaojianbang.app.TestAbstract
com.xiaojianbang's classname com.xiaojianbang.hook.BankCard
com.xiaojianbang's classname com.xiaojianbang.encrypt.RSA_Base64
com.xiaojianbang's classname com.xiaojianbang.hook.Wallet$InnerStructure
com.xiaojianbang's classname com.xiaojianbang.app.TestRegisterClass
com.xiaojianbang's classname com.xiaojianbang.app.MainActivity$1
com.xiaojianbang's classname com.xiaojianbang.ndk.NativeHelper
com.xiaojianbang's classname com.xiaojianbang.app.MainActivity
com.xiaojianbang.app.MainActivity interface android.view.View$OnClickListener
com.xiaojianbang's classname com.xiaojianbang.hook.Money
com.xiaojianbang's classname com.xiaojianbang.hook.Wallet
com.xiaojianbang's classname com.xiaojianbang.app.AbstractDemo
com.xiaojianbang's classname com.xiaojianbang.app.databinding.ActivityMainBinding
com.xiaojianbang.app.databinding.ActivityMainBinding interface androidx.viewbinding.ViewBinding
com.xiaojianbang's classname com.xiaojianbang.hook.Utils
com.xiaojianbang's classname com.xiaojianbang.reflect.ReflectEncrypt
*/可以让我们发现对应的"com.xiaojianbang"字段的类名以及接口名的对应

同理的方法获取父类的函数名

var classes = Java.enumerateLoadedClassesSync();
    for(const index in classes)
    {
        try{
            let className = classes[index];
            if(className.indexOf("com.xiaojianbang")==-1)continue;
            console.log("com.xiaojianbang's classname",className);
            //打印已经加载的class同时满足类名中包含"com.xiaojianbang"
            let clazz = Java.use(className);
            let resultArr = clazz.class.getSuperclass();//获取接口
            console.log("clazz.class.getSuperclass()",resultArr)
            for(let i=0;i<resultArr.length;i++)
            {
                if(resultArr[i].toString().indexOf("com.xiaojianbang.app.TestRegisterClass")==-1)//比对接口是否是包含该接口
                {
                    console.log(className,resultArr);    
                } 
            }
        }
        catch(e){
            console.log("className:"+"error:----------------------------------")
        }
    }

objection自动化HOOK工具

启动:

objection -g <app:com.xiaojianbang.app> explore

使用:

android hooking list classes 
//加载出所以的已加载的类,对应——> Java.enumerateLoadedClassesSync()
android hooking search classes <pattern字段>
//加载出所有包含字段的类
android hooking list class_methods <类名、路径> 如:com.xiaojianbang.hook.Wallet
//加载出对应类的类方法
android hooking watch class <类名>
//Hook对应类的所有方法(不包含构造函数)
android hooking watch class_method <路径、类名>如:com.xiaojianbang.hook.Wallet.getBalance
//Hook对应的类方法,同时可以选择参数:--dump-args 打印参数--dump-return 打印返回值 --dump-backtrace 打印堆栈

objection插件wallbreaker的使用

//objection插件加载:
plugin load "c:\Users\19817\Desktop\Wallbreaker-master" Wallbreaker Loaded plugin: Wallbreaker
//相关类搜索:
plugin Wallbreaker classsearch xiaojianbang
//相关方法的查看
 plugin Wallbreaker classdump com.xiaojianbang.hook.Utils
 //类方法实现的查看
plugin Wallbreaker objectsearch com.xiaojianbang.hook.Money
[0x294a]: com.xiaojianbang.hook.Money@959256
//利用实现的类名去实现相应类的方法查看
plugin Wallbreaker objectdump 0x2622
package com.xiaojianbang.hook
class Money {
        /* static fields */
        static String flag; => QQ:24358757
        /* instance fields */
        int amount; => 100
        String currency; => 人民币
        /* constructor methods */
        com.xiaojianbang.hook.Money(String, int);
        /* static methods */
        static String getFlag();
        static void setFlag(String);
        /* instance methods */
        int getAmount();
        String getCurrency();
        String getInfo();
        void setAmount(int);
        void setCurrency(String);
}

利用objection patchapk 重新打包apk,将frida-gadget注入(免root注入)

我们frida中的root注入的原理就是通过frida生成的一个so文件,实现root注入,我们通过将要hook的程序进行重新打包,并且将frida-gadget一并注入的结果就是,我们不需要root权限就可以直接实现注入过程

(这里会有代理检测,所以可以提前把对应版本的frida-gadget下到对应的框架版本里面去)

objection patchapk -a arm64-v8a -V 14.2.18 -s c:\Users\19817\Desktop\HookDemo.apk

成功打包之后直接frida注入就可以

frida -UF ./exp.js

base64java编码

import java.util.Base64;  
public class Base64Example {  
    public static void main(String[] args) {  
        // 待编码的字符串  
        String originalString = "Hello, World!";  
  
        // 使用Base64的getEncoder()方法获取编码器  
        Base64.Encoder encoder = Base64.getEncoder();  
  
        // 对字符串进行编码,注意:字符串需要先转换为字节数组  
        String encodedString = encoder.encodeToString(originalString.getBytes());  
  
        // 输出编码后的字符串  
        System.out.println("Encoded String: " + encodedString);  
  
        // 如果你想要进行URL安全的Base64编码,可以使用Base64.getUrlEncoder()  
        Base64.Encoder urlEncoder = Base64.getUrlEncoder();  
        String urlEncodedString = urlEncoder.encodeToString(originalString.getBytes());  
  
        // 输出URL安全的编码后的字符串  
        System.out.println("URL Encoded String: " + urlEncodedString);  
    }  
}

WebView的可调式权限Hook

Java.perform(function(){
    var WebView = Java.use('android.webkit.WebView');
    WebView.$init.overload('android.content.Context').implementation = function(a){
        console.log("WebView.$init is called!");
        var retval = this.$init(a);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet').implementation = function(a, b){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int').implementation = function(a, b, c){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'boolean').implementation = function(a, b, c, d){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'int').implementation = function(a, b, c, d){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'java.util.Map', 'boolean').implementation = function(a, b, c, d, e){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d, e);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
    WebView.$init.overload('android.content.Context', 'android.util.AttributeSet', 'int', 'int', 'java.util.Map', 'boolean').implementation = function(a, b, c, d, e, f){
        console.log("WebView.$init is called!");
        var retval = this.$init(a, b, c, d, e, f);
        this.setWebContentsDebuggingEnabled(true);
        return retval;
    }
//这里以上全是Hook的是WebView不同重载函数初始化的过程,其中this.setWebContentsDebuggingEnabled(true)也就是为了将调试权限设置为webview可调式的过程
    WebView.setWebContentsDebuggingEnabled.implementation = function(){
        this.setWebContentsDebuggingEnabled(true);
        console.log("setWebContentsDebuggingEnabled is called!");
    }
//这里是hook了这个setWebContentsDebuggingEnabled方法,不管如何就设置为ture
});

谷歌的webview的网址

chrome://inspect/#devices

假如我们发现的app其实是h5,也就是原本我们看到的一个登录的图示其实是浏览器的登录图示,只是直接嵌套到了app中,那么我们在实现了webview的调试权限之后就可以直接去利用web的js逆向了

md5和base64以及SHA加密java代码

        MessageDigest md51 = MessageDigest.getInstance("MD5");//md5实现的两种方法
        md51.update("md5_encode_date".getBytes());
        byte[] result =  md51.digest();
        StringBuilder hexString1 = new StringBuilder();
        for (byte b : result) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString1.append('0');
            hexString1.append(hex);
        }
        System.out.println(hexString1);
        System.out.println(Base64.getEncoder().encodeToString(result));
-----------------------------------------------------------------------------------------------
        byte[] md5 = MessageDigest.getInstance("MD5").digest("md5_encode_date".getBytes());
        StringBuilder hexString = new StringBuilder();
        for (byte b : md5) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        System.out.println(hexString);
        System.out.println(Base64.getEncoder().encodeToString(md5));
    }
-----------------------------------------------------------------------------------------------
        String date="{\"appid\":\"2\",\"username\":\"15828712516\",\"password\":\"passworf777\",\"cpsId\":\"tg001lxx\",\"imei\":\"2ffe0d3b-9a69-4513-9e7f-87d4334432bb\"}";
        String base64_date =Base64.getEncoder().encodeToString(date.getBytes());//Base64
        System.out.println(base64_date);
--------------------------------------------------------------------------------------
        MessageDigest md5 = MessageDigest.getInstance("SHA-256");//SHA-1,SHA-256.....
        md5.update("md5_encode_date".getBytes());
        byte[] result =  md5.digest();
        StringBuilder hexString = new StringBuilder();
        for (byte b : result) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        System.out.println(hexString);
        System.out.println(Base64.getEncoder().encodeToString(result));

Java利用第三方库实现加密签名算法通杀

Java.perform(function () {

    function showStacks() {
        console.log(
            Java.use("android.util.Log")
                .getStackTraceString(
                    Java.use("java.lang.Throwable").$new()
                )
        );
    }

    var ByteString = Java.use("com.android.okhttp.okio.ByteString");
    function toBase64(tag, data) {
        console.log(tag + " Base64: " + ByteString.of(data).base64());
    }
    function toHex(tag, data) {
        console.log(tag + " Hex: " + ByteString.of(data).hex());
    }
    function toUtf8(tag, data) {
        console.log(tag + " Utf8: " + ByteString.of(data).utf8());//以上是通过利用第三方库实现base64,hex,utf8的转换过程
    }      
    // toUtf8("xiaojianbang",[48, 49, 50, 51, 52]);
    // toBase64("xiaojianbang",[48, 49, 50, 51, 52]);
    // toHex("xiaojianbang",[48, 49, 50, 51, 52]);

    var messageDigest = Java.use("java.security.MessageDigest");//以下是对于messageDigest的update方法进行的HOOK,由于update是传入的是需要进行加密的byte数组,所以通过hook来查看初始数据
    messageDigest.update.overload('byte').implementation = function (data) {
        console.log("MessageDigest.update('byte') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");
        showStacks();
        return this.update(data);
    }
    messageDigest.update.overload('[B').implementation = function (data) {
        console.log("MessageDigest.update('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();//getAlgorithm()是该类下面的对应方法,是用来获取getInstance()中的内容的,也就是利用的算法
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.update('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    //这里以上是update的hook,以下是digest的hook
------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
    messageDigest.digest.overload().implementation = function () {
        console.log("MessageDigest.digest() is called!");
        showStacks();
        var result = this.digest();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest result";
        toUtf8(tag, result);
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B').implementation = function (data) {
        console.log("MessageDigest.digest('[B') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data);
        var tags = algorithm + " digest result";
        toUtf8(tag, result);
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================");
        return result;
    }
    messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("MessageDigest.digest('[B', 'int', 'int') is called!");
        showStacks();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " digest data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        var result = this.digest(data, start, length);
        var tags = algorithm + " digest result";
        toHex(tags, result);
        toBase64(tags, result);
        console.log("=======================================================", start, length);
        return result;
    }
    //这里以上是hook的digest(),同时将其的参数进行hex,utf8,base64的转码进行输出
------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
    var mac = Java.use("javax.crypto.Mac");
    mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
        console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
        return this.init(key, AlgorithmParameterSpec);
    }
    mac.init.overload('java.security.Key').implementation = function (key) {
        console.log("Mac.init('java.security.Key') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = key.getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        console.log("=======================================================");
        return this.init(key);
    }
    mac.update.overload('byte').implementation = function (data) {
        console.log("Mac.update('byte') is called!");
        return this.update(data);
    }
    mac.update.overload('java.nio.ByteBuffer').implementation = function (data) {
        console.log("Mac.update('java.nio.ByteBuffer') is called!");
        return this.update(data);
    }
    mac.update.overload('[B').implementation = function (data) {
        console.log("Mac.update('[B') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================");
        return this.update(data);
    }
    mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
        console.log("Mac.update('[B', 'int', 'int') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " update data";
        toUtf8(tag, data);
        toHex(tag, data);
        toBase64(tag, data);
        console.log("=======================================================", start, length);
        return this.update(data, start, length);
    }
    mac.doFinal.overload().implementation = function () {
        console.log("Mac.doFinal() is called!");
        var result = this.doFinal();
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " doFinal result";
        toUtf8(tag, result);
        toHex(tag, result);
        toBase64(tag, result);
        console.log("=======================================================");
        return result;
    }
    mac.doFinal.overload('[B').implementation = function (data) {
        console.log("Mac.doFinal.overload('[B') is called!");
        return this.doFinal(data);
    }
    mac.doFinal.overload('[B', 'int').implementation = function (output, outOffset) {
        console.log("Mac.doFinal.overload('[B', 'int') is called!");
        return this.doFinal(output, outOffset);
    }
});//这里是对于javax.crypto.Mac的init,update,doFinal的hook

DES加密算法java算法库实现

DES对称加密中的分组加密算法,故名思意,分组加密算法,一组一组的进行加密,前一组的数据不会影响后一组数据的加密,后组也不会影响前组。按照八字节八字节进行分组加密

对于ECB模板下的DES,不需要使用IV向量进行加密

对于CBC对称八字节的密钥,则需要八字节的IV与明文进行异或操作。(这里细节的讲一下CBC模型的iv的用法,由于DES是分组加密算法,每八个字节的进行加密,IV用于与第一个八字节的明文先进行异或操作,然后进行第一组的des加 密,得到的密文结果作为第二组加密时,与明文进行异或的数据,依次每组的结果异或下一组的明文)

同时填充方式:

NOPadding 没有填充,则明文的长度要和IV向量的一样,八字节,或者是八的倍数

PKCS5Padding 这种填充方式,填充的字节长度是1-8个字节的区间,当明文不是八的倍数的字节长度,直接补为八的倍数;当明文是八的倍数则补8个字节的长度。

	//encode
			try{
              SecretKeySpec secretKeySpec = new SecretKeySpec("12345678".getBytes(), "DES");//DES密钥是八个字节
              Cipher des = Cipher.getInstance("DES/CBC/PKCS5Padding");//创建对应的传参对象,确定加密信息
              byte[] iv = new byte[8];
              Arrays.fill(iv,(byte)0);
              IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);//iv
              des.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
              byte[] des_array = des.doFinal("Text_date".getBytes());
              System.out.println(hex(des_array));
              System.out.println(Base64.getEncoder().encodeToString(des_array));
  --------------------------------------------------------------------------------------------------
     //decode
              des.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
              byte[] decode = des.doFinal(des_array);
              System.out.println(new String(decode));
          }
          catch (Exception e)
          {
              e.printStackTrace();
          }

DESede(3DES)三次进行DES加密:第一次DES加密,第二次DES解密,第三次DES加密

          try{
              SecretKeySpec secretKeySpec = new SecretKeySpec("123456781234567812345678".getBytes(), "DESede");//这里由于我们是进行的DESede加密,这里的密钥,第一个八字节作为第一次加密的密钥,第二个八字节作为第二次解密的密钥,第三个八字节作为第三次加密的密钥
              Cipher des = Cipher.getInstance("DESede/CBC/NOPadding");
              IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes());
              des.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
              byte[] des_array = des.doFinal("TextdateTextdate".getBytes());
              System.out.println(hex(des_array));
              System.out.println(Base64.getEncoder().encodeToString(des_array));
              des.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
              byte[] decode = des.doFinal(des_array);
              System.out.println(new String(decode));
          }
          catch (Exception e)
          {
              e.printStackTrace();
          }
//这里由于我们第一个八字节密钥和第二个八字节密钥相等,所以加密又解密等于没加密,所以只进行了第三次的DES加密

利用加密算法库实现AES

同理于DES,不过密钥长度不同,16个字节对应的是AES的AES-128

              SecretKeySpec secretKeySpec = new SecretKeySpec("1234567812345678".getBytes(), "AES");
              Cipher des = Cipher.getInstance("AES/CBC/PKCS5Padding");
              IvParameterSpec ivParameterSpec = new IvParameterSpec("1234567812345678".getBytes());
              des.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
              byte[] des_array = des.doFinal("TextdateTextdate".getBytes());
              System.out.println(hex(des_array));

通杀算法,实现DES/AES通杀

通杀就要去hook算法DES和算法AES中的函数也就是

Cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);这一步

所以去HOOK———— >var Cipher = Java.use("javax.crypto.Cipher")中的init()方法

    Cipher.init.overload('int', 'java.security.Key').implementation=function()
    {
        console.log("Cipher.init.overload('int', 'java.security.Key')");
        var Algorithm = this.getAlgorithm();
        var tag = "Alogorithm:"+Algorithm+"init key";
        toHex(tag,arguments[1].getEncoded());
        toBase64(tag,arguments[1].getEncoded());
        toUtf8(tag,arguments[1].getEncoded());
        return this.init.apply(this,arguments);
    }
    Cipher.init.overload('int', 'java.security.cert.Certificate').implementation=function()
    {
        console.log("Cipher.init.overload('int', 'java.security.cert.Certificate')");
        return this.init.apply(this,arguments);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation=function()
    {
        console.log("overload('int', 'java.security.Key', 'java.security.SecureRandom')");
        return this.init.apply(this,arguments);
    }   
    Cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation=function()
    {
        console.log(".overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom')");
        return this.init.apply(this,arguments);
    }    
    Cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation=function()
    {
        console.log(".overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom'))");
        return this.init.apply(this,arguments);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation=function()
    {
        console.log(".overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom')");
        return this.init.apply(this,arguments);
    }
    Cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation=function()
    {
        var Algorithm = this.getAlgorithm();
        var IvParameterSpec = Java.cast(arguments[2],Java.use("javax.crypto.spec.IvParameterSpec"));//这里注意,因为我们hook的时候提示的就是AlgorithmParameterSpec,也就是IvParameterSpec方法的接口函数,所以要向下转型
        var iv = IvParameterSpec.getIV();
        toHex("iv",iv);
        toBase64("iv",iv);
        toUtf8("iv",iv);
        var tag = "Alogorithm:"+Algorithm+"init iv"+iv+"init key";
        toHex(tag,arguments[1].getEncoded());
        toBase64(tag,arguments[1].getEncoded());
        toUtf8(tag,arguments[1].getEncoded());
        return this.init.apply(this,arguments);
    }
----------------------------------------------------------------------------------------------------
//上面是自己写的
    cipher.init.overload('int', 'java.security.Key').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var className = JSON.stringify(arguments[1]);
        if(className.indexOf("OpenSSLRSAPrivateKey") === -1){
            var keyBytes = arguments[1].getEncoded();
            toUtf8(tag, keyBytes);
            toHex(tag, keyBytes);
            toBase64(tag, keyBytes);
        }
        console.log("=======================================================");
        return this.init.apply(this, arguments);
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function () {
        console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
        var algorithm = this.getAlgorithm();
        var tag = algorithm + " init Key";
        var keyBytes = arguments[1].getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
        var tags = algorithm + " init iv";
        var iv = Java.cast(arguments[2], Java.use("javax.crypto.spec.IvParameterSpec"));
        var ivBytes = iv.getIV();
        toUtf8(tags, ivBytes);
        toHex(tags, ivBytes);
        toBase64(tags, ivBytes);
        console.log("=======================================================");
        return this.init.apply(this, arguments);
    }

RSA公钥私钥生成过程中的细节

RSA中分为了PKCS1和PKCS8的两种生成方式,其中PKCS1中的私钥生成时的提示词是

"-----BEGIN RSA PRIVATE KEY-----" 而 PKCS8 则是 "-----BEGIN PRIVATE KEY-----"

同时在生成的RSA密钥对中,大多数是base64编码之后的结果,所以在利用过程中需要base64解密之后在填入对应的对象实例中去

              String publickey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHnsW9mHbbPD1wAZ4unJ2zhTY0" +
                      "B9HEC+7RFOLKRcRV4uXcRnXboHP9qY9BeTe7DsuVjFph8ErDV+bvrqqGQuAt/GzW" +
                      "myOH7XfVIW889OSyFMRUY+y1LvX+aU0PtDtug42GVJQ9JEn3EzjVlqqqUxyyRGJx" +
                      "QgmJZRrt7uPxwD2MOQIDAQAB";
              byte[] publickey_byte =  Base64.getDecoder().decode(publickey);
              X509EncodedKeySpec x509EncodedKeySpec =new X509EncodedKeySpec(publickey_byte);
              PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
              byte[] public_key_byte = publicKey.getEncoded();
              System.out.println(Base64.getEncoder().encodeToString(public_key_byte));

              String private_key = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANeg7Zme5y0W42c1" +
                      "mx6pKE+LZsFiv7e92IAEYo14VwVAtjdEseyK6XRzU7ZVZe2h0cEN9kOxsBHLEacB" +
                      "v8Gv6vIv7Z+diOrSnG68QM6zz5lJkzZi9ePvh2WpZA2HNhRky+hLvtxBfQEWDl4h" +
                      "dAnGsg1dKJEUXCRlKfWqs/q9Q9ENAgMBAAECgYEA0C7Vd14NwGC6ySjeTSnwe2wR" +
                      "l2BpzVKDtoWFSSUIj0+9HXs7dS9g2keGaSHmORnk08lRHGZvoZ43utBbfPsFj2tV" +
                      "/1Q2pI+cJwCMvO/qA+nl7TEwgbSmfLiPso3zymUUa6byuX3TxnZdCLDtTNDRsH7x" +
                      "DF4LNFqBPz1+hQkg1NkCQQD+KRGTu+HMYj2yVLgUXOiIOp/4mN/IFmeXB5kNLZ5T" +
                      "DZW/Zw9MmO62UI9jrAKwP7ybaL8KVQoTCcJgaPiS/yiHAkEA2TB22r06leTo4aRw" +
                      "CNTmtjAoFFMVf4kykzq1LAbbjZOxIr5arIuQBdPWVvWBLRfqrzZ9SAVL9xIKbodt" +
                      "fv1iywJAEkMXLTtRxSLF9htaQrROTQORXQZ3BeR+Ov7jw2uktakDOoaINePDOzxc" +
                      "oTfJ1aouvifvmil0GNwZqF3ChqVo3wJAZeKuXv7WNOsltHSHeh0l/eGpa59uPl4G" +
                      "M7sesoClDk1yk3Ho53ixH4q4yiJxbs4BciCFtPuHH2h/LEqrux7y8QJBAJwRL+1u" +
                      "WvSGULXoTC/FYYTa934jBw2FDZbqVlmYEMTXpPl05oipo/mKTNEEU0somOHFxvak" +
                      "+6XIZY4E06pqORw=";
              byte[] private_key_byte =  Base64.getDecoder().decode(private_key);
              PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(private_key_byte);
              PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
              System.out.println(Base64.getEncoder().encodeToString(privateKey.getEncoded()));

同时在安卓端的RSA的公钥和私钥的生成:

    public static PublicKey generatePublicKey() throws Exception {
        byte[] publicKeyBase64Bytes = ByteString.decodeBase64(publicKeyBase64).toByteArray();
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBase64Bytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(x509EncodedKeySpec);
    }

    public static PrivateKey generatePrivateKey() throws Exception {
        byte[] privateKeyBase64Bytes = ByteString.decodeBase64(privateKeyBase64).toByteArray();
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBase64Bytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey _privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Log.d("xiaojianbang", ByteString.of(_privateKey.getEncoded()).base64());
        return _privateKey;
    }
//sdk的版本不支持使用ByteString了

java端的RSA加解密

              String publickey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXoO2ZnuctFuNnNZseqShPi2bBYr+3vdiABGKNeFcFQLY3RLHsiul0c1O2VWXtodHBDfZDsbARyxGnAb/Br+ryL+2fnYjq0pxuvEDOs8+ZSZM2YvXj74dlqWQNhzYUZMvoS77cQX0BFg5eIXQJxrINXSiRFFwkZSn1qrP6vUPRDQIDAQAB";
              byte[] publickey_byte =  Base64.getDecoder().decode(publickey);
              X509EncodedKeySpec x509EncodedKeySpec =new X509EncodedKeySpec(publickey_byte);
              PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
              byte[] public_key_byte = publicKey.getEncoded();
              System.out.println("public key to base64 = "+Base64.getEncoder().encodeToString(public_key_byte));

              String private_key = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANeg7Zme5y0W42c1" +
                      "mx6pKE+LZsFiv7e92IAEYo14VwVAtjdEseyK6XRzU7ZVZe2h0cEN9kOxsBHLEacB" +
                      "v8Gv6vIv7Z+diOrSnG68QM6zz5lJkzZi9ePvh2WpZA2HNhRky+hLvtxBfQEWDl4h" +
                      "dAnGsg1dKJEUXCRlKfWqs/q9Q9ENAgMBAAECgYEA0C7Vd14NwGC6ySjeTSnwe2wR" +
                      "l2BpzVKDtoWFSSUIj0+9HXs7dS9g2keGaSHmORnk08lRHGZvoZ43utBbfPsFj2tV" +
                      "/1Q2pI+cJwCMvO/qA+nl7TEwgbSmfLiPso3zymUUa6byuX3TxnZdCLDtTNDRsH7x" +
                      "DF4LNFqBPz1+hQkg1NkCQQD+KRGTu+HMYj2yVLgUXOiIOp/4mN/IFmeXB5kNLZ5T" +
                      "DZW/Zw9MmO62UI9jrAKwP7ybaL8KVQoTCcJgaPiS/yiHAkEA2TB22r06leTo4aRw" +
                      "CNTmtjAoFFMVf4kykzq1LAbbjZOxIr5arIuQBdPWVvWBLRfqrzZ9SAVL9xIKbodt" +
                      "fv1iywJAEkMXLTtRxSLF9htaQrROTQORXQZ3BeR+Ov7jw2uktakDOoaINePDOzxc" +
                      "oTfJ1aouvifvmil0GNwZqF3ChqVo3wJAZeKuXv7WNOsltHSHeh0l/eGpa59uPl4G" +
                      "M7sesoClDk1yk3Ho53ixH4q4yiJxbs4BciCFtPuHH2h/LEqrux7y8QJBAJwRL+1u" +
                      "WvSGULXoTC/FYYTa934jBw2FDZbqVlmYEMTXpPl05oipo/mKTNEEU0somOHFxvak" +
                      "+6XIZY4E06pqORw=";
              byte[] private_key_byte =  Base64.getDecoder().decode(private_key);
              PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(private_key_byte);
              PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
              System.out.println("private key to base64 = "+Base64.getEncoder().encodeToString(privateKey.getEncoded()));
              Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
              cipher.init(Cipher.ENCRYPT_MODE, publicKey);
              byte[] encode_data = cipher.doFinal("Text:chen_chen_chen".getBytes());
              System.out.println("encode_data_to_base64 = "+Base64.getEncoder().encodeToString(encode_data));
              System.out.println("encode_data_to_Hex = "+hex(encode_data));
//encode
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//decode
              cipher.init(Cipher.DECRYPT_MODE, privateKey);
              byte[] decode_data =  cipher.doFinal(encode_data);
              System.out.println("decode_data_to_base64 = "+Base64.getEncoder().encodeToString(decode_data));
              System.out.println("dncode_data_to_Hex = "+hex(encode_data));
              System.out.println(new String(decode_data));

RSA的填充方式:

RSA中的NOPadding的填充方式:

  1. 明文最多字节数为密钥字节数
  2. 密文与密钥等长
  3. 填充字节0(在明文数据前填充0),加密后的密文不变

PKCS1Padding:

  1. 明文最大字节数为密钥字节数-11(比如128字节的密钥就只能加密117个明文字节)
  2. 密文与密钥等长
  3. 每一次的填充不一样,所以每一次加密的密文也不同

多种加密算法结合使用:

通过RSA和AES进行组合加密

  1. 随机生成的AES的key
  2. 利用生成的AESkey去加密plainText明文数据得到cipherText
  3. 将生成的对应的RSA中的密钥对(公钥和私钥)中的公钥去加密AES的key得到cipherkey
  4. 客户端直接将cipherText和cipherKey都发送给服务器端,服务器端有RSA的私钥可以直接去得到AES的key去解密cipherText
public class Main {   
	private static final char[] key = {'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', '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', '*', '!'};

    public static String hex(byte[] str)
    {
        StringBuilder hexString = new StringBuilder();
        for (byte b : str) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString().trim();
    }
    public static PublicKey get_PublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        String publickey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXoO2ZnuctFuNnNZseqShPi2bBYr+3vdiABGKNeFcFQLY3RLHsiul0c1O2VWXtodHBDfZDsbARyxGnAb/Br+ryL+2fnYjq0pxuvEDOs8+ZSZM2YvXj74dlqWQNhzYUZMvoS77cQX0BFg5eIXQJxrINXSiRFFwkZSn1qrP6vUPRDQIDAQAB";
        byte[] publickey_byte =  Base64.getDecoder().decode(publickey);
        X509EncodedKeySpec x509EncodedKeySpec =new X509EncodedKeySpec(publickey_byte);
        return KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
    }
    public static PrivateKey get_PrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        String private_key = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANeg7Zme5y0W42c1" +
                "mx6pKE+LZsFiv7e92IAEYo14VwVAtjdEseyK6XRzU7ZVZe2h0cEN9kOxsBHLEacB" +
                "v8Gv6vIv7Z+diOrSnG68QM6zz5lJkzZi9ePvh2WpZA2HNhRky+hLvtxBfQEWDl4h" +
                "dAnGsg1dKJEUXCRlKfWqs/q9Q9ENAgMBAAECgYEA0C7Vd14NwGC6ySjeTSnwe2wR" +
                "l2BpzVKDtoWFSSUIj0+9HXs7dS9g2keGaSHmORnk08lRHGZvoZ43utBbfPsFj2tV" +
                "/1Q2pI+cJwCMvO/qA+nl7TEwgbSmfLiPso3zymUUa6byuX3TxnZdCLDtTNDRsH7x" +
                "DF4LNFqBPz1+hQkg1NkCQQD+KRGTu+HMYj2yVLgUXOiIOp/4mN/IFmeXB5kNLZ5T" +
                "DZW/Zw9MmO62UI9jrAKwP7ybaL8KVQoTCcJgaPiS/yiHAkEA2TB22r06leTo4aRw" +
                "CNTmtjAoFFMVf4kykzq1LAbbjZOxIr5arIuQBdPWVvWBLRfqrzZ9SAVL9xIKbodt" +
                "fv1iywJAEkMXLTtRxSLF9htaQrROTQORXQZ3BeR+Ov7jw2uktakDOoaINePDOzxc" +
                "oTfJ1aouvifvmil0GNwZqF3ChqVo3wJAZeKuXv7WNOsltHSHeh0l/eGpa59uPl4G" +
                "M7sesoClDk1yk3Ho53ixH4q4yiJxbs4BciCFtPuHH2h/LEqrux7y8QJBAJwRL+1u" +
                "WvSGULXoTC/FYYTa934jBw2FDZbqVlmYEMTXpPl05oipo/mKTNEEU0somOHFxvak" +
                "+6XIZY4E06pqORw=";
        byte[] private_key_byte =  Base64.getDecoder().decode(private_key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(private_key_byte);
        return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
    }
    public static String RSA_encode(String plainText, PublicKey publicKey){
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] encode_data = cipher.doFinal(plainText.getBytes());
            return Base64.getEncoder().encodeToString(encode_data);
        }
        catch (Exception ignored) {
        }
        return "wrong data";
    }
    public static String RSA_decode(String plainText, PrivateKey privateKey){
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] encode_data = cipher.doFinal(Base64.getDecoder().decode(plainText));
            System.out.println(Base64.getEncoder().encodeToString(encode_data));
            return bytesToString(encode_data);
        } catch (NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | BadPaddingException |
                 InvalidKeyException e) {
            throw new RuntimeException(e);
        }
    }
    public static String bytesToString(byte[] bytes) {
        // 使用UTF-8字符编码将字节数组转换为字符串
        return new String(bytes, StandardCharsets.UTF_8);
    }
    public static String AES_encode(String plainText,String secretkeySpec) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
        Cipher aes = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretkeySpec.getBytes(),"AES");
        aes.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] encode_data = aes.doFinal(plainText.getBytes());
        return new String(Base64.getEncoder().encode(encode_data));
    }
        public static String get_random_AESKey() {
        Random random = new Random();
        StringBuilder stringBuilder = new StringBuilder();
        for(int i =0;i<16;i++)
        {
            int random_data= random.nextInt(100);
            String tmp = "0"+Integer.toHexString(random_data);
            tmp = tmp.substring(tmp.length()-2);
            stringBuilder.append(tmp);
        }
        return stringBuilder.toString();
    }
    public static void main(String[] args) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException, InvalidKeySpecException {
        String plainText = "plainText_encode";
        String random_AESkey =  get_random_AESKey();
        String cipherText =  AES_encode(plainText,random_AESkey);
        System.out.println("cipherText:"+cipherText);
        String AES_encodekey = RSA_encode(random_AESkey,get_PublicKey());
        System.out.println("cipherKEY:"+AES_encodekey);
        System.out.println("----------------------------------------------------------------");
        String AESKEY = RSA_decode(AES_encodekey,get_PrivateKey());
        Base64.getDecoder().decode(AESKEY);
        System.out.println("AESKEY:"+AESKEY);

数字签名算法

数字签名算法是通过私钥来进行算法签名的

  1. 我们先将要进行数字签名的数据利用私钥进行加密
  2. 得到的结果会在服务器端进行验签解密,查看该数据解密之后的数据是否符合规范

加密:利用私钥将原始数据data进行加密

        String private_key = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANeg7Zme5y0W42c1" +
                "mx6pKE+LZsFiv7e92IAEYo14VwVAtjdEseyK6XRzU7ZVZe2h0cEN9kOxsBHLEacB" +
                "v8Gv6vIv7Z+diOrSnG68QM6zz5lJkzZi9ePvh2WpZA2HNhRky+hLvtxBfQEWDl4h" +
                "dAnGsg1dKJEUXCRlKfWqs/q9Q9ENAgMBAAECgYEA0C7Vd14NwGC6ySjeTSnwe2wR" +
                "l2BpzVKDtoWFSSUIj0+9HXs7dS9g2keGaSHmORnk08lRHGZvoZ43utBbfPsFj2tV" +
                "/1Q2pI+cJwCMvO/qA+nl7TEwgbSmfLiPso3zymUUa6byuX3TxnZdCLDtTNDRsH7x" +
                "DF4LNFqBPz1+hQkg1NkCQQD+KRGTu+HMYj2yVLgUXOiIOp/4mN/IFmeXB5kNLZ5T" +
                "DZW/Zw9MmO62UI9jrAKwP7ybaL8KVQoTCcJgaPiS/yiHAkEA2TB22r06leTo4aRw" +
                "CNTmtjAoFFMVf4kykzq1LAbbjZOxIr5arIuQBdPWVvWBLRfqrzZ9SAVL9xIKbodt" +
                "fv1iywJAEkMXLTtRxSLF9htaQrROTQORXQZ3BeR+Ov7jw2uktakDOoaINePDOzxc" +
                "oTfJ1aouvifvmil0GNwZqF3ChqVo3wJAZeKuXv7WNOsltHSHeh0l/eGpa59uPl4G" +
                "M7sesoClDk1yk3Ho53ixH4q4yiJxbs4BciCFtPuHH2h/LEqrux7y8QJBAJwRL+1u" +
                "WvSGULXoTC/FYYTa934jBw2FDZbqVlmYEMTXpPl05oipo/mKTNEEU0somOHFxvak" +
                "+6XIZY4E06pqORw=";
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] private_key_bytes = Base64.getDecoder().decode(private_key);
        PKCS8EncodedKeySpec private_key_spec = new PKCS8EncodedKeySpec(private_key_bytes);
        PrivateKey privateKey = keyFactory.generatePrivate(private_key_spec);
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update("xiaojianbang".getBytes());
        byte[] signature_bytes = signature.sign();
        System.out.println(Base64.getEncoder().encodeToString(signature_bytes));

验签解密:利用公钥对于私钥加密的数据进行解密,比对与原始值data的区别

      	byte[] signature_bytes = signature.sign();  
        String publickey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDXoO2ZnuctFuNnNZseqShPi2bBYr+3vdiABGKNeFcFQLY3RLHsiul0c1O2VWXtodHBDfZDsbARyxGnAb/Br+ryL+2fnYjq0pxuvEDOs8+ZSZM2YvXj74dlqWQNhzYUZMvoS77cQX0BFg5eIXQJxrINXSiRFFwkZSn1qrP6vUPRDQIDAQAB";
        byte[] publickey_bytes = Base64.getDecoder().decode(publickey);
        X509EncodedKeySpec publickey_spec = new X509EncodedKeySpec(publickey_bytes);
        PublicKey publicKey = keyFactory.generatePublic(publickey_spec);
        signature.initVerify(publicKey);
        signature.update("xiaojianbang".getBytes());
        boolean check_sign =  signature.verify(signature_bytes);
        System.out.println(check_sign);

通杀HOOK:直接去hook--->Signature类下的initSign()和update()

 var Signature =  Java.use("java.security.Signature")
    Signature.update.overload('byte').implementation=function(a)
    {
        console.log("Signature.update rag'byte': " + a);
        var result = this.update.apply(this,arguments);
        return result;
    }
    Signature.update.overload('java.nio.ByteBuffer').implementation=function(A)
    {
        console.log("Signature.update rag'java.nio.ByteBuffer': " + A);

        var result = this.update.apply(this,arguments);
        return result;
    }
    Signature.update.overload('[B').implementation=function(A)
    {
        var Alogorithm =this.getAlgorithm();
        var tag=Alogorithm+"update:"
        console.log("Signature.update rag'[B'': " + A);
        toBase64(tag,A);
        toHex(tag,A);
        toUtf8(tag,A);
        var result = this.update.apply(this,arguments);
        return result;
    }
    Signature.update.overload('[B', 'int', 'int').implementation=function(a,b,c)
    {
        var Alogorithm =this.getAlgorithm();
        var tag=Alogorithm+"update:"
        console.log("Signature.update rag'[B', 'int', 'int': " + a+b+c);
        toBase64(tag,a);
        toHex(tag,a);
        toUtf8(tag,a);
        var result = this.update.apply(this,arguments);
        return result;
    }
    Signature.initSign.overload('java.security.PrivateKey').implementation=function(Key)
    {
        var Alogorithm = this.getAlgorithm();
        // console.log("json"+JSON.stringify(Key));
        //json"<instance: java.security.PrivateKey, $className: com.android.org.conscrypt.OpenSSLRSAPrivateKey>"
        //这里注意向下转型,先获取该参数的类,然后向下转型(不然不能进行getEncoded()方法)
        var OpenSSL = Java.use("com.android.org.conscrypt.OpenSSLRSAPrivateKey");
        var result =  Java.cast(Key, OpenSSL).getEncoded()
        var tag="Alogorithm:"+Alogorithm+"key:";
        toHex(tag,result);
        toBase64(tag,result);
        toUtf8(tag,result);
        return this.initSign.apply(this,arguments);
    }
    Signature.initSign.overload('java.security.PrivateKey', 'java.security.SecureRandom').implementation=function(Key,Random)
    {
        var Alogorithm = this.getAlgorithm();
        var OpenSSL = Java.use("com.android.org.conscrypt.OpenSSLRSAPrivateKey");
        var result =  Java.cast(Key, OpenSSL).getEncoded()
        var tag="Alogorithm:"+Alogorithm+"key:";
        toHex(tag,result);
        toBase64(tag,result);
        toUtf8(tag,result);
        return this.initSign.apply(this,arguments);
    }

CryptoJS消息摘要算法

可以在浏览器端实现 ——>f12——>source——>snippet下去创建文件传入之后直接run

直接运行js代码,需要创建var CryptoJS = module.exports;这样的实例对象

;
(function (root, factory) {
    if (typeof exports === "object") {
        module.exports = exports = factory();
    } else if (typeof define === "function" && define.amd) {
        define([], factory);
    } else {
        root.CryptoJS = factory();
    }
}(this, function () {
    var CryptoJS = CryptoJS || (function (Math, undefined) {
        var create = Object.create || (function () {
            function F() {};
            return function (obj) {
                var subtype;
                F.prototype = obj;
                subtype = new F();
                F.prototype = null;
                return subtype;
            };
        }
        ());
        var C = {};
        var C_lib = C.lib = {};
        var Base = C_lib.Base = (function () {
            return {
                extend: function (overrides) {
                    var subtype = create(this);
                    if (overrides) {
                        subtype.mixIn(overrides);
                    }
                    if (!subtype.hasOwnProperty('init') || this.init === subtype.init) {
                        subtype.init = function () {
                            subtype.$super.init.apply(this, arguments);
                        };
                    }
                    subtype.init.prototype = subtype;
                    subtype.$super = this;
                    return subtype;
                },
                create: function () {
                    var instance = this.extend();
                    instance.init.apply(instance, arguments);
                    return instance;
                },
                init: function () {},
                mixIn: function (properties) {
                    for (var propertyName in properties) {
                        if (properties.hasOwnProperty(propertyName)) {
                            this[propertyName] = properties[propertyName];
                        }
                    }
                    if (properties.hasOwnProperty('toString')) {
                        this.toString = properties.toString;
                    }
                },
                clone: function () {
                    return this.init.prototype.extend(this);
                }
            };
        }
        ());
        var WordArray = C_lib.WordArray = Base.extend({
            init: function (words, sigBytes) {
                words = this.words = words || [];
                if (sigBytes != undefined) {
                    this.sigBytes = sigBytes;
                } else {
                    this.sigBytes = words.length * 4;
                }
            },
            toString: function (encoder) {
                return (encoder || Hex).stringify(this);
            },
            concat: function (wordArray) {
                var thisWords = this.words;
                var thatWords = wordArray.words;
                var thisSigBytes = this.sigBytes;
                var thatSigBytes = wordArray.sigBytes;
                this.clamp();
                if (thisSigBytes % 4) {
                    for (var i = 0; i < thatSigBytes; i++) {
                        var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                        thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
                    }
                } else {
                    for (var i = 0; i < thatSigBytes; i += 4) {
                        thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2];
                    }
                }
                this.sigBytes += thatSigBytes;
                return this;
            },
            clamp: function () {
                var words = this.words;
                var sigBytes = this.sigBytes;
                words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
                words.length = Math.ceil(sigBytes / 4);
            },
            clone: function () {
                var clone = Base.clone.call(this);
                clone.words = this.words.slice(0);
                return clone;
            },
            random: function (nBytes) {
                var words = [];
                var r = (function (m_w) {
                    var m_w = m_w;
                    var m_z = 0x3ade68b1;
                    var mask = 0xffffffff;
                    return function () {
                        m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask;
                        m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask;
                        var result = ((m_z << 0x10) + m_w) & mask;
                        result /= 0x100000000;
                        result += 0.5;
                        return result * (Math.random() > .5 ? 1 : -1);
                    }
                });
                for (var i = 0, rcache; i < nBytes; i += 4) {
                    var _r = r((rcache || Math.random()) * 0x100000000);
                    rcache = _r() * 0x3ade67b7;
                    words.push((_r() * 0x100000000) | 0);
                }
                return new WordArray.init(words, nBytes);
            }
        });
        var C_enc = C.enc = {};
        var Hex = C_enc.Hex = {
            stringify: function (wordArray) {
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                var hexChars = [];
                for (var i = 0; i < sigBytes; i++) {
                    var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                    hexChars.push((bite >>> 4).toString(16));
                    hexChars.push((bite & 0x0f).toString(16));
                }
                return hexChars.join('');
            },
            parse: function (hexStr) {
                var hexStrLength = hexStr.length;
                var words = [];
                for (var i = 0; i < hexStrLength; i += 2) {
                    words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
                }
                return new WordArray.init(words, hexStrLength / 2);
            }
        };
        var Latin1 = C_enc.Latin1 = {
            stringify: function (wordArray) {
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                var latin1Chars = [];
                for (var i = 0; i < sigBytes; i++) {
                    var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                    latin1Chars.push(String.fromCharCode(bite));
                }
                return latin1Chars.join('');
            },
            parse: function (latin1Str) {
                var latin1StrLength = latin1Str.length;
                var words = [];
                for (var i = 0; i < latin1StrLength; i++) {
                    words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
                }
                return new WordArray.init(words, latin1StrLength);
            }
        };
        var Utf8 = C_enc.Utf8 = {
            stringify: function (wordArray) {
                try {
                    return decodeURIComponent(escape(Latin1.stringify(wordArray)));
                } catch (e) {
                    throw new Error('Malformed UTF-8 data');
                }
            },
            parse: function (utf8Str) {
                return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
            }
        };
        var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({
            reset: function () {
                this._data = new WordArray.init();
                this._nDataBytes = 0;
            },
            _append: function (data) {
                if (typeof data == 'string') {
                    data = Utf8.parse(data);
                }
                this._data.concat(data);
                this._nDataBytes += data.sigBytes;
            },
            _process: function (doFlush) {
                var data = this._data;
                var dataWords = data.words;
                var dataSigBytes = data.sigBytes;
                var blockSize = this.blockSize;
                var blockSizeBytes = blockSize * 4;
                var nBlocksReady = dataSigBytes / blockSizeBytes;
                if (doFlush) {
                    nBlocksReady = Math.ceil(nBlocksReady);
                } else {
                    nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
                }
                var nWordsReady = nBlocksReady * blockSize;
                var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
                if (nWordsReady) {
                    for (var offset = 0; offset < nWordsReady; offset += blockSize) {
                        this._doProcessBlock(dataWords, offset);
                    }
                    var processedWords = dataWords.splice(0, nWordsReady);
                    data.sigBytes -= nBytesReady;
                }
                return new WordArray.init(processedWords, nBytesReady);
            },
            clone: function () {
                var clone = Base.clone.call(this);
                clone._data = this._data.clone();
                return clone;
            },
            _minBufferSize: 0
        });
        var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({
            cfg: Base.extend(),
            init: function (cfg) {
                this.cfg = this.cfg.extend(cfg);
                this.reset();
            },
            reset: function () {
                BufferedBlockAlgorithm.reset.call(this);
                this._doReset();
            },
            update: function (messageUpdate) {
                this._append(messageUpdate);
                this._process();
                return this;
            },
            finalize: function (messageUpdate) {
                if (messageUpdate) {
                    this._append(messageUpdate);
                }
                var hash = this._doFinalize();
                return hash;
            },
            blockSize: 512 / 32,
            _createHelper: function (hasher) {
                return function (message, cfg) {
                    return new hasher.init(cfg).finalize(message);
                };
            },
            _createHmacHelper: function (hasher) {
                return function (message, key) {
                    return new C_algo.HMAC.init(hasher, key).finalize(message);
                };
            }
        });
        var C_algo = C.algo = {};
        return C;
    }
    (Math));
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var C_enc = C.enc;
        var Base64 = C_enc.Base64 = {
            stringify: function (wordArray) {
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                var map = this._map;
                wordArray.clamp();
                var base64Chars = [];
                for (var i = 0; i < sigBytes; i += 3) {
                    var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
                    var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff;
                    var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff;
                    var triplet = (byte1 << 16) | (byte2 << 8) | byte3;
                    for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) {
                        base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f));
                    }
                }
                var paddingChar = map.charAt(64);
                if (paddingChar) {
                    while (base64Chars.length % 4) {
                        base64Chars.push(paddingChar);
                    }
                }
                return base64Chars.join('');
            },
            parse: function (base64Str) {
                var base64StrLength = base64Str.length;
                var map = this._map;
                var reverseMap = this._reverseMap;
                if (!reverseMap) {
                    reverseMap = this._reverseMap = [];
                    for (var j = 0; j < map.length; j++) {
                        reverseMap[map.charCodeAt(j)] = j;
                    }
                }
                var paddingChar = map.charAt(64);
                if (paddingChar) {
                    var paddingIndex = base64Str.indexOf(paddingChar);
                    if (paddingIndex !== -1) {
                        base64StrLength = paddingIndex;
                    }
                }
                return parseLoop(base64Str, base64StrLength, reverseMap);
            },
            _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
        };
        function parseLoop(base64Str, base64StrLength, reverseMap) {
            var words = [];
            var nBytes = 0;
            for (var i = 0; i < base64StrLength; i++) {
                if (i % 4) {
                    var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2);
                    var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2);
                    words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8);
                    nBytes++;
                }
            }
            return WordArray.create(words, nBytes);
        }
    }
    ());
    (function (Math) {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var Hasher = C_lib.Hasher;
        var C_algo = C.algo;
        var T = [];
        (function () {
            for (var i = 0; i < 64; i++) {
                T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0;
            }
        }
        ());
        var MD5 = C_algo.MD5 = Hasher.extend({
            _doReset: function () {
                this._hash = new WordArray.init([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]);
            },
            _doProcessBlock: function (M, offset) {
                for (var i = 0; i < 16; i++) {
                    var offset_i = offset + i;
                    var M_offset_i = M[offset_i];
                    M[offset_i] = ((((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00));
                }
                var H = this._hash.words;
                var M_offset_0 = M[offset + 0];
                var M_offset_1 = M[offset + 1];
                var M_offset_2 = M[offset + 2];
                var M_offset_3 = M[offset + 3];
                var M_offset_4 = M[offset + 4];
                var M_offset_5 = M[offset + 5];
                var M_offset_6 = M[offset + 6];
                var M_offset_7 = M[offset + 7];
                var M_offset_8 = M[offset + 8];
                var M_offset_9 = M[offset + 9];
                var M_offset_10 = M[offset + 10];
                var M_offset_11 = M[offset + 11];
                var M_offset_12 = M[offset + 12];
                var M_offset_13 = M[offset + 13];
                var M_offset_14 = M[offset + 14];
                var M_offset_15 = M[offset + 15];
                var a = H[0];
                var b = H[1];
                var c = H[2];
                var d = H[3];
                a = FF(a, b, c, d, M_offset_0, 7, T[0]);
                d = FF(d, a, b, c, M_offset_1, 12, T[1]);
                c = FF(c, d, a, b, M_offset_2, 17, T[2]);
                b = FF(b, c, d, a, M_offset_3, 22, T[3]);
                a = FF(a, b, c, d, M_offset_4, 7, T[4]);
                d = FF(d, a, b, c, M_offset_5, 12, T[5]);
                c = FF(c, d, a, b, M_offset_6, 17, T[6]);
                b = FF(b, c, d, a, M_offset_7, 22, T[7]);
                a = FF(a, b, c, d, M_offset_8, 7, T[8]);
                d = FF(d, a, b, c, M_offset_9, 12, T[9]);
                c = FF(c, d, a, b, M_offset_10, 17, T[10]);
                b = FF(b, c, d, a, M_offset_11, 22, T[11]);
                a = FF(a, b, c, d, M_offset_12, 7, T[12]);
                d = FF(d, a, b, c, M_offset_13, 12, T[13]);
                c = FF(c, d, a, b, M_offset_14, 17, T[14]);
                b = FF(b, c, d, a, M_offset_15, 22, T[15]);
                a = GG(a, b, c, d, M_offset_1, 5, T[16]);
                d = GG(d, a, b, c, M_offset_6, 9, T[17]);
                c = GG(c, d, a, b, M_offset_11, 14, T[18]);
                b = GG(b, c, d, a, M_offset_0, 20, T[19]);
                a = GG(a, b, c, d, M_offset_5, 5, T[20]);
                d = GG(d, a, b, c, M_offset_10, 9, T[21]);
                c = GG(c, d, a, b, M_offset_15, 14, T[22]);
                b = GG(b, c, d, a, M_offset_4, 20, T[23]);
                a = GG(a, b, c, d, M_offset_9, 5, T[24]);
                d = GG(d, a, b, c, M_offset_14, 9, T[25]);
                c = GG(c, d, a, b, M_offset_3, 14, T[26]);
                b = GG(b, c, d, a, M_offset_8, 20, T[27]);
                a = GG(a, b, c, d, M_offset_13, 5, T[28]);
                d = GG(d, a, b, c, M_offset_2, 9, T[29]);
                c = GG(c, d, a, b, M_offset_7, 14, T[30]);
                b = GG(b, c, d, a, M_offset_12, 20, T[31]);
                a = HH(a, b, c, d, M_offset_5, 4, T[32]);
                d = HH(d, a, b, c, M_offset_8, 11, T[33]);
                c = HH(c, d, a, b, M_offset_11, 16, T[34]);
                b = HH(b, c, d, a, M_offset_14, 23, T[35]);
                a = HH(a, b, c, d, M_offset_1, 4, T[36]);
                d = HH(d, a, b, c, M_offset_4, 11, T[37]);
                c = HH(c, d, a, b, M_offset_7, 16, T[38]);
                b = HH(b, c, d, a, M_offset_10, 23, T[39]);
                a = HH(a, b, c, d, M_offset_13, 4, T[40]);
                d = HH(d, a, b, c, M_offset_0, 11, T[41]);
                c = HH(c, d, a, b, M_offset_3, 16, T[42]);
                b = HH(b, c, d, a, M_offset_6, 23, T[43]);
                a = HH(a, b, c, d, M_offset_9, 4, T[44]);
                d = HH(d, a, b, c, M_offset_12, 11, T[45]);
                c = HH(c, d, a, b, M_offset_15, 16, T[46]);
                b = HH(b, c, d, a, M_offset_2, 23, T[47]);
                a = II(a, b, c, d, M_offset_0, 6, T[48]);
                d = II(d, a, b, c, M_offset_7, 10, T[49]);
                c = II(c, d, a, b, M_offset_14, 15, T[50]);
                b = II(b, c, d, a, M_offset_5, 21, T[51]);
                a = II(a, b, c, d, M_offset_12, 6, T[52]);
                d = II(d, a, b, c, M_offset_3, 10, T[53]);
                c = II(c, d, a, b, M_offset_10, 15, T[54]);
                b = II(b, c, d, a, M_offset_1, 21, T[55]);
                a = II(a, b, c, d, M_offset_8, 6, T[56]);
                d = II(d, a, b, c, M_offset_15, 10, T[57]);
                c = II(c, d, a, b, M_offset_6, 15, T[58]);
                b = II(b, c, d, a, M_offset_13, 21, T[59]);
                a = II(a, b, c, d, M_offset_4, 6, T[60]);
                d = II(d, a, b, c, M_offset_11, 10, T[61]);
                c = II(c, d, a, b, M_offset_2, 15, T[62]);
                b = II(b, c, d, a, M_offset_9, 21, T[63]);
                H[0] = (H[0] + a) | 0;
                H[1] = (H[1] + b) | 0;
                H[2] = (H[2] + c) | 0;
                H[3] = (H[3] + d) | 0;
            },
            _doFinalize: function () {
                var data = this._data;
                var dataWords = data.words;
                var nBitsTotal = this._nDataBytes * 8;
                var nBitsLeft = data.sigBytes * 8;
                dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
                var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000);
                var nBitsTotalL = nBitsTotal;
                dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ((((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00));
                dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ((((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00));
                data.sigBytes = (dataWords.length + 1) * 4;
                this._process();
                var hash = this._hash;
                var H = hash.words;
                for (var i = 0; i < 4; i++) {
                    var H_i = H[i];
                    H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00);
                }
                return hash;
            },
            clone: function () {
                var clone = Hasher.clone.call(this);
                clone._hash = this._hash.clone();
                return clone;
            }
        });
        function FF(a, b, c, d, x, s, t) {
            var n = a + ((b & c) | (~b & d)) + x + t;
            return ((n << s) | (n >>> (32 - s))) + b;
        }
        function GG(a, b, c, d, x, s, t) {
            var n = a + ((b & d) | (c & ~d)) + x + t;
            return ((n << s) | (n >>> (32 - s))) + b;
        }
        function HH(a, b, c, d, x, s, t) {
            var n = a + (b ^ c ^ d) + x + t;
            return ((n << s) | (n >>> (32 - s))) + b;
        }
        function II(a, b, c, d, x, s, t) {
            var n = a + (c ^ (b | ~d)) + x + t;
            return ((n << s) | (n >>> (32 - s))) + b;
        }
        C.MD5 = Hasher._createHelper(MD5);
        C.HmacMD5 = Hasher._createHmacHelper(MD5);
    }
    (Math));
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var Hasher = C_lib.Hasher;
        var C_algo = C.algo;
        var W = [];
        var SHA1 = C_algo.SHA1 = Hasher.extend({
            _doReset: function () {
                this._hash = new WordArray.init([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]);
            },
            _doProcessBlock: function (M, offset) {
                var H = this._hash.words;
                var a = H[0];
                var b = H[1];
                var c = H[2];
                var d = H[3];
                var e = H[4];
                for (var i = 0; i < 80; i++) {
                    if (i < 16) {
                        W[i] = M[offset + i] | 0;
                    } else {
                        var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16];
                        W[i] = (n << 1) | (n >>> 31);
                    }
                    var t = ((a << 5) | (a >>> 27)) + e + W[i];
                    if (i < 20) {
                        t += ((b & c) | (~b & d)) + 0x5a827999;
                    } else if (i < 40) {
                        t += (b ^ c ^ d) + 0x6ed9eba1;
                    } else if (i < 60) {
                        t += ((b & c) | (b & d) | (c & d)) - 0x70e44324;
                    } else {
                        t += (b ^ c ^ d) - 0x359d3e2a;
                    }
                    e = d;
                    d = c;
                    c = (b << 30) | (b >>> 2);
                    b = a;
                    a = t;
                }
                H[0] = (H[0] + a) | 0;
                H[1] = (H[1] + b) | 0;
                H[2] = (H[2] + c) | 0;
                H[3] = (H[3] + d) | 0;
                H[4] = (H[4] + e) | 0;
            },
            _doFinalize: function () {
                var data = this._data;
                var dataWords = data.words;
                var nBitsTotal = this._nDataBytes * 8;
                var nBitsLeft = data.sigBytes * 8;
                dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
                dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000);
                dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal;
                data.sigBytes = dataWords.length * 4;
                this._process();
                return this._hash;
            },
            clone: function () {
                var clone = Hasher.clone.call(this);
                clone._hash = this._hash.clone();
                return clone;
            }
        });
        C.SHA1 = Hasher._createHelper(SHA1);
        C.HmacSHA1 = Hasher._createHmacHelper(SHA1);
    }
    ());
    (function (Math) {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var Hasher = C_lib.Hasher;
        var C_algo = C.algo;
        var H = [];
        var K = [];
        (function () {
            function isPrime(n) {
                var sqrtN = Math.sqrt(n);
                for (var factor = 2; factor <= sqrtN; factor++) {
                    if (!(n % factor)) {
                        return false;
                    }
                }
                return true;
            }
            function getFractionalBits(n) {
                return ((n - (n | 0)) * 0x100000000) | 0;
            }
            var n = 2;
            var nPrime = 0;
            while (nPrime < 64) {
                if (isPrime(n)) {
                    if (nPrime < 8) {
                        H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2));
                    }
                    K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3));
                    nPrime++;
                }
                n++;
            }
        }
        ());
        var W = [];
        var SHA256 = C_algo.SHA256 = Hasher.extend({
            _doReset: function () {
                this._hash = new WordArray.init(H.slice(0));
            },
            _doProcessBlock: function (M, offset) {
                var H = this._hash.words;
                var a = H[0];
                var b = H[1];
                var c = H[2];
                var d = H[3];
                var e = H[4];
                var f = H[5];
                var g = H[6];
                var h = H[7];
                for (var i = 0; i < 64; i++) {
                    if (i < 16) {
                        W[i] = M[offset + i] | 0;
                    } else {
                        var gamma0x = W[i - 15];
                        var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ ((gamma0x << 14) | (gamma0x >>> 18)) ^ (gamma0x >>> 3);
                        var gamma1x = W[i - 2];
                        var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ ((gamma1x << 13) | (gamma1x >>> 19)) ^ (gamma1x >>> 10);
                        W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];
                    }
                    var ch = (e & f) ^ (~e & g);
                    var maj = (a & b) ^ (a & c) ^ (b & c);
                    var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22));
                    var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25));
                    var t1 = h + sigma1 + ch + K[i] + W[i];
                    var t2 = sigma0 + maj;
                    h = g;
                    g = f;
                    f = e;
                    e = (d + t1) | 0;
                    d = c;
                    c = b;
                    b = a;
                    a = (t1 + t2) | 0;
                }
                H[0] = (H[0] + a) | 0;
                H[1] = (H[1] + b) | 0;
                H[2] = (H[2] + c) | 0;
                H[3] = (H[3] + d) | 0;
                H[4] = (H[4] + e) | 0;
                H[5] = (H[5] + f) | 0;
                H[6] = (H[6] + g) | 0;
                H[7] = (H[7] + h) | 0;
            },
            _doFinalize: function () {
                var data = this._data;
                var dataWords = data.words;
                var nBitsTotal = this._nDataBytes * 8;
                var nBitsLeft = data.sigBytes * 8;
                dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
                dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000);
                dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal;
                data.sigBytes = dataWords.length * 4;
                this._process();
                return this._hash;
            },
            clone: function () {
                var clone = Hasher.clone.call(this);
                clone._hash = this._hash.clone();
                return clone;
            }
        });
        C.SHA256 = Hasher._createHelper(SHA256);
        C.HmacSHA256 = Hasher._createHmacHelper(SHA256);
    }
    (Math));
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var C_enc = C.enc;
        var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = {
            stringify: function (wordArray) {
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                var utf16Chars = [];
                for (var i = 0; i < sigBytes; i += 2) {
                    var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff;
                    utf16Chars.push(String.fromCharCode(codePoint));
                }
                return utf16Chars.join('');
            },
            parse: function (utf16Str) {
                var utf16StrLength = utf16Str.length;
                var words = [];
                for (var i = 0; i < utf16StrLength; i++) {
                    words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16);
                }
                return WordArray.create(words, utf16StrLength * 2);
            }
        };
        C_enc.Utf16LE = {
            stringify: function (wordArray) {
                var words = wordArray.words;
                var sigBytes = wordArray.sigBytes;
                var utf16Chars = [];
                for (var i = 0; i < sigBytes; i += 2) {
                    var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff);
                    utf16Chars.push(String.fromCharCode(codePoint));
                }
                return utf16Chars.join('');
            },
            parse: function (utf16Str) {
                var utf16StrLength = utf16Str.length;
                var words = [];
                for (var i = 0; i < utf16StrLength; i++) {
                    words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16));
                }
                return WordArray.create(words, utf16StrLength * 2);
            }
        };
        function swapEndian(word) {
            return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff);
        }
    }
    ());
    (function () {
        if (typeof ArrayBuffer != 'function') {
            return;
        }
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var superInit = WordArray.init;
        var subInit = WordArray.init = function (typedArray) {
            if (typedArray instanceof ArrayBuffer) {
                typedArray = new Uint8Array(typedArray);
            }
            if (typedArray instanceof Int8Array || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || typedArray instanceof Int16Array || typedArray instanceof Uint16Array || typedArray instanceof Int32Array || typedArray instanceof Uint32Array || typedArray instanceof Float32Array || typedArray instanceof Float64Array) {
                typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
            }
            if (typedArray instanceof Uint8Array) {
                var typedArrayByteLength = typedArray.byteLength;
                var words = [];
                for (var i = 0; i < typedArrayByteLength; i++) {
                    words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8);
                }
                superInit.call(this, words, typedArrayByteLength);
            } else {
                superInit.apply(this, arguments);
            }
        };
        subInit.prototype = WordArray;
    }
    ());
    (function (Math) {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var Hasher = C_lib.Hasher;
        var C_algo = C.algo;
        var _zl = WordArray.create([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]);
        var _zr = WordArray.create([5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]);
        var _sl = WordArray.create([11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6]);
        var _sr = WordArray.create([8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11]);
        var _hl = WordArray.create([0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]);
        var _hr = WordArray.create([0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]);
        var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({
            _doReset: function () {
                this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]);
            },
            _doProcessBlock: function (M, offset) {
                for (var i = 0; i < 16; i++) {
                    var offset_i = offset + i;
                    var M_offset_i = M[offset_i];
                    M[offset_i] = ((((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00));
                }
                var H = this._hash.words;
                var hl = _hl.words;
                var hr = _hr.words;
                var zl = _zl.words;
                var zr = _zr.words;
                var sl = _sl.words;
                var sr = _sr.words;
                var al,
                    bl,
                    cl,
                    dl,
                    el;
                var ar,
                    br,
                    cr,
                    dr,
                    er;
                ar = al = H[0];
                br = bl = H[1];
                cr = cl = H[2];
                dr = dl = H[3];
                er = el = H[4];
                var t;
                for (var i = 0; i < 80; i += 1) {
                    t = (al + M[offset + zl[i]]) | 0;
                    if (i < 16) {
                        t += f1(bl, cl, dl) + hl[0];
                    } else if (i < 32) {
                        t += f2(bl, cl, dl) + hl[1];
                    } else if (i < 48) {
                        t += f3(bl, cl, dl) + hl[2];
                    } else if (i < 64) {
                        t += f4(bl, cl, dl) + hl[3];
                    } else {
                        t += f5(bl, cl, dl) + hl[4];
                    }
                    t = t | 0;
                    t = rotl(t, sl[i]);
                    t = (t + el) | 0;
                    al = el;
                    el = dl;
                    dl = rotl(cl, 10);
                    cl = bl;
                    bl = t;
                    t = (ar + M[offset + zr[i]]) | 0;
                    if (i < 16) {
                        t += f5(br, cr, dr) + hr[0];
                    } else if (i < 32) {
                        t += f4(br, cr, dr) + hr[1];
                    } else if (i < 48) {
                        t += f3(br, cr, dr) + hr[2];
                    } else if (i < 64) {
                        t += f2(br, cr, dr) + hr[3];
                    } else {
                        t += f1(br, cr, dr) + hr[4];
                    }
                    t = t | 0;
                    t = rotl(t, sr[i]);
                    t = (t + er) | 0;
                    ar = er;
                    er = dr;
                    dr = rotl(cr, 10);
                    cr = br;
                    br = t;
                }
                t = (H[1] + cl + dr) | 0;
                H[1] = (H[2] + dl + er) | 0;
                H[2] = (H[3] + el + ar) | 0;
                H[3] = (H[4] + al + br) | 0;
                H[4] = (H[0] + bl + cr) | 0;
                H[0] = t;
            },
            _doFinalize: function () {
                var data = this._data;
                var dataWords = data.words;
                var nBitsTotal = this._nDataBytes * 8;
                var nBitsLeft = data.sigBytes * 8;
                dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
                dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ((((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00));
                data.sigBytes = (dataWords.length + 1) * 4;
                this._process();
                var hash = this._hash;
                var H = hash.words;
                for (var i = 0; i < 5; i++) {
                    var H_i = H[i];
                    H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00);
                }
                return hash;
            },
            clone: function () {
                var clone = Hasher.clone.call(this);
                clone._hash = this._hash.clone();
                return clone;
            }
        });
        function f1(x, y, z) {
            return ((x) ^ (y) ^ (z));
        }
        function f2(x, y, z) {
            return (((x) & (y)) | ((~x) & (z)));
        }
        function f3(x, y, z) {
            return (((x) | (~(y))) ^ (z));
        }
        function f4(x, y, z) {
            return (((x) & (z)) | ((y) & (~(z))));
        }
        function f5(x, y, z) {
            return ((x) ^ ((y) | (~(z))));
        }
        function rotl(x, n) {
            return (x << n) | (x >>> (32 - n));
        }
        C.RIPEMD160 = Hasher._createHelper(RIPEMD160);
        C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160);
    }
    (Math));
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var Base = C_lib.Base;
        var C_enc = C.enc;
        var Utf8 = C_enc.Utf8;
        var C_algo = C.algo;
        var HMAC = C_algo.HMAC = Base.extend({
            init: function (hasher, key) {
                hasher = this._hasher = new hasher.init();
                if (typeof key == 'string') {
                    key = Utf8.parse(key);
                }
                var hasherBlockSize = hasher.blockSize;
                var hasherBlockSizeBytes = hasherBlockSize * 4;
                if (key.sigBytes > hasherBlockSizeBytes) {
                    key = hasher.finalize(key);
                }
                key.clamp();
                var oKey = this._oKey = key.clone();
                var iKey = this._iKey = key.clone();
                var oKeyWords = oKey.words;
                var iKeyWords = iKey.words;
                for (var i = 0; i < hasherBlockSize; i++) {
                    oKeyWords[i] ^= 0x5c5c5c5c;
                    iKeyWords[i] ^= 0x36363636;
                }
                oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes;
                this.reset();
            },
            reset: function () {
                var hasher = this._hasher;
                hasher.reset();
                hasher.update(this._iKey);
            },
            update: function (messageUpdate) {
                this._hasher.update(messageUpdate);
                return this;
            },
            finalize: function (messageUpdate) {
                var hasher = this._hasher;
                var innerHash = hasher.finalize(messageUpdate);
                hasher.reset();
                var hmac = hasher.finalize(this._oKey.clone().concat(innerHash));
                return hmac;
            }
        });
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var Base = C_lib.Base;
        var WordArray = C_lib.WordArray;
        var C_algo = C.algo;
        var SHA1 = C_algo.SHA1;
        var HMAC = C_algo.HMAC;
        var PBKDF2 = C_algo.PBKDF2 = Base.extend({
            cfg: Base.extend({
                keySize: 128 / 32,
                hasher: SHA1,
                iterations: 1
            }),
            init: function (cfg) {
                this.cfg = this.cfg.extend(cfg);
            },
            compute: function (password, salt) {
                var cfg = this.cfg;
                var hmac = HMAC.create(cfg.hasher, password);
                var derivedKey = WordArray.create();
                var blockIndex = WordArray.create([0x00000001]);
                var derivedKeyWords = derivedKey.words;
                var blockIndexWords = blockIndex.words;
                var keySize = cfg.keySize;
                var iterations = cfg.iterations;
                while (derivedKeyWords.length < keySize) {
                    var block = hmac.update(salt).finalize(blockIndex);
                    hmac.reset();
                    var blockWords = block.words;
                    var blockWordsLength = blockWords.length;
                    var intermediate = block;
                    for (var i = 1; i < iterations; i++) {
                        intermediate = hmac.finalize(intermediate);
                        hmac.reset();
                        var intermediateWords = intermediate.words;
                        for (var j = 0; j < blockWordsLength; j++) {
                            blockWords[j] ^= intermediateWords[j];
                        }
                    }
                    derivedKey.concat(block);
                    blockIndexWords[0]++;
                }
                derivedKey.sigBytes = keySize * 4;
                return derivedKey;
            }
        });
        C.PBKDF2 = function (password, salt, cfg) {
            return PBKDF2.create(cfg).compute(password, salt);
        };
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var Base = C_lib.Base;
        var WordArray = C_lib.WordArray;
        var C_algo = C.algo;
        var MD5 = C_algo.MD5;
        var EvpKDF = C_algo.EvpKDF = Base.extend({
            cfg: Base.extend({
                keySize: 128 / 32,
                hasher: MD5,
                iterations: 1
            }),
            init: function (cfg) {
                this.cfg = this.cfg.extend(cfg);
            },
            compute: function (password, salt) {
                var cfg = this.cfg;
                var hasher = cfg.hasher.create();
                var derivedKey = WordArray.create();
                var derivedKeyWords = derivedKey.words;
                var keySize = cfg.keySize;
                var iterations = cfg.iterations;
                while (derivedKeyWords.length < keySize) {
                    if (block) {
                        hasher.update(block);
                    }
                    var block = hasher.update(password).finalize(salt);
                    hasher.reset();
                    for (var i = 1; i < iterations; i++) {
                        block = hasher.finalize(block);
                        hasher.reset();
                    }
                    derivedKey.concat(block);
                }
                derivedKey.sigBytes = keySize * 4;
                return derivedKey;
            }
        });
        C.EvpKDF = function (password, salt, cfg) {
            return EvpKDF.create(cfg).compute(password, salt);
        };
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var C_algo = C.algo;
        var SHA256 = C_algo.SHA256;
        var SHA224 = C_algo.SHA224 = SHA256.extend({
            _doReset: function () {
                this._hash = new WordArray.init([0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4]);
            },
            _doFinalize: function () {
                var hash = SHA256._doFinalize.call(this);
                hash.sigBytes -= 4;
                return hash;
            }
        });
        C.SHA224 = SHA256._createHelper(SHA224);
        C.HmacSHA224 = SHA256._createHmacHelper(SHA224);
    }
    ());
    (function (undefined) {
        var C = CryptoJS;
        var C_lib = C.lib;
        var Base = C_lib.Base;
        var X32WordArray = C_lib.WordArray;
        var C_x64 = C.x64 = {};
        var X64Word = C_x64.Word = Base.extend({
            init: function (high, low) {
                this.high = high;
                this.low = low;
            }
        });
        var X64WordArray = C_x64.WordArray = Base.extend({
            init: function (words, sigBytes) {
                words = this.words = words || [];
                if (sigBytes != undefined) {
                    this.sigBytes = sigBytes;
                } else {
                    this.sigBytes = words.length * 8;
                }
            },
            toX32: function () {
                var x64Words = this.words;
                var x64WordsLength = x64Words.length;
                var x32Words = [];
                for (var i = 0; i < x64WordsLength; i++) {
                    var x64Word = x64Words[i];
                    x32Words.push(x64Word.high);
                    x32Words.push(x64Word.low);
                }
                return X32WordArray.create(x32Words, this.sigBytes);
            },
            clone: function () {
                var clone = Base.clone.call(this);
                var words = clone.words = this.words.slice(0);
                var wordsLength = words.length;
                for (var i = 0; i < wordsLength; i++) {
                    words[i] = words[i].clone();
                }
                return clone;
            }
        });
    }
    ());
    (function (Math) {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var Hasher = C_lib.Hasher;
        var C_x64 = C.x64;
        var X64Word = C_x64.Word;
        var C_algo = C.algo;
        var RHO_OFFSETS = [];
        var PI_INDEXES = [];
        var ROUND_CONSTANTS = [];
        (function () {
            var x = 1,
                y = 0;
            for (var t = 0; t < 24; t++) {
                RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64;
                var newX = y % 5;
                var newY = (2 * x + 3 * y) % 5;
                x = newX;
                y = newY;
            }
            for (var x = 0; x < 5; x++) {
                for (var y = 0; y < 5; y++) {
                    PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5;
                }
            }
            var LFSR = 0x01;
            for (var i = 0; i < 24; i++) {
                var roundConstantMsw = 0;
                var roundConstantLsw = 0;
                for (var j = 0; j < 7; j++) {
                    if (LFSR & 0x01) {
                        var bitPosition = (1 << j) - 1;
                        if (bitPosition < 32) {
                            roundConstantLsw ^= 1 << bitPosition;
                        } else {
                            roundConstantMsw ^= 1 << (bitPosition - 32);
                        }
                    }
                    if (LFSR & 0x80) {
                        LFSR = (LFSR << 1) ^ 0x71;
                    } else {
                        LFSR <<= 1;
                    }
                }
                ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw);
            }
        }
        ());
        var T = [];
        (function () {
            for (var i = 0; i < 25; i++) {
                T[i] = X64Word.create();
            }
        }
        ());
        var SHA3 = C_algo.SHA3 = Hasher.extend({
            cfg: Hasher.cfg.extend({
                outputLength: 512
            }),
            _doReset: function () {
                var state = this._state = [];
                for (var i = 0; i < 25; i++) {
                    state[i] = new X64Word.init();
                }
                this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32;
            },
            _doProcessBlock: function (M, offset) {
                var state = this._state;
                var nBlockSizeLanes = this.blockSize / 2;
                for (var i = 0; i < nBlockSizeLanes; i++) {
                    var M2i = M[offset + 2 * i];
                    var M2i1 = M[offset + 2 * i + 1];
                    M2i = ((((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) | (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00));
                    M2i1 = ((((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) | (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00));
                    var lane = state[i];
                    lane.high ^= M2i1;
                    lane.low ^= M2i;
                }
                for (var round = 0; round < 24; round++) {
                    for (var x = 0; x < 5; x++) {
                        var tMsw = 0,
                            tLsw = 0;
                        for (var y = 0; y < 5; y++) {
                            var lane = state[x + 5 * y];
                            tMsw ^= lane.high;
                            tLsw ^= lane.low;
                        }
                        var Tx = T[x];
                        Tx.high = tMsw;
                        Tx.low = tLsw;
                    }
                    for (var x = 0; x < 5; x++) {
                        var Tx4 = T[(x + 4) % 5];
                        var Tx1 = T[(x + 1) % 5];
                        var Tx1Msw = Tx1.high;
                        var Tx1Lsw = Tx1.low;
                        var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31));
                        var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31));
                        for (var y = 0; y < 5; y++) {
                            var lane = state[x + 5 * y];
                            lane.high ^= tMsw;
                            lane.low ^= tLsw;
                        }
                    }
                    for (var laneIndex = 1; laneIndex < 25; laneIndex++) {
                        var lane = state[laneIndex];
                        var laneMsw = lane.high;
                        var laneLsw = lane.low;
                        var rhoOffset = RHO_OFFSETS[laneIndex];
                        if (rhoOffset < 32) {
                            var tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset));
                            var tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset));
                        } else {
                            var tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset));
                            var tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset));
                        }
                        var TPiLane = T[PI_INDEXES[laneIndex]];
                        TPiLane.high = tMsw;
                        TPiLane.low = tLsw;
                    }
                    var T0 = T[0];
                    var state0 = state[0];
                    T0.high = state0.high;
                    T0.low = state0.low;
                    for (var x = 0; x < 5; x++) {
                        for (var y = 0; y < 5; y++) {
                            var laneIndex = x + 5 * y;
                            var lane = state[laneIndex];
                            var TLane = T[laneIndex];
                            var Tx1Lane = T[((x + 1) % 5) + 5 * y];
                            var Tx2Lane = T[((x + 2) % 5) + 5 * y];
                            lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high);
                            lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low);
                        }
                    }
                    var lane = state[0];
                    var roundConstant = ROUND_CONSTANTS[round];
                    lane.high ^= roundConstant.high;
                    lane.low ^= roundConstant.low; ;
                }
            },
            _doFinalize: function () {
                var data = this._data;
                var dataWords = data.words;
                var nBitsTotal = this._nDataBytes * 8;
                var nBitsLeft = data.sigBytes * 8;
                var blockSizeBits = this.blockSize * 32;
                dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32);
                dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80;
                data.sigBytes = dataWords.length * 4;
                this._process();
                var state = this._state;
                var outputLengthBytes = this.cfg.outputLength / 8;
                var outputLengthLanes = outputLengthBytes / 8;
                var hashWords = [];
                for (var i = 0; i < outputLengthLanes; i++) {
                    var lane = state[i];
                    var laneMsw = lane.high;
                    var laneLsw = lane.low;
                    laneMsw = ((((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) | (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00));
                    laneLsw = ((((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) | (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00));
                    hashWords.push(laneLsw);
                    hashWords.push(laneMsw);
                }
                return new WordArray.init(hashWords, outputLengthBytes);
            },
            clone: function () {
                var clone = Hasher.clone.call(this);
                var state = clone._state = this._state.slice(0);
                for (var i = 0; i < 25; i++) {
                    state[i] = state[i].clone();
                }
                return clone;
            }
        });
        C.SHA3 = Hasher._createHelper(SHA3);
        C.HmacSHA3 = Hasher._createHmacHelper(SHA3);
    }
    (Math));
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var Hasher = C_lib.Hasher;
        var C_x64 = C.x64;
        var X64Word = C_x64.Word;
        var X64WordArray = C_x64.WordArray;
        var C_algo = C.algo;
        function X64Word_create() {
            return X64Word.create.apply(X64Word, arguments);
        }
        var K = [X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817)];
        var W = [];
        (function () {
            for (var i = 0; i < 80; i++) {
                W[i] = X64Word_create();
            }
        }
        ());
        var SHA512 = C_algo.SHA512 = Hasher.extend({
            _doReset: function () {
                this._hash = new X64WordArray.init([new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179)]);
            },
            _doProcessBlock: function (M, offset) {
                var H = this._hash.words;
                var H0 = H[0];
                var H1 = H[1];
                var H2 = H[2];
                var H3 = H[3];
                var H4 = H[4];
                var H5 = H[5];
                var H6 = H[6];
                var H7 = H[7];
                var H0h = H0.high;
                var H0l = H0.low;
                var H1h = H1.high;
                var H1l = H1.low;
                var H2h = H2.high;
                var H2l = H2.low;
                var H3h = H3.high;
                var H3l = H3.low;
                var H4h = H4.high;
                var H4l = H4.low;
                var H5h = H5.high;
                var H5l = H5.low;
                var H6h = H6.high;
                var H6l = H6.low;
                var H7h = H7.high;
                var H7l = H7.low;
                var ah = H0h;
                var al = H0l;
                var bh = H1h;
                var bl = H1l;
                var ch = H2h;
                var cl = H2l;
                var dh = H3h;
                var dl = H3l;
                var eh = H4h;
                var el = H4l;
                var fh = H5h;
                var fl = H5l;
                var gh = H6h;
                var gl = H6l;
                var hh = H7h;
                var hl = H7l;
                for (var i = 0; i < 80; i++) {
                    var Wi = W[i];
                    if (i < 16) {
                        var Wih = Wi.high = M[offset + i * 2] | 0;
                        var Wil = Wi.low = M[offset + i * 2 + 1] | 0;
                    } else {
                        var gamma0x = W[i - 15];
                        var gamma0xh = gamma0x.high;
                        var gamma0xl = gamma0x.low;
                        var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7);
                        var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25));
                        var gamma1x = W[i - 2];
                        var gamma1xh = gamma1x.high;
                        var gamma1xl = gamma1x.low;
                        var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6);
                        var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26));
                        var Wi7 = W[i - 7];
                        var Wi7h = Wi7.high;
                        var Wi7l = Wi7.low;
                        var Wi16 = W[i - 16];
                        var Wi16h = Wi16.high;
                        var Wi16l = Wi16.low;
                        var Wil = gamma0l + Wi7l;
                        var Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0);
                        var Wil = Wil + gamma1l;
                        var Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0);
                        var Wil = Wil + Wi16l;
                        var Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0);
                        Wi.high = Wih;
                        Wi.low = Wil;
                    }
                    var chh = (eh & fh) ^ (~eh & gh);
                    var chl = (el & fl) ^ (~el & gl);
                    var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch);
                    var majl = (al & bl) ^ (al & cl) ^ (bl & cl);
                    var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7));
                    var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7));
                    var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9));
                    var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9));
                    var Ki = K[i];
                    var Kih = Ki.high;
                    var Kil = Ki.low;
                    var t1l = hl + sigma1l;
                    var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0);
                    var t1l = t1l + chl;
                    var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0);
                    var t1l = t1l + Kil;
                    var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0);
                    var t1l = t1l + Wil;
                    var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0);
                    var t2l = sigma0l + majl;
                    var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0);
                    hh = gh;
                    hl = gl;
                    gh = fh;
                    gl = fl;
                    fh = eh;
                    fl = el;
                    el = (dl + t1l) | 0;
                    eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0;
                    dh = ch;
                    dl = cl;
                    ch = bh;
                    cl = bl;
                    bh = ah;
                    bl = al;
                    al = (t1l + t2l) | 0;
                    ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0;
                }
                H0l = H0.low = (H0l + al);
                H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0));
                H1l = H1.low = (H1l + bl);
                H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0));
                H2l = H2.low = (H2l + cl);
                H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0));
                H3l = H3.low = (H3l + dl);
                H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0));
                H4l = H4.low = (H4l + el);
                H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0));
                H5l = H5.low = (H5l + fl);
                H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0));
                H6l = H6.low = (H6l + gl);
                H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0));
                H7l = H7.low = (H7l + hl);
                H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0));
            },
            _doFinalize: function () {
                var data = this._data;
                var dataWords = data.words;
                var nBitsTotal = this._nDataBytes * 8;
                var nBitsLeft = data.sigBytes * 8;
                dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
                dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000);
                dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal;
                data.sigBytes = dataWords.length * 4;
                this._process();
                var hash = this._hash.toX32();
                return hash;
            },
            clone: function () {
                var clone = Hasher.clone.call(this);
                clone._hash = this._hash.clone();
                return clone;
            },
            blockSize: 1024 / 32
        });
        C.SHA512 = Hasher._createHelper(SHA512);
        C.HmacSHA512 = Hasher._createHmacHelper(SHA512);
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_x64 = C.x64;
        var X64Word = C_x64.Word;
        var X64WordArray = C_x64.WordArray;
        var C_algo = C.algo;
        var SHA512 = C_algo.SHA512;
        var SHA384 = C_algo.SHA384 = SHA512.extend({
            _doReset: function () {
                this._hash = new X64WordArray.init([new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4)]);
            },
            _doFinalize: function () {
                var hash = SHA512._doFinalize.call(this);
                hash.sigBytes -= 16;
                return hash;
            }
        });
        C.SHA384 = SHA512._createHelper(SHA384);
        C.HmacSHA384 = SHA512._createHmacHelper(SHA384);
    }
    ());
    CryptoJS.lib.Cipher || (function (undefined) {
        var C = CryptoJS;
        var C_lib = C.lib;
        var Base = C_lib.Base;
        var WordArray = C_lib.WordArray;
        var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm;
        var C_enc = C.enc;
        var Utf8 = C_enc.Utf8;
        var Base64 = C_enc.Base64;
        var C_algo = C.algo;
        var EvpKDF = C_algo.EvpKDF;
        var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({
            cfg: Base.extend(),
            createEncryptor: function (key, cfg) {
                return this.create(this._ENC_XFORM_MODE, key, cfg);
            },
            createDecryptor: function (key, cfg) {
                return this.create(this._DEC_XFORM_MODE, key, cfg);
            },
            init: function (xformMode, key, cfg) {
                this.cfg = this.cfg.extend(cfg);
                this._xformMode = xformMode;
                this._key = key;
                this.reset();
            },
            reset: function () {
                BufferedBlockAlgorithm.reset.call(this);
                this._doReset();
            },
            process: function (dataUpdate) {
                this._append(dataUpdate);
                return this._process();
            },
            finalize: function (dataUpdate) {
                if (dataUpdate) {
                    this._append(dataUpdate);
                }
                var finalProcessedData = this._doFinalize();
                return finalProcessedData;
            },
            keySize: 128 / 32,
            ivSize: 128 / 32,
            _ENC_XFORM_MODE: 1,
            _DEC_XFORM_MODE: 2,
            _createHelper: (function () {
                function selectCipherStrategy(key) {
                    if (typeof key == 'string') {
                        return PasswordBasedCipher;
                    } else {
                        return SerializableCipher;
                    }
                }
                return function (cipher) {
                    return {
                        encrypt: function (message, key, cfg) {
                            return selectCipherStrategy(key).encrypt(cipher, message, key, cfg);
                        },
                        decrypt: function (ciphertext, key, cfg) {
                            return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg);
                        }
                    };
                };
            }
            ())
        });
        var StreamCipher = C_lib.StreamCipher = Cipher.extend({
            _doFinalize: function () {
                var finalProcessedBlocks = this._process(!!'flush');
                return finalProcessedBlocks;
            },
            blockSize: 1
        });
        var C_mode = C.mode = {};
        var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({
            createEncryptor: function (cipher, iv) {
                return this.Encryptor.create(cipher, iv);
            },
            createDecryptor: function (cipher, iv) {
                return this.Decryptor.create(cipher, iv);
            },
            init: function (cipher, iv) {
                this._cipher = cipher;
                this._iv = iv;
            }
        });
        var CBC = C_mode.CBC = (function () {
            var CBC = BlockCipherMode.extend();
            CBC.Encryptor = CBC.extend({
                processBlock: function (words, offset) {
                    var cipher = this._cipher;
                    var blockSize = cipher.blockSize;
                    xorBlock.call(this, words, offset, blockSize);
                    cipher.encryptBlock(words, offset);
                    this._prevBlock = words.slice(offset, offset + blockSize);
                }
            });
            CBC.Decryptor = CBC.extend({
                processBlock: function (words, offset) {
                    var cipher = this._cipher;
                    var blockSize = cipher.blockSize;
                    var thisBlock = words.slice(offset, offset + blockSize);
                    cipher.decryptBlock(words, offset);
                    xorBlock.call(this, words, offset, blockSize);
                    this._prevBlock = thisBlock;
                }
            });
            function xorBlock(words, offset, blockSize) {
                var iv = this._iv;
                if (iv) {
                    var block = iv;
                    this._iv = undefined;
                } else {
                    var block = this._prevBlock;
                }
                for (var i = 0; i < blockSize; i++) {
                    words[offset + i] ^= block[i];
                }
            }
            return CBC;
        }
        ());
        var C_pad = C.pad = {};
        var Pkcs7 = C_pad.Pkcs7 = {
            pad: function (data, blockSize) {
                var blockSizeBytes = blockSize * 4;
                var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes;
                var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes;
                var paddingWords = [];
                for (var i = 0; i < nPaddingBytes; i += 4) {
                    paddingWords.push(paddingWord);
                }
                var padding = WordArray.create(paddingWords, nPaddingBytes);
                data.concat(padding);
            },
            unpad: function (data) {
                var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff;
                data.sigBytes -= nPaddingBytes;
            }
        };
        var BlockCipher = C_lib.BlockCipher = Cipher.extend({
            cfg: Cipher.cfg.extend({
                mode: CBC,
                padding: Pkcs7
            }),
            reset: function () {
                Cipher.reset.call(this);
                var cfg = this.cfg;
                var iv = cfg.iv;
                var mode = cfg.mode;
                if (this._xformMode == this._ENC_XFORM_MODE) {
                    var modeCreator = mode.createEncryptor;
                } else {
                    var modeCreator = mode.createDecryptor;
                    this._minBufferSize = 1;
                }
                if (this._mode && this._mode.__creator == modeCreator) {
                    this._mode.init(this, iv && iv.words);
                } else {
                    this._mode = modeCreator.call(mode, this, iv && iv.words);
                    this._mode.__creator = modeCreator;
                }
            },
            _doProcessBlock: function (words, offset) {
                this._mode.processBlock(words, offset);
            },
            _doFinalize: function () {
                var padding = this.cfg.padding;
                if (this._xformMode == this._ENC_XFORM_MODE) {
                    padding.pad(this._data, this.blockSize);
                    var finalProcessedBlocks = this._process(!!'flush');
                } else {
                    var finalProcessedBlocks = this._process(!!'flush');
                    padding.unpad(finalProcessedBlocks);
                }
                return finalProcessedBlocks;
            },
            blockSize: 128 / 32
        });
        var CipherParams = C_lib.CipherParams = Base.extend({
            init: function (cipherParams) {
                this.mixIn(cipherParams);
            },
            toString: function (formatter) {
                return (formatter || this.formatter).stringify(this);
            }
        });
        var C_format = C.format = {};
        var OpenSSLFormatter = C_format.OpenSSL = {
            stringify: function (cipherParams) {
                var ciphertext = cipherParams.ciphertext;
                var salt = cipherParams.salt;
                if (salt) {
                    var wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext);
                } else {
                    var wordArray = ciphertext;
                }
                return wordArray.toString(Base64);
            },
            parse: function (openSSLStr) {
                var ciphertext = Base64.parse(openSSLStr);
                var ciphertextWords = ciphertext.words;
                if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) {
                    var salt = WordArray.create(ciphertextWords.slice(2, 4));
                    ciphertextWords.splice(0, 4);
                    ciphertext.sigBytes -= 16;
                }
                return CipherParams.create({
                    ciphertext: ciphertext,
                    salt: salt
                });
            }
        };
        var SerializableCipher = C_lib.SerializableCipher = Base.extend({
            cfg: Base.extend({
                format: OpenSSLFormatter
            }),
            encrypt: function (cipher, message, key, cfg) {
                cfg = this.cfg.extend(cfg);
                var encryptor = cipher.createEncryptor(key, cfg);
                var ciphertext = encryptor.finalize(message);
                var cipherCfg = encryptor.cfg;
                return CipherParams.create({
                    ciphertext: ciphertext,
                    key: key,
                    iv: cipherCfg.iv,
                    algorithm: cipher,
                    mode: cipherCfg.mode,
                    padding: cipherCfg.padding,
                    blockSize: cipher.blockSize,
                    formatter: cfg.format
                });
            },
            decrypt: function (cipher, ciphertext, key, cfg) {
                cfg = this.cfg.extend(cfg);
                ciphertext = this._parse(ciphertext, cfg.format);
                var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext);
                return plaintext;
            },
            _parse: function (ciphertext, format) {
                if (typeof ciphertext == 'string') {
                    return format.parse(ciphertext, this);
                } else {
                    return ciphertext;
                }
            }
        });
        var C_kdf = C.kdf = {};
        var OpenSSLKdf = C_kdf.OpenSSL = {
            execute: function (password, keySize, ivSize, salt) {
                if (!salt) {
                    salt = WordArray.random(64 / 8);
                }
                var key = EvpKDF.create({
                    keySize: keySize + ivSize
                }).compute(password, salt);
                var iv = WordArray.create(key.words.slice(keySize), ivSize * 4);
                key.sigBytes = keySize * 4;
                return CipherParams.create({
                    key: key,
                    iv: iv,
                    salt: salt
                });
            }
        };
        var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({
            cfg: SerializableCipher.cfg.extend({
                kdf: OpenSSLKdf
            }),
            encrypt: function (cipher, message, password, cfg) {
                cfg = this.cfg.extend(cfg);
                var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize);
                cfg.iv = derivedParams.iv;
                var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg);
                ciphertext.mixIn(derivedParams);
                return ciphertext;
            },
            decrypt: function (cipher, ciphertext, password, cfg) {
                cfg = this.cfg.extend(cfg);
                ciphertext = this._parse(ciphertext, cfg.format);
                var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt);
                cfg.iv = derivedParams.iv;
                var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg);
                return plaintext;
            }
        });
    }
    ());
    CryptoJS.mode.CFB = (function () {
        var CFB = CryptoJS.lib.BlockCipherMode.extend();
        CFB.Encryptor = CFB.extend({
            processBlock: function (words, offset) {
                var cipher = this._cipher;
                var blockSize = cipher.blockSize;
                generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher);
                this._prevBlock = words.slice(offset, offset + blockSize);
            }
        });
        CFB.Decryptor = CFB.extend({
            processBlock: function (words, offset) {
                var cipher = this._cipher;
                var blockSize = cipher.blockSize;
                var thisBlock = words.slice(offset, offset + blockSize);
                generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher);
                this._prevBlock = thisBlock;
            }
        });
        function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) {
            var iv = this._iv;
            if (iv) {
                var keystream = iv.slice(0);
                this._iv = undefined;
            } else {
                var keystream = this._prevBlock;
            }
            cipher.encryptBlock(keystream, 0);
            for (var i = 0; i < blockSize; i++) {
                words[offset + i] ^= keystream[i];
            }
        }
        return CFB;
    }
    ());
    CryptoJS.mode.ECB = (function () {
        var ECB = CryptoJS.lib.BlockCipherMode.extend();
        ECB.Encryptor = ECB.extend({
            processBlock: function (words, offset) {
                this._cipher.encryptBlock(words, offset);
            }
        });
        ECB.Decryptor = ECB.extend({
            processBlock: function (words, offset) {
                this._cipher.decryptBlock(words, offset);
            }
        });
        return ECB;
    }
    ());
    CryptoJS.pad.AnsiX923 = {
        pad: function (data, blockSize) {
            var dataSigBytes = data.sigBytes;
            var blockSizeBytes = blockSize * 4;
            var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes;
            var lastBytePos = dataSigBytes + nPaddingBytes - 1;
            data.clamp();
            data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8);
            data.sigBytes += nPaddingBytes;
        },
        unpad: function (data) {
            var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff;
            data.sigBytes -= nPaddingBytes;
        }
    };
    CryptoJS.pad.Iso10126 = {
        pad: function (data, blockSize) {
            var blockSizeBytes = blockSize * 4;
            var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes;
            data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)).concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1));
        },
        unpad: function (data) {
            var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff;
            data.sigBytes -= nPaddingBytes;
        }
    };
    CryptoJS.pad.Iso97971 = {
        pad: function (data, blockSize) {
            data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1));
            CryptoJS.pad.ZeroPadding.pad(data, blockSize);
        },
        unpad: function (data) {
            CryptoJS.pad.ZeroPadding.unpad(data);
            data.sigBytes--;
        }
    };
    CryptoJS.mode.OFB = (function () {
        var OFB = CryptoJS.lib.BlockCipherMode.extend();
        var Encryptor = OFB.Encryptor = OFB.extend({
            processBlock: function (words, offset) {
                var cipher = this._cipher
                var blockSize = cipher.blockSize;
                var iv = this._iv;
                var keystream = this._keystream;
                if (iv) {
                    keystream = this._keystream = iv.slice(0);
                    this._iv = undefined;
                }
                cipher.encryptBlock(keystream, 0);
                for (var i = 0; i < blockSize; i++) {
                    words[offset + i] ^= keystream[i];
                }
            }
        });
        OFB.Decryptor = Encryptor;
        return OFB;
    }
    ());
    CryptoJS.pad.NoPadding = {
        pad: function () {},
        unpad: function () {}
    };
    (function (undefined) {
        var C = CryptoJS;
        var C_lib = C.lib;
        var CipherParams = C_lib.CipherParams;
        var C_enc = C.enc;
        var Hex = C_enc.Hex;
        var C_format = C.format;
        var HexFormatter = C_format.Hex = {
            stringify: function (cipherParams) {
                return cipherParams.ciphertext.toString(Hex);
            },
            parse: function (input) {
                var ciphertext = Hex.parse(input);
                return CipherParams.create({
                    ciphertext: ciphertext
                });
            }
        };
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var BlockCipher = C_lib.BlockCipher;
        var C_algo = C.algo;
        var SBOX = [];
        var INV_SBOX = [];
        var SUB_MIX_0 = [];
        var SUB_MIX_1 = [];
        var SUB_MIX_2 = [];
        var SUB_MIX_3 = [];
        var INV_SUB_MIX_0 = [];
        var INV_SUB_MIX_1 = [];
        var INV_SUB_MIX_2 = [];
        var INV_SUB_MIX_3 = [];
        (function () {
            var d = [];
            for (var i = 0; i < 256; i++) {
                if (i < 128) {
                    d[i] = i << 1;
                } else {
                    d[i] = (i << 1) ^ 0x11b;
                }
            }
            var x = 0;
            var xi = 0;
            for (var i = 0; i < 256; i++) {
                var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4);
                sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63;
                SBOX[x] = sx;
                INV_SBOX[sx] = x;
                var x2 = d[x];
                var x4 = d[x2];
                var x8 = d[x4];
                var t = (d[sx] * 0x101) ^ (sx * 0x1010100);
                SUB_MIX_0[x] = (t << 24) | (t >>> 8);
                SUB_MIX_1[x] = (t << 16) | (t >>> 16);
                SUB_MIX_2[x] = (t << 8) | (t >>> 24);
                SUB_MIX_3[x] = t;
                var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
                INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8);
                INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16);
                INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24);
                INV_SUB_MIX_3[sx] = t;
                if (!x) {
                    x = xi = 1;
                } else {
                    x = x2 ^ d[d[d[x8 ^ x2]]];
                    xi ^= d[d[xi]];
                }
            }
        }
        ());
        var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
        var AES = C_algo.AES = BlockCipher.extend({
            _doReset: function () {
                if (this._nRounds && this._keyPriorReset === this._key) {
                    return;
                }
                var key = this._keyPriorReset = this._key;
                var keyWords = key.words;
                var keySize = key.sigBytes / 4;
                var nRounds = this._nRounds = keySize + 6;
                var ksRows = (nRounds + 1) * 4;
                var keySchedule = this._keySchedule = [];
                for (var ksRow = 0; ksRow < ksRows; ksRow++) {
                    if (ksRow < keySize) {
                        keySchedule[ksRow] = keyWords[ksRow];
                    } else {
                        var t = keySchedule[ksRow - 1];
                        if (!(ksRow % keySize)) {
                            t = (t << 8) | (t >>> 24);
                            t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
                            t ^= RCON[(ksRow / keySize) | 0] << 24;
                        } else if (keySize > 6 && ksRow % keySize == 4) {
                            t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff];
                        }
                        keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t;
                    }
                }
                var invKeySchedule = this._invKeySchedule = [];
                for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) {
                    var ksRow = ksRows - invKsRow;
                    if (invKsRow % 4) {
                        var t = keySchedule[ksRow];
                    } else {
                        var t = keySchedule[ksRow - 4];
                    }
                    if (invKsRow < 4 || ksRow <= 4) {
                        invKeySchedule[invKsRow] = t;
                    } else {
                        invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]];
                    }
                }
            },
            encryptBlock: function (M, offset) {
                this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX);
            },
            decryptBlock: function (M, offset) {
                var t = M[offset + 1];
                M[offset + 1] = M[offset + 3];
                M[offset + 3] = t;
                this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX);
                var t = M[offset + 1];
                M[offset + 1] = M[offset + 3];
                M[offset + 3] = t;
            },
            _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) {
                var nRounds = this._nRounds;
                var s0 = M[offset] ^ keySchedule[0];
                var s1 = M[offset + 1] ^ keySchedule[1];
                var s2 = M[offset + 2] ^ keySchedule[2];
                var s3 = M[offset + 3] ^ keySchedule[3];
                var ksRow = 4;
                for (var round = 1; round < nRounds; round++) {
                    var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++];
                    var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++];
                    var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++];
                    var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++];
                    s0 = t0;
                    s1 = t1;
                    s2 = t2;
                    s3 = t3;
                }
                var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++];
                var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++];
                var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++];
                var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++];
                M[offset] = t0;
                M[offset + 1] = t1;
                M[offset + 2] = t2;
                M[offset + 3] = t3;
            },
            keySize: 256 / 32
        });
        C.AES = BlockCipher._createHelper(AES);
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var WordArray = C_lib.WordArray;
        var BlockCipher = C_lib.BlockCipher;
        var C_algo = C.algo;
        var PC1 = [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4];
        var PC2 = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32];
        var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28];
        var SBOX_P = [{
            0x0: 0x808200,
            0x10000000: 0x8000,
            0x20000000: 0x808002,
            0x30000000: 0x2,
            0x40000000: 0x200,
            0x50000000: 0x808202,
            0x60000000: 0x800202,
            0x70000000: 0x800000,
            0x80000000: 0x202,
            0x90000000: 0x800200,
            0xa0000000: 0x8200,
            0xb0000000: 0x808000,
            0xc0000000: 0x8002,
            0xd0000000: 0x800002,
            0xe0000000: 0x0,
            0xf0000000: 0x8202,
            0x8000000: 0x0,
            0x18000000: 0x808202,
            0x28000000: 0x8202,
            0x38000000: 0x8000,
            0x48000000: 0x808200,
            0x58000000: 0x200,
            0x68000000: 0x808002,
            0x78000000: 0x2,
            0x88000000: 0x800200,
            0x98000000: 0x8200,
            0xa8000000: 0x808000,
            0xb8000000: 0x800202,
            0xc8000000: 0x800002,
            0xd8000000: 0x8002,
            0xe8000000: 0x202,
            0xf8000000: 0x800000,
            0x1: 0x8000,
            0x10000001: 0x2,
            0x20000001: 0x808200,
            0x30000001: 0x800000,
            0x40000001: 0x808002,
            0x50000001: 0x8200,
            0x60000001: 0x200,
            0x70000001: 0x800202,
            0x80000001: 0x808202,
            0x90000001: 0x808000,
            0xa0000001: 0x800002,
            0xb0000001: 0x8202,
            0xc0000001: 0x202,
            0xd0000001: 0x800200,
            0xe0000001: 0x8002,
            0xf0000001: 0x0,
            0x8000001: 0x808202,
            0x18000001: 0x808000,
            0x28000001: 0x800000,
            0x38000001: 0x200,
            0x48000001: 0x8000,
            0x58000001: 0x800002,
            0x68000001: 0x2,
            0x78000001: 0x8202,
            0x88000001: 0x8002,
            0x98000001: 0x800202,
            0xa8000001: 0x202,
            0xb8000001: 0x808200,
            0xc8000001: 0x800200,
            0xd8000001: 0x0,
            0xe8000001: 0x8200,
            0xf8000001: 0x808002
        }, {
            0x0: 0x40084010,
            0x1000000: 0x4000,
            0x2000000: 0x80000,
            0x3000000: 0x40080010,
            0x4000000: 0x40000010,
            0x5000000: 0x40084000,
            0x6000000: 0x40004000,
            0x7000000: 0x10,
            0x8000000: 0x84000,
            0x9000000: 0x40004010,
            0xa000000: 0x40000000,
            0xb000000: 0x84010,
            0xc000000: 0x80010,
            0xd000000: 0x0,
            0xe000000: 0x4010,
            0xf000000: 0x40080000,
            0x800000: 0x40004000,
            0x1800000: 0x84010,
            0x2800000: 0x10,
            0x3800000: 0x40004010,
            0x4800000: 0x40084010,
            0x5800000: 0x40000000,
            0x6800000: 0x80000,
            0x7800000: 0x40080010,
            0x8800000: 0x80010,
            0x9800000: 0x0,
            0xa800000: 0x4000,
            0xb800000: 0x40080000,
            0xc800000: 0x40000010,
            0xd800000: 0x84000,
            0xe800000: 0x40084000,
            0xf800000: 0x4010,
            0x10000000: 0x0,
            0x11000000: 0x40080010,
            0x12000000: 0x40004010,
            0x13000000: 0x40084000,
            0x14000000: 0x40080000,
            0x15000000: 0x10,
            0x16000000: 0x84010,
            0x17000000: 0x4000,
            0x18000000: 0x4010,
            0x19000000: 0x80000,
            0x1a000000: 0x80010,
            0x1b000000: 0x40000010,
            0x1c000000: 0x84000,
            0x1d000000: 0x40004000,
            0x1e000000: 0x40000000,
            0x1f000000: 0x40084010,
            0x10800000: 0x84010,
            0x11800000: 0x80000,
            0x12800000: 0x40080000,
            0x13800000: 0x4000,
            0x14800000: 0x40004000,
            0x15800000: 0x40084010,
            0x16800000: 0x10,
            0x17800000: 0x40000000,
            0x18800000: 0x40084000,
            0x19800000: 0x40000010,
            0x1a800000: 0x40004010,
            0x1b800000: 0x80010,
            0x1c800000: 0x0,
            0x1d800000: 0x4010,
            0x1e800000: 0x40080010,
            0x1f800000: 0x84000
        }, {
            0x0: 0x104,
            0x100000: 0x0,
            0x200000: 0x4000100,
            0x300000: 0x10104,
            0x400000: 0x10004,
            0x500000: 0x4000004,
            0x600000: 0x4010104,
            0x700000: 0x4010000,
            0x800000: 0x4000000,
            0x900000: 0x4010100,
            0xa00000: 0x10100,
            0xb00000: 0x4010004,
            0xc00000: 0x4000104,
            0xd00000: 0x10000,
            0xe00000: 0x4,
            0xf00000: 0x100,
            0x80000: 0x4010100,
            0x180000: 0x4010004,
            0x280000: 0x0,
            0x380000: 0x4000100,
            0x480000: 0x4000004,
            0x580000: 0x10000,
            0x680000: 0x10004,
            0x780000: 0x104,
            0x880000: 0x4,
            0x980000: 0x100,
            0xa80000: 0x4010000,
            0xb80000: 0x10104,
            0xc80000: 0x10100,
            0xd80000: 0x4000104,
            0xe80000: 0x4010104,
            0xf80000: 0x4000000,
            0x1000000: 0x4010100,
            0x1100000: 0x10004,
            0x1200000: 0x10000,
            0x1300000: 0x4000100,
            0x1400000: 0x100,
            0x1500000: 0x4010104,
            0x1600000: 0x4000004,
            0x1700000: 0x0,
            0x1800000: 0x4000104,
            0x1900000: 0x4000000,
            0x1a00000: 0x4,
            0x1b00000: 0x10100,
            0x1c00000: 0x4010000,
            0x1d00000: 0x104,
            0x1e00000: 0x10104,
            0x1f00000: 0x4010004,
            0x1080000: 0x4000000,
            0x1180000: 0x104,
            0x1280000: 0x4010100,
            0x1380000: 0x0,
            0x1480000: 0x10004,
            0x1580000: 0x4000100,
            0x1680000: 0x100,
            0x1780000: 0x4010004,
            0x1880000: 0x10000,
            0x1980000: 0x4010104,
            0x1a80000: 0x10104,
            0x1b80000: 0x4000004,
            0x1c80000: 0x4000104,
            0x1d80000: 0x4010000,
            0x1e80000: 0x4,
            0x1f80000: 0x10100
        }, {
            0x0: 0x80401000,
            0x10000: 0x80001040,
            0x20000: 0x401040,
            0x30000: 0x80400000,
            0x40000: 0x0,
            0x50000: 0x401000,
            0x60000: 0x80000040,
            0x70000: 0x400040,
            0x80000: 0x80000000,
            0x90000: 0x400000,
            0xa0000: 0x40,
            0xb0000: 0x80001000,
            0xc0000: 0x80400040,
            0xd0000: 0x1040,
            0xe0000: 0x1000,
            0xf0000: 0x80401040,
            0x8000: 0x80001040,
            0x18000: 0x40,
            0x28000: 0x80400040,
            0x38000: 0x80001000,
            0x48000: 0x401000,
            0x58000: 0x80401040,
            0x68000: 0x0,
            0x78000: 0x80400000,
            0x88000: 0x1000,
            0x98000: 0x80401000,
            0xa8000: 0x400000,
            0xb8000: 0x1040,
            0xc8000: 0x80000000,
            0xd8000: 0x400040,
            0xe8000: 0x401040,
            0xf8000: 0x80000040,
            0x100000: 0x400040,
            0x110000: 0x401000,
            0x120000: 0x80000040,
            0x130000: 0x0,
            0x140000: 0x1040,
            0x150000: 0x80400040,
            0x160000: 0x80401000,
            0x170000: 0x80001040,
            0x180000: 0x80401040,
            0x190000: 0x80000000,
            0x1a0000: 0x80400000,
            0x1b0000: 0x401040,
            0x1c0000: 0x80001000,
            0x1d0000: 0x400000,
            0x1e0000: 0x40,
            0x1f0000: 0x1000,
            0x108000: 0x80400000,
            0x118000: 0x80401040,
            0x128000: 0x0,
            0x138000: 0x401000,
            0x148000: 0x400040,
            0x158000: 0x80000000,
            0x168000: 0x80001040,
            0x178000: 0x40,
            0x188000: 0x80000040,
            0x198000: 0x1000,
            0x1a8000: 0x80001000,
            0x1b8000: 0x80400040,
            0x1c8000: 0x1040,
            0x1d8000: 0x80401000,
            0x1e8000: 0x400000,
            0x1f8000: 0x401040
        }, {
            0x0: 0x80,
            0x1000: 0x1040000,
            0x2000: 0x40000,
            0x3000: 0x20000000,
            0x4000: 0x20040080,
            0x5000: 0x1000080,
            0x6000: 0x21000080,
            0x7000: 0x40080,
            0x8000: 0x1000000,
            0x9000: 0x20040000,
            0xa000: 0x20000080,
            0xb000: 0x21040080,
            0xc000: 0x21040000,
            0xd000: 0x0,
            0xe000: 0x1040080,
            0xf000: 0x21000000,
            0x800: 0x1040080,
            0x1800: 0x21000080,
            0x2800: 0x80,
            0x3800: 0x1040000,
            0x4800: 0x40000,
            0x5800: 0x20040080,
            0x6800: 0x21040000,
            0x7800: 0x20000000,
            0x8800: 0x20040000,
            0x9800: 0x0,
            0xa800: 0x21040080,
            0xb800: 0x1000080,
            0xc800: 0x20000080,
            0xd800: 0x21000000,
            0xe800: 0x1000000,
            0xf800: 0x40080,
            0x10000: 0x40000,
            0x11000: 0x80,
            0x12000: 0x20000000,
            0x13000: 0x21000080,
            0x14000: 0x1000080,
            0x15000: 0x21040000,
            0x16000: 0x20040080,
            0x17000: 0x1000000,
            0x18000: 0x21040080,
            0x19000: 0x21000000,
            0x1a000: 0x1040000,
            0x1b000: 0x20040000,
            0x1c000: 0x40080,
            0x1d000: 0x20000080,
            0x1e000: 0x0,
            0x1f000: 0x1040080,
            0x10800: 0x21000080,
            0x11800: 0x1000000,
            0x12800: 0x1040000,
            0x13800: 0x20040080,
            0x14800: 0x20000000,
            0x15800: 0x1040080,
            0x16800: 0x80,
            0x17800: 0x21040000,
            0x18800: 0x40080,
            0x19800: 0x21040080,
            0x1a800: 0x0,
            0x1b800: 0x21000000,
            0x1c800: 0x1000080,
            0x1d800: 0x40000,
            0x1e800: 0x20040000,
            0x1f800: 0x20000080
        }, {
            0x0: 0x10000008,
            0x100: 0x2000,
            0x200: 0x10200000,
            0x300: 0x10202008,
            0x400: 0x10002000,
            0x500: 0x200000,
            0x600: 0x200008,
            0x700: 0x10000000,
            0x800: 0x0,
            0x900: 0x10002008,
            0xa00: 0x202000,
            0xb00: 0x8,
            0xc00: 0x10200008,
            0xd00: 0x202008,
            0xe00: 0x2008,
            0xf00: 0x10202000,
            0x80: 0x10200000,
            0x180: 0x10202008,
            0x280: 0x8,
            0x380: 0x200000,
            0x480: 0x202008,
            0x580: 0x10000008,
            0x680: 0x10002000,
            0x780: 0x2008,
            0x880: 0x200008,
            0x980: 0x2000,
            0xa80: 0x10002008,
            0xb80: 0x10200008,
            0xc80: 0x0,
            0xd80: 0x10202000,
            0xe80: 0x202000,
            0xf80: 0x10000000,
            0x1000: 0x10002000,
            0x1100: 0x10200008,
            0x1200: 0x10202008,
            0x1300: 0x2008,
            0x1400: 0x200000,
            0x1500: 0x10000000,
            0x1600: 0x10000008,
            0x1700: 0x202000,
            0x1800: 0x202008,
            0x1900: 0x0,
            0x1a00: 0x8,
            0x1b00: 0x10200000,
            0x1c00: 0x2000,
            0x1d00: 0x10002008,
            0x1e00: 0x10202000,
            0x1f00: 0x200008,
            0x1080: 0x8,
            0x1180: 0x202000,
            0x1280: 0x200000,
            0x1380: 0x10000008,
            0x1480: 0x10002000,
            0x1580: 0x2008,
            0x1680: 0x10202008,
            0x1780: 0x10200000,
            0x1880: 0x10202000,
            0x1980: 0x10200008,
            0x1a80: 0x2000,
            0x1b80: 0x202008,
            0x1c80: 0x200008,
            0x1d80: 0x0,
            0x1e80: 0x10000000,
            0x1f80: 0x10002008
        }, {
            0x0: 0x100000,
            0x10: 0x2000401,
            0x20: 0x400,
            0x30: 0x100401,
            0x40: 0x2100401,
            0x50: 0x0,
            0x60: 0x1,
            0x70: 0x2100001,
            0x80: 0x2000400,
            0x90: 0x100001,
            0xa0: 0x2000001,
            0xb0: 0x2100400,
            0xc0: 0x2100000,
            0xd0: 0x401,
            0xe0: 0x100400,
            0xf0: 0x2000000,
            0x8: 0x2100001,
            0x18: 0x0,
            0x28: 0x2000401,
            0x38: 0x2100400,
            0x48: 0x100000,
            0x58: 0x2000001,
            0x68: 0x2000000,
            0x78: 0x401,
            0x88: 0x100401,
            0x98: 0x2000400,
            0xa8: 0x2100000,
            0xb8: 0x100001,
            0xc8: 0x400,
            0xd8: 0x2100401,
            0xe8: 0x1,
            0xf8: 0x100400,
            0x100: 0x2000000,
            0x110: 0x100000,
            0x120: 0x2000401,
            0x130: 0x2100001,
            0x140: 0x100001,
            0x150: 0x2000400,
            0x160: 0x2100400,
            0x170: 0x100401,
            0x180: 0x401,
            0x190: 0x2100401,
            0x1a0: 0x100400,
            0x1b0: 0x1,
            0x1c0: 0x0,
            0x1d0: 0x2100000,
            0x1e0: 0x2000001,
            0x1f0: 0x400,
            0x108: 0x100400,
            0x118: 0x2000401,
            0x128: 0x2100001,
            0x138: 0x1,
            0x148: 0x2000000,
            0x158: 0x100000,
            0x168: 0x401,
            0x178: 0x2100400,
            0x188: 0x2000001,
            0x198: 0x2100000,
            0x1a8: 0x0,
            0x1b8: 0x2100401,
            0x1c8: 0x100401,
            0x1d8: 0x400,
            0x1e8: 0x2000400,
            0x1f8: 0x100001
        }, {
            0x0: 0x8000820,
            0x1: 0x20000,
            0x2: 0x8000000,
            0x3: 0x20,
            0x4: 0x20020,
            0x5: 0x8020820,
            0x6: 0x8020800,
            0x7: 0x800,
            0x8: 0x8020000,
            0x9: 0x8000800,
            0xa: 0x20800,
            0xb: 0x8020020,
            0xc: 0x820,
            0xd: 0x0,
            0xe: 0x8000020,
            0xf: 0x20820,
            0x80000000: 0x800,
            0x80000001: 0x8020820,
            0x80000002: 0x8000820,
            0x80000003: 0x8000000,
            0x80000004: 0x8020000,
            0x80000005: 0x20800,
            0x80000006: 0x20820,
            0x80000007: 0x20,
            0x80000008: 0x8000020,
            0x80000009: 0x820,
            0x8000000a: 0x20020,
            0x8000000b: 0x8020800,
            0x8000000c: 0x0,
            0x8000000d: 0x8020020,
            0x8000000e: 0x8000800,
            0x8000000f: 0x20000,
            0x10: 0x20820,
            0x11: 0x8020800,
            0x12: 0x20,
            0x13: 0x800,
            0x14: 0x8000800,
            0x15: 0x8000020,
            0x16: 0x8020020,
            0x17: 0x20000,
            0x18: 0x0,
            0x19: 0x20020,
            0x1a: 0x8020000,
            0x1b: 0x8000820,
            0x1c: 0x8020820,
            0x1d: 0x20800,
            0x1e: 0x820,
            0x1f: 0x8000000,
            0x80000010: 0x20000,
            0x80000011: 0x800,
            0x80000012: 0x8020020,
            0x80000013: 0x20820,
            0x80000014: 0x20,
            0x80000015: 0x8020000,
            0x80000016: 0x8000000,
            0x80000017: 0x8000820,
            0x80000018: 0x8020820,
            0x80000019: 0x8000020,
            0x8000001a: 0x8000800,
            0x8000001b: 0x0,
            0x8000001c: 0x20800,
            0x8000001d: 0x820,
            0x8000001e: 0x20020,
            0x8000001f: 0x8020800
        }
        ];
        var SBOX_MASK = [0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f];
        var DES = C_algo.DES = BlockCipher.extend({
            _doReset: function () {
                var key = this._key;
                var keyWords = key.words;
                var keyBits = [];
                for (var i = 0; i < 56; i++) {
                    var keyBitPos = PC1[i] - 1;
                    keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1;
                }
                var subKeys = this._subKeys = [];
                for (var nSubKey = 0; nSubKey < 16; nSubKey++) {
                    var subKey = subKeys[nSubKey] = [];
                    var bitShift = BIT_SHIFTS[nSubKey];
                    for (var i = 0; i < 24; i++) {
                        subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6);
                        subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6);
                    }
                    subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31);
                    for (var i = 1; i < 7; i++) {
                        subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3);
                    }
                    subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27);
                }
                var invSubKeys = this._invSubKeys = [];
                for (var i = 0; i < 16; i++) {
                    invSubKeys[i] = subKeys[15 - i];
                }
            },
            encryptBlock: function (M, offset) {
                this._doCryptBlock(M, offset, this._subKeys);
            },
            decryptBlock: function (M, offset) {
                this._doCryptBlock(M, offset, this._invSubKeys);
            },
            _doCryptBlock: function (M, offset, subKeys) {
                this._lBlock = M[offset];
                this._rBlock = M[offset + 1];
                exchangeLR.call(this, 4, 0x0f0f0f0f);
                exchangeLR.call(this, 16, 0x0000ffff);
                exchangeRL.call(this, 2, 0x33333333);
                exchangeRL.call(this, 8, 0x00ff00ff);
                exchangeLR.call(this, 1, 0x55555555);
                for (var round = 0; round < 16; round++) {
                    var subKey = subKeys[round];
                    var lBlock = this._lBlock;
                    var rBlock = this._rBlock;
                    var f = 0;
                    for (var i = 0; i < 8; i++) {
                        f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0];
                    }
                    this._lBlock = rBlock;
                    this._rBlock = lBlock ^ f;
                }
                var t = this._lBlock;
                this._lBlock = this._rBlock;
                this._rBlock = t;
                exchangeLR.call(this, 1, 0x55555555);
                exchangeRL.call(this, 8, 0x00ff00ff);
                exchangeRL.call(this, 2, 0x33333333);
                exchangeLR.call(this, 16, 0x0000ffff);
                exchangeLR.call(this, 4, 0x0f0f0f0f);
                M[offset] = this._lBlock;
                M[offset + 1] = this._rBlock;
            },
            keySize: 64 / 32,
            ivSize: 64 / 32,
            blockSize: 64 / 32
        });
        function exchangeLR(offset, mask) {
            var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask;
            this._rBlock ^= t;
            this._lBlock ^= t << offset;
        }
        function exchangeRL(offset, mask) {
            var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask;
            this._lBlock ^= t;
            this._rBlock ^= t << offset;
        }
        C.DES = BlockCipher._createHelper(DES);
        var TripleDES = C_algo.TripleDES = BlockCipher.extend({
            _doReset: function () {
                var key = this._key;
                var keyWords = key.words;
                this._des1 = DES.createEncryptor(WordArray.create(keyWords.slice(0, 2)));
                this._des2 = DES.createEncryptor(WordArray.create(keyWords.slice(2, 4)));
                this._des3 = DES.createEncryptor(WordArray.create(keyWords.slice(4, 6)));
            },
            encryptBlock: function (M, offset) {
                this._des1.encryptBlock(M, offset);
                this._des2.decryptBlock(M, offset);
                this._des3.encryptBlock(M, offset);
            },
            decryptBlock: function (M, offset) {
                this._des3.decryptBlock(M, offset);
                this._des2.encryptBlock(M, offset);
                this._des1.decryptBlock(M, offset);
            },
            keySize: 192 / 32,
            ivSize: 64 / 32,
            blockSize: 64 / 32
        });
        C.TripleDES = BlockCipher._createHelper(TripleDES);
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var StreamCipher = C_lib.StreamCipher;
        var C_algo = C.algo;
        var RC4 = C_algo.RC4 = StreamCipher.extend({
            _doReset: function () {
                var key = this._key;
                var keyWords = key.words;
                var keySigBytes = key.sigBytes;
                var S = this._S = [];
                for (var i = 0; i < 256; i++) {
                    S[i] = i;
                }
                for (var i = 0, j = 0; i < 256; i++) {
                    var keyByteIndex = i % keySigBytes;
                    var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff;
                    j = (j + S[i] + keyByte) % 256;
                    var t = S[i];
                    S[i] = S[j];
                    S[j] = t;
                }
                this._i = this._j = 0;
            },
            _doProcessBlock: function (M, offset) {
                M[offset] ^= generateKeystreamWord.call(this);
            },
            keySize: 256 / 32,
            ivSize: 0
        });
        function generateKeystreamWord() {
            var S = this._S;
            var i = this._i;
            var j = this._j;
            var keystreamWord = 0;
            for (var n = 0; n < 4; n++) {
                i = (i + 1) % 256;
                j = (j + S[i]) % 256;
                var t = S[i];
                S[i] = S[j];
                S[j] = t;
                keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8);
            }
            this._i = i;
            this._j = j;
            return keystreamWord;
        }
        C.RC4 = StreamCipher._createHelper(RC4);
        var RC4Drop = C_algo.RC4Drop = RC4.extend({
            cfg: RC4.cfg.extend({
                drop: 192
            }),
            _doReset: function () {
                RC4._doReset.call(this);
                for (var i = this.cfg.drop; i > 0; i--) {
                    generateKeystreamWord.call(this);
                }
            }
        });
        C.RC4Drop = StreamCipher._createHelper(RC4Drop);
    }
    ());
    CryptoJS.mode.CTRGladman = (function () {
        var CTRGladman = CryptoJS.lib.BlockCipherMode.extend();
        function incWord(word) {
            if (((word >> 24) & 0xff) === 0xff) {
                var b1 = (word >> 16) & 0xff;
                var b2 = (word >> 8) & 0xff;
                var b3 = word & 0xff;
                if (b1 === 0xff) {
                    b1 = 0;
                    if (b2 === 0xff) {
                        b2 = 0;
                        if (b3 === 0xff) {
                            b3 = 0;
                        } else {
                            ++b3;
                        }
                    } else {
                        ++b2;
                    }
                } else {
                    ++b1;
                }
                word = 0;
                word += (b1 << 16);
                word += (b2 << 8);
                word += b3;
            } else {
                word += (0x01 << 24);
            }
            return word;
        }
        function incCounter(counter) {
            if ((counter[0] = incWord(counter[0])) === 0) {
                counter[1] = incWord(counter[1]);
            }
            return counter;
        }
        var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({
            processBlock: function (words, offset) {
                var cipher = this._cipher
                var blockSize = cipher.blockSize;
                var iv = this._iv;
                var counter = this._counter;
                if (iv) {
                    counter = this._counter = iv.slice(0);
                    this._iv = undefined;
                }
                incCounter(counter);
                var keystream = counter.slice(0);
                cipher.encryptBlock(keystream, 0);
                for (var i = 0; i < blockSize; i++) {
                    words[offset + i] ^= keystream[i];
                }
            }
        });
        CTRGladman.Decryptor = Encryptor;
        return CTRGladman;
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var StreamCipher = C_lib.StreamCipher;
        var C_algo = C.algo;
        var S = [];
        var C_ = [];
        var G = [];
        var Rabbit = C_algo.Rabbit = StreamCipher.extend({
            _doReset: function () {
                var K = this._key.words;
                var iv = this.cfg.iv;
                for (var i = 0; i < 4; i++) {
                    K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00);
                }
                var X = this._X = [K[0], (K[3] << 16) | (K[2] >>> 16), K[1], (K[0] << 16) | (K[3] >>> 16), K[2], (K[1] << 16) | (K[0] >>> 16), K[3], (K[2] << 16) | (K[1] >>> 16)];
                var C = this._C = [(K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff)];
                this._b = 0;
                for (var i = 0; i < 4; i++) {
                    nextState.call(this);
                }
                for (var i = 0; i < 8; i++) {
                    C[i] ^= X[(i + 4) & 7];
                }
                if (iv) {
                    var IV = iv.words;
                    var IV_0 = IV[0];
                    var IV_1 = IV[1];
                    var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00);
                    var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00);
                    var i1 = (i0 >>> 16) | (i2 & 0xffff0000);
                    var i3 = (i2 << 16) | (i0 & 0x0000ffff);
                    C[0] ^= i0;
                    C[1] ^= i1;
                    C[2] ^= i2;
                    C[3] ^= i3;
                    C[4] ^= i0;
                    C[5] ^= i1;
                    C[6] ^= i2;
                    C[7] ^= i3;
                    for (var i = 0; i < 4; i++) {
                        nextState.call(this);
                    }
                }
            },
            _doProcessBlock: function (M, offset) {
                var X = this._X;
                nextState.call(this);
                S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16);
                S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16);
                S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16);
                S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16);
                for (var i = 0; i < 4; i++) {
                    S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00);
                    M[offset + i] ^= S[i];
                }
            },
            blockSize: 128 / 32,
            ivSize: 64 / 32
        });
        function nextState() {
            var X = this._X;
            var C = this._C;
            for (var i = 0; i < 8; i++) {
                C_[i] = C[i];
            }
            C[0] = (C[0] + 0x4d34d34d + this._b) | 0;
            C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0;
            C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0;
            C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0;
            C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0;
            C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0;
            C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0;
            C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0;
            this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0;
            for (var i = 0; i < 8; i++) {
                var gx = X[i] + C[i];
                var ga = gx & 0xffff;
                var gb = gx >>> 16;
                var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb;
                var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0);
                G[i] = gh ^ gl;
            }
            X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0;
            X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0;
            X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0;
            X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0;
            X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0;
            X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0;
            X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0;
            X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0;
        }
        C.Rabbit = StreamCipher._createHelper(Rabbit);
    }
    ());
    CryptoJS.mode.CTR = (function () {
        var CTR = CryptoJS.lib.BlockCipherMode.extend();
        var Encryptor = CTR.Encryptor = CTR.extend({
            processBlock: function (words, offset) {
                var cipher = this._cipher
                var blockSize = cipher.blockSize;
                var iv = this._iv;
                var counter = this._counter;
                if (iv) {
                    counter = this._counter = iv.slice(0);
                    this._iv = undefined;
                }
                var keystream = counter.slice(0);
                cipher.encryptBlock(keystream, 0);
                counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0
                for (var i = 0; i < blockSize; i++) {
                    words[offset + i] ^= keystream[i];
                }
            }
        });
        CTR.Decryptor = Encryptor;
        return CTR;
    }
    ());
    (function () {
        var C = CryptoJS;
        var C_lib = C.lib;
        var StreamCipher = C_lib.StreamCipher;
        var C_algo = C.algo;
        var S = [];
        var C_ = [];
        var G = [];
        var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({
            _doReset: function () {
                var K = this._key.words;
                var iv = this.cfg.iv;
                var X = this._X = [K[0], (K[3] << 16) | (K[2] >>> 16), K[1], (K[0] << 16) | (K[3] >>> 16), K[2], (K[1] << 16) | (K[0] >>> 16), K[3], (K[2] << 16) | (K[1] >>> 16)];
                var C = this._C = [(K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff)];
                this._b = 0;
                for (var i = 0; i < 4; i++) {
                    nextState.call(this);
                }
                for (var i = 0; i < 8; i++) {
                    C[i] ^= X[(i + 4) & 7];
                }
                if (iv) {
                    var IV = iv.words;
                    var IV_0 = IV[0];
                    var IV_1 = IV[1];
                    var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00);
                    var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00);
                    var i1 = (i0 >>> 16) | (i2 & 0xffff0000);
                    var i3 = (i2 << 16) | (i0 & 0x0000ffff);
                    C[0] ^= i0;
                    C[1] ^= i1;
                    C[2] ^= i2;
                    C[3] ^= i3;
                    C[4] ^= i0;
                    C[5] ^= i1;
                    C[6] ^= i2;
                    C[7] ^= i3;
                    for (var i = 0; i < 4; i++) {
                        nextState.call(this);
                    }
                }
            },
            _doProcessBlock: function (M, offset) {
                var X = this._X;
                nextState.call(this);
                S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16);
                S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16);
                S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16);
                S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16);
                for (var i = 0; i < 4; i++) {
                    S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00);
                    M[offset + i] ^= S[i];
                }
            },
            blockSize: 128 / 32,
            ivSize: 64 / 32
        });
        function nextState() {
            var X = this._X;
            var C = this._C;
            for (var i = 0; i < 8; i++) {
                C_[i] = C[i];
            }
            C[0] = (C[0] + 0x4d34d34d + this._b) | 0;
            C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0;
            C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0;
            C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0;
            C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0;
            C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0;
            C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0;
            C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0;
            this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0;
            for (var i = 0; i < 8; i++) {
                var gx = X[i] + C[i];
                var ga = gx & 0xffff;
                var gb = gx >>> 16;
                var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb;
                var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0);
                G[i] = gh ^ gl;
            }
            X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0;
            X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0;
            X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0;
            X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0;
            X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0;
            X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0;
            X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0;
            X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0;
        }
        C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy);
    }
    ());
    CryptoJS.pad.ZeroPadding = {
        pad: function (data, blockSize) {
            var blockSizeBytes = blockSize * 4;
            data.clamp();
            data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes);
        },
        unpad: function (data) {
            var dataWords = data.words;
            var i = data.sigBytes - 1;
            while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) {
                i--;
            }
            data.sigBytes = i + 1;
        }
    };
    return CryptoJS;
}));
var CryptoJS = module.exports;


//console.log(CryptoJS.MD5("xiaojianbangsaltstr") + '');
//console.log(CryptoJS.SHA256("xiaojianbangsaltstr").toString());
//console.log(CryptoJS.HmacSHA1("xiaojianbangsaltstr", "12345678").toString());

// var hasher = CryptoJS.algo.SHA256.create();
// hasher.update('xiao');
// hasher.reset();
// hasher.update('xiao');
// hasher.update('jianbang');
// var hash = hasher.finalize('saltstr');
// console.log(hash + '');

// var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA1, "12345678");
// hmacHasher.reset();
// hmacHasher.update('xiaojianbangsaltstr');
// var hmac = hmacHasher.finalize();
// console.log(hmac + '');

// console.log(CryptoJS.enc.Utf8.parse("xiaojianbang"));
// console.log(CryptoJS.enc.Hex.parse("7869616f6a69616e62616e67"));
// console.log(CryptoJS.enc.Base64.parse("eGlhb2ppYW5iYW5n"));

// console.log(CryptoJS.SHA256("xiaojianbangsaltstr") + '');
// console.log(CryptoJS.SHA256("xiaojianbangsaltstr").toString());
// console.log(CryptoJS.SHA256("xiaojianbangsaltstr").toString(CryptoJS.enc.Base64));
//
// var wordArray = CryptoJS.SHA256("xiaojianbangsaltstr");
// // console.log(CryptoJS.enc.Utf8.stringify(wordArray));
// console.log(CryptoJS.enc.Hex.stringify(wordArray));
// console.log(CryptoJS.enc.Base64.stringify(wordArray));

// var HexStr = "3078fff92edf23cd79b3ebff7fb9a5b28daefee9ee3aa5d532b7b4bc7423887e";
// var HexBytes = CryptoJS.enc.Hex.parse(HexStr);
// console.log(CryptoJS.enc.Base64.stringify(HexBytes));

// var md5Str = "xiaojianbangsaltstr";
// console.log(CryptoJS.MD5(md5Str).toString());
//
// var md5Str = "7869616f6a69616e62616e6773616c74737472";
// var HexBytes = CryptoJS.enc.Hex.parse(md5Str);
// console.log(CryptoJS.MD5(HexBytes).toString());

// var plainTextBytes = CryptoJS.enc.Utf8.parse("xiaojianxiaojia");
// var HexKeyBytes = CryptoJS.enc.Hex.parse("0102030405060708");
// var Utf8IVBytes = CryptoJS.enc.Utf8.parse("12345678");
// var cfg = {
//     iv: Utf8IVBytes,
//     // mode: CryptoJS.mode.CBC,
//     // padding: CryptoJS.pad.Pkcs7,
//     format: CryptoJS.format.Hex
// };
// var ciphertextObj = CryptoJS.DES.encrypt(plainTextBytes, HexKeyBytes, cfg);
// console.log(ciphertextObj);
// console.log(ciphertextObj.key + '');
// console.log(ciphertextObj.iv + '');
// console.log(ciphertextObj + '');
// console.log(ciphertextObj.ciphertext + '');
// console.log(ciphertextObj.toString(CryptoJS.format.Hex));

// var plainTextObj = CryptoJS.DES.decrypt(ciphertext, HexKeyBytes, cfg);
// console.log(plainTextObj.toString(CryptoJS.enc.Utf8));


// var plainTextBytes = CryptoJS.enc.Utf8.parse("xiaojianxiaojia");
// var HexKeyBytes = CryptoJS.enc.Hex.parse("01020304050607080102030405060708");
// var Utf8IVBytes = CryptoJS.enc.Utf8.parse("1234567812345678");
// var cfg = {
//     iv: Utf8IVBytes,
//     mode: CryptoJS.mode.CBC,
//     padding: CryptoJS.pad.Pkcs7,
//     format: {
//         stringify: function (data){
//             let e = {
//                 ct: data.ciphertext.toString(),
//                 miaoshu: "这是我们的自定义输出内容"
//             };
//             return JSON.stringify(e)
//         },
//         parse: function (data){
//             let json = JSON.parse(data);
//             let newVar = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse(json.ct)});
//             return newVar
//         }
//     }
// };
// var ciphertextObj = CryptoJS.AES.encrypt(plainTextBytes, HexKeyBytes, cfg);
// var ciphertext = ciphertextObj.toString();
// console.log(ciphertext);
//
// var plainTextObj = CryptoJS.AES.decrypt(ciphertext, HexKeyBytes, cfg);
// console.log(plainTextObj.toString(CryptoJS.enc.Utf8));


// let ripemd160 = CryptoJS.RIPEMD160("xiaojianbang");
// console.log(ripemd160 + '');
// let hmacRIPEMD160 = CryptoJS.HmacRIPEMD160("xiaojianbang", "12345678");
// console.log(hmacRIPEMD160 + '');
//
// let pbkdf2 = CryptoJS.PBKDF2("xiaojianbang", "saltstr", {keySize: 8, iterations: 10000});
// console.log(pbkdf2 + '');
//
// let evpKDF = CryptoJS.EvpKDF("xiaojianbang", "saltstr", {keySize: 8, iterations: 10000});
// console.log(evpKDF + '');

摘要算法使用:

CryptoJS 中消息摘要算法的使用
CryptoJS.MD5(message);
CryptoJS.HmacMD5(message, key);
CryptoJS.SHA1(message);
CryptoJS.HmacSHA1(message, key);
CryptoJS.SHA256(message);
CryptoJS.HmacSHA256(message, key);
CryptoJS.SHA512(message);
CryptoJS.HmacSHA512(message, key);
//CryptoJS.SHA3('xiaojianbang', {outputLength: 256})
SHA256
var hasher = CryptoJS.algo.SHA256.create();
hasher.reset();
hasher.update('message');
hasher.update(wordArray);
var hash = hasher.finalize();
var hash = hasher.finalize('message');
var hash = hasher.finalize(wordArray);
HmacSHA256
var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key);
hmacHasher.reset();
hmacHasher.update('message');
hmacHasher.update(wordArray);
var hmac = hmacHasher.finalize();
var hmac = hmacHasher.finalize('message');
var hmac = hmacHasher.finalize(wordArray);
-----------------------------------------------------------------------------------------------------
字符串解析
1. string 转 wordArray
CryptoJS.enc.Utf8.parse(utf8String);
CryptoJS.enc.Hex.parse(hexString);
CryptoJS.enc.Base64.parse(base64String);
2. wordArray 转 string
wordArray + '';
wordArray.toString();
wordArray.toString(CryptoJS.enc.Utf8);
wordArray.toString(CryptoJS.enc.Hex);
wordArray.toString(CryptoJS.enc.Base64);
CryptoJS.enc.Utf8.stringify(wordArray);
CryptoJS.enc.Hex.stringify(wordArray);
CryptoJS.enc.Base64.stringify(wordArray);
-----------------------------------------------------------------------------------------------------
对称加密算法
CryptoJS 中对称加密算法的使用
var ciphertext = CryptoJS.DES.encrypt(message, key, cfg);
var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg);
var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg);
var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg);
var ciphertext = CryptoJS.AES.encrypt(message, key, cfg);
var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg);
var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg);
var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg);
2. cfg 的详细含义
var cfg = {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
format: CryptoJS.format.Hex
};
-----------------------------------------------------------------------------------------------------
    CryptoJS( 对称加密算法 )
3. cfg 中没有传 mode 和 padding ,默认使用 CBC 的加密模式, Pkcs7 的填充方式
4. 加密结果是 wordArray 对象,调用 toString 默认转 Base64 编码的密文,转 hex 可以使用
var hexString = wordArray.ciphertext.toString();
5. CryptoJS 中提供的加密模式
CBC ECB CFB OFB CTRGladman CTR
6. CryptoJS 中提供的填充方式
NoPadding ZeroPadding Pkcs7(Pkcs5) Iso10126 Iso97971 AnsiX923
7. 对称加密算法的解密
8. 密文 / 明文的自定义输出 / 输入 (cfg 中 format 的指定 )
format: {
stringify: function (data){
let e = {
ct: data.ciphertext.toString(),
miaoshu: " 这是我们的自定义输出内容 "
};
return JSON.stringify(e)
},
parse: function (data){
let json = JSON.parse(data);
let newVar = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Hex.parse(json.ct)});
return newVar
}
}

利用Findhash结合得到的so层自吐算法

function monitor_constants(targetSo) {
    let const_array = [];
    let const_name = [];
    let const_addr = [['padding used in hashing algorithms (0x80 0 ... 0)', '0x6000']];

    for (var i = 0; i < const_addr.length; i++) {
        const_array.push({base:targetSo.add(const_addr[i][1]),size:0x1});
        const_name.push(const_addr[i][0]);
    }

    MemoryAccessMonitor.enable(const_array, {
        onAccess: function (details) {
            console.log("\n");
            console.log("监控到疑似加密常量的内存访问\n");
            console.log(const_name[details.rangeIndex]);
            console.log("访问来自:"+details.from.sub(targetSo)+"(可能有误差)");
    }
});
}

function hook_suspected_function(targetSo) {
    const funcs = [['MD5Init(MD5_CTX *)', '函数MD5Init(MD5_CTX *)疑似哈希函数,包含初始化魔数的代码。', '0x2a1c'], ['MD5Transform(uint *,uchar *)', '函数MD5Transform(uint *,uchar *)疑似哈希函数运算部分。', '0x2d04']];
    for (var i in funcs) {
        let relativePtr = funcs[i][2];
        let funcPtr = targetSo.add(relativePtr);
        let describe = funcs[i][1];
        let handler = (function() {
        return function(args) {
            console.log("\n");
            console.log(describe);
            console.log(Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n"));
        };//Thread.backtrace(this.context, Backtracer.ACCURATE):获取调用栈,this.context 包含当前寄存器状态,Backtracer.ACCURATE 表示精确模式。
        })();//DebugSymbol.fromAddress:将每个地址解析成符号信息(例如函数名或文件偏移)
    hook_native_addr(funcPtr);
}
}


function main() {
    var targetSo = Module.findBaseAddress('libxiaojianbang.so');
    // 对疑似哈希算法常量的地址进行监控,使用frida MemoryAccessMonitor API,有几个缺陷,在这里比较鸡肋。
    // 1.只监控第一次访问,所以如果此区域被多次访问,后续访问无法获取。可以根据这篇文章做改良和扩展。https://bbs.pediy.com/thread-262104-1.htm
    // 2.ARM 64无法使用
    // 3.无法查看调用栈
    // 在这儿用于验证这些常量是否被访问,访问了就说明可能使用该哈希算法。
    // MemoryAccessMonitor在别处可能有较大用处,比如ollvm过的so,或者ida xref失效/过多等情况。
    // hook和monitor这两个函数,只能分别注入和测试,两个同时会出错,这可能涉及到frida inline hook的原理
    // 除非hook_suspected_function 没结果,否则不建议使用monitor_constants。
    // monitor_constants(targetSo);

    hook_suspected_function(targetSo);
}

setImmediate(main);
    
	
function print_arg(addr){
	var module = Process.findRangeByAddress(addr);
	if(module != null){
		return hexdump(addr) + "\n";
	}else{
		return ptr(addr) + "\n";
	}
}

function hook_native_addr(funcPtr){
	var module = Process.findModuleByAddress(funcPtr);
	Interceptor.attach(funcPtr, {
		onEnter: function(args){
			this.args0 = args[0];
			this.args1 = args[1];
			this.args2 = args[2];
			this.args3 = args[3];
			this.logs = [];
			this.logs.push("call " + module.name + "!" + ptr(funcPtr).sub(module.base) + "\n");
			this.logs.push("this.args0 onEnter: " + print_arg(this.args0));
			this.logs.push("this.args1 onEnter: " + print_arg(this.args1));
			this.logs.push("this.args2 onEnter: " + print_arg(this.args2));
			this.logs.push("this.args3 onEnter: " + print_arg(this.args3));
			
		}, onLeave: function(retval){
			this.logs.push("this.args0 onLeave: " + print_arg(this.args0));
			this.logs.push("this.args1 onLeave: " + print_arg(this.args1));
			this.logs.push("this.args2 onLeave: " + print_arg(this.args2));
			this.logs.push("this.args3 onLeave: " + print_arg(this.args3));
			this.logs.push("retval onLeave: " + retval + "\n");
			console.log(this.logs);
		}
	});
}

结合findhash和traceNatives插件合成的升级自吐算法:

由于我们findhash的js代码需要hook的位置是对应的函数偏移的位置offset的位置,所以我们可以先使用traceNatives先获取函数偏移的位置,再通过修改findhash生成的frida的hook代码实现我们的自吐算法的合成:

修改traceNatives代码:

        so_path, so_name = getSoPathAndName()
        search_result = [f"'{offset}'" for offset in search_result]
        search_result = ",".join(search_result)
        search_result = '['+search_result+']'
        script_name = so_name.split(".")[0] + "_" + str(int(time.time())) +".txt"
        save_path = os.path.join(so_path, script_name)

这里原本是初始的 -a 然后是名字 以及对应的偏移地址,由于我们需要的是偏移地址的数组,所以直接进行修改了

['0x152c','0x16c4','0x16fc','0x1734','0x177c','0x187c','0x18b4','0x18f4','0x193c','0x1974','0x19a8','0x1b60','0x1c5c','0x1e04','0x1ecc','0x1f14','0x1fb0','0x1ff0','0x21c4','0x2380','0x25ac','0x25ec','0x262c','0x2854','0x2a1c','0x2a8c','0x2d04','0x4264','0x4360','0x449c']

得到的数组就是这样的,然后我们直接去修改findhash的代码

修改findhash的frida的hook代码:

function hook_suspected_function(targetSo) {
    const funcs = ['0x152c','0x16c4','0x16fc','0x1734','0x177c','0x187c','0x18b4','0x18f4','0x193c','0x1974','0x19a8','0x1b60','0x1c5c','0x1e04','0x1ecc','0x1f14','0x1fb0','0x1ff0','0x21c4','0x2380','0x25ac','0x25ec','0x262c','0x2854','0x2a1c','0x2a8c','0x2d04','0x4264','0x4360','0x449c'];
    for (var i in funcs) {
        let relativePtr = funcs[i];
        let funcPtr = targetSo.add(relativePtr);
    hook_native_addr(funcPtr);
}
function hook_native_addr(funcPtr){
	var module = Process.findModuleByAddress(funcPtr);
	Interceptor.attach(funcPtr, {
		onEnter: function(args){
			this.args0 = args[0];
			this.args1 = args[1];
			this.args2 = args[2];
			this.args3 = args[3];
			this.logs = [];
			this.logs.push("call " + module.name + "!" + ptr(funcPtr).sub(module.base) + "\n");
			this.logs.push("this.args0 onEnter: " + print_arg(this.args0));
			this.logs.push("this.args1 onEnter: " + print_arg(this.args1));
			this.logs.push("this.args2 onEnter: " + print_arg(this.args2));
			this.logs.push("this.args3 onEnter: " + print_arg(this.args3));
			
		}, onLeave: function(retval){
			this.logs.push("this.args0 onLeave: " + print_arg(this.args0));
			this.logs.push("this.args1 onLeave: " + print_arg(this.args1));
			this.logs.push("this.args2 onLeave: " + print_arg(this.args2));
			this.logs.push("this.args3 onLeave: " + print_arg(this.args3));
			this.logs.push("retval onLeave: " + retval + "\n");
			console.log(this.logs);
		}
	});
}

hook_native_addr(funcPtr)是我们自定义的函数,为的就是输出对应的参数和函数返回之后的参数

枚举导出表和导入表

先了解一下JSON.stringify()方法,之前一直在使用,但是不了解

JSON.stringify() JavaScript 中的一个方法,用于将 JavaScript 对象或数组转换为 JSON 字符串。它常用于将数据序列化为 JSON 格式,以便传输或存储。

JSON 字符串的特点:

  • 结构化数据:JSON字符串通常用来表示结构化的数据,比如对象、数组等。
  • 字符串格式:它是一个字符串,符合特定的 JSON 语法规范。
  • 通用性:虽然 JSON 起源于 JavaScript,但它已经被广泛应用于几乎所有编程语言中,是网络数据传输的常见格式。
{
  "name": "Alice",
  "age": 25,
  "isStudent": false,
  "skills": ["JavaScript", "Python"],
  "address": {
    "city": "New York",
    "zip": "10001"
  }
}

导出表和导入表的枚举:

    var Imports= Module.enumerateImports("libencryptlib.so");
    for(var i=0; i<Imports.length; i++)
    {
        console.log(JSON.stringify(Imports[i]));//导入表
    }
    console.log("=================================================================================");
    var Exports= Module.enumerateExports("libencryptlib.so");
    for(var i=0; i<Exports.length; i++)
    {
        console.log(JSON.stringify(Exports[i]));//导出表
    }
    console.log("================================================================================");
    var Symbols =  Module.enumerateSymbols("libencryptlib.so");
    console.log(JSON.stringify(Symbols[0]));//符号表

这里其实可能导出表和符号表出现的信息会有重叠

原因其实是导出表查看的是——>DYNSYM(Dynamic Symbol Table(动态符号表)

符号表查看的是——>SYMTAB(Symbol Table(符号表))

SO层HOOK函数

    var MakePassMD5 = Module.findExportByName("libencryptlib.so","_ZN7MD5_CTX11MakePassMD5EPhjS0_");
    Interceptor.attach(MakePassMD5,{
        onEnter:function(args)
        {
            var InputChars =  args[1];
            var InputCharsLen = args[2];
            var OutputChars =  args[3];
            console.log(" InPut: ",hexdump(InputChars));
            console.log(InputCharsLen);
            console.log(" OtPut: ",hexdump(OutputChars));
            this.OutputChars_addr = OutputChars;
        },
        onLeave:function(result){
            console.log("result: ",hexdump(this.OutputChars_addr));
        }
    });
Interceptor.attach(function_addr,{
    onEnter:function(args){
    
    }
    onLeave:function(result){
           
    }
}

so库函数基地址获取以及通过偏移hook函数:

假如我们的函数没有出现在导出表和导入表之中,我们要HOOK函数就只能通过利用so的基地址+函数在so中的偏移地址[+1]来实现,得到对应函数的地址

    var Module2 =  Process.findModuleByName("libencryptlib.so")
    console.log("findModuleByName: ",Module2.base);
    var Module1 = Process.getModuleByName("libencryptlib.so")
    console.log("getModuleByName:",Module1.base);
    var module =  Process.enumerateModules();
    for(var i=0; i<module.length; i++)
    {
        if(module[i].name=="libencryptlib.so")
        {
            console.log(JSON.stringify(module[i]));
            console.log(module[i].base);
            break;
        }
    }
    var base =  Module.findBaseAddress("libencryptlib.so")
    console.log("findBaseAddress: ",base);

我们可以在ida中查看相应的函数的偏移地址,利用so基地址+偏移地址就可以实现hoo

函数地址的计算

如果是 thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 + 1
如果是 arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移
在安卓中, 32 位的 so 中的函数,基本都是 thumb 指令
在安卓中, 64 位的 so 中的函数,基本都是 arm 指令

也可以通过显示汇编指令对应的 opcode bytes ,来判断

options -> general -> Number of opcode bytes (non-graph) 4
arm 指令为 4 个字节,如果函数中有些指令是两个字节,那么函数地址计算需要 + 1

var soAddr = Module.findBaseAddress("libxiaojianbang.so");
var funcAddr = soAddr.add(0x23F4); // 函数地址计算 thumb+1 ARM 不加

    var baseaddr = Module.findBaseAddress("libencryptlib.so").add(0x1FA38);
    Interceptor.attach(baseaddr,{
        onEnter:function(args)
        {
            var InputChars =  args[1];
            var InputCharsLen = args[2];
            var OutputChars =  args[3];
            console.log(" InPut: ",hexdump(InputChars));
            console.log(InputCharsLen);
            console.log(" OtPut: ",hexdump(OutputChars));
            this.OutputChars_addr = OutputChars;
        },
        onLeave:function(result){
            console.log("result: ",hexdump(this.OutputChars_addr));
        }

so层地址HOOK格式:

Java.perform(function() {
    function print_args(args) {
        if (args.isNull()) {
            return "null";
        }
        if (args instanceof NativePointer) {
            var module = Process.findRangeByAddress(args);
            if (module != null) {
                return hexdump(args) + "\n";
            }
        }
        return ptr(args)+"\n";
    }
    
    function hook_native_add(funPtr, paramNum) {
        var Module1 = Process.findModuleByAddress(funPtr);
        if (Module1 === null) {
            console.log("Module not found for function pointer");
            return;
        }
        Interceptor.attach(funPtr, {
            onEnter: function(args) {
                this.logs = [];
                this.param = [];
                for (let i = 0; i < paramNum; i++) {
                    this.param.push(args[i]);
                }
                this.logs.push("call " + Module1.name + "!" + ptr(funPtr).sub(Module1.base) + "\n");
                for (let i = 0; i < paramNum; i++) {
                    this.logs.push("this.args" + i + " onEnter: " + print_args(this.param[i]) + "\n");
                }
            },
            onLeave: function(retval) {
                for (let i = 0; i < paramNum; i++) {
                    this.logs.push("this.args" + i + " onLeave: " + print_args(this.param[i]) + "\n");
                }
                this.logs.push("retval onLeave: " + retval + "\n");
                console.log(this.logs.join("\n"));
                this.logs = [];
            }
        });
    }
    var moduleBase = Module.findBaseAddress("libencryptlib.so");
    if (moduleBase !== null) {
        var baseaddr = moduleBase.add(0x1FA38);
        hook_native_add(baseaddr, 4);
    } else {
        console.log("Module not found: libencryptlib.so");
    }
})

Frida 框架来绕过 Android SSL 钉扎(SSL pinning)机制

创建自定义的 TrustManager:实现一个新的 TrustManager,所有 SSL 证书都被接受,从而绕过 SSL 验证。

  • checkClientTrustedcheckServerTrusted 方法被重写以不执行任何检查。

修改 SSLContext 的初始化:重载 SSLContext 的 init() 方法,使用自定义的 TrustManager 进行 SSL 连接。

拦截 OkHttp:针对 OkHttp 库,重载相关方法(如 CertificatePinner.check()hostnameVerifier),以跳过 SSL 钉扎检查。

处理 WebView:重载 WebViewClient 中的 SSL 错误处理方法,允许所有 SSL 错误,直接调用 sslErrorHandler.proceed()

这里是直接对应需要检测的证书,SSLpinning全部去重载了对应的函数,让其检测什么也不做,或者是定义一些变量,直接传入对应的函数,什么也不做,取消对应的功能

Java.perform(function() {

/*
hook list:
1.SSLcontext
2.okhttp
3.webview
4.XUtils
5.httpclientandroidlib
6.JSSE
7.network\_security\_config (android 7.0+)
8.Apache Http client (support partly)
9.OpenSSLSocketImpl
10.TrustKit
11.Cronet
*/
	// Attempts to bypass SSL pinning implementations in a number of
	// ways. These include implementing a new TrustManager that will
	// accept any SSL certificate, overriding OkHTTP v3 check()
	// method etc.
	var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
	var HostnameVerifier = Java.use('javax.net.ssl.HostnameVerifier');
	var SSLContext = Java.use('javax.net.ssl.SSLContext');
	var quiet_output = false;

	// Helper method to honor the quiet flag.

	function quiet_send(data) {

		if (quiet_output) {

			return;
		}

		send(data)
	}


	// Implement a new TrustManager
	// ref: https://gist.github.com/oleavr/3ca67a173ff7d207c6b8c3b0ca65a9d8
	// Java.registerClass() is only supported on ART for now(201803). 所以android 4.4以下不兼容,4.4要切换成ART使用.
	/*
06-07 16:15:38.541 27021-27073/mi.sslpinningdemo W/System.err: java.lang.IllegalArgumentException: Required method checkServerTrusted(X509Certificate[], String, String, String) missing
06-07 16:15:38.542 27021-27073/mi.sslpinningdemo W/System.err:     at android.net.http.X509TrustManagerExtensions.<init>(X509TrustManagerExtensions.java:73)
        at mi.ssl.MiPinningTrustManger.<init>(MiPinningTrustManger.java:61)
06-07 16:15:38.543 27021-27073/mi.sslpinningdemo W/System.err:     at mi.sslpinningdemo.OkHttpUtil.getSecPinningClient(OkHttpUtil.java:112)
        at mi.sslpinningdemo.OkHttpUtil.get(OkHttpUtil.java:62)
        at mi.sslpinningdemo.MainActivity$1$1.run(MainActivity.java:36)
*/
	var X509Certificate = Java.use("java.security.cert.X509Certificate");
	var TrustManager;
	try {
		TrustManager = Java.registerClass({//创建自定义的 TrustManager
			name: 'org.wooyun.TrustManager',
			implements: [X509TrustManager],
			methods: {
				checkClientTrusted: function(chain, authType) {},//checkClientTrusted 和 checkServerTrusted 方法被重写以不执行任何检查。
				checkServerTrusted: function(chain, authType) {},
				getAcceptedIssuers: function() {
					// var certs = [X509Certificate.$new()];
					// return certs;
					return [];
				}
			}
		});
	} catch (e) {
		quiet_send("registerClass from X509TrustManager >>>>>>>> " + e.message);
	}

	// Prepare the TrustManagers array to pass to SSLContext.init()
	var TrustManagers = [TrustManager.$new()];

	try {
		// Prepare a Empty SSLFactory修改 SSLContext 的初始化,载入新修改的TrustManagers
		var TLS_SSLContext = SSLContext.getInstance("TLS");
		TLS_SSLContext.init(null, TrustManagers, null);
		var EmptySSLFactory = TLS_SSLContext.getSocketFactory();
	} catch (e) {
		quiet_send(e.message);
	}

	send('Custom, Empty TrustManager ready');

	// Get a handle on the init() on the SSLContext class
	var SSLContext_init = SSLContext.init.overload(
		'[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom');

	// Override the init method, specifying our new TrustManager
	SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {

		quiet_send('Overriding SSLContext.init() with the custom TrustManager');

		SSLContext_init.call(this, null, TrustManagers, null);
	};

	/*** okhttp3.x unpinning ***/


	// Wrap the logic in a try/catch as not all applications will have
	// okhttp as part of the app.
	try {

		var CertificatePinner = Java.use('okhttp3.CertificatePinner');
		quiet_send('OkHTTP 3.x Found');
		CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function() {
			quiet_send('OkHTTP 3.x check() called. Not throwing an exception.');
		}

		var OkHttpClient$Builder = Java.use('okhttp3.OkHttpClient$Builder');
		quiet_send('OkHttpClient$Builder Found');
		console.log("hostnameVerifier", OkHttpClient$Builder.hostnameVerifier);
		OkHttpClient$Builder.hostnameVerifier.implementation = function () {
			quiet_send('OkHttpClient$Builder hostnameVerifier() called. Not throwing an exception.');
			return this;
		}

		var myHostnameVerifier = Java.registerClass({
			name: 'com.xiaojianbang.MyHostnameVerifier',
			implements: [HostnameVerifier],
			methods: {
				verify: function (hostname, session) {
					return true;
				}
			}
		});

		var OkHttpClient = Java.use('okhttp3.OkHttpClient');
		OkHttpClient.hostnameVerifier.implementation = function () {
			quiet_send('OkHttpClient hostnameVerifier() called. Not throwing an exception.');
			return myHostnameVerifier.$new();
		}

	} catch (err) {

		// If we dont have a ClassNotFoundException exception, raise the
		// problem encountered.
		if (err.message.indexOf('ClassNotFoundException') === 0) {

			throw new Error(err);
		}
	}

	// Appcelerator Titanium PinningTrustManager

	// Wrap the logic in a try/catch as not all applications will have
	// appcelerator as part of the app.
	try {

		var PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager');

		send('Appcelerator Titanium Found');

		PinningTrustManager.checkServerTrusted.implementation = function() {

			quiet_send('Appcelerator checkServerTrusted() called. Not throwing an exception.');
		}

	} catch (err) {

		// If we dont have a ClassNotFoundException exception, raise the
		// problem encountered.
		if (err.message.indexOf('ClassNotFoundException') === 0) {

			throw new Error(err);
		}
	}

	/*** okhttp unpinning ***/


	try {
		var OkHttpClient = Java.use("com.squareup.okhttp.OkHttpClient");
		OkHttpClient.setCertificatePinner.implementation = function(certificatePinner) {
			// do nothing
			quiet_send("OkHttpClient.setCertificatePinner Called!");
			return this;
		};

		// Invalidate the certificate pinnet checks (if "setCertificatePinner" was called before the previous invalidation)
		var CertificatePinner = Java.use("com.squareup.okhttp.CertificatePinner");
		CertificatePinner.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function(p0, p1) {
			// do nothing
			quiet_send("okhttp Called! [Certificate]");
			return;
		};
		CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(p0, p1) {
			// do nothing
			quiet_send("okhttp Called! [List]");
			return;
		};
	} catch (e) {
		quiet_send("com.squareup.okhttp not found");
	}

	/*** WebView Hooks ***/

	/* frameworks/base/core/java/android/webkit/WebViewClient.java */
	/* public void onReceivedSslError(Webview, SslErrorHandler, SslError) */
	var WebViewClient = Java.use("android.webkit.WebViewClient");

	WebViewClient.onReceivedSslError.implementation = function(webView, sslErrorHandler, sslError) {
		quiet_send("WebViewClient onReceivedSslError invoke");
		//执行proceed方法
		sslErrorHandler.proceed();
		return;
	};

	WebViewClient.onReceivedError.overload('android.webkit.WebView', 'int', 'java.lang.String', 'java.lang.String').implementation = function(a, b, c, d) {
		quiet_send("WebViewClient onReceivedError invoked");
		return;
	};

	WebViewClient.onReceivedError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function() {
		quiet_send("WebViewClient onReceivedError invoked");
		return;
	};

	/*** JSSE Hooks ***/

	/* libcore/luni/src/main/java/javax/net/ssl/TrustManagerFactory.java */
	/* public final TrustManager[] getTrustManager() */
	/* TrustManagerFactory.getTrustManagers maybe cause X509TrustManagerExtensions error  */
	// var TrustManagerFactory = Java.use("javax.net.ssl.TrustManagerFactory");
	// TrustManagerFactory.getTrustManagers.implementation = function(){
	//     quiet_send("TrustManagerFactory getTrustManagers invoked");
	//     return TrustManagers;
	// }

	var HttpsURLConnection = Java.use("com.android.okhttp.internal.huc.HttpsURLConnectionImpl");
	HttpsURLConnection.setSSLSocketFactory.implementation = function(SSLSocketFactory) {
		quiet_send("HttpsURLConnection.setSSLSocketFactory invoked");
	};
	HttpsURLConnection.setHostnameVerifier.implementation = function(hostnameVerifier) {
		quiet_send("HttpsURLConnection.setHostnameVerifier invoked");
	};

	/*** Xutils3.x hooks ***/
	//Implement a new HostnameVerifier
	var TrustHostnameVerifier;
	try {
		TrustHostnameVerifier = Java.registerClass({
			name: 'org.wooyun.TrustHostnameVerifier',
			implements: [HostnameVerifier],
			method: {
				verify: function(hostname, session) {
					return true;
				}
			}
		});

	} catch (e) {
		//java.lang.ClassNotFoundException: Didn't find class "org.wooyun.TrustHostnameVerifier"
		quiet_send("registerClass from hostnameVerifier >>>>>>>> " + e.message);
	}

	try {
		var RequestParams = Java.use('org.xutils.http.RequestParams');
		RequestParams.setSslSocketFactory.implementation = function(sslSocketFactory) {
			sslSocketFactory = EmptySSLFactory;
			return null;
		}

		RequestParams.setHostnameVerifier.implementation = function(hostnameVerifier) {
			hostnameVerifier = TrustHostnameVerifier.$new();
			return null;
		}

	} catch (e) {
		quiet_send("Xutils hooks not Found");
	}

	/*** httpclientandroidlib Hooks ***/
	try {
		var AbstractVerifier = Java.use("ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier");
		AbstractVerifier.verify.overload('java.lang.String', '[Ljava.lang.String', '[Ljava.lang.String', 'boolean').implementation = function() {
			quiet_send("httpclientandroidlib Hooks");
			return null;
		}
	} catch (e) {
		quiet_send("httpclientandroidlib Hooks not found");
	}

	/***
android 7.0+ network_security_config TrustManagerImpl hook
apache httpclient partly
***/
	var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
	// try {
	//     var Arrays = Java.use("java.util.Arrays");
	//     //apache http client pinning maybe baypass
	//     //https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#471
	//     TrustManagerImpl.checkTrusted.implementation = function (chain, authType, session, parameters, authType) {
	//         quiet_send("TrustManagerImpl checkTrusted called");
	//         //Generics currently result in java.lang.Object
	//         return Arrays.asList(chain);
	//     }
	//
	// } catch (e) {
	//     quiet_send("TrustManagerImpl checkTrusted nout found");
	// }

	try {
		// Android 7+ TrustManagerImpl
		TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
			quiet_send("TrustManagerImpl verifyChain called");
			// Skip all the logic and just return the chain again :P
			//https://www.nccgroup.trust/uk/about-us/newsroom-and-events/blogs/2017/november/bypassing-androids-network-security-configuration/
			// https://github.com/google/conscrypt/blob/c88f9f55a523f128f0e4dace76a34724bfa1e88c/platform/src/main/java/org/conscrypt/TrustManagerImpl.java#L650
			return untrustedChain;
		}
	} catch (e) {
		quiet_send("TrustManagerImpl verifyChain nout found below 7.0");
	}
	// OpenSSLSocketImpl
	try {
		var OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl');
		OpenSSLSocketImpl.verifyCertificateChain.implementation = function(certRefs, authMethod) {
			quiet_send('OpenSSLSocketImpl.verifyCertificateChain');
		}

		quiet_send('OpenSSLSocketImpl pinning')
	} catch (err) {
		quiet_send('OpenSSLSocketImpl pinner not found');
	}
	// Trustkit
	try {
		var Activity = Java.use("com.datatheorem.android.trustkit.pinning.OkHostnameVerifier");
		Activity.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function(str) {
			quiet_send('Trustkit.verify1: ' + str);
			return true;
		};
		Activity.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function(str) {
			quiet_send('Trustkit.verify2: ' + str);
			return true;
		};

		quiet_send('Trustkit pinning')
	} catch (err) {
		quiet_send('Trustkit pinner not found')
	}

	try {
		//cronet pinner hook
		//weibo don't invoke

		var netBuilder = Java.use("org.chromium.net.CronetEngine$Builder");

		//https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/CronetEngine.Builder.html#enablePublicKeyPinningBypassForLocalTrustAnchors(boolean)
		netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.implementation = function(arg) {

			//weibo not invoke
			console.log("Enables or disables public key pinning bypass for local trust anchors = " + arg);

			//true to enable the bypass, false to disable.
			var ret = netBuilder.enablePublicKeyPinningBypassForLocalTrustAnchors.call(this, true);
			return ret;
		};

		netBuilder.addPublicKeyPins.implementation = function(hostName, pinsSha256, includeSubdomains, expirationDate) {
			console.log("cronet addPublicKeyPins hostName = " + hostName);

			//var ret = netBuilder.addPublicKeyPins.call(this,hostName, pinsSha256,includeSubdomains, expirationDate);
			//this 是调用 addPublicKeyPins 前的对象吗? Yes,CronetEngine.Builder
			return this;
		};

	} catch (err) {
		console.log('[-] Cronet pinner not found')
	}
});

过Root检测

这里有两个Root检测的函数,

一个是在命令行中执行 which su 找到对应root权限的路径

new BufferedReader(new InputStreamReader(process.getInputStream())).readLine() 读取对应的可能得到的路径

假如能够读出来,就说明有root权限

   public static boolean checkSuFile() {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[]{"which", "su"});
            if (new BufferedReader(new InputStreamReader(process.getInputStream())).readLine() != null) {
                if (process != null) {
                    process.destroy();
                }
                return true;
            }
            if (process != null) {
                process.destroy();
            }
            return false;
        } catch (Throwable th) {
            if (process != null) {
                process.destroy();
            }
            throw th;
        }
    }

这里的ROOT检测就是直接去执行对应路径的命令,查看有权限

    public static File checkRootFile() {
        String[] strArr = {"/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su", "/system/bin/failsafe/su", "/data/local/su"};
        int length = strArr.length;
        File file = null;
        int i = 0;
        while (i < length) {
            File file2 = new File(strArr[i]);
            if (file2.exists()) {
                return file2;
            }
            i++;
            file = file2;
        }
        return file;
    }

绕过:

    var SystemUtils =  Java.use("com.hoge.android.factory.util.system.SystemUtils");
    SystemUtils.checkSuFile.implementation=function()
    {
        var result = this.checkSuFile();
        console.log(result);
        return false;
    }//第二个函数其实也是看第一个函数的返回值

JNI函数HOOK插件jnitrace

https://github.com/chame1eon/jnitrace

使用方法:

-R <host>:<port>- 用于指定远程 Frida 服务器的网络位置。如果未指定 :,则默认使用 localhost:27042。
-m <spawn|attach>- 用于指定要使用的 Frida 附加机制。它可以是 spawn 或 attachment。Spawn 是默认和推荐的选项。
-b <fuzzy|accurate|none>- 用于控制回溯输出。默认情况下,jnitrace将在模式下运行回溯器accurate。可以使用此选项更改为fuzzy模式或使用该none选项停止回溯。有关差异的说明,请参阅 Frida 文档。
-i <regex>- 用于指定应跟踪的方法名称。这有助于减少特别大型 JNI 应用中的噪音。该选项可以多次提供。例如,-i Get -i RegisterNatives将仅包含名称中包含 Get 或 RegisterNatives 的 JNI 方法。
-e <regex>- 用于指定应在跟踪中忽略的方法名称。这有助于减少特别大型 JNI 应用中的噪音。该选项可以多次提供。例如,-e ^Find -e GetEnv将从结果中排除所有以 Find 开头或包含 GetEnv 的 JNI 方法名称。
-I <string>- 用于指定应跟踪的库的导出。这对于您只想跟踪少量方法的库很有用。jnitrace 认为导出的函数是任何可从 Java 端直接调用的函数,例如,包括使用 RegisterNatives 绑定的方法。该选项可以多次提供。例如, -I stringFromJNI -I nativeMethod([B)V可用于包含调用的库的导出Java_com_nativetest_MainActivity_stringFromJNI和使用 RegisterNames 绑定的方法,签名为nativeMethod([B)V。
-E <string>用于指定不应跟踪的库的导出。这对于您想要忽略一组繁忙的本机调用的库很有用。jnitrace 认为导出的函数是任何可从 Java 端直接调用的函数,例如,包括使用 RegisterNatives 绑定的方法。该选项可以多次提供。例如,-E JNI_OnLoad -E nativeMethod将从跟踪中排除JNI_OnLoad函数调用和任何名为 的方法nativeMethod。
-o path/output.json- 用于指定jnitrace存储所有跟踪数据的输出路径。信息以 JSON 格式存储,以便以后对跟踪数据进行后处理。
-p path/to/script.js- 提供的路径用于在jnitrace脚本加载之前将 Frida 脚本加载到目标进程中。这可用于在jnitrace启动之前击败反 Frida 或反调试代码。
-a path/to/script.js- 提供的路径用于在jnitrace加载后将 Frida 脚本加载到目标进程中。
--hide-data- 用于减少控制台中显示的输出量。此选项将隐藏显示为十六进制转储或字符串反引用的其他数据。
--ignore-env- 使用此选项将隐藏应用程序使用 JNIEnv 结构进行的所有调用。
--ignore-vm- 使用此选项将隐藏应用程序使用 JavaVM 结构进行的所有调用。
--aux <name=(string|bool|int)value>- 用于在生成应用程序时传递自定义参数。例如,--aux='uid=(int)10'将为用户 10 而不是默认用户 0 生成应用程序。

最常使用的命令,按照附加的方式来进行:

jnitrace -m attach -l liblogin_encrypt.so com.ximalaya.ting.android -o .\out.json

这个插件是对于JNI函数的HOOK,在SO层的HOOK中,so文件有可能会被混淆,导致关键函数混淆不清,不过在SO层的加密终究需要的是JNI函数将C/C++转为Java的数据类型,这时候就需要JNI函数的使用,我们直接去HOOK了JNI函数,也可以了解到大概的执行流程。

部分JSON的输出:

[
    {
        "struct": "JNIEnv",
        "method": {
            "name": "NewStringUTF",
            "args": [
                "JNIEnv*",
                "char*"
            ],
            "ret": "jstring"
        },
        "thread_id": 17066,
        "timestamp": 1565,
        "backtrace": [
            {
                "address": "0xbd1260df",
                "module": {
                    "name": "liblogin_encrypt.so",
                    "base": "0xbd122000",
                    "size": 57344,
                    "path": "/data/app/com.ximalaya.ting.android-h0ODZtd-h4A7yO2l2eCojw==/lib/arm/liblogin_encrypt.so"
                },
                "symbol": {
                    "address": "0xbd1260df",
                    "name": "Java_com_ximalaya_ting_android_loginservice_LoginEncryptUtil_PDuxkguhSq+0x1a",
                    "moduleName": "liblogin_encrypt.so",
                    "fileName": "",
                    "lineNumber": 0
                }
            }
        ],
        "args": [
            {
                "value": "0xf3889140"
            },
            {
                "value": "0xbd12f870",
                "data": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVhaR3Or7suUlwHUl2Ly36uVmboZ3+HhovogDjLgRE9CbaUokS2eqGaVFfbxAUxFThNDuXq/fBD+SdUgppmcZrIw4HMMP4AtE2qJJQH/KxPWmbXH7Lv+9CisNtPYOlvWJ/GHRqf9x3TBKjjeJ2CjuVxlPBDX63+Ecil2JR9klVawIDAQAB"
            }
        ],
        "ret": {
            "value": "0x75"
        },
        "java_params": []
    },
    {
        "struct": "JNIEnv",
        "method": {
            "name": "NewStringUTF",
            "args": [
                "JNIEnv*",
                "char*"
            ],
            "ret": "jstring"
        },
        "thread_id": 17066,
        "timestamp": 1872,
        "backtrace": [
            {
                "address": "0xbd122b93",
                "module": {
                    "name": "liblogin_encrypt.so",
                    "base": "0xbd122000",
                    "size": 57344,
                    "path": "/data/app/com.ximalaya.ting.android-h0ODZtd-h4A7yO2l2eCojw==/lib/arm/liblogin_encrypt.so"
                },
                "symbol": {
                    "address": "0xbd122b93",
                    "name": "0xb93",
                    "moduleName": "liblogin_encrypt.so",
                    "fileName": "",
                    "lineNumber": 0
                }
            }
        ],
        "args": [
            {
                "value": "0xf3889140"
            },
            {
                "value": "0xbd12f055",
                "data": "RSA"
            }
        ],
        "ret": {
            "value": "0x89"
        },
        "java_params": []
    },
    {
        "struct": "JNIEnv",
        "method": {
            "name": "FindClass",
            "args": [
                "JNIEnv*",
                "char*"
            ],
            "ret": "jclass"
        },
        "thread_id": 17066,
        "timestamp": 1885,
        "backtrace": [
            {
                "address": "0xbd122ba5",
                "module": {
                    "name": "liblogin_encrypt.so",
                    "base": "0xbd122000",
                    "size": 57344,
                    "path": "/data/app/com.ximalaya.ting.android-h0ODZtd-h4A7yO2l2eCojw==/lib/arm/liblogin_encrypt.so"
                },
                "symbol": {
                    "address": "0xbd122ba5",
                    "name": "0xba5",
                    "moduleName": "liblogin_encrypt.so",
                    "fileName": "",
                    "lineNumber": 0
                }
            }
        ],
        "args": [
            {
                "value": "0xf3889140"
            },
            {
                "value": "0xbd12f060",
                "data": "java/security/KeyFactory"
            }
        ],
        "ret": {
            "value": "0x99",
            "metadata": "java/security/KeyFactory"
        },
        "java_params": []
    },

部分cmd输出:

           /* TID 17812 */
   1962 ms [+] JNIEnv->NewStringUTF
   1962 ms |- JNIEnv*          : 0xf3889140
   1962 ms |- char*            : 0xb61ea870
   1962 ms |:     MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVhaR3Or7suUlwHUl2Ly36uVmboZ3+HhovogDjLgRE9CbaUokS2eqGaVFfbxAUxFThNDuXq/fBD+SdUgppmcZrIw4HMMP4AtE2qJJQH/KxPWmbXH7Lv+9CisNtPYOlvWJ/GHRqf9x3TBKjjeJ2CjuVxlPBDX63+Ecil2JR9klVawIDAQAB
   1962 ms |= jstring          : 0x75

   1962 ms ----------------------------------------------------------Backtrace----------------------------------------------------------
   1962 ms |-> 0xb61e10df: Java_com_ximalaya_ting_android_loginservice_LoginEncryptUtil_PDuxkguhSq+0x1a (liblogin_encrypt.so:0xb61dd000)


           /* TID 17812 */
   2245 ms [+] JNIEnv->NewStringUTF
   2245 ms |- JNIEnv*          : 0xf3889140
   2245 ms |- char*            : 0xb61ea055
   2245 ms |:     RSA
   2245 ms |= jstring          : 0x89

   2245 ms ---------------------------------Backtrace---------------------------------
   2245 ms |-> 0xb61ddb93: liblogin_encrypt.so!0xb93 (liblogin_encrypt.so:0xb61dd000)


           /* TID 17812 */
   2254 ms [+] JNIEnv->FindClass
   2254 ms |- JNIEnv*          : 0xf3889140
   2254 ms |- char*            : 0xb61ea060
   2254 ms |:     java/security/KeyFactory
   2254 ms |= jclass           : 0x95    { java/security/KeyFactory }

   2254 ms ---------------------------------Backtrace---------------------------------
   2254 ms |-> 0xb61ddba5: liblogin_encrypt.so!0xba5 (liblogin_encrypt.so:0xb61dd000)


           /* TID 17812 */
   2263 ms [+] JNIEnv->GetStaticMethodID
   2263 ms |- JNIEnv*          : 0xf3889140
   2263 ms |- jclass           : 0x95    { java/security/KeyFactory }
   2263 ms |- char*            : 0xb61ea079
   2263 ms |:     getInstance
   2263 ms |- char*            : 0xb61ea090
   2263 ms |:     (Ljava/lang/String;)Ljava/security/KeyFactory;
   2263 ms |= jmethodID        : 0x7036fae8    { getInstance(Ljava/lang/String;)Ljava/security/KeyFactory; }

   2263 ms ---------------------------------Backtrace---------------------------------
   2263 ms |-> 0xb61ddbc1: liblogin_encrypt.so!0xbc1 (liblogin_encrypt.so:0xb61dd000)


           /* TID 17812 */
   2271 ms [+] JNIEnv->CallStaticObjectMethod
   2271 ms |- JNIEnv*          : 0xf3889140
   2271 ms |- jclass           : 0x95    { java/security/KeyFactory }
   2271 ms |- jmethodID        : 0x7036fae8    { getInstance(Ljava/lang/String;)Ljava/security/KeyFactory; }
   2271 ms |: jstring          : 0x89
   2271 ms |= jobject          : 0xa9    { java/security/KeyFactory }

   2271 ms ---------------------------------Backtrace---------------------------------
   2271 ms |-> 0xb61ddbd3: liblogin_encrypt.so!0xbd3 (liblogin_encrypt.so:0xb61dd000)


           /* TID 17812 */
   2281 ms [+] JNIEnv->ExceptionCheck
   2281 ms |- JNIEnv*          : 0xf3889140
   2281 ms |= jboolean         : 0    { false }

这里看来其实Cmd看着更顺应。

修改函数数值参数返回值

var helloAddr = Module.findExportByName("libxiaojianbang.so", "add");
console.log(helloAddr);
if(helloAddr != null){
	Interceptor.attach(helloAddr,
	{
	onEnter: function(args){
	args[2] = ptr(1000); //new NativePointer()  //这里修改参数要传入的是指针
	console.log(args[2].toInt32());
	},
	onLeave: function(retval)
	{
	retval.replace(20000);//返回值要使用replace()方法
	console.log("retval", retval.toInt32());
	}
});
}

修改函数字符串参数(通过创建字符串,写入地址)

缺点:可能会覆盖内存数据

var soAddr = Module.findExportByName("libxiaojianbang.so", "add");
var MD5Update = soAddr.add(0x1D68);
Interceptor.attach(MD5Update, {
    onEnter: function (args) {
        if(args[1].readCString() == "xiaojianbang"){//这里判断是因为Update有三次,两次填充
            var newStr = "xiaojian";//修改字符串参数要内存地址对应值的修改
            args[1].writeByteArray(hexToBytes(stringToHex(newStr) + "00"));//所以这里要进行参数的写入,同时,字符串要遇到‘00’截断,所以要补0
            console.log(hexdump(args[1]));
            args[2] = ptr(newStr.length);
            console.log(args[2].toInt32());
        }
    }, onLeave: function (retval) {
    }
});

修改函数字符串参数(通过申请空间来实现参数的赋值,实现修改参数)

var soAddr = Module.findBaseAddress("libxiaojianbang.so");
console.log("soAddr", soAddr);
var MD5Update = soAddr.add(0x1D68);
var newStr = "gdfgdhfgjghjgkhjkh;kl;k;";//这里设置成了全局变量,这样才不会在onLeave的时候被销毁
var newStrAddr = Memory.allocUtf8String(newStr);
Interceptor.attach(MD5Update, {
    onEnter: function (args) {
        this.args0 = args[0];
        this.args1 = args[1];
        if(args[1].readCString() == "xiaojianbang"){
            args[1] = newStrAddr;
            console.log(hexdump(args[1]));
            args[2] = ptr(newStr.length);
            console.log(args[2].toInt32());
        }
    }, onLeave: function (retval) {
        if(this.args1.readCString() == "xiaojianbang"){
            console.log(hexdump(this.args0));
        }
    }
});

监听dlopen来HOOK已加载的函数

利用dlopen实现so之间的相互使用,这里通过去得到path,利用dlopen实现了so之间的访问和使用

    class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState);
        Log.d("Tag", "getPath:"+getPath(getApplicationContext()));//调用getApplicationContext()来获取需要的Context类
        val path: String? = getPath(getApplicationContext()+"/libchen_chen_chen2.so");//获取path
        val stringVar: String = path as String;//转字符串,假如是null就会抛出异常
        stringVar+="/libchen_chen_chen2.so"
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        Log.d("tag","oncreate");
        // Example of a call to a native method
        binding.sampleText.text = stringFromJNI(stringVar);
    }
    private external fun stringFromJNI(string: String): String
    ------------------------------------------------------------------------------------------------
    
    void* soinfo = dlopen(cpath, RTLD_NOW);//这里去获取对应路径下的so文件的句柄
    if (soinfo == nullptr) {
        // 处理 dlopen 失败的情况
        __android_log_print(ANDROID_LOG_ERROR, "JNI", "Failed to load library: %s", dlerror());
        env->ReleaseStringUTFChars(path, cpath);
        return nullptr;
    }
    //由于我们是通过的对应路径去查找的so文件的函数名,所以这里创建了一个函数指针去得到对应的函数句柄,然后直接调用
    void (*def)(char*) = reinterpret_cast<void (*)(char*)>(dlsym(soinfo, "_Z7seconedv"));//直接去利用

所以我们HOOK也可以去使用dlopen,实现监测so是否被加载,加载之后就可以直接进行HOOK

同时在程序版本不同时,有两个dlopen可能会去实现so的访问,所以两个都要HOOK

dlopen_HOOK:

function Hook_dlopen(addr,Soname,callback)
{
    Interceptor.attach(addr,{
        onEnter: function(args){
            var pathname =args[0].readCString();
            console.log("dlopen args[0]: ", pathname);
            if(pathname.indexOf(Soname)!=-1)
            {
                this.hook=true;
                console.log("-------------------HOOK_function_starting--------------------");
            }

        },
        onLeave:function(retval) {
            console.log("onLeave retvel :",retval)
            if(this.hook)callback()
        }
    })
}
function hookfunc(){
    var soAddr = Module.findBaseAddress("libxiaojianbang.so");
    console.log("soAddr", soAddr);
    var MD5Final = soAddr.add(0x3540);
    Interceptor.attach(MD5Final, {
        onEnter: function (args) {
            this.args1 = args[1];
        }, onLeave: function (retval) {
            console.log(hexdump(this.args1));
        }
    });
    console.log("-------------------HOOK_function_END--------------------");
}
var dlopen = Module.findExportByName(null, "dlopen");
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
Hook_dlopen(dlopen, "libxiaojianbang.so", hookfunc);
Hook_dlopen(android_dlopen_ext, "libxiaojianbang.so", hookfunc);

这里的dlopen的HOOKfunc是去HOOK了一个MD5的dofinal的位置

内存读写

  1. 读取指定地址的字符串
var soAddr = Module.findBaseAddress("libxiaojianbang.so");
console.log(soAddr.add(0x2C00).readCString());
  1. dump 指定地址的内存
console.log(hexdump(soAddr.add(0x2C00)));
  1. 读指定地址的内存
console.log(soAddr.add(0x2C00).readByteArray(16));
console.log(Memory.readByteArray(soAddr.add(0x2C00), 16)); // 原先的 API
  1. 写指定地址的内存
soAddr.add(0x2C00).writeByteArray(stringToBytes("xiaojianbang"));//这里的stringToBytes是直接单字符的传入对应的ASCII码值,所以内存存入的是xiaojianbang这几个字符
console.log(hexdump(soAddr.add(0x2C00)));
-----------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------
soAddr.add(0x2C00).writeByteArray(hexToBytes("0123456789abcdef"));//这里的hex数据是读取两个字符作为一个hex数据,所以传入的数据在内存中是对应的值,01,23,45........
console.log(hexdump(soAddr.add(0x2C00)));
  1. 申请新内存写入
var memory = Memory.alloc();
memory.writeByteArrary(stringToBytes("xiaojianbang\0"))//这里同理写入是单字符对应ASCII,传入的是对应字符串
Memory.allocUtf8String()
  1. 修改内存权限
Memory.protect(ptr(libso.base), libso.size, 'rwx');

frida修改so函数代码

  1. 修改地址对应的指令

var soAddr = Module.findBaseAddress("libxiaojianbang.so");
soAddr.add(0x1684).writeByteArray(hexToBytes("0001094B"));

ARM与Hex在线转换 https://armconverter.com/

  1. 将对应地址的指令解析成汇编

var ins = Instruction.parse(soAddr.add(0x1684));
console.log(ins.toString());
  1. 利用frida提供的api来写汇编代码

new Arm64Writer(soAddr.add(0x167C)).putNop();
console.log(Instruction.parse(soAddr.add(0x167C)).toString());
  1. 利用frida提供的api来写汇编代码

var codeAddr = soAddr.add(0x167C);
Memory.patchCode(codeAddr, 8, function (code) {
var Writer = new Arm64Writer(code, {pc: codeAddr});
Writer.putBytes(hexToBytes("0001094B"));
Writer.putBytes(hexToBytes("FF830091"));
Writer.putRet();
Writer.flush();
});

循环读取HOOK之后的代码:

for(var i =0 ; i<3;i++)
{
  var SOaddr = Module.findBaseAddress("libxiaojianbang.so");
  var ins = Instruction.parse(SOaddr.add(0x167C+i*4));
  console.log(ins.toString());
}

so层的主动调用

原生函数

  • new NativeFunction(address, returnType, argTypes[, abi]):创建一个新的 NativeFunction 来调用函数address(用 指定 NativePointer),其中returnType指定返回类型,argTypes数组指定参数类型。abi如果不是系统默认值,您也可以选择指定。对于可变参数函数,'...' 在固定参数和可变参数之间添加一个条目argTypes
var soaddr = Module.findBaseAddress("libxiaojianbang.so");
var funcaddr = soaddr.add(0x124C);
var sofunction =  new NativeFunction(funcaddr,"pointer", ["pointer","pointer"]);
var env =  Java.vm.tryGetEnv();
console.log("env: ",JSON.stringify(env));
var jString =  env.newStringUtf("chen_chen_chen");
var cstring = sofunction(env,jString);
console.log("jstring_2_cstring:",hexdump(cstring));
console.log("jstring_2_cstring:",cstring.readCString());

调用libc库函数,写入文件

function call_libc_writefile()
{
    var addr_fopen = Module.findExportByName("libc.so", "fopen");
    var addr_fputs = Module.findExportByName("libc.so", "fputs");
    var addr_fclose = Module.findExportByName("libc.so", "fclose");
    console.log("addr_fopen: ",addr_fopen);
    console.log("addr_fputs: ",addr_fputs);
    console.log("addr_fclose: ",addr_fclose);
    var filename =  Memory.allocUtf8String("/data/data/com.xiaojianbang.app/xiaojianbang.txt");
    var mode=Memory.allocUtf8String("w");
    var str =  Memory.allocUtf8String("this is a test\n");
    var fopen =  new NativeFunction(addr_fopen,"pointer", ["pointer","pointer"]);
    var fputs =  new NativeFunction(addr_fputs,"int", ["pointer","pointer"]);
    var fclose =  new NativeFunction(addr_fclose,"int", ["pointer"]);
    var file =  fopen(filename,mode);
    fputs(str,file);
    fclose(file);
}

HOOK JNI函数

JNI在libart.so中,所以要去枚举这里的函数

function Hook_JNI()
{
    var newStringUTF =null
    var Symbols = Process.findModuleByName("libart.so").enumerateSymbols();
    for (let i = 0; i < Symbols.length; i++) {
        var sym = Symbols[i];
        if (sym.name.indexOf("CheckJNI"==-1) && sym.name.indexOf("NewStringUTF")!=-1)
        {
            console.log("symbols name: " + sym.name);
            console.log("symbols addr: " + sym.address);
            newStringUTF = sym.address;
            console.log("newStringUTF: ",newStringUTF);
            break;
        }
    }
    console.log("newStringUTF: ",newStringUTF);
    if(newStringUTF != null){
        Interceptor.attach(newStringUTF,{
            onEnter: function(args){
                console.log("jni args[1]: ", args[1].readCString());
            },
            onLeave:function(retval) {
            }
        })
    }
}

主动调用JNI函数

function Hook_JNI()
{
    var env = Java.vm.tryGetEnv().handle;
    var newStringUTF =null
    var Symbols = Process.findModuleByName("libart.so").enumerateSymbols();
    for (let i = 0; i < Symbols.length; i++) {
        var sym = Symbols[i];
        if (sym.name.indexOf("CheckJNI"==-1) && sym.name.indexOf("NewStringUTF")!=-1)
        {
            console.log("symbols name: " + sym.name);
            console.log("symbols addr: " + sym.address);
            newStringUTF = sym.address;
            console.log("newStringUTF: ",newStringUTF);
            break;
        }
    }
    console.log("newStringUTF: ",newStringUTF);
    var newString =  new NativeFunction(newStringUTF,"pointer", ["pointer","pointer"]);
    var mode=Memory.allocUtf8String("w");
    var jString =  Memory.allocUtf8String("this is a test\n");
    var newStringaddr =  newString(env,jString);
    console.log(newStringaddr);

So层堆栈打印

console.log(Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') + '\n')

二级指针构造

function call_func() {
    var soAddr = Module.findBaseAddress("libxiaojianbang.so");
    var xiugaiStr = soAddr.add(0x17D0);
    var xiugaiStr_func = new NativeFunction(xiugaiStr, 'int64', ['pointer']);
    var strAddr = Memory.allocUtf8String("dajianbang");//一级指针里面存放字符串
    console.log(hexdump(strAddr));
    var finalAddr = Memory.alloc(8).writePointer(strAddr);//二级指针指向一级指针
    xiugaiStr_func(finalAddr);//这里需要传入二级指针
    console.log(hexdump(strAddr));
} 

判断SO层对应函数存在于哪个so文件中

dlsym函数HOOK

dlsym函数的主要功能是在运行时动态加载共享库(动态链接库)中的符号(函数或变量),并返回该符号的地址。

    void (*def)(char*) = reinterpret_cast<void (*)(char*)>(dlsym(soinfo, "_Z7seconedv"));//直接去利用
    dlsym(sopath,funcname)
function hook_dlsym()
{
    var dlsymAddr = Module.findExportByName("libdl.so","dlsym");
    console.log("dlsymAddr: ",dlsymAddr);
    Interceptor.attach(dlsymAddr,
        {
        onEnter: function(args)
        {
            this.funcname = args[1];
        },
        onLeave: function(result) {
            var module = Process.findModuleByAddress(result);
            console.log("dlsym: ",this.funcname);
            console.log("dlsym result: ",result);
            // console.log(Thread.backtrace(this.context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\n') +'\n')
            if(module==null) return;
            console.log(this.funcname.readCString() + "moduele:"+module.name );
            console.log("addr:"+result);
            console.log("offset:",result.sub(module.base))
        }
    })
}

RegisterNatives()函数HOOK

RegisterNatives()在Native中注册函数

    auto clazz = env->FindClass("com/xiaojianbang/ndk/NativeHelper");
    JNINativeMethod methods[] = {
            {"encode", "()Ljava/lang/String;", (void *)_strcat},
    };
    env->RegisterNatives(clazz, methods, 1);
function hook_RegisterNatives()
{
    var symbols =  Process.getModuleByName("libart.so").enumerateSymbols();
    var RegisterNatives =null;
    for (let i = 0; i < symbols.length; i++) {
        if(symbols[i].name.indexOf("CheckJNI")==-1&&symbols[i].name.indexOf("RegisterNatives")!=-1)
        RegisterNatives = symbols[i].address; 
    }
    console.log("RegisterNatives address:"+RegisterNatives);

    Interceptor.attach(RegisterNatives,
        {
        onEnter: function(args)
        {   
            var env =  Java.vm.tryGetEnv();
            var classname = env.getClassName(args[1]);
            console.log("RegisterNatives classname:"+classname);
            var methodnum = args[3].toInt32();
            var methodname =  args[2].readPointer().readCString();
            var signature = args[2].add(Process.pointerSize).readPointer().readCString();
            var fnPtr = args[2].add(Process.pointerSize*2).readPointer();
            var module = Process.findModuleByAddress(fnPtr);
            console.log("RegisterNatives methodname:"+methodname);
            console.log("RegisterNatives signature:"+signature);
            console.log("RegisterNatives methodnum:"+methodnum);
            console.log("module name:"+module.name);
            console.log("func:"+ fnPtr+"\n"+ "offset:" +fnPtr.sub(module.base));
        },
        onLeave: function(result) {

        }
    })
}

HOOK汇编inlineHOOK

function inline_Hook()
{
    var module_base_addr =  Module.getBaseAddress("libxiaojianbang.so");
    var Hook_addr = module_base_addr.add(0x01D40);
    Interceptor.attach(Hook_addr,
        {
        onEnter: function(args)
        {  
            console.log(this.context.x12.toInt32());
        },
        onLeave: function(result) {
            console.log(this.context.x12.toInt32());
        }});
}

安卓loadLibrary源码分析

loadLibrary——>loadLibrary0(classLoader, libname);

部分源码:

998    synchronized void loadLibrary0(ClassLoader loader, String libname) {
999        if (libname.indexOf((int)File.separatorChar) != -1) {
1000            throw new UnsatisfiedLinkError(
1001    "Directory separator should not appear in library name: " + libname);
1002        }
1003        String libraryName = libname;
1004        if (loader != null) {
1005            String filename = loader.findLibrary(libraryName);//这里实现了so文件名的拼接
1006            if (filename == null) {
1007                // It's not necessarily true that the ClassLoader used
1008                // System.mapLibraryName, but the default setup does, and it's
1009                // misleading to say we didn't find "libMyLibrary.so" when we
1010                // actually searched for "liblibMyLibrary.so.so".
1011                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
1012                                               System.mapLibraryName(libraryName) + "\"");
1013            }
1014            String error = nativeLoad(filename, loader);//这里开始进行加载
   // -----------------------------------------------------------------------------------
1015            if (error != null) {
1016                throw new UnsatisfiedLinkError(error);
1017            }
1018            return;
1019        }
1020
1021        String filename = System.mapLibraryName(libraryName);
1022        List<String> candidates = new ArrayList<String>();
1023        String lastError = null;
1024        for (String directory : getLibPaths()) {
1025            String candidate = directory + filename;
1026            candidates.add(candidate);
1027
1028            if (IoUtils.canOpenReadOnly(candidate)) {
1029                String error = nativeLoad(candidate, loader);
1030                if (error == null) {
1031                    return; // We successfully loaded the library. Job done.
1032                }
1033                lastError = error;
1034            }
1035        }

loadLibrary0(classLoader, libname)——>String error = nativeLoad(filename, loader)——>JVM_NativeLoad

部分源码:

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
324                                 jstring javaFilename,
325                                 jobject javaLoader) {
326  ScopedUtfChars filename(env, javaFilename);
327  if (filename.c_str() == NULL) {
328    return NULL;
329  }
330
331  std::string error_msg;
332  {
333    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
334    bool success = vm->LoadNativeLibrary(env,
335                                         filename.c_str(),
336                                         javaLoader,
337                                         &error_msg);
338    if (success) {
339      return nullptr;
340    }
341  }

JVM_NativeLoad ——> LoadNativeLibrary——>OpenNativeLibrary(这里开始dlopen和dlsym)

957  const char* path_str = path.empty() ? nullptr : path.c_str();
958  bool needs_native_bridge = false;
959  void* handle = android::OpenNativeLibrary(env,//从这个函数开始加载dlopen
960                                            runtime_->GetTargetSdkVersion(),
961                                            path_str,
962                                            class_loader,
963                                            library_path.get(),
964                                            &needs_native_bridge,
965                                            error_msg);

这里之后直接去通过dlopen加载so路径去实现so文件的载入了,同时这里加载成功之后就要进行init以及init_array了

JNI_load函数

这里是loadLibrary0之后的代码,也就是说JNI_load也是在so文件加载之后init,init_array初始化之后进行的JNI_load的加载

1001  if (!created_library) {
1002    LOG(INFO) << "WOW: we lost a race to add shared library: "
1003        << "\"" << path << "\" ClassLoader=" << class_loader;
1004    return library->CheckOnLoadResult();
1005  }
1006  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
1007
1008  bool was_successful = false;
1009  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
1010  if (sym == nullptr) {
1011    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
1012    was_successful = true;
1013  }

同时这里的JNI_load是通过dlsym来加载得到的

154void* Agent::FindSymbol(const std::string& name) const {
155  CHECK(dlopen_handle_ != nullptr) << "Cannot find symbols in an unloaded agent library " << this;
156  return dlsym(dlopen_handle_, name.c_str());
157}
function dlopen()
{
	init()
	init_array()
};
dlsym(JNI_load)

通过这里,我们知道了:要是我们要HOOK init和init_array 需要在dlopen之后,也就是so文件加载之后,但是要在JNI_load之前,这样才能确定我们上述的分析是合理的

所以我们要先利用HOOK dlopen来使得我们知道libxiaojianbang.so被加载了,之后再进行call_constructorsEv的HOOK(因为这里的位置正好是我们上述分析所说的位置)在这里HOOK Onentry的位置,进行HOOK

function main() {
    function hook_dlopen(addr, soName, callback) {
        Interceptor.attach(addr, {
            onEnter: function (args) {
                var soPath = args[0].readCString();
                if(soPath.indexOf(soName) != -1) callback();
            }, onLeave: function (retval) {
            }
        });
    }
    var dlopen = Module.findExportByName("libdl.so", "dlopen");
    var android_dlopen_ext = Module.findExportByName("libdl.so", "android_dlopen_ext");
    hook_dlopen(dlopen, "libxiaojianbang.so", hook_call_constructors);
    hook_dlopen(android_dlopen_ext, "libxiaojianbang.so", hook_call_constructors);
//---------------------------------------------------------------------------这里判断是我们的libxiaojianbang.so进行的dlopen操作
    var isHooked = false;
    function hook_call_constructors() {
        var symbols = Process.getModuleByName("linker64").enumerateSymbols();
        var call_constructors_addr = null;
        for (let i = 0; i < symbols.length; i++) {
            var symbol = symbols[i];
            if(symbol.name.indexOf("__dl__ZN6soinfo17call_constructorsEv") != -1){
                call_constructors_addr = symbol.address;
            }
        }
        console.log("call_constructors_addr: ", call_constructors_addr);
        Interceptor.attach(call_constructors_addr, {
            onEnter: function (args) {
                if(!isHooked) {
                    hook_initarray();
                    isHooked = true;
                }
            }, onLeave: function (retval) {
            }
        });
    }
    //-----------------------------------------------------------这里以上是匹配我们的init_array的位置

    function hook_initarray(){
        var xiaojianbangAddr = Module.findBaseAddress("libxiaojianbang.so");
        var func1_addr = xiaojianbangAddr.add(0x1C54);
        var func2_addr = xiaojianbangAddr.add(0x1C7C);
        var func3_addr = xiaojianbangAddr.add(0x1C2C);
        Interceptor.replace(func1_addr, new NativeCallback(function () {
            console.log("func1 is replaced!!!");
        }, 'void', []));

        Interceptor.replace(func2_addr, new NativeCallback(function () {
            console.log("func2 is replaced!!!");
        }, 'void', []));

        Interceptor.replace(func3_addr, new NativeCallback(function () {
            console.log("func3 is replaced!!!");
        }, 'void', []));
    }
}
//----------------------------------------------------------------------这里就直接HOOK了
main();

JNI_Onload的HOOK

我们的JNI_OnLoad也需要通过我们的dlopen来进行HOOK,因为由于我们直接启用就直接HOOK,可能由于so没有被加载,而导致可能并没有HOOK到,假如以附加的形式来进行HOOK,也可能会该JNI_onLoad已经完成了。

var dlopen = Module.findExportByName("libdl.so", "dlopen");
var android_dlopen_ext = Module.findExportByName("libdl.so", "android_dlopen_ext");
Hook_dlopen(dlopen, "libxiaojianbang.so", HOOK_JNIonLoad);
Hook_dlopen(android_dlopen_ext, "libxiaojianbang.so", HOOK_JNIonLoad);
function HOOK_JNIonLoad()
{
    var module =  Module.getBaseAddress("libxiaojianbang.so");
    var addr =  module.add(0x1ccc);
    Interceptor.replace(addr,new NativeCallback(function(){
        console.log("Native");
    },"void",[]));
}

线程创建HOOK hook_pthread_create

我们可以去HOOK 创建子线程的一些函数,因为在检测过程中,可能是通过开启子线程来进行检测过程的,所以我们通过HOOK创建线程的函数,可以使得我们得到一些有用的信息,同时由于pthread_create是系统函数,所以不用dlopen检测,直接进行HOOK就可以了

int pthread_create 函数
(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);



function hook_pthread_create(){
    var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create");
    console.log("pthread_create_addr: ", pthread_create_addr);
    Interceptor.attach(pthread_create_addr,{
        onEnter:function(args){   //args[2]是子线程的函数,假如是检测可以直接删线程
            console.log(args[0], args[1], args[2], args[3]);
        },onLeave:function(retval){
            console.log("retval is =>",retval)
        }
    })
posted @   fisherman-ovo  阅读(898)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示