博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
转: android apk 防止反编译技术(1~5连载)
阅读量:7125 次
发布时间:2019-06-28

本文共 12579 字,大约阅读时间需要 41 分钟。

转:

 android apk 防止反编译技术

做android framework方面的工作将近三年的时间了,现在公司让做一下android apk安全方面的研究,于是最近就在网上找大量的资料来学习。现在将最近学习成果做一下整理总结。学习的这些成果我会做成一个系列慢慢写出来与大家分享,共同进步。这篇主要讲apk的加壳技术,废话不多说了直接进入正题。

一、加壳技术原理

所谓apk的加壳技术和pc exe的加壳原理一样,就是在程序的外面再包裹上另外一段代码,保护里面的代码不被非法修改或反编译,在程序运行的时候优先取得程序的控制权做一些我们自己想做的工作。(哈哈,跟病毒的原理差不多)

PC exe的加壳原理如下:

二、android apk加壳实现

  要想实现加壳需要解决的技术点如下:

(1)怎么第一时间执行我们的加壳程序?

首先根据上面的原理我们在apk中要想优先取得程序的控制权作为android apk的开发人员都知道Application会被系统第一时间调用而我们的程序也会放在这里执行。

(2)怎么将我们的加壳程序和原有的android apk文件合并到一起?

我们知道android apk最终会打包生成dex文件,我们可以将我们的程序生成dex文件后,将我们要进行加壳的apk和我们dex文件合并成一个文件,然后修改dex文件头中的checksum、signature和file_size的信息,并且要附加加壳的apk的长度信息在dex文件中,以便我们进行解壳保证原来apk的正常运行。加完壳后整个文件的结构如下:

(3)怎么将原来的apk正常的运行起来?

按照(2)中的合并方式在当我们的程序首先运行起来后,逆向读取dex文件获取原来的apk文件通过DexClassLoader动态加载。

具体实现如下:

(1)修改原来apk的AndroidMainfest.xml文件,假如原来apk的AndroidMainfest.xml文件内容如下:

1.  <application  

2.      android:icon="@drawable/ic_launcher"  

3.      android:label="@string/app_name"  

4.      android:theme="@style/AppTheme" android:name="com.android.MyApplication" >  

5.  </application>

修改后的内容如下:

1.  <application  

2.      android:icon="@drawable/ic_launcher"  

3.      android:label="@string/app_name"  

4.      android:theme="@style/AppTheme" android:name="com.android.shellApplication" >  

5.  <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/>

6.  </application> 

com.android.shellApplication这个就是我们的程序的的application的名称,而

7.  <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/>

是原来的apk的application名称。

(2)合并文件代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
public 
class 
ShellTool {
  
/**
   
* @param args
   
*/
  
public 
static 
void 
main(String[] args) {
         
// TODO Auto-generated method stub
         
try 
{
                
File payloadSrcFile = 
new 
File(
"payload.apk"
);
//我们要加壳的apk文件
                
File unShellDexFile = 
new 
File(
"classes.dex"
);
//我们的程序生成的dex文件
                
byte
[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
                
byte
[] unShellDexArray = readFileBytes(unShellDexFile);
                
int 
payloadLen = payloadArray.length;
                
int 
unShellDexLen = unShellDexArray.length;
                
int 
totalLen = payloadLen + unShellDexLen +
4
;
                
byte
[] newdex = 
new 
byte
[totalLen];
                
//添加我们程序的dex
                
System.arraycopy(unShellDexArray, 
0
, newdex, 
0
, unShellDexLen);
                
//添加要加壳的apk文件
                
System.arraycopy(payloadArray, 
0
, newdex, unShellDexLen,
                              
payloadLen);
                
//添加apk文件长度
                
System.arraycopy(intToByte(payloadLen), 
0
, newdex, totalLen-
4
4
);
                        
//修改DEX file size文件头
                
fixFileSizeHeader(newdex);
                
//修改DEX SHA1 文件头
                
fixSHA1Header(newdex);
                
//修改DEX CheckSum文件头
                
fixCheckSumHeader(newdex);
  
  
                
String str = 
"outdir/classes.dex"
;
                
File file = 
new 
File(str);
                
if 
(!file.exists()) {
                       
file.createNewFile();
                
}
                 
                
FileOutputStream localFileOutputStream = 
new 
FileOutputStream(str);
                
localFileOutputStream.write(newdex);
                
localFileOutputStream.flush();
                
localFileOutputStream.close();
  
  
         
catch 
(Exception e) {
                
// TODO Auto-generated catch block
                
e.printStackTrace();
         
}
  
}
   
  
//直接返回数据,读者可以添加自己加密方法
  
private 
static 
byte
[] encrpt(
byte
[] srcdata){
         
return 
srcdata;
  
}
  
  
  
private 
static 
void 
fixCheckSumHeader(
byte
[] dexBytes) {
         
Adler32 adler = 
new 
Adler32();
         
adler.update(dexBytes, 
12
, dexBytes.length - 
12
);
         
long 
value = adler.getValue();
         
int 
va = (
int
) value;
         
byte
[] newcs = intToByte(va);
         
byte
[] recs = 
new 
byte
[
4
];
         
for 
(
int 
i = 
0
; i < 
4
; i++) {
                
recs[i] = newcs[newcs.length - 
1 
- i];
                
System.out.println(Integer.toHexString(newcs[i]));
         
}
         
System.arraycopy(recs, 
0
, dexBytes, 
8
4
);
         
System.out.println(Long.toHexString(value));
         
System.out.println();
  
}
  
  
  
public 
static 
byte
[] intToByte(
int 
number) {
         
byte
[] b = 
new 
byte
[
4
];
         
for 
(
int 
i = 
3
; i >= 
0
; i--) {
                
b[i] = (
byte
) (number % 
256
);
                
number >>= 
8
;
         
}
         
return 
b;
  
}
  
  
  
private 
static 
void 
fixSHA1Header(
byte
[] dexBytes)
                
throws 
NoSuchAlgorithmException {
         
MessageDigest md = MessageDigest.getInstance(
"SHA-1"
);
         
md.update(dexBytes, 
32
, dexBytes.length - 
32
);
         
byte
[] newdt = md.digest();
         
System.arraycopy(newdt, 
0
, dexBytes, 
12
20
);
         
String hexstr = 
""
;
         
for 
(
int 
i = 
0
; i < newdt.length; i++) {
                
hexstr += Integer.toString((newdt[i] & 
0xff
) + 
0x100
16
)
                              
.substring(
1
);
         
}
         
System.out.println(hexstr);
  
}
  
  
  
private 
static 
void 
fixFileSizeHeader(
byte
[] dexBytes) {
  
  
         
byte
[] newfs = intToByte(dexBytes.length);
         
System.out.println(Integer.toHexString(dexBytes.length));
         
byte
[] refs = 
new 
byte
[
4
];
         
for 
(
int 
i = 
0
; i < 
4
; i++) {
                
refs[i] = newfs[newfs.length - 
1 
- i];
                
System.out.println(Integer.toHexString(newfs[i]));
         
}
         
System.arraycopy(refs, 
0
, dexBytes, 
32
4
);
  
}
  
  
  
private 
static 
byte
[] readFileBytes(File file) 
throws 
IOException {
         
byte
[] arrayOfByte = 
new 
byte
[
1024
];
         
ByteArrayOutputStream localByteArrayOutputStream = 
new 
ByteArrayOutputStream();
         
FileInputStream fis = 
new 
FileInputStream(file);
         
while 
(
true
) {
                
int 
i = fis.read(arrayOfByte);
                
if 
(i != -
1
) {
                       
localByteArrayOutputStream.write(arrayOfByte, 
0
, i);
                
else 
{
                       
return 
localByteArrayOutputStream.toByteArray();
                
}
         
}
  
}
  
  
}

(3)在我们的程序中加载运行原来的apk文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
public 
class 
shellApplication 
extends 
Application {
  
  
  
private 
static 
final 
String appkey = 
"APPLICATION_CLASS_NAME"
;
  
private 
String apkFileName;
  
private 
String odexPath;
  
private 
String libPath;
  
  
  
protected 
void 
attachBaseContext(Context base) {
         
super
.attachBaseContext(base);
         
try 
{
                
File odex = 
this
.getDir(
"payload_odex"
, MODE_PRIVATE);
                
File libs = 
this
.getDir(
"payload_lib"
, MODE_PRIVATE);
                
odexPath = odex.getAbsolutePath();
                
libPath = libs.getAbsolutePath();
                
apkFileName = odex.getAbsolutePath() + 
"/payload.apk"
;
                
File dexFile = 
new 
File(apkFileName);
                
if 
(!dexFile.exists())
                       
dexFile.createNewFile();
                
// 读取程序classes.dex文件
                
byte
[] dexdata = 
this
.readDexFileFromApk();
                
// 分离出解壳后的apk文件已用于动态加载
                
this
.splitPayLoadFromDex(dexdata);
                
// 配置动态加载环境
                
Object currentActivityThread = RefInvoke.invokeStaticMethod(
                              
"android.app.ActivityThread"
"currentActivityThread"
,
                              
new 
Class[] {}, 
new 
Object[] {});
                
String packageName = 
this
.getPackageName();
                
HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
                              
"android.app.ActivityThread"
, currentActivityThread,
                              
"mPackages"
);
                
WeakReference wr = (WeakReference) mPackages.get(packageName);
                
DexClassLoader dLoader = 
new 
DexClassLoader(apkFileName, odexPath,
                              
libPath, (ClassLoader) RefInvoke.getFieldOjbect(
                                            
"android.app.LoadedApk"
, wr.get(), 
"mClassLoader"
));
                
RefInvoke.setFieldOjbect(
"android.app.LoadedApk"
"mClassLoader"
,
                              
wr.get(), dLoader);
  
  
         
catch 
(Exception e) {
                
// TODO Auto-generated catch block
                
e.printStackTrace();
         
}
  
}
  
  
  
public 
void 
onCreate() {
         
{
  
  
                
// 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
                
String appClassName = 
null
;
                
try 
{
                       
ApplicationInfo ai = 
this
.getPackageManager()
                                     
.getApplicationInfo(
this
.getPackageName(),
                                                   
PackageManager.GET_META_DATA);
                       
Bundle bundle = ai.metaData;
                       
if 
(bundle != 
null
                                     
&& bundle.containsKey(
"APPLICATION_CLASS_NAME"
)) {
                              
appClassName = bundle.getString(
"APPLICATION_CLASS_NAME"
);
                       
else 
{
                              
return
;
                       
}
                
catch 
(NameNotFoundException e) {
                       
// TODO Auto-generated catch block
                       
e.printStackTrace();
                
}
  
  
                
Object currentActivityThread = RefInvoke.invokeStaticMethod(
                              
"android.app.ActivityThread"
"currentActivityThread"
,
                              
new 
Class[] {}, 
new 
Object[] {});
                
Object mBoundApplication = RefInvoke.getFieldOjbect(
                              
"android.app.ActivityThread"
, currentActivityThread,
                              
"mBoundApplication"
);
                
Object loadedApkInfo = RefInvoke.getFieldOjbect(
                              
"android.app.ActivityThread$AppBindData"
,
                              
mBoundApplication, 
"info"
);
                
RefInvoke.setFieldOjbect(
"android.app.LoadedApk"
"mApplication"
,
                              
loadedApkInfo, 
null
);
                
Object oldApplication = RefInvoke.getFieldOjbect(
                              
"android.app.ActivityThread"
, currentActivityThread,
                              
"mInitialApplication"
);
                
ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
                              
.getFieldOjbect(
"android.app.ActivityThread"
,
                                            
currentActivityThread, 
"mAllApplications"
);
                
mAllApplications.remove(oldApplication);
                
ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
                              
.getFieldOjbect(
"android.app.LoadedApk"
, loadedApkInfo,
                                            
"mApplicationInfo"
);
                
ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
                              
.getFieldOjbect(
"android.app.ActivityThread$AppBindData"
,
                                            
mBoundApplication, 
"appInfo"
);
                
appinfo_In_LoadedApk.className = appClassName;
                
appinfo_In_AppBindData.className = appClassName;
                
Application app = (Application) RefInvoke.invokeMethod(
                              
"android.app.LoadedApk"
"makeApplication"
, loadedApkInfo,
                              
new 
Class[] { 
boolean
.
class
, Instrumentation.
class 
},
                              
new 
Object[] { 
false
null 
});
                
RefInvoke.setFieldOjbect(
"android.app.ActivityThread"
,
                              
"mInitialApplication"
, currentActivityThread, app);
  
  
                
HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(
                              
"android.app.ActivityThread"
, currentActivityThread,
                              
"mProviderMap"
);
                
Iterator it = mProviderMap.values().iterator();
                
while 
(it.hasNext()) {
                       
Object providerClientRecord = it.next();
                       
Object localProvider = RefInvoke.getFieldOjbect(
                                     
"android.app.ActivityThread$ProviderClientRecord"
,
                                     
providerClientRecord, 
"mLocalProvider"
);
                       
RefInvoke.setFieldOjbect(
"android.content.ContentProvider"
,
                                     
"mContext"
, localProvider, app);
                
}
                
app.onCreate();
         
}
  
}
  
  
  
private 
void 
splitPayLoadFromDex(
byte
[] data) 
throws 
IOException {
         
byte
[] apkdata = decrypt(data);
         
int 
ablen = apkdata.length;
         
byte
[] dexlen = 
new 
byte
[
4
];
         
System.arraycopy(apkdata, ablen - 
4
, dexlen, 
0
4
);
         
ByteArrayInputStream bais = 
new 
ByteArrayInputStream(dexlen);
         
DataInputStream in = 
new 
DataInputStream(bais);
         
int 
readInt = in.readInt();
         
System.out.println(Integer.toHexString(readInt));
         
byte
[] newdex = 
new 
byte
[readInt];
         
System.arraycopy(apkdata, ablen - 
4 
- readInt, newdex, 
0
, readInt);
         
File file = 
new 
File(apkFileName);
         
try 
{
                
FileOutputStream localFileOutputStream = 
new 
FileOutputStream(file);
                
localFileOutputStream.write(newdex);
                
localFileOutputStream.close();
  
  
         
catch 
(IOException localIOException) {
                
throw 
new 
RuntimeException(localIOException);
         
}
  
  
         
ZipInputStream localZipInputStream = 
new 
ZipInputStream(
                       
new 
BufferedInputStream(
new 
FileInputStream(file)));
         
while 
(
true
) {
                
ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                
if 
(localZipEntry == 
null
) {
                       
localZipInputStream.close();
                       
break
;
                
}
                
String name = localZipEntry.getName();
                
if 
(name.startsWith(
"lib/"
) && name.endsWith(
".so"
)) {
                       
File storeFile = 
new 
File(libPath + 
"/"
                                     
+ name.substring(name.lastIndexOf(
'/'
)));
                       
storeFile.createNewFile();
                       
FileOutputStream fos = 
new 
FileOutputStream(storeFile);
                       
byte
[] arrayOfByte = 
new 
byte
[
1024
];
                       
while 
(
true
) {
                              
int 
i = localZipInputStream.read(arrayOfByte);
                              
if 
(i == -
1
)
                                     
break
;
                              
fos.write(arrayOfByte, 
0
, i);
                       
}
                       
fos.flush();
                       
fos.close();
                
}
                
localZipInputStream.closeEntry();
         
}
         
localZipInputStream.close();
  
  
  
}
  
  
  
private 
byte
[] readDexFileFromApk() 
throws 
IOException {
         
ByteArrayOutputStream dexByteArrayOutputStream = 
new 
ByteArrayOutputStream();
         
ZipInputStream localZipInputStream = 
new 
ZipInputStream(
                       
new 
BufferedInputStream(
new 
FileInputStream(
                                     
this
.getApplicationInfo().sourceDir)));
         
while 
(
true
) {
                
ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                
if 
(localZipEntry == 
null
) {
                       
localZipInputStream.close();
                       
break
;
                
}
                
if 
(localZipEntry.getName().equals(
"classes.dex"
)) {
                       
byte
[] arrayOfByte = 
new 
byte
[
1024
];
                       
while 
(
true
) {
                              
int 
i = localZipInputStream.read(arrayOfByte);
                              
if 
(i == -
1
)
                                     
break
;
                              
dexByteArrayOutputStream.write(arrayOfByte, 
0
, i);
                       
}
                
}
                
localZipInputStream.closeEntry();
         
}
         
localZipInputStream.close();
         
return 
dexByteArrayOutputStream.toByteArray();
  
}
  
  
  
// //直接返回数据,读者可以添加自己解密方法
  
private 
byte
[] decrypt(
byte
[] data) {
         
return 
data;
  
}

 根据上面的讲述相信大家对apk的加壳技术有了一定的了解,下一篇我们将讲解另一种android apk防止反编译技术-运行时修改dalvik指令()。如果对这篇讲的技术有任何疑问及想要获得这篇文章讲的技术的工程源码

欢迎关注个人微信公众平台:程序员互动联盟(coder_online),扫一扫下方二维码或搜索微信号coder_online即可关注,我们可以在线交流。

 

 

 

转载地址:http://inael.baihongyu.com/

你可能感兴趣的文章
python 逻辑运算符
查看>>
Hibernate技术
查看>>
js实现限制输入框只能输入数字
查看>>
CentOS下杀毒工具ClamAV安装
查看>>
编译参数查看
查看>>
httpd学习:http基础
查看>>
硬盘结构与工作原理
查看>>
改动过.gitignore文件之后设置生效
查看>>
LAMP编译安装1
查看>>
Does GATHER_STATS_JOB gather all objects' stats every time?
查看>>
Oracle绑定变量窥探
查看>>
BPDU guard和bpdufilter的一些介绍
查看>>
批量删除文件
查看>>
DNS服务器的学习和搭建
查看>>
C++语言笔记C11库
查看>>
systemd及启动流程
查看>>
java转换ppt,ppt转成图片,获取备注,获取文本
查看>>
lvs 负载均衡fullnat 模式clientip 怎样传递给 realserver
查看>>
python实现FTP服务器
查看>>
负载均衡7层nginx(提供软件包)
查看>>