Android逆向之旅---基于对so中的section加密技术实现so加固

本文详细介绍了如何通过对SO文件中的section进行加密来实现SO加固。首先,文章感谢看雪论坛的ThomasKing大神提供的思路。接着,作者阐述了加密和解密的技术原理,利用__attribute__((constructor))属性确保解密函数在程序启动前执行。在实现流程部分,作者分步骤展示了如何编写加密和解密的native代码,并给出了加密程序的Java和C版本。最后,通过Android测试DEMO验证了加密后的SO文件能够成功运行。
摘要由CSDN通过智能技术生成
               

致谢:

感谢看雪论坛的ThomasKing大神,提供牛逼思路,原文地址:http://bbs.pediy.com/thread-191649.htm


一、前言

好长时间没有更新文章了,主要还是工作上的事,连续加班一个月,没有时间研究了,只有周末有时间,来看一下,不过我还是延续之前的文章,继续我们的逆向之旅,今天我们要来看一下如何通过对so加密,在介绍本篇文章之前的话,一定要先阅读之前的文章:

so文件格式详解以及如何解析一个so文件

http://blog.csdn.net/jiangwei0910410003/article/details/49336613

这个是我们今天这篇文章的基础,如果不了解so文件的格式的话,下面的知识点可能会看的很费劲

下面就来介绍我们今天的话题:对so中的section进行加密


二、技术原理

加密:在之前的文章中我们介绍了so中的格式,那么对于找到一个section的base和size就可以对这段section进行加密了

解密:因为我们对section进行加密之后,肯定需要解密的,不然的话,运行肯定是报错的,那么这里的重点是什么时候去进行解密,对于一个so文件,我们load进程序之后,在运行程序之前我们可以从哪个时间点来突破?这里就需要一个知识点:

__attribute__((constructor));

关于这个,属性的用法这里就不做介绍了,网上有相关资料,他的作用很简单,就是优先于main方法之前执行,类似于Java中的构造函数,当然其实C++中的构造函数就是基于这个属性实现的,我们在之前介绍elf文件格式的时候,有两个section会引起我们的注意:


对于这两个section,其实就是用这个属性实现的函数存在这里,

在动态链接器构造了进程映像,并执行了重定位以后,每个共享的目标都获得执行 某些初始化代码的机会。这些初始化函数的被调用顺序是不一定的,不过所有共享目标 初始化都会在可执行文件得到控制之前发生。
类似地,共享目标也包含终止函数,这些函数在进程完成终止动作序列时,通过 atexit() 机制执行。动态链接器对终止函数的调用顺序是不确定的。
共享目标通过动态结构中的 DT_INIT 和 DT_FINI 条目指定初始化/终止函数。通常 这些代码放在.init 和.fini 节区中。

这个知识点很重要,我们后面在进行动态调试so的时候,还会用到这个知识点,所以一定要理解。

所以,在这里我们找到了解密的时机,就是自己定义一个解密函数,然后用上面的这个属性声明就可以了。


三、实现流程

第一、我们编写一个简单的native代码,这里我们需要做两件事:

1、将我们核心的native函数定义在自己的一个section中,这里会用到这个属性:__attribute__((section (".mytext")));

其中.mytext就是我们自己定义的section.

说到这里,还记得我们之前介绍的一篇文章中介绍了,动态的给so添加一个section:

http://blog.csdn.net/jiangwei0910410003/article/details/49361281

2、需要编写我们的解密函数,用属性: __attribute__((constructor));声明

这样一个native程序就包含这两个重要的函数,使用ndk编译成so文件


第二、编写加密程序,在加密程序中我们需要做的是:

1、通过解析so文件,找到.mytext段的起始地址和大小,这里的思路是:

找到所有的Section,然后获取他的name字段,在结合String Section,遍历找到.mytext字段

2、找到.mytext段之后,然后进行加密,最后在写入到文件中。


四、技术实现

前面介绍了原理和实现方案,下面就开始coding吧,

第一、我们先来看看native程序

#include <jni.h>#include <stdio.h>#include <android/log.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <elf.h>#include <sys/mman.h>jstring getString(JNIEnv*) __attribute__((section (".mytext")));jstring getString(JNIEnv* env)return (*env)->NewStringUTF(env, "Native method return!");};void init_getString() __attribute__((constructor));unsigned long getLibAddr();void init_getString()char name[15];  unsigned int nblock;  unsigned int nsize;  unsigned long base;  unsigned long text_addr;  unsigned int i;  Elf32_Ehdr *ehdr;  Elf32_Shdr *shdr;    base = getLibAddr();    ehdr = (Elf32_Ehdr *)base;  text_addr = ehdr->e_shoff + base;    nblock = ehdr->e_entry >> 16;  nsize = ehdr->e_entry & 0xffff;  __android_log_print(ANDROID_LOG_INFO, "JNITag", "nblock =  0x%x,nsize:%d", nblock,nsize);  __android_log_print(ANDROID_LOG_INFO, "JNITag", "base =  0x%x", text_addr);  printf("nblock = %d\n", nblock);    if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC | PROT_WRITE) != 0){    puts("mem privilege change failed");     __android_log_print(ANDROID_LOG_INFO, "JNITag", "mem privilege change failed");  }    for(i=0;i< nblock; i++){      char *addr = (char*)(text_addr + i);    *addr = ~(*addr);  }    if(mprotect((void *) (text_addr / PAGE_SIZE * PAGE_SIZE), 4096 * nsize, PROT_READ | PROT_EXEC) != 0){    puts("mem privilege change failed");  }  puts("Decrypt success");}unsigned long getLibAddr()unsigned long ret = 0char name[] = "libdemo.so"char buf[4096], *temp;  int pid;  FILE *fp;  pid = getpid();  sprintf(buf, "/proc/%d/maps", pid);  fp = fopen(buf, "r");  if(fp == NULL)  {    puts("open failed");    goto _error;  }  while(fgets(buf, sizeof(buf), fp)){    if(strstr(buf, name)){      temp = strtok(buf, "-");      ret = strtoul(temp, NULL, 16);      break;    }  }_error:  fclose(fp);  return ret;}JNIEXPORT jstring JNICALLJava_com_example_shelldemo_MainActivity_getString( JNIEnv* env,                                                  jobject thiz ){
   #if defined(__arm__)  #if defined(__ARM_ARCH_7A__)    #if defined(__ARM_NEON__)      #define ABI "armeabi-v7a/NEON"    #else      #define ABI "armeabi-v7a"    #endif  #else   #define ABI "armeabi"  #endif#elif defined(__i386__)   #define ABI "x86"#elif defined(__mips__)   #define ABI "mips"#else   #define ABI "unknown"#endif    return getString(env);}
下面来分析一下代码:

1、定义自己的段

jstring getString(JNIEnv*) __attribute__((section (".mytext")));jstring getString(JNIEnv* env)return (*env)->NewStringUTF(env, "Native method return!");};
这里的getString返回一个字符串,提供给Android上层,然后将getString定义在.mytext段中。

2、获取so加载到内存中的起始地址

unsigned long getLibAddr()unsigned long ret = 0char name[] = "libdemo.so"char buf[
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值