so文件调用
随着Android移动安全的高速发展,不管是为了执行效率还是程序的安全性等,关键代码下沉native层已成为基本操作。
native层的开发就是通指的JNI/NDK开发,通过JNI可以实现java层和native层(主要是 C/C++)的相互调用,native层经编译后产生so动态链接库,so文件具有可移植性广,执行效率高,保密性强等优点。
那么问题来了,如何调用so文件显得异常重要,当然你也可以直接分析so文件的伪代码,利用强悍的编程功底直接模拟关键操作,但是我想对于普通人来说头发还是比较重要的。
当前调用so文件的主流操作应该是:
1,基于Unicorn的各种实现(还在学习中,暂且不表)
2,Android服务器的搭建,在app内起http服务完成调用so的需求(当然前提是过了so的效验等操作)
至于为什么选用AndServer,好吧,不为什么,只是因为搜索到了它
为什么结合Service,在学习Android开发的时候了解到了Service的生命周期,个人理解用Service去创建Http服务比较好。
当然也有Application的简单使用,因为在正式环境中,大多数so文件的逻辑中都有context的一些包名了,签名了的效验等,自定义Application的话获取context传参就好了。
libyemu.so简介
这是我编译好的一个so文件,就是根据入参做下简单的字符串拼接(以下是native层编译前的c代码)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_fw_myapplication_ndktest_NdkTest_stringFromUTF(JNIEnv *env, jobject instance, jstring str_) {
jclass String_clazz = env->FindClass("java/lang/String");
jmethodID concat_methodID = env->GetMethodID(String_clazz, "concat", "(Ljava/lang/String;)Ljava/lang/String;");
jstring str = env->NewStringUTF(" from so --[NightTeam夜幕]");
jobject str1 = env->CallObjectMethod(str_, concat_methodID, str);
const char *chars = env->GetStringUTFChars((jstring)str1, 0);
return env->NewStringUTF(chars);
}
这部分代码还是有必要贴一下的,简单的静态注册使用了反射的思想,反射在逆向中至关重要 接下来是java代码,定义了native函数
package com.fw.myapplication.ndktest;
public class NdkTest {
public static native String stringFromUTF(String str);
static {
System.loadLibrary("yemu");
}
}
如果到这里有点懵逼的同学可能需要去补下Android开发基础了
Android项目测试so
先说下我的环境,因为这个环境影响太大了
1,AndroidStudio 3.4
2,手机 Android 6 架构 armeabi-v7a
打开AndroidStudio 新建project
把编译好的so文件复制到libs文件夹下(和刚才的jniLibs.srcDirs对应)
把so对应的java代码也copy过来,注意包名类名的一致性
打开activity_main.xml文件为TextView添加id
打开MainActiviy.java开始编码
这两行的意思就是,先从布局中找到对应id的TextView,然后为其设置Text(调用native函数的返回值)
下面测试一下咱们的so调用情况
可以看到咱们的so文件调用成功(这里咱们的so没有效验,只是测试app是否可以正常调用)
AndServer代码编写
AndServer官方文档:yanzhenjie.com/AndServer/
打开官方文档,看看人家的入门介绍,新建java文件
package com.nightteam.httpso;
import android.app.Application;
public class MyApp extends Application {
private static MyApp myApp;
public static MyApp getInstance() {
return myApp;
}
@Override
public void onCreate() {
super.onCreate();
myApp = this;
}
}
然后在manifest文件中指定要启动的Application
接下来把官方文档-服务器的代码copy下来
导入一些包,修改部分代码如下
新版本的AndServer.serverBuilder已经需要传递context了,这里把网络地址和端口号也修改为从构造参数中获取,到这里AndServer的东西基本完了,实际上咱们就搭建一个调so的接口,并没有过多的业务逻辑,所以代码就是使用的最简单的
Service代码编写
咱们这里用按钮的点击事件启动Service,故在activity_main.xml中添加一个button并指定点击事件
package com.nightteam.httpso.Service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import com.nightteam.httpso.ServerManager;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class MyService extends Service {
private static final String TAG = "NigthTeam";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: MyService");
new Thread() {
@Override
public void run() {
super.run();
InetAddress inetAddress = null;
try {
inetAddress = InetAddress.getByName("0.0.0.0");
Log.d(TAG, "onCreate: " + inetAddress.getHostAddress());
ServerManager serverManager = new ServerManager(getApplicationContext(), inetAddress, 8005);
serverManager.startServer();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}.start();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
打上了几个log,在子线程中启动AndServer的服务(何时使用UI线程和子线程是Android基础,这里就不赘述了)
注意一下,这里从0.0.0.0获取inetAddress,可不要写错了,localhost和0.0.0.0的区别请移步搜索引擎
然后就是向ServerManager的构造函数传递context,inetAddress,port用来new对象,随后开启服务
最后注意检查下manifest文件中Service的声明
开启Service,并获取本机ip
回到我们的MainActivity.java的operate(button的点击事件)编写启动Service代码
public void operate(View view) {
switch (view.getId()){
case R.id.id_bt_index:
//启动服务:创建-->启动-->销毁
//如果服务已经创建了,后续重复启动,操作的都是同一个服务,不会再重新创建了,除非你先销毁它
Intent it1 = new Intent(this, MyService.class);
Log.d(TAG, "operate: button");
startService(it1);
((Button) view).setText("服务已开启");
break;
}
}
到这里我们的服务基本搭建好了,但是为了方便起见,我想把咱们的本机ip显示在app上,这样我们就不用去设置再查看了
我在网上找到了一个获取ip地址的一个工具类,源码如下:
package com.nightteam.httpso;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.regex.Pattern;
public class NetUtils {
private static final Pattern IPV4_PATTERN = Pattern.compile("^(" +
"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" +
"([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
private static boolean isIPv4Address(String input) {
return IPV4_PATTERN.matcher(input).matches();
}
//获取本机IP地址
public static InetAddress getLocalIPAddress() {
Enumeration<NetworkInterface> enumeration = null;
try {
enumeration = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
e.printStackTrace();
}
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
NetworkInterface nif = enumeration.nextElement();
Enumeration<InetAddress> inetAddresses = nif.getInetAddresses();
if (inetAddresses != null)
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (!inetAddress.isLoopbackAddress() && isIPv4Address(inetAddress.getHostAddress())) {
return inetAddress;
}
}
}
}
return null;
}
}
把工具类copy到我们的Android项目中,继续在MainActivity.java中编码
申请权限,启动App
最后一步就是为app申请网络权限了
看下AndroidStudio日志
好像一切正常,在浏览器访问下试试(ip就是app中显示的ip地址)
如图正常访问到了我们想要的内容 回过头来说下Service,打开我们手机的设置,找到应用程序管理-运行中的服务(手机不同,方式不同) 可以看到我们的程序,运行了一个服务,这个服务就是咱们编码的MyService 接下来杀掉该App进程,再次查看运行中的服务 我这里在权限管理设置了自动运行,可以保持服务的运行。(这个地方还是根据系统有大小差异) 至此使用App起http服务调so就完成了
文章作者:「夜幕团队 NightTeam 」- 妄为
夜幕团队成立于 2019 年,团队成员包括崔庆才、周子淇、陈祥安、唐轶飞、冯威、蔡晋、戴煌金、张冶青和韦世东。
涉猎的主要编程语言为 Python、Rust、C++、Go,领域涵盖爬虫、深度学习、服务研发和对象存储等。团队非正亦非邪,只做认为对的事情,请大家小心。
本篇文章由一文多发平台ArtiPub自动发布