# Android SO逆向2-实例分析

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-11-16T04:15:00Z
Canonical: https://wepostx.com/topics/1181

> 乌云历史资料归档
>
> **原始作者：** 3xpl0it
> **原始编号：** superkieran-wooyundrops:891
> **原始发布时间：** 2015-11-16 12:15
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

# 0x00 概述

[TOC]
这篇文章主要介绍几个简单的例子，来逆向分析so，当然有些破解可以在smali里直接修改，但是这里均采用逆向so的方式。

# 0x01 ARM—HEX转换
1.将ARM指令转换为Hex时，可以使用工具Arm_Asm，如下图：

![原文图片](/media/2016/06/17aa31ff65e900a0a47d7aa66ebfee58)

2.可以根据ARM参考手册，手动转换。比如add的指令格式如下：

![原文图片](/media/2016/06/b2ff9f6324237e63c676b9874799f80b)

当执行add r0,#1时，Rdn为0，imm8为1，所以二进制为00110000 00000001，转换为16进制就是3001，所以Hex code就是0130
3.百度文库上有人上传了一份整理好的thumb指令集，如果不想到ARM参考手册上找，可以看看这个。

```text
v is immed_value n is Rn m is Rm s is Rs r is register_list c is condition 表一：按指令字母升序排列 0100 0001 01mm mddd -- ADC Rd,Rm 0001 110v vvnn nddd -- ADD Rd,Rn,#immed_3 0011 0ddd vvvv vvvv -- ADD Rd,#immed_8 0001 100m mmnn nddd -- ADD Rd,Rn,Rm 0100 0100 hhmm mddd -- ADD Rd,Rm h1h2,h1 is msb for Rd,h2 is msb for Rm 1010 0ddd vvvv vvvv -- ADD Rd,PC,#immed_8*4 1010 1ddd vvvv vvvv -- ADD Rd,SP,#immed_8*4 1011 0000 0vvv vvvv -- ADD SP,#immed_7*4 0100 0000 00mm mddd -- AND Rd,Rm 0001 0vvv vvmm mddd -- ASR Rd,Rm,#immed_5 0100 0001 00ss sddd -- ASR Rd,Rs 1101 cccc vvvv vvvv -- Bcc signed_immed_8 1110 0vvv vvvv vvvv -- B signed_immed_11 0100 0011 10mm mddd -- BIC Rd,Rm 1011 1110 vvvv vvvv -- BKPT immed_8 111h hvvv vvvv vvvv -- BL(X) immed_11 0100 0111 1hmm mSBZ -- BLX Rm 0100 0111 0Hmm mSBZ -- BX Rm 0100 0010 11mm mnnn -- CMN Rn,Rm 0010 1nnn vvvv vvvv -- CMP Rn,#immed_8 0100 0010 10mm mnnn -- CMP Rn,Rm 0100 0101 hhmm mnnn -- CMP Rn,Rm 0100 0000 01mm mddd -- EOR Rd,Rm 1100 1nnn rrrr rrrr -- LDMIA Rn!,reg_list 0110 1vvv vvnn nddd -- LDR Rd,[Rn+#immed_5*4] 0101 100m mmnn nddd -- LDR Rd,[Rn,Rm] 0100 1ddd vvvv vvvv -- LDR Rd,[PC+#immed_5*4] 1001 1ddd vvvv vvvv -- LDR Rd,[SP,#immed_8*4] 0111 1vvv vvnn nmmm -- LDRB Rd,[Rn,#immed_5*4] 0101 110m mmnn nddd -- LDRV Rd,[Rn,Rm] 1000 1vvv vvnn nddd -- LDRH Rd,[Rn,#immed_5*2] 0101 101m mmnn nddd -- LDRH Rd,[Rn,Rm] 0101 011m mmnn nddd -- LDRSB Rd,[Rn,Rm] 0101 111m mmnn nddd -- LDRSH Rd,[Rn,Rm] 0000 0vvv vvmm mnnn -- LSL Rd,Rm,#immed_5 0100 0000 10ss sddd -- LSL Rd,Rs 0000 1vvv vvmm mddd -- LSR Rd,Rm,#immed_5 0100 0000 11ss sddd -- LSR Rd,Rs 0010 0ddd vvvv vvvv -- MOV Rd,#immed_8 0001 1100 00nn nddd -- MOV Rd,Rn 0100 0110 hhmm mddd -- MOV Rd,Rm 0100 0011 01mm mddd -- MUL Rd,Rm 0100 0011 11mm mddd -- MVN Rd,Rm 0100 0010 01mm mddd -- NEG Rd,Rm 0100 0011 00mm mddd -- ORR Rd,Rm 1011 110R rrrr rrrr -- POP reg_list 1011 010R rrrr rrrr -- PUSH reg_list 0100 0001 11ss sddd -- ROR Rd,Rs 0100 0001 10mm mddd -- SBC Rd,Rm 1100 0nnn rrrr rrrr -- STMIA Rn!,reg_list 0110 0vvv vvnn nddd -- STR Rd,[Rn,#immed_5*4] 0101 000m mmnn nddd -- STR Rd,[Rn,Rm] 1001 0ddd vvvv vvvv -- STR Rd,[SP,#immed_8*4] 0111 0vvv vvnn nddd -- STRB Rd,[Rn,#immed_5] 0101 010m mmnn nddd -- STRB Rd,[Rn,Rm] 1000 0vvv vvnn nddd -- STRH Rd,[Rn,#immed_5*2] 0101 001m mmnn nddd -- STRH Rd,[Rn,Rm] 0001 111v vvnn nddd -- SUB Rd,Rn,#immed_3 0011 1ddd vvvv vvvv -- SUB Rd,#immed_8 0001 101m mmnn nddd -- SUB Rd,Rn,Rm 1011 0000 1vvv vvvv -- SUB Sp,#immed_7*4 1101 1111 vvvv vvvv -- SWI immed_8 0100 0010 00mm mnnn -- TST Rn,Rm 表二：按指令机器码升序排列 0000 0vvv vvmm mnnn -- LSL Rd,Rm,#immed_5 0000 1vvv vvmm mddd -- LSR Rd,Rm,#immed_5 0001 0vvv vvmm mddd -- ASR Rd,Rm,#immed_5 0001 100m mmnn nddd -- ADD Rd,Rn,Rm 0001 101m mmnn nddd -- SUB Rd,Rn,Rm 0001 110v vvnn nddd -- ADD Rd,Rn,#immed_3 0001 111v vvnn nddd -- SUB Rd,Rn,#immed_3 0001 1100 00nn nddd -- MOV Rd,Rn 0010 0ddd vvvv vvvv -- MOV Rd,#immed_8 0010 1nnn vvvv vvvv -- CMP Rn,#immed_8 0011 0ddd vvvv vvvv -- ADD Rd,#immed_8 0011 1ddd vvvv vvvv -- SUB Rd,#immed_8 0100 0000 00mm mddd -- AND Rd,Rm 0100 0000 01mm mddd -- EOR Rd,Rm 0100 0000 10ss sddd -- LSL Rd,Rs 0100 0000 11ss sddd -- LSR Rd,Rs 0100 0001 00ss sddd -- ASR Rd,Rs 0100 0001 01mm mddd -- ADC Rd,Rm 0100 0001 10mm mddd -- SBC Rd,Rm 0100 0001 11ss sddd -- ROR Rd,Rs 0100 0010 00mm mnnn -- TST Rn,Rm 0100 0010 01mm mddd -- NEG Rd,Rm 0100 0011 00mm mddd -- ORR Rd,Rm 0100 0010 10mm mnnn -- CMP Rn,Rm 0100 0010 11mm mnnn -- CMN Rn,Rm 0100 0011 01mm mddd -- MUL Rd,Rm 0100 0011 10mm mddd -- BIC Rd,Rm 0100 0011 11mm mddd -- MVN Rd,Rm 0100 0100 hhmm mddd -- ADD Rd,Rm h1h2,h1 is msb for Rd,h2 is msb for Rm 0100 0101 hhmm mnnn -- CMP Rn,Rm 0100 0110 hhmm mddd -- MOV Rd,Rm 0100 0111 0Hmm mSBZ -- BX Rm 0100 0111 1hmm mSBZ -- BLX Rm 0100 1ddd vvvv vvvv -- LDR Rd,[PC+#immed_5*4] 0101 000m mmnn nddd -- STR Rd,[Rn,Rm] 0101 001m mmnn nddd -- STRH Rd,[Rn,Rm] 0101 010m mmnn nddd -- STRB Rd,[Rn,Rm] 0101 011m mmnn nddd -- LDRSB Rd,[Rn,Rm] 0101 100m mmnn nddd -- LDR Rd,[Rn,Rm] 0101 101m mmnn nddd -- LDRH Rd,[Rn,Rm] 0101 110m mmnn nddd -- LDRV Rd,[Rn,Rm] 0101 111m mmnn nddd -- LDRSH Rd,[Rn,Rm] 0110 0vvv vvnn nddd -- STR Rd,[Rn,#immed_5*4] 0110 1vvv vvnn nddd -- LDR Rd,[Rn+#immed_5*4] 0111 1vvv vvnn nmmm -- LDRB Rd,[Rn,#immed_5*4] 0111 0vvv vvnn nddd -- STRB Rd,[Rn,#immed_5] 1000 0vvv vvnn nddd -- STRH Rd,[Rn,#immed_5*2] 1000 1vvv vvnn nddd -- LDRH Rd,[Rn,#immed_5*2] 1001 0ddd vvvv vvvv -- STR Rd,[SP,#immed_8*4] 1001 1ddd vvvv vvvv -- LDR Rd,[SP,#immed_8*4] 1010 0ddd vvvv vvvv -- ADD Rd,PC,#immed_8*4 1010 1ddd vvvv vvvv -- ADD Rd,SP,#immed_8*4 1011 0000 0vvv vvvv -- ADD SP,#immed_7*4 1011 0000 1vvv vvvv -- SUB Sp,#immed_7*4 1011 010R rrrr rrrr -- PUSH reg_list 1011 110R rrrr rrrr -- POP reg_list 1011 1110 vvvv vvvv -- BKPT immed_8 1100 0nnn rrrr rrrr -- STMIA Rn!,reg_list 1100 1nnn rrrr rrrr -- LDMIA Rn!,reg_list 1101 cccc vvvv vvvv -- Bcc signed_immed_8 1101 1111 vvvv vvvv -- SWI immed_8 1110 0vvv vvvv vvvv -- B signed_immed_11 111h hvvv vvvv vvvv -- BL(X) immed_11
```

# 0x02 示例

先对上篇文章中的switch分支进行逆向。
getSwitch()的代码如下：

```text
JNIEXPORT jint JNICALL Java_com_example_hellojni_GetSwitch_getSwitch (JNIEnv *, jclass, jint a, jint b, jint i){ switch (i){ case 1: return a + b; break; case 2: return a - b; break; case 3: return a * b; break; case 4: return a / b; break; default: return a + b; break; } }
```
1.逆向APK后，使用IDA打开libs目录下的libHelloJNI.so，alt+t找到GetSwitch函数

![原文图片](/media/2016/06/10ab2db5a036f7a093aacb2df4aa950b)

![原文图片](/media/2016/06/1622b1c619d96ba947e3866d56dc22bf)

2.当我们传入的参数是4，2，2是，执行的是a - b,所以返回的结果是2

![原文图片](/media/2016/06/c320da3db68c37c16ede16c3725a83c1)

3.现在，我们将a - b改为a + b，就是要返回结果为6，找到i=2的减法分支，计算add ro,r2,r3的hex为D018

![原文图片](/media/2016/06/0e524459b1c7caa8dfcdec5e29db7e17)

4.然后切换到Hex View，修改Hex为D018，再回到图形模式时，可以看到SUBS R0,R2,R3已经更改为ADDS R0,R2,R3了

![原文图片](/media/2016/06/c781f92e23a1f40a205679f98efd6a94)

![原文图片](/media/2016/06/8531e6d31416b0c49bdbdfbc41e61bfa)

5.使用010editor，ctrl+g跳转到104A的地址，修改Hex为D018，然后保存。

![原文图片](/media/2016/06/bb0c8b2c920cf0c15fc2a112dfb6195d)

6.重新编译成APK，运行时，发现结果已经变为6了。

![原文图片](/media/2016/06/fbda1e5beceb86e196062c4dd4f9ed0e)

# 0x03 内购破解

1.正常情况下，购买金币时，是需要付费的，因为使用的是模拟器，所以显示付费失败。

![原文图片](/media/2016/06/727d6b0afc86c158911ac40c6a573cc7)

2.从smali中找到相应的so，IDA打开。找到函数GameCoin，先F5看一下，看到关键点GameMain::GameCoin = v3 + v1,就是当前游戏的金币数量，对应的汇编为：ADDS R0,R0,R6

![原文图片](/media/2016/06/3b4068931a3b0b36cce3d0866b363678)

![原文图片](/media/2016/06/328fa8749d723bef13fc8331ea88f064)

3.我们知道R0是返回值，R6是计算出的当前金币数，所以我们把此处改为ADDS R0,#255，所以每次都会增加255个金币，Hex为FF30

![原文图片](/media/2016/06/818d01e10b99c9f19d00a6f781730a82)

![原文图片](/media/2016/06/f832dee9cbc779993bf9fbd8c25c03e5)

4.修改后，初始金币为10，点击一次后，增加了255个。

![原文图片](/media/2016/06/a2d3211f49a9fa6ef45a05f72b82e857)

![原文图片](/media/2016/06/410ce573cef879e4d272cb742826cf6e)

# 0x04 加密解密

这篇文章是从吾爱破解([http://www.52pojie.cn/thread-396463-1-1.html](http://www.52pojie.cn/thread-396463-1-1.html))看到的，作者是adslxyz。
准备工具：

- IntelliJ IDEA

- baksmali

- AndroidKiller

- IDA6.6

- Fiddler

- 一台Android真机(或者模拟器)

- eclipse
手机连上wifi，与电脑处于同一局域网，然后设置好Fiddler，具体步骤不详述。打开宝宝树孕育6.2.1版本，进入登陆窗口，输入账号密码点登陆，在电脑查看封包。可以得到请求URL: http://www.babytree.com/api/muser/login

```text
POST：android_id=d77c45cb6182db0e&build_serial=mmmm&local_ts=1438682239&source_channel=pc_pregnancy_tongyong_app_140729&longitude=122.535462&latitude=35.582332&phone_number=buROv8rMh50AdCj7mYdAZwWv9YEpsIR2mClGrO43QK6E5GHko7aEZwtZMT%2F2nvyuUCYsFtx%2Bkz1kwpthbRlpZecGXjkbyaaeIE4ocYjolDyLXLduydRCjnG%2Bk9OEfBr3QCShaiJ2Z%2BeDQIeqgTssdlzZqzFfpHzsg3oWpdF3RbMLF3O93RzGWntJgkgvzjzK7BfwvRF8p8o6Se2l7mX7malpYrn35H2kh4mEAaLaBGoj2naCxcSXCn6X6w9za6hofkhogCtB7PpY6pulzY%2BOhejwG1jdoCpqRxcctRcsFPWNz72SD8Mmqkq6wRVeLYX5ZIoWNafwEJuPobuRtmvhQzg%3D%3D&version=6.2.1&imei=90000451032112&secret=b8a310e750c8af5187197c279a7e0241&email=zLkZURsmEpT8fGCGGNADu2KAZ79zkZI3b%2FMBDh5i1vCAbvFmKoGu7L4nBPsOvnzzsnWtuOJipiITnoRQ5w%2FNTy%2FvEx%2BX%2FO8Ff8jcU2qBAXTzGgd0x1lZ8PsGuan0944ax5n1douoaTC370Hv4A%2F58IhP8rhcQWnO4FzFol8FC%2FXQ%2Fd1%2FoAQOycwdLeBUm0JJ5tRKiaT2nBwD1z7Dn1CShswWCNN2zDCvYgtJ7Ig3JIQipUahL9OaR%2FQnzUzk%2BmaC%2Fm6iRr1q4wkiz9lexJkXFHf1g5VuFqshCNgxdgeDw%2BZHuagedfj5MakZD96Lj%2BtZtRf6GhaLbY29tSL65EdSgsg%3D%3D&password=AhOxbuypr6JYDgpP%2Fw%2BLhVlfzwusSMOaE0137YBOvOAZkk%2BbCHwzfduzqsjasx6zrhZUkZv3aoZ27HDIFAebdgsRS9lh2eeuxXiIxZdkaVPy47UNMdLHo1fFnuUEOCbE144H%2FL%2B2EeY%2BjbE8Ebjg0gXuZ7ziNZ%2Bmrgt4%2FrN8YbizcbyCx7A%2Fue3GD3p58GV3ztnlxWp9d4D1IlMT5V%2B8qxi3VDpQMr0Ghi4MaDTkH%2FV8fcRSCBqutHTDsEs%2F5AfBgksHFT6Dcxl3tpBIIfdgEmU4EZ%2BpqLqVxVFLdjPA0MJOhzE36P8NqTPiKEXzTuJcmaaS5qU34d2dHGYP2wc7Dg%3D%3D&client_type=android&app_id=pregnancy&mac=5c%3A51%5A57%3A55%3A26%3A4d&client_baby_status=1&bpreg_brithday=2100-01-01
```
分析可知：phone_number,password,email,secret四个参数为加密，其他参数均为固定或者地理位置信息等参数。所以此次目标就为这4个参数

- 先把apk拖入AndroidKiller进行分析，可知无壳。

- 根据先前分析，可在AndroidKiller中搜索相应字符串查找我们感兴趣的函数搜索muser\login,可知登陆的函数在com.babytree.platform.api.muser中的login.smali中。由图可知Login.java的父类是ApiBase,可以猜想,所有Api的请求所公共的函数都会在此类中生成。关键地方找一个就行,本次分析主要还是以动态调试为主.

- 用baksmali将apk反编译成.smali文件

- 然后打开IntelliJ IDEA,创建一个java工程.将上一步反编译出的smali文件导入到src文件夹中。如图:

![原文图片](/media/2016/06/66851bf81b4333e5318888873dcc62ff)

- 然后打开eclipse，切换到DDMS视图,选中手机,查看其端口,如图:

![原文图片](/media/2016/06/c76d26fe3f766724dbaeb68e2f9fa0d3)

图上可以看到,目标应用的端口为8631.

- 切换到IntelliJ IDEA,选择Run-Configurations.

- 然后新建一个Remote,填写端口为8631,然后选择source using module's classpath为我们之前创建的项目,点击OK

- 接下来在IntelliJ IDEA中打开Login.smali,快速浏览一下,选择一个感兴趣的地方下断点,这里我选择.method protected a()Ljava/lang/String;在函数起始处下断点后,在手机中输入账号密码,点击登陆,可以看到断点成功断下,如图所示:

![原文图片](/media/2016/06/a0cfc9b9028d4676a966b4e443669b88)

从图中可以看到,左下角为函数的调用堆栈,从 登陆按钮 按下(OnClick),到api.user.login整个的调用过程。展开右下角的p0,可以看到此时已经生成了3个参数,

![原文图片](/media/2016/06/aa774fde88161de5eb24d03a00c1bb74)

说明在断点之前,参数已经生成好了。所以我们按照函数的调用栈,挨个查看调用过的函数。查阅一番后可以发现。Login对象是在LoginActivity中的.method public a(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V方法创建的。在new-instance v0, Lcom/babytree/platform/api/muser/Login;处下断点，重新点击登陆按钮，如图:

![原文图片](/media/2016/06/e4f8a40e880e8b378119d5e009c731e5)

此时v1.v2.v3寄存器分别出现了加密的结果，由上可知，分别对应phone_number,password,email三个参数。
所以加密的函数应该就在上面。往上下断点,重新来一次观察一下加密的过程。
可知：

```text
iget-object v0, p0, Lcom/babytree/apps/pregnancy/activity/LoginActivity;->u:Ljava/lang/String;
```
这一句函数调用之后,v0是一串固定字符串。

```text
invoke-static {p1, v0}, Lcom/babytree/apps/pregnancy/h/a;->a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
```
调用之后,v1为加密结果。查看a函数:

![原文图片](/media/2016/06/bee819778ed8aedcf4e3d2bab04b4a67)

为RSA加密，所以之前的v0就是RSA的pubKey了。在a函数中下断点，看一看加密的参数是什么:

![原文图片](/media/2016/06/00e085b2d570280af7929ad369ed1745)

一切尽收眼底。由此可知phone_number,password,email三个参数的算法。

- 接下来找secret的算法。
回到AndroidKiller搜索关键字，定位到com/babytree/platform/api/ApiCommonParams;大致看一下，这个是API通用参数生成的类。且加载了一个so：api_encrypt.so在secret关键字附近看一下，调用了so的invoke-static {v1, v2}, Lcom/babytree/platform/api/ApiCommonParams;->nativeGetParam(Landroid/content/Context;Ljava/util/List;)Ljava/lang/String;方法。
我们先在这句上面下个断点，看看传给so函数的是什么参数。断点断下后，看到传递给so的参数是一个List,POST参数中除secret之外的所有参数。参数知道了，调用点知道了。分析一下so，看看需不需要动态调试。载入ida，找到调用的nativeGetParam函数。大致看一下，发现又调用了java的md5函数，好了，调试so的步骤剩下了。

![原文图片](/media/2016/06/701a00a0cd2c7f3026eb9dd2ebe02de3)

回到IntelliJ IDEA,在getMD5Str处下断点,继续调试:

![原文图片](/media/2016/06/864d158451e704970f2b2e2c45eca3f0)

很快我们可以知道,p0中就是进行md5的参数啦。
分析一下可知，secret就是 MD5(时间戳+所有参数排序+&asdf12341dfas!@#$%()(Ujjlasdflasdfj;asjdf23412313kljajsdflasjdflasjdflajsdf;lajsdf2342234sdfsdfffds)

总结:
动态调试apk的方式让我们了解apk运作的过程，通过动静结合的分析方式，快速找到想要的东西～

# 0x05 签名校验

将关键代码放到so中，在底层获取签名信息并验证。因为获取和验证的方法都封闭在更安全的so库里面，能够起到一定意义上的保护作用。实例如下：

- 在程序中搜索getSignature,发现并没有调用此函数的地方,猜测在so文件中,搜索loadLibrary。

![原文图片](/media/2016/06/c8e2035461268be874c5ff5ef8932f3b)

- 在代码中可以查找,可以找到调用的是signaturex.so

- 使用IDA打开signaturex.so,然后搜索getSiganture,找到调用此函数的地方。从代码中可以看到,此函数调用了org.cocos2dx.cpp.AppActivity.getSignature

![原文图片](/media/2016/06/f74a276fa9e475740bbdcb2193175f84)

![原文图片](/media/2016/06/9dc3d4c3260088eb01730cd3c57d92ac)

- 查看F5代码,发现此函数是判断签名的函数,然后我们双击此函数的调用者,部分代码如下。

![原文图片](/media/2016/06/fee260e38d26b3108d7f8bd9c9eb14a9)

- 从上图可以看出,只需要修改BEQ loc_11F754,让其不跳转到jjni——>error,就可以绕过签名校验。 查看HEX,使用010editor跳到0011F73E,修改D0为D1。成功绕过签名校验。

![原文图片](/media/2016/06/b093779d5da2c7ba2c44199fdb406274)

## Replies
