首页 > 代码库 > 360手机助手关于签名校验的分析

360手机助手关于签名校验的分析

360手机助手中软件更新

 

0x01:分析过程

       使用wireshark抓包分析更新时请求的是一个配置文件,请求urlupdate.api.sj.360.cn/mintf/getAppsByPackNames  后面是参数,请求方式为POST,内容如下:

 

可以看到其中包含了几个关键的参数down_urlapk_md5signature_md5size,其中比较关键的一个校验是signature_md5,其他都可以轻松替换,所以我们的重点就是对signature_md5,校验的破解,去分析他对signature_md5加密的方式,以及如何模拟算法,

 

0x02:知识补充

关于安卓apk数字签名校验的知识补充,安卓apk数字校验使用的是rsa非对称加密算法也就是我们常说的公钥私钥加密,这个算法



指数运算谁都懂,不必说了,先说说模运算。模运算是整数运算,有一个整数m,以n为模做模运算,即m mod n。怎样做呢?让m去被n整除,只取所得的余数作为结果,就叫做模运算。例如,10 mod 3=1;26 mod 6=2;28 mod 2 =0等等。 
模指数运算就是先做指数运算,取其结果再做模运算。如
好,现在开始正式讲解RSA加密算法。
算法描述:
(1)选择一对不同的、足够大的素数p,q。
(2)计算n=pq。
(3)计算f(n)=(p-1)(q-1),同时对p, q严加保密,不让任何人知道。
(4)找一个与f(n)互质的数e,且1<e<f(n)。
(5)计算d,使得de≡1 mod f(n)。这个公式也可以表达为d ≡e-1 mod f(n)
这里要解释一下,≡是数论中表示同余的符号。公式中,≡符号的左边必须和符号右边同余,也就是两边模运算结果相同。显而易见,不管f(n)取什么值,符号 右边1 mod f(n)的结果都等于1;符号的左边d与e的乘积做模运算后的结果也必须等于1。这就需要计算出d的值,让这个同余等式能够成立。
(6)公钥KU=(e,n),私钥KR=(d,n)。
(7)加密时,先将明文变换成0至n-1的一个整数M。若明文较长,可先分割成适当的组,然后再进行交换。设密文为C,则加密过程为:
(8)解密过程为:

 

Android的签名机制,通过分析signapk.jar这个可执行包可以知晓签名APK的整个过程

1.程序遍历整个apk文件包中的所有文件(entry),对非文件夹非签名文件的文件,逐个生成SHA1的数字签名信息,再用Base64进行编码,之后将生成的签名写入MANFEST.MF文件

2.对前一步生成的Manifest,使用SHA1-RSA算法,用私钥进行签名,生成CERT.SF文件

3.生成MANIFEST.MF没有使用密钥信息,生成CERT.SF文件使用了私钥文件。那么我们可以很容易猜测到,CERT.RSA文件的生成肯定和公钥相关CERT.RSA文件中保存了公钥、所采用的加密算法等信息

 

知晓了加密流程,我们可以认识到

1、 Android签名机制其实是对APK包完整性和发布机构唯一性的一种校验机制。
    2、 Android签名机制不能阻止APK包被修改,但修改后的再签名无法与原先的签名保持一致。(拥有私钥的情况除外)。
    3、 APK包加密的公钥就打包在APK包内,且不同的私钥对应不同的公钥。换句话言之,不同的私钥签名的APK公钥也必不相同。所以我们可以根据公钥的对比,来判断私钥是否一致。

 

也就是说360手机助手也不可能知道需要升级软件数字签名的私钥,所以可以猜测它进行的是对公钥的加密,那么接下来要验证这个观点

 

0x03逆向分析

  这里我们使用一个强大的安卓反编译软件ApkIDE.exe,然后把360手机助手逆向分析一下:

1.搜索signature_md5我们发现了另一个量sign_md5_default_value,那么我们来定位一下:在Apk改之理中搜索sign_md5_default_value 我们分析出现的位置

分析中在d.smali文件中有一段很关键

method private static b(Landroid/content/Context;Lcom/qihoo/appstore/resource/app/App;)V

    .locals 3 //  private static void b(Context paramContext, App paramApp)

 

    invoke-virtual {p1}, Lcom/qihoo/appstore/resource/app/App;->Z()Ljava/lang/String;

 

    move-result-object v0

 

    invoke-static {v0}, Lcom/qihoo/appstore/j/d;->c(Ljava/lang/String;)Z

 

    move-result v0

 

    if-eqz v0, :cond_0

 

    sget-object v0, Lcom/qihoo/appstore/j/d;->a:Landroid/content/Context;

 

    invoke-virtual {p1}, Lcom/qihoo/appstore/resource/app/App;->Z()Ljava/lang/String;

 

    move-result-object v1

 

    invoke-static {v0, v1}, Lcom/qihoo/appstore/utils/de;->f(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;

 

    move-result-object v0

 

    invoke-virtual {p1}, Lcom/qihoo/appstore/resource/app/App;->bL()Ljava/lang/String;

 

    move-result-object v1

 

    const-string v2, "sign_md5_default_value"

 

    invoke-virtual {v2, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

 

    move-result v2

 

    if-nez v2, :cond_1

 

    const-string v2, ""

 

    invoke-virtual {v1, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

 

    move-result v2

 

    if-nez v2, :cond_1

 

    sget-object v2, Lcom/qihoo/appstore/j/d;->a:Landroid/content/Context;

 

    invoke-static {v2, v0, v1}, Lcom/qihoo/appstore/utils/de;->a(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z

 

    move-result v0

 

    if-nez v0, :cond_1

 

    const/4 v0, 0x1

 

    invoke-virtual {p1, v0}, Lcom/qihoo/appstore/resource/app/App;->r(Z)V

 

    :cond_0

    :goto_0

    return-void

 

    :cond_1

    const/4 v0, 0x0

 

    invoke-virtual {p1, v0}, Lcom/qihoo/appstore/resource/app/App;->r(Z)V

 

    goto :goto_0

.end method

前面那几句大概意思就是用一个boolean方法判断App成员的Z()方法也就是说那个是不是一个String,换句话我们可以大胆猜测这里是判断有没有获取到程序的MD5

所以定位到这一句关键的代码Lcom/qihoo/appstore/utils/de;->f(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;

其中使用了de的 f() 方法对Context, String 参数进行处理 我们可以猜测这里的String代表的就是我们的MD5

.method public static f(Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String;

    .locals 2

 

    :try_start_0

    invoke-virtual {p0}, Landroid/content/Context;->getPackageManager()Landroid/content/pm/PackageManager;

 

    move-result-object v0

 

    const/16 v1, 0x40

 

    invoke-virtual {v0, p1, v1}, Landroid/content/pm/PackageManager;->getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;

 

    move-result-object v0

 

    iget-object v0, v0, Landroid/content/pm/PackageInfo;->signatures:[Landroid/content/pm/Signature;

 

    const/4 v1, 0x0

 

    aget-object v0, v0, v1

 

    invoke-virtual {v0}, Landroid/content/pm/Signature;->toByteArray()[B

 

    move-result-object v0

 

    invoke-static {v0}, Ljava/util/Arrays;->toString([B)Ljava/lang/String;

 

    move-result-object v0

 

    invoke-static {v0}, Lcom/qihoo/appstore/utils/de;->e(Ljava/lang/String;)Ljava/lang/String;

 

    move-result-object v0

 

    invoke-virtual {v0}, Ljava/lang/String;->toLowerCase()Ljava/lang/String;

    :try_end_0

    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

 

    move-result-object v0

 

    :goto_0

    return-object v0

 

    :catch_0

    move-exception v0

 

    sget-boolean v1, Lcom/qihoo360/mobilesafe/a/a;->a:Z

 

    if-eqz v1, :cond_0

 

    invoke-virtual {v0}, Ljava/lang/Exception;->printStackTrace()V

 

    :cond_0

    const/4 v0, 0x0

 

    goto :goto_0

.end method

 

首先调用系统的Context.getPackageManager().getPackageInfo(String,256/4).signature[0].toByteArray() 然后返回一个String对象然后再调用de类的e方法

 

 

 

 

.method public static e(Ljava/lang/String;)Ljava/lang/String;

    .locals 2

 

    :try_start_0

    const-string v0, "MD5"

 

    invoke-static {v0}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;

 

    move-result-object v0

 

    invoke-virtual {p0}, Ljava/lang/String;->getBytes()[B

 

    move-result-object v1

 

    invoke-virtual {v0, v1}, Ljava/security/MessageDigest;->update([B)V

 

    invoke-virtual {v0}, Ljava/security/MessageDigest;->digest()[B

 

    move-result-object v0

 

    invoke-static {v0}, Lcom/qihoo/appstore/utils/de;->a([B)Ljava/lang/String;

    :try_end_0

    .catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_0

 

    move-result-object p0

 

    :goto_0

    return-object p0

 

    :catch_0

    move-exception v0

 

    invoke-virtual {v0}, Ljava/security/NoSuchAlgorithmException;->printStackTrace()V

 

    goto :goto_0

.end method

 

这段话就是对String进行一次MD5运算然后调用a方法,其中我们看到的是调用的abyte[]参数的方法

 

 

 

 

 

method public static a([B)Ljava/lang/String;

    .locals 4

 

    new-instance v1, Ljava/lang/StringBuilder;

 

    array-length v0, p0

 

    mul-int/lit8 v0, v0, 0x2

 

    invoke-direct {v1, v0}, Ljava/lang/StringBuilder;-><init>(I)V

 

    const/4 v0, 0x0

 

    :goto_0

    array-length v2, p0

 

    if-ge v0, v2, :cond_0

 

    sget-object v2, Lcom/qihoo/appstore/utils/de;->f:[C

 

    aget-byte v3, p0, v0

 

    and-int/lit16 v3, v3, 0xf0

 

    ushr-int/lit8 v3, v3, 0x4

 

    aget-char v2, v2, v3

 

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;

 

    sget-object v2, Lcom/qihoo/appstore/utils/de;->f:[C

 

    aget-byte v3, p0, v0

 

    and-int/lit8 v3, v3, 0xf

 

    aget-char v2, v2, v3

 

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;

 

    add-int/lit8 v0, v0, 0x1

 

    goto :goto_0

 

    :cond_0

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

 

    move-result-object v0

 

    return-object v0

.end method

这段代码的作用就是对一个Byte[]数组进行格式化处理返回一个String对象

 

 

那么现在可以猜测360手机助手是对软件公钥的数字签名放到一个数组中,然后取它的第1个成员sinagture[0]进行MD5加密,下面我们来验证猜想

 

0x04 验证猜想

首先搭建好google sdk 环境 然后我们来写一个安卓程序来模拟360手机助手加密的过程,这里我们用google skd自带的Eclipse进行操作

其中代码如下:

 

import java.lang.reflect.Array;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import java.util.Arrays;

 

import android.app.Activity;

import android.content.pm.PackageInfo;

import android.content.pm.PackageManager;

import android.content.pm.PackageManager.NameNotFoundException;

import android.content.pm.Signature;

import android.os.Bundle;

import android.text.TextUtils;

import android.view.Menu;

import android.view.View;

import android.widget.EditText;

import android.widget.TextView;

import android.widget.Toast;

 

public class MainActivity extends Activity {

 

private EditText et_pkgname;

private TextView tv_signature;

private PackageManager manager;

private PackageInfo packageInfo;

private Signature[] signs;

private StringBuilder builder;

private String signature;

private static final char[] f = new char[] { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70 };

 

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

et_pkgname = (EditText) findViewById(R.id.et_pkgname);

tv_signature = (TextView) findViewById(R.id.tv_signature);

manager = getPackageManager();

builder = new StringBuilder();

}

 

public void getSignature(View view) {

String pkgname = et_pkgname.getText().toString();

boolean isEmpty = TextUtils.isEmpty(pkgname);

if (isEmpty) {

Toast.makeText(this, "应用程序的包名不能为空!", Toast.LENGTH_SHORT);

} else {

try {

/** 通过包管理器获得指定包名包含签名的包信息 **/

packageInfo = manager.getPackageInfo(pkgname, PackageManager.GET_SIGNATURES);

/******* 通过返回的包信息获得签名数组 *******/

signs = packageInfo.signatures;

/******* 循环遍历签名数组拼接应用签名 *******/

 

 

/************** 得到应用签名 **************/

 

String str = e(Arrays.toString(packageInfo.signatures[0].toByteArray()).toLowerCase());

builder.append(str);

 

signature = builder.toString();

 

tv_signature.setText(signature);

} catch (NameNotFoundException e) {

e.printStackTrace();

}

}

}

 

  public static String e(String paramString)

  {

    try

    {

      MessageDigest localMessageDigest = MessageDigest.getInstance("MD5");

      localMessageDigest.update(paramString.getBytes());

      String str = a(localMessageDigest.digest());

      return str;

    }

    catch (NoSuchAlgorithmException localNoSuchAlgorithmException)

    {

      localNoSuchAlgorithmException.printStackTrace();

    }

    return paramString;

  }

  

  public static String a(byte[] paramArrayOfByte)

  {

    StringBuilder localStringBuilder = new StringBuilder(2 * paramArrayOfByte.length);

    for (int i1 = 0; i1 < paramArrayOfByte.length; i1++)

    {

      localStringBuilder.append(f[((0xF0 & paramArrayOfByte[i1]) >>> 4)]);

      localStringBuilder.append(f[(0xF & paramArrayOfByte[i1])]);

    }

    return localStringBuilder.toString();

  }

 

 

 

public boolean onCreateOptionsMenu(Menu menu) {

getMenuInflater().inflate(R.menu.activity_main, menu);

return true;

}

}

 

我们要在onCreate()函数调用我们的方法也就是我们构造出来的计算signature[]的 MD5的方法

其中

localStringBuilder.append(f[((0xF0 & paramArrayOfByte[i1]) >>> 4)]);

localStringBuilder.append(f[(0xF & paramArrayOfByte[i1])]);

这两句代码是模拟360手机助手的格式化MD5的方法取前四位放到StringBuilder然后取后四位放到StringBuilder中。。

 

然后我们来用安卓模拟器进行模拟计算新浪微博的md5算法,获得新浪包名,adb shell下查看就行了:adb shell  , cd  data,cd   app, ls,

 

 

包名是:com.sina.weibo

运行我们写好的安卓程序来计算我们的签名:

 

我们来查看与360计算的结果是否相同:

 

 

0x05 后记

在后面的入侵测试中,由于本地签名保存,会导致360提示签名非法

从图中我们可以猜测360手机助手是在本地保存了原有的数字签名公钥,然后会对比公钥是否一致,而在实际的代码分析中360的确是有读取sigaature到一个数组中然后保存到了本地,虽然这次入侵结果是失败了,不过过程很迷人,结论就是:安卓公钥私钥的数字签名校验确保了安卓软件的安全性。

 

 

360手机助手关于签名校验的分析