17、网络协议逆向:Wireshark抓包分析、自定义协议识别、加密算法定位与Hook

网络协议逆向,说白了就是跟数据包「打交道」。你拿到一个黑盒软件,不知道它怎么跟服务器通信,不知道它传了什么数据,更不知道它有没有加密。怎么办?抓包看呗。

我个人习惯,拿到一个待分析的软件,第一件事就是开Wireshark。不是直接上IDA,也不是急着跑调试器。先看看它到底在「说」什么,比什么都重要。

17.1 Wireshark抓包基础与技巧

Wireshark这东西,大家都会用。但真正用好的人不多。我见过太多人打开Wireshark,看到满屏花花绿绿的包就懵了。

其实核心就三点:过滤、追踪、统计

17.1.1 抓包前的准备

先确定你要抓哪个网卡。别抓错了,抓了半天发现是虚拟机网卡,白费功夫。

  • 本地回环:如果软件和服务器都在本机,用loopbackNpcap的环回抓包
  • 物理网卡:软件连远程服务器,选你上网的那张卡
  • 无线网卡:注意混杂模式,有些网卡不支持
小技巧:我习惯先开Wireshark,再启动目标软件。这样能抓到完整的TCP三次握手,方便判断连接是否正常。

17.1.2 过滤表达式

Wireshark的过滤语法,我建议你背下来几个常用的。别每次都去查手册,效率太低。

过滤表达式 作用
ip.addr == 192.168.1.100 只看某个IP的流量
tcp.port == 443 只看某个端口的流量
http.request 只看HTTP请求
tcp contains "login" 查找包含特定字符串的包
!arp and !dns 排除ARP和DNS噪音

我曾经遇到一个软件,它发的包全是乱码。我一开始以为是加密了,后来用tcp contains "0x"一搜,发现是自定义的二进制协议。嗯,这里要注意,不是所有乱码都是加密。

17.2 自定义协议识别

很多商业软件不会用HTTP这种公开协议。它们自己定义一套二进制协议,包头、包体、校验码,全自己来。

怎么识别?我一般分三步走。

17.2.1 找特征码

先抓一批包,看看有没有固定的字节序列。比如每个包的前4个字节是不是固定的魔数?

// 假设抓到的包长这样
// 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
// 0xAB 0xCD 0x00 0x01 0x00 0x2A ... 数据 ...
// 0xAB 0xCD 0x00 0x02 0x00 0x1E ... 数据 ...

// 前两个字节 0xAB 0xCD 就是魔数
// 第3-4字节是命令字
// 第5-6字节是包长度

你想想看,如果每个包都以0xABCD开头,那这基本就是协议魔数了。接下来就是分析每个字段的含义。

17.2.2 字段边界分析

我习惯用「差分法」。就是对比两个相似但不同的包,看哪些字节变了,哪些没变。

  • 长度字段:包长度变化时,总有一个字段跟着变
  • 序列号:连续发包,某个字段递增
  • 校验和:改一个字节,整个包尾部某个字段就变了
  • 命令字:不同操作,某个字段值不同
核心思路:协议逆向的本质就是「找规律」。你不需要一开始就理解所有字段,先找到那些「会变的」和「不会变的」。

17.2.3 实战:识别一个自定义协议

我记得有一次分析一个游戏外挂。它跟服务器通信,包结构是这样的:

// 包结构
// [魔数:2字节] [命令:2字节] [包体长度:2字节] [包体:可变] [校验:4字节]

// 魔数固定为 0xFE 0xED
// 命令字:0x0001=登录,0x0002=心跳,0x0003=数据上报
// 包体长度 = 包体实际字节数
// 校验 = CRC32(魔数+命令+长度+包体)

怎么发现的?我改了包里的一个字节,服务器就断开了。然后我对比了正常包和异常包的尾部,发现最后4个字节不一样。一算,是CRC32。嗯,这里要注意,很多协议会用CRC做校验,但不是所有CRC都是标准的。

17.3 加密算法定位

协议识别完了,发现数据是加密的。怎么办?找加密算法。

我一般从两个方向入手:静态分析动态调试

17.3.1 静态定位:找常量

很多加密算法有固定的常量。比如AES的S盒、MD5的初始向量、Base64的字母表。在IDA里搜这些常量,一搜一个准。

算法 特征常量
AES 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, ...(S盒)
MD5 0x67452301, 0xEFCDAB89, ...(初始向量)
Base64 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef...(字母表)
CRC32 0xEDB88320(多项式)

我个人习惯,先在IDA里搜0x63, 0x7C,如果搜到了,基本就是AES。然后顺着交叉引用找到加密函数。

17.3.2 动态定位:Hook加密函数

静态分析找不到?那就动态调。用Frida或者API Monitor,Hook常见的加密API。

// Frida Hook Windows CryptoAPI
// 目标:定位加密函数调用点

Interceptor.attach(Module.findExportByName("advapi32.dll", "CryptEncrypt"), {
    onEnter: function(args) {
        console.log("[CryptEncrypt] called");
        console.log("  hKey:", args[0]);
        console.log("  dwDataLen:", args[2].toInt32());
        // 打印加密前的数据
        var dataPtr = args[1];
        var dataLen = args[2].toInt32();
        console.log("  Data:", hexdump(dataPtr, {length: dataLen}));
    },
    onLeave: function(retval) {
        console.log("[CryptEncrypt] returned:", retval);
    }
});

我曾经遇到一个软件,它没用Windows的CryptoAPI,而是自己实现了一个XOR加密。我Hook了memcpymemmove,发现它每次拷贝数据前后都会调用一个内部函数。跟进去一看,就是个简单的XOR循环。说白了,很多所谓的「加密」其实就是XOR,别想复杂了。

注意:Hook加密函数时,要注意线程安全。有些加密库是多线程的,你Hook进去打印日志,可能会打乱时序,导致程序崩溃。我建议先Hook关键点,确认没问题了再加详细日志。

17.4 加密算法Hook实战

定位到加密算法后,下一步就是Hook它,拿到明文。

17.4.1 思路

核心思路就一句话:在加密前拿到明文,在解密后拿到明文

  • 加密函数:Hook入口,参数里就是明文
  • 解密函数:Hook返回,返回值里就是明文
  • 密钥:如果密钥是动态生成的,Hook密钥生成函数

17.4.2 实战:Hook一个自定义AES

假设我们找到了一个自定义的AES加密函数,签名大概是:

void MyAESEncrypt(unsigned char* input, int inputLen, unsigned char* output, unsigned char* key);

用Frida Hook它:

// 找到目标函数地址
var targetAddr = Module.findExportByName("target.exe", "MyAESEncrypt");
// 或者用偏移
// var targetAddr = Module.findBaseAddress("target.exe").add(0x12345);

Interceptor.attach(targetAddr, {
    onEnter: function(args) {
        console.log("[MyAESEncrypt] called");
        console.log("  Input length:", args[1].toInt32());
        console.log("  Input data:", hexdump(args[0], {length: args[1].toInt32()}));
        console.log("  Key:", hexdump(args[3], {length: 16})); // AES-128
    },
    onLeave: function(retval) {
        // 输出是第二个参数?还是返回值?要看具体实现
        // 这里假设输出是 args[2]
        console.log("[MyAESEncrypt] output:", hexdump(this.context.r2, {length: 32}));
    }
});

你想想看,一旦你拿到了加密前的明文和加密后的密文,再加上密钥,这个加密对你来说就是透明的了。你可以自己写个脚本,随时加解密。

17.4.3 避坑指南

我曾经踩过一个坑。有个软件用了动态密钥,每次启动都不一样。我Hook了加密函数,拿到了明文和密文,但密钥是空的。后来发现,它的密钥是在另一个线程里生成的,我Hook早了。

解决办法:先Hook密钥生成函数,等密钥生成了,再Hook加密函数。或者用Frida的Stalker跟踪代码执行流,找到密钥的传递路径。

建议:如果你发现加密函数被频繁调用,但数据量很小,可能是心跳包。别浪费时间分析心跳,先找到真正的业务数据包。

17.5 知识体系总览

下面这张图,是我对网络协议逆向整个流程的总结。从抓包开始,到协议识别,再到加密定位和Hook,每一步都有对应的技术和工具。

网络协议逆向知识体系 Wireshark抓包 过滤 / 追踪 / 统计 自定义协议识别 魔数 / 字段 / 校验 加密算法定位 常量搜索 / API Hook 静态分析 IDA Pro 搜索常量 / 交叉引用 识别算法类型(AES / MD5 / XOR) 动态分析 Frida Hook 加密/解密函数 获取明文 / 密钥 / 算法参数 加密算法Hook实战 加密前拿明文 / 解密后拿明文 / 密钥提取 从抓包到Hook,一步步还原网络通信

这张图把整个流程串起来了。你从左上角的Wireshark开始,抓到包后识别协议,发现加密了就定位算法,最后用Hook拿到明文。每一步都有对应的工具和方法。

说白了,网络协议逆向没那么神秘。它就是「观察-分析-验证」的循环。你观察数据包,分析规律,验证你的猜测。错了就重来,对了就继续往下走。

我做了这么多年逆向,最大的体会就是:耐心比技术更重要。有时候一个协议要分析好几天,但只要你坚持,总能找到规律。

最后说一句:协议逆向不是为了破解,而是为了理解。你理解了软件的通信方式,才能更好地维护它、改进它。这才是「软件维护与逆向工程」这门课的核心价值。