IMCAFS

Home

snow safety forum

Posted by lipsius at 2020-03-07
all

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!