逆向技能+2 JNI介绍

http://www.cnblogs.com/devinzhang/archive/2012/02/29/2373729.html
关于NDK的介绍如上:

JNI

what 什么是JNI?

*java native interface native 本地语言  系统是由什么开发的 linux是由c开发的 那么这种语言对于这个系统来说就是本地语言

*native 本地语言 本地代码

*作用是java 和c/c++相互调用

jni 实质是一个接口 java的方法调用c的函数
jni 相当于是 把c/c++函数翻译成java
也可以把java翻译成c/c++代码

why 为什么用jni?

*java的优势 一处编译 到处运行 java虚拟机跨平台

  *java想直接访问系统底层的驱动 

  *android里的java代码都是跑在虚拟机里面的  

  *不能直接控制驱动  jni扩展java代码的能力   驱动大部分是由c编写的

  *特斯拉车载智能系统就是用的linux --按下按钮登就开了。

  *c编译生成的就是机器码  java需要虚拟机翻译(解释型语言--效
率低) 大型的3d游戏或者是音视频解码就不能用java

  *android系统是16ms刷新一次屏幕 通过JNI 放到C或者C++来实现

  *抗反编译的能力加强  加密的登录逻辑一定要放在c里面如密文和算法   eg拿到上传包+源码加密逻辑  即可破解 
  
开发者平时编写android时。framwork层连接函数库层也是用的JNI(Native Developer Kit) 
只是google已经提开发者封装好了,提供给开发者一个API。所以我们也用JNI,只是没有感觉到而已。

how 怎么用JNI

会用 java
c/c++ 能看懂,会调用。
interface JNI的开发流程

交叉编译

—在一个平台上编译出另一个平台可以运行的本地代码

cpu平台

arm架构 x86(Atom) mips
不同架构支持的指令集是有差别的
精简指令和复杂指令集
##操作系统平台
windows linux Mac os unix
模拟另一个平台的特点进行编译

jni开发工具

NDk native develop kit 本地开发工具
NDK的目录

> 引用块内容

结构
docs 帮助文档
platforms 根据不同的android版本分了不同的文件夹
--include  jni开发常用的头文件
--lib  google官方提供 jni开发中可能用到的库
build\tools   .sh文件linux下批处理文件 系统自动调用 开始交叉编译的过程
samples  样例
sources  ndk相关源码
ndk-build.cmd 开始交叉编译的命令

CDT 高亮显示c关键字 c代码提示

jni helloworld

新建一个android工程JNI_01
先写JAVA代码 用native关键字 声明本地方法 本地方法不用实现 在c中实现

看源码

JNIEnv 是一个别名 上面是结构体的指针

#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

    jint        (*GetVersion)(JNIEnv *);

这个是函数体的指针
通过JNIEnv调用

再写玩c之后用ndk-build构建头文件
cd /d 项目所在的目录即可构建头文件
编辑后报错说缺少android.mk文件
补充上mk文件makefile文件

    #找到jni目录的路径
    LOCAL_PATH := $(call my-dir)
    #清除上一次编译的local_Path不会被清除
   include $(CLEAR_VARS)
    #编译生成的文件的名字
 LOCAL_MODULE    := hello
    #本地资源文件,指定.c的文件   hello.c hello2.c
         LOCAL_SRC_FILES := hello.c 
    #指定编译动态链接库.so(linux)  .dll(windows)
         include $(BUILD_SHARED_LIBRARY)

在cd到项目目录下 用ndk-build再编译

jni开发常见错误

natvie method not found
1.本地函数名字 不符合命名规则
方法名或者包名有下滑线的需要进行+1操作
hello____1111world11___JNi();
用javah生成头文件
如果是jdk1.7以上的就到项目的src目录下看
如果是jdk1.6以下的就到bin/classes在哪运行头文件就到哪去找
2. System.loadlibrary("hello");忘记写了

loader ....findlibrary returned null

只截取lib之后的modle libhello

1.System.loadlibrary("libhello")
2.cpu平台不支持(比如  把你个只支持arm处理器的.so文件部署到x86架构下)
这时候就需要解决需要生成x86的so文件---途径如下:
建立一个Application.mk 导入APP_ABI  :x86 armeabi 
也可解决mindsdkversion的问题
APP_PLATFORM :=android-14   #解决警告
再用ndk-build重新编译一次

jni开发简便流程

先写java代码 用native关键字 声明本地方法 本地方法不需要实现

添加本地支持  选择 adroid toos -》add native support
有的需要 点击window -》preferencs -》android 》ndk

会自动生成jni目录

再用javah生成头文件

实例分析

以前常见的卸载 弹窗
https://raw.githubusercontent.com/venshine/AppUninstall/master/app/src/main/cpp/uninstall.cpp

/*
 * Copyright (C) 2016 venshine.cn@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <jni.h>
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <android/log.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/system_properties.h>

#define TAG "venshine"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

static const char APP_DIR[] = "/data/data/com.wx.appuninstall";
static const char APP_FILES_DIR[] = "/data/data/com.wx.appuninstall/files";
static const char APP_OBSERVED_FILE[] = "/data/data/com.wx.appuninstall/files/observedFile";
static const char APP_LOCK_FILE[] = "/data/data/com.wx.appuninstall/files/lockFile";
static const char *HOST_ADDR = "www.baidu.com";
static const char *SERVER_ADDR = "http://www.baidu.com";
static const int OK = 0;
static const int ERROR = -1;
int watchDescriptor;
int fileDescriptor;
pid_t observer = -1;

#ifdef __cplusplus
extern "C" {
#endif

/**
 * 获取SDK版本号
 */
int get_sdk_version();

/**
 * Jstring转char*
 */
char *JstringToCStr(JNIEnv *env, jstring jstr) ;

/**
 * 上传统计数据
 */
int uploadStatData(char* versionName, jint versionCode);

/**
 * 监听
 */
int startObserver(void *p_buf);

/**
 * 判断是否进程活着
 */
int isProcessAlive(const char *pid);

/**
 * 记录pid
 */
void writePidFile(const char *pid);

/**
 * 监控程序
 */
jint Java_com_wx_appuninstall_Uninstall_watch(
        JNIEnv *env, jobject thiz, jobject upload_obj) {

    if (upload_obj == NULL) {
        exit(1);
    }

    // 获得UploadInfo类引用
    jclass upload_cls = env->GetObjectClass(upload_obj);
    if (upload_cls == NULL) {
        exit(1);
    }

    // 判断监听进程是否活着
    if (isProcessAlive(APP_OBSERVED_FILE) == OK) {
        LOGE("watch process already exists");
        return observer;
    }

    // 若被监听文件存在,删除
    FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
    if (p_observedFile != NULL) {
        LOGD("delete observed file");
        remove(APP_OBSERVED_FILE);
        fclose(p_observedFile);
    }

    // 若被监听文件存在,删除
    FILE *p_LockedFile = fopen(APP_LOCK_FILE, "r");
    if (p_LockedFile != NULL) {
        LOGD("delete lock file");
        remove(APP_LOCK_FILE);
        fclose(p_LockedFile);
    }

    // 创建进程
    pid_t pid = fork();
//    prctl(PR_SET_NAME, "m.uninstall");
    // 根据返回值不同做不同操作
    if (pid < 0) {  // 创建进程失败
        LOGE("fork process error!");
    } else if (pid == 0) {  // 创建第一个子进程成功,代码运行在子进程中
        LOGD("fork first process succ pid = %d", getpid());
        setsid();  // 将进程和它当前的对话过程和进程组分离开,并且把它设置成一个新的对话过程的领头进程。
        umask(0);  // 为文件赋予更多的权限,因为继承来的文件可能某些权限被屏蔽
        int pid = fork();
//        prctl(PR_SET_NAME, "j.k.l.uninstall");
        if (pid == 0) { // 第二个子进程
            // 保存监听进程id
            LOGD("fork second process succ pid = %d", getpid());
            // 分配缓存,以便读取event,缓存大小等于一个struct inotify_event的大小,这样一次处理一个event
            void *p_buf = malloc(sizeof(struct inotify_event));
            if (p_buf == NULL) {
                LOGD("malloc failed !!!");
                exit(1);
            }
            // 通过linux中的inotify机制来监听应用的卸载。inotify是linux内核用于通知用户空间文件系统变化的机制,文件的添加或卸载等事件都能够及时捕获到。
            if (startObserver(p_buf) != 0) {
                return 0;
            }
            writePidFile(APP_OBSERVED_FILE);

            // 开始监听
            while (1) {
                LOGD("start watch");
                // 调用read函数开始监听,read会阻塞进程
                ssize_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event));

                // 走到这里说明收到目录被删除的事件
                if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask) {
                    LOGD("IN_DELETE_SELF");
                    // 若文件被删除,可能是已卸载,还需进一步判断app文件夹是否存在
                    FILE *p_appDir = fopen(APP_DIR, "r");
                    if (p_appDir != NULL) {
                        // 应用主目录还在(可能还没有来得及清除),sleep一段时间后再判断
                        sleep(5);
                        p_appDir = fopen(APP_DIR, "r");
                    }
                    // 确认已卸载
                    if (p_appDir == NULL) {
                        LOGD("inotify rm watch");
                        inotify_rm_watch(fileDescriptor, watchDescriptor);
                        break;
                    } else {  // 未卸载,可能用户执行了"清除数据"
                        LOGD("not uninstall");
                        fclose(p_appDir);
                        // 应用没有卸载,重新监听
                        if (startObserver(p_buf) != 0) {
                            return 0;
                        }
                    }

                } else {
                    LOGD("NOT IN_DELETE_SELF");
                }
            }
            LOGD("end watch");
            remove(APP_OBSERVED_FILE);
            remove(APP_LOCK_FILE);
            free(p_buf);

            jfieldID nameFieldID = env->GetFieldID(upload_cls, "versionName", "Ljava/lang/String;"); // 获得属性ID
            jfieldID codeFieldID = env->GetFieldID(upload_cls, "versionCode", "I"); // 获得属性ID
            jfieldID browserFieldID = env->GetFieldID(upload_cls, "isBrowser", "Z");   // 获得属性ID
            jstring versionName = (jstring) env->GetObjectField(upload_obj, nameFieldID);// 获得属性值
            jint versionCode = env->GetIntField(upload_obj, codeFieldID);  // 获得属性值
            jboolean isBrowser = env->GetBooleanField(upload_obj, browserFieldID);    // 获得属性值
            char *vName = JstringToCStr(env, versionName);

            // 上传统计数据
            if (uploadStatData(vName, versionCode) == OK) {
                LOGD("upload data succ");
            }

            // 是否打开浏览器
            if (isBrowser) {    // TODO 打开浏览器命令在有些手机上可能失效
                // 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)
                execlp("am", "am", "start", "--user", "0", "-a", "android.intent.action.VIEW", "-d",
                       SERVER_ADDR,
                       (char *) NULL);
            }

        } else {
            exit(0);
        }
    } else {
        // 父进程直接退出,使子进程被init进程领养,以避免子进程僵死,同时返回子进程pid
        LOGD("parent process exit");
    }
    return pid;
}

/**
 * 监听
 */
int startObserver(void *p_buf) {

    // 若监听文件所在文件夹不存在,创建文件夹
    FILE *p_filesDir = fopen(APP_FILES_DIR, "r");
    if (p_filesDir == NULL) {
        int filesDirRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH);
        if (filesDirRet == -1) {
            LOGE("create app files dir failed");
            exit(1);
        }
    }

//    if (access(APP_FILES_DIR, F_OK) != 0) {
//        LOGD("folder not exists");
//        if (mkdir(APP_FILES_DIR, 0755) == -1) {
//            LOGE("mkdir failed!");
//            exit(1);
//        }
//    }

    // 若被监听文件不存在,创建监听文件
    FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
    if (p_observedFile == NULL) {
        p_observedFile = fopen(APP_OBSERVED_FILE, "w");
        LOGD("create app observed file");
    }
    fclose(p_observedFile);

    // 创建锁文件,通过检测加锁状态来保证只有一个卸载监听进程
    int lockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY);
    if (lockFileDescriptor == -1) {
        lockFileDescriptor = open(APP_LOCK_FILE, O_CREAT);
        LOGD("create app lock file");
    }
    int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB);
    if (lockRet == -1) {
        LOGE("watch by other process");
        return ERROR;
    }

    // 初始化inotify进程
    fileDescriptor = inotify_init();
    if (fileDescriptor < 0) {
        LOGE("inotify init failed");
        free(p_buf);
        exit(1);
    }

    // 添加inotify监听器,监听APP_OBSERVED_FILE文件
    watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS);
    if (watchDescriptor < 0) {
        LOGE("inotify watch failed");
        free(p_buf);
        exit(1);
    }
    return OK;
}

/**
 * 上传统计数据
 */
int uploadStatData(char* versionName, jint versionCode) {
    LOGD("upload stat data");

    struct sockaddr_in serv_addr;
    struct hostent *host;
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    if ((host = gethostbyname(HOST_ADDR)) == NULL) {
        LOGE("host name is null.");
        return ERROR;
    }

    memset(&serv_addr, 0, sizeof(serv_addr));  // 每个字节都用0填充
    serv_addr.sin_family = AF_INET;  // 使用IPv4地址
//    serv_addr.sin_addr.s_addr = inet_addr("192.168.1.1");  // 具体的IP地址
    serv_addr.sin_addr = *((struct in_addr *) host->h_addr);
    serv_addr.sin_port = htons(80);  //端口

    if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        LOGE("connect error");
        return ERROR;
    }

    LOGD("connect succ");
    int sdkVersion = get_sdk_version();
    char request[200];
    sprintf(request, "GET /web/index.html?versionName=%s&versionCode=%d&sdkVersion=%d", versionName,
            versionCode, sdkVersion);
    if (write(sock, request, strlen(request)) < 0) {
        LOGE("request failed");
        return ERROR;
    }
    LOGD("request success");
    // 关闭套接字
    close(sock);
    return OK;
}

/**
 * 判断是否进程活着
 */
int isProcessAlive(const char *pid) {

    FILE *pidFile;
    char observerPID[32];
    if ((pidFile = fopen(pid, "rb")) == NULL) {
        LOGE("can't open pid file");
        return ERROR;
    }
    // fread(&observerPID,sizeof(observerPID),1,pidFile);
    fscanf(pidFile, "%d", &observer);
    fclose(pidFile);
    if (observer > 1) {
        sprintf(observerPID, "%d/n", observer);
        LOGD("read saved pid");

        if (kill(observer, 0) == 0) {
            LOGD("process is alive");
            return OK;
        }

        LOGD("process is not alive");
    } else {
        LOGD("not read saved pid");
        return ERROR;
    }
}

/**
 * 记录pid
 */
void writePidFile(const char *pid) {
    char str[32];
    int pidFile = open(pid, O_WRONLY | O_TRUNC);
    if (pidFile < 0) {
        LOGE("pid is %d", pidFile);
        exit(1);
    }

    if (flock(pidFile, LOCK_EX | LOCK_NB) < 0) {
        LOGD("cann't lock pid file: %s", pid);
        fprintf(stderr, "can't lock pid file: %s", pid);
        exit(1);
    }

    sprintf(str, "%d/n", getpid());
    ssize_t len = strlen(str);
    ssize_t ret = write(pidFile, str, len);

    if (ret != len) {
        LOGE("can't write pid file: %s", pid);
        fprintf(stderr, "can't write pid file: %s", pid);
        exit(1);
    }
    close(pidFile);
    LOGD("write pid file success");
}

/**
 * 获取SDK版本号
 */
int get_sdk_version() {
    char value[8] = "";
    __system_property_get("ro.build.version.sdk", value);
    return atoi(value);
}

/**
 * Jstring转char*
 */
char *JstringToCStr(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = (*env).FindClass("java/lang/String"); //String
    jstring strencode = (*env).NewStringUTF("GB2312"); // 得到一个java字符串 "GB2312"
    jmethodID mid = (*env).GetMethodID(clsstring, "getBytes",
                                       "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
    jbyteArray barr = (jbyteArray) (*env).CallObjectMethod(jstr, mid,
                                                           strencode); // String .getByte("GB2312");
    jsize alen = (*env).GetArrayLength(barr); // byte数组的长度
    jbyte *ba = (*env).GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    (*env).ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

#ifdef __cplusplus
}
#endif

代码总结:

  1. bytes to bytesarray to char
/**
 * Jstring转char*
 */
char *JstringToCStr(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = (*env).FindClass("java/lang/String"); //String
    jstring strencode = (*env).NewStringUTF("GB2312"); // 得到一个java字符串 "GB2312"
    jmethodID mid = (*env).GetMethodID(clsstring, "getBytes",
                                       "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
    jbyteArray barr = (jbyteArray) (*env).CallObjectMethod(jstr, mid,
                                                           strencode); // String .getByte("GB2312");
    jsize alen = (*env).GetArrayLength(barr); // byte数组的长度
    jbyte *ba = (*env).GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    (*env).ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}

2.子进程离开父进程

3.native传数据

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值