so文件在线加固加密_安卓so文件解密

so文件在线加固加密_安卓so文件解密这篇是一系列的关于SO文件保护的自我理解,SO文件保护分为加固,混淆以及最近炒的比较火的虚拟机,由于本人菜鸟,无力分析虚拟机,我相信以后会有机会。。。加固就是将真正的so代码保护起来,不让攻击者那么轻易的发现,至于混淆,由于ART机制的介入,使得O-LLVM越来越火,这以后有机会再分析,这次主要是基于有源码的so文件保护,下次介绍无源码的so文件保护,废话不多说,开搞。在这之前首先对e

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

 <span style="font-size:24px;">这篇是一系列的关于SO文件保护的自我理解,SO文件保护分为加固,混淆以及最近炒的比较火的虚拟机,由于本人菜鸟,无力分析虚拟机,我相信以后会有机会。。。加固就是将真正的so代码保护起来,不让攻击者那么轻易的发现,至于混淆,由于ART机制的介入,使得O-LLVM越来越火,这以后有机会再分析,这次主要是基于有源码的so文件保护,下次介绍无源码的so文件保护,废话不多说,开搞</span>
  在这之前首先对elf文件结构有一定的了解,不一定完全了解,本菜鸟就不是完全懂,在文章开始之前有个知识点必须了解:
so文件在线加固加密_安卓so文件解密

这两个节头要有所了解:

.init:可执行指令,构成进程的初始化代码,发生在main函数调用之前。

.fini:进程终止指令,发生在main函数调用之后。

以上这么分析感觉有点像c++的构造函数和析构函数,的确构造和析构是由此实现的。

并且结合GGC的可扩展机制:

__attribute__((section(".mytext")));可以把相应的函数和要保护的代码放在自己所定义的节里面。

这就引入了我们今天的主题,可以把我们关键的so文件中的核心函数放在自己所定义的节里面,然后进行加密保护,在合适的时机构造解密函数,当然解密函数可以用这个_attribute__((constructor))进行定义;类似于C++构造函数发生在main函数之前。

OK这个就是这篇文章的核心思想。

流程安排:

1.编写一个Native程序,对里面的关键函数放在自己所定义的节中,并且编写解密函数(当然这个是在你已知加密函数的基础上)

2.对得到的.so文件进行加密

3.加密后的替换验证

接下来走流程:

1.编写一个简单的计算器,把核心的代码放在.so文件里面如图:

so文件在线加固加密_安卓so文件解密

这个比较简单很容易理解:

接下来是关键函数的自定义与解密函数:直接看代码:

#include "com_example_jni02_CallSo.h"
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <elf.h>
#include <sys/mman.h>
#include <Android/log.h>
//这里对 Java_com_example_jni02_CallSo_plus这个方法进行加密保护
jint JNICALL Java_com_example_jni02_CallSo_plus(JNIEnv* env, jobject obj, jint a, jint b)  __attribute__((section (".mytext")));
JNIEXPORT jstring JNICALL Java_com_example_jni02_CallSo_getString
  (JNIEnv* env, jobject obj){
	return (*env)->NewStringUTF(env,"Hello");
}
JNIEXPORT jint JNICALL Java_com_example_jni02_CallSo_plus
  (JNIEnv* env, jobject obj, jint a, jint b){

	return a+b;
}
//在调用so文件进行解密
void init_Java_com_example_jni02_CallSo_plus() __attribute__((constructor));
unsigned long getLibAddr();

void init_Java_com_example_jni02_CallSo_plus(){
	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%d,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");
}
//获取到SO文件加载到内存中的起始地址,只有找到起始地址才能够进行解密;
unsigned long getLibAddr(){
	unsigned long ret=0;
	char name[]="libaddcomputer.so";
	char buf[4096];
	char *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;
}

在这里重点解释这个解密函数:

首先看到的是getLibAddr()这个函数:在介绍这个函数之前首先了解一个内存映射问题:

和Linux一样,Android提供了基于/proc的“伪文件”系统来作为查看用户进程内存映像的接口(cat /proc/pid/maps)。可以说,这是Android系统内核层开放给用户层关于进程内存信息的一扇窗户。通过它,我们可以查看到当前进程空间的内存映射情况,模块加载情况以及虚拟地址和内存读写执行(rwxp)属性等。

so文件在线加固加密_安卓so文件解密

接下来包括内存权限的修改以及函数的解密算法,最后包括内存权限的修改回去,应该都比较好理解。ok,以上编写完以后就编译生成.so文件。

2.对得到的.so文件进行加密:

这一块也是一个重点,大致上逻辑我们可以这么认为:先找到那个我们自己所定义的节,然后找到对应的offset和size,最后进行加密,加密完以后重新的写到另一个新的.so文件中,这块是需要建立在对ELF了解的基础上

这里重点了解一下这个加密函数,在自己写的时候可以在这个基础上进行改进。

首先看一下这个核心加密代码:

private static void encodeSection(byte[] fileByteArys){
		//读取String Section段
		System.out.println();
		
		int string_section_index = Utils.byte2Short(type_32.hdr.e_shstrndx);
		elf32_shdr shdr = type_32.shdrList.get(string_section_index);
		int size = Utils.byte2Int(shdr.sh_size);
		int offset = Utils.byte2Int(shdr.sh_offset);

		int mySectionOffset=0,mySectionSize=0;
		for(elf32_shdr temp : type_32.shdrList){
			int sectionNameOffset = offset+Utils.byte2Int(temp.sh_name);
			if(Utils.isEqualByteAry(fileByteArys, sectionNameOffset, encodeSectionName)){
				//这里需要读取section段然后进行数据加密
				mySectionOffset = Utils.byte2Int(temp.sh_offset);
				mySectionSize = Utils.byte2Int(temp.sh_size);
				byte[] sectionAry = Utils.copyBytes(fileByteArys, mySectionOffset, mySectionSize);
				for(int i=0;i<sectionAry.length;i++){
					//sectionAry[i] = (byte)(sectionAry[i] ^ 0xFF);
					sectionAry[i]=(byte) ~sectionAry[i];
				}
				Utils.replaceByteAry(fileByteArys, mySectionOffset, sectionAry);
			}
		}

		//修改Elf Header中的entry和offset值
		int nSize = mySectionSize/4096 + (mySectionSize%4096 == 0 ? 0 : 1);
		byte[] entry = new byte[4];
		entry = Utils.int2Byte((mySectionSize<<16) + nSize);
		Utils.replaceByteAry(fileByteArys, 24, entry);
		byte[] offsetAry = new byte[4];
		offsetAry = Utils.int2Byte(mySectionOffset);
		Utils.replaceByteAry(fileByteArys, 32, offsetAry);
	}

以上加密是没有问题的,但是对于最后so文件头的修改简单的说明一下:

修改so文件为什么不会报错的原因进行简单的说明:

我们在这考虑一个问题就是Section与Segment的区别,由于OS在映射ELF到内存时,每一个段会占用是页的整数倍,这样会产生浪费,在操作系统的层面来讲,可以吧相同权限的section放在一起成为一个Segment再进行映射,这样一来减少浪费,但是在映射的时候会有一部分信息不会映射到内存中,可以看这个图:

so文件在线加固加密_安卓so文件解密

因此来说修改这些不会报错。

3.对于文件替换后没有什么问题,运行结果为:

so文件在线加固加密_安卓so文件解密

总结:

该篇是在有源码的基础上进行对特定的section进行加密,但是试想一下,有多少情况下才能有源码,因此局限性比较大,

下一篇是基于二进制级别的特定函数的加密,链接为:点击打开链接
源码是:http://download.csdn.net/detail/feibabeibei_beibei/9532172


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/193600.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 响应式久草编程基础教程:久草Spring Boot 与 Lettuce 在线整合「建议收藏」

    响应式久草编程基础教程:久草Spring Boot 与 Lettuce 在线整合「建议收藏」本文主要介绍响应式编程访问Redis,以及SpringBoot与Lettuce的整合使用。Lettuce是可扩展性线程安全的Redis客户端,用于同步、异步和响应式使用。如果多个线程避免阻塞和事务性操作(例如BLPOP和MULTI/EXEC),则它们可以共享一个连接。Lettuce是基于Netty构建的。支持很多高级的Redis特性。根据SpringDataRedis2.0的更新的消息显示,SpringDataRedis不再支持JRedis的驱动,

    2022年10月19日
    2
  • Spring笔记(3)

    Spring笔记(3)

    2021年11月11日
    45
  • android 测试用例模板下载,app测试用例模板.doc

    android 测试用例模板下载,app测试用例模板.docapp测试用例模板APP基本测试用例个人首页1.我的页面2.个人信息页面3.个性标签页面4.TA的页面消息页面消息页面发布商品和图片发布商品分享图片买买买页面买买买页面一级分类页面买手热荐品类二级分类页面侧边栏页面购物车页面我的钱包页面一、编号条件步骤预期结果实际结果1打开我的页面?出现我的信息(头像、昵称、签名、关注数、粉丝数、入手、出手)、中部出现切换我发表的与我喜欢的tab、下部列表出现内容…

    2022年7月17日
    13
  • Postman教程大全「建议收藏」

    Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件Postman背景介绍用户在开发或者调试网络程序或者是网页B/S模式的程序的时候是需要一些方法来跟踪网页请求的,用户可以使用一些网络的监视工具比如著名的Firebug等网页调试工具。今天给大家介绍的这款网页调试工具不仅可以调试简单的css、html、脚本等简单的网页基本信息,它还可以发送几乎所有类型的HTTP请求!Postman在发送网络HTTP请求方面可以说是Chrome插件类产品中的代表产品之一。Postman的操

    2022年4月6日
    48
  • phpstome2021激活码【最新永久激活】[通俗易懂]

    (phpstome2021激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html1STL5S9V8F-eyJsaWN…

    2022年3月27日
    78
  • 【游戏开发实战】2D游戏摄像机镜头跟随,屏幕边缘限制镜头移动(使用Cinemachine组件)

    【游戏开发实战】2D游戏摄像机镜头跟随,屏幕边缘限制镜头移动(使用Cinemachine组件)【游戏开发实战】2D游戏摄像机镜头跟随,屏幕边缘限制镜头移动(使用Cinemachine组件)

    2022年5月28日
    46

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号