声明

本文仅限于技术讨论,不得用于非法途径,后果自负。

参考资料

  1. https://bbs.pediy.com/thread-206293.htm
  2. https://github.com/parkerpeng/DroidAnti
  3. http://blog.csdn.net/cool_way/article/details/22827433
  4. http://blog.csdn.net/myarrow/article/details/7096460
  5. http://blog.csdn.net/guojin08/article/details/9454467
  6. http://blog.sina.com.cn/s/blog_628cc2b70101c8zu.html
  7. https://www.cnblogs.com/skywang12345/p/3624177.html

编写目的

防内存dump比较笼统,本篇只介绍使用inotify相关实现(以BB为例)。

写在前面

内存dump介绍

关于内存dump相关介绍,请参考如下链接:

  1. 讨论android加固防内存dump的技术及vmp壳的防护强度:
    https://bbs.pediy.com/thread-206293.htm

  2. android应用反调试以及反内存dump代码收集:
    https://github.com/parkerpeng/DroidAnti

inotify主要功能

关于inotify相关介绍,请参考如下链接:

  1. inotify不生效问题:
    http://blog.csdn.net/cool_way/article/details/22827433

  2. Linux inotify功能及实现原理:
    http://blog.csdn.net/myarrow/article/details/7096460

/proc/pid/mem 与/proc/pid/pagemep

  1. 使用/proc/pid/mem访问其他进程的内存变量:
    http://blog.csdn.net/guojin08/article/details/9454467
  2. pagemap的解读:
    http://blog.sina.com.cn/s/blog_628cc2b70101c8zu.html

inotify防内存dump

  1. 从上面的知识可知,通过对目标进程文件/proc/pid/mem文件操作,可以获得其内存的数据。

  2. 而inotify可以监控文件系统的变化,当文件被打开、删除,读写等操作时,同时用户相应变化。

  3. 因此可以通过监控/proc/pid/mem 与/proc/pid/pagemep来防止内存dump。

    BB对防内存dump的介绍

    我们先看一下BB在防篡改技术的介绍,下图是BB官网上关于防篡改的介绍。梆梆官网介绍

    从官网上无法判断其采取何种措施,下面通过实际逆向分析来学习其相关防护策略。 首先介绍下其使用到的数据结构。

算法与数据结构

防内存dump用到了红黑树的数据结构。下面是关于红黑树数据结构的介绍。
红黑树之 C语言的实现:
https://www.cnblogs.com/skywang12345/p/3624177.html

自定义的结构变量

FileWatchKey结构

其定义如下:

typedef struct FileWatchKey
{char* fileName;                //监控的文件名int      wd;                   //inotify_add_watch 返回值
}FileWatchKey;

FileWatchKey结构用于保存监控的文件名以及对应的inotify_add_watch句柄。

RBTree数据结构

这个结构用于保存所有监控文件的信息,其定义如下:

typedef struct  RBTree
{RBTree*    left;                     //红黑树左节点RBTree* right;                       //红黑树右节点RBTree* parent;                      //父节点int        color;                    //红节点 黑节点FileWatchKey* keybuf;                //key
} RBTree;

RBRoot数据结构

本结构用于定义头结点以及用于节点比较的函数指针。

typedef struct RBRoot
{void* compareFun;                    //用于红黑树比较的函数int      const_0;                    //常量RBTree* treeHead;                    //红黑树头结点
}RBRoot;

逆向分析流程

创建线程用于文件监控

该函数位于libdexhelp.so文件中。如下:

void __fastcall createFileWatch_thread(int fatherPid, pthread_t a2)
{ int v2; // r4 signed int v3; // r5 _DWORD *pfatherPid; // r6 pthread_t newthread; // [sp+4h] [bp-14h] newthread = a2; v2 = fatherPid; v3 = 31; pfatherPid = malloc(4u); *pfatherPid = v2; while ( pthread_create(&newthread, 0, (void *(*)(void *))File_notify_threadProc, pfatherPid) ) { if ( !--v3 ) break; sleep(1u); }
}

从上面的代码可以看到File_notify_threadProc为真正的处理函数。

ile_notify_threadProc 函数

signed int __fastcall File_notify_threadProc(__pid_t *pfatherPid) int fatherPid; // r5
unsigned int result; // r0
signed int v3; // r6
_DWORD *v4; // r7 struct inotify_event *inotifyeventStr; // r0 pthread_t newthread; // [sp+4h] [bp-1Ch] fatherPid = *pfatherPid; free(pfatherPid); result = inotify_init_function(); if ( result ) { inotify_add_watchByPid(fatherPid); v3 = 0x1F; v4 = malloc(4u); *v4 = fatherPid; while ( pthread_create(&newthread, 0, (void *(*)(void *))watchAllTask_threadProc, v4) ) { if ( !--v3 ) break; sleep(1u); } inotifyeventStr = (struct inotify_event *)read_filewatch_event(-1); if ( inotifyeventStr ) GetFileWatchRBTreeKeyName(inotifyeventStr->wd); filewatch_Delete(fatherPid); pthread_kill(newthread, 10); result = killProcess(fatherPid, 9);} return result;

该函数步骤如下:

  1. 调用 inotify_init_function函数用于初始化红黑树头信息
  2. 调用inotify_add_watchByPid(fatherPid)函数,将父进程的/proc/fatherpid/mem与/proc/fatherpid/pagemap纳入到监控中,同时将相应文件名和wd插入到红黑树中。
  3. 创建线程watchAllTask_threadProc,其将/proc/fatherpid/task/下的所有线程对应的mem与pagemap文件纳入到监控中,同时将
    同时将相应文件名和wd插入到红黑树中。
  4. 调用read_filewatch_event函数对进行监控,如果没发生事件,则阻塞,如果发生事件,则函数返回。
  5. 调用filewatch_Delete移除监控事件。
  6. 结束watchAllTask_threadProc线程。
  7. 结束父进程。
  8. 线程退出。

inotify_init_function函数

该函数用于初始化文件监控相关信息。本函数经过了混淆,去除如下:

signed int __fastcall inotify_init_function()
{ signed int result; // r0 //如果初始化过,则直接返回。 if ( g_inotify_init_finshFlag ) return 1; g_fileWatch_errno = 0; //初始化文件监控 g_inotify_init = inotify_init(); if ( g_inotify_init < 0 ) { result = 0; g_fileWatch_errno = g_inotify_init; return result; } //置位初始化完成标志 g_inotify_init_finshFlag = 1;          //初始化以监控句柄比较的红黑树RBRoot 结构 g_fileWatch_wd_root = (struct RBRoot *)inotifyfile_ini((int)is_same_inotify_wd, 0); //初始化以监控文件名比较的红黑树RBRoot 结构 g_fileWatch_name_root = (struct RBRoot *)inotifyfile_ini((int)is_same_inotify_filename, 0); return 1;
}

从上面可以看到其主要是调用inotifyfile_ini 用于初始化g_fileWatch_wd_root以及g_fileWatch_wd_root,对应的数据结构为RBRoot。
下面看看inotifyfile_ini函数:

truct RBRoot *__fastcall inotifyfile_ini(void *comparefun, int const_0)
{ void *comparefun_1; // r5 int const_0_1; // r4 struct RBRoot *result; // r0 comparefun_1 = comparefun; const_0_1 = const_0; result = (struct RBRoot *)malloc(0xCu); if ( result ) { result->fun = comparefun_1; result->const_0 = const_0_1; result->root = (struct RBTree *)&g_inotify_node_NoValidFlag; } return result;
}

从上面可以看到inotifyfile_ini用于malloc一个RBRoot结构,同时将初始化相关成员。其中g_inotify_node_NoValidFlag表示头结点无效。因为目前没有设置任何要监控的文件。
我们看一下2个用于比较的函数。is_same_inotify_wd与is_same_inotify_filename。

is_same_inotify_wd函数

该函数用于比较红黑树的key为wd。

struct FileWatchKey *__fastcall is_same_inotify_wd(struct FileWatchKey *node, int a2)
{ struct FileWatchKey *result; // r0 if ( node && a2 ) result = (struct FileWatchKey *)(node->wd - *(_DWORD *)(a2 + 4)); else result = (struct FileWatchKey *)((char *)node - a2); return result;
}

本函数主要用于判断新的节点是左节点还是右节点。返回0,是同一个节点,大于0是在右节点分支,小于0在左节点分支。

is_same_inotify_filename函数

该函数用于比较红黑树的key为filename。

int __fastcall is_same_inotify_filename(struct FileWatchKey *node, const char *a2)
{ int result; // r0 if ( node && a2 ) result = strcmp(node->name, *(const char **)a2); else result = (char *)node - a2; return result;
}

本函数主要用于判断新的节点是左节点还是右节点。返回0,是同一个节点,大于0是在右节点分支,小于0在左节点分支。

inotify_add_watchByPid

inotify_add_watchByPid函数将对应进程的mem与pagemap文件纳入到监控中,同时创建红黑树节点并插入到相应红黑树中。

int __fastcall inotify_add_watchByPid(int fatherPid)
{ int fatherPid_1; // r7 int v3; // [sp+0h] [bp-140h] char v4; // [sp+4h] [bp-13Ch] char v5; // [sp+5h] [bp-13Bh] char v6; // [sp+6h] [bp-13Ah] char v7; // [sp+7h] [bp-139h] char v8; // [sp+8h] [bp-138h] char v9; // [sp+9h] [bp-137h] char v10; // [sp+Ah] [bp-136h] char v11; // [sp+Bh] [bp-135h] char v12; // [sp+Ch] [bp-134h] char v13; // [sp+Dh] [bp-133h] char v14; // [sp+Eh] [bp-132h] char v15; // [sp+10h] [bp-130h] char v16; // [sp+11h] [bp-12Fh] char v17; // [sp+12h] [bp-12Eh] char v18; // [sp+13h] [bp-12Dh] char v19; // [sp+14h] [bp-12Ch] char v20; // [sp+15h] [bp-12Bh] char v21; // [sp+16h] [bp-12Ah] char v22; // [sp+17h] [bp-129h] char v23; // [sp+18h] [bp-128h] char v24; // [sp+19h] [bp-127h] char v25; // [sp+1Ah] [bp-126h] char v26; // [sp+1Bh] [bp-125h] char v27; // [sp+1Ch] [bp-124h] char v28; // [sp+1Dh] [bp-123h] char v29; // [sp+1Eh] [bp-122h] char v30; // [sp+1Fh] [bp-121h] char v31; // [sp+20h] [bp-120h] char v32; // [sp+21h] [bp-11Fh] char v33; // [sp+22h] [bp-11Eh] char procmemString; // [sp+24h] [bp-11Ch] fatherPid_1 = fatherPid; memset(&v3, 0, 0x10u); v4 = -34; v5 = -61; v6 = -49; v8 = -119; v9 = -64; BYTE1(v3) = 126; v10 = -56; HIWORD(v3) = -9085; v7 = -125; v11 = -125; v13 = -55; v12 = -63; v14 = -63; ((void (__fastcall *)(int *, signed int, signed int))DecodeString9)(&v3, 13, 0xD2);// /proc/%ld/mem sprintf(&procmemString, (const char *)&v3, fatherPid_1); inotify_add_watch_insert_node((int)&procmemString, 0xFFFu); memset(&v15, 0, 0x14u); v19 = -43; v20 = -56; v21 = -60; v23 = -126; v24 = -53; v25 = -61; v29 = -64; v17 = -120; v22 = -120; v26 = -120; v30 = -62; v16 = 85; v28 = -58; v31 = -54; v32 = -58; v18 = -41; v27 = -41; v33 = -41; ((void (__fastcall *)(char *, signed int, signed int))DecodeString9)(&v15, 17, 242);// /proc/%ld/pagemap sprintf(&procmemString, &v15, fatherPid_1); return inotify_add_watch_insert_node((int)&procmemString, 0xFFFu);
}

该函数做了如下事情:

  1. 调用DecodeString9解密字符串“/proc/%ld/mem”;
  2. 格式化字符串“ /proc/pid/mem”;
  3. 调用inotify_add_watch_insert_node 将对应文件纳入到监控中;
  4. 调用DecodeString9解密字符串“/proc/%ld/pagemap”;
  5. 格式化字符串“ /proc/pid/pagemap”;

DecodeString9函数

字符串解密函数,如下:

char * DecodeString9(char *buf, int len, char key)
{ int i; // r4 for(i = 0; i < len; i++) { buf[i]= buf[i + 2] ^key ^ buf[i+1]; } buf[i]=0; return buf;
}

inotify_add_watch_insert_node函数

signed int __fastcall inotify_add_watch_insert_node(int procmemString, uint32_t mask_2) { char *procmemString_1_1; // r6 int procmemString_1; // r4 const char *v4; // r1 int fd; // r0 int v6; // r4 int v7; // r5 uint32_t mask; // [sp+4h] [bp-1Ch] procmemString_1 = procmemString; mask = mask_2; g_fileWatch_errno = 0; for ( g_watchIndex = 0; ; ++g_watchIndex ) { v4 = *(const char **)(4 * g_watchIndex + procmemString_1); if ( !v4 ) return 1; fd = inotify_add_watch(g_inotify_init, v4, mask); g_fileWatch_fd = fd; if ( fd < 0 ) break; if ( !JudeFileIsDir(*(char **)(4 * g_watchIndex + procmemString_1)) || (v7 = *(_DWORD *)(4 * g_watchIndex + procmemString_1), *(_BYTE *)(v7 + strlen(*(_DWORD *)(4 * g_watchIndex + procmemString_1)) - 1) == '/') ) { // 是文件或者是目录同时最后一个字符是\  procmemString_1_1 = strdup(*(const char **)(4 * g_watchIndex + procmemString_1));} inotifyList_user_add_node(g_fileWatch_fd, procmemString_1_1); free(procmemString_1_1); } v6 = 0; if ( fd == -1 ) g_fileWatch_errno = *(_DWORD *)_errno(-1); return v6; }

本函数有如下步骤:

  1. 对输入的字符串数组进行inotify_add_watch;
  2. 调用JudeFileIsDir判断是否是目录
  3. 调用inotifyList_user_add_node将wd于文件名写入对应的红黑树中。

JudeFileIsDir函数

该函数去混淆后如下:

bool  JudeFileIsDir(char *file)
{ bool result; // r0 if(lstat(file, &g_fileStatStruct)<0) return false; return (g_fileStatStruct.st_mode & 0xF000) - 0x4000 <= 0;
}

inotifyList_user_add_node函数

_DWORD *__fastcall inotifyList_user_add_node(int fd, _DWORD *procmemString)
{ _DWORD *inotifyfileKeyBuf; // r4 const char *v3; // r5 int v4; // r6 inotifyfileKeyBuf = 0; if ( fd > 0 ) { inotifyfileKeyBuf = procmemString; if ( procmemString ) { v3 = (const char *)procmemString; v4 = fd; inotifyfileKeyBuf = (_DWORD *)getinotifyListByWDnode(fd); if ( !inotifyfileKeyBuf ) { inotifyfileKeyBuf = calloc(1u, 0x40u); inotifyfileKeyBuf[1] = v4; *inotifyfileKeyBuf = strdup(v3); insertNewNode((int)inotifyfileKeyBuf, (int)g_fileWatch_wd_root); insertNewNode((int)inotifyfileKeyBuf, (int)g_fileWatch_name_root); } } } return inotifyfileKeyBuf;
}

本函数有如下步骤:

  1. 调用getinotifyListByWDnode判断对应fd是否已经在红黑树中了;
  2. 如果在,则直接返回;
  3. 如果不在则调用insertNewNode插入到对应红黑树中。
    下面我们看一getinotifyListByWDnode函数
int __fastcall getinotifyListByWDnode(int node_wd, struct RBRoot *rbroot)
{ int result; // r0 int v3; // r0 if ( rbroot && (void **)rbroot->root != &g_inotify_node_NoValidFlag && (query_insert_node(0, node_wd, rbroot), (void **)v3 != &g_inotify_node_NoValidFlag) ) { result = *(_DWORD *)(v3 + 0x10); } else { result = 0; } return result;
}

getinotifyListByWDnode调用query_insert_node来进行查询。

void __fastcall query_insert_node(int createFlag, int inotifyfileKeyBuf, struct RBRoot *rbroot)
{ struct RBRoot *rbroot_1; // r7 signed int find; // r5 struct RBTree *curNode; // r4 void **fatherNode; // r6 int result; // r0 struct RBTree *tempNode; // r3 struct RBTree *newNode; // r5 struct RBTree *newNode_1; // r4 struct RBTree *rootHead; // r3 struct RBTree *parent; // r6 struct RBTree *gparent; // r1 struct RBTree *v14; // r2 struct RBTree *v15; // r1 struct RBTree *v16; // [sp+4h] [bp-24h] struct RBTree *root; // [sp+4h] [bp-24h] int inotifyfileKeyBuf_1; // [sp+8h] [bp-20h] int createFlag_1; // [sp+Ch] [bp-1Ch] int v20; // [sp+18h] [bp-10h] rbroot_1 = rbroot; find = 0; curNode = rbroot->root; fatherNode = &g_inotify_node_NoValidFlag; createFlag_1 = createFlag; inotifyfileKeyBuf_1 = inotifyfileKeyBuf; while ( curNode != (struct RBTree *)&g_inotify_node_NoValidFlag ) { if ( find ) goto LABEL_35; result = ((int (__fastcall *)(int, struct FileWatchKey *, int))rbroot_1->fun)( inotifyfileKeyBuf_1, curNode->keybuf, rbroot_1->const_0); if ( result >= 0 ) { if ( result ) { tempNode = curNode->right;  // 没找到,目标节点比当前节点大 } else { tempNode = curNode;        // 找到了 find = 1; } } else { tempNode = curNode->left;    // 没找到,比当前节点小 } fatherNode = (void **)&curNode->left; curNode = tempNode; } if ( find || !createFlag_1 || (newNode = (struct RBTree *)malloc(0x14u)) == 0 )
LABEL_35: JUMPOUT(__CS__, v20);          // 找到了或没找到但是不创建新节点,则返回 newNode->parent = (struct RBTree *)fatherNode;// 创建新节点,初始化 newNode->keybuf = (struct FileWatchKey *)inotifyfileKeyBuf_1; if ( fatherNode == &g_inotify_node_NoValidFlag ) { rbroot_1->root = newNode;     // 更新根节点 } else if ( ((int (__fastcall *)(int, void *, int))rbroot_1->fun)(inotifyfileKeyBuf_1, fatherNode[4], rbroot_1->const_0) >= 0 ) { fatherNode[1] = newNode;   // 比父节点大 更新父节点右节点 } else { *fatherNode = newNode;      // 比父节点小更新父节点的右节点 } newNode_1 = newNode; newNode->left = (struct RBTree *)&g_inotify_node_NoValidFlag;// 初始化新节点 newNode->right = (struct RBTree *)&g_inotify_node_NoValidFlag; newNode->color = 1;          // 先设置为红色 while ( 1 ) { while ( 1 ) { rootHead = rbroot_1->root; if ( newNode_1 == rootHead || (parent = newNode_1->parent, parent->color != 1) ) { rootHead->color = 0;// 新节点是跟节点 或者新节点的父节点颜色不是红色,则将当前节点设置成黑色并退出 goto LABEL_35; } gparent = parent->parent; v14 = gparent->left; if ( parent == gparent->left ) break; if ( v14->color == 1 ) { parent->color = 0; v14->color = 0; newNode_1->parent->parent->color = 1;
LABEL_31: newNode_1 = newNode_1->parent->parent; } else { root = (struct RBTree *)&rbroot_1->root; if ( newNode_1 == parent->left ) { rbtree_left_rotate(root, parent); newNode_1 = parent; } newNode_1->parent->color = 0; newNode_1->parent->parent->color = 1; rbtree_right_rotate(root, newNode_1->parent->parent); } } v15 = gparent->right; if ( v15->color == 1 ) { parent->color = 0; v15->color = 0; newNode_1->parent->parent->color = 1; goto LABEL_31; } v16 = (struct RBTree *)&rbroot_1->root; if ( newNode_1 == parent->right ) { rbtree_right_rotate(v16, parent); newNode_1 = parent; } newNode_1->parent->color = 0; newNode_1->parent->parent->color = 1; rbtree_left_rotate(v16, newNode_1->parent->parent); } }
}

query_insert_node函数进行如下操作:

  1. 遍历二叉树进行查找进行节点查找;
  2. 如果找到则返回对应节点;、如果没找到,并且不创建新节点则返回0;
  3. malloc一个新的RBTree;
  4. 初始化其父节点;
  5. 初始化新的RBTree;
  6. 调用 rbtree_left_rotate和rbtree_right_rotate对红黑树进行修正。

上面完成了getinotifyListByWDnode函数的分析,继续分析insertNewNode。

int __fastcall insertNewNode(int inotifyfileKeyBuf, int a2)
{ int result; // r0 int v3; // r0 if ( a2 && (query_insert_node(1, inotifyfileKeyBuf, (struct RBRoot *)a2), (void **)v3 != &g_inotify_node_NoValidFlag) ) result = *(_DWORD *)(v3 + 16); else result = 0; return result;
}

该函数调用query_insert_node进行新节点的插入操作。
至此函数inotify_add_watchByPid分析完了。
下面看看watchAllTask_threadProc函数的工作。

watchAllTask_threadProc线程入口函数

void __fastcall __noreturn watchAllTask_threadProc(int *pfatherPid)
{ struct dirent *v1; // r5 const char *v2; // r5 int v3; // r0 int threadID; // r0 DIR *dirp; // [sp+4h] [bp-2CCh] int pfatherPid_1; // [sp+Ch] [bp-2C4h] int dot; // [sp+14h] [bp-2BCh] int dotdot; // [sp+18h] [bp-2B8h] char v9; // [sp+1Ch] [bp-2B4h] char v10; // [sp+20h] [bp-2B0h] char v11; // [sp+21h] [bp-2AFh] char v12; // [sp+22h] [bp-2AEh] char v13; // [sp+23h] [bp-2ADh] char v14; // [sp+24h] [bp-2ACh] char v15; // [sp+25h] [bp-2ABh] void (__noreturn *pthread_exit)(); // [sp+28h] [bp-2A8h] char v17; // [sp+38h] [bp-298h] __int16 v18; // [sp+48h] [bp-288h] char v19; // [sp+A0h] [bp-230h] char v20; // [sp+A1h] [bp-22Fh] char v21; // [sp+A2h] [bp-22Eh] char v22; // [sp+A3h] [bp-22Dh] char v23; // [sp+A4h] [bp-22Ch] char v24; // [sp+A5h] [bp-22Bh] char v25; // [sp+A6h] [bp-22Ah] char v26; // [sp+A7h] [bp-229h] char v27; // [sp+A8h] [bp-228h] char v28; // [sp+A9h] [bp-227h] char v29; // [sp+AAh] [bp-226h] char v30; // [sp+ABh] [bp-225h] char v31; // [sp+ACh] [bp-224h] char v32; // [sp+ADh] [bp-223h] char v33; // [sp+AEh] [bp-222h] char v34; // [sp+AFh] [bp-221h] char v35; // [sp+B0h] [bp-220h] char proctaskString; // [sp+B4h] [bp-21Ch] char v37; // [sp+1B4h] [bp-11Ch] pfatherPid_1 = *pfatherPid; free(pfatherPid); memset(&pthread_exit, 0, 0x10u); pthread_exit = pthread_exit_0; sigaction(10, (const struct sigaction *)&pthread_exit, 0); memset(&v19, 0, 0x12u); v22 = 30; v23 = 28; v24 = 1; v25 = 13; v27 = 75; v28 = 2; v29 = 10; v31 = 26; v33 = 29; v20 = -88; v34 = 5; v21 = 65; v26 = 65; v30 = 65; v32 = 15; v35 = 65; ((void (__fastcall *)(char *))DecodeString9)(&v19);// /proc/%ld/task/ sprintf(&proctaskString, &v19, pfatherPid_1); while ( 1 ) { do dirp = opendir(&proctaskString); while ( !dirp ); while ( 1 ) { v1 = readdir(dirp); if ( !v1 ) break; dot = 0; *(_WORD *)((char *)&dot + 1) = -25275; ((void (__fastcall *)(int *, signed int, signed int))DecodeString9)(&dot, 1, 246);// . dotdot = 0; *(_WORD *)((char *)&dotdot + 1) = -21672; v2 = &v1->d_name[8]; v9 = 0; HIBYTE(dotdot) = -85; ((void (__fastcall *)(int *, signed int, signed int))DecodeString9)(&dotdot, 2, 221);// .. if ( strcmp(v2, (const char *)&dot) ) { if ( strcmp(v2, (const char *)&dotdot) ) { memset(&v37, 0, 0x100u); memset(&v10, 0, 7u); v11 = 23; v12 = -127; v14 = -127; v13 = -41; v15 = -41; ((void (__fastcall *)(char *, signed int, signed int))DecodeString9)(&v10, 4, 179);// %s%s sprintf(&v37, &v10, &proctaskString, v2);// /proc/15557/task/15557 if ( lstat(&v37, (struct stat *)&v17) != -1 && (v18 & 0xF000) == 0x4000 ) { v3 = atoi(v2); inotify_add_watchByPid(v3); threadID = atoi(v2); inotify_add_watchByTid(pfatherPid_1, threadID); } } } } closedir(dirp); sleep(2u); } }
}
  1. 调用DecodeString9解密字符串“/proc/%ld/task/”;
  2. 格式化字符串“/proc/pid/task/”;
  3. 调用opendir 打开“/proc/pid/task/”目录;
  4. 调用readdir读取“/proc/pid/task/”目录;
  5. 如果返回空,则到步骤11;
  6. 返回不是空,过滤字符串“ .”与“ ..”;
  7. 调用DecodeString9解密字符串“/proc/pid/task/tid”;
  8. 调用inotify_add_watchByPid将tid下的mem与pagemap文件纳入监控中;
  9. 调用inotify_add_watchByTid(pfatherPid_1,threadID)
    将“/proc/pid/task/tid”中的mem与pagemap纳入到监控中;
  10. 重复步骤4-步骤9;
  11. 调用closedir关闭目录;
  12. 线程睡眠2秒;
  13. 重复步骤1-12。

思考:从上面可以看到线程会持续的对应用的所有线程下的mem与pagemap文件进行监控,是否可以在步骤13直接线程结束?
这样是不行的,如果此时结束,对于后面新创建的线程则不能纳入到本进程中。对于已经被watch的文件再次watch将返回上次的wd,引用次数会加1。
这里面可能有个小问题是:如果线程被删除了则对应的红黑树链表的节点不会被删除,造成内存泄漏。极端情况应用一致持续不断的创建线程然后线程2秒后销毁,运行一段时间后内存会崩溃。
下面看一下inotify_add_watchByTid函数。

inotify_add_watchByTid函数

int __fastcall inotify_add_watchByTid(int fatherpid, int tid)
{ int fatherpid_1; // ST00_4 int tid_1; // ST04_4 int v4; // ST00_4 int v5; // ST04_4 char s; // [sp+8h] [bp-158h] char v8; // [sp+9h] [bp-157h] char v9; // [sp+Ah] [bp-156h] char v10; // [sp+Bh] [bp-155h] char v11; // [sp+Ch] [bp-154h] char v12; // [sp+Dh] [bp-153h] char v13; // [sp+Eh] [bp-152h] char v14; // [sp+Fh] [bp-151h] char v15; // [sp+10h] [bp-150h] char v16; // [sp+11h] [bp-14Fh] char v17; // [sp+12h] [bp-14Eh] char v18; // [sp+13h] [bp-14Dh] char v19; // [sp+14h] [bp-14Ch] char v20; // [sp+15h] [bp-14Bh] char v21; // [sp+16h] [bp-14Ah] char v22; // [sp+17h] [bp-149h] char v23; // [sp+18h] [bp-148h] char v24; // [sp+19h] [bp-147h] char v25; // [sp+1Ah] [bp-146h] char v26; // [sp+1Bh] [bp-145h] char v27; // [sp+1Ch] [bp-144h] char v28; // [sp+1Dh] [bp-143h] char v29; // [sp+1Eh] [bp-142h] char v30; // [sp+1Fh] [bp-141h] char v31; // [sp+24h] [bp-13Ch] char v32; // [sp+25h] [bp-13Bh] char v33; // [sp+26h] [bp-13Ah] char v34; // [sp+27h] [bp-139h] char v35; // [sp+28h] [bp-138h] char v36; // [sp+29h] [bp-137h] char v37; // [sp+2Ah] [bp-136h] char v38; // [sp+2Bh] [bp-135h] char v39; // [sp+2Ch] [bp-134h] char v40; // [sp+2Dh] [bp-133h] char v41; // [sp+2Eh] [bp-132h] char v42; // [sp+2Fh] [bp-131h] char v43; // [sp+30h] [bp-130h] char v44; // [sp+31h] [bp-12Fh] char v45; // [sp+32h] [bp-12Eh] char v46; // [sp+33h] [bp-12Dh] char v47; // [sp+34h] [bp-12Ch] char v48; // [sp+35h] [bp-12Bh] char v49; // [sp+36h] [bp-12Ah] char v50; // [sp+37h] [bp-129h] char v51; // [sp+38h] [bp-128h] char v52; // [sp+39h] [bp-127h] char v53; // [sp+3Ah] [bp-126h] char v54; // [sp+3Bh] [bp-125h] char v55; // [sp+3Ch] [bp-124h] char v56; // [sp+3Dh] [bp-123h] char v57; // [sp+3Eh] [bp-122h] char v58; // [sp+3Fh] [bp-121h] char v59; // [sp+44h] [bp-11Ch] fatherpid_1 = fatherpid; tid_1 = tid; memset(&s, 0, 0x19u); v10 = 5; v11 = 7; v19 = 1; v12 = 26; v8 = -4; v20 = 20; v17 = 17; v26 = 17; v9 = 90; v14 = 90; v18 = 90; v21 = 6; v23 = 90; v27 = 90; v15 = 80; v16 = 25; v24 = 80; v25 = 25; v29 = 16; v13 = 22; v22 = 30; v28 = 24; v30 = 24; ((void (__fastcall *)(char *))DecodeString9)(&s); sprintf(&v59, &s, fatherpid_1, tid_1, fatherpid_1, tid_1);// /proc/16709/task/16709/mem inotify_add_watch_insert_node((int)&v59, 0xFFFu); memset(&v31, 0, 0x1Du); v35 = -9; v36 = -22; v37 = -26; v32 = 99; v40 = -23; v45 = -10; v33 = -86; v38 = -86; v42 = -86; v47 = -86; v51 = -86; v41 = -31; v46 = -18; v54 = -30; v43 = -15; v49 = -23; v55 = -32; v34 = -11; v44 = -28; v50 = -31; v52 = -11; v53 = -28; v57 = -28; v58 = -11; v56 = -24; v39 = -96; v48 = -96; ((void (__fastcall *)(char *, signed int, signed int))DecodeString9)(&v31, 26, 230); sprintf(&v59, &v31, v4, v5); return inotify_add_watch_insert_node((int)&v59, 0xFFFu); }
}

这个函数相对比较简单。

里程碑

至此将目标文件纳入到监控中的相关处理已经分析完了,下面看发生相关事件是的处理流程。

read_filewatch_event函数

int __fastcall read_filewatch_event1(int a1_FF, int const_1)
{ char *v2; // r1 char *v3; // r3 struct timeval *timeout; // r4 int inotify_init; // r0 int v7; // r0 int v8; // r3 int a1_FF_1; // [sp+8h] [bp-20h] int const_1_1; // [sp+Ch] [bp-1Ch] if ( const_1 <= 0 ) return 0; a1_FF_1 = a1_FF; const_1_1 = const_1; setjmp((struct __jmp_buf_tag *)&unk_53D04); g_fileWatch_errno = 0; if ( dword_53E04 ) { if ( dword_53E04 <= dword_53E08 - 16 ) { v2 = (char *)&dword_53E0C + dword_53E04; dword_63E0C = (int)&dword_53E0C + dword_53E04; v3 = (char *)&(*(struct inotify_event **)((char *)&dword_53E0C + dword_53E04 + 12))[1] + dword_53E04; dword_53E04 = (int)v3; if ( v3 == (char *)dword_53E08 ) { dword_53E04 = 0; } else if ( (signed int)v3 > dword_53E08 ) { dword_53E08 = (char *)&dword_53E0C + dword_53E08 - v2; memcpy(&dword_53E0C, v2, dword_53E08); return read_filewatch_event1(a1_FF_1, const_1_1); } if ( g_inotify_init_flag1 ) p9E01CAAA70B3E111F16A18AB1BC2AB55((int *)v2); return dword_63E0C; } } else { dword_53E08 = dword_53E04; } g_FF = a1_FF_1; timeout = (struct timeval *)&g_FF; dword_53D00 = 0; if ( a1_FF_1 <= 0 ) timeout = 0; dword_63E10 = (int)timeout; memset(&g_fds, 0, 0x80u); inotify_init = g_inotify_init; *((_DWORD *)&unk_63DA8 + (g_inotify_init >> 5) + 0x1B) = 1 << (g_inotify_init & 0x1F); v7 = select(inotify_init + 1, (fd_set *)&g_fds, 0, 0, timeout);//  //监控fd的事件。当有事件发生时,返回值>0 g_slect_returnValue = v7; if ( v7 < 0 ) goto LABEL_21; if ( !v7 ) return 0; while ( 1 ) { v7 = ioctl(g_inotify_init, 0x541Bu, &g_fileWatch_event_MaxLen); g_slect_returnValue = v7; if ( v7 ) break; if ( g_fileWatch_event_MaxLen >= (unsigned int)(16 * const_1_1) ) goto LABEL_20; } if ( v7 == -1 ) goto LABEL_21;
LABEL_20: v7 = read(g_inotify_init, &(&dword_53E0C)[4 * dword_53E08], 0x10000 - dword_53E08); g_fileWatch_event_readLen = v7; if ( v7 < 0 ) {
LABEL_21: g_fileWatch_errno = *(_DWORD *)_errno(v7); return 0; } if ( !v7 ) return 0; dword_53E08 += v7; dword_63E0C = (int)&dword_53E0C; v8 = dword_53E18 + 0x10; if ( dword_53E18 + 0x10 == dword_53E08 ) v8 = 0; dword_53E04 = v8; if ( g_inotify_init_flag1 ) p9E01CAAA70B3E111F16A18AB1BC2AB55((int *)&dword_53E0C); return dword_63E0C;
}

read_filewatch_event函数步骤如下:

  1. 调用select函数对inotify初始化句柄进行阻塞。当发生事件时,则线程唤醒;
  2. 调用ioctl函数获得对应事件的长度;
  3. 调用read函数将发生的事件信息读取到全局变量中。
  4. 返回对应的事件BUF。
    当发生事件时,就开始进行事件处理流程,首先调用filewatch_Delete清除watch。

filewatch_Delete函数

int __fastcall filewatch_Delete(int fatherPid)
{ int fatherPid_1; // r7 int v3; // [sp+0h] [bp-140h] char v4; // [sp+4h] [bp-13Ch] char v5; // [sp+5h] [bp-13Bh] char v6; // [sp+6h] [bp-13Ah] char v7; // [sp+7h] [bp-139h] char v8; // [sp+8h] [bp-138h] char v9; // [sp+9h] [bp-137h] char v10; // [sp+Ah] [bp-136h] char v11; // [sp+Bh] [bp-135h] char v12; // [sp+Ch] [bp-134h] char v13; // [sp+Dh] [bp-133h] char v14; // [sp+Eh] [bp-132h] char v15; // [sp+10h] [bp-130h] char v16; // [sp+11h] [bp-12Fh] char v17; // [sp+12h] [bp-12Eh] char v18; // [sp+13h] [bp-12Dh] char v19; // [sp+14h] [bp-12Ch] char v20; // [sp+15h] [bp-12Bh] char v21; // [sp+16h] [bp-12Ah] char v22; // [sp+17h] [bp-129h] char v23; // [sp+18h] [bp-128h] char v24; // [sp+19h] [bp-127h] char v25; // [sp+1Ah] [bp-126h] char v26; // [sp+1Bh] [bp-125h] char v27; // [sp+1Ch] [bp-124h] char v28; // [sp+1Dh] [bp-123h] char v29; // [sp+1Eh] [bp-122h] char v30; // [sp+1Fh] [bp-121h] char v31; // [sp+20h] [bp-120h] char v32; // [sp+21h] [bp-11Fh] char v33; // [sp+22h] [bp-11Eh] char v34; // [sp+24h] [bp-11Ch] fatherPid_1 = fatherPid; memset(&v3, 0, 0x10u); v4 = -75; v5 = -88; v6 = -92; v8 = -30; v9 = -85; BYTE1(v3) = 62; v10 = -93; HIWORD(v3) = -18456; v7 = -24; v11 = -24; v13 = -94; v12 = -86; v14 = -86; ((void (__fastcall *)(int *, signed int, signed int))DecodeString9)(&v3, 13, 249); sprintf(&v34, (const char *)&v3, fatherPid_1);// /proc/1340/mem filewatch_DeleteByFile((int)&v34); memset(&v15, 0, 0x14u); v19 = 10; v20 = 23; v21 = 27; v23 = 93; v24 = 20; v25 = 28; v29 = 31; v17 = 87; v22 = 87; v26 = 87; v30 = 29; v16 = -111; v28 = 25; v31 = 21; v32 = 25; v18 = 8; v27 = 8; v33 = 8; ((void (__fastcall *)(char *, signed int, signed int))DecodeString9)(&v15, 17, 233); sprintf(&v34, &v15, fatherPid_1); return filewatch_DeleteByFile((int)&v34);
}

filewatch_Delete函数步骤如下:

  1. 格式化字符/proc/pid/mem;
  2. 调用filewatch_DeleteByFile删除/proc/pid/mem的watch;
  3. 格式化字符/proc/pid/pagemap;
  4. 调用filewatch_DeleteByFile删除/proc/pid/pagemap的watch;
    可能存在问题:只删除了进程的对应watch,对于/proc/tid/相关并没有删除。同时task目录下的也没有删除。
    下面看一下filewatch_DeleteByFile函数。

filewatch_DeleteByFile函数

filewatch_DeleteNode(keybuf, g_fileWatch_wd_root);
filewatch_DeleteNode(keybuf, g_fileWatch_name_root);
filewatch_rm(keybuf)
freeKeyBuf(keybuf);

该函数调用步骤如下:

  1. 调用filewatch_DeleteNode删除wd相关watch;
  2. 调用filewatch_DeleteNode删除filename相关watch;
  3. 调用filewatch_rm移除wd;
  4. 调用freeKeyBuf释放FileWatchKey。

函数filewatch_DeleteNode如下:

nt __fastcall filewatch_DeleteNode(struct FileWatchKey *keybuf, struct RBRoot *rbroot) struct RBRoot *v3; // r6 int v4; // r0 int v5; // r7 void **v6; // r5 void **v7; // r4 void **v8; // r3 struct RBTree **v9; // r2 struct RBTree *v10; // r1 struct RBTree *v11; // r2 struct RBTree *v12; // r3 struct RBTree *v13; // r2 int v14; // [sp+4h] [bp-1Ch] if ( !rbroot ) return 0; v3 = rbroot; query_insert_node(0, (int)keybuf, rbroot); v5 = v4; if ( (void **)v4 == &g_inotify_node_NoValidFlag ) return 0; v6 = (void **)v4; v14 = *(_DWORD *)(v4 + 16); if ( *(void ***)v4 != &g_inotify_node_NoValidFlag && *(void ***)(v4 + 4) != &g_inotify_node_NoValidFlag ) v6 = sub_28F7C((void **)v4); v7 = (void **)*v6; if ( *v6 == &g_inotify_node_NoValidFlag ) v7 = (void **)v6[1]; v7[2] = v6[2]; v8 = (void **)v6[2]; if ( v8 == &g_inotify_node_NoValidFlag ) { v3->root = (struct RBTree *)v7; } else if ( v6 == *v8 ) { *v8 = v7; } else { v8[1] = v7; } if ( v6 != (void **)v5 ) *(_DWORD *)(v5 + 16) = v6[4]; if ( !v6[3] ) { while ( 1 ) { if ( v7 == (void **)v3->root || v7[3] ) { v7[3] = 0; break; } v9 = (struct RBTree **)v7[2]; v10 = *v9; if ( v7 == (void **)*v9 ) { v10 = v9[1]; if ( v10->color == 1 ) { v10->color = 0; *((_DWORD *)v7[2] + 3) = 1; rbtree_right_rotate((struct RBTree *)&v3->root, (struct RBTree *)v7[2]); v10 = (struct RBTree *)*((_DWORD *)v7[2] + 1); } v11 = v10->right; if ( v10->left->color || v11->color ) { if ( !v11->color ) { v10->left->color = 0; v10->color = 1; rbtree_left_rotate((struct RBTree *)&v3->root, v10); v10 = (struct RBTree *)*((_DWORD *)v7[2] + 1); } v10->color = *((_DWORD *)v7[2] + 3); *((_DWORD *)v7[2] + 3) = 0; v10->right->color = 0; rbtree_right_rotate((struct RBTree *)&v3->root, (struct RBTree *)v7[2]); goto LABEL_35; }
ABEL_31: v10->color = 1; v7 = (void **)v7[2]; } else { if ( v10->color == 1 ) { v10->color = 0; *((_DWORD *)v7[2] + 3) = 1; rbtree_left_rotate((struct RBTree *)&v3->root, (struct RBTree *)v7[2]); v10 = *(struct RBTree **)v7[2]; } v12 = v10->right; v13 = v10->left; if ( !v12->color && !v13->color ) goto LABEL_31; if ( !v13->color ) { v12->color = 0; v10->color = 1; rbtree_right_rotate((struct RBTree *)&v3->root, v10); v10 = *(struct RBTree **)v7[2]; } v10->color = *((_DWORD *)v7[2] + 3); *((_DWORD *)v7[2] + 3) = 0; v10->left->color = 0; rbtree_left_rotate((struct RBTree *)&v3->root, (struct RBTree *)v7[2]);
LABEL_35: v7 = (void **)&v3->root->left; } } } free(v6); return v14;
}

filewatch_DeleteNode函数进行节点删除,以及红黑树调整相关操作。下面看一下函数filewatch_rm。

unsigned int __fastcall filewatch_rm(struct FileWatchKey *keybuf) int v1; // r1 int v2; // r0 signed int v3; // r3 v1 = keybuf->wd; g_fileWatch_errno = 0; v2 = inotify_rm_watch(g_inotify_init, v1); v3 = 1; if ( v2 < 0 ) { v3 = 0; g_fileWatch_errno = v2; } return v3;

调用inotify_rm_watch进行wd移除。下面看一下freeKeyBuf函数。

void __fastcall freeKeyBuf(void *ptr)
{ void *v1; // r4 void *v2; // r0 v1 = ptr; v2 = *(void **)ptr; if ( v2 ) free(v2); free(v1);
}

freeKeyBuf函数进行内存释放工作。
至此删除文件监控分析结束。

下面将开始进行终止线程和父进程相关操作。

进程线程终止

pthread_kill(newthread, 10);
killProcess(fatherPid, 9);

先将监控所有task的线程结束。然后调用killProcess结束父进程。

unsigned int __fastcall killProcess(__pid_t a1, int a2)
{ unsigned int result; // r0 result = linux_eabi_syscall(__NR_kill, a1, a2); return result;
}

bypass

其实 bypass文件监控的方法很多,具有可移植性的方法的是HOOK inotify_add_watch
对于防守方可以监控inotify_add_watch函数是否HOOK。
样本下载连接
学习群号:211730239

梆梆加固之防内存dump分析相关推荐

  1. android内存dump分析,[原创]梆梆加固之防内存dump分析

     声明(打字好累) 本文仅限于技术讨论,不得用于非法途径,后果自负.  参考资料https://bbs.pediy.com/thread-206293.htm https://github.com ...

  2. dump分析工具_Java应用CPU过高,如何排查?参考解决思路和常用工具总结

    本文总结了一些常见的线上应急现象和对应排查步骤和工具.分享的主要目的是想让对线上问题接触少的同学有个预先认知,免得在遇到实际问题时手忙脚乱.毕竟作者自己也是从手忙脚乱时走过来的. 只不过这里先提示一下 ...

  3. mat分析dump分析_使用Eclipse Memory Analyzer Tool(MAT)分析线上故障(一)

    来源:  https://sourl.cn/ggYpYX 前言 Eclipse Memory Analyzer Tool(MAT)是一个强大的基于Eclipse的内存分析工具,可以帮助我们找到内存泄露 ...

  4. JVM内存Dump原理与在线分析实战 | 得物技术

    1.前言 当前我们微服务容器化部署JVM 实例很多,常常需要进行JVM heap dump analysis,为了提升JVM 问题排查效率,得物技术保障团队研究了JVM内存Dump 原理与设计开发了J ...

  5. 梆梆企业版加固技术之防篡改剖析

    (本文仅限于技术讨论,不得用于非法途径,造成不良后果,与作者无关) 本篇主要介绍梆梆安全加固防篡改的原理和方法.选择梆梆,是因为其在防篡改方面是比较突出的.如果后续有时间会陆续将梆梆企业版使用的加固技 ...

  6. dalvik下替换so简单dump出梆梆加固保护的odex

    由于保护技术更迭迅速,不保证本文方法适用于后续或者其它版本的梆梆加固,需要读者自行测试. 梆梆加固后的apk,里面的classes.dex只是个外壳,负责加载libDexHelper.so,而真正的d ...

  7. 梆梆加固的病毒分析-破解篇

    一.文件信息 文件名:tx.qq898507339.bzy9-1.apk MD5:77603118E1B061374DDB8A4D1EA70CB2 SHA1:A85B9387DD598E64548F6 ...

  8. 梆梆安全加固企业版分析

    我是一名奋战在安全第一线的程序猿,爱好和平,爱好打包不平,Freedom Forever ! 最近移动安全太火,火的我都忍不住玩了一把,最近几个月在研究各家的安全加固方案,大多dex加密.反调试等技术 ...

  9. Apk脱壳圣战之---脱掉“梆梆加固”的保护壳

    一.前言 现如今Android用户的安全意识不是很强,又有一些恶意开发者利用应用的名字吸引眼球,包装一个恶意锁机收费的应用,在用户被骗的安装应用之后,立马手机锁机,需要付费方可解锁.这样的恶意软件是非 ...

最新文章

  1. 用递归法计算斐波那契数列的第n项
  2. [SDOI2011]染色 (线段树维护子段问题+树剖)
  3. docker清空为none的镜像
  4. springboot中如何添加第三方的jar包或者说如何配置本地jar
  5. MFC的进程和线程,非正常终止
  6. win2003 https 网站的图文配置教程
  7. 7、单向一对多的关联关系(1的一方有n的一方的集合属性,n的一方却没有1的一方的引用)...
  8. Centos7.0-安装docker
  9. IE 6 特有的条件注释详情
  10. Linux的辅助数据和传递文件描述符
  11. hadoop——Map/Reduce中combiner的使用
  12. keil4调试时出现Error Flash Download failed - “Cortex-M4”问题
  13. annotation-driven 配置详解
  14. 论文-《Conversational Recommender System》
  15. Unity3D 手机摇一摇 实现震动效果
  16. 微信支付系统的单号原来是这样设计的
  17. imac15款和17款区别_5K屏2019款iMac轻体验:优缺点都很明显
  18. 使用Aspose.Pdf修改PDF文件中的表格字段
  19. 利用nsca监控oracle的session数
  20. iOS使用GCDSocketManager实现长连接

热门文章

  1. 现代软件工程 怎么教好课 (读书笔记)
  2. 杰里之. 输出 3 路 PMW【篇】
  3. 我的世界服务器皮肤显示怎么用,我的世界皮肤站怎么用 皮肤站使用方法介绍...
  4. svm算法java实现_谁有用JAVA实现机器学习svm算法的代码,感激不尽
  5. Windows如何连接远程桌面?远程桌面控制软件推荐
  6. ubuntu中的内网穿透cpolar下载安装,后台运行及开机自启
  7. centos——记录一次开机启动设置
  8. Java ArrayList add()方法与示例
  9. 网易游戏(互娱)2020校招在线笔试-游戏研发
  10. 一个形式良好的XML文档