Android apk 反编译学习记录
来源:互联网 发布:suse linux yum配置 编辑:程序博客网 时间:2024/06/06 18:12
最近因为业务需要,接触了一下apk 的反编译。项目忙完了,写篇东西来总结一下自己的学习。
我们通过一个简单的例子来简单介绍一下反编译及修改代码的方法
学习目标:
•学习如何利用开源工具反编译android 的普通 apk
•尝试修改xml 文件或 更换资源文件,并重新打包apk
•利用 DDMS 和反编译出来的代码,分析代码逻辑,帮助读懂Smali 文件
•尝试修改 Smali 文件,修改代码逻辑,并重新打包apk
•重新再真机上运行 修改过后的apk,检查修改效果
•记录并解决遇到的问题
前期准备:
只需要下载 apktool 工具,还有本机安装有 JDK,以及 dex2jar 和 jd-gui.exe 两个工具。看smali 代码推荐使用 Notepad++
先从简单的开始,我们先自己写一个 简单的 app,就实现在activity里显示一个字符串的功能,作为反编译用的例子apk
public class MainActivity extends Activity {private TextView tv;private param p;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv = (TextView) this.findViewById(R.id.tv);tv.setText(R.string.hello_world);}}运行看看修改前的效果
然后,通过apktool 工具对这个apk 进行反编译,十分简单
步骤:
1.在命令行里 cd 到你apktool 的文件夹里(就是你 aapt.exe,apktool.jar,apktool.bat 这几个文件的路径)
2.执行 apktool d –f 你的apk的路径 反编译生成物的路径 (例如:apktool d -f smalitest.apk C:/out)
执行成功后,就可以看到反编译出来的文件了,xml 跟 资源文件都可以直接修改了,java 代码会被反编译成smali 文件,这是一种 虚拟机的执行语言 ,后面介绍怎么阅读及修改
现在,我们尝试修改一下 res/values 文件里的 strings.xml 里的 字符串,然后重新打包一下apk 查看效果
<resources> <string name="app_name">SmaliTest</string> <string name="hello_world"><span style="color:#ff0000;">It have been modified</span></string></resources>
执行 apktool b 你修改过的apk文件路径(我的命令是:apktool b c:/out)
执行完成后,你会看到多了两个文件夹,一个build 一个 dist重新打包的apk 就在 dist 文件夹下
即使生成了新的apk,但是这个apk还是不能正常安装的,因为这是一个裸的apk,木有经过数字签名。Android 系统不能接受没有签名的apk 的安装及运行,所以我们还需要对裸的apk 进行签名认证
我自己使用jdk 自带的签名工具jarsigner进行签名,这个工具位置在你的java 安装路径下的 JDK/bin 目录下
执行 jarsigner -verbose –keystore 秘钥仓库 -signedjar 签名完后的apk名字 被签名的裸apk路径 私钥
(我的命令是: jarsigner -verbose –keystore lzq.keystore -signedjar test_signed.apk test.apk lzq)
因为我使用的是跟之前apk 的签名相同的数字签名,所以可以直接安装进手机里,覆盖掉原来的apk,如果使用了与原来反编译前不同的签名,需要先把原来的apk 卸载掉才能顺利安装。
如果安装包名相同,但签名不一样的apk,会提示如下信息
Re-installation failed due todifferent application signatures.
运行重现打包的apk,
嗯,修改成功。
如果,我们需要修改代码逻辑,那又需要怎么做呢?嗯,就得去修改对应的smali 文件了
还是上面的例子,我们尝试再MainActivity.java 里去 把
tv.setText(R.string.hello_world); 改成直接出入一个字符串显示
tv.setText("It have been modified");
找到这个类反编译出来的文件,MainActivity.smali
定位到相关代码块
iget-object v0, p0, Lcom/gdut/smalitest/MainActivity;->tv:Landroid/widget/TextView;//获取tv这个类型为TextView的对象并赋给v0 const v1, 0x7f050002 //给v1 赋值 invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(I)V //实现v0对象的setText方法并传入v1对象
上面的V0,V1等V开头的代表寄存器,P0,P1等P开头的代表参数寄存器
我们对传入的寄存器的值进行修改
iget-object v0, p0, Lcom/gdut/smalitest/MainActivity;->tv:Landroid/widget/TextView; const-string v1, "It have been modified" invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
重新打包签名,运行
上面的例子里,我们是知道源代码的情况下,可以快速定位到 smali 相关的代码处,但是,如果我们只有一个apk,并没有其源代码,也没必要一来就去阅读 smali文件,毕竟smali 语言不太好看,这效率也不高。
我们可以通过dex2jar 工具得出 一个 jar 文件,再通过jd-gui 这个工具去阅读里面的java 代码
具体步骤也好简单,先解压 你的apk,把里面的classes.dex解压出来,然后在命令行里cd 到dex2jar 工具目录下运行
dex2jar.bat 你的classes.dex文件的路径
完成后,就就可以看到在classes.dex 同一个文件夹里,多了一个 classes_dex2jar.jar文件,直接把它拖动到 jd-gui 里就可以查看代码了
现在就可以看到熟悉的 java 代码了(PS:有些apk是被代码混淆过的,所以阅读起来也比较吃力,不过可以结合smali文件一起看,还是可以慢慢理解代码逻辑的)
遇到的问题总结:
1. dex2jar 工具 反编译出来的代码里,会掺杂了好多 while 之类的 循环语句,其他源码里是没有的,观察貌似跟 源码里的 if 语句有关。具体规律可以自行尝试。注意不要被误导
2. 修改smali 文件时,木有语法错误提示,打包编译只要你的代码符合 smali 语法就不会有错,但是这个smali语法没有错,不代表java 语法没有错,所以,如果你修改的smali 代码有java 语法上的错误,会在真机运行时发生fatal,提示java.lang.verifyErro 这个exception,所以当你修改后的apk 发生这个错误,就要去检查你的修改是否有java 上的语法错误了
至此,apk的反编译和修改打包学习就讲完了,下面,我们来拿一个普通的apk 来简单实践一下
背景交代:
某朋友的老板突发奇想,要他们的手机上安装公司的打开app,要通过GPS 定位来实现每天回公司打卡。这让我朋友这位天天迟到的主懊恼不已,来跟我吐槽。
他问我能否帮忙破解一下android版本的打开客户端,我表示尽力而为。
总结一下:
Android客户端情况:名字:xxx公司圈,开发机构:xxx外包公司
实现目标:不管在哪里打开定位,都会是公司的地理坐标。
首先,自己去试着操作一次打卡,看整个流程是登录后,点打卡的选项,然后弹出一个一直转的Loading 框,转完了就会弹出显示当地时间跟位置的对话框
手机连接 DDMS,用 Hierarchy View 工具查看当前的activity
接着我们用 apktool 工具 跟 dex2jar 工具分别反编译出 smali 跟 jar 文件。
代码的包结构如下:
这个app 的定位应该是用 百度地图的 API,那应该有个实现 BDLocationListener 接口的类,里面返回 BDLocation 类型,从中获得位置信息。而从上面可以知道,打卡对应的activity 是 com.cmp.mobile.attence.activity.AttenceActivity.java, 从这两方面去分析打卡具体流程。
中间过程略
分析结果:
继承 Application 类的 com.cmp.mobile.app.App.java 里的内部类 MyLocationListenner实现了 BDLocationListener接口
public class MyLocationListenner implements BDLocationListener { public MyLocationListenner() { } public void onReceiveLocation(BDLocation paramBDLocation) { if (paramBDLocation == null); do { do { return; App.this.posY = Double.valueOf(paramBDLocation.getLatitude()); App.this.posX = Double.valueOf(paramBDLocation.getLongitude()); App.this.address = paramBDLocation.getAddrStr(); } while (App.this.activeActivity == null); try { BaseActivity localBaseActivity = (BaseActivity)App.this.activeActivity; if ((!App.this.isAuto) && ((localBaseActivity instanceof AttenceActivity))) { localBaseActivity.locationSucCallBack(); return; } } catch (Exception localException) { localException.printStackTrace(); return; } } while (!App.this.isAuto); new ComMainActivity().autoLocationSucCallBack(); } public void onReceivePoi(BDLocation paramBDLocation) { if (paramBDLocation == null); } }
当定位完后,把返回的BDLocation 里的经纬度 和街道 信息分别放入 App.java 里的 posX,posY,address 三个public变量,然后回调 AttenceActivity.java 的 locationSucCallBack() 方法,里面通过 GetShortDistance()方法,用 App.posX 和App.posY 去跟 公司的 经纬度做距离计算 如果距离小于com.cmp.mobile.app.model.OccupierData.java 里的 distance 值,就把 App.address 值设为 "公司",不然就设为 百度地图API 返回的街道名
(AttenceActivity.java 的逻辑,我们直接查看 对应的 smali 文件)
invoke-static/range {v0 .. v7}, Lcom/cmp/mobile/attence/activity/AttenceActivity;->GetShortDistance(DDDD)D //计算现在的地点跟公司的距离 move-result-wide v9 .line 232 .local v9, length:D iget-object v0, p0, Lcom/cmp/mobile/attence/activity/AttenceActivity;->app:Lcom/cmp/mobile/app/App; iget-object v0, v0, Lcom/cmp/mobile/app/App;->occupierData:Lcom/cmp/mobile/app/model/OccupierData; iget-object v0, v0, Lcom/cmp/mobile/app/model/OccupierData;->distance:Ljava/lang/Integer; invoke-virtual {v0}, Ljava/lang/Integer;->intValue()I move-result v0 int-to-double v0, v0 cmpg-double v0, v9, v0 if-gtz v0, :cond_0 .line 233 iget-object v0, p0, Lcom/cmp/mobile/attence/activity/AttenceActivity;->app:Lcom/cmp/mobile/app/App; const-string v1, "\u516c\u53f8" //把 address 设成 公司 iput-object v1, v0, Lcom/cmp/mobile/app/App;->address:Ljava/lang/String; .line 235 :cond_0 iget-object v0, p0, Lcom/cmp/mobile/attence/activity/AttenceActivity;->app:Lcom/cmp/mobile/app/App; iget-object v0, v0, Lcom/cmp/mobile/app/App;->address:Ljava/lang/String; invoke-virtual {v8, v0}, Lcom/cmp/mobile/attence/view/AttenceDialog;->setAddress(Ljava/lang/String;)V //把address 设置成API返回的街道名
然后在打卡的对话框里,按下确定,就会new 一个 com.cmp.mobile.attence.listener.SaveListener 把 App.posX,App.posY,App.address 全都打包发给服务器
public SaveListener(DataLoadDelegate paramDataLoadDelegate, String paramString1, String paramString2, Integer paramInteger, Double paramDouble1, Double paramDouble2) { super(paramDataLoadDelegate, "SaveListener"); this.address = paramString1; this.remark = paramString2; this.type = paramInteger; this.posX = paramDouble1; this.posY = paramDouble2; } protected Object parseJSON(Object paramObject) throws JSONException { return null; } public void send() { String str = HttpUtils.makeUrl("/attence/operate.do?op=save", new Object[0]); HashMap localHashMap = new HashMap(); localHashMap.put("posX", this.posX); localHashMap.put("posY", this.posY); localHashMap.put("address", this.address); localHashMap.put("remark", this.remark); localHashMap.put("type", this.type); addPostRequest(str, localHashMap, 0, this); }
需要关注的流程,就是这些。然后,思考一下如果用最少的修改量来达到目的。
我想到的最简单的办法是,把百度地图定位的返回参数BDLocation.java 里的 经纬度跟街道名给写死成公司的经纬度就好,怎么get 都是返回公司的经纬度 和 街道名
然后,开始修改
定位到 BDLocation.smali 文件,把getLongitude() 函数改成
.method public getLongitude()D .locals 2 const-wide v0, 0x40441647a9e2bcf9L //<span style="font-family: Arial, Helvetica, sans-serif;">赋给他一个double值 </span><span style="font-family: Arial, Helvetica, sans-serif;">116.457546</span><span style="font-family: Arial, Helvetica, sans-serif;"></span> return-wide v0.end method
把 getLatitude()函数改成
.method public getLatitude()D .locals 2 const-wide v0, 0x405d1d486f049a99L //赋给他一个double值 <span style="font-family: Arial, Helvetica, sans-serif;">40.174062</span> return-wide v0.end method
把 getAddrStr()函数改成
# virtual methods.method public getAddrStr()Ljava/lang/String; .locals 1 const-string v0, "\u5f3a\u54e5\u5df2\u7ecf\u628a\u8fd9\u4e2a\u5b9a\u4f4d\u4e3a \u4e1c\u7ecf:116.457546\uff0c\u5317\u7eac:40.174062" //暂时把这句话改成“强哥已经把这个定位为 东经:116.457546 北纬:40.174062” return-object v0.end method
PS:强哥不是我
然后又是重现打包,签名。再在真机上运行查看
嗯,修改有效,而且通过抓Tcp包对比普通的打卡,发上去的post 命令内容 是一样的。
大功告成
0 0
- Android apk 反编译学习记录
- Android-APK反编译学习
- android apk的反编译学习
- Android学习必经之路--apk反编译
- Android学习笔记-APK反编译
- APK反编译——学习记录
- Android apk反编译学习【天天酷跑】
- Android开发学习笔记:反编译APK文件
- android APK反编译及混淆学习总结
- Android学习笔记----反编译APK文件
- android。apk反编译学习,精简实用版
- Android apk安全 反编译及防反编译 简单记录
- Mac上反编译Android-apk傻瓜式记录。
- 反编译Android apk文件
- 反编译ANDROID APK文件
- Android APK 反编译
- Android 反编译 .apk 文件
- 反编译android apk
- 网络各层功能职责——计算机网络
- 结构体定义 typedef struct 用法详解和用法小结
- bestCoder 2015 百度之星程序设计大赛 资格赛1004放盘子
- spring面试题
- 理想的程序员与平庸的程序员
- Android apk 反编译学习记录
- Javascript继承两种形式详解
- 应用便签读书法把知识拆为已用
- Java基础 数组_排序,查找,进制转换,内存结构
- 量子力学第十一弹——变分法
- 深入理解Java对象序列化
- 初学者dos界面编译运行Java程序
- JOptionPane类提示框的一些常用的方法
- bestCoder 2015 百度之星程序设计大赛 资格赛 1006单调区间