探秘金山隐私保险箱 (解密出加密的数据)

背景

信息化时代的高速发展,同时也孕育了更多的网络攻击。网银被盗、隐私信息泄露等

无疑成为了广大网民最为关注的问题。几年前,“艳照门”事件的曝光,更是引发了互联网的一阵恐慌。

如今,移动互联网的迅速普及,手机相机的像素也越来越高,我们可以很方便的使用手机拍摄自己感兴趣的东西并上传到朋友圈、微博等。但是,这同时也引入了另外一个问题,拍了这么多东西,总有自己的一些隐私数据是不想对外公开的。于是,各大互联网安全厂商纷纷推出了能在移动设备上加密照片、音乐、视频等文件的应用程序。但是,这些应用真的能有效的保护好用户的隐私数据吗?他们的实现原理又是什么呢?带着这些疑问,今天我们就来分析下“金山隐私保险箱”的实现原理。

测试环境

红米TD版

百度云ROM 正式版V6

金山隐私保险箱1.3Beta2

程序分析

金山隐私保险箱安装完之后,加密一张自己拍的照片。此时,程序会将加密好的文件保存到sd卡的.ksbox目录下,如图1所示。

图1

将.ksbox目录导出到本地,使用sqliteexpert工具打开db.sqlite文件,表结构入图2所示。

图2

根据表结构我们大致可以知道,原始文件名、文件大小、被加密后的文件名等信息。知道了这些基本信息,我们接下来使用APK IDE解包程序,发现金山隐私保险箱自己实现了一个ImageInputStream的类,该派生自InputStream,具体的实现文件为com/ijinshan/mPrivacy/c/j.smali,如图3所示。

图3

使用APK IDE搜索Lcom/ijinshan/mPrivacy/c/j,结果如图4所示。

图4

定位到第一个new-instance的地方,代码如下所示,只截取我们所关注的部分。

# 解码一个inputstream到Bitmap

.method private statica(Ljava/lang/String;I)Landroid/graphics/Bitmap;

.locals 11

.prologue

const/4 v3, 0x1

const/4 v9, -0x1

const/high16 v6,0x3f800000

const/4 v8, 0x0

.line 197

.line 200

:try_start_0

# 新建一个自定义的InputStream对象

new-instance v0,Lcom/ijinshan/mPrivacy/c/j;

# 使用文件初始化InputStream

invoke-direct {v0, p0},Lcom/ijinshan/mPrivacy/c/j;-><init>(Ljava/lang/String;)V

.line 201

invoke-virtual {v0},Lcom/ijinshan/mPrivacy/c/j;->available()I

move-result v1

if-ne v1, v9, :cond_0

move-object v0, v8

.line 264

:goto_0

return-object v0

.line 205

:cond_0

# 新建一个BitmapFactory对象

new-instance v1,Landroid/graphics/BitmapFactory$Options;

invoke-direct {v1},Landroid/graphics/BitmapFactory$Options;-><init>()V

.line 208

const/4 v2, 0x1

iput-boolean v2, v1,Landroid/graphics/BitmapFactory$Options;->inJustDecodeBounds:Z

.line 209

const/4 v2, 0x0

# 调用BitmapFactory的decodeStream方法,解码input stream到Bitmap

invoke-static {v0, v2, v1}, Landroid/graphics/BitmapFactory;->decodeStream(Ljava/io/InputStream;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;

调用decodeStream函数之后,就会进入我们派生的ImageInputStream类中。该类重写了read方法,主要用来自定义解码算法。我们来看下主要代码:

.method public final read([BII)I

.locals 7

.prologue

const/4 v6, 0x0

const/16 v5, 0x400

.line 61

iget-object v0, p0,Lcom/ijinshan/mPrivacy/c/j;->a:Ljava/io/FileInputStream;

# p2(byteOffset),p3(byteCount)=0x10000

invoke-virtual {v0, p1,p2, p3}, Ljava/io/FileInputStream;->read([BII)I

move-result v0

.line 63

const/4 v1, -0x1

# 判断返回值是否为-1,-1即读到文件末尾

if-ne v0, v1, :cond_0

.line 103

:goto_0

return v0

.line 70

:cond_0

# f保存了已读的字节数

iget-wide v1, p0, Lcom/ijinshan/mPrivacy/c/j;->f:J

const-wide/16 v3, 0x400

cmp-long v1, v1, v3

# 判断已读的字节数是否大于或等于0x400字节

if-gtz v1, :cond_5

# 第一次读的话,执行如下代码

.line 73

# e是个bool值,判断是否已经解密了前面的0x400字节

iget-boolean v1, p0, Lcom/ijinshan/mPrivacy/c/j;->e:Z

if-nez v1, :cond_1

# 第一次读取,未解密,执行如下代码

.line 75

iget-object v1, p0,Lcom/ijinshan/mPrivacy/c/j;->c:Lcom/ijinshan/mPrivacy/c/g;

# b是个String类型的变量,其中保存了加密后文件的路径,例如/storage/sdcard0/.ksbox/6b2c357d

iget-object v1, p0,Lcom/ijinshan/mPrivacy/c/j;->b:Ljava/lang/String;

# 调用g;->b方法,解密前面0x400字节

invoke-static {v1},Lcom/ijinshan/mPrivacy/c/g;->b(Ljava/lang/String;)[B

move-result-object v1

# 将解密出来的字节数组保存到d变量中

iput-object v1, p0, Lcom/ijinshan/mPrivacy/c/j;->d:[B

.line 76

iget-object v1, p0, Lcom/ijinshan/mPrivacy/c/j;->d:[B

# 判断字节数组是否为空

if-eqz v1, :cond_1

.line 77

const/4 v1, 0x1

# 返回不为空,那么设置变量e为true,即解密成功

iput-boolean v1, p0, Lcom/ijinshan/mPrivacy/c/j;->e:Z

.line 80

:cond_1

# v0寄存器保存了实际读取到的字节数,p3是想要读取的字节数,即0x10000

if-ge v0, p3, :cond_3

move v1, v0

.line 82

:goto_1

# v2 = byteOffset + 实际读到的字节数

add-int v2, p2, v1

# 如果v2大于0x400,就跳到cond_4

if-gt v2, v5, :cond_4

.line 84

iget-object v2, p0,Lcom/ijinshan/mPrivacy/c/j;->d:[B

if-eqz v2, :cond_2

.line 85

# 将前面解密的数据赋给v2寄存器

iget-object v2, p0, Lcom/ijinshan/mPrivacy/c/j;->d:[B

# v2拷贝到p1,p2为srcOffset,v6是desOffset,v1为拷贝大小

invoke-static {v2, p2, p1,v6, v1}, Ljava/lang/System;->arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V

.line 100

:cond_2

:goto_2

# 已经读取的字节数

iget-wide v1, p0, Lcom/ijinshan/mPrivacy/c/j;->f:J

# v0为实际读到的字节数,转成long,保存到v3

int-to-long v3, v0

add-long/2addr v1, v3

# 本次实际读到的字节数 + 以前已经读取的字节数,保存到f变量

iput-wide v1, p0, Lcom/ijinshan/mPrivacy/c/j;->f:J

goto :goto_0

:cond_3

move v1, p3

.line 80

goto :goto_1

.line 89

:cond_4

if-ge p2, v5, :cond_2

.line 91

iget-object v1, p0, Lcom/ijinshan/mPrivacy/c/j;->d:[B

if-eqz v1, :cond_2

.line 92

iget-object v1, p0, Lcom/ijinshan/mPrivacy/c/j;->d:[B

sub-int v2, v5, p2

# 后面的数据不用解密,直接拷贝即可

invoke-static {v1, p2, p1,v6, v2}, Ljava/lang/System;->arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V

goto :goto_2

.line 98

# 如果已读的字节数大于0x400,就跳到这里执行

:cond_5

const/4 v1, 0x0

# 清空d变量

iput-object v1, p0, Lcom/ijinshan/mPrivacy/c/j;->d:[B

goto :goto_2

.end method

上面这段smali代码中比较关键的一个调用是invoke-static {v1},Lcom/ijinshan/mPrivacy/c/g;->b(Ljava/lang/String;)[B,我们跟进去看一下。

# 解密文件

# p0: 加密后文件的路径,例如/storage/sdcard0/.ksbox/6b2c357d

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

.locals 2

.prologue

const/4 v1, 0x0

.line 456

:try_start_0

# 判断是否是我们的加密文件,判断文件开头特征等等

invoke-static {p0},Lcom/ijinshan/mPrivacy/c/g;->h(Ljava/lang/String;)[B

move-result-object v0

.line 457

if-nez v0, :cond_0

move-object v0, v1

.line 472

:goto_0

return-object v0

.line 461

:cond_0

# 调用b(Ljava/lang/String;I)[B,读取_e文件的内容

invoke-static {p0},Lcom/ijinshan/mPrivacy/c/g;->i(Ljava/lang/String;)[B

# v0即为_e文件的内容

move-result-object v0

.line 462

if-eqz v0, :cond_1

.line 464

# 调用解密函数,解密v0

invoke-static {v0},Lcom/ijinshan/mPrivacy/c/g;->a([B)[B

:try_end_0

.catchLjava/io/IOException; {:try_start_0 .. :try_end_0} :catch_0

move-result-object v0

goto :goto_0

.line 467

:catch_0

move-exception v0

invoke-virtual {v0},Ljava/io/IOException;->printStackTrace()V

:cond_1

move-object v0, v1

.line 472

goto :goto_0

.end method

这里最为关键的是invoke-static{v0}, Lcom/ijinshan/mPrivacy/c/g;->a([B)[B这个调用,a([B)[B这个函数是专门用来解密byte数组的,代码如下所示。

# 解密算法

# buffer[i] = buffer[i] ^ 0x6b;

.method public static a([B)[B

.locals 3

.prologue

.line 264

array-length v0, p0

# 判断传入参数的buffer是不是大于0

.line 266

const/4 v1, 0x0

# 判断v1是否大于buffer的大小

:goto_0

if-ge v1, v0, :cond_0

# 取一个字节保存到v2

.line 267

aget-byte v2, p0, v1

# 与0x6b异或

xor-int/lit8 v2, v2, 0x6b

int-to-byte v2, v2

# 把异或得到的值写回原来的buffer中

aput-byte v2, p0, v1

# v1 + 1

.line 266

add-int/lit8 v1, v1, 0x1

# 继续循环

goto :goto_0

.line 270

:cond_0

return-object p0

.end method

程序分析到这里,我们大致知道了金山隐私保险箱的解密步骤:

1.       从InputStream类中派生自己的类,调用BitmapFactory的decodeStream函数解码文件输入流;

2.       重写InputStream类的read函数,用来实现自己的解密算法;

3.       解密的时候判断如果是前面最开始的0x400字节,那么读取filename_e文件,每个字节异或0x6B,如果是大于0x400字节,那么直接读取filename文件;

4.       按照上面的步骤解密,最后输出的文件即为原始文件。

编写解密程序

既然知道了金山隐私保险箱的解密算法,那么自己实现一个解密程序也就很简单了,大致代码如下所示。

#include "stdafx.h"

#include <Windows.h>

// szName - 加密文件的文件名

// szOriginName - 原始文件名

BOOL DecodeStream(WCHAR *szName, WCHAR *szOriginName)

{

BOOL bRet = FALSE;

if (!szName ||!szOriginName)

{

return bRet;

}

HANDLE hFile =CreateFile(szName,

FILE_ALL_ACCESS,

FILE_SHARE_READ| FILE_SHARE_WRITE,

NULL,

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

NULL);

if (hFile ==INVALID_HANDLE_VALUE)

{

return bRet;

}

DWORD dwHigh = 0;

DWORD dwSize =GetFileSize(hFile, &dwHigh);

if (dwSize < 0x400)

{

CloseHandle(hFile);

return bRet;

}

PBYTE pBuffer =(PBYTE)malloc(dwSize);

if (pBuffer == NULL)

{

CloseHandle(hFile);

return bRet;

}

memset(pBuffer, 0,dwSize);

HANDLE hSaveFile =CreateFile(szOriginName,

FILE_ALL_ACCESS,

FILE_SHARE_READ| FILE_SHARE_WRITE,

NULL,

CREATE_ALWAYS,

FILE_ATTRIBUTE_NORMAL,

NULL);

if (hSaveFile ==INVALID_HANDLE_VALUE)

{

CloseHandle(hFile);

free(pBuffer);

return bRet;

}

WCHAR szPath[MAX_PATH]= {0};

wsprintf(szPath,L"%s%s", szName, L"_e");

HANDLE hFile_e =CreateFile(szPath,

FILE_ALL_ACCESS,

FILE_SHARE_READ| FILE_SHARE_WRITE,

NULL,

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

NULL);

if (hFile_e ==INVALID_HANDLE_VALUE)

{

CloseHandle(hFile);

CloseHandle(hSaveFile);

free(pBuffer);

return bRet;

}

DWORD dwRet = 0;

bRet =ReadFile(hFile_e, pBuffer, 0x400, &dwRet, NULL);

if (!bRet)

{

CloseHandle(hFile);

CloseHandle(hSaveFile);

CloseHandle(hFile_e);

free(pBuffer);

return bRet;

}

SetFilePointer(hFile,0x400, NULL, FILE_BEGIN);

bRet = ReadFile(hFile,pBuffer+0x400, dwSize-0x400, &dwRet, NULL);

if (!bRet)

{

CloseHandle(hFile);

CloseHandle(hSaveFile);

CloseHandle(hFile_e);

free(pBuffer);

return bRet;

}

for (int i = 0; i <0x400; i++)

{

pBuffer[i] =pBuffer[i] ^ 0x6b;

}

WriteFile(hSaveFile,pBuffer, dwSize, &dwRet, NULL);

CloseHandle(hFile);

CloseHandle(hSaveFile);

CloseHandle(hFile_e);

free(pBuffer);

return bRet;

}

int _tmain(int argc, _TCHAR* argv[])

{

DecodeStream(L"C:\\Users\\Administrator\\Desktop\\98fca88",

L"C:\\Users\\Administrator\\Desktop\\1.jpg");

return 0;

}

执行完如上代码之后,图片被解密出来,并且能正常打开。自此,金山隐私保险箱就被我们轻易的攻破了。如图5所示:

图5

后记

分析完金山隐私保险箱之后,我后来又去看了下360隐私保险箱和腾讯手机管家的隐私保险箱,大致的加解密流程都差不多,都只加解密文件开头的0x400字节,只是各自的加密算法不同罢了,但是回过头来想想,既然它们都能把文件还原回去,也就是说这个过程一定是可逆的。

经过上面的分析,目前移动端的隐私保护软件基本上也就只是个心里安慰罢了。在日常生活中,我们还是要自珍自爱,尽量不要把私密的文件保存在移动设备上,也不要去下载来历不明的软件、外挂等。

探秘金山隐私保险箱 (解密出加密的数据)

时间: 2024-10-19 17:39:24

探秘金山隐私保险箱 (解密出加密的数据)的相关文章

RSA公钥加密—私钥解密&amp;私钥加密—公钥解密&amp;私钥签名—公钥验证签名

关于RSA算法,前面有介绍,点击打开链接. 这里直接有实现. 代码太多就不直接贴了,免积分下载. http://download.csdn.net/detail/acmjk/7310847 RSA公钥加密-私钥解密&私钥加密-公钥解密&私钥签名-公钥验证签名,布布扣,bubuko.com

使用X.509数字证书加密解密实务(三)-- 使用RSA证书结合对称加密技术加密长数据

本文全部源代码下载:/Files/chnking/EncryptLongData.rar 一.  使用证书结合对称加密算法加.解密长数据 上一章节讨论了如何使用RSA证书加密数据,文中提到:“Dotnet的RSA实现有个特点,它必须要在明文中添加一些随机数,所以明文不能把128字节占满,实际测试,明文最多为117字节,留下的空间用来填充随机数”.也就是说对于1024位密钥的RSA来说,一次只能加密128字节的数据,对于Dotnet的RSA实现更是只能加密117个字节的数据. 这就引出一个问题,超

php中des加密解密&#160;匹配C#des加密解密&#160;对称加密

原文:php中des加密解密 匹配C#des加密解密 对称加密 网上找来的 php des加密解密 完全匹配上一篇C# 字符串加密解密函数  可以用于C#和php通信 对数据进行加密,其中$key 是加密密钥,$iv 是偏移量,默认偏移量和加密密匙是一样的, <?php class DES { var $key; var $iv; //偏移量 function DES( $key, $iv=0) { //key长度8例如:1234abcd $this->key = $key; if( $iv

一个最简单的通过WireShark破解SSL加密网络数据包的方法

原文地址: http://article.yeeyan.org/view/530101/444688 一般来说,我们用WireShark来抓取包进行分析是没有多大问题的.但这里有个问题是,如果你碰到的是用SSL/TLS等加密手段加密过的网络数据的时候,往往我们只能束手无策.在过去的话,如果我们拥有的该传输会话的私钥的话我们还是可以将它提供给WireShark来让其对这些加密数据包进行解密的 1. 简介 相信能访问到这篇文章的同行基本上都会用过流行的网络抓包工具WireShark,用它来抓取相应的

Android开发周报:Android L默认加密用户数据

Android开发周报:Android L默认加密用户数据 新闻 <iCloud前车之鉴,Android L默认开启加密功能>:iCloud 艳照风波再起,第二波女星照片流出,大量女星的裸照又开始在社交网站疯传,本次大规模的“艳照门”依然有可能是黑客利用苹果iCloud云端系统的漏洞, 在破解了女艺人们所设的简单密码后侵入并非法盗取了裸照,继而在网络论坛发布.注重保护用户的隐私,是厂商们义不容辞的责任,谷歌宣布Android L上将默认加密用户数据. <苹果出了个指南教你怎么从Andro

发现新大陆:一个最简单的破解SSL加密网络数据包的方法

1. 简介 相信能访问到这篇文章的同行基本上都会用过流行的网络抓包工具WireShark,用它来抓取相应的网络数据包来进行问题分析或者其他你懂的之类的事情. 一般来说,我们用WireShark来抓取包进行分析是没有多大问题的.但这里有个问题是,如果你碰到的是用SSL/TLS等加密手段加密过的网络数据的时候,往往我们只能束手无策.在过去的话,如果我们拥有的该传输会话的私钥的话我们还是可以将它提供给WireShark来让其对这些加密数据包进行解密的,但这已经是想当年还用RSA进行网络数据加密的年代的

【转】用ASP.NET加密Cookie数据

来源:http://www.cnblogs.com/taizhouxiaoba/archive/2009/02/05/1384772.html Cookie中的数据以文本的形式存在客户端计算机,考虑它的安全性,最好在将数据存入Cookie之前对其进行加密. 加密的方法很多,比较简单一点的有:Base64,md5,sha等,而相对比较复杂一点的有:DES,TripleDES,RC2,Rijndael等. 下面是的代码实现了将数据存入Cookie之前采用散列的算法进行加密. 1 Private vo

Linux加密和数据安全性

加密和安全 墨菲定律 墨菲定律:一种心理学效应,是由爱德华·墨菲(Edward A. Murphy)提出的, 原话:如果有两种或两种以上的方式去做某件事情,而其中一种选择方式将导 致灾难,则必定有人会做出这种选择 主要内容: 任何事都没有表面看起来那么简单 所有的事都会比你预计的时间长 会出错的事总会出错 如果你担心某种情况发生,那么它就更有可能发生 安全机制 信息安全防护的目标 保密性 Confidentiality 完整性  Integrity 可用性 Usability 可控制性 Cont

Linux分区加密保护数据

一.首先创建分区 二.创建新的加密卷 加密分区 解密分区 创建文件系统 创建挂载点,然后挂载 卸载加密卷,并锁定加密的卷 下次要是再想使用这个加密卷怎么办? 再次打开加密卷就OK啦! 关于上面的图片解释下,作者刚刚改变映射名称时候,手动是可以挂载和打开的,第一次不知道怎么回事,挂载不上去,说是文件系统不对,搞了好一会都不知道怎么回事,所以重新格式化了一下,即重做文件系统: mkfs.ext4 /dev/mapper/jiami,第一次做的映射名是mydisk,建议这个映射名还是不要改的好,每次打