用户
搜索

该用户从未签到

i春秋-脚本小子

Rank: 2

50

主题

50

帖子

181

魔法币
收听
0
粉丝
2
注册时间
2016-11-28
发表于 2017-1-11 10:20:41 42046
[+] Author: niexinming
[+] Team: n0tr00t security team
[+] Create: 2017-01-10


此次逆向的 apk 为 tmri_10101_1461033981072_release.apk (pass: 3qun) ,首先把 apk 安装在模拟器上,然后抓包分析网络流量,把 apk 拖进模拟器中之后安装并设置代理,虽然 app 走的 http 但是抓到的包全是乱码,所以在内部肯定对流量进行了加密操作。

0x01 jeb 静态分析

用 jeb 打开此 apk 之后查看代码,因为在 app 打开的时候会把系统信息传给服务器,而且访问的链接是 writeDeviceInfo ,经过我的一番查找,居然在 com.tmri.services 下面找到 writeDeviceInfo 这样的函数,右键反编译,打开这个函数发现确实是app发送设备信息的代码:
1.jpg
然后对代码进行查看,发现类x可能是发送请求的代码,然后点击x后打开继续跟进:
2.jpg

看到 /m/deviceInfo/writeDeviceInfo (猜想正确),往下拉继续查看这个类:
3.jpg


发现这样的一个函数,其中 this.d().a(……) 这个函数似乎是在把数据包组装起来准备做发送,然后我注意到这个函数的参数有一个 ((HttpEntity)v2) ,这样的东西,我猜测这个可能是发送的 http body ,然后跟踪 v2 这个变量,发现在上面 v2 = new f(v0_3, ((Map)v1), null); 进行了初始化 现在跟进f类,在f类中找到这样的函数:
4.jpg


d.d(……) 这个函数是要发送的明文,那么下面的 this.a(v2, this.f); 就是加密函数了吧?跟进去之后发现确实是加密的核心函数:
5.jpg


其中arg7传入的明文数据,而arg6是传出的密文数据,这个代码的最关键部分在:
[AppleScript] 纯文本查看 复制代码
byte[] v2 = com.tmri.app.services.packet.c.b(com.tmri.app.services.packet.b.b(arg7, com.tmri.app.services.packet.b$a.values()[this.j]), com.tmri.app.services.packet.c$a.values()[this.k]);  

而v2就是加密之后的数据,继续跟进 com.tmri.app.services.packet.c.b :
6.jpg

跟到这一步之后,静态分析就显得有点吃力了,首先:
[AppleScript] 纯文本查看 复制代码
    com.tmri.app.services.packet.c.b(com.tmri.app.services.packet.b.b(arg7, 
    com.tmri.app.services.packet.b$a.values()[this.j]), 
    com.tmri.app.services.packet.c$a.values()[this.k]);

这个函数中传入的 j 和 k 的值不能确定,进而无法确定这个函数将流量进行了何种的加密方式,所以下面将继续 apk 的动态分析。

0x02 apk 动态分析

这里参考 http://www.52pojie.cn/thread-502219-1-1.html ,首先用 apktool 解开 app,然后修改 AndroidManifest.xml 中:
[XML] 纯文本查看 复制代码
<application android:allowBackup="false" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:name="com.tmri.app.DemoApplication" android:theme="@style/AppTheme" android:debuggable="true">  

把android:debuggable="true" 改成true,随后使用 apktool 打包,再用 signapk.jar 做个签名,最后 baksmali 反编译一下:baksmali tmri_sign.apk -o SmaliDebug/src ,剩下的步骤和之前链接提到的差不多,但这里我们使用的 Android Studio (步骤也是差不多的),下面开始正式开始动态调试。

首先运行 apk 然后在终端里面输入:adb shell ps ,找到apk运行之后的名字,这个apk的名字是:com.tmri.app.main ,然后在 AndroidManifest.xml 找到 application 这个标签,在这个标签下找到 activity 标签。在终端运行 android:name字段,这个字段就是 app 的主 activity ,这个 app 的主 activity 是:com.tmri.app.ui.activity.TmriActivity ,在终端运行:
[AppleScript] 纯文本查看 复制代码
adb shell am start -D -n com.tmri.app.main/com.tmri.app.ui.activity.TmriActivity  

模拟器如下图所示的话说明成功:
7.jpg
再次运行这个 app,在终端中输入 adb shell ps,找到这个app的pid,现在运行的pid是:2166 ,运行: adb forward tcp:8700 jdwp:2166,将调试进程附到 2166 这个进程中,然后在android studio中的src目录下找到刚才找到的加密函数,这个函数在 com.tmri.app.services.packet 的f类中:
8.jpg

阅读smali代码,找到刚才那个加密函数:
9.jpg

在 smali 代码中找到:
10.jpg

然后在这个函数的 iget-byte v4, p0, Lcom/tmri/app/services/packet/f;->k:B 下断点,然后在菜单上点 run-》debug”debug” ,成功后这样的:
11.jpg

现在随便输入一个用户名和密码点登陆,发现 android studio 成功的断了下来:
12.jpg

在右下角的窗口我添加 v0,v1,v2,v3 查看各个寄存器的值,然后按下f8单步运行这个apk ,看到i=0,j=0,k=3 ,再用jeb找到具体的加密函数,看他到底是怎么加密流量的:
13.jpg

这个对应的 smali 在 com.tmri.app.services.packet 的c这个类里面:
14.jpg

我在每一个加密函数的入口加一个断点,看看它会用哪个函数进行加密流量,运行之后看到他运行了:
[AppleScript] 纯文本查看 复制代码
invoke-static {p0, v0},Lcom/tmri/app/services/packet/AesCipherJni;->native_t_set([BI][B  

这个函数,对应的java代码是:
[Java] 纯文本查看 复制代码
arg3 = AesCipherJni.native_t_set(arg3, arg3.length);  

我们可以在在右下角的寄存器中看到进入函数的值是:
15.jpg


到这里就很明显了,它调用了一个动态链接库进行加密,然后把加密结果再传递出来,我们可以通过jeb的静态分析知道这个这个加密函数调用的是哪个动态链接库:
16.jpg

这个库在 lib 目录下,armeabi/armeabi-v7a/x86 这三个目录对应的是cpu的类型,每个 cpu 类型的目录下都有名字一样的 so 文件,因为我的模拟器跑着x86的模拟器里面,所以我要分析的动态链接库在 x86/ libAesCipher-Jni.so 中。

0x03 静态分析 so 文件

使用 IDA 进行分析,终于分析到倒数第二个函数(sub_DB0)的时候我看到了熟悉的东西:
17.jpg
openssl的aes加密函数,其中参数a3,a4就是我想要的aes加密密钥key和iv,但是点进去之后,却看不到任何东西,看来只能动态调试了,这里先把sub_DB0的起始虚拟地址和结束虚拟地址找到,分别为 0000DB0 和 0000EF0 :
18.jpg

0x04 动态分析 so 文件

首先从网上下载 gdbserver 上传到手机里面,然后 adb shell ps 找到 app 的 pid 是 2166:
19.jpg

在 gdbserver 目录下运行 ./gdbserver :23946 --attach 2166 ,再开一窗口运行 adb forward tcp:23946 tcp:23946 ,运行这两个指令之后我就能用本地的 gdb 调试代码了,但是我想用ida pro(破解版,只能在win上跑)提供的方便的功能进行调试,怎么办?于是我就想到了端口转发工具,于是我在网上找的一份这样的[开源代码](http://blog.knownsec.com/2012/02/open-source-rtcp/ ):python rtcp.py l:3333 c:127.0.0.1:23946 ,我实现的调试拓扑:
20.jpg


在远程的 win 打开 ida pro,选择菜单的 debugger->attach->Remote GDB debugger ,然后 houstname 填写地址,port 是监听端口,之后进入:
21.jpg

下面我来寻找动态链接库的基地址,因为我调制的 pid 是 2166 ,所以查看 proc maps 文件来找动态链接库的基地址,在终端中运行 adb shell cat /proc/2166/maps | grep libAes, :
22.jpg

这个程序加载了三次这个动态链接库,但是每个动态链接库的执行权限不同,分别是是r-xp,r—p,rw-p,要调试的动态链接库应该是具有执行权限的,即r-xp,所以调试的基地址是:e2269000,刚才找到的函数的虚拟地址是0000DB0,所以函数在内存中的地址是基地址+虚拟地址:e2269000+0000DB0=e2269db0 ,然后跳到 e2269db0 下断点运行起来:
23.jpg

在手机中输入用户名和密码之后调试器被断住,断点正好在我下断的e2269db0位置,然后先不着急按f8调试,先在里面右键创建函数,然后再右键编辑函数,找到函数结束的地址为刚才找到的函数结束虚拟地址+基地址:e2269000+0000EF0= e2269EF0 进行修改:
24.jpg

点确定后把光标移到函数中,按下 f5 找到加密的函数下断点然后运行起来可以看到程序被成功的断下:
25.jpg

将鼠标指向a3和a4的位置上,查看他们的地址:
26.jpg

然后找到对应的寄存器或者栈地址,将其显示出来:
27.jpg

然后就找到aes加密的key是:
95 8A FA EB CA EF A4 96 EC 7B 7E 97 D0 75 EA 48

iv是:
E0 A4 14 94 34 3A 26 1A 35 64 C6 3C 3A F0 43 57

0x05 END

最后编写流量解密程序来验证拿到的key和iv是不是正确的,先在下面的两个函数上下断点来看加密之后的数据是什么样的:
28.jpg

发现是02 9d 79 2c开头的 23 8a d1 88 先设置burp的拦截,然后运行app,这样burp成功拦下app发出的数据:
29.jpg

然后我们用 .net 写了一个 aes 解密程序来进行破解:
30.jpg

解密程序:
[Python] 纯文本查看 复制代码
using System;  
using System.Collections.Generic;  
using System.Text;  
using System.Security.Cryptography;  
using System.IO;

namespace aes  
{
    class AESHelper
    {
        /// <summary>
        /// AES解密
        /// </summary>
        /// <param name="Data">被解密的密文</param>
        /// <param name="Key">密钥</param>
        /// <param name="Vector">向量</param>
        /// <returns>明文</returns>
        public static String AESDecrypt(String Data, byte[] Key, byte[] Vector)
        {
            Byte[] encryptedBytes = Convert.FromBase64String(Data);
            Byte[] bKey = Key;
            Byte[] bVector = Vector;
            Byte[] original = null; // 解密后的明文

            Rijndael Aes = Rijndael.Create();
            Aes.Mode = CipherMode.CBC;
            Aes.Padding= PaddingMode.Zeros;
            Aes.BlockSize = 128;

            try
            {
                // 开辟一块内存流,存储密文
                using (MemoryStream Memory = new MemoryStream(encryptedBytes))
                {
                    // 把内存流对象包装成加密流对象
                    using (CryptoStream Decryptor = new CryptoStream(Memory,
                    Aes.CreateDecryptor(bKey, bVector),
                    CryptoStreamMode.Read))
                    {
                        // 明文存储区
                        using (MemoryStream originalMemory = new MemoryStream())
                        {
                            Byte[] Buffer = new Byte[1024];
                            Int32 readBytes = 0;
                            while ((readBytes = Decryptor.Read(Buffer, 0, Buffer.Length)) > 0)
                            {
                                originalMemory.Write(Buffer, 0, readBytes);
                            }
                            original = originalMemory.ToArray();
                        }
                    }
                }
            }
            catch(Exception e)
            {
                Console.WriteLine("失败"+e.ToString());
                original = null;
                return null;
            }
            return Encoding.UTF8.GetString(original);
        }
        // 把十六进制字符串转换成字节型
        public static byte[] StringToByte(string InString)
        {
            string[] ByteStrings;
            ByteStrings = InString.Split(' ');
            byte[] ByteOut;
            ByteOut = new byte[ByteStrings.Length];
            for (int i = 0; i < ByteStrings.Length; i++)
            {
                ByteOut[i] = Convert.ToByte("0x"+ByteStrings,16);
         [/i]   }
            return ByteOut;
        }[/color][/size][size=3][color=#000000]
        public static byte[] GetPictureData(string imagepath)
        {
            ////根据图片文件的路径使用文件流打开,并保存为byte[]   
            FileStream fs = new FileStream(imagepath, FileMode.Open);//可以是其他重载方法 
            byte[] byData = new byte[fs.Length];
            fs.Read(byData, 0, byData.Length);
            fs.Close();
            return byData;
        }
        static void Main(string[] args)
        {
            string strkey = "95 8A FA EB CA EF A4 96 EC 7B 7E 97 D0 75 EA 48";
            string striv = "E0 A4 14 94 34 3A 26 1A 35 64 C6 3C 3A F0 43 57";
            byte[] key = StringToByte(strkey);
            byte[] iv = StringToByte(striv);
            byte[] bfile = new byte[2048];
            bfile=GetPictureData(@"C:\Users\hehe\apk\333");

            string pic = Convert.ToBase64String(bfile);

            string ok = AESDecrypt(pic, key, iv);

            Console.WriteLine(ok);
        }
    }
}



最后附上APK地址:
https://pan.baidu.com/s/1slcZDDz
密码:
3qun

发表于 2017-1-11 12:22:13
很详细的讲述了这个从静态调试到动态调试然后拿到APK的AES的加密密钥还有偏移值 不错的一个文章,值得我们学习
楼主幸苦了
使用道具 举报 回复
很完整,如果能够继续跟进渗透会更加精彩,感谢楼主辛苦付出!
使用道具 举报 回复
使用道具 举报 回复
发表于 2017-2-3 10:04:16
使用道具 举报 回复
发新帖
您需要登录后才可以回帖 登录 | 立即注册