实战案例分析:安卓逆向反调试
2021-11-02 18:07:00 Author: www.freebuf.com(查看原文) 阅读量:36 收藏

前言

反调试,在我们脱壳的第一步。反调试虽然不能完全阻止攻击者,但是还是能加大攻击者的时间成本,一般与加壳结合使用,核心还是加壳部分。反调试可以分为两类:一类是检测,另一类是攻击。本文是对安卓逆向中的反调试进行案例分析。

环境

IDA

AndroidKiller

jadx

一部安卓机

一、文件检测

反调试时,第一个会检测android_server,文件名检测。
实战分析filecheck文件。

IDA 反编译

image-20211010201805446

.so文件是可以找到JNl_onload,它是编译可执行文件,看具体的逻辑,在Exports模块下搜索start

,找main函数的入口函数,双击进去。

image-20211010202121422

image-20211010202317147

如果这个函数的参数超过了四个以上(>4),跳转的地址就得用其他的寄存器来替代。

libc_init容纳的函数的参数是在四个以上(>4)

当它这里没有被替代的时候,往下找最大的寄存器只出现了R*3为止,没出现R4以上的寄存器

那么这里猜测传入的个数的参数为︰是三个

具体验证一下

这里TAB进来

image-20211010202915704

可以看到 确实是三个参数

第三个参数是main函数

image-20211010203005790

双击分析main函数

image-20211010203119854

这就是我们要找的main函数了

image-20211010203347098

或者左边直接双击main

也可以进入main函数

image-20211010203526065

进入main函数之后 TAB 分析代码

image-20211010203702535

右键进行隐藏类型

image-20211010203740024

下面是一个if判断

双击 sub 进来

image-20211010204218907

image-20211010204322423

分析代码

v0 = opendir("/data/local/tmp");

opendir打开/data/local/tmp目录给v0,它是一个文件指针

result = getpid();
getpid是当前进程的id
简单讲,就是v0不等于0的时候,要执行while里面的循环逻辑

v3 = readdir(v0);
这里的v0就是打开的文件指针

!strncmp(v5, "android_server", 0xEu)
strncmp是一个字符串比较函数
如果文件底层存在android_server,就会直接kill结束程序

Android底层分析

拿出我的安卓真机

adb上传filecheck

adb上传android_server

adb push C:\Users\12550\Desktop\filecheck data/local/tmp

adb push C:\Users\12550\Desktop\android_server data/local/tmp

加权

chmod 777 android_server

chmod 777 filecheck

image-20211014232222221

执行

./filecheck

image-20211014232354162

因为有android_server所以 被kill掉了

这就是一个文件反调试逻辑思路

绕过

即我们改名字 即可

二、端口检测

源码分析

这里给各位师傅 贴上源码

checkTCP.c

#include <unistd.h>
#include <stdio.h>

int num = 54321;

//检测常用的端口
void check()
{
    FILE* pfile=NULL;
    char buf[0x1000]={0};
    // 执行命令
    char* strCatTcp= "cat /proc/net/tcp |grep :5D8A";
    char* strNetstat="netstat |grep :23946";
    pfile=popen(strCatTcp,"r");
	int pid=getpid();
    if(NULL==pfile)
    {
        printf("打开命令失败!\n");
        return;
    }
    // 获取结果
    while(fgets(buf,sizeof(buf),pfile))
    {
        // 执行到这里,判定为调试状态
        printf("执行cat /proc/net/tcp |grep :5D8A的结果:\n");
        printf("%s",buf);
		int ret=kill(pid,SIGKILL);
    }
    pclose(pfile);
}
int main()
{
	int x = 2;
	int y = 3;
	int key;
	x = x ^ y;
	y = x ^ y;
	x = x ^ y;
	int X = x ^ y;
	int Y = x & y;
	Y= Y << 1;
	int X0 = X ^ Y;
	int Y0 = X & Y;
	Y0 = Y0 << 1;
	if (Y0==0)
	{
		key = X0+4543;
	}
	int encrypt = num ^ key;
	int decrypt = encrypt ^ key;
	check();
	printf("加密前:%d\n",num);
	printf("加密后值:%d\n",encrypt);
	printf("解密后值:%d\n", decrypt);
    return 0;
}

源码分析

首先FILE文件指针定义字符数组(char buf[0x1000]={0};)

然后执行命令cat /proc/net/tcp |grep :5D8A

这里grep过滤端口只查看5D8A端口,5D8A换算是:23946

image-20211012201008955

android_server 默认的端口也是23946

如果有5D8Akill(pid,SIGKILL);

IDA反编译

双击main函数

image-20211012201546386

image-20211012201956772

空白处TAB进入伪C代码

image-20211012202026178

双击进入check()函数

image-20211012202154440

image-20211012202204772

右键进行隐藏类型

然后查看即可

Android底层分析

adb push C:\Users\12550\Desktop\checkTCP data/local/tmp

chmod 777 checkTCP

image-20211014232627607

执行

image-20211016122648923

可以看到是被kill掉了

绕过

启动时指定端口(-p)即可

三、进程名称检测

源码分析

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

//进程名称检测
void coursecheck(){
	const int bufsize = 1024;
    char filename[bufsize];
    char line[bufsize];
    char name[bufsize];
    char nameline[bufsize];
    int pid = getpid();
    //先读取Tracepid的值
    sprintf(filename, "/proc/%d/status", pid);
    FILE *fd=fopen(filename,"r");
    if(fd!=NULL)
	{
        while(fgets(line,bufsize,fd))
		{
            if(strstr(line,"TracerPid")!=NULL)
            {
                int statue =atoi(&line[10]);
                if(statue!=0)
				{
                    sprintf(name,"/proc/%d/cmdline",statue);
                    FILE *fdname=fopen(name,"r");
                    if(fdname!= NULL)
					{
                        while(fgets(nameline,bufsize,fdname))
						{
                            if(strstr(nameline,"android_server")!=NULL)
							{
                                int ret=kill(pid,SIGKILL);
                            }
                        }
                    }
                    fclose(fdname);
                }
            }
        }
    }
    fclose(fd);
}

void order(int* p,int n)//n:表示数组的长度 
{
     int i,j;
     int k; 
     for(i=0;i<n-1;i++)
     {
       for(j=0;j<n-1-i;j++)
       {
             if(*(p+j)>*(p+j+1))
             {
                  k=*(p+j);//k=a; 
                  *(p+j)=*(p+j+1);//a=b;
                  *(p+j+1)=k;  //b=k;            
             }              
       }            
     }
     printf("排序后的数组为:");
     for(i=0;i<n;i++)
     {
          if(i%5==0)
              printf("\n");
          printf("%4d",*(p+i));    
     }
     printf("\n");
}


int main()
{
    int n;
    printf("请输入数组元素的个数:");
    scanf("%d",&n);
    
    int sum[n];
    printf("请输入各个元素:");
    int i;
	coursecheck();
    for(i=0;i<n;i++)
    {
        //scanf("%d",sum+i);
        scanf("%d",&sum[i]);  
    }
    order(sum,n);//实现冒泡排序 
    return 0;
}

分析main函数

关注这个方法

image-20211011161410452

获取(getpid)

pid值放到%d里面

然后以只读的方式(r)打开

进行获取当前进程的一个状态

image-20211011161518870

文件打开成功就进入while循环

image-20211011161715671

IDA反编译

找到main函数进来

image-20211012202702845

空白处TAB查看伪C代码

关注这个方法

image-20211012202650210

右键进行隐藏类型

image-20211012203316052

Android底层分析

adb push C:\Users\12550\Desktop\BubbleSort data/local/tmp

image-20211014233114969

运行程序

查看进程列表

ps | grep BubbleSort 

程序的PID号:5760

image-20211014233228865

执行 cat 遍历程序

cat /proc/5760/status

image-20211014233408549

IDA动态调试

./a001 -p20365adb forward tcp:20365 tcp:3192

image-20211012205252104

增加设置

image-20211012205630626

绕过

当我们在动态调试的时候

找到Tracepid赋值的地方,手动把它赋值改为0即可

四、轮循检测

前言

1.函数类型:safe_attach函数handle_events函数

2.简介:轮询检测反调试技术基于循环检测进程的状态

3.目的:判断当前进程是否正在被调试

4.优点:实现比较简单

5.缺点:系统资源消耗大

6.原理:读取进程的/proc/[pid]/status文件,通过该文件得到调式当前进程的调式器(检测调式器的[pid])

7.实现:通过status文件内的TracerPid字段的值判断当前进程或线程是否正在被调式

8.status文件信息:Name:进程名称State:进程的状态Tgid:一般指进程的名称Pid:一般指进程Id,他的值与getting函数的返回值相等PPid:父进程的IdTraceerPid:实现调试功能的进程Id,值为0表示当前进程未被调试

9.反-反调试方案:动态调试时修改TraceerPid字段值为0修改内核,让TraceerPid字段值为负值

源码分析

这里给师傅们 贴上源码

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>

#define TRACERPID "TracerPid:"
#define TRACERPID_LEN (sizeof(TRACERPID) - 1)

void loop()
{
    while(true) 
    {
        sleep(60);
    }
}

bool check_debugger(pid_t pid)
{
    const int pathSize = 256;
    const int bufSize = 1024;

    char path[pathSize];
    char line[bufSize];

    snprintf(path, sizeof(path) - 1, "/proc/%d/status", pid);

    bool result = true;
    FILE *fp = fopen(path, "rt");
    if (fp != NULL) 
    {   
        while (fgets(line, sizeof(line), fp)) 
        {
            if (strncmp(line, TRACERPID, TRACERPID_LEN) == 0) 
            {
                pid_t tracerPid = 0; 

                sscanf(line, "%*s%d", &tracerPid);
                if (!tracerPid) result = false;
                
                printf("%s", line);
                break;
            }
        }
        fclose(fp);
    }
    return result;
}

void *anti_debugger_thread(void *data)
{
    pid_t pid = getpid();

    while (true)
    {
        check_debugger(pid);
        sleep(1);
    }
}

void anti_debugger()
{    
    pthread_t tid;
    pthread_create(&tid, NULL, &anti_debugger_thread, NULL);
}

int main()
{
    printf("pid: %d\n", getpid());

    anti_debugger();
    loop();
    
    return 0;
}

从main函数入手

main函数中调用了anti_debugger()函数

anti_debugger()函数定义了t tid参数

image-20211014224334016

anti_debugger_thread作为参数传入

image-20211014224553203

然后继续往上看

image-20211014224939134

分析check_debugger(pid);

1.fopen:打开文件操作的意思

2.path是上面传入path路径的256

3.权限是以rt模式打开

4.然后是一个if判断是否打开为空

5.执行while循环,whie循环有fgets,打开指向的fp文件,会进行条件遍历

6.if中根据传入的tracerPid进行strncmp比较

7.如果传入的tracerPid为0就直接打印8.if (!tracerPid)不为0就返回false9.最后关闭文件指针

image-20211014225143034

Andriod底层debugger调试

拿出我的安卓机

上传

adb push C:\Users\12550\Desktop\poll_anti_debug data/local/tmp
adb push C:\Users\12550\Desktop\debugger data/local/tmp

image-20211014230538572

赋予权限

chmod 777 debugger
chmod 777 poll_anti_debug

image-20211014230626635

运行检测调试程序poll_anti_debug

可以看到在循环打印

TracerPid: 0

并且pid:5333

image-20211014230832241

debugger调试程序

./debugger 5333

image-20211014231021185

五、self-debugging反调试

简介

1.原理父进程创建一个子进程,通过子进程调试父进程
2.特点非常实用、高效的实时反调式技术
3.优点(可以作为受保护进程的主流反调试方案)消耗的系统资源比较少几乎不影响受保护进程性能可以轻易地阻止其他进程调式受保护的进程
4.缺点实现比较复杂
5.实现
5.1 核心ptrace函数
5.2进程的信号机制
6.注意进程暂停状态比较多
7.暂停状态
7.1 signal-delivery-stop状态调试器和被调试进程之间的关系
7.2 group-stop状态(难)sigcont信号同时满足两个条件:    
进程/线程处于被调式状态    
被调式进程/线程收到了暂停信号-->重置为0 sigstop sigtstp sigttin sigttou
7.3 sysco1l-stop状态7.4 ptrace-event-stop状态
8.反-反调式
8.1.让父进程不fork
8.2.把 while函数循环去掉
8.3.不能调试父进程,但可以调式子进程,配合双IDA调式,挂起子进程

fork函数

fork是一个函数fork函数fork出一个子进程来调试自己,那么别的函数就无法进行调试了那么通过调试fork出来的子进程从而调试父进程同一时刻,我们调试的时候,一个进程只能被一个进程附加

image-20211015160113404

Android底层分析

上传

adb push C:\Users\12550\Desktop\debugger data/local/tmp
adb push C:\Users\12550\Desktop\self-debugging data/local/tmp

加权

chmod 777 debugger
chmod 777 self-debugging

image-20211015165005234

找一个主进程进行调试

这里以找.com为例

ps | grep .com

这里以nfc进程进行调试

进程PID是:3294

image-20211015165254570

debugger调试

./debugger 3*94

image-20211015165553595

查看nfc进程的TracerPid

cat /proc/3294/status

image-20211015165818838

我们在看看 这个TracerPid 具体对应哪个进程

ps | grep 9624

image-20211015165909246

它对应我们的debugger程序

所以

当我们用debugger调试某个进程的时候,TracerPid会变成调试器的Pid

运行self-debugging

image-20211015170303811

主进程Pid:9842

子进程Pid:9843

子进程调用父进程

之前有提及到过

同一时刻,我们调试的时候,一个进程只能被一个进程附加

所以

image-20211015170600047

绕过

综上所述:过掉self-debugger直接附加他的子进程即可绕过

六、Java层反调试

原理

JDWP协议动态调式
安卓程序动态调式条件:
1.在AndroidMainfest.xml中,application标签下
Android:debuggable=true
2.系统默认调式,在build.prop(boot.img),ro.debugable=1
Android SDK中有android.os.debug类提供了一个isDebuggerConnected方法,用于判断JDWP调式器是否正在工作
两个满足之一即可

静态分析

jadx打开案例apk

我们先看AndroidMainfest.xml文件

image-20211015172152904

然后去看StubApplication下的onCreate()

image-20211015172321129

我们关注isDebuggerConnected方法

因为它是:用于判断JDWP调式器是否正在工作

image-20211015172459840

Debug.isDebuggerConnected获取到一个值进行比较

如果为真就进行加载loadLibrary

image-20211015172614033

继续往上看

image-20211015172806381

所以只有符合条件成立才会执行if里面的逻辑,进行加载so库

这就是在java层进行反调试,也能用来保护代码

绕过

进行Androidkiller 反编译

这个APK是加壳的 这里不是我们的重点 先跳过

我们今天是要熟悉该APK 中Java层 反调试的逻辑

image-20211015173150170

我们直接工程搜索isDebuggerConnected方法

我们就找到了这个方法的Smali代码

image-20211015173506188

代码分析

image-20211015173651530

这里是判断

然后v0返回给cond_0

那么我们取反

nez 改成 eqz 即可

七、实战案例分析

拿到一个apk,我们一个简单的思路

第一步:先去查壳

第二步:APK反编译查看有没有签名

第三步:逆向分析逻辑

AntiDebug.apk-静态分析

jadx反编译AntiDebug.apk

找onCreate()

image-20211015175536296

开始分析

loadLibrary直接加载了antidebug,那么说明逻辑在so库里面

image-20211015175722505

上IDA 分析

把AntiDebug.apk用解压软件打开 找到so库文件

image-20211015175905937

拖入IDA 即可

image-20211015175940672

找静态注册的动态函数:JNI_Onload

image-20211015180059251

TAB伪代码进行查看

image-20211015180140937

image-20211015180154756

右键 进行隐藏类型

image-20211015180401231

然后进行代码分析

两个赋值

然后是if判断

if判断中用了或运算符

后面四个参数 有一个 达成条件既满足if判断

需要将这四个函数 都为假 才能绕过

anti_time()

先查看anti_time()参数 双击进来

image-20211015180715253

image-20211015180745160

代码分析

1.首先定义结构体类型

2.定义v0=getpid

3.然后调用了同一个函数gettimefday传入两个不同的值

4.v1 = tv.tv_sec - v3.tv_sec;通过传入两个不同的参数调用tv_sec做差值获取到v1

5.如果if判断,v1小于等于1返回0,否者就kill,这就是一个简单的时间检测

image-20211015181216322

anti_breakpoint

image-20211015181428762

双击进来

image-20211015181503472

我们关注 return 返回值

想要返回值不触发,只需要这里面函数的返回值都为0即可

image-20211015200101336

这里很多if和while嵌套循环,那么最终不执行return 1,可以在多个if中进行修改条件即可

anti_pthread()

image-20211015200130765

双击进来

分析代码

1.pthread_self创建子线程

2.pipe(&pipefd)是管道意思是实现进程通信

3.pthread_create创建线程,传入了四个参数

查看第三个参数: anti_thread

image-20211015200448188

if判断 然后return 0

这是一个获取线程

image-20211015200620213

registerNativeMethod是一个无关的参数

AntiDebug.apk-动态分析

获取主类

image-20211015201008945

com.qianyu.antidebug/.MainActivity

上传安装apk

adb install C:\Users\12550\Desktop\AntiDebug.apk

image-20211015201240431

挂起apk程序

adb shell am start -D -n com.qianyu.antidebug/.MainActivity

image-20211015201314823

打开ddms

image-20211015201401254

启动android_server

注:我更名且指定了端口

image-20211015201546487

端口转发

adb forward tcp:7788 tcp:7788

image-20211015201741921

IDA 连接

image-20211015201820665

image-20211015201846064

image-20211015201923358

增加设置

image-20211015202035609

image-20211015202057651

F9 进入运行状态

image-20211015202219775

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8600

image-20211015202343671

IDA 运行一下 加载so库

image-20211015231232628

image-20211015231315739

找到JNI_OnLoad

image-20211015230157761

双击进入

下断点

F9 运行

image-20211015232446516

F7单步步入

可以看到状态寄存器 T=1

说明这里面是Thumb模式指令

image-20211015232605850

0 2 4 6 8 A

两位数

image-20211015232721274

动静结合

F8 跳下来

注意R*3

R*3是GetEnv

在这里插入图片描述

注:这里实在截图不方便 ,手机拍照 师傅们见谅

如果Enν获取成功的话就会执行后面的三个函数

image-20211015233743564

这三个函数会在这里进行反调试,如何跳过不执行呢?

同步PC寄存器

选中上面第一个函数

然后下面栈右键

image-20211015234051422

image-20211015234313376

F2 开始进行修改 为00

最后F2保存

image-20211015234433281

把后面两个参数的栈地址一样的方法进行修改为00

image-20211015234534196

前三个参数 就没有了看第四个参数

双击进入

image-20211015234651609

image-20211015234721723

静态代码也双击进入第四个参数

image-20211015234759009

image-20211015234812080

R5有四个参数

只需要看第三个参数即可

image-20211015234900460

F4 跳到R5

然看第三个参数R2

点击箭头进入

image-20211015235200428

识别成功后地址什么都没有

image-20211015235329024

分析函数源码

这里直接给师傅们贴出来

#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/inotify.h>
#include <elf.h>
#include <pthread.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <android/log.h>
#define LOG_TAG "qianyu"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN  , LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)
#define MAX 1024
//检测代码执行时间差
jint anti_time(){
	 int pid = getpid();
	 struct timeval t1;
	 struct timeval t2;
	 struct timezone tz;
	 gettimeofday(&t1, &tz);
	 gettimeofday(&t2, &tz);
	 int timeoff = (t2.tv_sec) - (t1.tv_sec);
	 LOGD("time %d",timeoff);
	 if (timeoff > 1) {
		int ret = kill(pid, SIGKILL);
		return 1;
	 }
	return 0;
}
//inotify检测
jint anti_inotify(){
	const int MAXLEN = 2048;
	int ppid =getpid();
	char buf[1024],readbuf[MAXLEN];
	int pid, wd, ret,len,i;
	int fd;
	fd_set readfds;
	//防止调试子进程
	ptrace(PTRACE_TRACEME, 0, 0, 0);
	fd =  inotify_init();
	sprintf(buf, "/proc/%d/maps",ppid);

	wd = inotify_add_watch(fd, buf, IN_ALL_EVENTS);
	if (wd < 0) {
		LOGD("can't watch %s",buf);
	    return 0;
	}
	while (1) {
		i = 0;
	    //注意要对fd_set进行初始化
	    FD_ZERO(&readfds);
	    FD_SET(fd, &readfds);
	    //第一个参数固定要+1,第二个参数是读的fdset,第三个是写的fdset,最后一个是等待的时间
	    //最后一个为NULL则为阻塞
	    //select系统调用是用来让我们的程序监视多个文件句柄的状态变化
	    ret = select(fd + 1, &readfds, 0, 0, 0);
	    if (ret == -1)
	    	break;
	    if (ret) {
	        len = read(fd,readbuf,MAXLEN);
	        while(i < len){
	        	//返回的buf中可能存了多个inotify_event
	        	struct inotify_event *event = (struct inotify_event*)&readbuf[i];
	        	LOGD("event mask %d\n",(event->mask&IN_ACCESS) || (event->mask&IN_OPEN));
				//这里监控读和打开事件
				if((event->mask&IN_ACCESS) || (event->mask&IN_OPEN)){
					LOGD("kill!!!!!\n");
					//事件出现则杀死父进程
					int ret = kill(ppid,SIGKILL);
					LOGD("ret = %d",ret);
					return 1;
				}
				i+=sizeof (struct inotify_event) + event->len;
	        }
	    }
	}
	inotify_rm_watch(fd,wd);
	close(fd);
	return 0;
}

/*
 * 检测在调试状态下的软件断点(断点扫描)
 * 读取其周围的偏移地址有没有ARM等指令集的断点指令
 * 遍历so中可执行segment,查找是否出现breakpoint指令即可
 * */
unsigned long GetLibAddr() {
    unsigned long ret = 0;
    char name[] = "libantidebug.so";
    char buf[4096], *temp;
    int pid;
    FILE *fp;
    pid = getpid();
    sprintf(buf, "/proc/%d/maps", pid);
    fp = fopen(buf, "r");
    if (fp == NULL) {
        puts("open failed");
        goto _error;
    }
    while (fgets(buf, sizeof(buf), fp)) {
        if (strstr(buf, name)) {
            temp = strtok(buf, "-");//将buf由"-"参数分割成片段
            ret = strtoul(temp, NULL, 16);//将字符串转换成unsigned long(无符号长整型数)
            break;
        }
    }
    _error: fclose(fp);
    return ret;
}

jint anti_breakpoint(){
	Elf32_Ehdr *elfhdr;
	Elf32_Phdr *pht;
	unsigned int size, base, offset,phtable;
	int n, i,j;
	char *p;
	//从maps中读取elf文件在内存中的起始地址
	base = GetLibAddr();
	if(base == 0){
		LOGD("find base error/n");
	    return 0;
	}
	elfhdr = (Elf32_Ehdr *) base;
	phtable = elfhdr->e_phoff + base;
	for(i=0;i<elfhdr->e_phnum;i++){
		pht = (Elf32_Phdr*)(phtable+i*sizeof(Elf32_Phdr));
	    if(pht->p_flags&1){
	    	offset = pht->p_vaddr + base + sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr)*elfhdr->e_phnum;
	        LOGD("offset:%#x ,len:%#x",offset,pht->p_memsz);
	        p = (char*)offset;
	        size = pht->p_memsz;
	        for(j=0,n=0;j<size;++j,++p){
	        	if(*p == 0x10 && *(p+1) == 0xde){
	        		n++;
	                LOGD("### find thumb bpt %#x /n",p);
	                return 1;
	        	}else if(*p == 0xf0 && *(p+1) == 0xf7 && *(p+2) == 0x00 && *(p+3) == 0xa0){
	                n++;
	                LOGD("### find thumb2 bpt %#x /n",p);
	                return 1;
	            }else if(*p == 0x01 && *(p+1) == 0x00 && *(p+2) == 0x9f && *(p+3) == 0xef){
	                n++;
	                LOGD("### find arm bpt %#x /n",p);
	                return 1;
	            }
	        }
	        LOGD("### find breakpoint num: %d/n",n);
	    }
	}
	return 0;
}

//多进程/线程
int pipefd[2];
int childpid;
int isAnti=0;
void *anti_thread(void*){
	int statue=-1,alive=1,conut=0;
	close(pipefd[1]);
	while(read(pipefd[0],&statue,4)>0)
		break;
	sleep(1);
	//这里改为非阻塞
	fcntl(pipefd[0],F_SETFL,O_NONBLOCK);
	LOGI("pip-->read=%d",statue);
	while(true){
		LOGI("pip-->read=%d",statue);
		read(pipefd[0],&statue,4);
		sleep(1);
		LOGI("pip-->read=%d",statue);
		if(statue!=0){
			if(isAnti==0)
				return NULL;
			kill(childpid,SIGKILL);
			kill(getpid(),SIGKILL);
			return NULL;
		}
		statue=-1;
		isAnti=1;
	}
}
void anti(){
	int pid,p;
	FILE *fd;
	char filename[MAX];
	char line[MAX];
	pid=getpid();
	//读取/proc/pid/status中的tracerPid
	sprintf(filename,"/proc/%d/status",pid);
	p=fork();
	if(p==0){
		LOGI("child");
		//关闭子进程的读管道
		close(pipefd[0]);
		int pt,alive=0;
		//子进程反调试
		pt=ptrace(PTRACE_TRACEME,0,0,0);
		while(true){
			fd=fopen(filename,"r");
			while(fgets(line,MAX,fd)){
				if(strstr(line,"TracerPid")!=NULL){
					LOGI("line %s",line);
					int statue=atoi(&line[10]);
					LOGI("tracer pid:%d",statue);
					write(pipefd[1],&statue,4);
					fclose(fd);
					if(statue!=0){
						LOGI("tracer pid:%d",statue);
						return;
					}
					break;
				}
			}
			sleep(1);
		}
	}else{
		LOGI("father");
		childpid=p;
	}
}

jint anti_pthread(){
	// id_0:新线程标识符
	pthread_t id_0;
	id_0=pthread_self();
	pipe(pipefd);
	pthread_create(&id_0,NULL,anti_thread,(void*)NULL);
	LOGI("start");
	anti();
	return 0;
}
JNINativeMethod nativeMethod[]={};

jint registerNativeMethod(JNIEnv* env){
	jclass clszz=env->FindClass("com/qianyu/antidebug/MainActivity");
	if(env->RegisterNatives(clszz,nativeMethod,sizeof(nativeMethod)/sizeof(nativeMethod[0]))!=JNI_OK){
		return JNI_ERR;
	}
	return JNI_OK;
}

文章来源: https://www.freebuf.com/articles/mobile/291894.html
如有侵权请联系:admin#unsafe.sh