保护 Android Service 防止被一键清理的方法

目前很多安全软件能够将我们开发的程序杀死,如果我们的程序在后台需要一直运行,那么就需要一种方法能够在服务被关掉的时候重新启动。

目前已知的方法:

(1)onDestroy中重启;

(2)AlarmManger定时扫描;

(3)onStartCommand返回值;

(4)setForeground;

(5)双进程互相保护;

这些方法均无法逃脱如小米的一键清理功能。难道没有路子可寻吗?大家也发现了如墨迹天气等一些软件都实现了自动启动,但是关于墨迹天气如何做到的,笔者并不知道,这里笔者提供一种很猥琐的方法,笔者通过不断地努力终于实现了,解决方法是通过NDK编一个本地非android程序,这个程序不会被清理,通过这个不被清理的程序来保护后台服务能够在杀死后复活。


具体实现如下:



(1)本地Native程序:与JNI的so不同,Android.mk文件需要重写

这是Android.mk内容

include $(CLEAR_VARS)
LOCAL_SRC_FILES:= test.cpp
LOCAL_MODULE:= test1
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_STATIC_LIBRARIES := libc android
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug
LOCAL_MODULE_FILENAME := "libtest.so"   #------------------注意这里的命令,必须编译成这种文件名,当然是为了逃过android安装
LOCAL_LDLIBS := -lm -llog -landroid
include $(BUILD_EXECUTABLE) 


程序代码(该程序定时执行一个操作,那个操作用来启动后台):

int main(int argc, char* argv[]) {
FILE *fp1;
char ch;
/********************************************
* 创建一个文件,确保当前的路径能够写入
* ******************************************/
if ((fp1 = fopen("textc.txt", "w")) == NULL) {
LOGI("hellofork: test.cpp/main: open file failed", LOG);
exit(0);
}

fprintf(fp1, "test string1");
fclose(fp1);

char tmc[300];
sprintf(tmc, "hellofork: test.cpp/main: argc=%d", argc);

char* srvname = NULL;
if (argc >= 1) {
srvname = argv[1];
} else {
srvname = a;
}


while (1) {

//LOGI( "hellofork.test.main.while(1): Monitor Tick", LOG);

/********************************************

* 验证刚才创建的文件存在则不退出

* 否则可能是被卸载了

* ******************************************/

FILE* fpt = fopen("textc.txt", "r");

if (fpt)

fclose(fpt);

else

break;


check_and_restart_service(srvname);//这个函数负责重启服务


sleep(4);

}


return 0;

}

(2)JNI辅助函数:检查进程是否存在

检测代码可以用JNI调用:


#include <string.h>
#include <jni.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>


#include <dirent.h>
#include <sys/types.h> // for opendir(), readdir(), closedir()
#include <sys/stat.h> // for stat()
#define PROC_DIRECTORY "/proc/"
#define CASE_SENSITIVE    1
#define CASE_INSENSITIVE  0
#define EXACT_MATCH       1
#define INEXACT_MATCH     0


//是不是数字
int IsNumeric(const char* ccharptr_CharacterList) {

for (; *ccharptr_CharacterList; ccharptr_CharacterList++)

if (*ccharptr_CharacterList < '0' || *ccharptr_CharacterList > '9')

return 0; // false

return 1; // true

}


//intCaseSensitive=0大小写不敏感
int strcmp_Wrapper(const char *s1, const char *s2, int intCaseSensitive) {

if (intCaseSensitive)

return !strcmp(s1, s2);

else

return !strcasecmp(s1, s2);

}


//intCaseSensitive=0大小写不敏感
int strstr_Wrapper(const char* haystack, const char* needle,int intCaseSensitive) {

if (intCaseSensitive)

return (int) strstr(haystack, needle);

else

return (int) strcasestr(haystack, needle);

}


pid_t GetPIDbyName_implements(const char* cchrptr_ProcessName,int intCaseSensitiveness, int intExactMatch) {

char chrarry_CommandLinePath[100];

char chrarry_NameOfProcess[300];

char* chrptr_StringToCompare = NULL;

pid_t pid_ProcessIdentifier = (pid_t) -1;

struct dirent* de_DirEntity = NULL;

DIR* dir_proc = NULL;


int (*CompareFunction)(const char*, const char*, int);


if (intExactMatch)

CompareFunction = &strcmp_Wrapper;

else

CompareFunction = &strstr_Wrapper;



dir_proc = opendir(PROC_DIRECTORY);

if (dir_proc == NULL) {

perror("Couldn't open the " PROC_DIRECTORY " directory");

return (pid_t) -2;

}


// Loop while not NULL

while ((de_DirEntity = readdir(dir_proc))) {

if (de_DirEntity->d_type == DT_DIR) {

if (IsNumeric(de_DirEntity->d_name)) {

strcpy(chrarry_CommandLinePath, PROC_DIRECTORY);

strcat(chrarry_CommandLinePath, de_DirEntity->d_name);

strcat(chrarry_CommandLinePath, "/cmdline");

FILE* fd_CmdLineFile = fopen(chrarry_CommandLinePath, "rt"); //open the file for reading text

if (fd_CmdLineFile) {

fscanf(fd_CmdLineFile, "%s", chrarry_NameOfProcess); //read from /proc/<NR>/cmdline

fclose(fd_CmdLineFile); //close the file prior to exiting the routine


/*取最右边的除号右边的文件名称

if (strrchr(chrarry_NameOfProcess, '/'))

chrptr_StringToCompare = strrchr(chrarry_NameOfProcess,'/') + 1;

else

*/


chrptr_StringToCompare = chrarry_NameOfProcess;


//printf("Process name: %s\n", chrarry_NameOfProcess);

//这个是全路径,比如/bin/ls

//printf("Pure Process name: %s\n", chrptr_StringToCompare );

//这个是纯进程名,比如ls

//这里可以比较全路径名,设置为chrarry_NameOfProcess即可

if (CompareFunction(chrptr_StringToCompare,cchrptr_ProcessName, intCaseSensitiveness)) {

pid_ProcessIdentifier = (pid_t) atoi(de_DirEntity->d_name);

closedir(dir_proc);

return pid_ProcessIdentifier;

}

}

}

}

}

closedir(dir_proc);

return pid_ProcessIdentifier;

}


//简单实现 // If -1 = not found, if -2 = proc fs access error
pid_t GetPIDbyName_Wrapper(const char* cchrptr_ProcessName) {

return GetPIDbyName_implements(cchrptr_ProcessName, 0, 0); //大小写不敏感 sub串匹配

}


(3)通过am启动service(am 命令格式在不同系统可能有所不同)

代码通过popen来执行:

void ExecuteCommandWithPopen(char* command, char* out_result,int resultBufferSize) {

FILE * fp;

out_result[resultBufferSize - 1] = '\0';

fp = popen(command, "r");

if (fp) {

fgets(out_result, resultBufferSize - 1, fp);

out_result[resultBufferSize - 1] = '\0';

pclose(fp);

}

}

//检测服务,如果不存在服务则启动
void check_and_restart_service(char* service) {

/**********************************************************

* 为了简化问题,这里通过am命令启动一个laucher服务<br>

* 由laucher服务负责进行主服务的检测<br>

* 这个laucher服务在检测后自动退出

* ********************************************************/

if (service == NULL) {

service = a;

}



char cmdline[200];

//sprintf(cmdline, " /system/bin/sh -c \"am startservice %s\"", service);

sprintf(cmdline, "am startservice %s", service);



//writeFile(cmdline, "/data/data/com.qihoo.hellofork/srvname.txt");

char tmp[200];

sprintf(tmp, "hellofork: test.cpp/check_and_restart_service: cmd=%s",cmdline);

LOGI( tmp, LOG);

//system(tmp);


ExecuteCommandWithPopen(cmdline, tmp, 200);

LOGI( tmp, LOG);


}

(4)laucher服务(一个用来启动服务的服务,为了简化am的执行):

public class hostMonitor extends Service {


@Override

public IBinder onBind(Intent intent) {

// TODO Auto-generated method stub

return null;

}



@Override

public void onCreate() {

// TODO Auto-generated method stub

super.onCreate();

Log.i("hellofork",

"hellofork: hostMonitor Came Back! I can not be Killed!!!");

//该服务启动后会立刻停止,在停止前可以执行一些操作,可以在这里通过ServiceManager来判断要不要启动其他服务

//这样am的执行会很省电


stopSelf();

}

}

(5)如何使本地程序启动呢(做了jni接口)?

public class NativeRuntime {

private static NativeRuntime theInstance = null;

private NativeRuntime() {

}

/******************************************

* 获得实例

* ****************************************/

public static NativeRuntime getInstance() {

if (theInstance == null)

theInstance = new NativeRuntime();

return theInstance;

}

/***************************************************

* RunExecutable 启动一个可自行的lib*.so文件<br>

* 文件要全名filename<br>

* alias 是别名<br>

* args是参数

*********************************************** **/

public String RunExecutable(String pacaageName, String filename,String alias, String args) {

String path = "/data/data/" + pacaageName;

String cmd1 = path + "/lib/" + filename;

String cmd2 = path + "/" + alias;

String cmd2_a1 = path + "/" + alias + " " + args;

String cmd3 = "chmod 777 " + cmd2;

String cmd4 = "dd if=" + cmd1 + " of=" + cmd2;

StringBuffer sb_result = new StringBuffer();


if (!new File("/data/data/" + alias).exists()) {

RunLocalUserCommand(pacaageName, cmd4, sb_result); // 拷贝lib/libtest.so到上一层目录,同时命名为test.

sb_result.append(";");

}

RunLocalUserCommand(pacaageName, cmd3, sb_result); // 改变test的属性,让其变为可执行

sb_result.append(";");

RunLocalUserCommand(pacaageName, cmd2_a1, sb_result); // 执行test程序.

sb_result.append(";");

return sb_result.toString();

}


/****************************************************

* RunLocalUserCommand<br>

* 执行本地用户命令

* **************************************************/

public boolean RunLocalUserCommand(String pacaageName, String command,StringBuffer sb_out_Result) {

Process process = null;

try {

process = Runtime.getRuntime().exec("sh"); // 获得shell进程

DataInputStream inputStream = new DataInputStream(process.getInputStream());

DataOutputStream outputStream = new DataOutputStream(process.getOutputStream());

outputStream.writeBytes("cd /data/data/" + pacaageName + "\n"); // 保证在command在自己的数据目录里执行,才有权限写文件到当前目录


outputStream.writeBytes(command + " &\n"); // 让程序在后台运行,前台马上返回

outputStream.writeBytes("exit\n");

outputStream.flush();

process.waitFor();


byte[] buffer = new byte[inputStream.available()];

inputStream.read(buffer);

String s = new String(buffer);

if (sb_out_Result != null)

sb_out_Result.append("CMD Result:\n" + s);

} catch (Exception e) {

if (sb_out_Result != null)

sb_out_Result.append("Exception:" + e.getMessage());

return false;

}

return true;

}


public native String findProcess(String processname);

public native String getTestString();


static {

System.loadLibrary("hellofork");

}


}


界面类:
public class HelloforkActivity extends Activity {
/** Called when the activity is first created. */

TextView text1, text2;
TextView mTextView01;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

text1 = (TextView) findViewById(R.id.text1);
text2 = (TextView) findViewById(R.id.text2);
mTextView01 = (TextView) findViewById(R.id.textView01);

text1.setText(NativeRuntime.getInstance().getTestString());

String executable = "libtest.so";
String aliasfile = "test";
String parafind = "/data/data/" + getPackageName() + "/" + aliasfile;
String retx = NativeRuntime.getInstance().findProcess(parafind);
if (!retx.substring(0, 4).equalsIgnoreCase("true")) {
String r = NativeRuntime.getInstance().RunExecutable(
getPackageName(), executable, aliasfile,
"com.qihoo.hellofork/.hostMonitor");
text2.setText(r);
mTextView01.setText("create new process " + retx);
} else {
mTextView01.setText("exist " + retx.substring(4) + " reload");
NativeRuntime.getInstance().RunLocalUserCommand(getPackageName(),
"kill -9 " + retx.substring(4), null);
NativeRuntime.getInstance().RunExecutable(getPackageName(),
executable, aliasfile, "com.qihoo.hellofork/.hostMonitor");
}

}

}

注:无需root权限

依赖:am命令,proc文件系统












  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值