安卓 day2
怎么隔了这么多天?
frida
1 2 3 4 5
| mkdir my-agent cd my-agent frida-craete -t agent npm install npm install frida-java-bridge
|
1 2 3 4 5
| import Java from "frida-java-bridge"
Java.perform(() => { ... });
|
Python 内编译
1 2 3 4 5 6 7 8 9
| ... PROJECT_ROOT = os.path.abspath("./my-agent") ENTRY = "agent.ts"
compiler = frida.Compiler() compiler.on("diagnostics", on_diagnostics)
bundle = compiler.build(ENTRY, project_root=PROJECT_ROOT) ...
|
手动编译(略)
攻防世界 ill-intentions hook
先看 manifest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.example.hellojni" platformBuildVersionCode="22" platformBuildVersionName="5.1.1-1819727"> <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="23"/> <permission android:name="ctf.permission._MSG" android:protectionLevel="signature" android:description="@string/android.permission._msg"/> <permission android:name="ctf.permission._SEND" android:description="@string/android.permission._msg"/> <application android:label="CTF Application" android:icon="@mipmap/ic_launcher"> <activity android:label="Main Activity" android:name="com.example.application.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:label="Activity: Is This The Real One" android:name="com.example.application.IsThisTheRealOne"/> <activity android:label="This Is The Real One" android:name="com.example.application.ThisIsTheRealOne"/> <activity android:label="Definitely Not This One" android:name="com.example.application.DefinitelyNotThisOne"/> <receiver android:name="com.example.application.Send_to_Activity" android:exported="true"/> </application> </manifest>
|
关注到主活动外有三个活动,都是解密过程,分别调用了一个不同的 native 函数,算法都不是很复杂可以直接分析,这里考虑 hook,但先按下不表。
还有一个 receiver,先看一眼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Send_to_Activity extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String msgText = intent.getStringExtra("msg"); if (msgText.equalsIgnoreCase("ThisIsTheRealOne")) { Intent outIntent = new Intent(context, (Class<?>) ThisIsTheRealOne.class); context.startActivity(outIntent); } else if (msgText.equalsIgnoreCase("IsThisTheRealOne")) { Intent outIntent2 = new Intent(context, (Class<?>) IsThisTheRealOne.class); context.startActivity(outIntent2); } else if (msgText.equalsIgnoreCase("DefinitelyNotThisOne")) { Intent outIntent3 = new Intent(context, (Class<?>) DefinitelyNotThisOne.class); context.startActivity(outIntent3); } else { Toast.makeText(context, "Which Activity do you wish to interact with?", 1).show(); } } }
|
做的就是 dispatcher 的工作,看一眼主活动
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(getApplicationContext()); tv.setText("Select the activity you wish to interact with.To-Do: Add buttons to select activity, for now use Send_to_Activity"); setContentView(tv); IntentFilter filter = new IntentFilter(); filter.addAction("com.ctf.INCOMING_INTENT"); BroadcastReceiver receiver = new Send_to_Activity(); registerReceiver(receiver, filter, Manifest.permission._MSG, null); } }
|
发现这个 receiver 的 filter 是动态注册的,权限为 ctf.permission._MSG,manifest 里面可用看到是 signature 级别(只有同签名的app才有该权限),不考虑重打包基本没办法利用。
尝试 adb 直接启动那三个活动
1
| adb shell am start -n com.example.hellojni/com.example.application.IsThisTheRealOne
|
报错,活动的 exported 标签不为真,外部启动不了。了解到工具 objection 可以把 agent 注入到进程内部,在 app 的进程里构造 intent 可以绕过这个限制。那么可以把那三个 native 函数钩住,再用 objection 启动活动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function main() { Java.perform(function() { var DefinitelyNotThisOneHandler = Java.use('com.example.application.DefinitelyNotThisOne'); DefinitelyNotThisOneHandler.definitelyNotThis.implementation = function(arg0, arg1) { console.log("[*] Entered DefinitelyNotThisOneHandler(), arg0: " + arg0 + ", arg1: " + arg1); var ret = this.definitelyNotThis(arg0, arg1); console.log("[*] Finished DefinitelyNotThisOneHandler(), return value: " + ret); return ret; }
var ThisIsTheRealOneHandler = Java.use("com.example.application.ThisIsTheRealOne"); ThisIsTheRealOneHandler.orThat.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(arg0, arg1, arg2) { console.log('[*] Entered ThisIsTheRealOneHandler(), arg0: ' + arg0 + ', arg1: ' + arg1 + ', arg2: ' + arg2); var ret = this.orThat(arg0, arg1, arg2); console.log('[*] Finished ThisIsTheRealOneHandler(), return value: ' + ret); return ret; }
var IsThisTheRealOneHandler = Java.use("com.example.application.IsThisTheRealOne"); IsThisTheRealOneHandler.perhapsThis.overload('java.lang.String', 'java.lang.String', 'java.lang.String').implementation = function(arg0, arg1, arg2) { console.log('[*] Entered IsThisTheRealOneHandler(), arg0: ' + arg0 + ', arg1: ' + arg1 + ', arg2: ' + arg2); var ret = this.perhapsThis(arg0, arg1, arg2); console.log('[*] Finished IsThisTheRealOneHandler(), return value: ' + ret); return ret; } }) }
setImmediate(main)
|
1
| objection --name com.example.hellojni start
|
1 2 3
| android hooking list activities android intent launch_activity com. example.application.IsThisTheRealOne
|
1 2 3 4
| [MI 6::com.example.hellojni ]-> [*] Entered IsThisTheRealOneHandler(), arg0: TRytfrgooq|F{i-JovFBungFk\VlphgQbwvj~HuDgaeTzuSt.@Lex^~, arg1: ZGFkNGIwYzIWYjEzMTUWNjVjNTVlNjZhOGJkNhYtODIyOGEaMTMWNmQaOTVjZjkhMzRjYmUzZGE? , arg2: MzQxZTZmZjAxMmIiMWUzNjUxMmRiYjIxNDUwYTUxMWItZGQzNWUtMzkyOWYyMmQeYjZmMzEaNDQ?
[*] Finished IsThisTheRealOneHandler(), return value: Congratulation!YouFoundTheRightActivityHereYouGo-CTF{IDontHaveABadjokeSorry}
|
重打包
1 2 3 4 5 6 7 8 9 10 11
| apktool d -f raw.apk -o out_dir apktool b -f out_dir -o unsigned.apk
zipalign -p -f 4 unsigned.apk aligned.apk
keytool -genkeypair -alias myapp -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -validity 10000 -keystore my-release.keystore -storetype PKCS12 -v
apksigner sign --ks my-release.keystore --out signed.apk aligned.apk
|
JNI 函数在 C/C++ 侧的真实形态
1
| public static native int foo(String str);
|
通常对应:
1
| JNIEXPORT jint JNICALL Java_com_example_test_JNI_foo(JNIEnv* env, jclass clazz, jstring str);
|
JNIEnv* env 是 JNI 环境指针,线程相关,里面是一堆函数表,对应 *JNINativeInterface
jclass clazz 是类对象(如果不是 static,则是 jobject thiz)
jstring str Java 传进来的 String
JNI 环境指针函数表中的函数通常通过 *env + offset 调用,结合地址位数可以算出来是哪个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| int __fastcall Java_com_example_test_ctf03_JNI_getResult(int a1, int a2, int a3) { int v3; const char *v4; char *v5; char *v6; char *v7; int i; int j;
v3 = 0; str = (*(*a1 + 676))(a1, a3, 0); if ( strlen(v4) == 15 ) { v5 = malloc(1u); v6 = malloc(1u); v7 = malloc(1u); Init(v5, v6, v7, v4, 15); if ( !First(v5) ) return 0; for ( i = 0; i != 4; ++i ) v6[i] ^= v5[i]; if ( !strcmp(v6, a5) ) { for ( j = 0; j != 4; ++j ) v7[j] ^= v6[j]; return strcmp(v7, "AFBo}") == 0; } else { return 0; } } return v3; }
|
676/4=169,索引到 GetStringUTFChars()(查文档或者对应版本的 jni.h,也可以用参数和用途推断),这个函数把 java 的 String 转成 C 字符串。