逆向加固的apk详细教程

此次逆向的apk为tmri_10101_1461033981072_release.apk
首先把apk安装在模拟器上,然后抓包分析网络流量
把apk拖进模拟器中之后安装:
然后设置代理

逆向加固的apk详细教程_第1张图片

逆向加固的apk详细教程_第2张图片

然后打开apk抓包

逆向加固的apk详细教程_第3张图片

虽然app走的是http,但是抓到的包全是乱码,所以app在内部肯定对流量进行了加密操作
1.用jeb做静态分析
用jeb打开此apk之后查看代码
因为在app打开的时候会把系统信息传给服务器,而且访问的链接是writeDeviceInfo
经过我的一番查找,居然在com.tmri.services下面找到writeDeviceInfo这样的函数
逆向加固的apk详细教程_第4张图片
右键反编译,打开这个函数:
发现确实是app发送设备信息的代码
逆向加固的apk详细教程_第5张图片
然后对代码进行查看,发现类x可能是发送请求的代码,然后点击x后打开继续跟进
逆向加固的apk详细教程_第6张图片
看到/m/deviceInfo/writeDeviceInfo
发现自己的猜想确实正确
往下拉继续查看这个类
逆向加固的apk详细教程_第7张图片
发现这样的一个函数,其中this.d().a(……)这个函数似乎是在把数据包组装起来准备做发送
然后我注意到这个函数的参数有一个((HttpEntity)v2),这样的东西,我猜测这个可能是发送的http body,然后跟踪v2这个变量,发现在上面
v2 = new f(v0_3, ((Map)v1), null);进行了初始化
现在跟进f类
在f类中找到这样的函数:
逆向加固的apk详细教程_第8张图片
d.d(……)这个函数是要发送的明文
那么下面的this.a(v2, this.f);就是加密函数了吧
跟进去之后发现确实是加密的核心函数

其中arg7传入的明文数据,而arg6是传出的密文数据,这个代码的最关键部分在
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
逆向加固的apk详细教程_第9张图片
跟到这一步之后,静态分析就显得有点吃力了,首先
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的动态分析
2.apk动态分析
这里参考的文章是:http://www.52pojie.cn/thread-502219-1-1.html
首先:用apktool解开app,然后修改AndroidManifest.xml中的
把android:debuggable="true" 改成true
然后再用apktool打包
再用signapk.jar做个签名
然后用baksmali反编译一下:
baksmali tmri_sign.apk -o SmaliDebug/src
剩下的步骤和http://www.52pojie.cn/thread-502219-1-1.html里面讲的差不多,但是我用的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,在终端运行:adb shell am start -D -n com.tmri.app.main/com.tmri.app.ui.activity.TmriActivity
然后模拟器中出现:
逆向加固的apk详细教程_第10张图片
说明成功,然后点击关闭这个窗口
然后再运行这个app,在终端中输入adb shell ps,然后找到这个app的pid,我现在运行的pid是:2166,然后运行:
adb forward tcp:8700 jdwp:2166,将调试进程附到2166这个进程中
然后在android studio中的src目录下找到刚才找到的加密函数,这个函数在com.tmri.app.services.packet的f类中
逆向加固的apk详细教程_第11张图片
然后阅读smali代码,找到刚才那个加密函数:

在smali代码中找到在:
逆向加固的apk详细教程_第12张图片

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

成功后这样的:

逆向加固的apk详细教程_第13张图片

现在我的app的状态是:
逆向加固的apk详细教程_第14张图片
随便输入一个用户名和密码点登陆:
发现android studio成功的断了下来
逆向加固的apk详细教程_第15张图片

在右下角的窗口我添加v0,v1,v2,v3查看各个寄存器的值,然后按下f8单步运行这个apk
然后看到i=0,j=0,k=3
然后我再用jeb找到具体的加密函数,看他到底是怎么加密流量的
逆向加固的apk详细教程_第16张图片

这个对应的smali在com.tmri.app.services.packet的c这个类里面:
逆向加固的apk详细教程_第17张图片

我在每一个加密函数的入口加一个断点,看看它会用哪个函数进行加密流量:
运行之后看到他运行了invoke-static {p0, v0}, Lcom/tmri/app/services/packet/AesCipherJni;->native_t_set([BI][B
这个函数,对应的java代码是arg3 = AesCipherJni.native_t_set(arg3, arg3.length);
我可以在在右下角的寄存器中看到进入函数的值是:
逆向加固的apk详细教程_第18张图片
进入后:

逆向加固的apk详细教程_第19张图片
到这里就很明显了,它调用了一个动态链接库进行加密,然后把加密结果再传递出来
我们可以通过jeb的静态分析知道这个这个加密函数调用的是哪个动态链接库:
逆向加固的apk详细教程_第20张图片
这个库在lib目录下

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

用ida pro打开要分析的so文件,找到刚才被调用的函数:n_token_set然后打开,按f5做反编译为类c语言代码

逆向加固的apk详细教程_第21张图片
慢慢分析一下,终于分析到倒数第二个函数(sub_DB0)的时候我看到了熟悉的东西
逆向加固的apk详细教程_第22张图片
openssl的aes加密函数,其中参数a3,a4就是我想要的aes加密密钥key和iv,但是点进去之后,却看不到任何东西,看来只能动态调试了,这里先把sub_DB0的起始虚拟地址和结束虚拟地址找到:分别为:0000DB0和0000EF0
逆向加固的apk详细教程_第23张图片

4.动态分析so文件
首先从网上下载gdbserver,上传到手机里面,然后找到用adb shell ps找到app的pid,我现在的pid是2166
逆向加固的apk详细教程_第24张图片
然后在gdbserver
目录下运行./gdbserver :23946 --attach 2166

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


逆向加固的apk详细教程_第25张图片






然后在远程的win打开ida pro,选择菜单的debugger->attach->Remote GDB debugger
逆向加固的apk详细教程_第26张图片

然后

逆向加固的apk详细教程_第27张图片

houstname 填的是我mac的地址,port是mac监听的端口,点击一直点ok,然后成功进入

逆向加固的apk详细教程_第28张图片

下面我来寻找动态链接库的基地址,因为我调制的pid是2166,所以,我查看proc的maps文件来找动态链接库的基地址,在终端中运行adb shell cat /proc/2166/maps | grep libAes,
逆向加固的apk详细教程_第29张图片
这个程序加载了三次这个动态链接库,但是每个动态链接库的执行权限不同,分别是是r-xp,r—p,rw-p,要调试的动态链接库应该是具有执行权限的,即r-xp,所以调试的基地址是:e2269000,刚才找到的函数的虚拟地址是0000DB0,所以函数在内存中的地址是:基地址+虚拟地址:e2269000+0000DB0= e2269db0
然后跳到e2269db0
逆向加固的apk详细教程_第30张图片
然后下断点,然后运行起来,
逆向加固的apk详细教程_第31张图片
在手机中输入用户名和密码之后,调试器被断住,断点正好在我下断的e2269db0位置,然后,先不着急按f8调试,先在里面右键,创建函数
逆向加固的apk详细教程_第32张图片
然后再右键编辑函数,找到函数结束的地址:为刚才找到的函数结束虚拟地址+基地址:e2269000+0000EF0= e2269EF0,然后修改一下
逆向加固的apk详细教程_第33张图片
点确定,然后把光标移到函数中,按下f5
汇编代码变成了类c的语言,
逆向加固的apk详细教程_第34张图片
然后找到加密的函数下断点,然后运行起来

逆向加固的apk详细教程_第35张图片
看到程序被成功的断下

将鼠标指向a3和a4的位置上,查看他们的地址:
逆向加固的apk详细教程_第36张图片
然后找到对应的寄存器或者栈地址,将其显示出来
逆向加固的apk详细教程_第37张图片
然后就找到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

这样,这个app的破解就差不多了
5.收尾
下面要编写流量解密程序,来验证拿到的key和iv是不是正确的,先在下面的两个函数上下断点来看加密之后的数据是什么样的

逆向加固的apk详细教程_第38张图片
发现是02 9d 79 2c开头并且以 23 8a d1 88结尾的数据
先设置burp的拦截,然后运行app,这样burp成功拦下app发出的数据
逆向加固的apk详细教程_第39张图片
然后保存这段数据为一个文件
逆向加固的apk详细教程_第40张图片

然后我用.net写的一个aes解密程序破解成功,
逆向加固的apk详细教程_第41张图片


解密程序如下

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace aes
{
    class AESHelper
    {
       
       

        /// 
        /// AES解密
        /// 
        /// 被解密的密文
        /// 密钥
        /// 向量
        /// 明文
        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[i],16);
            }
            return ByteOut;
        }



        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);
        }


    }
}


你可能感兴趣的:(软件破解)