反编译工具apktool使用问题
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)数字签名
3.2 APK签名工具
jarsign
是Java本生自带的一个工具,可以对jar进行签名signapk
是后面专门为了Android应用程序apk进行签名的工具,7.0之后强制要求使用该工具进行签名
区别:
- 使用的签名文件不一样
jarsign工具签名时使用的是
signapk工具签名时使用的是pk8,x509.pem文件 -
生成的签名文件名不一样
jarsign的签名文件名包含了keystore的别名
signapk固定的名字,默认为CERT
- 使用的默认签名算法不一样
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文件,该文件包含了摘要算法、加密算法、签名和公钥等信息。
如下:
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技术实践。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
如何开始写你的第一个python脚本——简单爬虫入门!
好多朋友在入门python的时候都是以爬虫入手,而网络爬虫是近几年比较流行的概念,特别是在大数据分析热门起来以后,学习网络爬虫的人越来越多,哦对,现在叫数据挖掘了! 其实,一般的爬虫具有2个功能:取数据和存数据!好像说了句废话。。。 而从这2个功能拓展,需要的知识就很多了:请求数据、反爬处理、页面解析、内容匹配、绕过验证码、保持登录以及数据库等等相关知识,今天我们就来说说做一个简单的爬虫,一般需要的步骤! 存数据 先说存数据,是因为在初期学习的时候,接触的少,也不需要太过于关注,随着学习的慢慢深入,我们需要保存大批量的数据的时候,就需要去学习数据库的相关知识了!这个我们随后开篇单独说明。 初期,我们抓到需要的内容后,只需要保存到本地,无非保存到文档、表格(excel)等等几个方法,这里大家只需要掌握with语句就基本可以保证需求了。大概是这样的: with open(路径以及文件名,保存模式) as f: f.write(数据) #如果是文本可直接写入,如果是其他文件,数据为二进制模式更好 当然保存到excel表格或者word文档需要用到 xlwt库(excel)、python-doc...
- 下一篇
Java Web-Servlet
章节目录 什么是Servlet Servlet 、ServletContext、Servlet Container、web 容器之间的区别 Servlet、ServletConfig、GenericServlet、HttpServlet、自定义Servlet 之间的联系 HttpServlet 源码解析 Servlet 是否是线程安全的-线程非安全 1.什么是Servlet 1.1 定义 首先看Servlet接口源码中对于Servlet的定义 A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol. 译文如下: Servlet 是运行在Web服务器上的小型Java程序,Servlet 接受并响应来自Web 客户端的请求,通常请求通过HTTP协议进行请求传输。 总结如下: 1.Java Serv...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Mario游戏-低调大师作品
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,CentOS7官方镜像安装Oracle11G
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装