2025某集团ctf决赛-RATTRACER-AsyncRAT通信解密

作者:Cloud Security lab 发布:2025-12-09 09:20 收录:2026-09-16 08:31 1 次阅读 约 2206 字
摘要:电信小伙伴给了两道ctf说帮忙看看,感觉挺有意思的,这里简单记录一下。
推荐理由:本文围绕「CTF」、「ZIP」、「开源」等主题展开。

 

小伙伴给了两道ctf说帮忙看看,感觉挺有意思的,这里简单记录一下。
附件,https://bohemian.top/images/RATTRACER.zip,有兴趣的小伙伴可以先试一下。

开局给到两个文件

这里一看就是 证书 需要解密

打开wireshark 可以看到是 tlsv1的流量 这里需要解密。
回头看p12文件
p12 文件其实就是 PKCS#12 格式的证书文件,通常以 .p12 或 .pfx 结尾,用来 同时存储证书 (Certificate) + 私钥 (Private Key),而且可以设置密码保护。

这里知道p12有私钥,会有密码保护
openssl pkcs12 -info -in ST.p12敲这个会让我们输入密码,直接回车,显示
说明没有密码,openssl pkcs12 -legacy -in ST.p12 -nocerts -nodes -out key.pem直接命令提私钥即可。让我们输密码,直接回车即可。
此时导入私钥。wireshrak ->编解->首选项-->protocols->tls

ip port 协议 和私钥填写好。

此时你随便选中一个tls流量就可以看到解密后的流量了,

此时看到里面还是 加密的,但是可以看到一点点特征。这个rat的特征呢。

经过对这个特征的搜索,找到一篇大师傅的文章,发现为AsyncRAT,

开源项目https://github.com/NYAN-x-CAT/AsyncRAT-C-Sharp

https://mp.weixin.qq.com/s/AJUQ8Zd_4Q3Ub9TarQx5Gg文章中 已经给出了详细的解密流程,简单来说就是外层是套了tls的数据,有私钥就能解,内层也就是现在看到的,不涉及加解密,只涉及gunzipunzip,再就是文件结构了。在仔细看大师傅文章中说的使用到的aes key及HMACSHA256 key,aes key及HMACSHA256 key是对配置信息进行解密,也就是解密样本的,在此题中不涉及,就不讲了。接下来看通信。

这里选择一个数据包,其中从1f 8b 08开始,这是gzip的hex头,然后gunzip即可,解出所有流量了,这里给出python批量脚本。这里需要先将所有的流都保存为bin文件,其实什么文件都行。

import os
import struct
import binascii
import glob
import gzip
import hashlib
from common import reverse_data, bytes_to_int, gzip_uncompress, hash_data_sha256, write_file

defgetbuflen(buf, isreverse=False):
    """获取缓冲区长度"""
    tmp = buf[:]
    if isreverse:
        tmp = reverse_data(tmp)
    return bytes_to_int(tmp)

defparseMsgPack(data):
    """解析MsgPack数据"""
    output = []

    # 键值对数量
    output.append(str(data[0] - 0x80))

    off = 1
    while off < len(data):
        # 解析载荷数据
        buf, num = readstring(data[off:])
        if num != 0:
            output.append(buf)
            off += num
        else:
            break

        if buf == "Msgpack":
            msgbuf, num = readbinary(data[off:])
            if num != 0:
                output.append(msgbuf)
                off += num

        iflen(output) >= 2and output[-1] == "Dll"and output[-2] == "savePlugin":
            msgbuf, num = readbinary(data[off:])
            if num != 0:
                output.append(msgbuf)
                off += num

    return"->".join(output), off

defreadstring(data):
    """读取字符串"""
    ifnot data:
        return""0

    byteflag = data[0]
    if160 <= byteflag <= 191:
        num = byteflag - 160
        iflen(data) < num + 1:
            return""0
        output = data[1:num+1].decode('utf-8', errors='ignore')
        return output, num + 1
    elif byteflag == 217:
        iflen(data) < 2:
            return""0
        num = data[1]
        iflen(data) < num + 2:
            return""0
        output = data[2:num+2].decode('utf-8', errors='ignore')
        return output, num + 2
    return""0

defreadbinary(buf):
    """读取二进制数据"""
    ifnot buf:
        return""0

    byteflag = buf[0]
    if byteflag == 0xc4:
        iflen(buf) < 2:
            return""0
        len_zipdata = buf[1]
        iflen(buf) < 6 + len_zipdata - 4:
            return""0
        len_data = getbuflen(buf[2:6], True)
        zipdata = buf[6:6+len_zipdata-4]
        data = gzip_uncompress(zipdata)
        if data isNoneor len_data != len(data):
            return""0
        output, off = parseMsgPack(data)
        if off != len(data):
            return""0
        return output, 2 + len_zipdata

    elif byteflag == 0xc6:
        iflen(buf) < 9:
            return""0
        len_zipdata = getbuflen(buf[1:5], False)
        len_data = getbuflen(buf[5:9], True)
        iflen(buf) < 9 + len_zipdata - 4:
            return""0
        zipdata = buf[9:9+len_zipdata-4]
        data = gzip_uncompress(zipdata)
        if data isNoneor len_data != len(data):
            return""0

        if data.startswith(b'MZ'):
            filename = "dll_" + hash_data_sha256(data)
            write_file(filename, data)
            output = "保存DLL,文件名为-" + filename
            return output, 1 + 4 + len_zipdata
        else:
            output, off = parseMsgPack(data)
            if"wallpaper"in output:
                if off + 5 < len(data):
                    len_image = getbuflen(data[off+1:off+5], False)
                    if off + 5 + len_image <= len(data):
                        imagedata = data[off+5:off+5+len_image]
                        filename = "image_" + hash_data_sha256(imagedata)
                        write_file(filename, imagedata)
                        output = "保存图片,文件名为-" + filename
            return output, 1 + 4 + len_zipdata

    return""0

defprocess_bin_file(filename, output_file):
    """处理单个bin文件"""
    try:
        withopen(filename, 'rb'as f:
            data = f.read()
    except Exception as e:
        print(f"读取文件失败 {filename}{e}")
        return

    print(f"处理文件: {filename}")
    base_name = os.path.splitext(os.path.basename(filename))[0]

    withopen(output_file, 'a', encoding='utf-8'as f:
        f.write(f"\n=== 处理文件: {filename} ===\n\n")

    num = 0
    pkt_num = 0

    while num < len(data):
        if num + 4 > len(data):
            break

        len_encodedata = getbuflen(data[num:num+4], True)
        if num + len_encodedata + 4 > len(data):
            break

        encodedata = data[num+4:num+4+len_encodedata]
        iflen(encodedata) < 4:
            num += len_encodedata + 4
            continue

        len_decodedata = getbuflen(encodedata[:4], True)
        zipdata = encodedata[4:]
        decodedata = gzip_uncompress(zipdata)

        if decodedata andlen(decodedata) == len_decodedata:
            # 保存解密数据到统一文件
            withopen(output_file, 'a', encoding='utf-8'as f:
                f.write(f"--- 数据包 {pkt_num} ---\n")
                if len_encodedata > 0x200:
                    f.write(f"加密数据:{binascii.hexlify(data[num:num+0x200+4]).decode()}\n")
                    f.write(f"解密数据_hex:{binascii.hexlify(decodedata[:0x200]).decode()}\n")
                else:
                    f.write(f"加密数据:{binascii.hexlify(data[num:num+len_encodedata+4]).decode()}\n")
                    f.write(f"解密数据_hex:{binascii.hexlify(decodedata).decode()}\n")

                output, off = parseMsgPack(decodedata)
                if off != len(decodedata):
                    if"2->Packet->pong->Message"notin output:
                        f.write("Error\n")
                f.write(f"解密通信指令:{output}\n\n")

            print(f"已处理数据包 {pkt_num}")
            pkt_num += 1
        else:
            print("!!!解压失败!!!")

        num += len_encodedata + 4

defmain():
    """主函数"""
    # 获取所有bin文件
    bin_files = glob.glob("*.bin")
    print(f"找到 {len(bin_files)} 个bin文件")

    # 创建统一的输出文件
    output_file = "decrypted_output.txt"
    # 清空文件
    withopen(output_file, 'w', encoding='utf-8'as f:
        f.write("解密报告 - 所有bin文件数据\n")
        f.write("=" * 50 + "\n\n")

    for filename in bin_files:
        process_bin_file(filename, output_file)

    print(f"解密完成,结果保存到 {output_file}")

    main()

if __name__ == "__main__":
    main()
然后就可以解出所有相关信息了。这里小伙伴又说忘记题目是啥了,根据已知的信息,并没有flag关键字,那我们就将所有的流量提出来,这里你会问,不是已经把所有的流量都解出来了么,是的,但是有一个没有解出来,就是>remoteDesktop-,这是远程桌面的流量,可能出题人只是远程cat flag,
2流就是所有的数据,想办法提出来,首先找到
这种长的流,很长,
先解hex 在解gunzip 在进行tohex,这里就有图片的前缀了,
然后在解码就好了。

就可以解密所有rdp的流量了,一个一个提确实有点小慢,这里简单写个脚本

import os
import struct
import gzip
import binascii
from common import reverse_data, bytes_to_int, gzip_uncompress

defgetbuflen(buf, isreverse=False):
    """获取缓冲区长度"""
    tmp = buf[:]
    if isreverse:
        tmp = reverse_data(tmp)
    return bytes_to_int(tmp)

defextract_jpeg_from_bin(bin_file, output_dir):
    """从 bin 文件中提取 JPEG 图像"""
    ifnot os.path.exists(output_dir):
        os.makedirs(output_dir)

    try:
        withopen(bin_file, 'rb'as f:
            data = f.read()
    except Exception as e:
        print(f"读取文件失败 {bin_file}{e}")
        return

    print(f"处理文件: {bin_file}")

    num = 0
    pkt_num = 0
    jpeg_count = 0

    while num < len(data):
        if num + 4 > len(data):
            break

        len_encodedata = getbuflen(data[num:num+4], True)
        if num + len_encodedata + 4 > len(data):
            break

        encodedata = data[num+4:num+4+len_encodedata]
        iflen(encodedata) < 4:
            num += len_encodedata + 4
            continue

        len_decodedata = getbuflen(encodedata[:4], True)
        zipdata = encodedata[4:]
        decodedata = gzip_uncompress(zipdata)

        if decodedata andlen(decodedata) == len_decodedata:
            # 查找 JPEG 数据
            soi_index = decodedata.find(b'\xff\xd8\xff\xe0')
            if soi_index != -1:
                jpeg_data = decodedata[soi_index:]

                # 查找 EOI 标记
                eoi_index = jpeg_data.find(b'\xff\xd9')
                if eoi_index != -1:
                    jpeg_data = jpeg_data[:eoi_index + 2]  # 包含 EOI

                    # 保存 JPEG 文件
                    filename = f"{output_dir}/packet_{pkt_num}.jpg"
                    withopen(filename, 'wb'as img_file:
                        img_file.write(jpeg_data)

                    print(f"提取 JPEG: {filename}")
                    jpeg_count += 1
                else:
                    print(f"数据包 {pkt_num}: 未找到 JPEG EOI 标记")
            else:
                print(f"数据包 {pkt_num}: 未找到 JPEG SOI 标记")

            pkt_num += 1
        else:
            print(f"数据包 {pkt_num}: 解压失败")

        num += len_encodedata + 4

    print(f"\n提取完成,共提取 {jpeg_count} 个 JPEG 图像到 {output_dir} 目录")

if __name__ == "__main__":
    extract_jpeg_from_bin("bb.bin""extracted_images")
这样就会生成一个目录,
可以看到攻击者rdp了主机,看了一个程序 up.exe

在解析中也可以看到相关程序。

总的来说就是先解tls,在进行去除不需要的数据,在进行gunzip。

这里小伙伴也是不知道问题是啥了,看所有数据应该是这个了,有小伙伴知道的话,麻烦告知一下,咱们在研究。
做这道题花费了一下午的时间,并且在一直查资料问gpt,小伙伴说比赛环境是不出网的,啊这。

致谢

最后感谢您读到现在,这篇文章匆忙构成肯定有不周到或描述不正确的地方,期待业界师傅们用各种方式指正勘误。如果您感觉文章写的不错,帮笔者点给公众号点点关注,先谢谢大家了。

emmm 太菜了
一直在路上

参考

https://mp.weixin.qq.com/s/AJUQ8Zd_4Q3Ub9TarQx5Gg AsyncRAT通信模型剖析
https://xz.aliyun.com/news/12945AsyncRAT通信模型剖析及自动化解密脚本实现

 


转载声明:本文转载自原发布平台 (作者:Cloud Security lab), 原文标题《2025某集团ctf决赛-RATTRACER-AsyncRAT通信解密》, 查看原文。 版权归原作者及原发布平台所有,本站仅作收录与展示,未对正文内容作实质性修改; 若涉及侵权请联系本站处理。