探索黑客技术攻防,实战研究与安全创新

导航菜单

在Android系统中直接调用SO文件

Android虚拟机不能直接调用底层设备,如果要对底层设备进行调用,就需要用到so使用C语言或C++编写完成,利用NDK进行编译,直接运行在Linux内核中,按jni调用so时,基本类型可以直接交互。

在apk里打包进.so文件的方法有两种,一是在Android.mk文件里增加“LOCAL_JNI_SHARED_LIBRARIES:=libxxx”,这样在编译的时候,NDK自动会把这个libxxx打包进apk,放在xxx/lib/目录下;二是在应用的目录下手工建libs/armeabi目录,然后把libxxx.so拷贝到这个目录下,NDK也会自动把这个libxxx.so打包进apk,位置还是在xxx/lib/目录下。

在代码里,使用System.loadLibrary(xxx);就可以加载这个动态库了。这里要注意,参数只写xxx就可以了,不需要写libxxx,也不需要写libxxx.so。

还有一点要说明,System.loadLibrary这个函数会在如下路径搜索libxxx.so文件:


/system/lib
/data/data/xxxapkpackage/lib


如果libxxx.so还依赖其它.so文件,比如libyyy.so,则System.loadLibrary只会在/system/lib目录下查找,如果没找到,不会自动到/data/data/xxxapkpackage/lib下去找,这个时候就会报动态库没找到的错误。解决方法是在loadlibxxx.so之前,先loadlibyyy.so,具体如下:


System.loadLibrary(yyy);
System.loadLibrary(xxx);


本文结合使用实例进行说明。

创建jni目录.libs目录

在工程根目录下创建jni目录,libs目录不用手动建立,如图1所示。注意,这里使用的是ndk_R7,所以不需要用jdk去生成C文件。


图片1.png

Java编写接口文件(Device.java)

Device.java的代码实现如下:


publicclassDevice{
static{
System.loadLibrary(device);
}
publicnativeStringdeviceTestString(Stringtest);
}


方法名必须使用native关键字声明,并且必须使用system.loadLibrary(SO文件名)承载C类库。

编写C文件(devices.c)

这里编写的C代码属于LinuxC范畴,实现代码如下:


#includestring.h
#includejni.h
char*jstringTostrM(JNIEnv*env,jstringjstr)
{
char*pStr=NULL;
jclass
jstrObj
encode
=(*env)-FindClass(env,java/lang/String);
=(*env)-NewStringUTF(env,utf-8);
jstring
jmethodID
methodId
=
(*env)-GetMethodID(env,
jstrObj,
getBytes,
(Ljava/lang/String;)[B);
jbyteArraybyteArray=(jbyteArray)(*env)-CallObjectMethod(env,jstr,methodId,
encode);
jsize
strLen
=(*env)-GetArrayLength(env,byteArray);
jbyte
*jBuf=(*env)-GetByteArrayElements(env,byteArray,
JNI_FALSE);
if(jBuf0)
{
pStr=(char*)malloc(strLen+1);
if(!pStr)
{
returnNULL;
}
memcpy(pStr,jBuf,strLen);
pStr[strLen]=0;
}
(*env)-ReleaseByteArrayElements(env,byteArray,jBuf,0);
returnpStr;
}
jstringJava_com_jack_Device_deviceTestString(JNIEnv*env,jclassclazz,jstringpath){
//system(echodevices.sotest/sdcard/log/log.txt);
char*test=jstringTostrM(env,path);
return(*env)-NewStringUTF(env,test);
}


注意C的函数命名规则,Java的jni标准必须有,com_jack_Device是Device.java文件的全名,再下来才是C函数名,jstringTostrM函数必须写在Java_com_jack_Device_deviceTestString函数前,如果不是,必须要在C文件头进行声明,声明代码为:


char*jstringTostrM(JNIEnv*env,jstringjstr);


编写Android.mk和编译android.mk

Android.mk的代码如下:


LOCAL_PATH:=$(callmy-dir)
include$(CLEAR_VARS)
LOCAL_MODULE:=device
LOCAL_SRC_FILES:=device.c
include$(BUILD_SHARED_LIBRARY)


如果要编译成可执行文件,还需包含代码include$(BUILD_EXECUTABLE),之后按照图2所示编译即可。

图片2.png

编写Java代码进行C函数调用


Devicedevice=newDevice();
Stringtest=device.deviceTestString(你好~!!!);
Toast
toast
=
Toast.makeText(Jack_ndk_jstringActivity.this,test,
Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP,0,150);
toast.show();
TextViewtext=(TextView)findViewById(R.id.text1);
text.setText(test);
注意,最后在AndroidManifest.xml文件中要加入文件控制权限,代码如下:
!--文件权限--
uses-permission
android:name=android.permission.MOUNT_UNMOUNT_FILESYSTEMS/
uses-permissionandroid:name=android.permission.WRITE_EXTERNAL_STORAGE/


(完)