安全公司-* * * *-面试题:_ 安卓逆向分析分享

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进制串如下:

"\x33\x33\x33\x32\x33\x31\x36\x33\x36\x32\x36\x31"

                            所以此处的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 数据:\n");
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!\n";
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

posted @ 2019-11-12 17:20  leibso二狗  阅读(1326)  评论(0编辑  收藏  举报