精华内容
下载资源
问答
  • Android SO 加壳

    千次阅读 2016-07-11 19:17:06
    android so加壳

    Android SO(动态链接库)UPX加固指南

    前言

             随着移动互联网的爆发性增长,人们对移动应用的需求变得越来越复杂,企业在带给用户众多便利和享受的同时,却容易忽视应用自身的安全性问题,一旦遭受攻击,就会给企业和用户的经济或声誉带来影响。本文主要是站在企业的角度,阐述如何通过给android SO(动态链接库)加壳来提升移动APP的安全性,减少SO被逆向反汇编分析的风险。

    注:本文只做单方面的总结,如果对整体提升移动应用安全性有需求的人员,可参考作者另外一份文档:《移动应用安全开发指南v1.0(Android)》。

    撰写本文的目的

    1、  为移动应用开发人员提供安全加固技术指导,作为发布时加固的实际依据。

    2、  对实施过程做详细的记录和总结,为需要单独创建加固环境的人员提供具体细节,避免或少走弯路。

    建议使用方法:

             对于需要创建加固环境的人员,请阅读“创建加固环境”章节,对于只需要在加固环境下对SO加固的人员(比如开发人员),只需了解“加固步骤”章节即可。

    创建加固环境(X64 Linux)

    1、下载UPX和依赖组件的源码

    UPX -3.92-src:https://www.pysol.org:4443/hg/upx.hg/archive/tip.tar.gz

    注:v3.92为写本文档时的最新官方非正式发布版本(正式发布版本为v3.91)

    下载入口如下:

    clip_image002

    LZMA4.43:http://nchc.dl.sourceforge.net/project/sevenzip/LZMA%20SDK/4.43/lzma443.tar.bz2

    UCL1.03:http://www.oberhumer.com/opensource/ucl/download/ucl-1.03.tar.gz

    ZLIB1.2.3:http://pkgs.fedoraproject.org/repo/pkgs/zlib/zlib-1.2.3.tar.gz/debc62758716a169df9f62e6ab2bc634/zlib-1.2.3.tar.gz

    2、删除UPX壳描述信息

    编辑$(UPX_SRC_ROOT)/src/packer.cpp,删除该文件中定义的关于UPX壳的相关描述信息(详情请参考附录à相关问题总结5.5)。

    clip_image004

    3、编译

    3.1、编译zlib:

    tar zxvf zlib-1.2.3.tar.gz

    cd zlib-1.2.3

    make //编译生成libz.a

    cp libz.a /usr/lib64/libz.a //拷贝到系统默认的动态链接库路径下。

    clip_image006

    clip_image008

    3.2、编译UPX并执行

    cd $(UPX_SRC_ROOT)  //进入UPX源码根目录

    CXX=g++ UPX_UCLDIR=/home/soft/ucl-1.03 UPX_LZMADIR=/home/soft/lzma-4.43 UPX_LZMA_VERSION=0x443  make all  //编译UPX

    说明:UPX_UCLDIR和UPX_LZMADIR的值分别为UCL和LZMA解压后的根路径,UPX_LZMA_VERSION环境变量则指定了LZMA的版本。

    编译成功后可在$(UPX_SRC_ROOT)/src下查看到可执行文件upx.out,如下图所示:

    clip_image010

    执行效果如下:

    clip_image012

    加固步骤

    1、配置NDK集成开发环境

             参考附录《配置和使用NDK集成开发环境》的《配置步骤》章节。

    2、修改native代码

    2.1、在native代码中定义全局变量用于增加生成的二进制的体积,例如:

             C:int const dummy_to_make_this_compressible[100000] = {1,2,3};

             C++:extern "C" int const dummy_to_make_this_compressible[100000] = {1,2,3};

             注意:如果编译出来的库本身足够大,则此步骤可省略。

    2.2、在native代码中声明_init()函数,用于在编译时生成_init段,例如:

             C:void _init(void){}

             C++:extern "C" {void _init(void){}}

             注意:C和C++代码定义或声明的方式是有所区别的,在C++中必须使用extern “C”关键          字进行修饰,被extern "C"修饰的变量和函数是按照C语言方式编译和连接的

    2.3、在native代码中使用宏定义混淆函数名,用于增加静态反汇编分析难度,例如:

             #define startSimpleWifi sSW

             #define sendData sD

             ……

    3、对SO库文件加壳

    3.1、打开cygwin,进入Android工程目录,在NDK环境中编译native代码(详情可参考         附录《配置和使用NDK集成开发环境》的《编译native代码》章节),也可以通过CDT        自动编译(参考《配置和使用CDT编译环境》),编译通过后将在libs目录下生成SO    动态链接库文件。

    3.2、将编译生成的SO库文件上传到加固服务器(本文将其和UPX执行文件放同一目录),   如下图所示:

    clip_image014

    3.3、对SO库进行加壳,常用命令:upx.out –o libhello-jniupx.so libhello-jni.so

    clip_image016

    4、验证

    4.1、在eclipse的Android工程中使用加壳SO替换原有的SO,如图所示:

    clip_image018

    clip_image020

    clip_image022

    4.2、将使用加壳SO的Android程序安装到设备或模拟器中执行功能验证,若各项功能均正   常则表示加固成功。

    clip_image024

    加固前后效果对比

    1、加固前反汇编:

    clip_image026

    clip_image028

    2、加固后反汇编:

    clip_image030

    clip_image032

    附录

    1、术语表

    术语

    定义

    UPX

    UPX是一个著名的压缩壳,主要功能是压缩可执行文件(比如exe,dll和elf等文件),有时候也可能被病毒用于免杀。

    加壳

    加壳的全称应该是可执行程序资源压缩,是保护文件的常用手段。加壳的程序经常想尽办法阻止外部程序或软件对加壳程序的反汇编分析或者动态分析,以达到它不可告人的目的,这种技术也常用来保护软件版权,防止被软件破解。

    交叉编译

    就是在一个平台上生成另一个平台上的可执行代码,比如在X86 Linux上编译出可在ARM Linux上执行的程序。

    JNI

    JNI是Java Native Interface的缩写,中文为JAVA本地调用,它允许Java代码和其他语言(比如C/C++)写的代码进行交互。

    反汇编

    把目标代码转为汇编代码的过程,也可以说是把机器语言转换为汇编语言代码、低级转高级的意思,常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域。

     

    2、配置和使用NDK集成开发环境

    2.1、配置步骤:

    2.1.1、http://www.cygwin.com/下载cygwin并双击安装。

    2.1.2、选择从internet安装。

             clip_image034

    2.1.3、选择一种能连上网络的方式

             clip_image036

    2.1.4、建议选国内的镜像站点(速度快)

             clip_image038

    2.1.5、在search中输入”make”,选择Devel列表中的所有package进行安装,选择方法是点击package名,由keep变成install字样即可。注:本机已安装,故显示为reinstall。

             clip_image040

    2.1.6、继续下一步直至安装完毕。

    2.1.7、运行cgywin bash,输入cygcheck -c cygwin命令,若显示Cygwin 的package信息则表  示运行正常。

             clip_image042

    2.1.8、分别运行gcc –version、g++ --version make –version和gdb –version来检查相关组件是   否运行正常。

             clip_image044

             clip_image046

             clip_image048

             clip_image050

    2.2、编译native代码:

    2.2.1、使用NDK编译一个程序,首先我们要找到我们cygwin的程序安装目录,找到一个       home\<你的用户名>\.bash_profile文件,如下图所示

             clip_image052

    2.2.2、在该文件末尾添加ndk=/cygdrive/<你的盘符>/<android ndk 目录> 例如:         ndk=/cygdrive/f/android/adt-bundle-windows-x86-20131030/android-ndk-r9d

             export ndk

             注:"ndk"这个名字随便起,因为后面要经常使用,建议不要太长。如下图所示

    clip_image054

    2.2.3、之后重新打开cygwin,输入 cd $ndk,如果进入了android ndk 目录,证明环境变量    设置成功了

    clip_image056

    2.2.4、尝试使用NDK编译android NDK的样例程序hello-jni,路径为:

    <android ndk 目      录>/samples/hello-jni,比如:

    F:\android\adt-bundle-windows-x86-20131030\android-ndk-r9d\samples\hello-jni

    2.2.5、进入工程根目录

    clip_image058

    2.2.6、执行$ndk/ndk-build命令,执行成功后它会自动生成一个libs目录,把编译生成的.so文件放里边,使用file命令可查看到文件为经过交叉编译的ARM Linux 动态链接库。

    clip_image060

    注:执行$ndk/ndk-build实际等于执行NDK目录下的ndk-build命令,如下图所示:

    clip_image062

    3、配置和使用CDT编译环境(非必须)

    3.1、为eclipse安装CDT插件,也可以直接下载带CDT插件的eclipse(略)。

    3.2、在eclipse中选择右键选中项目,选择Properties,在弹出的对话框中选择Builders。

             clip_image064

    3.3、点击对话框中的New新建一个编译器,在新弹出对话框中选择ProgramàOK。

             clip_image066

    3.4、对新建编译器进行配置,如下图:

    clip_image068

    参数解析如下:

    Name:编译器的名字,可随便取。

    Location: <你cygwin安装路径>\bin\bash.exe程序,即cygwin的bash程序的路径。

    Working Directory: 你cygwin安装路径>\bin目录。

    Arguments:给cygwin bash传递的参数,里面主要包含进入工程目录并使用NDK执行编译的命令。

    3.5、接着切换到Refresh选项卡,给Refresh resources upon completion打上钩,如图:

             clip_image070

    3.6、最后切换到Build Options选项卡,勾选上最后三项,如下图所示:

             clip_image072

    3.7、点击Specify Resources按钮,选择资源目录,勾选你的项目目录即可,如下图所示:

             clip_image074

    3.8、保存配置后将回到Properties对话框,点右边的Up按钮,把它排到第一位,否则C代码的编译晚于Java代码的编译,会造成你的C代码要编译两次才能看到最新的修改,如图:

             clip_image076

    3.9、在eclipse中build Android工程,可以看到同时编译native代码并生成了SO库。

    clip_image078

    4、APK重打包流程

    4.1、下载并安装APK改之理,下载地址:http://www.xiaomiren.net/

    4.2、启动APK改之理,选择项目à打开APK 打开要重打包的APK程序。

             clip_image080

    4.3、打开成功后可看到原有的工程目录结构,打开libs目录可以看到APK需要载入的SO库。

             clip_image082

    4.4、删除要替换的SO,并把修改过的SO(比如经过加壳的SO)添加进来。

             clip_image084

    4.5、重新编译APK,即可安装到设备中运行并观察替换SO后的效果。

             clip_image086

    clip_image088

    5、相关问题总结

    5.1、编译UPX出现“cannot find -lz”错误。

             分析:原因是链接器LD没有找到编译出来的zlib库libz.so或libz.a。

             解决方法:将libz.so或libz.a拷贝到系统默认的动态链接库路径下,比如/usr/lib,/usr/lib64      等。

    5.2、编译UPX出现“CantPackException: DT_TEXTREL found; re-compile with -fPIC”错误。

             分析:这是早期NDK版本的BUG。

             解决方案:使用NDK9或以上的版本

    5.3、编译UPX出现“NotCompressibleException”错误。

             分析:UPX对被加壳的二进制文件有最小限制,太小的文件将无法被加壳。

             解决方案:在native代码中定义足够大的数据变量,使得编译出来的二进制文件容易达          到UPX的要求(参考《加固步骤》之《修改native代码》章节)。

    5.4、编译UPX出现“UnknownExecutableFormatException”错误。

             分析:被加壳的二进制文件必须存在init段,否则UPX将无法脱壳还原原始代码。

             解决方案:在native代码中定义_init()方法,需要注意C和C++的区别(参考《加固步    骤》之《修改native代码》章节)。

             备注:查看二进制文件是否存在init段的命令:readelf –dynamic xxx.so,如下图:

             clip_image090

    5.5、使用UPX加壳的SO,在eclipse中启动Android程序时出现”Fatal signal…”错误,如图:

             clip_image092

             分析:此错误是因为UPX解析某些特殊字符处理不当导致的,该BUG已经有人提交UPX        官方解决,但是当前官方正式发布的正式版本(V3.91)并没有fix该问题,而是在未正      式发布的V3.92才解决了该问题,因此本文档使用源代码版本为V3.92而非V3.91。

             解决方案:下载使用V3.92源码,下载入口请参考《创建加固环境》章节。

    5.6、为何删除UPX源码中的软件信息,以及如何定位查找这些信息。

             分析:UPX对文件进行加壳时会把这些信息写入壳内,通过静态反汇编可查看到这些壳信息,进而寻找对应的脱壳机进行脱壳,使得攻击难度降低。

             解决方案:在UPX源码中删除这些信息,并重新编译,步骤如下:

             5.6.1、使用原始版本对文件进行加壳。

             5.5.2、使用IDA反汇编加壳文件,在反汇编文件的上下文中查找UPX壳特征字符串,    如下图所示:

             clip_image094

             5.5.3、在UPX源码中查找这些特征字符串(建议使用Search and Replace),并一一删除,      如下图:

             clip_image004

             5.5.4、重新编译UPX(参考“创建加固环境”章节)。

    5.7、在没有eclipse源码工程的情况下如何直接替换APK的SO并观察结果?

             解决方案:参考《附录》之《APK重打包流程》,值得注意的是,如果Android程序中     内置了检查签名合法性的安全机制,使用该方法的前提是先破解该签名验证机制。


    转自: http://www.cnblogs.com/fishou/p/4202061.html

    展开全文
  • Android so加固的简单脱壳

    千次阅读 2017-09-24 16:24:21
    Android应用的so库文件的加固一直存在,也比较常见,特地花时间整理了一下Android so库文件加固方面的知识。本篇文章主要是对看雪论坛《简单的so脱壳器》这篇文章的思路和代码的分析,很久之前就阅读过这篇文章但是...

    本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78077603


    Android应用的so库文件的加固一直存在,也比较常见,特地花时间整理了一下Android so库文件加固方面的知识。本篇文章主要是对看雪论坛《简单的so脱壳器》这篇文章的思路和代码的分析,很久之前就阅读过这篇文章但是一直没有时间来详细分析它的代码,最近比较有空来分析一下这篇文章中提到的Android so脱壳器的代码udog,github下载地址为:https://github.com/devilogic/udog,Android so加固的一般手法就是去除掉外壳Android so库文件的 ELF 链接视图 相关的信息,例如区节头表的偏移、区节头表的项数、区节头表名称字符串表的序号等,这样处理以后将Android so加固的外壳so库文件拖到IDA中去分析的时候,直接提示区节头表无效的错误,IDA工具不能对Android so加固的外壳so库文件进行分析,达到抗IDA工具静态分析的目的。


    Android so加固中被保护的Android so库文件是由外壳Android so库文件在.init段或者.init_array段的构造函数里自定linker进行内存加载和解密的,被保护的Android so库文件自定义内存加载、映射完成以后将外壳Android so库文件的soinfo*(dlopen函数返回的)修改为被保护Android so库文件的soinfo*,这样被保护的Android so库文件的内存加载就成功了并且就可以被调用了。尽管被加固保护的Android so库文件被加密保护起来了,但是该Android so库文件还是会在内存中进行解密出来,因此我们可以在被加固保护的Android so库文件内存解密时进行内存dump处理,然后对dump出来的Android so库文件进行ELF文件格式的调整和修复以及section区节头表的重建,就可以实现被保护的Android so库文件的脱壳了。


    简单so脱壳器这篇文章中提到的so脱壳器udog的代码比较简单,之前以为udog的代码比较复杂,后来整理了一下作者玩命的代码发现很多代码都是废弃的,核心关键的代码部分不是很复杂但是对于学习Android so加固的脱壳很有作用,也是Android so加固脱壳和内存dump后修复的第一步,玩命版主主要实现了被加固Android so库文件的内存dump和ELF文件格式部分参数的修复处理,对于脱壳后ELF文件的section区节头表等的重建并没有实现。



    udog代码的入口 main函数 在linker.cpp文件中如下图所示:




    在linker.cpp文件中,main函数工作流程是: 先对用户输入的命令行参数进行解析处理得到 用户参数解析结果描述结构体options_t,然后根据用户输入的命令参数解析的结果options_t 进行Android so的脱壳相关的操作。udog脱壳器中帮助命令行在文件options.cpp中实现,如下图所示:




    用户输入的命令行参数解析结果保存结构体options_t中,如下图代码所示:

    // 保存用户输入命令行参数的解析结果
    struct options_t {
    
    	bool call_dt_init;
    	bool call_dt_init_array;
    	bool call_dt_finit;
    	bool call_dt_finit_array;
    	bool load_pre_libs;
    	bool load_needed_libs;
    
    	bool load;
    	bool not_relocal;              /* 不对重定位表进行修复 */
    	bool make_sectabs;             /* 重建elf文件的区节头表 */
    	bool dump;
    	bool help;
    	bool version;
    	bool debug;
    	bool check;
    	bool clear_entry;
    
    	int debuglevel;
    	unsigned xct_offset;
    	unsigned xct_size;
    	char dump_file[128];
    	char target_file[128];
    };

    对用户输入的命令行参数进行解析处理的操作在函数handle_arguments中实现,如下图代码所示:

    // main函数在linker.cpp类
    int main(int argc, char* argv[]) {
    
    	// 解析用户输入的命令行参数
    	g_opts = handle_arguments(argc, argv);
    // 解析用户输入的命令行参数
    struct options_t* handle_arguments(int argc, char* argv[]) {
    
    	// 保存命令行参数解析后的结果
    	static struct options_t opts;
    	// 清零
    	memset(&opts, 0, sizeof(opts));
    	// 默认参数的设置
    	opts.call_dt_init = true;
    	opts.call_dt_init_array = true;
    	opts.call_dt_finit = true;
    	opts.call_dt_finit_array = true;
    	opts.load_pre_libs = true;
    	opts.load_needed_libs = true;
    
    	int opt;
    	int longidx;
    	int dump = 0, help = 0, version = 0,
    		debug = 0, check = 0, xcto = 0,
    		xcts = 0, clear_entry = 0;
    
    	if (argc == 1) {
    		return NULL;
    	}
    
    	// 输入参数选项的解析顺序和规则
    	// 该数据结构包括了所有要定义的短选项,每一个选项都只用单个字母表示。
    	// 如果该选项需要参数,则其后跟一个冒号
    	const char* short_opts = ":hvcd:";
    	// 解析参数的长选项
    	struct option long_opts[] = {
    		// 1--选项需要参数
    	 	{ "dump", 1, &dump, 1 },
    		// 0--选项无参数
    		{ "help", 0, &help, 2 },
    		{ "version", 0, &version, 3 },
    		{ "debug", 1, &debug, 4 },
    		{ "check", 0, &check, 5 },
    		{ "xcto", 1, &xcto, 6 },
    		{ "xcts", 1, &xcts, 7 },
    		{ "clear-entry",0, &clear_entry, 8 },
    		// 2--选项参数可选
    	 	{ 0, 0, 0, 0 }
    	};
    
    	// 对输入的命令行参数进行解析,longidx为解析参数长选项中的序号数
    	// 参考:https://baike.baidu.com/item/getopt_long/5634851?fr=aladdin
    	// 参考:http://blog.csdn.net/ast_224/article/details/3861625
    	while ((opt = getopt_long(argc, argv, short_opts, long_opts, &longidx)) != -1) {
    
    		switch (opt) {
    		case 0:
    			// 进行so库文件的dump处理
    			if (dump == 1) {
    
    				opts.dump = true;
    				// 暂时不对dump的so库文件的重定位表进行修复
    				opts.not_relocal = false;
    				// 对dump的so库文件的区节头表进行重建
    				opts.make_sectabs = true;
    
    				// 当处理一个带参数的选项时,全局变量optarg会指向它的参数
    				// optarg为目标so库文件dump后的文件保存路径
    				strcpy(opts.dump_file, optarg);
    				// 加载dump的so库文件
    				opts.load = true;
    				dump = 0;
    
    			} else if (help == 2) {
    
    				opts.help = true;
    				help = 0;
    
    			} else if (version == 3) {
    
    				opts.version = true;
    				version = 0;
    
    			} else if (debug == 4) {
    
    				opts.debug = true;
    				opts.debuglevel = atoi(optarg);
    				debug = 0;
    
    			} else if (check == 5) {
    
    				opts.check = true;
    				check = 0;
    
    			} else if (xcto == 6) {
    
    				opts.xct_offset = strtol(optarg, NULL, 16);
    				xcto = 0;
    
    			} else if (xcts == 7) {
    
    				opts.xct_size = strtol(optarg, NULL, 16);
    				xcts = 0;
    
    			} else if (clear_entry == 8) {
    
    				opts.clear_entry = true;
    				clear_entry = 0;
    
    			} else {
    
    				//printf("unknow options: %c\n", optopt);
    				return NULL;
    			}
    			break;
    		case 'c':
    			opts.check = true;
    			break;
    		case 'h':
    			opts.help = true;
    			break;
    		case 'v':
    			opts.version = true;
    			break;
    		case 'd':
    			opts.dump = true;
    			opts.not_relocal = false;
    			opts.make_sectabs = true;
    			strcpy(opts.dump_file, optarg);
    			opts.load = true;
    			break;
    		case '?':
    			//printf("unknow options: %c\n", optopt);
    			return NULL;
    			break;
    		case ':':
    			//printf("option need a option\n");
    			return NULL;
    			break;
    		}/* end switch */
    	}/* end while */
    
    
    	/* 无文件 */
    	if (optind == argc) {
    		return NULL;
    	}
    
    	// 当函数分析完所有参数时,全局变量optind(into argv)会指向第一个‘非选项’的位置
    	// 需要被dump的so库文件的文件路径
    	strcpy(opts.target_file, argv[optind]);
    
    	// 返回的引用
    	return &opts;
    }

    根据对用户输入命令行参数的解析结果options_t,进行Android so加固脱壳相关的操作,这里主要关注的是被加固的Android so库文件的内存dump相关的处理部分,如下图所示:

    	// 加载需要dump的so库文件
    	if (g_opts->load) {
    
    		// 清零处理
    		memset(&g_infos, 0, sizeof(g_infos));
    
    		// 构建libdl.so库文件系统符号表symtab的填充
    		fill_libdl_symtab();
    		// 构建libdl_info结构体的填充
    		fill_libdl_info();
    
    		// unsigned ret = __linker_init((unsigned **)(argv-1));
    		// if (ret == 0) return ret;
    
    		// 获取到需要被dump的so库文件的文件路径
    		char* fname = g_opts->target_file;
    		// 动态加载需要被dump的so库文件返回信息描述结构体soinfo指针
    		soinfo* lib = (soinfo*)dlopen(fname, 0);
    		if (lib == NULL) {
    
    			// 动态库加载失败的情况
    			return -1;
    		}
    
    		//void* handle = dlsym(lib, "prepare_key");
    		//if (handle) {
    		//	printf("%x\n", *(unsigned*)handle);
    		//}
    
    		// 从加载后的外壳so库文件中dump出解密后的被保护的so库文件
    		if (g_opts->dump) {
    
    			// dump出被保护的so库文件
    			if (dump_file(lib) != 0) {
    				return -1;
    			}
    
    			// 对dump出来被保护的so库文件进行区节头表的重建(玩命版主没有处理)
    			// if (g_opts->make_sectabs) {
    			//
    			// 	if (make_sectables(g_opts->dump_file) != 0) {
    			// 		return -1;
    			// 	}
    			// }
    
    		} else {
    
    			/* 打印代码CRC */
    			if (g_opts->check) {
    				checkcode_by_x((unsigned char*)(lib->base),
    							   "code text crc32",
    							   g_opts->xct_offset,
    							   g_opts->xct_size);
    			}
    		}
    
    		// 卸载外壳so库文件的加载
    		dlclose(lib);
    
    	// 不加载外壳so库文件的处理,crc32的校验
    	} else {
    
    		/* 未加载的功能  */
    		if (g_opts->check) {
    
    			checkcode(g_opts->target_file, "code text crc32", 
    					  g_opts->xct_offset, 
    					  g_opts->xct_size);
    		}
    
    	}

    在进行被加固Android so库文件的内存dump处理之前,还需要了解一下Android so库文件内存加载相关的知识。一般情况下,调用 dlopen函数 实现对Android so库文件的内存加载,调用dlopen函数成功以后返回Android so库文件内存加载后的模块句柄,其实该句柄就是 soinfo* (soinfo结构体的指针),Android so库文件内存加载成功的内存镜像就是由结构体soinfo来描述的,soinfo结构体在进程内存中比较完整的描述了ELF文件的执行视图相关的信息。

    /* so信息结构 */
    struct soinfo
    {
        char name[SOINFO_NAME_LEN];       /* so库文件的文件路径 */
        const Elf32_Phdr *phdr;           /* 指向程序段头表 */
        int phnum;						   /* 程序段头表的数量 */
        unsigned entry;				   /* so库文件的代码执行入口地址 */
        unsigned base;					   /* so库文件内存加载后的基地址 */
        unsigned size;                    /* so库文件所有可加载段的长度 */
    
        int unused;  // DO NOT USE, maintained for compatibility.
    
        unsigned *dynamic;				   /* .dynamic段描述结构体所在的起始地址*/
    
        unsigned unused2; // DO NOT USE, maintained for compatibility
        unsigned unused3; // DO NOT USE, maintained for compatibility
    
        soinfo *next;
        unsigned flags;
    
        const char *strtab;               /* .strtab段所在的内存地址 */
        Elf32_Sym *symtab;				   /* .symtab段所在的内存地址 */
    
        unsigned nbucket;
        unsigned nchain;
        unsigned *bucket;
        unsigned *chain;
    
        unsigned *plt_got;
    
        Elf32_Rel *plt_rel;
        unsigned plt_rel_count;
    
        Elf32_Rel *rel;
        unsigned rel_count;
    
        unsigned *preinit_array;
        unsigned preinit_array_count;
    
        unsigned *init_array;
        unsigned init_array_count;
    
        unsigned *fini_array;
        unsigned fini_array_count;
    
        void (*init_func)(void);
        void (*fini_func)(void);
    
    #if defined(ANDROID_ARM_LINKER)
        /* ARM EABI section used for stack unwinding. */
        unsigned *ARM_exidx;
        unsigned ARM_exidx_count;
    #elif defined(ANDROID_MIPS_LINKER)
    #if 0
         /* not yet */
         unsigned *mips_pltgot
    #endif
         unsigned mips_symtabno;
         unsigned mips_local_gotno;
         unsigned mips_gotsym;
    #endif /* ANDROID_*_LINKER */
    
        unsigned refcount;
        struct link_map linkmap;
    
        int constructors_called;                   /* 构造函数已经被调用 */
    
        /* When you read a virtual address from the ELF file, add this
         * value to get the corresponding address in the process' address space */
        Elf32_Addr load_bias;
        int has_text_relocations;
    
    	/* 表明是否是从主程序中调用 */
    	//int loader_is_main;
    };

    被加固Android so库文件脱壳操作的流程: 调用dlopen函数加载外壳Android so库文件,dlopen函数成功返回(即被加固保护的Android so库文件在内存中解密、自定义加载成功得到soinfo*,替换掉外壳Android so库文件返回的soinfo结构体指针 soinfo*  为被加固保护的Android so库文件加载成功后得到的 soinfo* ,实现被加固保护的Android so与外壳Android so的无缝衔接)得到被加固保护的Android so库文件的soinfo*(被加固保护Android so库文件内存镜像的描述结构体),然后对被加固保护的Android so库文件的内存soinfo结构体指针进行解析,获取到被加固保护的Android so库文件ELF文件格式执行视图的描述信息,并根据获取到的这些信息对被加固Android so库文件进行内存dump处理。




    被加固保护Android so库文件的内存dump操作在 函数dump_file 中实现,代码如下图所示:

    // 从外壳so动态加载返回的soinfo中dump出被加固so库文件
    // 参考的源码文件: /bionic/linker/linker.h
    int dump_file(soinfo* lib) {
    
    	// 创建新文件,用以保存dump出来的so库文件
    	FILE* fp = fopen(g_opts->dump_file, "w");
    	if (NULL ==fp) {
    
    		printf("create new file: %s error !", g_opts->dump_file);
    		return -1;
    	}
    
    	// 修改外壳so库文件的整个内存加载区域为可读可写可执行
    	int ret = mprotect((void*)lib->base, lib->size, 7 /**全部权限打开**/);
    
    	// 打印外壳so库文件内存加载返回的soinfo中的信息
    	printf("--------------------------------------------------\n");
    	// so库文件的内存加载地址
    	printf("base = 0x%x\n", lib->base);
    	// so库文件的内存加载映射大小
    	printf("size = 0x%x\n", lib->size);
    	// so库文件的代码指令的入口地址
    	printf("entry = 0x%x\n", lib->entry);
    	// so库文件的程序段头表的数量
    	printf("program header count = %d\n", lib->phnum);
    	printf("--------------------------------------------------\n");
    
    	// dump出来的so库文件的大小
    	unsigned dump_size = lib->size;
    	unsigned buf_size = dump_size + 0x10;
    
    	// 申请内存空间
    	unsigned char* buf = new unsigned char [buf_size];
    	if (NULL == buf) {
    
    		printf("size = alloc memery err !\n");
    		return -1;
    	}
    	// 将soinfo描述的so库文件的内存数据拷贝到申请的内存空间中
    	memcpy(buf, (void*)lib->base, lib->size);
    
    	// 定位到soinfo描述的so库文件(ELF)的文件头Elf32_Ehdr
    	Elf32_Ehdr* elfhdr = (Elf32_Ehdr*)(void*)buf;
    
    	// 修改区节头表的数量为0
    	elfhdr->e_shnum = 0;
    	// 修改该elf文件的区节数据的文件偏移为0
    	elfhdr->e_shoff = 0;
    	// 修改该elf文件区节表名称字符串所在的区节头表的序号为0
    	elfhdr->e_shstrndx = 0;
    
    	// 获取到该elf文件的程序段头表的文件偏移
    	unsigned phoff = elfhdr->e_phoff;
    	// 定位到该elf文件的程序段头表的位置
    	Elf32_Phdr* phdr = (Elf32_Phdr*)(void*)(buf + phoff);
    
    	// 遍历该elf文件的程序段头表
    	for (int i = 0; i < lib->phnum; i++, phdr++) {
    
    		// 获取该程序段头描述的程序段所在的相对虚拟内存地址
    		unsigned v = phdr->p_vaddr;
    		// 修正该程序段的文件偏移地址为虚拟内存地址
    		phdr->p_offset = v;
    
    		// 获取该程序段头描述的程序段的内存对齐后的数据长度大小
    		unsigned s = phdr->p_memsz;
    		// 修正该程序段的文件数据长度大小为内存对齐后的数据长度大小
    		phdr->p_filesz = s;
    	}
    
    	/* 是否清除DT_INIT入口点 */
    	if (g_opts->clear_entry)
    		fix_entry(buf, lib);
    
    	// 将该soinfo描述的so库文件修正后的内存数据写入到新创建的g_opts->dump_file文件中
    	ret = fwrite((void*)buf, 1, dump_size, fp);
    
    	// 刷新文件流
    	fflush(fp);
    	// 资源的清理
    	if (buf) delete [] buf;
    	// 关闭文件
    	fclose(fp);
    
    	printf("Dump so Successful\n");
    	return 0;
    }

    根据被加固保护Android so库文件内存镜像描述结构体 soinfo* 从进程内存中dump出so库文件的初步操作流程梳理如下:

    1. 根据 传入参数soinfo*  获取到被加固Android so库文件的内存加载基地址和内存所有段的长度并修改该so库文件所在内存区域的内存属性为可读可写可执行。






    2. 定位到soinfo描述的so库文件(ELF)的文件头Elf32_Ehdr,由于在Android so库文件内存加载时是基于ELF文件的可执行视图,因此该so库文件的链接视图的信息都会被去掉。为了避免so库文件内存dump后被IDA分析出错一般会将Android so库文件中seciton区节头表相关描述信息的参数设置为0,注意:Android 7.0版本的linker在进行Android so库文件的加载时会进行seciton区节头表相关描述信息参数的检查。




    3.  由于ELF文件加载内存时需要进行内存对齐的处理,因此内存中的Android so库的程序段的文件偏移和文件数据长度的大小需要进行修正处理。(从Android so库文件比较完整修复的角度来考虑,这一步不是必须甚至是应该去掉的,参考《ELF section修复的一些思考》。)

     	// elf32文件的程序段的描述头结构
    	typedef struct elf32_phdr{
    	  Elf32_Word    p_type;		// 程序段的属性值
    	  Elf32_Off 	p_offset;	// 程序段的文件偏移
    	  Elf32_Addr    p_vaddr;	// 程序段的相对虚拟地址RVA
    	  Elf32_Addr    p_paddr;	// 程序段的物理地址
    	  Elf32_Word    p_filesz;	// 程序段的文件数据长度大小
    	  Elf32_Word    p_memsz;	// 程序段的文件数据内存对齐处理后的长度大小
    	  Elf32_Word    p_flags;	// 程序段加载到内存后的可读可写可执行等内存属性值
    	  Elf32_Word    p_align;	// 程序需要内存对齐的数值
    	} Elf32_Phdr;



    4. Android so库文件的 .init段构造函数地址是否清除 的处理。



    void fix_entry(unsigned char* buf, soinfo* lib) {
    
    	// 获取.dynamic段所在的内存地址
    	unsigned* d = lib->dynamic;
    	// 遍历.dynamic段的描述结构体
    	while (*d) {
    
    		// 获取到so库文件的初始化代码地址的描述结构体
    		if (*d == DT_INIT) {
    
    			// 获取到so库文件的初始化代码地址所在的文件偏移
    			unsigned offset = (unsigned)(d+1) - lib->base;
    			// 设置so库文件的初始化代码地址的相对虚拟地址为0
    			*(unsigned*)(void*)(buf + offset) = 0;
    			break;
    		}
    		d += 2;
    	}
    }

    5. Android so库文件脱壳的修复还缺少的步骤:对比较简单的Android so脱壳和修复来说,上面的这些脱壳步骤已经可以了,IDA已经能够比较正常的分析了,但是基于Android so脱壳和修复进一步处理而言,上面的步骤3应该去掉。udog作者进行了Android so库文件的内存dump和初步的ELF文件修复处理,关于ELF文件的section区节头表相关信息的重建还没有完成,留给读者自己来完成,但是作者玩命已经给出了大致的修复思路。关于Android so库文件内存dump后修复的详细处理思路可以参考文章《ELF section修复的一些思考》、《从零打造简单的SODUMP工具》、《基于init_array加密的SO的脱壳》、《安卓so文件脱壳思路(java版)附源代码》、《ELF文件格式学习,section修复》,后面有时间我也会对这几篇文章进行学习和分析。




    6. Android so库文件内存dump处理操作的说明:自我感觉文章中,对于 dump_file函数 处理的Android so库文件的描述不是很准确(这里提到的被内存dump处理的Android so库文件不一定是被加固保护的Android so库文件的,因为 dlopen函数返回的soinfo* 也可能是外壳so加载器的Android so库文件的,具体以实际操作得到的结果为准,与Android so加固的对抗思路有一定的关系,不好准确描述。)


    完整注释版udog代码下载地址:http://download.csdn.net/download/qq1084283172/9997375


    展开全文
  • Android SO 加壳(加密)与脱壳思路

    万次阅读 2016-08-14 17:56:23
    0x01 常见的Android SO加壳(加密)思路 1.1 破坏Elf Header 将Elf32_Ehdr 中的e_shoff, e_shnum, e_shstrndx, e_shentsize字段处理,变为无效值。由于在链接过程中,这些字段是无用的,所以可以随意修改,这会...

       0x01 常见的Android SO加壳(加密)思路

        1.1 破坏Elf Header

        将Elf32_Ehdr 中的e_shoff, e_shnum, e_shstrndx, e_shentsize字段处理,变为无效值。由于在链接过程中,这些字段是无用的,所以可以随意修改,这会导致ida打不开这个so文件。

     

        1.2 删除Section Header

        同样的道理,在链接过程中,Section Header是没有用到的,可以随意删除,这会导致ida打不开这个so文件。

     

        1.3 有源码加密Section或者函数

        一是对section加壳,一个是对函数加壳。参考Android逆向之旅---基于对so中的section加密技术实现so加固Android逆向之旅---基于对so中的函数加密技术实现so加固

     

        1.4 无源码加密Section或者函数

        将解密函数放在另一个so中,只需保证解密函数在被加密函数执行前执行即可。和其他so一样,解密so的加载也放在类的初始化static{}中。我们可以在解密so加载过程中执行解密函数,就能保证被加密函数执行前解密。执行时机可以选在linker执行.init_array时,也可以选在OnLoad函数中。当然,解密so一定要放在被解密so后加载。否则,搜索进程空间找不到被解密的so。

        详细介绍和代码:无源码加解密实现 && NDK Native Hook 

     

        1.5 自定义loader来加载SO,即从内存加载SO

        我们可以DIY SO,然后使用我们自定义的loader来加载,破解难度又加大了。详解的介绍参考SO文件格式及linker机制学习总结(1)SO文件格式及linker机制学习总结(2)

     

        1.6 在原so外面加一层壳

        

         packed so相当于把loader的代码插入到原so的init_array或者jni_onload处,然后重新打包成packed so,加载这个so,首先执行init_array或者jni_onload,在这里完成对原so的解密,从内存加载,并形成soinfo结构,然后替换原packed so的soinfo结构。

     

        1.7 llvm源码级混淆

        

        Clang ( 发音为 /klæŋ/) 是 LLVM 的一个编译器前端,它目前支持 C, C++, Objective-C 以及 Objective-C++ 等编程语言。Clang 对源程序进行词法分析和语义分析,并将分析结果转换为 Abstract Syntax Tree ( 抽象语法树 ) ,最后使用 LLVM 作为后端代码的生成器。

        在Android llvm源码级混淆比较成熟的是Safengine。

        如果想自己研究llvm源码级混淆,可以参考Android LLVM-Obfuscator C/C++ 混淆编译的深入研究,通过修改NDK的编译工具,来实现编译混淆。

     

        1.8 花指令

        通过在C语言中,内嵌arm汇编的方式,可以加入arm花指令,迷惑IDA。

     

        1.9 so vmp保护

        写一个arm虚拟机虚拟执行so中被保护的代码,在手机上效率是一个问题。    

     

        0x02 对应的脱壳思路

        1.1 针对破坏Elf Header和删除Section Header

        参考ELF section修复的一些思考

        里面有个知识点,需要说明下:

        从DT_PLTGOT可以得到__global_offset_table的偏移位置。由got表的结构知道,__global_offset_table前是rel.dyn重定位结构,之后为rel.plt重定位结构,都与rel一一对应。我们还是以libPLTUtils.so为例,下载地址:http://download.csdn.net/detail/jltxgcy/9602803

        arm-linux-androideabi-readelf -d ~/Public/libPLTUtils.so:

     

    Dynamic section at offset 0x2e8c contains 32 entries:
      Tag        Type                         Name/Value
     0x00000003 (PLTGOT)                     0x3fd4
     0x00000002 (PLTRELSZ)                   64 (bytes)
     0x00000017 (JMPREL)                     0xc74
     0x00000014 (PLTREL)                     REL
     0x00000011 (REL)                        0xc24
     0x00000012 (RELSZ)                      80 (bytes)
     0x00000013 (RELENT)                     8 (bytes)
     0x6ffffffa (RELCOUNT)                   7
     0x00000006 (SYMTAB)                     0x18c
     0x0000000b (SYMENT)                     16 (bytes)
     0x00000005 (STRTAB)                     0x51c
     0x0000000a (STRSZ)                      1239 (bytes)
     0x00000004 (HASH)                       0x9f4
     0x00000001 (NEEDED)                     Shared library: [liblog.so]
     0x00000001 (NEEDED)                     Shared library: [libdl.so]
     0x00000001 (NEEDED)                     Shared library: [libstdc++.so]
     0x00000001 (NEEDED)                     Shared library: [libm.so]
     0x00000001 (NEEDED)                     Shared library: [libc.so]
     0x0000000e (SONAME)                     Library soname: [libPLTUtils.so]
     0x0000001a (FINI_ARRAY)                 0x3e80
     0x0000001c (FINI_ARRAYSZ)               8 (bytes)
     0x00000019 (INIT_ARRAY)                 0x3e88
     0x0000001b (INIT_ARRAYSZ)               4 (bytes)
     0x00000010 (SYMBOLIC)                   0x0
     0x0000001e (FLAGS)                      SYMBOLIC BIND_NOW
     0x6ffffffb (FLAGS_1)                    Flags: NOW
     0x6ffffff0 (VERSYM)                     0xb74
     0x6ffffffc (VERDEF)                     0xbe8
     0x6ffffffd (VERDEFNUM)                  1
     0x6ffffffe (VERNEED)                    0xc04
     0x6fffffff (VERNEEDNUM)                 1
     0x00000000 (NULL)                       0x0

        我们看到PLTGOT的value为0x3fb4。

     

        然后arm-linux-androideabi-readelf -r ~/Public/libPLTUtils.so:

     

    Relocation section '.rel.dyn' at offset 0xc24 contains 10 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    00003e80  00000017 R_ARM_RELATIVE   
    00003fb4  00000017 R_ARM_RELATIVE   
    00003fb8  00000017 R_ARM_RELATIVE   
    00003fbc  00000017 R_ARM_RELATIVE   
    00003fc0  00000017 R_ARM_RELATIVE   
    00003fc8  00000017 R_ARM_RELATIVE   
    00003fcc  00000017 R_ARM_RELATIVE   
    00004004  00000402 R_ARM_ABS32       00000000   puts
    00003fc4  00000915 R_ARM_GLOB_DAT    00000000   __gnu_Unwind_Find_exid
    00003fd0  00001f15 R_ARM_GLOB_DAT    00000000   __cxa_call_unexpected
    
    Relocation section '.rel.plt' at offset 0xc74 contains 8 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    00003fe0  00000216 R_ARM_JUMP_SLOT   00000000   __cxa_atexit
    00003fe4  00000116 R_ARM_JUMP_SLOT   00000000   __cxa_finalize
    00003fe8  00000416 R_ARM_JUMP_SLOT   00000000   puts
    00003fec  00000916 R_ARM_JUMP_SLOT   00000000   __gnu_Unwind_Find_exid
    00003ff0  00000f16 R_ARM_JUMP_SLOT   00000000   abort
    00003ff4  00001116 R_ARM_JUMP_SLOT   00000000   memcpy
    00003ff8  00001c16 R_ARM_JUMP_SLOT   00000000   __cxa_begin_cleanup
    00003ffc  00001d16 R_ARM_JUMP_SLOT   00000000   __cxa_type_match

        我们可以看到0x00003fb4恰好是其分界线。

     

        在ida中,是这样的。

    .got:00003FB4 ; ===========================================================================
    .got:00003FB4
    .got:00003FB4 ; Segment type: Pure data
    .got:00003FB4                 AREA .got, DATA
    .got:00003FB4                 ; ORG 0x3FB4
    .got:00003FB4 global_fun_ptr  DCD global_fun          ; DATA XREF: Java_com_example_ndkplt_PLTUtils_pltTest+16o
    .got:00003FB4                                         ; Java_com_example_ndkplt_PLTUtils_pltTest+18r ...
    .got:00003FB8 __aeabi_unwind_cpp_pr0_ptr DCD __aeabi_unwind_cpp_pr0 ; DATA XREF: sub_E54+1Cr
    .got:00003FB8                                         ; .text:off_E98o
    .got:00003FBC __aeabi_unwind_cpp_pr1_ptr DCD __aeabi_unwind_cpp_pr1 ; DATA XREF: sub_E54+28r
    .got:00003FBC                                         ; .text:off_E9Co
    .got:00003FC0 __aeabi_unwind_cpp_pr2_ptr DCD __aeabi_unwind_cpp_pr2 ; DATA XREF: sub_E54+34r
    .got:00003FC0                                         ; .text:off_EA0o
    .got:00003FC4 __gnu_Unwind_Find_exidx_ptr DCD __imp___gnu_Unwind_Find_exidx
    .got:00003FC4                                         ; DATA XREF: sub_EA4+8r
    .got:00003FC4                                         ; .text:off_F9Co
    .got:00003FC8 off_3FC8        DCD aCallTheMethod      ; DATA XREF: sub_EA4+48r
    .got:00003FC8                                         ; .text:off_FA0o
    .got:00003FC8                                         ; "call the method"
    .got:00003FCC off_3FCC        DCD 0x2304              ; DATA XREF: sub_EA4+4Cr
    .got:00003FCC                                         ; .text:off_FA4o
    .got:00003FD0 __cxa_call_unexpected_ptr DCD __cxa_call_unexpected ; DATA XREF: sub_150C+3A8r
    .got:00003FD0                                         ; .text:off_18F8o
    .got:00003FD4 _GLOBAL_OFFSET_TABLE_ DCD 0             ; DATA XREF: .plt:00000CBCo
    .got:00003FD4                                         ; .plt:off_CC4o
    .got:00003FD8                 DCD 0
    .got:00003FDC                 DCD 0
    .got:00003FE0 __cxa_atexit_ptr DCD __imp___cxa_atexit ; DATA XREF: __cxa_atexit+8r
    .got:00003FE4 __cxa_finalize_ptr DCD __imp___cxa_finalize ; DATA XREF: __cxa_finalize+8r
    .got:00003FE8 puts_ptr        DCD __imp_puts          ; DATA XREF: puts+8r
    .got:00003FEC __gnu_Unwind_Find_exidx_ptr_0 DCD __imp___gnu_Unwind_Find_exidx
    .got:00003FEC                                         ; DATA XREF: __gnu_Unwind_Find_exidx+8r
    .got:00003FF0 abort_ptr       DCD __imp_abort         ; DATA XREF: abort+8r
    .got:00003FF4 memcpy_ptr      DCD __imp_memcpy        ; DATA XREF: memcpy+8r
    .got:00003FF8 __cxa_begin_cleanup_ptr DCD __imp___cxa_begin_cleanup
    .got:00003FF8                                         ; DATA XREF: __cxa_begin_cleanup+8r
    .got:00003FFC __cxa_type_match_ptr DCD __imp___cxa_type_match
    .got:00003FFC                                         ; DATA XREF: __cxa_type_match+8r
    .got:00003FFC ; .got          ends

        我们在0x00003fb4地址前,看到了:

     

     

    00004004  00000402 R_ARM_ABS32       00000000   puts
    00003fc4  00000915 R_ARM_GLOB_DAT    00000000   __gnu_Unwind_Find_exid
    00003fd0  00001f15 R_ARM_GLOB_DAT    00000000   __cxa_call_unexpected

        在这个地址后面,看到了:

    00003fe0  00000216 R_ARM_JUMP_SLOT   00000000   __cxa_atexit
    00003fe4  00000116 R_ARM_JUMP_SLOT   00000000   __cxa_finalize
    00003fe8  00000416 R_ARM_JUMP_SLOT   00000000   puts
    00003fec  00000916 R_ARM_JUMP_SLOT   00000000   __gnu_Unwind_Find_exid
    00003ff0  00000f16 R_ARM_JUMP_SLOT   00000000   abort
    00003ff4  00001116 R_ARM_JUMP_SLOT   00000000   memcpy
    00003ff8  00001c16 R_ARM_JUMP_SLOT   00000000   __cxa_begin_cleanup
    00003ffc  00001d16 R_ARM_JUMP_SLOT   00000000   __cxa_type_match

     

     

        1.2 针对有源码加密Section或者函数
        使用dlopen来加载so,返回一个soinfo结构体如下:

    struct soinfo {
        const char name[SOINFO_NAME_LEN]; Elf32_Phdr *phdr; //Elf32_Phdr 实际内存地址 int phnum;
        unsigned entry;
        unsigned base; //SO 起始
        unsigned size; //内存对齐后占用大小
        int unused; // DO NOT USE, maintained for compatibility. unsigned *dynamic; //.dynamic 实际内存地址
        unsigned wrprotect_start; //mprotect 调用 unsigned wrprotect_end;
        soinfo *next; //下一个 soinfo unsigned flags;
        const char *strtab; //.strtab 实际内存地址 Elf32_Sym *symtab; //. symtab 实际内存地址
        //hash 起始位置:bucket – 2 * sizeof(int) unsigned nbucket; //size = nbucket * sizeof(int) unsigned nchain; //size = nchain * sizeof(int) unsigned *bucket;
        unsigned *chain;
        unsigned *plt_got; //对应.dynamic: DT_PLTGOT Elf32_Rel *plt_rel; //函数重定位表
        unsigned plt_rel_count;
        Elf32_Rel *rel; //符号重定位表 unsigned rel_count;
        ....
    };

        得到这个结构体时,已经执行了init_array,已经实现了解密。剩下的工作就是如何恢复原so了,这部分参考ELF section修复的一些思考和从零打造简单的SODUMP工具

     

        1.3 针对无源码加密Section或者函数和自定义loader来加载SO,即从内存加载SO

        和针对有源码加密Section或者函数类似,但不像原来那样,只要在ndk开发中调用dlopen即可。从soinfo结构体恢复so文件的时机,要选择在Android源码中,具体时机,如果日后有需要,再做研究。

    展开全文
  • Android SO 文件结构及readelf命令

    千次阅读 2016-08-14 10:51:40
    0x00 Android SO 文件结构 so文件结构详细的分析,请看Android逆向之旅---SO(ELF)文件格式详解,这里就不重复造车轮子了。 我们待分析的so已经上传到csdn,下载地址:...

       0x00 Android SO 文件结构

        so文件结构详细的分析,请看Android逆向之旅---SO(ELF)文件格式详解,这里就不重复造车轮子了。

        我们待分析的so已经上传到csdn,下载地址:http://download.csdn.net/detail/jltxgcy/9602803


       0x01 readelf命令

       1.1 so文件整体图

       


        1.2 readelf文件位置

        Android下readelf位于android-ndk-r12/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin中,文件名称为arm-linux-androideabi-readelf


        1.3 read ELF Header    

        arm-linux-androideabi-readelf -h ~/Public/libPLTUtils.so

    ELF Header:
      Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF32
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              DYN (Shared object file)
      Machine:                           ARM
      Version:                           0x1
      Entry point address:               0x0
      Start of program headers:          52 (bytes into file)
      Start of section headers:          12664 (bytes into file)
      Flags:                             0x5000200, Version5 EABI, soft-float ABI
      Size of this header:               52 (bytes)
      Size of program headers:           32 (bytes)
      Number of program headers:         9
      Size of section headers:           40 (bytes)
      Number of section headers:         26
      Section header string table index: 25

        

        1.4 read Program Header

        arm-linux-androideabi-readelf -l ~/Public/libPLTUtils.so 

    Elf file type is DYN (Shared object file)
    Entry point 0x0
    There are 9 program headers, starting at offset 52
    
    Program Headers:
      Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
      PHDR           0x000034 0x00000034 0x00000034 0x00120 0x00120 R   0x4
      INTERP         0x000154 0x00000154 0x00000154 0x00013 0x00013 R   0x1
          [Requesting program interpreter: /system/bin/linker]
      LOAD           0x000000 0x00000000 0x00000000 0x0246a 0x0246a R E 0x1000
      LOAD           0x002e80 0x00003e80 0x00003e80 0x00188 0x00188 RW  0x1000
      DYNAMIC        0x002e8c 0x00003e8c 0x00003e8c 0x00128 0x00128 RW  0x4
      NOTE           0x000168 0x00000168 0x00000168 0x00024 0x00024 R   0x4
      GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0
      EXIDX          0x002304 0x00002304 0x00002304 0x00100 0x00100 R   0x4
      GNU_RELRO      0x002e80 0x00003e80 0x00003e80 0x00180 0x00180 RW  0x4
    
     Section to Segment mapping:
      Segment Sections...
       00     
       01     .interp 
       02     .interp .note.gnu.build-id .dynsym .dynstr .hash .gnu.version .gnu.version_d .gnu.version_r .rel.dyn .rel.plt .plt .text .ARM.extab .ARM.exidx .rodata 
       03     .fini_array .init_array .dynamic .got .data 
       04     .dynamic 
       05     .note.gnu.build-id 
       06     
       07     .ARM.exidx 
       08     .fini_array .init_array .dynamic .got

        1.5 read Section Header

    There are 26 section headers, starting at offset 0x3178:
    
    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .interp           PROGBITS        00000154 000154 000013 00   A  0   0  1
      [ 2] .note.gnu.build-i NOTE            00000168 000168 000024 00   A  0   0  4
      [ 3] .dynsym           DYNSYM          0000018c 00018c 000390 10   A  4   1  4
      [ 4] .dynstr           STRTAB          0000051c 00051c 0004d7 00   A  0   0  1
      [ 5] .hash             HASH            000009f4 0009f4 000180 04   A  3   0  4
      [ 6] .gnu.version      VERSYM          00000b74 000b74 000072 02   A  3   0  2
      [ 7] .gnu.version_d    VERDEF          00000be8 000be8 00001c 00   A  4   1  4
      [ 8] .gnu.version_r    VERNEED         00000c04 000c04 000020 00   A  4   1  4
      [ 9] .rel.dyn          REL             00000c24 000c24 000050 08   A  3   0  4
      [10] .rel.plt          REL             00000c74 000c74 000040 08  AI  3  11  4
      [11] .plt              PROGBITS        00000cb4 000cb4 000074 00  AX  0   0  4
      [12] .text             PROGBITS        00000d28 000d28 0015a0 00  AX  0   0  4
      [13] .ARM.extab        PROGBITS        000022c8 0022c8 00003c 00   A  0   0  4
      [14] .ARM.exidx        ARM_EXIDX       00002304 002304 000100 08  AL 12   0  4
      [15] .rodata           PROGBITS        00002404 002404 000066 01 AMS  0   0  1
      [16] .fini_array       FINI_ARRAY      00003e80 002e80 000008 00  WA  0   0  4
      [17] .init_array       INIT_ARRAY      00003e88 002e88 000004 00  WA  0   0  1
      [18] .dynamic          DYNAMIC         00003e8c 002e8c 000128 08  WA  4   0  4
      [19] .got              PROGBITS        00003fb4 002fb4 00004c 00  WA  0   0  4
      [20] .data             PROGBITS        00004000 003000 000008 00  WA  0   0  4
      [21] .bss              NOBITS          00004008 003008 000000 00  WA  0   0  1
      [22] .comment          PROGBITS        00000000 003008 000028 01  MS  0   0  1
      [23] .note.gnu.gold-ve NOTE            00000000 003030 00001c 00      0   0  4
      [24] .ARM.attributes   ARM_ATTRIBUTES  00000000 00304c 00002b 00      0   0  1
      [25] .shstrtab         STRTAB          00000000 003077 0000fe 00      0   0  1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings)
      I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)
        这里我们看到从.fini_array段开始虚拟地址都比文件偏移增加了0x1000,与此对应的是Program Header的第三个段,Load段,也是以.fini_array为开始,并且虚拟地址也比文件偏移增加了0x1000。
     

        1.6 Section Actual Data

        这里面是放1.4提到的25个段的实际数据。

      

        1.7 read Dynamic Setion Actual Data

        arm-linux-androideabi-readelf -d ~/Public/libPLTUtils.so

    Dynamic section at offset 0x2e8c contains 32 entries:
      Tag        Type                         Name/Value
     0x00000003 (PLTGOT)                     0x3fd4
     0x00000002 (PLTRELSZ)                   64 (bytes)
     0x00000017 (JMPREL)                     0xc74
     0x00000014 (PLTREL)                     REL
     0x00000011 (REL)                        0xc24
     0x00000012 (RELSZ)                      80 (bytes)
     0x00000013 (RELENT)                     8 (bytes)
     0x6ffffffa (RELCOUNT)                   7
     0x00000006 (SYMTAB)                     0x18c
     0x0000000b (SYMENT)                     16 (bytes)
     0x00000005 (STRTAB)                     0x51c
     0x0000000a (STRSZ)                      1239 (bytes)
     0x00000004 (HASH)                       0x9f4
     0x00000001 (NEEDED)                     Shared library: [liblog.so]
     0x00000001 (NEEDED)                     Shared library: [libdl.so]
     0x00000001 (NEEDED)                     Shared library: [libstdc++.so]
     0x00000001 (NEEDED)                     Shared library: [libm.so]
     0x00000001 (NEEDED)                     Shared library: [libc.so]
     0x0000000e (SONAME)                     Library soname: [libPLTUtils.so]
     0x0000001a (FINI_ARRAY)                 0x3e80
     0x0000001c (FINI_ARRAYSZ)               8 (bytes)
     0x00000019 (INIT_ARRAY)                 0x3e88
     0x0000001b (INIT_ARRAYSZ)               4 (bytes)
     0x00000010 (SYMBOLIC)                   0x0
     0x0000001e (FLAGS)                      SYMBOLIC BIND_NOW
     0x6ffffffb (FLAGS_1)                    Flags: NOW
     0x6ffffff0 (VERSYM)                     0xb74
     0x6ffffffc (VERDEF)                     0xbe8
     0x6ffffffd (VERDEFNUM)                  1
     0x6ffffffe (VERNEED)                    0xc04
     0x6fffffff (VERNEEDNUM)                 1
     0x00000000 (NULL)                       0x0
        关于Dynamic Section字段的含义,详细请参考http://download.csdn.net/detail/jiangwei0910410003/9204051


        1.8 read Dynsym Section Actual Data

        arm-linux-androideabi-readelf -s ~/Public/libPLTUtils.so

    Symbol table '.dynsym' contains 57 entries:
       Num:    Value  Size Type    Bind   Vis      Ndx Name
         0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_finalize@LIBC (2)
         2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@LIBC (2)
         3: 00000d65    52 FUNC    GLOBAL DEFAULT   12 Java_com_example_ndkplt_P
         4: 00000000     0 FUNC    GLOBAL DEFAULT  UND puts@LIBC (2)
         5: 00004004     4 OBJECT  GLOBAL DEFAULT   20 global_fun
         6: 00001904     8 FUNC    WEAK   DEFAULT   12 __aeabi_unwind_cpp_pr1
         7: 000018fc     8 FUNC    GLOBAL DEFAULT   12 __aeabi_unwind_cpp_pr0
         8: 0000190c     8 FUNC    WEAK   DEFAULT   12 __aeabi_unwind_cpp_pr2
         9: 00000000     0 FUNC    WEAK   DEFAULT  UND __gnu_Unwind_Find_exidx
        10: 00001ca4     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Restore_VFP_
        11: 00001c94     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Restore_VFP
        12: 00001cb4     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Restore_VFP_
        13: 00001cc4     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Restore_WMMX
        14: 00001d4c     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Restore_WMMX
        15: 00000000     0 FUNC    GLOBAL DEFAULT  UND abort@LIBC (2)
        16: 00001c80    20 FUNC    GLOBAL DEFAULT   12 restore_core_regs
        17: 00000000     0 FUNC    GLOBAL DEFAULT  UND memcpy@LIBC (2)
        18: 000011c0     8 FUNC    GLOBAL DEFAULT   12 _Unwind_GetCFA
        19: 000011c8   164 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_RaiseExcepti
        20: 0000126c    28 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_ForcedUnwind
        21: 00001288   116 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Resume
        22: 000012fc    32 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Resume_or_Re
        23: 0000131c     4 FUNC    GLOBAL DEFAULT   12 _Unwind_Complete
        24: 00001320    24 FUNC    GLOBAL DEFAULT   12 _Unwind_DeleteException
        25: 00001338    92 FUNC    GLOBAL DEFAULT   12 _Unwind_VRS_Get
        26: 000013bc    92 FUNC    GLOBAL DEFAULT   12 _Unwind_VRS_Set
        27: 00001444   200 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Backtrace
        28: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_begin_cleanup
        29: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_type_match
        30: 00001eb4   904 FUNC    GLOBAL DEFAULT   12 __gnu_unwind_execute
        31: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_call_unexpected
        32: 00001914   876 FUNC    GLOBAL DEFAULT   12 _Unwind_VRS_Pop
        33: 00001cac     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Save_VFP_D
        34: 00001c9c     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Save_VFP
        35: 00001cbc     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Save_VFP_D_1
        36: 00001d08     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Save_WMMXD
        37: 00001d60     0 FUNC    GLOBAL DEFAULT   12 __gnu_Unwind_Save_WMMXC
        38: 00001c80    20 FUNC    GLOBAL DEFAULT   12 __restore_core_regs
        39: 00001d74    36 FUNC    GLOBAL DEFAULT   12 ___Unwind_RaiseException
        40: 00001d74    36 FUNC    GLOBAL DEFAULT   12 _Unwind_RaiseException
        41: 00001d98    36 FUNC    GLOBAL DEFAULT   12 ___Unwind_Resume
        42: 00001d98    36 FUNC    GLOBAL DEFAULT   12 _Unwind_Resume
        43: 00001dbc    36 FUNC    GLOBAL DEFAULT   12 ___Unwind_Resume_or_Rethr
        44: 00001dbc    36 FUNC    GLOBAL DEFAULT   12 _Unwind_Resume_or_Rethrow
        45: 00001de0    36 FUNC    GLOBAL DEFAULT   12 ___Unwind_ForcedUnwind
        46: 00001de0    36 FUNC    GLOBAL DEFAULT   12 _Unwind_ForcedUnwind
        47: 00001e04    36 FUNC    GLOBAL DEFAULT   12 ___Unwind_Backtrace
        48: 00001e04    36 FUNC    GLOBAL DEFAULT   12 _Unwind_Backtrace
        49: 0000223c    64 FUNC    GLOBAL DEFAULT   12 __gnu_unwind_frame
        50: 0000227c    16 FUNC    GLOBAL DEFAULT   12 _Unwind_GetRegionStart
        51: 0000228c    28 FUNC    GLOBAL DEFAULT   12 _Unwind_GetLanguageSpecif
        52: 000022a8     8 FUNC    GLOBAL DEFAULT   12 _Unwind_GetDataRelBase
        53: 000022b0     8 FUNC    GLOBAL DEFAULT   12 _Unwind_GetTextRelBase
        54: 00004008     0 NOTYPE  GLOBAL DEFAULT  ABS _edata
        55: 00004008     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start
        56: 00004008     0 NOTYPE  GLOBAL DEFAULT  ABS _end
        关于DynSym Section字段的含义,详细请参考http://download.csdn.net/detail/jiangwei0910410003/9204051


        1.9 Dynstr Section和Shstrtab Section Actual Data

        Dynstr Section的实际数据是Dynsym Setion Actual Data中的函数名组成的字符表。

        而Shstrtab则是Section Header中的每个setion名字组成的字符表。


        2.0 read Relocation Section

        arm-linux-androideabi-readelf -r ~/Public/libPLTUtils.so

    Relocation section '.rel.dyn' at offset 0xc24 contains 10 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    00003e80  00000017 R_ARM_RELATIVE   
    00003fb4  00000017 R_ARM_RELATIVE   
    00003fb8  00000017 R_ARM_RELATIVE   
    00003fbc  00000017 R_ARM_RELATIVE   
    00003fc0  00000017 R_ARM_RELATIVE   
    00003fc8  00000017 R_ARM_RELATIVE   
    00003fcc  00000017 R_ARM_RELATIVE   
    00004004  00000402 R_ARM_ABS32       00000000   puts
    00003fc4  00000915 R_ARM_GLOB_DAT    00000000   __gnu_Unwind_Find_exid
    00003fd0  00001f15 R_ARM_GLOB_DAT    00000000   __cxa_call_unexpected
    
    Relocation section '.rel.plt' at offset 0xc74 contains 8 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    00003fe0  00000216 R_ARM_JUMP_SLOT   00000000   __cxa_atexit
    00003fe4  00000116 R_ARM_JUMP_SLOT   00000000   __cxa_finalize
    00003fe8  00000416 R_ARM_JUMP_SLOT   00000000   puts
    00003fec  00000916 R_ARM_JUMP_SLOT   00000000   __gnu_Unwind_Find_exid
    00003ff0  00000f16 R_ARM_JUMP_SLOT   00000000   abort
    00003ff4  00001116 R_ARM_JUMP_SLOT   00000000   memcpy
    00003ff8  00001c16 R_ARM_JUMP_SLOT   00000000   __cxa_begin_cleanup
    00003ffc  00001d16 R_ARM_JUMP_SLOT   00000000   __cxa_type_match

        2.1 read Text Section

        ./arm-linux-androideabi-objdump -d ~/Public/libPLTUtils.so


    展开全文
  • 如何查看Android SO库的cpu架构

    千次阅读 2019-09-16 11:49:42
    mac环境下如何查看Android SO库的cpu架构? readelf -a libxxxx.so objdump -x libxxxx.so greadelf -alibxxx.so gobjdump -xlibxxx.so 终端输入命令可能不支持,需要安装binutils brew update brew install ...
  • TensorFlow编Android so库总结

    千次阅读 2018-01-16 15:33:56
    TensorFlow编Android so库总结 一、下载安装tensorflow 参考(http://blog.csdn.net/u013832707/article/details/73161071) 获取源码 git clone –recurse-submodules ...
  • android so库的源码路径列表

    千次阅读 2016-02-19 16:25:27
    android so库的源码路径列表
  • Android so文件浅析

    万次阅读 2018-05-19 17:50:30
    Android中的so文件是动态链接库,是二进制文件,即ELF文件。多用于NDK开发中。 二. 基础知识 三. so文件格式解析 so文件即ELF文件,是一个二进制文件,我们可以用UltraEdit打开查看。如下: 上面有一处很...
  • 如何调试Android SO中的init函数

    千次阅读 2016-06-28 23:06:57
    Android SO精要调试今天在这里从使用角度简介一下IDA调试断在SO中init的具体过程
  • 0x00 参考文章 关于so加载分析,就是从System.loadlibrary开始的,层层玻璃代码真相,在Android SO 加壳(加密)与脱壳思路,我们说ELF Header中的一些字段是无用的,Section Header也是无用的,这是为什么呢?...
  • 简言: 现在公司做的项目应用到地图,使用的是高德地图,按照高德地图的api进行集成,集成很简单, ...1. 我们首先要了解的是android so文件,abi 和cpu的关系,推荐一篇博客: http://blog.csdn.net/xx32...
  • vs 编译android so

    千次阅读 2020-06-05 22:58:47
    配置包含目录dlib:dlib for android下的dlib并没有头文件,直接用原dlib的即可,OpenCV:third_party\OpenCV-android-sdk\sdk\native\jni\include,注意,库目录不是在这里配置 C/C++的属性配置,划红线的三个...
  • Android SO(动态链接库)UPX加固指南

    千次阅读 2017-02-06 17:21:53
    Android SO(动态链接库)UPX加固指南 前言  随着移动互联网的爆发性增长,人们对移动应用的需求变得越来越复杂,企业在带给用户众多便利和享受的同时,却容易忽视应用自身的安全性问题,一旦遭受攻击,就会给...
  • Android so文件详解

    千次阅读 2016-11-23 11:51:44
    国际管理上图说明 一图诠释Android的.so文件 ... 上周末参加AndroidBus开发者论坛[上海站],《小红书Android演进之路》(主讲:桑明明)提到.so文件,保留jniLibs下armeabi-v7a就可以了,这样就大大降低...
  • 转载整合自: 关于Android的.so文件你所需要知道的 和Android SO 文件的兼容和适配  不论是否被发现,一切荣誉归属于大佬。现有的CPU架构类型开发Android应用时,有时候Java层的编码不能满足实现需求,就需要到C...
  • 在前面的博客《在Android so文件的.init、.init_array上和JNI_OnLoad处下断点》中,已经分享了Dalvik虚拟机模式下如何在Android so库文件的.init段、.init_array段构造函数上下断点的方法,也是网上流传比较多的...
  • android so文件是什么

    千次阅读 2017-09-08 19:52:03
    补充:在Android中调用动态库文件(*.so)都是通过jni的方式。Android中加载so文件的提供的API:void System.load(String pathName); 说明:pathName:文件名+文件路径;  本回答由电子数码分类达人 陆祥钰...
  • ffmpeg 编译android so库文件

    千次阅读 2018-09-01 17:41:33
    ffmpeg for android 编译so库文件 ffmpeg是现在主流播放器和直播软件中常用的库,主要用于视频的编解码,下面主要介绍一下,ffmpeg如何在android平台上使用。ffmpeg想在android上使用,一般都是在官网下载源码,...
  • 1. 首先,在VMWARE上安装 android 系统 2. 安装完成后 安装APK程序, 3. 启动APK程序 4. 启动andriod上的terminal 然后su 到root 5. 用ps命令查看进程要调试的APK进程ID 6.在Andriod 上启动gdbserver 并attach ...
  • Android So简单加固

    千次阅读 2016-10-05 15:33:58
    Android下的dex加固技术从最初爱加密实现的dex整体内存加载,到目前各大厂商都在研究的代码虚拟化技术,整体技术已经很成熟了。但是因为Android系统开源的特性,这种加固方法的安全性并不高,只要修改源码中dex加载...
  • Android SO库文件头分析

    千次阅读 2018-07-03 15:28:32
    转自:https://blog.micblo.com/2018/02/10/Android-SO%E5%BA%93%E6%96%87%E4%BB%B6%E5%A4%B4%E5%88%86%E6%9E%90/因为项目的需要,我对Android系统加载.so文件有一些些研究,把最近看过的一些大牛的分析和现状结合...
  • Android SO文件的兼容和适配

    千次阅读 2017-05-08 14:44:05
    开发Android应用时,有时候Java层的编码不能满足实现需求,就需要到C/C++实现后生成SO文件,再用System.loadLibrary()加载进行调用,这里成为JNI层的实现。常见的场景如:加解密算法,音视频编解码等。在生成SO文件...
  • 先说说so的编译类型 Android只支持3种cpu架构分为:arm,mips,x86,目前用的最多的是arm体系cpu,x86和mips体系的很少用到了。 arm体系中,又分32位和64位: armeabi/armeabi-v7a:这个架构是arm类型的,主要用于Android ...
  • 在安卓脱壳过程中,经常需要用ida动态调试so,并且在解密代码执行前下断点,下面介绍用IDA在Android so文件的.init、.init_array上和JNI_OnLoad处下断点方法。我们以自己编写的一个小程序initTest.apk为例,该apk实现...
  • android so文件编译

    千次阅读 2015-12-17 11:22:50
    早期的Android系统几乎只支持ARMv5的CPU架构,你知道现在它支持多少种吗?7种! Android系统目前支持以下七种不同的CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和...
  • 在上一篇中讲到了关于Android so的动态调试,没看的可以点这里:点击打开链接; 我自认为写的还是挺全的,在上文中我们说到关于最后一步jdb附加调试时,很多时候都会出现附加不上的问题,使人很闹心。。。于是这一篇...
  • android so库 堆栈错误查看

    千次阅读 2017-01-25 11:45:09
    前面修改mars的数据发送,重新编译后 启动app发现堆栈错误可以使用如下命令找到出错的地方adb logcat | ndk-stack -sym F:\code\android\mars\mars\mars\libraries\mars_android_sdk\obj\local\x86注意这里我用的是...
  • Android SO Inject

    千次阅读 2012-07-31 19:37:02
    Android下面注入SO成功,连SO中的输出数据都打出来了。至少这一周多每天逆向学习还是没有白费。值得高兴啊!    PS:总结一下    1、意义:  我深刻的感觉,这次逆向完全就像是一个无底洞,其实我能...
  • Cydia Substrate Android SO Hook

    千次阅读 2016-06-03 21:06:48
    Cydia Substrate出了android版本的hook框架,不仅能hook java层函数,还能hook so中的函数,其核心原理是函数的inline hook,与xposed有着十分大的差别(xposed主要通过hookMethodNative将java层函数替换成native层...
  • vs2019开发android so

    千次阅读 2019-11-18 16:00:14
    1.NDK问题 从这里下载ndk,不知道什么原因,我用本地下载的好的ndk(21.0....2.修改so库名称 修改这两个,只修改根命名空间也行 其他的参考以下链接 https://blog.csdn.net/luoyu510183/article/details/94590497 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 51,329
精华内容 20,531
关键字:

androidso