Android anti debugging
1. Debug port detection
Read / proc / net / TCP to find the 23946 port used by IDA remote debugging. If it is found, the process is being debugged by IDA.
void CheckPort23946ByTcp()
{
FILE* pfile=NULL;
char buf[0x1000]={0};
//Execute command
char* strCatTcp= "cat /proc/net/tcp |grep :5D8A";
//char* strNetstat="netstat |grep :23946";
pfile=popen(strCatTcp,"r");
if(NULL==pfile)
{
Loga ("checkport23946bytcp Popen open command failed!");
Return;
}
//Get results
while(fgets(buf,sizeof(buf),pfile))
{
//When it is executed here, it is judged as debugging status
Loga ("result of cat / proc / net / TCP | grep: 5d8a execution: \ n");
LOGB("%s",buf);
}//while
pclose(pfile);
}
2. Debugging process name detection
Traverse the process, find the fixed process name, and find the debugger running. (for example, a fixed process name, Android server, GDB server, etc.)
void SearchObjProcess()
{
FILE* pfile=NULL;
char buf[0x1000]={0};
//Execute command
//Pfile = Popen ("PS | awk '{print $9}'", "R"); / / some do not support the awk command
pfile=popen("ps","r");
if(NULL==pfile)
{
Loga ("searchobjprocess Popen open command failed!");
Return;
}
//Get results
Loga ("Popen scheme: \ n");
while(fgets(buf,sizeof(buf),pfile))
{
//Printing process
Logb ("traversal process:% s \ n", buf);
//Find substring
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)
{
//When it is executed here, it is judged as debugging status
Logb ("discovery target process:% s \ n", buf);
}//if
}//while
pclose(pfile);
}
3. Parent process name detection
Sometimes, instead of using APK additional debugging method to reverse, write an. Out executable file to load so directly
Debugging, so that the name of the parent process of the program is different from the name of the parent process that normally starts APK.
Read / proc / PID / CmdLine to see if the content is zygote
void CheckParents()
{
///////////////////
/ / set buf
char strPpidCmdline[0x100]={0};
snprintf(strPpidCmdline, sizeof(strPpidCmdline), "/proc/%d/cmdl
ine", getppid());
//Open file
int file=open(strPpidCmdline,O_RDONLY);
If (file<0)
{
Loga ("checkparents open error!");
Return;
}
//File contents read into memory
memset(strPpidCmdline,0,sizeof(strPpidCmdline));
ssize_t ret=read(file,strPpidCmdline,sizeof(strPpidCmdline));
If (-1==ret)
{
Loga ("checkparents read error!");
Return;
}
//Can't find return 0
char sRet=strstr(strPpidCmdline,"zygote");
if(NULL==sRet)
{
//When it is executed here, it is judged as debugging status
Loga ("parent process CmdLine has no zygote substring!");
Return;
}
Int i=0;
Return;
}
4. Self process name detection (same as above)
5. APK thread detection
A normal APK process usually has more than ten threads running (for example, there will be jdwp threads),
There is usually only one thread to load so by writing executable,
According to this difference, we can test the debugging environment
void CheckTaskCount()
{
char buf[0x100]={0};
char* str="/proc/%d/task";
snprintf(buf,sizeof(buf),str,getpid());
//Open Directory:
DIR* pdir = opendir(buf);
If (pdir)
{
perror("CheckTaskCount open() fail.\n");
Return;
}
//To view the number of files in the directory:
struct dirent* pde=NULL;
Int Count=0;
while ((pde = readdir(pdir)))
{
//Character filtering
if ((pde->d_name[0] <= '9') && (pde->d_name[0] >= '0'))
{
++Count;
Logb ("% d thread Name:% s \ n", count, PDE - > d_name);
}
}
Logb ("number of threads is:% d", count);
If (1>=Count)
{
//It is determined as debugging status here
Loga ("debug state!");
}
Int i=0;
Return;
}
6. APK process FD file detection
According to the number difference of files in / proc / PID / FD / path, the process status is judged.
7. Android system has its own debugging and detection function
Analyze the implementation of the debug detection function isdebuggerconnected() in native,
Try to use in native
(1) In Dalvik mode:
Find the dvmdbgisdebuggerconnected() function in libdvm.so in the process,
Call him to see if the program is being debugged.
dlopen(/system/lib/libdvm.so)
dlsym(_Z25dvmDbgIsDebuggerConnectedv)
(2) In art mode:
In art mode, the results are stored in the global variable gdebuggeractive in libart.so,
The symbol name is "zn3art3dbg15gdebuggeractivee".
But it seems that the new version of Android does not allow non NDK native libraries, and dlopen (libart. So) will fail.
So we can't use Dalvik.
There is a troublesome way to search libart module in memory manually, and then find the global variable symbol manually.
//Only Dalvik's code is written, and art's is not
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 failed to open libdvm. So!");
Return;
}
PPP Fun = (PPP)dlsym(Handle, "_Z25dvmDbgIsDebuggerConnectedv");
if(NULL==Fun)
{
Loga ("dlsym failed to get" z25dvmdbgisdebuggerconnectedv! ");
Return;
}
Else
{
wbool ret = Fun();
If (1==ret)
{
//It is determined as debugging mode here
Loga ("dalvikm mode, debug state!");
Return;
}
}
Return;
}
8. Ptrace detection
Each process can only be ptrace by one debugging process at the same time (for those who don't know ptrace, see http://blog.csdn.net/edonlii/article/details/8717029)
Active ptrace process
//Single thread ptrace
void ptraceCheck()
{
//Ptrace returns - 1 if it is debugged and 0 if it is running normally
int iRet=ptrace(PTRACE_TRACEME, 0, 0, 0);
if(-1 == iRet)
{
Loga ("ptrace failed, process is being debugged \ n");
Return;
}
Else
{
Logb ("ptrace return value is:% d \ n", IRET);
Return;
}
}
9. Function hash value detection
The instruction of function in so file is fixed, but if the software breakpoint is set, the instruction will change (the breakpoint address is changed
Written as Bkpt breakpoint instruction), it can calculate the hash value of a section of instruction in memory to check whether the function has been modified or not
Broken point
10. Breakpoint instruction detection
If the function is broken by the software, the breakpoint address will be rewritten as the Bkpt instruction,
The Bkpt instruction can be searched in the function body to detect software breakpoints
//Parameter 1: function first address parameter 2: function size
typedef uint8_t u8;
typedef uint32_t u32;
void checkbkpt(u8* addr,u32 size)
{
/ / result
U32 uRet=0;
//Breakpoint instruction
// 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;
//Judgment mode
int mode=(u32)addr%2;
if(1==mode) {
Loga ("checkbkpt: (thumb mode) the address is thumb mode \ n");
u8* start=(u8*)((u32)addr-1);
u8* end=(u8*)((u32)start+size);
//Ergodic comparison
While (1)
{
if(start >= end) {
URet=0;
Loga ("checkbkpt: (no find Bkpt) no breakpoint found. \ n");
Break;
}
if( 0==memcmp(start,thumbBkpt,2) ) {
URet=1;
Loga ("checkbkpt: (find it) found breakpoint. \ n");
Break;
}
start=start+2;
}//while
}//if
Else
{
Loga ("checkbkpt: (arm mode) the address is arm mode \ n";
u8* start=(u8*)addr;
u8* end=(u8*)((u32)start+size);
//Ergodic comparison
While (1)
{
if (start >= end) {
URet = 0;
Loga ("checkbkpt: (no find) no breakpoint found. \ n");
Break;
}
if (0 == memcmp(start,armBkpt , 4)){
URet = 1;
Loga ("checkbkpt: (find it) found breakpoint. \ n");
Break;
}
start = start + 4;
}//while
}//else
Return;
}
11. System source code modification detection
The most popular anti debugging scheme under Android native is to read the status or status of the process to detect traceid. The principle is debugging
Process traceid in state is not 0
bool checkSystem()
{
//Establish pipeline
int pipefd[2];
if (-1 == pipe(pipefd)){
LOGA("pipe() error.\n");
return false;
}
//Create child process
pid_t pid = fork();
LOGB("father pid is: %d\n",getpid());
LOGB("child pid is: %d\n",pid);
/ / for failed
if(0 > pid) {
LOGA("fork() error.\n");
return false;
}
//Subprocess program
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");
//Get traceid
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;
}
//Write data to pipe
Close (pipefd [0]); / / close the pipeline read end
Write (pipefd [1], (void *) & childtracepid, 4); / / write to the write end of the pipeline
data
Close (pipefd [1]); / / close the pipeline after writing
end
LOGA("child succeed, Finish.\n");
Exit (0);
}
Else
{
//Parent process program
Loga ("start waiting for child process. \ n");
Waitpid (PID, null, null); / / wait for subprocess
End
int buf2 = 0;
Close (pipefd [1]); / / close the writer
Read (pipefd [0], (void *) & buf2, 4); / / read from the reader
Data to buf
Close (pipefd [0]); / / close the reader
Logb ("content passed by subprocess is:% d \ n", buf2); / / output content
//Traceid after judging the child process ptace
if(0 == buf2) {
Loga ("source code has been modified. \ n");
}else{
Loga ("source code has not been modified. \ n");
}
Return true;
}
}
Void Smain ()
{
bool bRet=checkSystem();
if(true==bRet)
LOGA("check succeed.\n");
Else
LOGA("check failed.\n");
LOGB("main Finish pid:%d\n",getpid());
Return;
}
12. Detection of first intercepted signal characteristics by IDA
Ida will intercept the signal first, so that the process cannot receive the signal, so that the signal processing function will not be executed. Key processes
In the signal processing function, if it is not executed, it is the debugged state.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void myhandler(int sig)
{
//signal(5, myhandler);
printf("myhandler.\n");
Return;
}
int g_ret = 0;
int main(int argc, char **argv)
{
//Set the processing function of sigtrap signal to myhandler()
g_ret = (int)signal(SIGTRAP, myhandler);
if ( (int)SIG_ERR == g_ret )
printf("signal ret value is SIG_ERR.\n");
//Print the return value of signal (address of original processing function)
printf("signal ret value is %x\n",(unsigned char*)g_ret);
//Send sigtrap signal to own process actively
raise(SIGTRAP);
raise(SIGTRAP);
raise(SIGTRAP);
kill(getpid(), SIGTRAP);
printf("main.\n");
Return 0;
}
13. Using IDA to resolve defects and reverse debugging
Ida uses recursive descent algorithm to disassemble instructions. The biggest disadvantage of this algorithm is that it can't handle indirect code paths,
The jump calculated dynamically is not recognized. Because of the arm and thumb instruction set, the instruction set is involved in the arm architecture
In some cases, IDA can not recognize arm and thumb instructions intelligently, which leads to the failure of pseudo code restoration.
In IDA dynamic debugging, this problem still exists. If the breakpoint is written in the wrong place of instruction identification, it may cause debugging
The device crashed. (it may write breakpoints, I don't know whether to write arm or thumb, resulting in crash)
#if(JUDGE_THUMB)
#define GETPC_KILL_IDAF5_SKATEBOARD \
__asm __volatile( \
"mov r0,pc \n\t" \
"adds r0,0x9 \n\t" \
"push {r0} \n\t" \
"pop {r0} \n\t" \
"bx r0 \n\t" \
"
".byte 0x00 \n\t" \
".byte 0xBF \n\t" \
"
".byte 0x00 \n\t" \
".byte 0xBF \n\t" \
"
".byte 0x00 \n\t" \
".byte 0xBF \n\t" \
:: "R0".
);
#else
#define GETPC_KILL_IDAF5_SKATEBOARD \
__asm __volatile( \
"mov r0,pc \n\t" \
"add r0,0x10 \n\t" \
"push {r0} \n\t" \
"pop {r0} \n\t" \
"bx r0 \n\t" \
".int 0xE1A00000 \n\t" \
".int 0xE1A00000 \n\t" \
".int 0xE1A00000 \n\t" \
".int 0xE1A00000 \n\t" \
:: "R0".
);
#endif
//Constant label version
#if(JUDGE_THUMB)
#define IDAF5_CONST_1_2 \
__asm __volatile( \
"b T1 \n\t" \
"T2: \n\t".
"adds r0,1 \n\t" \
"bx r0 \n\t" \
"T1: \n\t".
"mov r0,pc \n\t" \
"b T2 \n\t" \
:: "R0".
);
#else
#define IDAF5_CONST_1_2 \
__asm __volatile( \
"b T1 \n\t" \
"T2: \n\t".
"bx r0 \n\t" \
"T1: \n\t".
"mov r0,pc \n\t" \
"b T2 \n\t" \
:: "R0".
);
#endif
14.5 code execution time detection
The first type:
Principle:
For a piece of code, get the time at a, run it for a while, and then get the time at B,
Then calculate the time difference through (b time... A time). Normally, the time difference will be very small,
If the time difference is large, it means that it is being debugged step by step.
Practice:
Five APIs to get time:
Time () function
Time structure
Clock() function
Clock ﹣ T structure
Gettimeofday() function
Timeval structure
Timezone structure
Clock ou gettime() function
Timespec structure
Getusage() function
Rusage structure
#include <sys/time.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
static int _getrusage (); //Invalid
static int _clock (); //Invalid
static int _time ();
static int _gettimeofday ();
static int _clock_gettime ();
Int main ()
{
_getrusage ();
_clock ();
_time ();
_gettimeofday ();
_clock_gettime ();
Return 0;
}
int _getrusage ()
{
struct rusage t1;
/* breakpoint */
getrusage (RUSAGE_SELF, &t1);
long used = t1.ru_utime.tv_sec + t1.ru_stime.tv_sec;
if (used > 2) {
puts ("debugged");
}
Return 0;
}
int _clock ()
{
clock_t t1, t2;
t1 = clock ();
/* breakpoint */
t2 = clock ();
double used = (double)(t2 - t1) / CLOCKS_PER_SEC;
if (used > 2) {
puts ("debugged");
}
Return 0;
}
Int _time ()
{
time_t t1, t2;
Time (&t1);
/* breakpoint */
Time (&t2);
if (t2 - t1 > 2) {
puts ("debugged");
}
Return 0;
}
int _gettimeofday ()
{
struct timeval t1, t2;
struct timezone t;
gettimeofday (&t1, &t);
/* breakpoint */
gettimeofday (&t2, &t);
if (t2.tv_sec - t1.tv_sec >2 ) {
puts ("debugged");
}
Return 0;
}
int _clock_gettime ()
{
struct timespec t1, t2;
clock_gettime (CLOCK_REALTIME, &t1);
/* breakpoint */
clock_gettime (CLOCK_REALTIME, &t2);
if (t2.tv_sec - t1.tv_sec > 2) {
puts ("debugged");
}
Return 0;
}
15. Three kinds of process information structure detection
Some process files store process information, which can be read to know whether it is debugging status.
The first is:
/proc/pid/status
/proc/pid/task/pid/status
Tracerpid is not 0
Write t (tracing stop) in the status field
Second species:
/proc/pid/stat
/proc/pid/task/pid/stat
The second field is t
Third species:
/proc/pid/wchan
/proc/pid/task/pid/wchan
Ptrace_stop
16. Inotify event monitoring dump
Usually, the shell will decrypt the text before the program runs, so the shelling can be dump through DD and GDB ﹣ gcore
/Proc / PID / MEM or / proc / PID / pagemap to get the decrypted code content.
You can monitor the opening or access events of MEM or pagemap through the inotify series API,
Stop dump by ending the process as soon as it occurs.
void thread_watchDumpPagemap()
{
LOGA("-------------------watchDump:Pagemap-------------------
\n "";
char dirName[NAME_MAX]={0};
snprintf(dirName,NAME_MAX,"/proc/%d/pagemap",getpid());
int fd = inotify_init();
If (FD < 0)
{
LOGA("inotify_init err.\n");
Return;
}
int wd = inotify_add_watch(fd,dirName,IN_ALL_EVENTS);
If (WD < 0)
{
LOGA("inotify_add_watch err.\n");
Close (FD);
Return;
}
const int buflen=sizeof(struct inotify_event) * 0x100;
char buf[buflen]={0};
fd_set readfds;
While (1)
{
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
Int IRET = select (FD + 1, & readfds, 0,0,0); / / blocked here
Logb ("return value of IRET:% d \ n", IRET);
If (-1==iRet)
Break;
If (iRet)
{
memset(buf,0,buflen);
int len = read(fd,buf,buflen);
Int i=0;
while(i < len)
{
struct inotify_event *event = (struct inotify_even
T*) &buf[i];
Logb ("the value of 1 event mask is:% d \ n", event - > mask);
if( (event->mask==IN_OPEN) )
{
//Here, it is determined that there is true and execution crashes
Logb ("2 someone opens pagemap, the% d time. \ n \ n", I);
//__asm __volatile(".int 0x8c89fa98");
}
i += sizeof (struct inotify_event) + event->len;
}
Loga ("------ 3 exit small loop ----- \ n");
}
}
inotify_rm_watch(fd,wd);
Close (FD);
Loga ("------ 4 exit the large cycle, turn off monitoring ----- \ n");
Return;
}
Void Smain ()
{
//Monitoring / proc / PID / MEM
pthread_t ptMem,t,ptPageMap;
Int iRet=0;
//Monitoring / proc / PID / pagemap
iRet=pthread_create(&ptPageMap,NULL,(PPP)thread_watchDumpPagema
P, NULL);
If (0! =iRet)
{
LOGA("Create,thread_watchDumpPagemap,error!\n");
Return;
}
iRet=pthread_detach(ptPageMap);
If (0! =iRet)
{
LOGA("pthread_detach,thread_watchDumpPagemap,error!\n");
Return;
}
LOGA("-------------------smain-------------------\n");
LOGB("pid:%d\n",getpid());
Return;
}
The topic of the 2020 SDC (security developers Summit) was collected in Beijing, China in July!