1. 程式人生 > 實用技巧 >FridaHook框架學習(2)

FridaHook框架學習(2)

FridaHook框架學習(2)

前言

學習過程參考https://bbs.pediy.com/thread-227233.htm。

逆向分析

安裝並執行例子程式,可以看到這個例子是一個驗證註冊碼的程式。

使用jadx解析這個APK。

通過類名以及AndroidManifest可以猜測以及知道LauncherActivity是這個程式的啟動類。先看看LauncherActivity的程式碼。

public class LauncherActivity extends AppCompatActivity {
    /* access modifiers changed from: protected */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_launcher);
    }

    public void verifyClick(View v) {
        try {
            InputStream in = new URL("http://broken.license.server.com/query?license=" + ((EditText) findViewById(R.id.text_license)).getText().toString()).openConnection().getInputStream();
            StringBuilder responseBuilder = new StringBuilder();
            byte[] b = new byte[0];
            while (in.read(b) > 0) {
                responseBuilder.append(b);
            }
            String response = responseBuilder.toString();
            if (response.equals("LICENSEKEYOK")) {
                String activatedKey = new String(MainActivity.xor(getMac().getBytes(), response.getBytes()));
                SharedPreferences.Editor editor = getApplicationContext().getSharedPreferences("preferences", 0).edit();
                editor.putString("KEY", activatedKey);
                editor.commit();
                new AlertDialog.Builder(this).setTitle((CharSequence) "Activation successful").setMessage((CharSequence) "Activation successful").setIcon(17301543).show();
                return;
            }
            new AlertDialog.Builder(this).setTitle((CharSequence) "Invalid license!").setMessage((CharSequence) "Invalid license!").setIcon(17301543).show();
        } catch (Exception e) {
            new AlertDialog.Builder(this).setTitle((CharSequence) "Error occured").setMessage((CharSequence) "Server unreachable").setNeutralButton((CharSequence) "OK", (DialogInterface.OnClickListener) null).setIcon(17301543).show();
        }
    }

    private String getKey() {
        return getApplicationContext().getSharedPreferences("preferences", 0).getString("KEY", "");
    }

    private String getMac() {
        try {
            return ((WifiManager) getApplicationContext().getSystemService("wifi")).getConnectionInfo().getMacAddress();
        } catch (Exception e) {
            return "";
        }
    }

    public void showPremium(View view) {
        Intent i = new Intent(this, MainActivity.class);
        i.putExtra("MAC", getMac());
        i.putExtra("KEY", getKey());
        startActivity(i);
    }
}

可以通過方法名猜測verifyClick方法應該就是VERIFY按鈕的點選事件,showPremium應該就是另一個按鈕的點選事件。可以看到程式的邏輯大概為通過網路驗證license的正確性,若正確就呼叫MainActivity的xor方法通過MAC和response生成金鑰並儲存在本地的preference中。

其中InputStream是Java一個用於讀取流的類。SharedPreferences是一個類似Python中字典的一種資料結構。

再來看看MainActivity的程式碼

public class MainActivity extends AppCompatActivity {
    public native String stringFromJNI(String str, String str2);

    public static byte[] xor(byte[] val, byte[] key) {
        byte[] o = new byte[val.length];
        for (int i = 0; i < val.length; i++) {
            o[i] = (byte) (val[i] ^ key[i % key.length]);
        }
        return o;
    }

    public void onCreate(Bundle savedInstanceState) {
        String key = getIntent().getStringExtra("KEY");
        String mac = getIntent().getStringExtra("MAC");
        if (key == "" || mac == "") {
            key = "";
            mac = "";
        }
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_main);
        ((TextView) findViewById(R.id.sample_text)).setText(stringFromJNI(key, mac));
    }

    static {
        System.loadLibrary("native-lib");
    }
}

可以看到顯示flag的函式寫在了So中,通過金鑰KEY和MAC生成。

可以得到Hook思路:Hook getKey方法直接使用MAC和LICENSEOK構造金鑰並返回,之後點選PREMIUMCONTENT按鈕即可

Hook程式碼編寫

import frida
import sys


jscode = """
    Java.perform(function(){

        function stringToBytes(str){
            var javaString = Java.use('java.lang.String');
            var bytes = [];
            bytes = javaString.$new(str).getBytes();
            return bytes;
        }
    
        function bytesToString(bytes){
            var javaString = Java.use('java.lang.String');
            return javaString.$new(bytes);
        }


        var launcheractivity = Java.use('de.fraunhofer.sit.premiumapp.LauncherActivity');
        var main = Java.use('de.fraunhofer.sit.premiumapp.MainActivity');
        var license = "LICENSEKEYOK";
        launcheractivity.getKey.implementation = function() {
            var mac = this.getMac();

            var macbyte = stringToBytes(mac);
            var licensebyte = stringToBytes(license);

            var xor = main.xor(macbyte,licensebyte);
            send(xor)
            var key = bytesToString(xor);
            send(key.toString());
            return key;
        }

        launcheractivity.verifyClick.implementation = function(v) {
            this.showPremium(v);
        }
    });
"""

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)


process = frida.get_usb_device().attach('de.fraunhofer.sit.premiumapp')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
sys.stdin.read()

執行結果

總結

編寫js程式碼的時候是真的累,因為要寫在字串裡,沒法自動補全,有些變數名就打錯了,還全部都是綠綠的很難看出來,終於也看懂了一點js的報錯才找出來了。Js中可以通過Java.use使用Java中的一些基類用於靈活轉換型別。