借助Wireshark抓取Android模拟器中“QQ同步助手”登录和同步数据时的流量,回答以下问题:
(1)筛选流量中,对应域名 "mpssync.3g.qq.com" 的IP地址;
(2)同步报文的TCP流量源IP:端口,目的IP:端口;
(3)分析同步流量的数据特征,并根据这些特征,能否获取报文的一些信息,例如密文长度信息;
(4)保存并上传流量分组文件。
mpssync.3g.qq.com
的IP地址: 183.3.225.36
和 113.96.237.110
10.12.181.187
, 源端口为 3550
, 目的 IP 为 113.96.237.110
, 目的端口为 14000
.dns
, 容易找到对域名 mpssync.3g.qq.com
的 DNS 查询和响应包, 如下图所示, 可以得到该域名对应的 IP 地址有 183.3.225.36
和 116.96.237.110
.mpssync.3g.qq.com
相关的数据包. 过滤条件如下, 即数据包的源或目的 IP 为域名的IP.ip.src==113.96.237.110 or ip.dst == 113.96.237.110 or ip.dst == 183.3.225.36 or ip.src==183.3.225.36
如下图为过滤后得到的数据包.
由于本机的 IP 地址是 10.12.181.187
, 因此可以得出, 同步报文的 TCP 流量源 IP 为本机的 IP 10.12.181.187
, 源端口为 3550
, 目的 IP 为域名 mpssync.3g.qq.com
的其中一个 IP 地址 113.96.237.110
, 目的端口为 14000
.
3. 选择其中一个报文, 右键"追踪流"并使用"Hex 转储"模式查看上述报文的传输数据.
通过观察上述数据可以发现, 传输的数据是经过加密的, 而且前四个字节表示了加密的数据长度(即传输的数据长度-4), 如上图报文长度为 158h, 加密的数据长度为 154h, 而前 4 个字节的数据即为 154h. 而提供了数据长度的加密类型一般是分组加密.
借助AndroidKiller分析和DDMS的“Method Profiling”功能,分析“QQ手机助手”登录和同步时的函数调用栈,回答以下问题:
(1)程序的包名是什么?
(2)在DDMS中对“QQ手机助手”进行动态跟踪时,PC端与手机(或模拟器)端连接的端口是多少,并参考实验手册中6.2节中的示例,附上截图。
(3)trace文件中,列举若干涉及加解密函数的调用栈(附上截图)。
(4)上传trace文件。
com.tencent.tccsync.TccTeaEncryptDecrypt.tccXXTeaDecrypt
com.tencent.tccsync.TccTeaEncryptDecrypt.encrypt
com.tencent.tccsync.TccTeaEncryptDecrypt.decrypt
com.tencent.tccsync.TccTeaEncryptDecrypt.tccXXTeaEncrypt
com.tencent.tccsync.TccTeaEncryptDecrypt.getXXTccTeaEncryptDecryptKey
com.tencent.qqpim
tools/monitor.bat
文件进行启动, 要求 JDK 版本不能超过 1.8
.tools/tracview.bat
文件启动, 使用如下命令打开 trace 文件.PS> .\traceview.bat E:\xxx\ddms_lab3.trace # 需要trace文件的绝对路径
在 Find 文本框中输入"encrypt"和"decrypt"来搜索和加密相关的函数, 具体的搜索结果和截图见上文的答案.
安装Frida工具,编写Python脚本,基于动态跟踪结果,回答以下问题:
(1)基于DDMS动态跟踪结果,列举同步过程中,调用了哪些加解密函数,并分析函数参数列表中的参数含义,比如,明文、密文和密钥。
(2)针对每一加解密函数,编写Python脚本,跟踪函数的输入和输出,分析可能采用的密钥、明文或密文数组。
(3)在跟踪函数参数时,同时利用Wireshark抓取同步时的报文,将报文与跟踪的函数参数(密文)进行比对。
(4)上传相关脚本和跟踪结果文件。
byte[] com.tencent.tccsync.TccTeaEncryptDecrypt.tccXXTeaDecrypt(byte[] arg1, byte[] arg2)
: 参数1为密文, 参数2为密钥, 返回值为明文.byte[] com.tencent.tccsync.TccTeaEncryptDecrypt.tccXXTeaEncrypt(byte[] arg1, byte[] arg2)
: 参数1为明文, 参数2为密钥, 返回值为密文.byte[] com.tencent.tccsync.TccTeaEncryptDecrypt.encrypt(byte[] arg1)
: 参数1为明文, 返回值为密文.byte[] com.tencent.tccsync.TccTeaEncryptDecrypt.decrypt(byte[] arg1)
: 参数1为密文, 返回值为明文.byte[] com.tencent.tccsync.TccTeaEncryptDecrypt.getXXTccTeaEncryptDecryptKsyy()
: 返回值为密钥.tccXXTeaEncrypt
和 tccXXTeaDecrypt
函数可能采用的密钥有: [8,6,3,8,1,8,0,2,6,9,6,3,3,3,1,h,^,J,9,o,`]
, [1,7,7,2,7,0,1,4,9,7,&,C,O,M,N,:,8,6,3,8,1,8,0,2,6,9,6,3,3,3,1,&,1,2,3,8,5,9,0,5,6,9,&,1,6,2,1,6,6,9,5,1,0]
, [D,F,G,#,$,%,^,#,%,$,R,G,H,R,(,&,*,M,<,>,<]
.tccXXTeaDecrypt
解密的明文有一些类似网址的数据.tccXXTeaEncypt
和 encrypt
加密的明文数据难以识别, 猜测应该是编码格式不正确. 不过在其中找到了个人的QQ号等信息decrypt
函数中能在解密数据中看到一些类似网址的信息.getXXTccTeaEncryptDecryptKsyy()
函数并没有截获到.tccXXTeaEncypt
加密的数据与实际 Wireshark 截获的发送的数据是一致的. 同样的, decrypt
待解密的数据与 Wireshark 截获的收到的数据是一致的. 不同之处在于实际在发送时会在加密数据前添加了 4 个字节表示数据长度.import frida
import sys
# 获取设备
redev = frida.get_remote_device()
print("redev:", redev)
# 获取应用进程
front_app = redev.get_frontmost_application()
print("front_app:", front_app)
qqtb = "com.tencent.qqpim"
session = redev.attach(qqtb)
# Js脚本对应字符串
jscode = """
//转换为数组
var toStr = function(title, obj){
return title + ":["+JSON.parse(JSON.stringify(obj))+"] ";
}
//函数标题
var funcTitle = function(funcName) {
return "Hooked Function: "+funcName+" "
}
//转换为ASCII码输出
var toAscii = function(arr) {
var s=[];
for(var i=0;i
script = session.create_script(jscode)
def on_message(message,data):
print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()
跟踪结果: 略安装 Frida 及其相关工具:
在安装有 python 的基础上, 使用 pip 工具下载 Frida 及其工具
PS> pip install frida # 安装Frida
PS> pip install frida-tools # 安装frida-tools
PS> frida --version # 查看Frida版本
安装 frida-server 并启动:
在 GitHub 上下载对应的服务端程序, 对应雷电模拟器, 下载的为 frida-server-14.2.18-android-x86
.
雷电模拟器的根目录下提供了 adb 工具, 使用该工具将 frida-server 传至手机模拟器中.
使用 adb devices
指令可以查看当前连接的设备列表, 在启动雷电模拟器的情况下, 是自动连接的, 如下图所示, "-"后的数字即为连接的端口号.
使用 push
命令将 frida-server 传至手机模拟器.
PS> .\adb.exe push .\frida-server-14.2.18-android-x86 /data/local/tmp
使用 chmod
命令更高 frida-server 的权限, 然后启动.
PS> ./adb.exe shell
# cd /data/local/tmp
# chmod 777 frida-server-14.2.18-android-x86 # 更改权限
# ./frida-server-14.2.18-android-x86 # 启动
采用 frida 跟踪 Java 函数参数:
编写如下 Python 代码:
import frida
import sys
# 获取设备
redev = frida.get_remote_device()
print("redev:", redev)
# 获取应用进程
front_app = redev.get_frontmost_application()
print("front_app:", front_app)
qqtb = "com.tencent.qqpim"
session = redev.attach(qqtb)
# Js脚本对应字符串
jscode = """
Java.perform(function () {
//选择捕获函数TccTeaEncryptDecrypt
var tted = Java.use("com.tencent.tccsync.TccTeaEncryptDecrypt");
//设置捕获函数
tted.tccXXTeaDecrypt.implementation = function (arg1, arg2) {
//捕获到了函数
send("Hook start ...");
//输出参数1
send("arg1:");
send(arg1);
//输出参数2
send("arg2:");
var ss = [];
for (var i = 0; i < arg2.length; i++) {
//将参数有ASCII码转为字符
ss.push(String.fromCharCode(arg2[i]));
}
send(ss.toString());
//输出返回值
var rtn = this.tccXXTeaDecrypt(arg1, arg2);
send("rtn:");
send(rtn);
return rtn;
};
});
"""
script = session.create_script(jscode)
def on_message(message,data):
print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()
该脚本实际上就是选择了 QQ 手机助手中的解密函数 com.tencent.tccsync.TccTeaEncryptDecrypt
进行了捕获操作, 将其函数参数和返回结果进行了输出.
在手机模拟器中开启 QQ 手机助手后运行脚本. 并在手机模拟器中使用 QQ 手机助手进行同步操作, 即可捕获到数据.
如图为脚本捕获的数据, 经分析可以看出, 对于该函数 TccTeaEncryptDecrypt
, 其第一个参数值有正有负, 应该是密文; 第二个参数长度固定, 且在后续的捕获中重复出现, 应该是解密用的密钥; 返回值为 128 以内的数字, 应该是明文.
frida.ServerNotRunningError: unable to connect to remote frida-server
的错误, 则需要开启端口转发, 使用如下命令:PS> .\adb.exe forward tcp:27042 tcp:27042
同理, 使用上述方法, 通过修改脚本中的捕获函数, 可以对其它几个加解密函数进行捕获分析.
最终脚本如下:
import frida
import sys
# 获取设备
redev = frida.get_remote_device()
print("redev:", redev)
# 获取应用进程
front_app = redev.get_frontmost_application()
print("front_app:", front_app)
qqtb = "com.tencent.qqpim"
session = redev.attach(qqtb)
# Js脚本对应字符串
jscode = """
//转换为数组
var toStr = function(title, obj){
return title + ":["+JSON.parse(JSON.stringify(obj))+"] ";
}
//函数标题
var funcTitle = function(funcName) {
return "Hooked Function: "+funcName+" "
}
//转换为ASCII码输出
var toAscii = function(arr) {
var s=[];
for(var i=0;i
script = session.create_script(jscode)
def on_message(message,data):
print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()
在跟踪的同时打开 Wireshark 进行截包, 按上述的过滤条件找到与域名 mpssync.3g.qq.com
的 IP 相关的数据包, 跟踪流后进行数据包和使用脚本捕获的数据进行对比, 可以看到是一致的, 具体见上述结果.
借助IDA Pro分析“QQ手机助手”中与同步相关的so文件,回答以下问题:
(1)与同步操作有关的文件名称;
(2)比较Java代码(借助AndroidKiller分析)与二进制代码中对应函数参数列表的差异;
(3)还原与加密密钥相关的二进制代码,以C代码形式呈现;
(4)还原二进制代码中核心的加解密代码,以C代码形式呈现;
(5)上传还原的C代码(说明其主要功能)。
lib/armeabi/libSync.so
getXXTccTeaEncryptDecryptKey
函数同 Java 代码一样, 函数参数均为 0; 其余 4 个加解密函数, 二进制代码比 Java 代码多 2 个参数: 第一个参数的类型为 JNIEnv*
, 第二个参数的类型为 jobject
.getXXTccTeaEncryptDecryptKey
: 获取固定密钥 DFG#$%^#%$RGHR(&*M<><
sub_16F4
decrypt
: 给定密文使用固定密钥进行解密.
sub_16F4
, sub_183C
encrypt
: 给定明文使用固定密钥进行加密.
sub_16F4
, sub_183C
tccXXTeaDecrypt
: 给定明文和密钥进行解密.
sub_183C
tccXXTeaEncrypt
: 给定密文和密钥进行解密.
sub_183C
sub_16F4
: 获取固定密钥 DFG#$%^#%$RGHR(&*M<><
sub_183C
: 给定待处理的数据, 密钥和模式(加密或者解密)进行加解密.
sub_C62C
, sub_C49C
sub_C62C
: 加密函数. 六个参数分别为明文数组指针, 明文长度, 密钥数组指针, 密钥长度, 用于存放密文的缓冲区指针及其长度. 函数的返回值为密文的长度.
sub_C4D8
sub_C49C
: 解密函数. 六个参数分别为密文数组指针, 密文长度, 密钥数组指针, 密钥长度, 用于存放明文的缓冲区指针及其长度. 函数的返回值为明文的长度.
sub_C340
sub_C4D8
: 具体的加密函数, 参数和返回值同 sub_C62C
, 对密钥进行 MD5 哈希后使用类似 XXTEA 的加密算法进行数据加密.
sub_C2C4
, sub_144C0
sub_C340
: 具体的解密函数, 参数和返回值同 sub_C49C
, 对密钥进行 MD5 哈希后使用类似 XXTEA 的解密算法进行数据解密.
sub_C2C4
, sub_144C0
sub_C2C4
: MD5 哈希算法sub_144C0
: 除法运算.com.tencent.tccsync.TccTeaEncryptDecrypt
找到对应的 smali 文件, 并进行反编译, 得到其 Java 代码.getLibName()
, 因此可以得知这些函数是通过外部加载的, 点击该函数跳转到其所在的类, 可以得到加载的库文件名 “Sync”, 因此最终可以确定加解密的函数应该在库文件 libSync.so
中实现.lib/armeabi/libSync.so
文件, 可以在函数列表中找到这些加解密函数.tccTeaEncrypt
函数反编译后的 C 代码, 可以看到其有 4 个参数, 比 Java 代码中的参数多了 2 个, 其他几个函数也是如此. 只有没有参数的 getXXTccTeaEncryptDecryptKey
, 其反汇编后的 C 代码也没有参数. 根据 .so 库中有关 Java 导出函数的参数的约定,第一个参数的类型是结构体指针 JNIEnv*
,第二个参数的类型一定是 jobject
tccXXTeaDecrypt
、tccXXTeaEncrypt
、encrypt
和 decrypt
四个函数中都调用了 sub_183C
函数. 进过分析, 该 sub_183C
函数是进行数据加解密的核心函数, 其完整的函数签名为 jbyteArray __fastcall sub_183C(JNIEnv *env, jbyteArray srcArray, jbyteArray key, int doEncrypt)
. 其中第二个参数是待处理的数据的 byte[]
类型数组, 加密时是明文, 解密时是密文; 第三个参数是密钥的 byte[]
数组; 第四个参数是一个标记参数, 为 1 时表示加密, 为 0 时表示解密. 此外, 对于 tccXXTeaDecrypt
、tccXXTeaEncrypt
两个函数, 密钥数组是直接由参数传递给 sub_183C
函数; 而对于 encrypt
和 decrypt
函数, 密钥是由 sub_16F4
函数得到的, 而实际上其返回的是一个固定的密钥 DFG#$%^#%$RGHR(&*M<><
. 而函数 getXXTccTeaEncryptDecryptKey
就是直接调用的 sub_16F4
获取的这一固定密钥.sub_183C
函数即核心的加解密函数, 其主要分为三部分, 第一部分是由 GetByteArrayElements
和 GetArrayLength
分别获取待处理数组 SRCArray
和密钥 key
所对应的原始 byte
数组的指针以及数组的长度. 接下来第二部分通过判断第四个参数 doEncrypt
来对数据进行加密或者解密, 加密调用函数 subC62C
, 解密调用函数 sub_C49C
. 最后第三部分是通过 NewByteArray
函数为加密或解密后的数据创建 Java 的 byte
数组, 然后通过 SetByteArrayRegion
将由第二部分的函数得到的加密或解密的数据存储到 Java 的数组中.sub_C62C
. 其函数签名为 jsize __fastcall sub_C62C(jbyte *plainBuf, jsize plainLen, jbyte *key, jsize keyLen, void *cipherBuf, int bufLen)
. 六个参数分别为明文数组指针, 明文长度, 密钥数组指针, 密钥长度, 用于存放密文的缓冲区指针及其长度. 函数的返回值为密文的长度. 在该函数中主要进行了 cipherBuf
的特殊情况处理, 然后主要是通过调用 sub_C4D8
函数完成的数据加密.sub_C49C
. 其函数签名为 jsize __fastcall sub_C49C(jbyte *cipherBuf, jsize cipherLen, jbyte *keyBuf, jsize keyLen, char *plainBuf, int bufLen)
. 六个参数分别为密文数组指针, 密文长度, 密钥数组指针, 密钥长度, 用于存放明文的缓冲区指针及其长度. 函数的返回值为明文的长度. 在该函数中, 主要的解密操作是通过调用函数 sub_C340
得到的.sub_C4D8
和 sub_C340
函数, 它们的函数签名分别和调用它们的函数 sub_C62C
和 sub_C49C
是相同的. 通过分析和查阅有关资料, 通过一些比较有特点的数值, 如 52
和 0x9e3779b9
可以得到, 这两个函数的核心算法与 XXTEA 加解密算法是相同的. 不过在此之前, 均使用了 sub_C2C4
函数进行了处理. 在 sub_C2C4
函数中调用的 sub_C160
中有 1732584193
, -271733879
等一组数字, 而这些数字是在 MD5 算法中用到的, 因此可以推测 sub_C2C4
函数实际上为 MD5 哈希函数, 也就是说密钥在使用之前都会进行 MD5 的哈希操作.libSync.so
文件及相应的 IDA 文件: libSync.so libSync.idb根据抓取的报文,跟踪的密钥和还原代码,测试对跟踪和分析结果的正确性。上传分析过程和结果。