由于某些原因,本文暂不提供成型的软件,只提供思路和部分代码,感兴趣的同学可以用Swing或者JavaFX写个UI出来

前段时间,我的企鹅号莫名其妙的被封了7天,在此期间无论如何申诉都没有效果,只好作罢。但这个时候正好是我申请各种offer的紧急时刻,有很多重要的联系人列表封存在其中提取不出来,又想起之前有人说企鹅安卓客户端在app数据目录里面以<qq号>.db的文件名保存了联系人列表的各种信息和聊天记录,于是就想着能否查看一下,然后申请一个小号加好友救急。

但拖入navicat中发现关键字段似乎用某种加密算法和谐掉了,说明这个SQLITE3数据库肯定是用单独的密钥加密过的:

此时我们看看能不能通过逆向工程获得解密算法,对关键字段进行解密。于是将apk导出,先查看目录结构,发现存在名为dbencrypt的动态链接库,于是猜测其本身和对应的java代码和解密数据库字段有关

将该SO库拖入IDA,查看导出函数,发现似乎只和加密有关,而无解密相关函数

转而定位对应的java类,将apk整体拖入看雪论坛版主gjden大佬开发的GDA逆向分析工具中,从函数和变量名猜测,此处存在大量计算、读取和写入密钥文件的操作,而且为明文,路径为kc,于是在app数据目录下搜索名为kc的文件。

打开文件查看,发现果然是明文,和代码中的逻辑比对,发现这个值应该是获取设备IMEI或者mac号失败后硬编码的常量字符串

在这个代码中,先计算出了str的值,然后将其写入kc文件,并且更新全局变量codeKey。此时我们查找codeKey的引用,发现他在encode()函数里面使用过,并且将参数传入了先前提到的so库中的JNI函数

 同时又注意到上面的decode()函数是直接调用的encrypt()函数,说明这是个对称加密算法,因此必须对刚才的so库重新进行分析

拖入ida中并且导入jni.h头文件,添加其中的JNIEnv, JavaVM, JNINativeInterface, JNINativeMethod, JNIInvokeInterface五个结构体,并把encryptByte()函数的声明改为

jbyte *Java_com_tencent_mobileqq_utils_SecurityUtile_encryptByte(JNIEnv*, jobject, jbyte*, jchar*, jint)

以增强可读性,同时得出如下部分源码:

可以很清楚的看到,这种加密算法其实就是对输入的byte[]数组按次序和密钥做与其长度相同的循环异或运算,得到密文,解密时把密文作为形参再调用一次该函数,得到原文。但与以前的算法不同的是,密文对应的byte数组的下标并不是和密钥的下标一一对应(比如说,原来的算法对密文的第四位解密时用的是密钥的第四位异或,但是这次要操作的密文下标是另一个特定的值,比如8),这其中就专门多了一个函数sub_7E4,计算要操作密文的下标值。这个函数比较复杂,我们就不在此分析,优化改写后直接套用即可。

回到java部分,传入的byte[]和char[]参数是由UTF-8的字符串类型转来的,因此计算完成后还要还原。故解密密文的核心代码如下:

public static String doDec(byte[] data, String key) {try {int keylen = key.length();byte[] b = key.getBytes(StandardCharsets.UTF_8);if (b == null) return "";int i = 0, j = 0;byte[] dst = data.clone();while (j < data.length) {int cal = 8 - Integer.toBinaryString((data[j] ^ -1) & 0xff).length();if (cal < 0 || cal > 4) {System.err.println("BadEncode");break;}if (cal == 0) {dst[j] = (byte) (data[j] ^ b[i % keylen]);j++;} else {dst[(j + cal) - 1] = (byte) (data[(j + cal) - 1] ^ b[i % keylen]);j += cal;}i++;}return new String(dst, StandardCharsets.UTF_8);} catch (Exception e0) {e0.printStackTrace();return null;}
}

但是光有解密部分还不行,我们还需要从数据库文件中读取对应栏目的字段值,比如企鹅号、昵称、名称、备注名、性别、年龄等信息。由于时间仓促,目前我也就实现了这几个栏目数据的读取,诸位还可以拓展。打开navicat,查得这些信息位于数据库Friends表中的age, alias, gender, name, remark, uin列中。

因此,我们要先打开数据库文件,获得连接信息

    public static Connection getConnection(String dbfn) throws SQLException {if ((dbfn = dbfn.toLowerCase()).indexOf("\\") != -1)dbfn = dbfn.replaceAll("\\\\", "/");String result = "";if (dbfn.indexOf("/") >= 0 || dbfn.indexOf("\\") >= 0) {result = String.valueOf("jdbc:sqlite://") + dbfn;} else {result = String.valueOf("jdbc:sqlite:") + dbfn;}try {Class.forName("org.sqlite.JDBC");return DriverManager.getConnection(result);} catch (ClassNotFoundException cne) {cne.printStackTrace();return null;}}

读取对应列的所有字段,将每个栏目的名称作为键、字段在SQL数据库中的类型名作为值存入哈希表中

    public static HashMap getColumns(Connection cnn) {String cmd = "SELECT * FROM Friends limit 0,1;";HashMap<String, String> hashMap = new HashMap<>();try {Statement st = cnn.createStatement();ResultSet rst = st.executeQuery(cmd);if(rst.next()) {ResultSetMetaData rsmd = rst.getMetaData();int col = rsmd.getColumnCount();for(int b=0;b<col;b++) {hashMap.put(rsmd.getColumnName(b+1), rsmd.getColumnTypeName(b+1));}}rst.close();st.close();} catch (SQLException se) {se.printStackTrace();}return hashMap;}

根据栏目名称(列)读取该列下每行的值,存入动态二维数组中

    private static ArrayList combine(ResultSet resultSet, Set set, HashMap hashMap) {ArrayList arrayList = new ArrayList();Iterator it = set.iterator();while (it.hasNext()) {String str = (String) it.next();arrayList.add(getType(resultSet, str, (String) hashMap.get(str)));}return arrayList;}public static ArrayList getValues(Connection cnn, HashMap hashMap) {String val = "SELECT * FROM Friends;";Set set = hashMap.keySet();ArrayList<ArrayList> arrayList = new ArrayList();try {Statement statement = cnn.createStatement();ResultSet resultSet = statement.executeQuery(val);while (resultSet.next()) {arrayList.add(combine(resultSet, set, hashMap));}resultSet.close();statement.close();} catch (SQLException se) {se.printStackTrace();}return arrayList;}

接下来还要根据字段的类型和获得的Object对象的类型来判断是否需要解密、需要用何种方式解密,故定义下面函数判断类型

    private static HashMap getType(ResultSet rs, String col, String ts) {HashMap<Object, Object> hashMap = new HashMap<>();try {switch (ts) {case "STRING":hashMap.put(col, rs.getString(col));break;case "INTEGER":case "INT":hashMap.put(col, Integer.valueOf(rs.getInt(col)));break;case "BLOB":hashMap.put(col, rs.getBytes(col));break;case "BYTE":hashMap.put(col, Byte.valueOf(rs.getByte(col)));break;case "CHAR":hashMap.put(col, rs.getString(col));break;case "LONG":hashMap.put(col, Long.valueOf(rs.getLong(col)));break;case "REAL":hashMap.put(col, Float.valueOf(rs.getFloat(col)));break;case "TEXT":hashMap.put(col, rs.getString(col));break;case "VARCHAR":hashMap.put(col, rs.getString(col));break;default:break;}} catch (SQLException e) {e.printStackTrace();}return hashMap;}
    private static Object UnkFunc(Object obj, String k) throws UnsupportedEncodingException {byte[] bArr;if (k == null) {return obj;}if (obj instanceof String) {bArr = ((String)obj).getBytes("utf-8");} else if (!(obj instanceof byte[])) {return obj;} else {bArr = (byte[]) obj;}return doDec(bArr, k);}public static Object Proc(String ts, Object obj, String key) throws UnsupportedEncodingException {switch (ts) {case "INTEGER":return obj instanceof Integer ? Integer.valueOf(((short) ((Integer) obj).intValue())) : obj;case "STRING":case "TEXT":return UnkFunc(obj, key);default:return obj;}}

最后可以在主函数(或者是UI的消息处理线程)中进行解密了

try {Connection cnx = DbConnection.getConnection(src_path);HashMap<String, String> con = getColumns(cnx);ArrayList<ArrayList> db = getValues(cnx, con);String[] col_names = con.keySet().toArray(new String[] {});Object[][] objArr = new Object[db.size()][col_names.length];for(int i = 0; i < db.size(); i++) { // 行长度FrList fri = new FrList();for(int j = 0; j < col_names.length; j++) { // 列长度HashMap tengo = (HashMap) (db.get(i)).get(j);String ts = con.get(col_names[j]);objArr[i][j] = tengo.get(col_names[j]);if(objArr[i][j] != null) objArr[i][j] = Proc(ts, objArr[i][j], input_key);else objArr[i][j] = "NULL";if(col_names[j].equals("age")) fri.setAge((Integer) objArr[i][j]);if(col_names[j].equals("alias")) fri.setAlias((String) objArr[i][j]);if(col_names[j].equals("gender")) {int v0 = (Integer)objArr[i][j];String gv = (v0 == 0) ? "Unknown" : ((v0 == 1) ? "Male" : "Female");fri.setGender(gv);}if(col_names[j].equals("name")) fri.setName((String) objArr[i][j]);if(col_names[j].equals("remark")) fri.setRemark((String) objArr[i][j]);if(col_names[j].equals("uin")) fri.setQQID((String) objArr[i][j]);}grid.add(fri);}tb.setItems(grid);DbConnection.closeConnection(cnx); // 记得关闭数据库连接
} catch (Exception e) {e.printStackTrace();
}

最终解密效果如下:

企鹅安卓客户端联系人列表数据解密分析及Java实现相关推荐

  1. 某小说App返回数据 解密分析

    一.目标 李老板:奋飞呀,最近被隔离在小区里,没啥可干的呀. 奋飞:看小说呀,量大管饱. 我们今天的目标就是某小说App v2021_09_53 二.步骤 搜索url字符串 App请求小说内容的时候没 ...

  2. Lync 2013 客户端联系人列表为空的解决方法

    问题描述: Lync2013客户端外部用户登陆后联系人列表为空,用户无法添加,如下图: 查找原因: Ctrl+右键点击右下角任务栏Lync小图标,打开"配置信息",如下图: 查看& ...

  3. android仿微信点击好友,安卓开发仿微信联系人列表-机器人列表视图仿微通道聊天多久最底部滑动...

    楼主你好!根据你的描述,让我给你答案! :新内容加进来,列表视图重新为setSelection后,定位结束后,拉起一个页面放. . 希望你能有所帮助,如果满意,请记得采纳像下拉条为微信好友如何实现 简 ...

  4. 一种客户端即时通信数据的加密和解密方法

    一种客户端即时通信数据的加密和解密方法  摘要 本发明适用于即时通信领域,提供了一种客户端即时通信数据的加密和解密方法,所述方法包括以下步骤:A.客户端加密本地保存的即时通信数据,并将数据加密密钥上传 ...

  5. pandorabox mysql_GitHub - gy-games/pandorabox: 基于非对称加密(RSA)的私密信息传递工具,数据由本地客户端进行加密、解密操作。...

    PandoraBox | 私密信息传递工具 基于非对称加密(RSA)的私密信息传递工具,数据由本地客户端进行加密.解密操作,密文通过网络进行传输,确保信息的安全性! -- Browse Website ...

  6. 安卓连接mysql客户端_安卓客户端与mysql服务器端数据交互

    1.安卓客户端的配置(上传数据) package com.dlvtc.upphp; import java.io.IOException; import java.io.UnsupportedEnco ...

  7. 使用客户端对象模型读取SharePoint列表数据

    使用客户端对象模型读取SharePoint列表数据 客户端对象模型提供了强有力的方式,从远程客户端应用程序管理列表. 1. 管理员身份打开VS,新建项目Windows窗体应用程序,命名ReadSPLi ...

  8. JavaEE中用response向客户端输出中文数据乱码问题分析

    这篇文章主要介绍了JavaEE中用response向客户端输出中文数据乱码问题分析,需要的朋友可以参考下 Web服务器收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的reques ...

  9. Android逆向分析案例——某点评APP登陆请求数据解密

    今天,七夕,单身23载的程序汪,默默地写着博客~ 上一次的逆向分析案例中讲了如何去分析某酒店的APP登陆请求,为了进一步学习如何逆向分析以及学习其他公司的网络传输加解密,本次案例将继续就登陆请求的数据 ...

最新文章

  1. 利用NVIDIA-NGC中的MATLAB容器加速语义分割
  2. 如何阅读JAVA 字节码(一)
  3. Mybatis错误:Parameter 'XXX' not found. Available parameters are [1, 0, param1, param2]
  4. ASP.NET MVC5+EF6+EasyUI 后台管理系统(19)-权限管理系统-用户登录
  5. linux 网卡是块设备吗,什么是网络块设备(Network Block Device)?
  6. c++ createtoolhelp32snapshot取进程路径_Linux进程间通信(上)之管道、消息队列实践
  7. web前端技术分享:多行文本溢出问题解决方案
  8. Unix时代的开创者Ken Thompson
  9. 交易猫鸿蒙空间,回收站是指
  10. 从零开始学Node.js(二)
  11. 2017.8.10 loli 测试
  12. Intel Haswell/Broadwell架构/微架构/流水线 (8)-Broadwell微架构
  13. c语言五子棋人机对弈算法_从零开始编写C语言五子棋程序1
  14. JPA + Hibernate + PostgreSQL + Maven基本配置示例
  15. 双向链表中基本函数的实现
  16. php passport security,php写的Passport加密函数
  17. 【空间】C++内存管理
  18. opencv中mean函数耗时_使用OpenCV进行人脸对齐
  19. 情人节,请带走我给您的祝福
  20. 短视频视频数据分析 5个解析

热门文章

  1. 苹果手机照片不小心删除了怎么恢复?
  2. 网站关键词怎么写好(网站标题,关键词,描述书写技巧)
  3. Cocos2d-x小蜜蜂游戏
  4. 查看动态代理生成的类文件
  5. SOLIDWORKS Manage多种自定义类型的BOM管理
  6. 阿里电商架构演变之路
  7. 【大学物理实验】刚体转动定律的研究
  8. ROS 禁止公网暴力破解SSH FTP
  9. Html 播放 mp4格式视频提示 没有发现支持的视频格式和mime类型
  10. vscode自定义大小写转换快捷键