so层反调试方法以及部分反反调试的方法

1.检测ida远程调试所占的常用端口23946,是否被占用

//检测idaserver是否占用了23946端口
void CheckPort23946ByTcp() {
    FILE* pfile=NULL;
    char buf[0x1000]={0};
    //执行命令
    char* strCatTcp="cat /proc/net/tcp | grep :5D8A";
    //char* strNetstat="netstat -apn | grep :23946"
    pfile=popen(strCatTcp,"r");
    //说明是没有被调试
    if(NULL==pfile)
    {
        return;
    }
    //获取执行命令后的结果,并存入buf字符数组中
    while(fgets(buf,sizeof(buf),pfile))
    {
        printf("执行 cat /proc/net/tcp | grep :5D8A的结果:\n");
        printf("%s",buf);
    }
    pclose(pfile);
}

上面的netstat -apn | grep 23946 那个-apn是必须加的,之前看大佬的pdf好像漏了

反反调试方法:

1.直接nop掉

2.汇编级直接改寄存器值绕过

3. 既然是检测23946端口,那我就不运行在23946端口了,换一个端口运行

二.调试器进程名检测

原理: android调试时需要运行androidserver,androidserver64,gdb,gdbserver等进程

反调试代码:

void SerachObjectProcess()
{
    FILE* pfile=NULL;
    char buf[0x1000]={0};
    //执行命令
    //pfile=popen("ps | awk'{print $9}'","r");
    pfile=popen("ps","r");
    if(pfile==NULL) {
        printf("命令打开失败");
        return;
    }
    //获取查询结果
    while(fgets(buf,sizeof(buf),pfile))
    {
        //打印进程
        printf("遍历进程:%s\n",buf);
        //查找子串
        char* strA=NULL;
        char *strB = NULL;
        char *strC=NULL;
        char *strD=NULL;
        //IDA检测
        strA=strstr(buf,"android_server");
        //gdb检测
        strB=strstr(buf,"gdbserver");
        strC=strstr(buf,"gdb");
        strD=strstr(buf,"fuwu");
        if(strA||strB||strC||strD) {
            printf("被调试了,%s\n", buf);
            return;
        }
    }
    pclose(pfile);

}

反反调试:

直接修改调试器server的名字,运行的话./自定义的server名字 -p xxxx(自定义端口) 

三.父进程名检测

原理:附加调试时,父进程名都为zygote,有时候调试会使用可执行文件直接加载so文件进行调试,所以如果父进程名非zygote的话,必然是被调试的,充分非必要条件

反调试代码:

void CheckParents()
{
    char strPpidCmdline[0x100]={0};
    snprintf(strPpidCmdline, sizeof(strPpidCmdline),"proc/%d/cmdline",getppid());
    int file=open(strPpidCmdline,O_RDONLY);
    if(file<0)
    {
        printf("打开文件错误");
        return;
    }
    //初始化一下
    memset(strPpidCmdline,0, sizeof(strPpidCmdline));
    //将文件内容读入内存中,方便比较
    ssize_t  ret=read(file,strPpidCmdline, sizeof(strPpidCmdline));
    if(-1==ret)
    {
        printf("读入内存失败");
        return;
    }
    char* sRet=strstr(strPpidCmdline,"zygote");
    if(sRet==NULL)
    {
        printf("被调试了");
        return;
    }
    int i=0;
    return;
}

反反调试:
那就直接附加调试呗,其他方法暂时我也不知道233

四.自身进程名检测

原理: 和上文一样如果用可执行文件加载so配合脱壳的话,进程名也会发生改变,检测是否是apk那种com.xxx.xx

五:检测线程的数量

原理: 正常apk启动时是要有许多进程要启动的,而如果用可执行文件加载so文件,那么必然只有一个线程

反调试代码:

void CheckTaskCount()
{
    char buf[0x100]={0};
    char* str="/proc/%d/task";
    snprintf(buf,sizeof(buf),str,getpid());
    //打开目录
    DIR* pdir=opendir(buf);
    if(!pdir)
    {
        perror("CheckTaskCount open() fail.\n");
        return;
    }
    //查看目录下文件的个数
    struct dirent* pde=NULL;
    int count=0;
    while((pde=readdir(pdir)))
    {
        //字符过滤,每个文件都是一个线程id
        if((pde->d_name[0]<='9')&&(pde->d_name[0]>='0'))
        {
            count++;
            printf("%d 线程名称:%s\n",count,pde->d_name);
        }
        if(count<=1)
        {
            //说明被调试了
            printf("被调试了");
        }
        return;
    }

}

https://blog.csdn.net/qq_40732350/article/details/81986548

六:apk进程的fd文件数量差异检测

原理:/proc/pid/fd目录下文件数,调试与非调试fd文件数量不同

七.安卓系统自带的检测函数

android.os.Debug.isDebuggerConnected(),这个函数是在java层中直接调用就行,

但是如果在native层使用这个也是有办法的,

1. dvm下的方式

找到进程中的libdvm.so中的dvmDbgIsDebuggerConnect()函数,调用它,通过返回值来判断程序是否被调试

dlopen(/system/lib/libdvm.so)

dlsym(_Z25dvmDbgIsDebuggerConnect())

typedef unsigned char wbool;
typedef wbool (*ppp)();
void NativeIsDBGConnected()
{
    void* Handle=NULL;
    Handle=dlopen("/system/lib/libdvm.so",RTLD_LAZY);
    if(Handle==NULL)
    {
        return;
    }
    ppp Fun=(ppp)dlsym(Handle,"_Z25dvmDbgIsDebuggerConnect"); //根据动态链接库的句柄和符号名,返回地址
    if(Fun==NULL) {
        printf("获取函数地址失败");
        return;
    } else
    {
        wbool ret=Fun();
        if(ret==1)
        {
            printf("被调试了");
            return;
        }
    }
}

2.art模式

结果存放在libart.so中的全局变量gDebuggerActive中,符号名

_ZN3art3Dbg15gDebuggerActiveE,art无法使用dlopen在打开so文件了
所以只能在内存搜索,手动查找
八.ptrace检测
原理:一个进程只能被ptrace一次,可以自己ptrace自己,如果一节被调试器ptrace了,自己ptrace肯定ptrace不了,根据返回值进行判断
void checkPtrace()
{
    int iRet;
    iRet=ptrace(PTRACE_TRACEME,0,0,0);
    if(iRet==-1)
    {
        //说明父进程调试失败,说明进程已经被别的进程ptrace了
        printf("已经被调试了!");
        return;
    } else
    {
        printf("还没被调试");
    }
}

反反调试:

1. 修改系统源码,将ptrace返回值直接返回0

2. hook ptrace

3.nop这个函数,或者汇编级修改寄存器绕过

九.函数hash值检测

原理:文件的函数指令一般固定,如果被下了断点,指令会发生改变(bkpt断点指令),可以计算内存中一段指令的hash值,做校验

十.断点指令检测

和上文一样,如果被下了断点的话,指令会被替换成(bkpt断点指令),那么在内存搜索一下不就完事了吗,注意arm和thumb指令有所区别

反调试代码:

void checkbkpt(u8* addr,u32 size)
{
    //结果
    u32 uRet=0;
    //断点指令
    u8 armBkpt[4]={0xf0,0x01,0xf0,0xe7};
    u8 thumbBkpt[2]={0x10,0xde};
    int mode=(u32)addr%2;
    if(1==mode)
    {
        u8* start=(u8*)((u32)addr-1);
        u8* end=(u8*)((u32)start+size);
        while(1)
        {
            if(start>=end)
            {
                uRet=0;
                return;
            }
            if(0==memcmp(start,thumbBkpt,2))
            {
                uRet=1;
                break;
            }
            start=start+2;
        }
    } else{
        //arm
        u8* start=(u8*)addr;
        u8* end=(u8*)((u32)start+size);
        while (1)
        {
            if(start>=end)
            {
                uRet=0;
                return;
            }
            if(0==memcmp(start,armBkpt,4))
            {
                uRet=1;
                break;
            }
            start=start+4;
        }
        
    }
}

十一.安卓系统源码修改反调试

原理: 直接通过修改安卓源码修改,ptrace的返回值,使其永远为零,那么我们可以先自身trace自身,然后再通过子进程再trace一遍,如果还返回为0,说明就有问题。

反调试代码:

未完

 

posted @ 2020-11-26 17:56  YenKoc  阅读(1874)  评论(1编辑  收藏  举报