您的位置:首页 > 移动开发 > Android开发

Android数据库加解密逆向分析(二)--Line数据库解密

2017-06-03 20:30 197 查看
接着上一篇文章,这篇文章主要通过都对Line这款APP数据库加密方式的逆向分析来看看Android第一种数据库加密方式。

开发环境:

1、测试设备:root手机一部

2、反编译工具:Android Killer或ApkTool

3、静态分析工具:jadx或者jd-gui

4、动态调试工具:Android Studio + smalidea插件

一、下载Line,反编译

1、首先从应用市场下载Line,安装。然后得到APK安装包将其拷贝到电脑中(也可以直接从电脑上下载APK,然后安装到手机中)。

使用解压工具将APK文件打开,然后将Line的三个dex文件解压出来:



2、解压出dex文件后使用jadx对将dex文件打开,得到Line的反编译后的java层代码。

3、使用Android Killer对APK进行反编译,得到samli代码:



上图为反编译后的目录结构,其中dex文件反编译后的smali代码保存在上面的3个smali文件中。

4、使用Android Studio导入反编译后的项目,准备动态调试:



导入成功后Project目录结构如下:



然后在Edit Configurations新建一个远程和手机的应用连接进行动态调试:





二、分析Line的java层源码:

在静态分析一个APP时,针对要分析的功能首先应该寻找关键点,找到关键点后通过一步步的静态分析和动态调试,最终得到我们要破解的结果。

在这个APP中由于我们要分析对数据库的数据的加解密,这里首先可以从加解密这个关键点作为入口,全局搜索一些与加解密相关的关键字,然后一步一步的分析。也可以从对数据库的操作作为入口:在对数据写入数据库时必然要对写入的明文先加密,从数据库中读取数据时,必然要对读取的密文进行解密,所以可以全局搜索与数据库插入、更新、读取相关的操作的关键字,然后一步步的分析下去,找到核心的加解密方法。

我们这里使用第二种方法,首先我们先打开APP的设置界面,如下:



可以看到有一个”允许他人通过ID添加“的选项,可以对应到数据库中的如下

字段,当打钩时,数据库中存储的信息如下:



当去掉钩时,数据库中存储的信息如下:



可以看到这里的value值只有两种,也就是说这里的这两个值应该就是true和false经过加密后的值。

所以我们分析这个打钩的过程,应该是打钩后将false改为true,然后对true进行加密写入数据库(更新数据库字段)。使用ddms进行method profiling:



该图为method profiling后的结果,先使用文本编辑器将这个trace文件打开,如下:



搜索文本update,得到如下结果:



可以看到这里便是对数据库的数据进行更新的方法。

所以在DDMS中查找update方法,结果如下:



可以看到调用这个update方法的是iec.a方法,在jadx中打开iec类,查看iec.a(SQliteDatabase,cc,String)Z方法:



在该方法前设置断点进行动态调试,如图:



this.k.containsKey(ccVar)的ccVar参数,也就是key值:



可以看到这句话便是得到加密后的value:



得到的str2的值:



同时this.k便是存储要写入到数据库的数据的key-value键值对:



所以需要进行代码跟踪,查找this.k的key == PROFILE_ALLOW_SEARCH_BY_ID的value值的生成的地方,也就是加密的地方。

经过一步步的跟踪,最终在iqc.a(iqf)这个方法里面找到了value值生成的地方:



通过iqa.a(String)方法对字符串进行加密:



通过iqa.a(String)方法可以看到其调用了am.a(long,String)方法,其中传入了一个long型常量15485863L,可以考虑这个long型常量可能为了生成对称加密的秘钥而传入的。

继续跟踪am.a(long,String)方法,如图:



在am.a中调用str = hph.a(a(l),str);

先不看a(l)方法,先看hph.a()方法:



可以很明显的看到hph.a方法使用的是AES对称加密算法对传入的str进行加密,在最后的将加密后的密文作为参数传入hxf.a()方法中,再看hxf.a()方法:



很明显是将最后加密的结果进行了Base64编码,然后返回给程序。

到这里再回到am.a中就可以很明显的知道在调用hph.a(a(l),str)方法时传入的参数a(l)就是AES加密算法的密码,通过这个密码,在hph.a方法中生成AES加密的秘钥。

来看AES加密算法的密码是如何生成的,进入am.a(long)方法:



如图,在hpj.a(long)中生成秘钥,进入hpj.a(long)方法:



再进入hpi.a(byte,long)方法中:



可以看到这里讲android_id的hashcode的byte值与常量long值进行一些底层运算,进而得到秘钥值。

三、编写解密DEMO

知道了数据库加密的原理后我们就可以直接编写出它的解密DEMO了。

首先在Android Studio中新建一个Project:LineDecrypt



然后新建一个AESDecrypt类用于对密文进行解密:

package com.zhayh.linedecrypt;

import android.util.Base64;
import android.util.Log;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSu
f59a
chPaddingException;
import javax.crypto.spec.SecretKeySpec;

/**
* Created by zyh16 on 2017/6/5.
*/
public class AESDecryotUtil {
/**
* 对字符串解密函数
* @param paramLong     Long常量15485863L
* @param paramString   要解密的密文
* @param ANDROIDID     adnroid_id的值
* @return              解密后的明文
*/
public static String decrypt(Long paramLong,String paramString,String ANDROIDID){
if(paramLong == null | paramString.equals("") | paramString == null | ANDROIDID == null){
return "参数错误";
}
byte[] bb = operationLong(paramLong,ANDROIDID);

return Base64Decrypt(bb,paramString);
}

/**
* 生成秘钥函数
* @param paramLong Long常量15485863L
* @param androidID adnroid_id的值
* @return          生成的秘钥的byte数组
*/
private static byte[] operationLong(Long paramLong , String androidID){
String str2 = androidID;
String str1 = str2;
if (str2 == null) {
str1 = "";
}
byte strByte = (byte)str1.hashCode();
return getAIDAndLong(strByte, paramLong);
}

/**
* 通过android_id的hashcode的byte值与Long常量15485863L生成秘钥
* @param paramByte android_id的hashcode的byte值
* @param paramLong Long常量15485863L
* @return          生成的秘钥的byte数组
*/
private static byte[] getAIDAndLong(byte paramByte, long paramLong){
int k = 0;
byte[] arrayOfByte = new byte[16];
arrayOfByte[0] = paramByte;
arrayOfByte[1] = ((byte)(paramByte - 71));
arrayOfByte[2] = ((byte)(paramByte - 71 - 71));

int i = 3;
while (i < 16)
{
arrayOfByte[i] = ((byte)(arrayOfByte[(i - 3)] ^ arrayOfByte[(i - 2)] ^ 0xFFFFFFB9 ^ i));
i += 1;
}
i = -7;
arrayOfByte = (byte[])arrayOfByte.clone();
int i2 = arrayOfByte.length;
long l = paramLong;

if (paramLong < 2L)
{

l = paramLong;
if (paramLong > -2L) {
l = -313187L + 13819823L * paramLong;
}
}

int j = 0;
while (j < i2)
{
int m = i2 - 1 & k + 1;
paramLong = arrayOfByte[m] * l + i;

int n = (byte)(int)(paramLong >> 32);
int i1 = (int)(paramLong + n);

k = i1;
i = n;

if (i1 < n)
{
k = i1 + 1;
i = n + 1;
}
arrayOfByte[m] = ((byte)(-2 - k));
j += 1;
k = m;
}
return arrayOfByte;
}

/**
* Base64解码函数,因为在加密时对最后生成的密文进行了Base64编码,所以在解密时先要解码
* @param paramArrayOfByte  秘钥的byte数组
* @param paramString       要解密的密文
* @return                  解密后的明文
*/
private static String Base64Decrypt(byte[] paramArrayOfByte,String paramString ){
byte[] base64 = Base64.decode(paramString,0);
Log.d("jw","base64 decode length == "+base64.length);
for(int i = 0;i<base64.length;i++){
Log.d("jw","base64["+i+"] == "+base64[i]);
}
byte[] paramArrayOfBytes = AESDecrypt(paramArrayOfByte, base64);

Log.d("jw","paramArrayOfBytes == "+paramArrayOfBytes.toString());
return new String(paramArrayOfBytes, 0, paramArrayOfBytes.length);
}

/**
* AES解密算法
* @param paramArrayOfByte1 秘钥
* @param paramArrayOfByte2 密文
* @return                  明文
*/
private static byte[] AESDecrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2){
SecretKeySpec key = new SecretKeySpec(paramArrayOfByte1, "AES");
Cipher localCipher = null;
byte[] bt = null;
try {
localCipher = Cipher.getInstance("AES");
localCipher.init(2, key);
bt =  localCipher.doFinal(paramArrayOfByte2);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}finally {
return bt;
}
}
}


解密工具类编写完毕后,设置布局文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.zhayh.linedecrypt.MainActivity">
<TextView
android:id="@+id/tv_androidID"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Android ID == "
android:textSize="20sp"
android:gravity="center"/>

<TextView
android:id="@+id/tv_cipherText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_androidID"
android:layout_marginTop="20dp"
android:text="密 文:"
android:textColor="#000"
android:textSize="20sp"/>
<EditText
android:id="@+id/et_cipherText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密文"
android:layout_alignBaseline="@id/tv_cipherText"
android:gravity="center"
android:layout_toRightOf="@id/tv_cipherText"/>

<TextView
android:id="@+id/tv_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_cipherText"
android:layout_marginTop="30dp"
android:text="名 文:"
android:textColor="#000"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="这里是默认值"
android:textSize="20sp"
android:gravity="center"
android:layout_toRightOf="@id/tv_text"
android:layout_alignBaseline="@id/tv_text"/>

<Button
android:id="@+id/btn_getContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_text"
android:layout_marginTop="10dp"
android:text="生成明文"
android:gravity="center" />
</RelativeLayout>


效果如下:



然后编写MainActivity类:

package com.zhayh.linedecrypt;

import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
private TextView tv_androidID = null;
private EditText et_cipherText = null;
private TextView tv_content = null;
private Button btn_getContent = null;
private Long longValue = 15485863L;
private String content = null;
private CharSequence cs = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv_androidID = (TextView) findViewById(R.id.tv_androidID);
et_cipherText = (EditText) findViewById(R.id.et_cipherText);
tv_content = (TextView) findViewById(R.id.tv_content);
btn_getContent = (Button) findViewById(R.id.btn_getContent);

cs = Settings.Secure.getString(this.getContentResolver(),"android_id");
tv_androidID.append(cs.toString());

btn_getContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
content = AESDecryotUtil.decrypt(Long.valueOf(longValue),et_cipherText.getText().toString().trim(),cs.toString());
tv_content.setText(content);
}
});
}
}


编写完成后运行程序,如图所示:



这里是数据库中存储的相关密文:



输入对应的密文后即可得到对应的明文,如:





四、总结

OK,关于Line的数据库解密就说到这里。这片文章的主要目的就是通过对Line这个APP的数据库加密过程进行逆向分析,介绍Android数据库加密中的对相关进行加密后写入数据库这种方法。

在Line的数据库加密的逆向分析中,主要是依靠静态分析与动态调试相结合的逆向分析手段,找准关键点,然后一步一步的分析,最终找到加密的地方。

最后给出DEMO项目地址:https://github.com/zyh16143998882/LineDecrypt
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息