本文仅限于技术讨论,不得用于非法途径,后果自负。
防内存dump比较笼统,本篇只介绍使用inotify相关实现(以BB为例)。
关于内存dump相关介绍,请参考如下链接:
1) 讨论android加固防内存dump的技术及vmp壳的防护强度:
https://bbs.pediy.com/thread-206293.htm。
2) android应用反调试以及反内存dump代码收集:
e2dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3j5i4u0C8k6i4u0H3k6h3&6Y4i4K6u0r3c8s2u0G2K9h3c8m8L8Y4c8A6i4@1f1K6i4K6R3H3i4K6R3J5
关于inotify相关介绍,请参考如下链接:
1) inotify不生效问题:
409K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0D9L8$3N6Q4x3X3g2U0M7$3c8F1i4K6u0W2L8X3g2@1i4K6u0r3j5$3!0G2L8q4)9#2k6Y4N6S2P5g2)9J5c8X3q4J5N6r3W2U0L8r3g2Q4x3V1k6V1k6i4c8S2K9h3I4K6i4K6u0r3x3U0t1^5x3U0M7@1x3K6y4Q4c8f1k6Q4b7V1y4Q4z5f1t1`.
2) Linux inotify功能及实现原理:
289K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0D9L8$3N6Q4x3X3g2U0M7$3c8F1i4K6u0W2L8X3g2@1i4K6u0r3L8i4W2S2M7Y4u0G2N6#2)9J5c8X3q4J5N6r3W2U0L8r3g2Q4x3V1k6V1k6i4c8S2K9h3I4K6i4K6u0r3y4K6l9&6y4U0b7$3x3q4!0q4x3#2)9^5x3q4)9^5x3R3`.`.
1)使用/proc/pid/mem访问其他进程的内存变量:
cb8K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0D9L8$3N6Q4x3X3g2U0M7$3c8F1i4K6u0W2L8X3g2@1i4K6u0r3k6%4g2G2K9X3W2F1x3o6S2Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0V1@1y4e0b7@1y4U0M7`.
2)pagemap的解读:
185K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0D9L8$3N6Q4x3X3g2K6K9h3&6S2i4K6u0W2j5$3!0E0i4K6u0W2j5$3&6Q4x3V1k6K6i4K6u0r3j5X3I4G2k6#2)9#2k6U0j5J5z5r3y4U0x3X3t1%4x3o6p5H3x3h3x3^5P5Y4g2Q4x3X3g2Z5N6r3#2D9
我们先看一下BB在防篡改技术的介绍,下图是BB官网上关于防篡改的介绍。(20fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1j5h3&6Y4j5$3I4W2i4K6u0W2j5$3!0E0i4K6u0r3M7s2u0G2k6s2g2U0N6s2y4Q4x3V1k6H3M7X3!0V1N6h3y4@1K9h3&6V1k6i4S2Q4x3@1k6H3M7X3!0V1N6h3y4@1i4K6g2X3K9h3c8Q4x3@1b7I4i4@1g2r3i4@1u0o6i4K6R3&6

#从官网上无法判断其采取何种措施,下面通过实际逆向分析来学习其相关防护策略。 首先介绍下其使用到的数据结构。
1)红黑树(二)之 C语言的实现:
574K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2U0L8X3u0D9L8$3N6K6i4K6u0W2j5$3!0E0i4K6u0r3M7$3E0&6N6$3q4F1k6K6p5J5x3K6b7#2i4K6u0r3M7q4)9J5c8U0x3$3x3U0b7I4y4K6N6Q4x3X3g2Z5N6r3#2D9
其定义如下:
FileWatchKey结构用于保存监控的文件名以及对应的inotify_add_watch句柄。
该函数位于libdexhelp.so文件中。如下:
从上面的代码可以看到File_notify_threadProc为真正的处理函数。
该函数步骤如下:
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、线程退出。
该函数用于初始化文件监控相关信息。本函数经过了混淆,去除如下:
从上面可以看到其主要是调用inotifyfile_ini 用于初始化g_fileWatch_wd_root以及g_fileWatch_wd_root,对应的数据结构为RBRoot。
下面看看inotifyfile_ini函数:
从上面可以看到inotifyfile_ini用于malloc一个RBRoot结构,同时将初始化相关成员。其中g_inotify_node_NoValidFlag表示头结点无效。因为目前没有设置任何要监控的文件。
我们看一下2个用于比较的函数。is_same_inotify_wd与is_same_inotify_filename。
该函数用于比较红黑树的key为wd。
本函数主要用于判断新的节点是左节点还是右节点。返回0,是同一个节点,大于0是在右节点分支,小于0在左节点分支。
该函数用于比较红黑树的key为filename。
本函数主要用于判断新的节点是左节点还是右节点。返回0,是同一个节点,大于0是在右节点分支,小于0在左节点分支。
该函数做了如下事情:
1、调用DecodeString9解密字符串“/proc/%ld/mem”;
2、格式化字符串“ /proc/pid/mem”;
3、调用inotify_add_watch_insert_node 将对应文件纳入到监控中;
4、调用DecodeString9解密字符串“/proc/%ld/pagemap”;
5、格式化字符串“ /proc/pid/pagemap”;
6、调用inotify_add_watch_insert_node 将对应文件纳入到监控中;
本函数有如下步骤:
1、对输入的字符串数组进行inotify_add_watch;
2、调用JudeFileIsDir判断是否是目录
2、调用inotifyList_user_add_node将wd于文件名写入对应的红黑树中。
本函数有如下步骤:
下面我们看一getinotifyListByWDnode函数
getinotifyListByWDnode调用query_insert_node来进行查询。
query_insert_node函数进行如下操作:
1、遍历二叉树进行查找进行节点查找;
2、如果找到则返回对应节点;
3、如果没找到,并且不创建新节点则返回0;
4、malloc一个新的RBTree;
5、初始化其父节点;
6、初始化新的RBTree;
7、调用 rbtree_left_rotate和rbtree_right_rotate对红黑树进行修正。
上面完成了getinotifyListByWDnode函数的分析,继续分析insertNewNode。
该函数调用query_insert_node进行新节点的插入操作。
至此函数inotify_add_watchByPid分析完了。
下面看看watchAllTask_threadProc函数的工作。
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。
这样是不行的,如果此时结束,对于后面新创建的线程则不能纳入到本进程中。对于已经被watch的文件再次watch将返回上次的wd,引用次数会加1。
这里面可能有个小问题是:如果线程被删除了则对应的红黑树链表的节点不会被删除,造成内存泄漏。极端情况应用一致持续不断的创建线程然后线程2秒后销毁,运行一段时间后内存会崩溃。
下面看一下inotify_add_watchByTid函数。
这个函数相对比较简单。
read_filewatch_event函数步骤如下:
1、调用select函数对inotify初始化句柄进行阻塞。当发生事件时,则线程唤醒;
2、调用ioctl函数获得对应事件的长度;
3、调用read函数将发生的事件信息读取到全局变量中。
4、返回对应的事件BUF。
filewatch_Delete函数步骤如下:
1、格式化字符/proc/pid/mem;
2、调用 filewatch_DeleteByFile删除/proc/pid/mem的watch;
3、格式化字符/proc/pid/pagemap;
4、调用 filewatch_DeleteByFile删除/proc/pid/pagemap的watch;
该函数调用步骤如下:
1、调用filewatch_DeleteNode删除wd相关watch;
2、调用filewatch_DeleteNode删除filename相关watch;
3、调用filewatch_rm移除wd;
4、调用freeKeyBuf释放FileWatchKey。
函数filewatch_DeleteNode如下:
filewatch_DeleteNode函数进行节点删除,以及红黑树调整相关操作。下面看一下函数filewatch_rm。
调用inotify_rm_watch进行wd移除。下面看一下freeKeyBuf函数。
freeKeyBuf函数进行内存释放工作。
至此删除文件监控分析结束。
先将监控所有task的线程结束。然后调用killProcess结束父进程。
对于防守方可以监控inotify_add_watch函数是否HOOK。
学习群号:211730239
从上面的知识可知,通过对目标进程文件/proc/pid/mem文件操作,可以获得其内存的数据。
而inotify可以监控文件系统的变化,当文件被打开、删除,读写等操作时,同时用户相应变化。
因此可以通过监控/proc/pid/mem 与/proc/pid/pagemep来防止内存dump。
防内存dump用到了红黑树的数据结构。下面是关于红黑树数据结构的介绍。
typedef struct FileWatchKey
{
char* fileName; //监控的文件名
int wd; //inotify_add_watch 返回值
}FileWatchKey;
[培训]科锐逆向工程师培训第53期2025年7月8日开班!