zoukankan      html  css  js  c++  java
  • 安全公司-* * * *-面试题:_ 安卓逆向分析分享

    1 总览:

    a)     查看清单文件AndroidManifest注册的各种信息(入口类、权限、广播、服务等)

    l  发现只有入口类信息:

    Ø  com.test.pac.demo.MainActivity

    b)   安装程序查看大致行为

    l  输入UserName(长度为16

    l  PassWord 可以输入,也可以点击Generate 生成

    l  点击Check按钮验证PassWord是否正确

     

    确认目标:分析PassWord生成算法并写出该算法程序,最后验证逆向的算法是否正确。

    2 逆向过程:

    2.1 配置.so 调试环境

           为了更好的观察数据变化,需要配置.so调试环境

    l  配置动态分析so 文件的环境

    手机没有root,额尝试了也root不了,提示没有匹配机型,动态调试so文件失败。。。。。。

    嗯,浪费了很多时间被多篇博客误导了;原来可以直接使用IDA 直接附加夜神模拟器进程动态调试so文件,配置参考:

    https://cloud.tencent.com/developer/article/1357098;但是其中附加方法我变了一下,原文在我机器上行不通;效果如下图

    clip_image002

     

    2.2 分析关键点

    使用反编译工具Jadx 查看反编译代码,确定关键调用:

    clip_image004

    分析:

    1>  首先根据长度信息和控件之间的对应关系,可以确定f7997xUserName编辑框控件、f7996wPassWord编辑框控件

    2>  进一步可以确定getck() 是关键校验算法getbt() 关键密码生成算法

    2.3 密码校验函数getck()

    l  查看该函数信息

    clip_image006

    分析:

           可以看到getck()是一个native 层的函数。PrtUtil.m7581a() 对 输入的PassWord 进行了数据转换:从左到右每两个字符转换成一个16进制数值,再强转此数值为一个字符。例如: “31323368656c6c6f》“123hello

           clip_image008

     

    l  进入so 文件后,发现getck() 函数有4个参数,但是实际是2个参数,,然后就有点小懵(以前还没有分析过)

    clip_image010

    l  重新设定a1 参数类型为JNIEnv* 类型,就清晰很多了

    clip_image012

    再通过查阅网上资料,发现a2 代表返回类型,a3 a4 分别是传入的参数。

     

    l  继续分析 getck()

    a)      SSE向量运算-转换UserName为其Ascii

    clip_image014

    分析:

    Ø  进入getck() 函数之后,该函数对UserName 通过一系列SSE2向量运算计算出一个数据(供后面计算摘要使用);经过分析,这里应该是优化后的代码实际只是求UserName串的Ascii

    例如

     “321cba” Ascii:"333231636261"

    --> 16进制串如下:

    "x33x33x33x32x33x31x36x33x36x32x36x31"

                                所以此处的C代码暂定为:

                                unsignedchar * GetAsAs(char * Dest,intLen)

    {

                        unsignedchar * Ret = (unsignedchar *)malloc(Len * 2);

                        memset(Ret, 0, Len * 2);

                        for (int i = 0; i < Len*2; i+=2)

                        {

                            unsignedchar Tmp = Dest[i/2]>>4& 0x0f|0x30;

                            Ret[i] = Tmp;

                            Ret[i + 1] = Dest[i/2]& 0x0f | 0x30;

                        }

                        return Ret;

    }

    b)     HMAC_sha256 签名

    clip_image016

    分析:

    Ø  然后,将前面计算的数据(UserNameAscii串的HMAC 摘要签名;并将签名信息作为参数(参数还有用户名、常见字符表、密码起始地址+4、密码起始地址)作为参数传入sub_34CC0()》这里我重命名KeyFunc_KKKKK()函数进一步计算。

     

    l  详细分析sub_34cc0() / KeyFunc_KKKKK() 函数
    clip_image018

    分析:

    Ø  可以看到;此函数只是一些OpenSSL函数的调用;简述流程就是创建Ctx对象,使用AES_256_gcm引擎;以UserName作为IV初始向量、前面计算的HMACResult签名作为密钥,解密出密文。(Aes_256_gcm 算法的的参考链接: https://www.cnblogs.com/0616--ataozhijia/p/11271433.html)

     

    l  最后以EVP_DecryptFinal_ex() 返回值作为这次getck() 函数成功与否的标识

    clip_image020

    分析:

    Ø  EVP_DecryptFinal_ex() 成功就返回一个正数,不然就标识着失败;这也验证了反编译出的源码中的调用返回结果的判别getck()>0 == { success;};

    clip_image021

    2.4 密码生成函数getbt()

    l  查看该函数信息:

    clip_image023

    分析:

    Ø  可以看到getbt() 也是一个native层的函数;而PrtUtil.m7580a()是将目标字符串转换成其Ascii16进制格式字符串,例如“123hello》“31323368656c6c6f”。特殊情况:如果字符Ascii16进制<0x10,就会填充一个0;例如:字符Ascii16进制为0x8  -->  08

    clip_image025

     

     

    l  getbt()函数中使用到的全局168240处的64Bytes数据分析

    看到getbt() 一开始就使用了一个全局变量;一全局的8qword = 64Bytes 的值,其起始地址168240,就分析了一下,如下:

    clip_image027

    clip_image029

                  分析:

    Ø  8qword = 64 Bytes的值是由一开始随机产生32 Bytes,还经过了一些计算(3HMAC计算,后面有分析)来填充到这64Bytes的区域的。在JNI_Onload 的时候会调用这个函数。一旦程序加载成功JNI_Onload调用之后;在程序运行过程中,这64Bytes 的数值就是稳定的。(暂时认为这个时候每次程序运行都是不相等的,所以想要编写注册机的话,需要每次动态读取这一块内存,加入密码计算过程)。(也可以使用调试查看数据,但毕竟不适合非专业人员使用)

    Ø  这时候注册机应该会涉及到Android的跨进程内存读写:

    (参考:https://blog.csdn.net/mldxs/article/details/14486827)

     

    Ø  进一步分析根据32Byres随机数据填充该168240位置64Bytes的算法

    clip_image031

    分析:

    n  使用了3HMAC 签名算法,计算出的签名值,用来填充168240开始的64字节区域。

    l  接着计算通过167004位置的cipher表映射+计算出的特征值

    clip_image033

    分析:

    Ø  结合后面的代码,这里的HIWORD(V45) = w_FeatureDigit就是密码的偏移2处的2Byte(这里w_FeatureDigit是重命名的),查看了 上下文,这个特征值在后面HMACMD计算、密码的数据都用到了;所以这块随机数据处理后的值,目前看来不能缺少。

    l  转换UserName为其Ascii

    (前面验证函数有详细分析,这里就不赘述了)

    clip_image035

    l  计算HMAC SHA256签名

    clip_image037

    分析:

    Ø  这里面HMAC就使用到了 前面计算了那64Bytes 的特征值(WORD)

    l  计算UserNameAscii全局映射表中计算出的特征

    clip_image039

    l  创建一份常见字符串文本(从后面代码看,是用来作为AAD数据

    clip_image041

    l  调用过程函数35210() 加密

    clip_image043

    分析:

    Ø  加密了UserNameAscii串经过Map_167004计算的特征值的密文2Byte并传出;然后将16Byte tag值也传出。

     

    l  最后组合密码

    clip_image045

    分析:

    Ø  根据刚才传出的区域EncryptRegion,组合v45为起始的密码区域。

    3 总结归纳编写注册机

    3.1 总结密码的构成分析

                  clip_image047

           解释

           该密码 由3部分组成:

    Ø  第一部分:是由UserNameAscii 串经过 全局的映射表(167004h)计算后的的2Bytes 特征值,使用Aes_256_gcm 加密后的密文2Byte值。

     

    Ø  第二部分:原始数据是JNI_Onload里面调用函数生成32Bytes的随机值复制两份,然后经过3HMAC运算结果填充到168240h处的64Byte;经167004h处的映射表计算,得出的2Byte值。

     

    Ø  第三部分:是经过AES_256_gcm 加密算法后的16Byte Tag

    3.2 总结需读进程内存的数据

    Ø  全局64Bytes数据(168240h起始的)

    解释:此区域数据,每次程序启动调用JNI_Onload函数后,会调用里面的

    35520()函数产生随机32Bytes数据,然后调用35330()函数将这32Bytes的的数据进行3HMAC_SHA256签名计算。将各层MD签名值赋值到168240h起始的64Bytes

     

    Ø  全局CipherTable (167004h 起始的,也可以手工从镜像中提取,但是都到这一步了,顺道就提取了)

    解释:此表在注册机中,在计算中提供了一种映射关系;每次程序启动

    调用JNI_Onload函数的会在168240h起始的64Bytes位置产生新的数据,而映射表,在计算中会因为不同的Bytes值,得到不同的特征值,供后面的HMAC签名运算和密码生成运算。

    3.3 注册机编写思路

    (1) 通过进程读取内存数据(168240h起始的64bytes,也可顺带把那个全局映射表也取出来)

           https://blog.csdn.net/mldxs/article/details/14486827

           步骤:

    a)      通过获取libnative-lib.so 所在模块的数据区段位置,来定位该数据,如下图:

    clip_image049

    分析:该模块具有很明显的特征 :所属libnative-lib.so并且具有rw权限。全局64Bytes 数据距离该区段起始位置的偏移为:1240h;全局映射表所在偏移:4h

    b) 编写程序获取数据(此程序在linux环境或者Cygwin中编译好,放到Android中运行)

                        

    (2) 编写小函数:UserName字符串转换成其Ascii串的Ascii

           如:“123》“333133323333

           代码:

                         

    unsignedchar * GetAsAs(char * Dest,intLen)
    
    {
    
                    unsignedchar * Ret = (unsignedchar *)malloc(Len * 2);
    
                    memset(Ret, 0, Len * 2);
    
                    for (int i = 0; i < Len*2; i+=2)
    
                    {
    
                        unsignedchar Tmp = Dest[i/2]>>4 &0x0f|0x30;
    
                        Ret[i] = Tmp;
    
                        Ret[i + 1] = Dest[i/2] & 0x0f | 0x30;
    
                    }
    
        return Ret;
    
    }

     

     

        // 后面就扣取代码了

    (3) 根据跨进程获取到的全局映射表(167004h)、全局64Bytes数据(168240h

    计算映射特征值,代码:

                 

    (4) 计算 HMACAES_256_gcm

           (需要安装配置openssl )

           代码:

                 

    (6) 得出结果

       

       

    3.4 代码

    注: 这里 还没有使用 进程内存读写,使用了读取出来 ,应该就可以使用下面的代码了

        // OpenSSL_DE_EN.cpp : 此文件包含"main" 函数。程序执行将在此处开始并结束。

      1 //
      2 
      3  
      4 
      5 #include"pch.h"
      6 
      7 #include<iostream>
      8 
      9 #include<openssl/ssl.h>
     10 
     11 #include<openssl/err.h>
     12 
     13 #include<Windows.h>
     14 
     15 #pragmacomment(lib,"libssl.lib")
     16 
     17 #pragmacomment(lib,"libcrypto.lib")
     18 
     19 unsignedchar g_Bytes[64];
     20 
     21 unsignedlonglongint g_4QW[4];
     22 
     23  
     24 
     25 // 获取UserName 的Ascii
     26 
     27 unsignedchar * GetAsAs(char * Dest, intLen)
     28 
     29 {
     30 
     31     unsignedchar * Ret = (unsignedchar *)malloc(Len * 2);
     32 
     33     memset(Ret, 0, Len * 2);
     34 
     35     for (int i = 0; i < Len * 2; i += 2)
     36 
     37     {
     38 
     39         unsignedchar Tmp = Dest[i / 2] >> 4& 0x0f | 0x30;
     40 
     41         Ret[i] = Tmp;
     42 
     43         Ret[i + 1] = Dest[i / 2]& 0x0f | 0x30;
     44 
     45     }
     46 
     47     return Ret;
     48 
     49 }
     50 
     51 // 加密算法
     52 
     53  
     54 
     55 size_t__cdecl Encrypt_sub_35210(unsignedchar* key, unsignedchar * IV_, intIV_len, unsignedchar * UsualChars_AAD, intAAD_Len, unsignedchar * plainText, intplain_Len_2, void *EncryptRegion)
     56 
     57 {
     58 
     59     int CtxObj_; // esi
     60 
     61     int Len_ = 0;
     62 
     63     int v13;
     64 
     65     unsignedchar * cipherText = (unsignedchar *)malloc(0x400);
     66 
     67     EVP_CIPHER_CTX* CtxObj = EVP_CIPHER_CTX_new();               // 创建一个 Ctx
     68 
     69     EVP_EncryptInit_ex(CtxObj, EVP_aes_256_gcm(), 0, 0,0);// 绑定Aes 引擎
     70 
     71     EVP_CIPHER_CTX_ctrl(CtxObj, 9, IV_len, 0);   // 设置IV 的长度
     72 
     73     EVP_EncryptInit_ex(CtxObj, 0, 0, key, IV_);  // 初始化Key密钥 和IV初始化向量
     74 
     75     EVP_EncryptUpdate(CtxObj, 0, &Len_, UsualChars_AAD, AAD_Len);// 提供任意AAD 数据;此方式可调用多次
     76 
     77     EVP_EncryptUpdate(CtxObj, cipherText, &Len_, plainText, plain_Len_2);// 提供需要被加密的文本,以及接收加密后的文本
     78 
     79     memcpy(EncryptRegion, cipherText, Len_);     // 拷贝 前一步2Byte 密文
     80 
     81     EVP_EncryptFinal_ex(CtxObj, cipherText, &v13);// 加密!
     82 
     83     EVP_CIPHER_CTX_ctrl(CtxObj, 16, 16, cipherText);// 获取Aes_256_gcm 此次的 Tag  16Byte
     84 
     85     int v10 = Len_;
     86 
     87     *(unsignedlonglong  *)((char *)EncryptRegion + Len_ + 8) = *((unsignedlonglong  *)&cipherText+1);// 这两步将16Byte 的tag 值输出到EnCryptRegion
     88 
     89     *(unsignedlonglong  *)((char *)EncryptRegion + v10) = *(unsignedlonglong  *)&cipherText;;
     90 
     91     EVP_CIPHER_CTX_free(CtxObj);
     92 
     93     free(cipherText);
     94 
     95     return 0;
     96 
     97 }
     98 
     99  
    100 
    101  
    102 
    103 int main()
    104 
    105 {
    106 
    107     printf("请输入 那64Bytes 数据:
    ");
    108 
    109     for (int i = 0; i < 4; i++)
    110 
    111     {
    112 
    113         scanf_s("%i64x", g_4QW + i);
    114 
    115     }
    116 
    117  
    118 
    119     EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    120 
    121     std::cout <<"Hello World!
    ";
    122 
    123  
    124 
    125     WORD v45[10];
    126 
    127     unsignedchar UserName_Bytes[32];
    128 
    129     //getCiphertTable
    130 
    131     unsignedlonglong  w_167004_MapTable_[1024];                                          // 当程序JNI_Onload()运行后,这随机值就稳定了;这时候输入的UserName 后Generate的密码就是稳定的。
    132 
    133     //get 64Bytes
    134 
    135     unsignedlong data64Bytes; //  通过引用查看 这是一个 随机值,赋值所在函数35520() ;在程序启动JNI_Onload调用的时候会调用35520()函数进行初始化。
    136 
    137     unsignedlonglong *p64Bytes = (unsignedlonglong *)&g_4QW;                                    // 但是重新进入程序,输入相同的UserName会发现这个值不是稳定的。就是因为JNI_ONload里面调用了35520()进行了随机数据赋值。
    138 
    139     int count_64 = 64;
    140 
    141     short  w_FeatureDigit = 0;
    142 
    143     do                                            //  计算出 程序JNIEnv_Onload程序的时候随机出的数据64bytes 通过167004处的全局cipher表;找出的随机数据的特征值
    144 
    145     {                                             // 根据 随机出的数据;查167004处的cipher表计算出特征值
    146 
    147         w_FeatureDigit = w_167004_MapTable_[*(unsignedchar *)p64Bytes ^ ((unsignedint)w_FeatureDigit >> 8)] ^ (w_FeatureDigit << 8);
    148 
    149         p64Bytes = (unsigned__int64 *)((unsignedchar *)p64Bytes + 1);
    150 
    151         --count_64;
    152 
    153     } while (count_64);
    154 
    155     short w_Rand64Bytes_FeatureDigit = w_FeatureDigit;
    156 
    157     v45[1] = (WORD)w_FeatureDigit;
    158 
    159     int v32 = 0;
    160 
    161  
    162 
    163     // 获取UserName 的Ascii
    164 
    165     char Name[] = { "123456789abcdefg" };
    166 
    167     unsignedchar * AscData = GetAsAs(Name, 0x10);
    168 
    169     HMAC_CTX *Ctx = HMAC_CTX_new();// 创建一个 Ctx
    170 
    171     HMAC_Init_ex(Ctx, AscData, 32, EVP_sha256(), NULL);// 创建一个 摘要引擎 EVP_sha256
    172 
    173     HMAC_Update(Ctx, (constunsignedchar *)&w_Rand64Bytes_FeatureDigit, 2);// 此函数 可以重复调用,将数据块加入到 计算结果中
    174 
    175     unsignedint len1 = 0;
    176 
    177     unsignedchar * result = (unsignedchar*)malloc(sizeof(char) * 0x20);
    178 
    179     HMAC_Final(Ctx, result, &len1);
    180 
    181     int v26 = 32;                                    // v45-32 的位置 刚好是 前面计算UserName 的Ascii串
    182 
    183     int v27 = 0;                                      // int16   - Word
    184 
    185     do
    186 
    187         v27 = w_167004_MapTable_[*((unsigned__int8 *)AscData + --v26) ^ ((unsignedint)v27 >> 8)] ^ (v27 << 8);
    188 
    189     while (v26);
    190 
    191     short UserNameMapFeature = v27;
    192 
    193     unsignedchar * UsualCharNum = (unsignedchar *)malloc(0x25);
    194 
    195     memcpy(UsualCharNum, "abcdefghijklmnopqrstuvwxyz0123456789", 0x25u);// 准备AES_GCM的AAD数据
    196 
    197     unsignedchar * EncryptRegion_ = (unsignedchar *)malloc(0x400);
    198 
    199     memset(&EncryptRegion_, 0, 0x400u);
    200 
    201     Encrypt_sub_35210(
    202 
    203         result,
    204 
    205         (unsignedchar *)UserName_Bytes,
    206 
    207         12,
    208 
    209         UsualCharNum,
    210 
    211         37,
    212 
    213         (unsignedchar *)UserNameMapFeature,
    214 
    215         2,
    216 
    217         EncryptRegion_);
    218 
    219     v45[0] = *(WORD *)EncryptRegion_;      // 密码的前2Byte
    220 
    221     *(unsignedlonglong *)&v45[2] = *(unsignedlonglong *)(EncryptRegion_ + 4);
    222 
    223     *(unsignedlonglong *)&v45[6] = *(unsignedlonglong *)(EncryptRegion_ + 12);// 这两步 是复制密码的tag 域16Byte/ / v45、v46、v47 这三个连续的一片区域就是 我们的密码
    224 
    225  
    226 
    227 }
    228 
    229  

    参考资料:

    [1] so文件调试环境搭建:

           https://cloud.tencent.com/developer/article/1357098

    [2] Intel白皮书SSE2相关指令查询:

    https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE2&text=_mm_cmpeq_epi32&expand=773

    [3] HMAC 摘要签名计算:

           https://www.xuebuyuan.com/3193335.html

    http://gmssl.org/docs/evp-api.html

    [4] OpenSSL AES_256_gcm加解密讲解

           https://s0www0openssl0org.icopy.site/docs/man1.1.1/man3/EVP_CipherFinal_ex.html

           http://gmssl.org/docs/evp-api.html

    [5] AES 的多种加解密模式

           https://www.cnblogs.com/0616--ataozhijia/p/11271433.html

    [6] Android 跨进程读取内存数据

           https://blog.csdn.net/mldxs/article/details/14486827

    [7] VS2017 使用OpenSSL

           https://blog.csdn.net/xiejie0226/article/details/80494458

  • 相关阅读:
    牛客 4C Alliances (dfs序)
    AC日记——楼房 codevs 2995
    AC日记——丑数 codevs 1246
    AC日记——砍树 codevs 1388
    AC日记——地鼠游戏 codevs 1052
    AC日记——蓬莱山辉夜 codevs 2830
    AC日记——最小的N个和 codevs 1245
    AC日记——二叉堆练习3 codevs 3110
    AC日记——滑动窗口 洛谷 P1886
    AC日记——忠诚 洛谷 P1816
  • 原文地址:https://www.cnblogs.com/leibso-cy/p/AndroidSecurity.html
Copyright © 2011-2022 走看看