Apk去签名校验详解

某些apk为了防止重打包,使用了签名校验。所以在破解的时候我们需要破解签名校验。在定位签名校验位置时常用的关键词有sign,signature,checkSign,signCheck,getPacka
强烈推介IDEA2021.1.3破解激活,IntelliJ IDEA 注册码,2021.1.3IDEA 激活码  
  某些apk为了防止重打包,使用了签名校验。所以在破解的时候我们需要破解签名校验。在定位签名校验位置时常用的关键词有sign,signature,checkSign,signCheck,getPackageManager,getPackageInfo,verify,same等。
  java层签名校验代码示例:
 1 //原签名信息
 2 private static final String SIGNATURE = "478yYkKAQF+KST8y4ATKvHkYibo=";
 3 private static final int VALID = 0;
 4 private static final int INVALID = 1;
 5 
 6 public static int checkAppSignature(Context context) {
 7     try {
 8         PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),PackageManager.GET_SIGNATURES);
 9 
10         for (Signature signature : packageInfo.signatures) {
11             byte[] signatureBytes = signature.toByteArray();
12             MessageDigest md = MessageDigest.getInstance("SHA");
13             md.update(signature.toByteArray());
14             
15             final String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
16 
17             Log.d("REMOVE_ME", "Include this string as a value for SIGNATURE:" + currentSignature);
18 
19             //compare signatures
20             if (SIGNATURE.equals(currentSignature)){
21                 return VALID;
22             };
23         }
24     } catch (Exception e) {
25         //assumes an issue in checking signature., but we let the caller decide on what to do.
26 
27     }
28     return INVALID;
29 }

只听到从山间传来架构君的声音:
愁肠已断无由醉,酒未到,先成泪。有谁来对上联或下联?

    最近遇到一个进行了签名校验的apk,它是在客户端获取签名信息然后在服务器端进行签名比对的,而且签名的获取是在native代码中实现的。这种签名校验的破解思路一般是在sd卡中保存一个原apk包,修改apk路径,让程序获取原apk的签名信息。修改办法一般有三种,下面一一介绍。

(一)在java层修改context进行破解

  首先安装apk,启动DDMS查看log信息:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
根据这个“verifyHashByC”这个字符串猜测跟签名校验有关系,于是在java层搜索verifyHashByC,没有找到对应的log打印处,只发现同名的native函数调用:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
猜测此log打印信息是在so中,于是找到该native函数对应的so加载处,发现加载的是libAppVerify.so这个动态库:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
用ida打开libAppVerify.so,搜索verifyHashByC,在VerifyHash_BySha函数中发现了log日志打印的代码:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
光看函数名称VerifyHash_BySha就知道是通过sha算法验证Hash,但是这是不是用于签名校验的呢,利用ida的交叉引用查看函数调用关系,发现这个函数在VerifyHash(_JNIEnv *, char *, char const*, int)函数中被调用。而VerifyHash则是由前面提到的native函数verifyHashByC来调用的。
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
VerifyHash函数先获取应用包名,然后调用GetApkMFData函数:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
在该函数中获取应用路径,然后解压应用安装包,读取里面的MANIFEST.MF文件内容,然后再进行签名比对:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
  获取apk路径的示例代码如下:
此代码由Java架构师必看网-架构君整理
1 private static String getApkPath(String pkgName) { 2 PackageManager pm = mContext.getPackageManager(); 3 ApplicationInfo pi = null; 4 try { 5 pi = pm.getApplicationInfo(pkgName,PackageManager.GET_UNINSTALLED_PACKAGES); 6 if(pi != null) 7 return pi.sourceDir; 8 else 9 return null; 10 } catch (NameNotFoundException e) { 11 e.printStackTrace(); 12 return null; 13 } 14 }

sourceDir保存了apk的完整路径:

Apk去签名校验详解

 

Apk去签名校验详解
 
 
 
接下来,解压apk,读取MANIFEST.MF文件:
 
Apk去签名校验详解
 
 
 
 
 
 
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
下图为MF文件中的内容,与上图中内存单元的内容一致:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
 
 
 
 
 
 
  分析了这么多,如何破解呢?通过前面的分析可知在so中获取apk包的路径的流程是:
 
Apk去签名校验详解
 
Apk去签名校验详解
 
 
传入context,通过ApplicationInfo的sourceDir获取到apk路径,如果我们修改sourceDir让它始终指向原apk,那么我们的目的就达到了。
通过反编译java代码,找到调用libAppVerify.so的地方,如图所示,context就是从这里传入的:
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
 
 
 
 
 
我们要做的就是修改这个context,使它最后指向的sourceDir为原apk路径。
 
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  根据上图原理,我们自己实现一个ApkApplicationInfo类指定sourceDir为原apk(SD卡中apk)路径,ApkApplicationInfo在getApplicationInfo函数中实例化,由于getApplicationInfo函数为PackageManager类的成员函数,所以需要自己实现一个ApkPackageManager类(继承自PackageManager类)来调用getApplicationInfo函数。ApkPackageManager类需要在getPackageManager函数中得到实例化,getPackageManager函数为Context的成员函数,所以还需要自己实现一个继承自Context的类ApkContext。
  于是,创建一个Android工程,创建ApkApplicationInfo、ApkPackageManager和ApkContext这三个类,设置sourceDir为“/sdcard/myapk.apk”。编译后,将生成的apk反编译,得到这的三个类的smali代码文件,如下图所示:
 
Apk去签名校验详解
 
Apk去签名校验详解
com.zip
 
 
 
然后,将原apk反编译,把上述三个文件放入反编译后的apk smali文件夹中,在Lcom/rytong/emp/security/AppVerify类的verifyHash函数中插入下述代码,如图所示:
 
Apk去签名校验详解
 
Apk去签名校验详解
 
 
 
 
 
重打包,安装运行,成功!

(二)使用hook技术

  我们知道进行签名校验是在libAppVerify.so中,所以我们只需hook这个so中的函数,更改传入的apk路径就行了。通过分析可知,该so是使用MINIZIP进行apk文件解压缩,然后获取签名信息的。
  使用MINIZIP进行apk文件解压缩的示例代码如下:

 1 void uncrypt_test()
 2 {
 3   //采用MINIZIP进行文件解压缩 
 4   unzFile uf=NULL;
 5     unzFile data[1200];
 6     unz_global_info64 gi;
 7     unz_file_info64 FileInfo;  
 8  
 9     //打开zip文件 
10     uf = unzOpen64("D:\\myfile.zip");
11     int result=unzGetGlobalInfo64(uf, &gi);
12     if (result != UNZ_OK)          
13         throw "文件错误";
14  
15     //循环解压缩文件 
16     for(int i=0;i<gi.number_entry;++i)
17     {
18         if (unzGetCurrentFileInfo64(uf, &FileInfo, 0, 0,NULL,0,NULL,0)!= UNZ_OK)
19              throw "文件错误";
20  
21         if(!(FileInfo.external_fa & FILE_ATTRIBUTE_DIRECTORY)) //文件,否则为目录 
22             //打开文件
23             //result=unzOpenCurrentFile(uf);/* 无密码 */
24             result=unzOpenCurrentFilePassword(uf,"123"); /* 有密码 */
25   
26         //读取内容
27         int size= unzReadCurrentFile(uf,data,sizeof(data));                    
28  
29         //关闭当前文件
30         unzCloseCurrentFile(uf);   
31  
32         //出错
33         if(i < gi.number_entry - 1 && unzGoToNextFile(uf) != UNZ_OK)
34           throw "error";        
35     }
36  
37     //关闭流
38     unzClose(uf);
39 }

  从上述代码可知,解压过程中,apk路径以参数形式传入unzOpen64函数。所以我们需要hook这个函数。但是如何知道libAppVerify.so何时被加载呢?我们知道系统通过dvmLoadNativeCode函数从指定的路径加载so,如果对系统函数dvmLoadNativeCode进行hook,当它加载libAppVerify.so的时候,再hook unzOpen64函数,修改apk路径,不就行了?

  dvmLoadNativeCode函数原型:  

此代码由Java架构师必看网-架构君整理
bool dvmLoadNativeCode(constchar* pathName, Object* classLoader, char** detail)

  这里我们使用cydia substrate这个hook框架,关键代码如下:

Apk去签名校验详解

Apk去签名校验详解

 
将编译生成的so文件放到原apk lib目录下。再通过java层进行加载,加载代码如下:
1     const-string/jumbo v0, "substrate"
2     invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
3     const-string/jumbo v0, "substrate-dvm"
4     invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
5     const-string/jumbo v0, "HookVerify.cy"
6     invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

由于需要在签名校验代码运行之前加载这些so,所以我们在Lcom/cgbchina/xpt/EMPView类的构造函数中添加上述代码,如下图所示:

Apk去签名校验详解
Apk去签名校验详解
重打包签名,安装运行成功!
HookVerify.zip

(3)直接修改so文件

由于签名验证验证的是MANIFEST.MF文件,我们将原apk中MANIFEST.MF文件改名为SIGNFILE.MF放到修改了的apk META-INF文件夹下,然后将so中验证的文件名改为SIGNFILE.MF,如图所示:
Apk去签名校验详解
Apk去签名校验详解
保存,替换原so,重打包安装,运行成功!

 

本文来源bamb00,由架构君转载发布,观点不代表Java架构师必看的立场,转载请标明来源出处:https://javajgs.com/archives/42244
0

发表评论