android apk 防止反编译技术第二篇-运行时修改字节码

上一篇我们讲了apk防止反编译技术中的加壳技术,如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372。接下来我们将介绍另一种防止apk反编译的技术-运行时修改字节码。这种方法是在工作中在实现app wrapping时,看到国外的一篇关于android 安全的介绍实现的并且独创。下面我们来介绍一下这种方法。

我们知道apk生成后所有的java生成的class文件都被dx命令整合成了一个classes.dex文件,当apk运行时dalvik虚拟机加载classes.dex文件并且用dexopt命令进行进一步的优化成odex文件。我们的方法就是在这个过程中修改dalvik指令来达到我们的目的。

一、dex文件格式

dex的文件格式通常有7个主要部分和数据區组成,格式如下:

header部分记录了主要的信息其他的部分只是索引,索引的内容存在data区域。

Header部分结构如下:

字段名称

偏移值

长度

描述

magic

0x0

8

'Magic'值,即魔数字段,格式如”dex/n035/0”,其中的035表示结构的版本。

checksum

0x8

4

校验码。

signature

0xC

20

SHA-1签名。

file_size

0x20

4

Dex文件的总长度。

header_size

0x24

4

文件头长度,009版本=0x5C,035版本=0x70。

endian_tag

0x28

4

标识字节顺序的常量,根据这个常量可以判断文件是否交换了字节顺序,缺省情况下=0x78563412。

link_size

0x2C

4

连接段的大小,如果为0就表示是静态连接。

link_off

0x30

4

连接段的开始位置,从本文件头开始算起。如果连接段的大小为0,这里也是0。

map_off

0x34

4

map数据基地址。

string_ids_size

0x38

4

字符串列表的字符串个数。

string_ids_off

0x3C

4

字符串列表表基地址。

type_ids_size

0x40

4

类型列表里类型个数。

type_ids_off

0x44

4

类型列表基地址。

proto_ids_size

0x48

4

原型列表里原型个数。

proto_ids_off

0x4C

4

原型列表基地址。

field_ids_size

0x50

4

字段列表里字段个数。

field_ids_off

0x54

4

字段列表基地址。

method_ids_size

0x58

4

方法列表里方法个数。

method_ids_off

0x5C

4

方法列表基地址。

class_defs_size

0x60

4

类定义类表中类的个数。

class_defs_off

0x64

4

类定义列表基地址。

data_size

0x68

4

数据段的大小,必须以4字节对齐。

data_off

0x6C

4

数据段基地址

dex与class文件相比的一个优势,就是将所有的常量字符串集统一管理起来了,这样就可以减少冗余,最终的dex文件size也能变小一些。详细的dex文件介绍就不说了,有兴趣的可以查看android 源码dalvik/docs目录下的dex-format.html文件有详细介绍。不过我记得在android4.0版本后就没有了这个文件。

根据上面的dex文件的格式结构,dalvik虚拟机运行dex文件执行的字节码就存在method_ids区域里面。我们查看dalvik虚拟机源码会有一个

struct DexCode {

u2 registersSize;

u2 insSize;

u2 outsSize;

u2 triesSize;

u4 debugInfoOff; /* file offsetto debug info stream */

u4 insnsSize; /* size of theinsns array, in u2 units */

u2 insns[1];

/* followed by optional u2 padding*/

/* followed by try_item[triesSize]*/

/* followed by uleb128 handlersSize */

/* followed bycatch_handler_item[handlersSize] */

};

这样一个结构,这里的insns数组存放的就是dalvik的字节码。我们只要定位到相关类方法的DexCode数据段,即可通过修改insns数组,从而实现我们的目的。

二、odex文件格式

apk安装或启动时,会通过dexopt来将dex生成优化的odex文件。过程是将apk中的classes.dex解压后,用dexopt处理并保存为/data/dalvik-cache/data@app@<package-name>-X.apk@classes.dex文件。

odex文件结构如下:

从上图中我们发现dex文件作为优化后的odex的一部分,我们只需要从odex中找出dex的部分即可以了。

三、方法实现

要实现修改字节码,就需要先定位到想要修改得代码的位置,这就需要先解析dex文件。dex文件的解析在dalvik源码的dexDump.cpp给出了我们具体的实现,根据它的实现我们可以查找我们需要的类及方法。具体实现步骤如下:

(1)找到我们apk生成的odex文件,获得odex文件在内存中的映射地址和大小。实现代码如下:

void *base = NULL;

int module_size = 0;

char filename[512];

// simple test code here!

for(int i=0; i<2; i++){

sprintf(filename,"/data/dalvik-cache/data@app@%s-%d.apk@classes.dex","com.android.dex", i+1);

base = get_module_base(-1,filename);//获得odex文件在内存中的映射地址

if(base != NULL){

break;

}

}

module_size = get_module_size(-1, filename); //获得odex文件大小

(2)知道dex文件在odex中的偏移,以便解析dex文件。代码如下:

// search dex from odex

void *dexBase = searchDexStart(base);

if(checkDexMagic(dexBase) == false){

ALOGE("Error! invalid dex format at: %p",dexBase);

return;

}

(3)找到dex偏移以后就可以解析dex文件,从而查找我们要进行替换的方法所在的类,然后在该类中找到该方法并返回该方法对应的DexCode结构体。函数实现如下:

static const DexCode*dexFindClassMethod(DexFile *dexFile, const char *clazz, const char *method)

{

DexClassData* classData =dexFindClassData(dexFile, clazz);

if(classData == NULL) return NULL;

const DexCode* code =dexFindMethodInsns(dexFile, classData, method);

if(code != NULL) {

dumpDexCode(code);

}

return code;

}

(4)找到DexCode后就可以进行指令替换了。实现如下:

const DexCode *code =

dexFindClassMethod(&gDexFile,"Lcom/android/dex/myclass;", "setflagHidden");

const DexCode*code2 =

dexFindClassMethod(&gDexFile, "Lcom/android/dex/myclass;","setflag");

// remap!!!!

if(mprotect(base, module_size, PROT_READ |PROT_WRITE | PROT_EXEC) == 0){

DexCode *pCode = (DexCode *)code2;

//Modify!

pCode->registersSize= code->registersSize;

for(u4 k=0; k<code->insnsSize;k++){

pCode->insns[k]= code->insns[k];

}

mprotect(base, module_size, PROT_READ |PROT_EXEC);

}

注意:由于是在运行时修改的dalvik指令,这是进程的内存映射为只读的,所以需要调用mprotect函数将只读改为读写才能进行指令的修改。

根据上面的讲述相信大家对运行时修改字节码的技术有了一定的了解,下一篇我们将讲解另一种android apk防止反编译技术,期待大家的捧场。

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-04-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏生信技能树

linux命令行文本操作一文就够

主要是 awk/grep/sed这三驾马车,加上vi这个神器,最后辅助一些小工具,包括 wc,cat,diff,join,paste,cut,uniq 这里 简...

3309
来自专栏pythonlove

Bash脚本编程(原创)

Bash,Unix shell的一種,在1987年由布萊恩·福克斯為了GNU計劃而编写。1989年釋出第一個正式版本,原先是計劃用在GNU作業系統上,但能运行于...

893
来自专栏Python

datetime

datetime是Python处理日期和时间的标准库。 获取当前日期和时间 我们先看如何获取当前日期和时间: >>> from datetime import ...

19110
来自专栏YG小书屋

Python3编码与mysql编码介绍

2975
来自专栏cs

c++那些事儿7.0 I/O流,文件操作

知识点综述: ---- C++ I/O: 在iostream头文件中定义 istream //通用输入流和其它输入流基类。 ...

3297
来自专栏游戏开发那些事

【Linux程序设计】之环境系统函数综合实验

这个系列的博客贴的都是我大二的时候学习Linux系统高级编程时的一些实验程序,都挺简单的。贴出来纯粹是聊胜于无。

913
来自专栏编程

云平台渗透之-python shell获取root权限

2018年的第一天,祝大家365天元气满满! 话不多说,先打响新年第一炮(不好意思,我又污了=.=) ***本系列内容仅用于技术分享,请勿对号入座*** 之前有...

2225
来自专栏C/C++基础

Linux命令(36)——awk命令

AWK是一个优良的文本处理工具,Linux及Unix环境中现有的功能最强大的数据处理引擎之一。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输...

1012
来自专栏ChaMd5安全团队

0ctf2018 babystack、babyheap、blackhole解析

0ctf2018 babystack writeup 赛题链接 https://github.com/eternalsakura/ctf_pwn/tree/ma...

4028
来自专栏专注研发

文件操作

函数原型:void open(const char*filename,int mode,int access);      

742

扫码关注云+社区