您现在的位置是:首页 > 文章详情

反编译工具apktool使用问题

日期:2018-06-13点击:379

1 工具篇

反编译和回编用到的一些工具:

apktool是解包APK 文件最常用的工具

keytool是一个Java数据证书的管理工具

jarsigner是JDK提供的针对jar包签名的通用工具

apksigner是Google官方提供的针对Android apk签名及验证的专用工具

zipalign是对zip包对齐的工具,使APK包内未压缩的数据有序排列对齐,从而减少APP运行时内存消耗

2 调试包回编操作

通过apktool d xxx.apk得到反编译后smali文件和manifest文件,进行修改后,利用apktool build命令进行重新打包。

2.1 apktool编译时错误

资源文件找不到: \res\values-v19\styles.xml:11: error: Error: No resource found that matches the given name: attr 'android:actionModeFindDrawable'. \res\values-v19\styles.xml:10: error: Error: No resource found that matches the given name: attr 'android:actionModeShareDrawable'. \res\values-v19\styles.xml:21: error: Error: No resource found that matches the given name: attr 'android:actionModeFindDrawable'. Exception in thread "main" brut.androlib.AndrolibException: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/tmp/brut_util_Jar_7346229083806801174.tmp, p, --forced-package-id, 127, --min-sdk-version, 15, --target-sdk-version, 26, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /tmp/APKTOOL6548347216162541619.tmp, -0, arsc, -0, arsc, -I, /root/.local/share/apktool/framework/1.apk, -S, /root/Desktop/test1/res, -M, /root/Desktop/test1/AndroidManifest.xml] at brut.androlib.Androlib.buildResourcesFull(Androlib.java:492) at brut.androlib.Androlib.buildResources(Androlib.java:426) at brut.androlib.Androlib.build(Androlib.java:305) at brut.androlib.Androlib.build(Androlib.java:270) at brut.apktool.Main.cmdBuild(Main.java:227) at brut.apktool.Main.main(Main.java:75) Caused by: brut.androlib.AndrolibException: brut.common.BrutException: could not exec (exit code = 1): [/tmp/brut_util_Jar_7346229083806801174.tmp, p, --forced-package-id, 127, --min-sdk-version, 15, --target-sdk-version, 26, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /tmp/APKTOOL6548347216162541619.tmp, -0, arsc, -0, arsc, -I, /root/.local/share/apktool/framework/1.apk, -S, /root/Desktop/test1/res, -M, /root/Desktop/test1/AndroidManifest.xml] at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:456) at brut.androlib.Androlib.buildResourcesFull(Androlib.java:478) ... 5 more Caused by: brut.common.BrutException: could not exec (exit code = 1): [/tmp/brut_util_Jar_7346229083806801174.tmp, p, --forced-package-id, 127, --min-sdk-version, 15, --target-sdk-version, 26, --version-code, 1, --version-name, 1.0, --no-version-vectors, -F, /tmp/APKTOOL6548347216162541619.tmp, -0, arsc, -0, arsc, -I, /root/.local/share/apktool/framework/1.apk, -S, /root/Desktop/test1/res, -M, /root/Desktop/test1/AndroidManifest.xml] at brut.util.OS.exec(OS.java:95) at brut.androlib.res.AndrolibResources.aaptPackage(AndrolibResources.java:450) ... 6 more 

错误原因很清楚,大致是资源文件和一些api版本兼容的问题,比如style,manifest一些application属性等识别不了。
通过google有些说需要升级新版本,或者低版本反编译高版本打包,网上的一些答案我一般是不信的(哈哈哈)。
通过查apktool的命令帮助,可以通过-r参数来避免resc的反编译,见下图。这样在打包的时候就不会重新编译resc文件包括xml。

usage: apktool -advance,--advanced prints advance information. -version,--version prints the version then exits usage: apktool if|install-framework [options] <framework.apk> -p,--frame-path <dir> Stores framework files into <dir>. -t,--tag <tag> Tag frameworks using <tag>. usage: apktool d[ecode] [options] <file_apk> -f,--force Force delete destination directory. -o,--output <dir> The name of folder that gets written. Default is apk.out -p,--frame-path <dir> Uses framework files located in <dir>. -r,--no-res Do not decode resources. -s,--no-src Do not decode sources. -t,--frame-tag <tag> Uses framework files tagged by <tag>. usage: apktool b[uild] [options] <app_path> -f,--force-all Skip changes detection and build all files. -o,--output <dir> The name of apk that gets written. Default is dist/name.apk -p,--frame-path <dir> Uses framework files located in <dir>. 

那么,我们需要修改xml的时候还是需要反编译resc,或者通过二进制写入hex中。其实上面编译失败的原因是不同level的API造成的。
查看apktool.jar可发现android-framework.jar:
apktool.jar\brut\androlib\android-framework.jar

而android-framework.jar其实是用来指定默认的反编译和编译的framework(不同手机和不同版本有不同的framework,但一般都是向下兼容的)。
执行apktool命令会检查/Users/Quan/Library/apktool/framework/路径下是否包含1.apk,否则会通过android-framework.jar生成1.apk,然后通过该apk作为framework的路径。通过apktool -p可以指定 framework的路径。
结论:导致以上编译失败的原因很可能是,前期用过的apktool版本比较低,在编译api level较高的apk时候出现资源找不到的情况,我们只需要删除/Users/Quan/Library/apktool/framework/1.apk然后最新版本的apktool重新执行编译命令就可以。

在重新打包我们的APK后,其实app是不能够正常运行的,首页会在application初始化中抛出apk签名错误的异常,然后退出。

2.2 修改smali的技巧

可以直接修改后或者通过编写java文件先生成dex然后通过反编译smali之后进行在源文件中插入。
javac helloworld.java #编译生成.class文件
dx --dex --output=helloworld.dex helloworld.class #生成dex
apktool d helloworld.dex #反编译生成smali

2.3 调试APK

1.反编译插入xml android:debuggable="true"
2.修改系统默认的配置ro.debuggable=1
此文不赘述。
参考:https://www.bodkin.ren/index.php/archives/533/

3 关于apk的签名

3.1 签名和数字证书

签名:
消息发送方生成一对公私钥,消息发送过程中:
1)对要发送的原始消息提取消息摘要;
2)对提取的信息摘要用自己的私钥加密。
数字证书
一般包含以下一些内容:
1)证书的发布机构(Issuer)
2)证书的有效期(Validity)
3)消息发送方的公钥
4)证书所有者(Subject)
5)数字签名所使用的算法
6)数字签名


img_3cd0b69fca09bded27c47582cd665fb3.png
签名和验证过程

3.2 APK签名工具

jarsign是Java本生自带的一个工具,可以对jar进行签名
signapk是后面专门为了Android应用程序apk进行签名的工具,7.0之后强制要求使用该工具进行签名
区别:

  • 使用的签名文件不一样
    jarsign工具签名时使用的是
    signapk工具签名时使用的是pk8,x509.pem文件
  • 生成的签名文件名不一样
    jarsign的签名文件名包含了keystore的别名


    img_81892fd57ab6b6e29b4086b42861771c.png
    jarsign签名的产物

signapk固定的名字,默认为CERT


img_fab042cd02514bd3331d242a3b3637aa.png
signapk签名的产物
  • 使用的默认签名算法不一样
    jarsign默认使用sha-256做摘要算法
    signapk 默认使用sha-1做摘要算法

3.3 keystore和pk8,x509.pem可互相转换

在线转换地址:https://myssl.com/cert_convert.html

3.4 APK签名后文件

signapk签名apk之后会包含以下文件,位于META-INF。

1.MANIFEST.MF

Manifest-Version: 1.0 Built-By: Generated-by-ADT Created-By: Android Gradle 3.0.1 Name: AndroidManifest.xml SHA1-Digest: OOwo62rpBBm58uyA9DaVke/pjYM= 对资源和源码文件sha1散列,base64加密 Name: META-INF/android.arch.core_runtime.version SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw= Name: META-INF/android.arch.lifecycle_extensions.version SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw= Name: META-INF/android.arch.lifecycle_livedata-core.version SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw= Name: META-INF/android.arch.lifecycle_livedata.version SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw= Name: META-INF/android.arch.lifecycle_runtime.version SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw= Name: META-INF/android.arch.lifecycle_viewmodel.version SHA1-Digest: BeF7ZGqBckDCBhhvlPj0xwl01dw= Name: META-INF/proguard/com.rong360.cccredit.base.comInputWidget.BaseV iewHolder.pro SHA1-Digest: 2A1Gn5+G2UZfIRuOe+B8eayD3h4= Name: META-INF/proguard/com.rong360.cccredit.base.view.BaseItemView.pr o SHA1-Digest: 5NlorzmAOWo2s54IOAzJ6fpbRso= Name: META-INF/rxjava.properties SHA1-Digest: vKB1Ac/XQ8wiPI/th9N8DZ/+T9Y= Name: assets/getui_popup_bg.9.png SHA1-Digest: 0i2ug9zD61+mUJM+VRU0su+rZBA= Name: assets/getui_popup_close.png SHA1-Digest: 3PxcPZ1vRVg/shO9Q8m4kCl+++0= Name: assets/home_producing.json SHA1-Digest: F7l2niyrW1QfhVDBJwGIfu9OIqU= Name: assets/loading_blue.json SHA1-Digest: aARfFQziHhwO3GhIK9Yy1aowXYc= 

2.CERT.SF

Signature-Version: 1.0 Created-By: 1.0 (Android) SHA1-Digest-Manifest: wMk2wph8ittVFJEdaSigTm0sCiU= //对MANIFEST.MF SHA1+base64 Name: AndroidManifest.xml SHA1-Digest: hgUK+DK9MfGkBwlilMAQGpievZ8= //对MANIFEST.MF中块+2次回车 SHA1+base64 Name: META-INF/android.arch.core_runtime.version SHA1-Digest: OPQCkzMXJVPQryHeMowVNZmfRMw= Name: META-INF/android.arch.lifecycle_extensions.version SHA1-Digest: 6wRGJv7GN0UPnHsBck6G9DEz/wQ= Name: META-INF/android.arch.lifecycle_livedata-core.version SHA1-Digest: TSBGEIW1zN2n2sraHWcuRYSO8JU= Name: META-INF/android.arch.lifecycle_livedata.version SHA1-Digest: 2SW0lUzWE/vv2diFzKyGpyt9JW8= Name: META-INF/android.arch.lifecycle_runtime.version SHA1-Digest: yMBVn3OtS/Z9YTo02MlcvU6yfFM= Name: META-INF/android.arch.lifecycle_viewmodel.version SHA1-Digest: 5aVTTikyBvFlGq287kN/q6fe0kA= 

3.CERT.RSA
证书文件,包含了.sf文件的签名信息(sha256+私钥加密)公钥信息和发布机构信息

3.5 查看证书信息

通过openssl工具可以查看rsa文件,该文件包含了摘要算法、加密算法、签名和公钥等信息。
如下:


img_bcfbe085efcb26ecc959197daff18fec.png
证书的内容

4 项目中APK的签名验证

验证应用的签名证书信息和release包真实的签名是否一致,因为私钥不可能被人获取。这也是app store上认领app的方式。

5 通过xposed绕过签名验证机制

由于目标的apk有在初始化进行证书hash的认证,我需要通过以下方法绕过签名验证机制。

两方式:
1.修改Signature的方法
so中可以用IDA进行搜索,通过IDA分析so库中签名散列值。
通过hook android.content.pm.Signature#hashCode方法的返回值,将其置为和通过逆向得到的值一致,从而达到了绕过签名验证的机制。
2.Hook系统的PMS服务

public static void hookPms(Context context, String signed, int hashCode){ try{ // 获取全局的ActivityThread对象 Class<!--?--> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 获取ActivityThread里面原始的sPackageManager Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager"); sPackageManagerField.setAccessible(true); Object sPackageManager = sPackageManagerField.get(currentActivityThread); // 准备好代理对象, 用来替换原始的对象 Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<!--?-->[] {Class.forName("android.content.pm.IPackageManager") }, new PmsHookBinderInvocationHandler(sPackageManager, signed, hashCode)); // 替换掉ActivityThread里面的 sPackageManager 字段 sPackageManagerField.set(currentActivityThread, proxy); // 替换 ApplicationPackageManager里面的 mPM对象 PackageManager pm = context.getPackageManager(); Field mPmField = pm.getClass().getDeclaredField("mPM"); mPmField.setAccessible(true); mPmField.set(pm, proxy); }catch (Exception e){ } } public class PmsHookBinderInvocationHandler implements InvocationHandler{ private Object base; //应用正确的签名信息 private String SIGN; private int hashCode = 0; public PmsHookBinderInvocationHandler(Object base, String sign, int hashCode) { try { this.base = base; this.SIGN = sign; this.hashCode = hashCode; } catch (Exception e) { } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("getPackageInfo".equals(method.getName())){ Integer flag = (Integer)args[1]; if(flag == PackageManager.<em>GET_SIGNATURES</em>){ Signature sign = new Signature(SIGN); if(hashCode != 0){ try{ Class<!--?--> clazz = sign.getClass(); Field mHaveHashCodeF = clazz.getDeclaredField("mHaveHashCode"); mHaveHashCodeF.setAccessible(true); mHaveHashCodeF.set(sign, true); Field mHashCodeF = clazz.getDeclaredField("mHashCode"); mHashCodeF.setAccessible(true); mHashCodeF.set(sign, hashCode); }catch(Exception e){ } } PackageInfo info = (PackageInfo) method.invoke(base, args); info.signatures[0] = sign; return info; } } return method.invoke(base, args); } } private void getSignature() { try { PackageInfo packageInfo = getPackageManager().getPackageInfo( getPackageName(), PackageManager.<em>GET_SIGNATURES</em>); if (packageInfo.signatures != null) { Log.d("", "sig:"+packageInfo.signatures[0].toCharsString()+ "hashcode:"+packageInfo.signatures[0].hashCode()); } } catch (Exception e2) { } } 

该方法不需要借助xposed也无需root,将方法插入application首行(通过smali来插入),然后编译成apk。因为获取签名服务的在Security#init中所以我们可以在此之前hook 住pms,从而修改签名的信息。Hook部分的代码参考Android Hook技术实践

原文链接:https://yq.aliyun.com/articles/638432
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章