今天给各位分享Android应用程序加固方法研究(篇三)的知识,其中也会对进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
Android APK加固技术研究(三)
为了保证Android应用程序的源代码安全,我们一般会对线上应用程序的代码进行混淆。然而,代码混淆还不够。我们还需要加强我们的应用程序,防止他人通过反编译获取我们的源代码。目前apk加固技术已经比较成熟和完善,市场上比较流行的就是“360加固”。本文对apk加固技术进行技术探索。希望读者读完后能够理解加固原理,并自行实现加固方案。
源码地址:https://gitee.com/openjk/apk-jiagu
在Android apk加固技术探索(二)中,我们通过创建Steady模块生成了shell.arr文件,用于对加密的dex文件进行解密和类加载。本文主要讲解如何对原apk的dex进行加密,并在原apk中输入shell.arr,最终生成一个新的apk
一、反编译 APK 文件
Android APK加固技术研究(一)讲解如何反编译apk文件。这里我们使用apktool工具来反编译apk。通过执行命令java -jar outlibs/apktool_2.5.0.jar d "解压后的apk的路径" -o "解压后要存放的路径"
/**
* 反编译APK文件
*/
有趣的apkDecode(){
println("开始反编译")
val 进程=Runtime.getRuntime()
.exec("java -jar outlibs/apktool_2.5.0.jar d "+ orginApk.absolutePath+" -o "+apkDecode.absolutePath)
进程.waitFor()
if(process.exitValue() !=0) {
FileUtils.printStream(process.errorStream)
}别的{
FileUtils.printStream(process.inputStream)
}
进程.destroy()
}
二、修改 AndroidManifest.xml 文件
获取步骤1解压后的文件目录,并在该目录中找到AndroidManifest文件。这里修改AndroidManifest.xml文件有两种方法。一是通过“AXMLEditor.jar”和“AXMLPrinter2.jar”工具修改AndroidManifest.xml文件。另一种是通过SAX解析xml文件,然后在对应的节点位置插入需要的数据,最后发现虽然方法一修改了xml文件,但最终打包的新APK中的AndroidManifest.xml文件没有生效。后来方法二就生效了。下面发布了这两种方法的代码。如果有人发现方法一有问题,请不吝赐教。
方法一:关于如何使用“AXMLEditor.jar”和“AXMLPrinter2.jar”这两个工具,大家可以自行百度搜索。我们不会在这里展开它们。
/**
* 修改AndroidManifest
*/
有趣的更改AndroidManifest(apkUnzipDir:File){
val aManifest=apkUnzipDir.listFiles { _, name -name?equals("AndroidManifest.xml")==true
}
val file=if (aManifest !=null aManifest.isNotEmpty()) {
清单[0]
}否则{空}
文件?let {
//将模板插入到AndroidManifest中
val process2=Runtime.getRuntime()
.exec("java -jar outlibs/AXMLEditor.jar -tag -i 工具/src/main/assets/ApplicationName.xml " +
文件.absolutePath+""+文件.absolutePath)
process2.waitFor()
if(process2.exitValue() !=0) {
打印("2")
FileUtils.printStream(process2.errorStream)
}
process2.destroy()
//解析出原来的Application类名
var process0=Runtime.getRuntime()
.exec("java -jar 工具/libs/AXMLPrinter2.jar "+file.absolutePath)
process0.waitFor()
val applicationPath=XmlParseUtils.sax2xml(process0.inputStream)
if(process0.exitValue() !=0){
println("0")
FileUtils.printStream(process0.errorStream)
}
process0.destroy()
//参考https://github.com/fourbrother/AXMLEditor
//修改Application下插入标签的值
val process1=Runtime.getRuntime()
.exec("java -jar tool/libs/AXMLEditor.jar -attr -i 元数据包值"+applicationPath
+ " " + 文件.absolutePath+" "+文件.absolutePath)
process1.waitFor()
if(process1.exitValue() !=0){
打印("1")
FileUtils.printStream(process1.errorStream)
}
process1.destroy()
//参考https://github.com/fourbrother/AXMLEditor
//修改Application下的name标签
val process3=Runtime.getRuntime()
.exec("java -jar tool/libs/AXMLEditor.jar -attr -m 应用程序包名称com.sakuqi.shell.NewApplication"
+ " " + 文件.absolutePath+" "+文件.absolutePath)
process3.waitFor()
if(process3.exitValue() !=0){
println("3")
FileUtils.printStream(process3.errorStream)
}
process3.destroy()
//解析出原来的Application类名
var process4=Runtime.getRuntime()
.exec("java -jar 工具/libs/AXMLPrinter2.jar "+file.absolutePath)
process4.waitFor()
FileUtils.printStream(process4.inputStream)
process4.destroy()
}
}方法二:查看相关API文档了解如何使用SAXReader。
/**
* 修改xml文件
*/
有趣的变化AndroidManifest(){
println("开始修改AndroidManifest")
var manifestFile=File("输出/apktool/decode/AndroidManifest.xml")
changeXmlBySax(manifestFile,"com.sakuqi.steady.SteadyApplication")
//com.sakuqi.steady.SteadyApplication名称是Shell.arr中的Application类
}
/**
* 修改xml文件
*/
有趣的changeXmlBySax(fileXml:File,newApplicationName:String){
var sax=SAXReader()
var 文档=sax.read(fileXml)
var root=document.rootElement
var application=root.element("应用程序")
//原应用名称
var applicationName=application.attributeValue("name")
var applicationAttr=application.attribute("名称")
//用shell中的应用替换原来的应用
applicationAttr.text=newApplicationName
var element=application.addElement("元数据")
element.addAttribute("android:name","app_name")
element.addAttribute("android:value",applicationName)
保存文档(文档,fileXml)
}
有趣的saveDocument(document:Document,file:File){
var osWrite=OutputStreamWriter(FileOutputStream(文件))
var format=OutputFormat.createPrettyPrint()//获取指定的输出格式
格式.编码="UTF-8"
var writer=XMLWriter(osWrite,格式)
writer.write(文档)
writer.flush()
writer.close()
}
三、编译修改 AndroidManifest.xml 后的反编译目录
/**
* 编译APK文件
*/
有趣的apkBuild(){
println("开始重新编译")
val 进程=Runtime.getRuntime()
.exec("java -jar outlibs/apktool_2.5.0.jar b "+ "反编译目录"+" -o "+ "编译目录")
进程.waitFor()
if(process.exitValue() !=0) {
FileUtils.printStream(process.errorStream)
}别的{
FileUtils.printStream(process.inputStream)
}
进程.destroy()
}
四、解压 APK 文件并加密所以 Dex 文件
java.util.zip.ZipFile类用于解压。这里封装了工具类,最终会放到源码中,所以这里就不展开了。解压后需要删除原apk中的签名文件,以便以后重新签名。过滤掉解压目录下所有dex后缀的文件,然后进行加密。需要注意的是,加密方法需要与shell.arr中的解密方法保持一致。这里使用的是AES加密方法。稍后将讨论源代码。在开源项目中显示。加密后需要删除原dex文件。大致代码如下:
/**
* 解压APK文件并加密所有dex文件
*/
有趣的unZipApkAndEncrypt(){
println("解压APK")
val apkUnzipDir=文件("输出/解压/apk")
if(!apkUnzipDir.exists()){
apkUnzipDir.mkdirs()
}
FileUtils.delete(apkUnzipDir)
ZipUtils.unZip(apkBuild,apkUnzipDir)
//删除META-INF/CERT.RSA,META-INF/CERT.SF,META-INF/MANIFEST.MF
val certRSA=文件(apkUnzipDir,"META-INF/CERT.RSA")
certRSA.delete()
val certSF=文件(apkUnzipDir,"META-INF/CERT.SF")
certSF.delete()
val manifestMF=文件(apkUnzipDir,"META-INF/MANIFEST.MF")
清单MF.delete()
//更改AndroidManifest(apkUnzipDir)
//获取dex文件
val apkFiles=apkUnzipDir.listFiles(对象:FilenameFilter{
覆盖乐趣接受(dir:文件?name:字符串?):布尔值{
返回名称?endsWith(".dex")==true
}
})
for (apkFiles 中的dexFile){
val 名称=dexFile.name
println("dex:$名称")
val 字节=DexUtils.getBytes(dexFile)
val encrypt: 字节数组?=EncryptUtils.加密(字节, EncryptUtils.ivBytes)
val fos: FileOutputStream=FileOutputStream(
文件(
dexFile.parent,
"秘密-" + dexFile.getName()
)
)
fos.write(加密)
fos.flush()
fos.close()
dexFile.delete()
}
}
五、解压壳 aar 得到 class.jar ,然后把 class.jar 在转换成 class.dex,再将class.dex 移到原 apk 的解压目录,最后压缩成新的 apk 文件
这里仍然使用unzip的工具类进行解压,使用Android SDK自带的命令dx来转换class.dex。
/**
* 解压shell aar并将jar转换为dex
*/
有趣的makeDecodeDex(){
println("解压AAR")
var shellUnzipDir=文件("输出/解压/shell")
if(!shellUnzipDir.exists()){
shellUnzipDir.mkdirs()
}
FileUtils.delete(shellUnzipDir)
//解压AAR
ZipUtils.unZip(shellAAR,shellUnzipDir)
//将jar转为dex
println("将jar 转换为dex")
var shellJar=文件(shellUnzipDir,"classes.jar")
var shellDex=File("输出/解压/apk","classes.dex")
DexUtils.dxCommand(shellJar,shellDex)
moveLibSoToApk()
//盒
println("打包APK")
var unsignedApk=File("输出/unsigned_$orginApkName")
ZipUtils.zip(文件("输出/解压/apk"),unsignedApk)
}
/**
* 将shell中的lib文件移动到apk中
*/
有趣的moveLibSoToApk(){
var shellUnzipLibDir=文件("输出/解压/shell/jni")
var apkUnzipLibDir=文件("输出/解压/apk/lib")
if(!apkUnzipLibDir.exists()){
apkUnzipLibDir.mkdirs()
}
FileUtils.copy(shellUnzipLibDir,apkUnzipLibDir)
}对象DexUtils {
@Throws(IOException:类,InterruptedException:类)
有趣的dxCommand(jar:File,dex:File){
var 运行时=Runtime.getRuntime()
var process=runtime.exec("dx --dex --output "+dex.absolutePath+" "+jar.absolutePath)
尝试{
进程.waitFor()
}捕获(e:InterruptedException){
e.printStackTrace()
扔e
}
if(process.exitValue() !=0){
val 输入流=process.errorStream
var 缓冲区=ByteArray(1024)
val bos=ByteArrayOutputStream()
var len=inputStream.read(缓冲区)
而(长度!=-1){
bos.write(缓冲区,0,len)
len=inputStream.read(缓冲区)
}
System.out.println(String(bos.toByteArray(), Charset.forName("GBK")))
抛出RuntimeException("dx 运行失败")
}别的{
System.out.println("执行成功:"+process.exitValue())
}
进程.destroy()
}
/**
* 读取文件
* @参数文件
* @返回
* @抛出异常
*/
@Throws(Exception:类)
fun getBytes(file: 文件?): ByteArray {
val r=RandomAccessFile(文件, "r")
val buffer=ByteArray(r.length().toInt())
r.readFully(缓冲区)
r.close()
返回缓冲区
}
}
五、将压缩后的新的 apk 文件进行 zip 对齐操作
/**
* 结盟
*/
有趣的zipalign(){
println("对齐打包的apk")
var unsignedApk=File("输出/unsigned_$orginApkName")
valalignedApk=File("输出/无符号对齐_$orginApkName")
val 进程=Runtime.getRuntime().exec(
"zipalign -p -f -v 4"+unsignedApk.absolutePath+""+alignedApk.absolutePath)
process.waitFor(5,TimeUnit.SECONDS)
尝试{
if (process.exitValue() !=0) {
println("压缩对齐错误")
FileUtils.printStream(process.errorStream)
} 别的{
FileUtils.printStream(process.inputStream)
}
println("完整的apk对齐")
进程.destroy()
}捕获(e:异常){
println("对齐超时.")
}
}
六、将对齐后的 apk 文件进行签名
/**
* 签署APK
*/
有趣的jksToApk(){
println("签名的APK")
varsignedApk=File("输出/signed_$orginApkName")
valalignedApk=File("输出/无符号对齐_$orginApkName")
SignUtils.signature(alignedApk,signedApk,signFile.absolutePath)
}对象SignUtils {
@Throws(InterruptedException:类,IOException:类)
有趣的签名(unsignedApk:文件,signedApk:文件,keyStore:字符串){
val cmd=arrayOf(
"jarsigner",
"-sigalg",
"SHA1withRSA",
"-消化",
"SHA1",
"-密钥库",
密钥库,
"-商店通行证",
"密码",
"-keypass",
"密码",
"-signedjar",
签名的Apk.absolutePath,
未签名的Apk.absolutePath,
"阿利纳斯"
)
val 进程=Runtime.getRuntime().exec(cmd)
println("开始标志")
尝试{
val waitResult=process.waitFor()
println("waitResult: $waitResult")
} catch (e: InterruptedException) {
e.printStackTrace()
扔e
}
println("process.exitValue()" + process.exitValue())
if (process.exitValue() !=0) {
val 输入流=process.errorStream
var len: 整数
val 缓冲区=ByteArray(2048)
val bos=ByteArrayOutputStream()
len=inputStream.read(缓冲区)
while (len!=-1) {
bos.write(缓冲区, 0, 长度)
len=inputStream.read(缓冲区)
}
println(String(bos.toByteArray(), Charset.forName("gbk")))
throw RuntimeException("签名执行失败")
}
println("完成签名")
进程.destroy()
}
【Android应用程序加固方法研究(篇三)】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于等到新的文章了,一直在关注这个系列。
有13位网友表示赞同!
想了解一下最新的加固技巧,看看能不能提升自己APP的安全防范措施。
有7位网友表示赞同!
这篇文章肯定能让我受益匪浅,期待深入学习。
有18位网友表示赞同!
以前对Android APK加固技术只知道皮毛,希望能通过这篇文章了解更多专业知识。
有18位网友表示赞同!
最近在搞app安全开发,正好学习一下加固技术。
有16位网友表示赞同!
希望解释详细一些,方便小白理解。
有18位网友表示赞同!
想知道有哪些最新的常用加固工具和方法?
有11位网友表示赞同!
关注这个题材好久了,每次更新都期待值满满!
有12位网友表示赞同!
作者的分析很透彻,希望能看到更多实用的案例分享。
有16位网友表示赞同!
学习一下Android APK加固技术,让自己成为更优秀的开发者。
有5位网友表示赞同!
想了解加固技术对app性能影响有多大?
有10位网友表示赞同!
想知道如何评估加固的效果,以及有哪些测试方法吗?
有18位网友表示赞同!
最近在考虑使用哪种加固方式,这篇文章应该能帮我做出决定。
有19位网友表示赞同!
期待看到文章讨论一些常见的加固技术漏洞和应对策略。
有13位网友表示赞同!
学习了这个系列后,可以做出一款更加安全的APP吗?
有9位网友表示赞同!
希望看到更多关于逆向工程和安全测试的内容。
有17位网友表示赞同!
作者的技术水平真的很棒!期待更精彩的分享。
有9位网友表示赞同!
这篇文章值得收藏,以后有机会再仔细研究一下。
有20位网友表示赞同!
对于开发新APP来说,加固技术是必不可少的环节吧?
有13位网友表示赞同!
感觉学习这些高深的知识会让我对Android编程有更深入的理解。
有7位网友表示赞同!