首页
社区
课程
招聘
安卓反调试
发表于: 2017-12-15 10:56 23034

安卓反调试

2017-12-15 10:56
23034
Android反调试
读取/proc/net/tcp,查找IDA远程调试所用的23946端口,若发现说明进程正在被IDA调试。
void CheckPort23946ByTcp()
{
FILE* pfile=NULL;
char buf[0x1000]={0};
// 执行命令
char* strCatTcp= "cat /proc/net/tcp |grep :5D8A";
//char* strNetstat="netstat |grep :23946";
pfile=popen(strCatTcp,"r");
if(NULL==pfile)
{
LOGA("CheckPort23946ByTcp popen打开命令失败!\n");
return;
}
// 获取结果
while(fgets(buf,sizeof(buf),pfile))
{
// 执行到这里,判定为调试状态
LOGA("执行cat /proc/net/tcp |grep :5D8A的结果:\n");
LOGB("%s",buf);
}//while
pclose(pfile);
}

遍历进程,查找固定的进程名,找到说明调试器在运行。(比如说固定的进程名 android_server gdb_server等等)
void SearchObjProcess()
{
FILE* pfile=NULL;
char buf[0x1000]={0};
// 执行命令
//pfile=popen("ps | awk '{print $9}'","r"); // 部分不支持awk命令
pfile=popen("ps","r");
if(NULL==pfile)
{
LOGA("SearchObjProcess popen打开命令失败!\n");
return;
}
// 获取结果
LOGA("popen方案:\n");
while(fgets(buf,sizeof(buf),pfile))
{
// 打印进程
LOGB("遍历进程:%s\n",buf);
// 查找子串
char* strA=NULL,strB=NULL,strC=NULL,strD=NULL;
strA=strstr(buf,"android_server");
strB=strstr(buf,"gdbserver");
strC=strstr(buf,"gdb");
strD=strstr(buf,"fuwu");
if(strA || strB ||strC || strD)
{
// 执行到这里,判定为调试状态
LOGB("发现目标进程:%s\n",buf);
}//if
}//while
pclose(pfile);
}

有的时候不使用apk附加调试的方法进行逆向,而是写一个.out可执行文件直接加载so进行
调试,这样程序的父进程名和正常启动apk的父进程名是不一样的。
读取/proc/pid/cmdline,查看内容是否为zygote
void CheckParents()
{
///////////////////
// 设置buf
char strPpidCmdline[0x100]={0};
snprintf(strPpidCmdline, sizeof(strPpidCmdline), "/proc/%d/cmdl
ine", getppid());
// 打开文件
int file=open(strPpidCmdline,O_RDONLY);
if(file<0)
{
LOGA("CheckParents open错误!\n");
return;
}
// 文件内容读入内存
memset(strPpidCmdline,0,sizeof(strPpidCmdline));
ssize_t ret=read(file,strPpidCmdline,sizeof(strPpidCmdline));
if(-1==ret)
{
LOGA("CheckParents read错误!\n");
return;
}
// 没找到返回0
char sRet=strstr(strPpidCmdline,"zygote");
if(NULL==sRet)
{
// 执行到这里,判定为调试状态
LOGA("父进程cmdline没有zygote子串!\n");
return;
}
int i=0;
return;
}


正常apk进程一般会有十几个线程在运行(比如会有jdwp线程),
自己写可执行文件加载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)))
{
// 字符过滤
if ((pde->d_name[0] <= '9') && (pde->d_name[0] >= '0'))
{
++Count;
LOGB("%d 线程名称:%s\n",Count,pde->d_name);
}
}
LOGB("线程个数为:%d",Count);
if(1>=Count)
{
// 此处判定为调试状态.
LOGA("调试状态!\n");
}
int i=0;
return;
}

根据/proc/pid/fd/路径下文件的个数差异,判断进程状态。

分析android自带调试检测函数isDebuggerConnected()在native的实现,
尝试在native使用

(1)dalvik模式下:
找到进程中libdvm.so中的dvmDbgIsDebuggerConnected()函数,
调用他就能得知程序是否被调试。
dlopen(/system/lib/libdvm.so)
dlsym(_Z25dvmDbgIsDebuggerConnectedv)
(2)art模式下:
art模式下,结果存放在libart.so中的全局变量gDebuggerActive中,
符号名为_ZN3art3Dbg15gDebuggerActiveE。
但是貌似新版本android不允许使用非ndk原生库,dlopen(libart.so)会失败。
所以无法用dalvik那样的方法了。
有一种麻烦的方法,手动在内存中搜索libart模块,然后手工寻找该全局变量符号。

// 只写了dalvik的代码,art的就不写了
typedef unsigned char wbool;
typedef wbool (*PPP)();
void NativeIsDBGConnected()
{
void* Handle=NULL;
Handle=dlopen("/system/lib/libdvm.so", RTLD_LAZY);
if(NULL==Handle)
{
LOGA("dlopen打开libdvm.so失败!\n");
return;
}
PPP Fun = (PPP)dlsym(Handle, "_Z25dvmDbgIsDebuggerConnectedv");
if(NULL==Fun)
{
LOGA("dlsym获取_Z25dvmDbgIsDebuggerConnectedv失败!\n");
return;
}
else
{
wbool ret = Fun();
if(1==ret)
{
// 此处判定为调试模式
LOGA("dalvikm模式,调试状态!\n");
return;
}
}
return;
}

每个进程同时刻只能被1个调试进程ptrace(不知道ptrace的可以去看http://blog.csdn.net/edonlii/article/details/8717029)
主动ptrace本进程
// 单线程ptrace
void ptraceCheck()
{
// ptrace如果被调试返回值为-1,如果正常运行,返回值为0
int iRet=ptrace(PTRACE_TRACEME, 0, 0, 0);
if(-1 == iRet)
{
LOGA("ptrace失败,进程正在被调试\n");
return;
}
else
{
LOGB("ptrace的返回值为:%d\n",iRet);
return;
}
}

so文件中函数的指令是固定,但是如果被下了软件断点,指令就会发生改变(断点地址被改
写为bkpt断点指令),可以计算内存中一段指令的hash值进行校验,检测函数是否被修改或
被下断点

如果函数被下软件断点,则断点地址会被改写为bkpt指令,
可以在函数体中搜索bkpt指令来检测软件断点

// 参数1:函数首地址 参数2:函数size
typedef uint8_t u8;
typedef uint32_t u32;
void checkbkpt(u8* addr,u32 size)
{
// 结果
u32 uRet=0;
// 断点指令
// u8 armBkpt[4]={0xf0,0x01,0xf0,0xe7};
// u8 thumbBkpt[2]={0x10,0xde};
u8 armBkpt[4]={0};
armBkpt[0]=0xf0;
armBkpt[1]=0x01;
armBkpt[2]=0xf0;
armBkpt[3]=0xe7;
u8 thumbBkpt[2]={0};
thumbBkpt[0]=0x10;
thumbBkpt[1]=0xde;
// 判断模式
int mode=(u32)addr%2;
if(1==mode) {
LOGA("checkbkpt:(thumb mode)该地址为thumb模式\n");
u8* start=(u8*)((u32)addr-1);
u8* end=(u8*)((u32)start+size);
// 遍历对比
while(1)
{
if(start >= end) {
uRet=0;
LOGA("checkbkpt:(no find bkpt)没有发现断点.\n");
break;
}
if( 0==memcmp(start,thumbBkpt,2) ) {
uRet=1;
LOGA("checkbkpt:(find it)发现断点.\n");
break;
}
start=start+2;
}//while
}//if
else
{
LOGA("checkbkpt:(arm mode)该地址为arm模式\n");
u8* start=(u8*)addr;
u8* end=(u8*)((u32)start+size);
// 遍历对比
while(1)
{
if (start >= end) {
uRet = 0;
LOGA("checkbkpt:(no find)没有发现断点.\n");
break;
}
if (0 == memcmp(start,armBkpt , 4)){
uRet = 1;
LOGA("checkbkpt:(find it)发现断点.\n");
break;
}
start = start + 4;
}//while
}//else
return;
}

安卓native下最流行的反调试方案是读取进程的status或stat来检测tracepid,原理是调试状
态下的进程tracepid不为0

bool checkSystem()
{
// 建立管道
int pipefd[2];
if (-1 == pipe(pipefd)){
LOGA("pipe() error.\n");
return false;
}
// 创建子进程
pid_t pid = fork();
LOGB("father pid is: %d\n",getpid());
LOGB("child pid is: %d\n",pid);
// for失败
if(0 > pid) {
LOGA("fork() error.\n");
return false;
}
// 子进程程序
int childTracePid=0;
if ( 0 == pid )
{
int iRet = ptrace(PTRACE_TRACEME, 0, 0, 0);
if (-1 == iRet)
{
LOGA("child ptrace failed.\n");
exit(0);
}
LOGA("%s ptrace succeed.\n");
// 获取tracepid
char pathbuf[0x100] = {0};
char readbuf[100] = {0};
sprintf(pathbuf, "/proc/%d/status", getpid());
int fd = openat(NULL, pathbuf, O_RDONLY);
if (-1 == fd) {
LOGA("openat failed.\n");
}
read(fd, readbuf, 100);
close(fd);
uint8_t *start = (uint8_t *) readbuf;
uint8_t des[100] = {0x54, 0x72, 0x61, 0x63, 0x65, 0x72, 0x5
0, 0x69, 0x64, 0x3A,0x09};
int i = 100;
bool flag= false;
while (--i)
{
if( 0==memcmp(start,des,10) )
{
start=start+11;
childTracePid=atoi((char*)start);
flag= true;
break;
}else
{
start=start+1;
flag= false;
}
}//while
if(false==flag) {
LOGA("get tracepid failed.\n");
return false;
}
// 向管道写入数据

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 2
支持
分享
最新回复 (17)
雪    币: 776
活跃值: (1423)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
2
排版整理一下吧
2017-12-15 11:18
0
雪    币: 2719
活跃值: (1595)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
可以
2017-12-15 11:23
0
雪    币: 2872
活跃值: (185)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
膜  辛苦了
2017-12-15 12:30
0
雪    币: 60
活跃值: (44)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
真全,我之前只整理了10种
2017-12-15 20:30
0
雪    币: 60
活跃值: (44)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
大神,再来份绕过反调试总结
2017-12-15 20:35
0
雪    币: 387
活跃值: (897)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
7
辛苦了
2017-12-15 23:09
0
雪    币: 102
活跃值: (2050)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
mark
2017-12-19 00:00
0
雪    币: 3
活跃值: (401)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
很全面,mark,整理辛苦
2017-12-19 15:24
0
雪    币: 14855
活跃值: (6083)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
Inotify事件监控dump?
kill暂停进程,然后dump,Inotify就不能监控了。
2017-12-21 09:41
0
雪    币: 438
活跃值: (228)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
11
mark。顶楼主。
2017-12-21 13:06
0
雪    币: 2141
活跃值: (7226)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
12
楼主,这个资料是我写的,我只在qq群里上传过,你特喵的一点都没改就给发上来了,是啥意思。。
2017-12-21 16:07
2
雪    币: 60
活跃值: (44)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
爱吃菠菜 楼主,这个资料是我写的,我只在qq群里上传过,你特喵的一点都没改就给发上来了,是啥意思。。
感谢分享
2017-12-21 20:04
0
雪    币: 1269
活跃值: (131)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
14
是原创不
2017-12-21 21:09
0
雪    币: 438
活跃值: (228)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
15
爱吃菠菜 楼主,这个资料是我写的,我只在qq群里上传过,你特喵的一点都没改就给发上来了,是啥意思。。
太坑了。
2017-12-25 15:35
0
雪    币: 14
活跃值: (30)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
学习了
2018-11-1 17:16
0
雪    币: 29
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
17
爱吃菠菜 楼主,这个资料是我写的,我只在qq群里上传过,你特喵的一点都没改就给发上来了,是啥意思。。
是哪个群呢
2018-11-3 16:24
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
爱吃菠菜 楼主,这个资料是我写的,我只在qq群里上传过,你特喵的一点都没改就给发上来了,是啥意思。。
2020-9-25 14:31
0
游客
登录 | 注册 方可回帖
返回
//