Skip to content

Commit

Permalink
bn: use existing_types to avoid loading jni.h multiple times
Browse files Browse the repository at this point in the history
  • Loading branch information
evilpan committed Oct 23, 2024
1 parent 079c165 commit 880bb01
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 87 deletions.
68 changes: 13 additions & 55 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -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/[email protected]
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Before | After
:----------:|:------------:
![i1][i1] | ![i2][i2]

## Binary Ninja
## Binary Ninja Plugin

see [Binary Ninja](./binary_ninja).

Expand Down
26 changes: 15 additions & 11 deletions binary_ninja/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ File -> Run Script
# Logging

```
[+] plugin start, bv=<BinaryView: '/root/jni_helper/assets/lib/arm64-v8a/libdemoc.so', len 0x2a88>
[+] 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=<BinaryView: '/root/jni_helper/assets/lib/arm64-v8a/libdemocpp.so', len 0x4aa08>
[+] 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:
Expand Down
37 changes: 17 additions & 20 deletions binary_ninja/jni_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}")
Expand All @@ -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):
Expand All @@ -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="<source>"):
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:
Expand All @@ -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:
Expand All @@ -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
Expand Down

0 comments on commit 880bb01

Please sign in to comment.