JNI 開發(fā)砸民,JNI開發(fā)

0. LOG 打印

首先:Android.mk 中添加 llog

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS += -llog
LOCAL_MODULE := demo
LOCAL_SRC_FILES := demo.c
include $(BUILD_SHARED_LIBRARY)

再:

include <android/log.h>

然后:
__android_log_print(ANDROID_LOG_INFO,"Wooo","--------1--------");

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)

調(diào)用:
LOGI("gMethods %s,%s,%p\n ",gMethods[0].name,gMethods[0].signature,gMethods[0].fnPtr);

1. 聲明

導(dǎo)入

#include <jni.h>
#include <android/log.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include "mycommom.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>

#include "packer.h"
#include <jni.h>
#include <dlfcn.h>
#include <android/log.h>
#include <errno.h>
#include <assert.h>
#include <sys/system_properties.h>
#include <string>
#include <cstdlib>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <vector>
#include <bits/unique_ptr.h>

基本類型

jclass myLoadedApk;     // 聲明類
jfieldID  LoadedApk_mClassLoader;   // 聲明 FieldID
jmethodID Application_onCreate;     // 聲明 MethodID
jint sdk_int;       // 聲明 int
jobject mObj;   // 聲明 Object
JNINativeMethod *dvm_dalvik_system_DexFile;     // 聲明本地方法
JValue* pResult;    // 聲明的序列化

還有基本類型:

jbyte栏饮、jboolean、jchar、jshort绪撵、jint、jlong祝蝠、jfloat音诈、jdouble

void 標(biāo)識無類型

對應(yīng)的數(shù)組類型有:

jstring、jobjectArray绎狭、jbooleanArray细溅、jbyteArray、jcharArray儡嘶、jshortArray喇聊、jintArray、jlongArray蹦狂、jfloatArray誓篱、jdoubleArray朋贬、jthrowable

以上類型對應(yīng)的 smali 及對應(yīng)的簽名:

char[]      [C
float[]     [F
double[]    [D
long[]      [J
String[]    [Ljava/lang/String;
Object[]    [Ljava/lang/Object;

剩余的簽名:
boolean     Z
short   S
byte    B
int     I
void    V

函數(shù)示例:
int fun1()      對應(yīng)簽名()I
int fun1(int i) 對應(yīng)簽名(I)I

2. JNINativeMethod 聲明

這個(gè)對應(yīng)這是一個(gè)結(jié)構(gòu)體:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;

第一個(gè)變量name是Java中函數(shù)的名字。
第二個(gè)變量signature窜骄,用字符串是描述了函數(shù)的參數(shù)和返回值 ((II)V)
第三個(gè)變量fnPtr是函數(shù)指針锦募,指向C函數(shù)。

  • 示例一(這個(gè)是自己聲明的本地方法):
void native_attachContextBaseContext(JNIEnv *env, jclass clazz,jobject ctx);
void native_onCreate(JNIEnv *env, jclass clazz);

static JNINativeMethod method_table[] = {
    { "attachBaseContext", "(Landroid/content/Context;)V", (void*)native_attachContextBaseContext},     // 聲明本地方法啊研,在類前面聲明
    { "onCreate","()V",(void*)native_onCreate}, // 觀察三個(gè)參數(shù)
};

以上返回的是 method_table 數(shù)組御滩,元素是 JNINativeMethod

  • 示例二(這里是調(diào)用第三方的 so 文件里的函數(shù)):
void (*openDexFile)(const u4* args,union  JValue* pResult); // 聲明方法類型,JValue 序列化聲明

void *ldvm = (void*) dlopen("libdvm.so", 1);    // 這個(gè)通過 dlopen 是找到 so 文件
JNINativeMethod* dvm_dalvik_system_DexFile = (JNINativeMethod*) dlsym(ldvm,"dvm_dalvik_system_DexFile");    // 這個(gè)是通過 dlsym 找到方法指針
if(0 == lookup(dvm_dalvik_system_DexFile, "openDexFile", "([B)I", &openDexFile))    // lookup 函數(shù)尋找函數(shù)党远,并賦予到 openDexFile 指針
{
    openDexFile = NULL;
    LOGI("method does not found ");
    return ;
}
else
{
    LOGI("openDexFile method found ! HAVE_BIG_ENDIAN");      
}

char* dexbase = (char*)mmap(0, dexLen, 3, 2, handle, 0);    // mmap 映射削解,handle 為文件描述符
char* arr;
arr=(char*)malloc(16+dexLen);
ArrayObject *ao=(ArrayObject*)arr;
ao->length=dexLen;
memcpy(arr+16,dexbase,dexLen);
munmap(dexbase, dexLen);    // 內(nèi)存映射解除
u4 args[] = { (u4) ao };
union JValue pResult;       // 聲明傳入?yún)?shù)
jint result;            // 返回值
if(openDexFile != NULL)
{
    openDexFile(args,&pResult);     // 函數(shù)調(diào)用
    result = (jint) pResult.l;      // 獲取返回值
}

int lookup(JNINativeMethod *table, const char *name, const char *sig,void (**fnPtrout)(u4 const *, union JValue *)) 
{
    int i = 0;
    while (table[i].name != NULL)   // 這個(gè)方法名字不等于 name
    {
        LOGI("lookup %d %s" ,i,table[i].name);
        if ((strcmp(name, table[i].name) == 0)
            && (strcmp(sig, table[i].signature) == 0)) 
        {
            *fnPtrout = table[i].fnPtr;     // 賦予函數(shù)指針
            return 1;
        }
        i++;
    }
    return 0;
}

3. 方法注冊

首先聲明方法,然后調(diào)用函數(shù)注冊

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);      // 在 JNI_OnLoad 內(nèi)開始注冊
#define JNIREG_CLASS "com/example/unpack/StubApplication"   // 聲明類
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))      // 用于算出方法數(shù)組包含參數(shù)

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = 0;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {  // 從當(dāng)前運(yùn)行環(huán)境中取出版本字符串沟娱,判斷 jni 合法性
        return result;
    }
    int status=register_ndk_load(env);
    if(!status)
    {
        LOGI("register call failed");
    }
    return JNI_VERSION_1_4;
}

static JNINativeMethod method_table[] = {       // 方法數(shù)組
    { "attachBaseContext", "(Landroid/content/Context;)V", (void*)native_attachContextBaseContext},
    { "onCreate","()V",(void*)native_onCreate},
};

int register_ndk_load(JNIEnv *env)
{
    
    return registerNativeMethods(env, JNIREG_CLASS, method_table, NELEM(method_table)); // 傳入?yún)?shù)氛驮,環(huán)境、類名济似、方法數(shù)組矫废、方法數(shù)量
}

static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == 0) {
        return JNI_FALSE;
    }
    LOGI("gMethods  %s,%s,%p\n ",gMethods[0].name,gMethods[0].signature,gMethods[0].fnPtr);
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {    // 方法注冊
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

4. 尋找類

  1. 函數(shù)調(diào)用,返回類對象到 myContextWrapper 指針內(nèi)
if( !myFindClass(env,&Build_version,"android/os/Build$VERSION"))
{
    LOGI("ERROR:Build$VERSION");
    return;
}

jfieldID fieldID= ((*env)->GetStaticFieldID)(env, Build_version, "SDK_INT", "I");   // 函數(shù)調(diào)用砰蠢,獲取 field
sdk_int=(*env)->GetStaticIntField(env,Build_version,fieldID);       // 獲取 version_id
LOGI("sdk_int %d\n",sdk_int); 

實(shí)現(xiàn)函數(shù):
jclass myFindClass(JNIEnv *env,jclass* ptr,char* name)
{
     jobject g_cls_string;
     jclass clazz = (*env)->FindClass(env,name);
     if(clazz)
     {
        g_cls_string = (*env)->NewGlobalRef(env,clazz);
        *ptr=g_cls_string;
        return g_cls_string;
     }
     else
     {
          return 0;
     }
}
  1. 根據(jù) object 獲取類
jclass v19 = (*env)->GetObjectClass(env, application_obj);  //獲取 application_obj 為類對象
  1. 獲取父類
    jclass BaseDexClassLoader = env->GetSuperclass(PathClassLoader);

  2. 新建一個(gè)對象
    由方法 ID 和 class 對象獲取一個(gè)新的 obiect 對象

jstring newapp= (*env)->NewStringUTF(env, "android.app.Application");
jobject findclass_classz = (*env)->CallObjectMethod(env, classLoader,classLoader_findClass, newapp);
jmethodID initMethod = (*env)->GetMethodID(env, findclass_classz, "<init>", "()V");
onCreateObj = (*env)->NewObject(env, findclass_classz, initMethod);

5. 獲取 MethodID 并調(diào)用

獲取 MethodID
Application_onCreate = (*env)->GetMethodID(env, myApplication, "onCreate", "()V");
// myApplication 指的是 Application 類蓖扑,onCreate 方法名, ()V 方法簽名

獲取 StaticMethodID
currentActivityThread = (*env)->GetStaticMethodID(env, ActivityThread,"currentActivityThread","()Landroid/app/ActivityThread;");
// ActivityThread 類台舱,currentActivityThread 方法名律杠, ()Landroid/app/ActivityThread; 方法簽名

調(diào)用返回值是 int 類型的函數(shù)
int count = (*env)->CallIntMethod(env, v11, arraylist_size);

調(diào)用返回值是 Object 類型的靜態(tài)函數(shù)
jobject tmp= (*env)->CallStaticObjectMethod(env, mySystem, system_getProperty, vmname);
// mySystem 指 java/lang/System 類,system_getProperty 指 getProperty(Ljava/lang/String;)Ljava/lang/String; 方法竞惋,vmname 為傳入的字符串參數(shù)

調(diào)用非靜態(tài)函數(shù)
jobject v9 = (*env)->CallObjectMethod(env, arraymap_mPackages, ArrayMap_get, thePackagename);
// arraymap_mPackages 為數(shù)組柜去,ArrayMap_get 為方法 ID,thePackagename 是 key 值

調(diào)用無返回值函數(shù)
(*env)->CallNonvirtualVoidMethod(env, application_obj, myContextWrapper, ContextWrapper_attachBaseContext,ctx);
// application_obj 為默認(rèn)傳入的 obj拆宛,myContextWrapper 是 "android/content/ContextWrapper" 類嗓奢,ContextWrapper_attachBaseContext 是 attachBaseContext(Landroid/content/Context;)V 方法,ctx 是傳入?yún)?shù)

調(diào)用無返回值函數(shù)
(*env)->CallVoidMethod(env, onCreateObj, Application_onCreate);
// onCreateObj 是 Application 對象浑厚,Application_onCreate 是方法 ID

6. 獲取 FieldID 并設(shè)置

獲取一個(gè)類內(nèi)的一個(gè)變量 FieldID
mPackages=(*env)->GetFieldID(env, ActivityThread, "mPackages", "Landroid/util/ArrayMap;");
// ActivityThread 類對象股耽,mPackages 變量名,Landroid/util/ArrayMap 變量類型钳幅、簽名

根據(jù) ID 獲取一個(gè)類實(shí)例內(nèi)的變量值
jobject arraymap_mPackages =(*env)->GetObjectField(env, theCurrentActivityThread, mPackages);
// theCurrentActivityThread 為一個(gè)類調(diào)用方法返回的對象豺谈,mPackages 為 FieldID
// theCurrentActivityThread 是一個(gè)對象

設(shè)置 Field 變量
(*env)->SetObjectField(env, DexFile, mCookie, art_MarCookie);
// DexFile 是要設(shè)置到的主體對象, mCookie 是對應(yīng)的 FieldID(對應(yīng) DexFile 內(nèi)的一個(gè)屬性)贡这,art_MarCookie 是要設(shè)置到 mCookie 的對象。就等同于:DexFile.mCookie.set(art_MarCookie)
示例如下:

DexPathList_Element_dexFile = (*env)->GetFieldID(env,myElement,"dexFile","Ldalvik/system/DexFile;");
jobject Element = (*env)->GetObjectArrayElement(env, v15, i);
jobject DexFile = (*env)->GetObjectField(env, Element, DexPathList_Element_dexFile);
mCookie = (*env)->GetFieldID(env,myDexFile, "mCookie", "I");
art_MarCookie=(*env)->CallStaticObjectMethod(env, myDexFile, myOpenDexFile, inPath,0,0);
(*env)->SetObjectField(env, DexFile, mCookie, art_MarCookie);

設(shè)置 int
env->SetIntField(mini_dex_obj, cookie_field, mCookie);

7. 字符串處理

格式標(biāo)準(zhǔn)化
jstring thePackagename=(*env)->NewStringUTF(env,mPackageName);

將字符串轉(zhuǎn)為 char 數(shù)組
const char* v22 = (*env)->GetStringUTFChars(env, tmp, 0); // tmp 為字符串

LOGI("------- vmNameStr:%s", v22);
(*env)->ReleaseStringUTFChars(env, tmp, v22); // 函數(shù)通知 JVM 這塊內(nèi)存已經(jīng)不使用了厂榛,你可以清除了

拼接字符串
char szPath[260]={0}; // 申請空間
sprintf(szPath,"/data/data/%s/files/dump.dex",mPackageName); // 字符串輸出為 szPath
// const char* mPackageName; 類型

設(shè)置 vm 的 APKPATH
setenv("APKPATH", mPackageResourePath, 1); // mPackageResourePath 為路徑字符串

8. 數(shù)組的獲取處理

獲取一個(gè)數(shù)組的長度
int count = (*env)->GetArrayLength(env, v15);
// v15 是一個(gè)數(shù)組

遍歷一個(gè)數(shù)組
while(i<count)
{
jobject Element = (*env)->GetObjectArrayElement(env, v15, i);
}

9. 刪除占用內(nèi)存

env->DeleteLocalRef(DexFileClass); // DexFileClass 是 jclass
env->DeleteLocalRef(apk); // apk 是 jstring

10. 一些小功能

1. 讀取 maps 獲取信息

char* get_path_frommaps(const char* pkgName,char* filter1,char* filter2)
{
int pid=getpid();
char map[256]={0};
sprintf(map,"/proc/%d/maps",pid);
FILE* fd=fopen(map,"r");
if(!fd){
    LOGE("[-]open %s failed",map);
    return NULL;
}
while(true){
    char line_buffer[256]={0};
    if(!fgets(line_buffer,255,fd))
        break;
    //尋找?guī)в邪?dex或.odex的行
    if (strstr(line_buffer,pkgName) && (strstr(line_buffer,filter1)|| strstr(line_buffer,filter2))) {
        LOGD("[+]targt line buffer:%s",line_buffer);
        char* p =strchr(line_buffer,'/');
        //獲取需要復(fù)制的長度
        //line_buffer結(jié)尾是一個(gè)換行符
        int copy_len=strlen(line_buffer)-(p-(char*)line_buffer)-1;
        memcpy((void*)g_fake_dex_path,p,copy_len);
        LOGD("[+]find dex path:%s from maps",g_fake_dex_path);
        return g_fake_dex_path;
    }
}
fclose(fd);
return NULL;
}

2. 調(diào)用 libart.so 內(nèi)的方法

{
std::string location = "";
std::string err_msg;
void* g_ArtHandle=NULL;
g_ArtHandle=get_lib_handle("libart.so");    // 獲取 so 句柄

org_artDexFileOpenMemory22 func = (org_artDexFileOpenMemory22)dlsym(g_ArtHandle, "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_");

if(!func){
    LOGE("[-]sdk_int:%d dlsym openMemory failed:%s",g_sdk_int,dlerror());
    return NULL;
}
const Header* dex_header = reinterpret_cast<const Header*>(base);
void *value = func((const unsigned char *)base, size, location, dex_header->checksum_, NULL, NULL, &err_msg);       // 方法調(diào)用盖矫。size 是 dex 的大小
if(!value){
    LOGE("[-]call artDexFileOpenMemory22 failed");
    return NULL;
}
LOGD("[+]openMemory value:%p",value);
return value;
}

void* get_lib_handle(const char* lib_path)
{
    void *handle_art = dlopen(lib_path, RTLD_NOW);
    if (!handle_art) {
        LOGE("[-]get %s handle failed:%s",dlerror());
        return NULL;
    }
    return handle_art;
}

.h 內(nèi)聲明方法類型

typedef void* (*org_artDexFileOpenMemory22)(const uint8_t* base, size_t size,const std::string& location, uint32_t location_checksum,void* mem_map, const void*oat_file, std::string* error_msg);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丽惭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辈双,更是在濱河造成了極大的恐慌责掏,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湃望,死亡現(xiàn)場離奇詭異换衬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)证芭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門瞳浦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人废士,你說我怎么就攤上這事叫潦。” “怎么了官硝?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵矗蕊,是天一觀的道長。 經(jīng)常有香客問我氢架,道長傻咖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任岖研,我火速辦了婚禮卿操,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缎玫。我一直安慰自己硬纤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布赃磨。 她就那樣靜靜地躺著筝家,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邻辉。 梳的紋絲不亂的頭發(fā)上溪王,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音值骇,去河邊找鬼莹菱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吱瘩,可吹牛的內(nèi)容都是我干的道伟。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜜徽!你這毒婦竟也來了祝懂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拘鞋,失蹤者是張志新(化名)和其女友劉穎砚蓬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盆色,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡灰蛙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隔躲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摩梧。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蹭越,靈堂內(nèi)的尸體忽然破棺而出障本,到底是詐尸還是另有隱情,我是刑警寧澤响鹃,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布驾霜,位于F島的核電站,受9級特大地震影響买置,放射性物質(zhì)發(fā)生泄漏粪糙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一忿项、第九天 我趴在偏房一處隱蔽的房頂上張望蓉冈。 院中可真熱鬧,春花似錦轩触、人聲如沸寞酿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伐弹。三九已至,卻和暖如春榨为,著一層夾襖步出監(jiān)牢的瞬間惨好,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工随闺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留日川,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓矩乐,卻偏偏與公主長得像龄句,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359