記事ディレクトリ
序文
ここに2つの逆コンパイルツールがあります
- jadx は、Android APK ファイルの逆コンパイル、静的逆コンパイル、強力な検索インデックスのためのオープン ソース ツールです。
- Jeb は IDA と非常によく似ており、動的デバッグに属し、Java アセンブリを監視して疑似コードを生成し、ターゲットに動的にアタッチしてデバッグすることもできます。
so ファイルの反転ツール選択
- IDA リバース ツールは、ソフトウェア リバース エンジニアリングの分野で広く使用されている逆アセンブラーで、さまざまなプラットフォームのバイナリ プログラム コードを逆アセンブルし、読み取り可能なアセンブリ コードに復元できます。
Objection は、Frida を利用したモバイル デバイス ランタイム エクスプロイト ツールであり、研究者がモバイル アプリケーションにアクセスし、ジェイルブレイクやルート化を行わずにモバイル アプリケーションのセキュリティを評価するのに役立ちます。
インストールコマンド
pip3 install objection
Frida は、JavaScript および Python コードを記述して frida_server と対話できる、移植可能な無料のプラットフォーム全体のフック フレームワークです。
frida のインストールについては、https: //www.jianshu.com/p/60cfd3f6afdeを参照してください。
1. イリュージョン
1.トピック
2. 質問に答える
1、jadx-gui
jadx-gui で apk ファイルを開きます。
package monkeylord.illusion;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/* loaded from: classes.dex */
public class MainActivity extends Activity {
public native String CheckFlag(String str, String str2);
static {
System.loadLibrary("native-lib");
}
@Override // android.app.Activity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
// from class: monkeylord.illusion.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
try {
String flag = ((EditText) MainActivity.this.findViewById(R.id.editText)).getText().toString();
String encflag = new BufferedReader(new InputStreamReader(MainActivity.this.getAssets().open("Flag"))).readLine();
if (encflag != null) {
((TextView) MainActivity.this.findViewById(R.id.sample_text)).setText(MainActivity.this.CheckFlag(flag, encflag));
}
} catch (Exception e) {
((TextView) MainActivity.this.findViewById(R.id.sample_text)).setText("Something Wrong");
}
}
});
}
}
検証のために CheckFlag 関数を呼び出します。この関数は、2 つのパラメータを渡します。1 つは flag で、もう 1 つは encflag です。その中で、flag は入力した値であり、encflag は読み取り用のテキストに格納されます。
2. So ファイル解析用 ida pro
検索して関数を見つけ、疑似コード分析を実行する
int __fastcall Java_monkeylord_illusion_MainActivity_CheckFlag(_JNIEnv *a1, int a2, int a3, int a4)
{
size_t v4; // r0
size_t i; // [sp+28h] [bp-34h]
char *v7; // [sp+30h] [bp-2Ch]
char *v8; // [sp+34h] [bp-28h]
char *v9; // [sp+38h] [bp-24h]
v9 = (char *)j_j_Jstring2CStr(a1, a3);
v4 = j_strlen(v9);
v8 = (char *)j_calloc(1u, v4 + 1);
v7 = (char *)j_j_Jstring2CStr(a1, a4);
for ( i = 0; i < j_strlen(v9); ++i )
v8[i] = ((unsigned __int64)sub_10C0(
(unsigned __int8)v9[i] + (unsigned int)(unsigned __int8)aE116c5c66e7b37[i] - 64,
93) >> 32)
+ 32;
if ( !j_strcmp(v8, v7) )
return j__JNIEnv::NewStringUTF(a1, "Correct!");
else
return j__JNIEnv::NewStringUTF(a1, "Try Again!");
}
2、sub_10C0
sub_10C0 関数は、a2 が 0 かどうかを判断して実行する関数を判断するものですが、
a2 の値は 93 で一定なので、sub_1028() 関数のみが実行されます。
int __fastcall sub_10C0(int a1, int a2)
{
if ( a2 )
return sub_1028();
else
return sub_10AC();
}
3、サブ1028
sub1028は少し長いですが、ロジックは非常に単純です
int __fastcall sub_1028(unsigned int a1, unsigned int a2)
{
int v2; // r12
unsigned int v3; // r3
int v4; // r2
int result; // r0
v2 = a1 ^ a2;
v3 = 1;
v4 = 0;
if ( (a2 & 0x80000000) != 0 )
a2 = -a2;
if ( (a1 & 0x80000000) != 0 )
a1 = -a1;
if ( a1 >= a2 )
{
while ( a2 < 0x10000000 && a2 < a1 )
{
a2 *= 16;
v3 *= 16;
}
while ( a2 < 0x80000000 && a2 < a1 )
{
a2 *= 2;
v3 *= 2;
}
while ( 1 )
{
if ( a1 >= a2 )
{
a1 -= a2;
v4 |= v3;
}
if ( a1 >= a2 >> 1 )
{
a1 -= a2 >> 1;
v4 |= v3 >> 1;
}
if ( a1 >= a2 >> 2 )
{
a1 -= a2 >> 2;
v4 |= v3 >> 2;
}
if ( a1 >= a2 >> 3 )
{
a1 -= a2 >> 3;
v4 |= v3 >> 3;
}
if ( !a1 )
break;
v3 >>= 4;
if ( !v3 )
break;
a2 >>= 4;
}
}
result = v4;
if ( v2 < 0 )
return -v4;
return result;
}
組み込みデータ文字列に対応する添字に、ユーザが入力した各文字を足して64を引き、
余り93をとった結果を比較
3.脚本
enc_flag = "Ku@'G_V9v(yGS"
data = "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
data = [ord(i) for i in data]
def main():
enc = [ord(i) for i in enc_flag]
flag = [0]*len(enc)
for i in range(len(enc)):
flag[i] = (enc[i]-32)+64-data[i]
while flag[i] < 0x32:
flag[i] += 93
while flag[i] > 125:
flag[i] -= 93
print("".join([chr(i) for i in flag]))
if __name__ == '__main__':
main()
# CISCN{GJ5728}
またはブルートフォース
package com.test;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.linux.android.AndroidARMEmulator;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.sun.jna.Pointer;
import unicorn.Arm64Const;
import unicorn.ArmConst;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class illusion extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private DvmClass cNative;
private static String r0;
private illusion () {
// 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("monkeylord.illusion").build();
// 获取模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
// vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/test/llusion.apk"));
vm = emulator.createDalvikVM();
// 加载目标SO
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/test/libnative-lib.so"), true); // 加载so到虚拟内存
//获取本SO模块的句柄,后续需要用它
module = dm.getModule();
vm.setJni(this); // 设置JNI
vm.setVerbose(false); // 打印日志
dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
}
public static void main(String[] args) {
illusion test = new illusion();
test.hook();
String flag_enc = "Ku@'G_V9v(yGS";
String flag = "";
for (int index = 0; index < flag_enc.length(); index++) {
for (int i = 33; i < 127; i++) {
test.CheckFlag(flag + String.valueOf((char) i), flag_enc); // 进行判断
if ((illusion.r0.length() > index) && (flag_enc.charAt(index) == illusion.r0.charAt(index))) {
flag += String.valueOf((char) i);
break;
}
}
System.out.println("Flag[0:"+index+"]: "+flag);
}
System.out.println("Completely Flag is: "+flag);
}
private void CheckFlag(String flag, String flag_enc) {
//创建jobject对象
DvmObject<?> thiz = vm.resolveClass("monkeylord.illusion").newObject(null);
List<Object> list = new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm, flag))); // arg 3
list.add(vm.addLocalObject(new StringObject(vm, flag_enc))); // arg 4
Number number = module.callFunction(emulator, 0xdc9, list.toArray());
// System.out.print("result:");
// DvmObject<?> object = vm.getObject(number.intValue());
// System.out.print(object.getValue().toString());
}
public void hook() {
HookZz hook = HookZz.getInstance(emulator);
hook.replace(module.base + 0x00000E64+1, new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
// System.out.println(context.getPointerArg(0).getString(0)); // 入参1 R0寄存器
illusion.r0 = context.getPointerArg(0).getString(0);
// System.out.println(context.getPointerArg(1).getString(0)); // 入参2 R1寄存器
return super.onCall(emulator, context,originFunction);
}
}, true);
}
}
フラグを次のように取得します。CISCN{GJ5728}