Android逆向-Android逆向基础(3)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36869808/article/details/79226672

0x00前言

不知所以然,请看

Android逆向-Android基础逆向(1)

Android逆向-Android基础逆向(2)

Android逆向-Android基础逆向(2-2)

Android逆向-Android基础逆向(2-3补充篇)

以及java系列:

Android逆向-java代码基础(1)

Android逆向-java代码基础(2)

Android逆向-java代码基础(3)

Android逆向-java代码基础(4)

Android逆向-java代码基础(5)

Android逆向-java代码基础(6)

Android逆向-java代码基础(7)

Android逆向-java代码基础(8)

本节说明

别节主要讲解Android签名相关的内容。

内容

1.什么是签名?
2.Android 签名过程分析?
3.如何进行签名?
4.如果防止二次签名?

0x01 签名

1.签名的必要性

1.1 什么是签名

(1)“数字签名”是指可以添加到文件的电子安全标记。 使用它可以验证文件的发布者以及帮助验证文件自被数字签名后是否发生更改。
(2)如果文件没有有效的数字签名,则无法确保该文件确实来自它所声称的源,或者无法确保它在发布后未被篡改。
(3)简单的说签名就是开发者的一个标记。

1.2 签名的重要性

(1) 真实性: 让用户确信此软件的来源。确定软件开发商的真实性
(2) 完整性:确保发布后没有被篡改

1.3 Android签名特点

(1)Android应用都必须有数字签名,Android系统不会安装没有数字签名的应用。
(2)签名的数字证书不需要权威机构来认证,是开发者产生的数字证书,就是自签名。
(3)正式发布Android应用的时候,必须使用一个合适的私钥生成的数字证书来给程序签名,不能使用ADT插件或者ANT工具生成的调试证书来发布。
(4)数字证书是有有效期的。Android应用只有在安装的时候检查证书的有效期,如果已经安装则不影响程序的正常功能。

2.签名基础知识

2.1 非对称加密

非对称加密算法需要两个密钥:公开密钥(简称公钥)和私有密钥(简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密;如果用私钥对数据进行加密,那么只有用对应的公钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

2.2 消息摘要算法

根据一定的运算规则对原始数据进行某种形式的信息提取,被提取出的信息被称为原始数据的信息摘要。比如说MD5

2.3 摘要算法的特点

(1)输入随意,输出稳定。比如MD5
(2)不可逆

2.4 数字签名步骤

(1)对要发送的信息提取消息摘要
(2)对提取的信息摘要用自己的私钥加密

2.5 验证

(1)对原始消息部分提取消息摘要,摘要算法要和发送方一致
(2)使用公钥解密
(3)比较是否一致,相同则没有篡改,不同则可能篡改
注意:这里的前提是要有正确的公钥。如果公钥被篡改,则消息不可信。

2.6 数字证书

我们要保证公钥的可信度,就是用了数字证书。
数字证书主要包括以下内容。
(1)证书的发布机构
(2)证书的有效期
(3)消息发送方的公钥
(4)证书所有者
(5)数字签名使用的算法
(6)数字签名

3 其他知识补充

3.1 SHA1算法

安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。

3.2 SHA1算法流程

对于任意长度的明文,SHA1首先对其进行分组,使得每一组的长度为512位,然后对这些明文分组反复重复处理。
对于每个明文分组的摘要生成过程如下:
(1) 将512位的明文分组划分为16个子明文分组,每个子明文分组为32位。
(2) 申请5个32位的链接变量,记为A、B、C、D、E。
(3) 16份子明文分组扩展为80份。
(4) 80份子明文分组进行4轮运算。
(5) 链接变量与初始链接变量进行求和运算。
(6) 链接变量作为下一个明文分组的输入重复进行以上操作。
(7) 最后,5个链接变量里面的数据就是SHA1摘要。
突然有一种想用代码实现SHA1算法的冲动。先压制一下,等一下再说。
2018年1月31日16:08:33

3.3 Base64 编码说明

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。

3.4 Base64算法的特点

1、将数据按照 3个字节一组的形式进行处理,每三个字节在编码之后被转换为4个字节。即:如果一个数据有6个字节,可编码后将包含6/3*4=8个字节
2、当数据的长度无法满足3的倍数的情况下,最后的数据需要进行填充操作,即补“=” ,这里“=”是填充字符,不要理解为第65个字符。

0x02 Android 签名流程

1.签名样式

在之前的文章中讲解了关于Android 文件的内容。今天来具体看一下签名文件。
这里写图片描述
这三个就是签名后产生的东西,也就是我们研究的主体。

2.MANIFEST.MF

2.1 MANIFEST.MF内容

因为内容过多,所以这里只是进行一个简单的展示。
这里写图片描述

2.2 查看signapk.jar源码

signapk.jar源代码下载:http://download.csdn.net/download/qq_36869808/10233623
用jd-gui打开。
首先我们来看一下main函数。

public static void main(String[] args)
  {
    if (args.length != 4)
    {
      System.err.println("Usage: signapk publickey.x509[.pem] privatekey.pk8 input.jar output.jar");


      System.exit(2);
    }
    JarFile inputJar = null;
    JarOutputStream outputJar = null;
    try
    {
      X509Certificate publicKey = readPublicKey(new File(args[0]));
      PrivateKey privateKey = readPrivateKey(new File(args[1]));
      inputJar = new JarFile(new File(args[2]), false);
      outputJar = new JarOutputStream(new FileOutputStream(args[3]));
      outputJar.setLevel(9);


      Manifest manifest = addDigestsToManifest(inputJar);
      manifest.getEntries().remove("META-INF/CERT.SF");
      manifest.getEntries().remove("META-INF/CERT.RSA");
      outputJar.putNextEntry(new JarEntry("META-INF/MANIFEST.MF"));
      manifest.write(outputJar);


      Signature signature = Signature.getInstance("SHA1withRSA");
      signature.initSign(privateKey);
      outputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
      writeSignatureFile(manifest, new SignatureOutputStream(outputJar, signature));



      outputJar.putNextEntry(new JarEntry("META-INF/CERT.RSA"));
      writeSignatureBlock(signature, publicKey, outputJar);


      copyFiles(manifest, inputJar, outputJar); return;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      System.exit(1);
    }
    finally
    {
      try
      {
        if (inputJar != null) {
          inputJar.close();
        }
        if (outputJar != null) {
          outputJar.close();
        }
      }
      catch (IOException e)
      {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

然后在main函数中找到MANIFEST.MF相关的内容。

      Manifest manifest = addDigestsToManifest(inputJar);
      manifest.getEntries().remove("META-INF/CERT.SF");
      manifest.getEntries().remove("META-INF/CERT.RSA");
      outputJar.putNextEntry(new JarEntry("META-INF/MANIFEST.MF"));
      manifest.write(outputJar);

然后找到对应的方法。

 private static Manifest addDigestsToManifest(JarFile jar)
    throws IOException, GeneralSecurityException
  {
    Manifest input = jar.getManifest();
    Manifest output = new Manifest();
    Attributes main = output.getMainAttributes();
    if (input != null)
    {
      main.putAll(input.getMainAttributes());
    }
    else
    {
      main.putValue("Manifest-Version", "1.0");
      main.putValue("Created-By", "1.0 (Android SignApk)");
    }
    BASE64Encoder base64 = new BASE64Encoder();
    MessageDigest md = MessageDigest.getInstance("SHA1");
    byte[] buffer = new byte[4096];
    for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements();)
    {
      JarEntry entry = (JarEntry)e.nextElement();
      String name = entry.getName();
      if ((!entry.isDirectory()) && (!name.equals("META-INF/MANIFEST.MF")))
      {
        InputStream data = jar.getInputStream(entry);
        int num;
        while ((num = data.read(buffer)) > 0) {
          md.update(buffer, 0, num);
        }
        Attributes attr = null;
        if (input != null) {
          attr = input.getAttributes(name);
        }
        attr = attr != null ? new Attributes(attr) : new Attributes();
        attr.putValue("SHA1-Digest", base64.encode(md.digest()));
        output.getEntries().put(name, attr);
      }
    }
    return output;
  }

我们通过了解代码可以知道,就是说这个签名是把除了签名文件的其他文件进行一个摘要算法(SHA1算法),然后进行一个Base64。
自己使用工具进行测试。
找到resources.arsc的位置。
这里写图片描述
首先计算SHA1
这里写图片描述
19665111F39A646CA88622A068539EEDE585A317
这个就是我们的SHA1的值,现在进行Base64编码转换
http://tomeko.net/online_tools/hex_to_base64.php?lang=en
这里写图片描述
和SHA1-Digest里一样。

CERT.SF

1.文件内容

这里写图片描述

2.原理

CERT.SF 做了两件事情。

2.1第一件事情

把生成的MANIFEST.MF,先sha-1然后base64。
我们来做下测试。
这里写图片描述
拿到sha-1信息。
332500D173C8D4D754BB2663AD85D49EF3E9E642
然后base64编码
这里写图片描述

2.2 第二件事情

把MANIFEST.MF块信息依次sha-1,然后base64编码。

CERT.RSA

这个文件就是用私钥计算出签名, 然后将签名以及包含公钥信息的数字证书一同写入 CERT.RSA 中保存。

结束语

以上就是Android签名的整个过程,总结下就是
(1)生成第一个文件MANIFEST.MF
(2)更具第一个文件,生成第二个文件 CERT.SF
(3)然后就是用私钥计算

0x03 Android 签名方式

1.AndroidStudio 签名

1.1 新建签名

这里写图片描述
打开Build —Generate Signed APK—选择create new

1.2 填写签名保存地址

这里写图片描述

1.3 填写密码

这里写图片描述

1.4 填写key

这里写图片描述

1.5 填写其他内容

这里写图片描述

1.6 点击下一步

这里写图片描述

1.7 选择finish

这里写图片描述

2.使用apktool box进行签名

这里写图片描述

3.结束语

还有很多签名的方式,还可以进行一个自己的签名。当然也可以制作一个签名文件。

0x04 Android 签名认证

1.验证过程

1.1 验证APK中每个文件的算法和MANIFEST.MF文件中的对应属性块内容是否配对

1.2 验证CERT.SF文件的签名信息和CERT.RSA中的内容是否一致

1.3 MANIFEST.MF整个文件签名在CERT.SF文件中头属性中的值是否匹配以及验证MANIFEST.MF文件中的各个属性块的签名在CERT.SF文件中是否匹配

2.验证作用

2.1 MANIFEST.MF

更改apk包中的内容,就要校验MANIFEST.MF的摘要信息。

2.2 CERT.SF

如何只更改了MANIFEST.MF信息的内容,CERT.SF校验也不会通过

2.3 说明

只要修改了Apk中的任何内容,就必须重新签名,不然会提示安装失败

3.防止重新打包

3.1 方法一,java本地验证

使用代码验证签名,用 PackageManager 获取签名信息

public static int getSignature(Context context) {
    PackageManager pm = context.getPackageManager();
    PackageInfo pi;
    StringBuilder sb = new StringBuilder();

    try {
        pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
        Signature[] signatures = pi.signatures;
        for (Signature signature : signatures) {
            sb.append(signature.toCharsString());
        }
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }

    return sb.toString().hashCode();
}

优点:能够吓退一级敌人。
缺点:smali全局搜索,干掉逻辑,很好绕过。
破解方式:搜索getSignature即可。

3.2 方法二,NDK层校验

使用非明文保存
(1)分段存放,将字符串分为多段
(2)进行加密,使用时进行解密。
把验证放在Native层用NDK开发。
优点:能反编译c或c++的较少
缺点:对于会反编译的,轻松破掉逻辑,但是也可以起到一定的保护能力。

3.3 方法三,服务器校验

将本地传输到服务器进行校验,然后返回一段核心代码进行执行

4. 签名防止二次打包出现特点

(1)直接抛出异常,禁止运行
(2)弹出提示框提示用户,提示框消失后,退出程序
(3)跟服务器交互传递签名信息,如果不正确则服务器不返回数据

0x05 Android java层签名认证实现

1.Android 实例

实例我们使用我们的helloworld就可以了。

2.源代码

public class MainActivity extends AppCompatActivity {
    public static int getSignature(Context context) {
        PackageManager pm = context.getPackageManager();
        PackageInfo pi;
        StringBuilder sb = new StringBuilder();
        try {
            pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
            Signature[] signatures = pi.signatures;
            for (Signature signature : signatures) {
                sb.append(signature.toCharsString());
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return sb.toString().hashCode();
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println(getSignature(this));
        if(getSignature(this)!= -510418825)
        {
            finish();
        }
    }

3.测试

使用apktool进行签名,测试出现闪退。

0x06 简单实验绕过签名

1.反编译

这里写图片描述

2.smali

好像好久没有分析smali代码了,这次就当开一下胃吧。

.class public Lcom/example/hanlei/first_demo/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"


# direct methods
.method public constructor <init>()V
    .locals 0

    .prologue
    .line 10
    invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V

    return-void
.end method

.method public static getSignature(Landroid/content/Context;)I
    .locals 9
    .param p0, "context"    # Landroid/content/Context;

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

    move-result-object v2

    .line 14
    .local v2, "pm":Landroid/content/pm/PackageManager;
    new-instance v3, Ljava/lang/StringBuilder;

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

    .line 16
    .local v3, "sb":Ljava/lang/StringBuilder;
    :try_start_0
    invoke-virtual {p0}, Landroid/content/Context;->getPackageName()Ljava/lang/String;

    move-result-object v6

    const/16 v7, 0x40

    invoke-virtual {v2, v6, v7}, Landroid/content/pm/PackageManager;->getPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;

    move-result-object v1

    .line 17
    .local v1, "pi":Landroid/content/pm/PackageInfo;
    iget-object v5, v1, Landroid/content/pm/PackageInfo;->signatures:[Landroid/content/pm/Signature;

    .line 18
    .local v5, "signatures":[Landroid/content/pm/Signature;
    array-length v7, v5

    const/4 v6, 0x0

    :goto_0
    if-ge v6, v7, :cond_0

    aget-object v4, v5, v6

    .line 19
    .local v4, "signature":Landroid/content/pm/Signature;
    invoke-virtual {v4}, Landroid/content/pm/Signature;->toCharsString()Ljava/lang/String;

    move-result-object v8

    invoke-virtual {v3, v8}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    :try_end_0
    .catch Landroid/content/pm/PackageManager$NameNotFoundException; {:try_start_0 .. :try_end_0} :catch_0

    .line 18
    add-int/lit8 v6, v6, 0x1

    goto :goto_0

    .line 21
    .end local v1    # "pi":Landroid/content/pm/PackageInfo;
    .end local v4    # "signature":Landroid/content/pm/Signature;
    .end local v5    # "signatures":[Landroid/content/pm/Signature;
    :catch_0
    move-exception v0

    .line 22
    .local v0, "e":Landroid/content/pm/PackageManager$NameNotFoundException;
    invoke-virtual {v0}, Landroid/content/pm/PackageManager$NameNotFoundException;->printStackTrace()V

    .line 24
    .end local v0    # "e":Landroid/content/pm/PackageManager$NameNotFoundException;
    :cond_0
    invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v6

    invoke-virtual {v6}, Ljava/lang/String;->hashCode()I

    move-result v6

    return v6
.end method


# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 2
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .prologue
    .line 30
    invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V

    .line 31
    const v0, 0x7f04001b

    invoke-virtual {p0, v0}, Lcom/example/hanlei/first_demo/MainActivity;->setContentView(I)V

    .line 32
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    invoke-static {p0}, Lcom/example/hanlei/first_demo/MainActivity;->getSignature(Landroid/content/Context;)I

    move-result v1

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(I)V

    .line 33
    invoke-static {p0}, Lcom/example/hanlei/first_demo/MainActivity;->getSignature(Landroid/content/Context;)I

    move-result v0

    const v1, -0x1e6c5f89

    if-eq v0, v1, :cond_0

    .line 35
    invoke-virtual {p0}, Lcom/example/hanlei/first_demo/MainActivity;->finish()V

    .line 37
    :cond_0
    return-void
.end method

3. 搜索Signature

这里写图片描述
我们看着一段代码,一般肯定要有if判断才可以。
我们发现在这个方法里并没有发现if语句,只是进行一个值的返回。
这里写图片描述
我们继续搜索,肯定是什么地方进行了这个函数的调用。我们继续搜索。
这里写图片描述
找到了这里。
发现这里有if语句。
绕过这里就行。
绕过的方法很多。但是最好就是能少改就少改。调整下if语句逻辑就可以了。
自己做测试就好。这里就不占篇幅了。

0x07 结束语

收获

1.签名相关
2.Android 签名过程
3.签名
4.防止二次签名
5.进行简单的绕过

以上

没有更多推荐了,返回首页