diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a87bb82..10d55a8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,70 +1,28 @@ name: CI -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] +on: push jobs: - build_apk: - name: Generate APK + build: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup JDK + - name: Build APK uses: actions/setup-java@v4 with: distribution: 'oracle' java-version: '17' - - name: Build APK run: cd demo_apk && bash ./gradlew assembleDebug --stacktrace - - name: Upload APK - uses: actions/upload-artifact@v4 - with: - name: demoapk - path: demo_apk/app/build/outputs/apk/debug/app-debug.apk - test: - name: Test JNI extraction - needs: build_apk - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Download APK from build - uses: actions/download-artifact@v4 - with: - name: demoapk - - name: preinstall + - name: Install Dependencies run: pip3 install -r requirements.txt - - name: test - run: ./extract_jni.py app-debug.apk - - release: - name: release APK - needs: build_apk - if: startsWith(github.ref, 'refs/tags/v') - runs-on: ubuntu-latest - steps: - - name: Download APK from build - uses: actions/download-artifact@v4 - with: - name: demoapk - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - - name: Upload Release APK - id: upload_release_asset - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Test + run: ./extract_jni.py demo_apk/app/build/outputs/apk/debug/app-debug.apk + - name: Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: app-debug.apk - asset_name: app-debug.apk - asset_content_type: application/zip + files: demo_apk/app/build/outputs/apk/debug/app-debug.apk + generate_release_notes: true diff --git a/README.md b/README.md index fb7a09d..22fe072 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Before | After :----------:|:------------: ![i1][i1] | ![i2][i2] -## Binary Ninja +## Binary Ninja Plugin see [Binary Ninja](./binary_ninja). diff --git a/binary_ninja/README.md b/binary_ninja/README.md index fde1f27..725e29f 100644 --- a/binary_ninja/README.md +++ b/binary_ninja/README.md @@ -6,17 +6,21 @@ File -> Run Script # Logging ``` -[+] plugin start, bv= -[+] init_header done. -[+] fix 0x3708 JNI_OnLoad -> jint(JavaVM* vm, void* reserved) -[+] fix 0x4980 JNI_OnUnload -> void(JavaVM* vm, void* reserved) -[+] fix 0x5388 Java_com_evilpan_demoapk_FacadeC_testStatic -> jint(JNIEnv* env, jclass clazz, jint a1) -[+] fix 0x5044 Java_com_evilpan_demoapk_FacadeC_stringFromJNI -> jstring(JNIEnv* env, jobject thiz) -[+] fix 0x5916 Java_com_evilpan_demoapk_FacadeC_testArray -> void(JNIEnv* env, jobject thiz, jintArray a1) -[+] fix 0x5468 Java_com_evilpan_demoapk_FacadeC_testClass -> jint(JNIEnv* env, jobject thiz, jobject a1) -[+] fix 0x5144 Java_com_evilpan_demoapk_FacadeC_testOverload__ -> jint(JNIEnv* env, jobject thiz) -[+] fix 0x5220 Java_com_evilpan_demoapk_FacadeC_testOverload__I -> jint(JNIEnv* env, jobject thiz, jint a1) -[+] fix 0x5300 Java_com_evilpan_demoapk_FacadeC_testOverload__JFD -> jint(JNIEnv* env, jobject thiz, jlong a1, jfloat a2, jdouble a3) +[+] plugin start, bv= +[+] init_header success. +[+] loaded 229 JNI interface +[+] fix 0x127016 Java_com_evilpan_demoapk_FacadeCpp_testStatic -> jint(JNIEnv* env, jclass clazz, jint a1) +[+] fix 0x126388 Java_com_evilpan_demoapk_FacadeCpp_stringFromJNI -> jstring(JNIEnv* env, jobject thiz) +[+] fix 0x128136 Java_com_evilpan_demoapk_FacadeCpp_testArray -> void(JNIEnv* env, jobject thiz, jintArray a1) +[+] fix 0x127096 Java_com_evilpan_demoapk_FacadeCpp_testClass -> jint(JNIEnv* env, jobject thiz, jobject a1) +[+] fix 0x126772 Java_com_evilpan_demoapk_FacadeCpp_testOverload__ -> jint(JNIEnv* env, jobject thiz) +[+] fix 0x126848 Java_com_evilpan_demoapk_FacadeCpp_testOverload__I -> jint(JNIEnv* env, jobject thiz, jint a1) +[+] fix 0x126928 Java_com_evilpan_demoapk_FacadeCpp_testOverload__JFD -> jint(JNIEnv* env, jobject thiz, jlong a1, jfloat a2, jdouble a3) +[+] cpp fix 0x1ecfc _JNIEnv::FindClass +[+] cpp fix 0x1ed30 _JNIEnv::RegisterNatives +[+] cpp fix 0x1eedc _JNIEnv::NewStringUTF +[+] cpp fix 0x1f1b0 _JNIEnv::GetObjectClass +... ``` High Level IL: diff --git a/binary_ninja/jni_helper.py b/binary_ninja/jni_helper.py index 6178d48..9693462 100644 --- a/binary_ninja/jni_helper.py +++ b/binary_ninja/jni_helper.py @@ -4,7 +4,7 @@ from binaryninja import TypeParser, BinaryView from binaryninja.typeparser import TypeParserResult -from binaryninja.types import StructureMember +from binaryninja.types import StructureMember, FunctionType from binaryninja.function import Function from binaryninja.interaction import ( OpenFileNameField, @@ -20,6 +20,7 @@ def __init__(self, bv: BinaryView): self.bv = bv self.jni_header = "" self.pr: TypeParserResult = None + self.sigmap: Dict[str, FunctionType] = {} def start(self): log(f"plugin start, bv={self.bv}") @@ -43,25 +44,14 @@ def fix_cpp_symbols(self): if not funcs: log("not cpp library, skip") return - # load correct signatures - sigmap = {} - for iface in self.pr.types: - if iface.name == 'JNINativeInterface_': - break - for member in iface.type.members[4:]: - if isinstance(member, StructureMember): - sigmap[member.name] = member.type.children[0] - log("loaded {} JNI interface".format(len(sigmap))) for fn in funcs: - name = fn.symbol.short_name[9:] - vtype = sigmap.get(name) + vtype = self.sigmap.get(fn.symbol.short_name) if vtype is None: log(f"WARN: no signature for {name}") continue fn.type = vtype log(f"cpp fix 0x{fn.start:x} {fn.symbol.short_name}") - def init_header(self): jni_file = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "headers", "jni.h") if not os.path.exists(jni_file): @@ -73,13 +63,23 @@ def init_header(self): pr = self.parse_source(self.jni_header, "jni.h") if not pr: return False - log("init_header done.") + self.pr = pr + log("init_header success.") + for pt in self.pr.types: + self.bv.define_user_type(pt.name, pt.type) + if pt.name == 'JNINativeInterface_': + member: StructureMember = None + for member in pt.type.members[4:]: + name = f"_JNIEnv::{member.name}" + self.sigmap[name] = member.type.children[0] + log("loaded {} JNI interface".format(len(self.sigmap))) return True def parse_source(self, source, name=""): options = ["-fdeclspec"] result, errors = TypeParser.default.parse_types_from_source( source, name, self.bv.platform, + existing_types=self.bv, options=options ) if result is None: @@ -101,7 +101,7 @@ def apply_signatures(self): return with open(file, 'r') as f: meta = json.load(f) - jni_ext = self.jni_header + "\n" + decls = "" func_map: Dict[str, Function] = {} for cls, methods in meta["dexInfo"].items(): for method in methods: @@ -117,13 +117,10 @@ def apply_signatures(self): line = f"{ret} {mangle}({args})" if cls == "__COMMON__": continue - jni_ext += line + ";\n" - pr = self.parse_source(jni_ext, "jni_ext.h") + decls += line + ";\n" + pr = self.parse_source(decls, "jni_ext.h") if pr is None: return - self.pr = pr - for pt in pr.types: - self.bv.define_user_type(pt.name, pt.type) for pf in pr.functions: if pf.name not in func_map: continue