当前位置: 首页 > 工具软件 > Netzob > 使用案例 >

教程:如何使用 Netzob 反转未知协议

尤博达
2023-12-01

本文介绍了 Netzob 关于如何对未知协议进行逆向工程的主要功能。它通过学习简单协议的消息格式及其状态机,并就如何生成流量以与实际实现进行通信提供一些见解。最后,我们展示了如何针对服务器实现应用一些基本的模糊测试。

Netzob介绍

Netzob 是一个开源工具,用于对通信协议进行逆向工程、流量生成和模糊测试。它允许通过被动和主动进程推断协议的消息格式和状态机。该模型随后可用于模拟现实和可控的流量以及对目标实现进行模糊测试。

通过本教程,我们将介绍 Netzob 的主要功能,即简单玩具协议的消息格式和语法的推断,以及最后的一些基本实现的模糊测试。所描述的功能包括以下功能:

  • 导入包含我们要反转的跟踪的文件
  • 推断消息格式
    • 遵循特定分隔符的消息分区
    • 根据特定的关键字段重新组合消息
    • 序列比对后的每个消息的子集分区
    • 在每组消息中搜索关系
    • 修改消息格式以应用找到的关系
  • 推断语法
    • 根据捕获的消息序列生成具有一个主要状态的自动机
    • 根据捕获的消息序列生成具有状态序列的自动机
    • 根据捕获的消息序列生成前缀树接受器 (PTA) 自动机
  • 生成流量并对服务器进行模糊测试
    • 按照每个组的推断消息格式并通过访问推断自动机生成消息
    • 通过生成更改的消息格式对实现进行模糊测试

安装 Netzob 并下载教程资源

首先,检索 Netzob 的源代码,安装其依赖项并编译底层库。如果需要,README 文件中提供了有关安装过程的更多详细信息。

$ git clone https://dev.netzob.org/git/netzob
$ cd ./netzob/
$ sudo apt-get install python python-dev python-impacket python-setuptools build-essential python-numpy
$ python setup.py build
$ python setup.py develop --user

然后,您可以检索本教程中使用的玩具协议实现的源代码,以及一些消息序列的 PCAP 文件:

本文的下一段将介绍可用于反转此玩具协议的不同步骤。在深入了解 Netzob 功能之前,您可以查看其文档,尤其是API的描述。

消息格式推断

从 PCAP 文件导入消息

大多数协议逆向工程 (PRE) 流程的第一步是收集和导入通信样本。在本教程中,示例采用 PCAP 文件的形式。从 PCAP 文件中读取数据包是通过PCAPImporter.readFile()静态函数完成的。该函数可以选择使用更多参数来指定BPF过滤器、导入层或要捕获的数据包数量,如文档所示:

def readFile(filePath, bpfFilter="", importLayer=5, nbPackets=0):
     """Read all messages from the specified PCAP file. A BPF filter
     can be set to limit the captured packets. The layer of import
     can also be specified:
      - When layer={1, 2}, it means we want to capture a raw layer (such as Ethernet).
      - If layer=3, we capture at the network level (such as IP).
      - If layer=4, we capture at the transport layer (such as TCP or UDP).
      - If layer=5, we capture at the applicative layer (such as the TCP or UDP payload).
     Finally, the number of packets to capture can be specified.

    :param filePath: the pcap path
    :type filePath: :class:`str`
    :param bpfFilter: a string representing a BPF filter.
    :type bpfFilter: :class:`str`
    :param importLayer: an integer representing the protocol layer to start importing.
    :type importLayer: :class:`int`
    :param nbPackets: the number of packets to import
    :type nbPackets: :class:`int`
    :return: a list of captured messages
    :rtype: a list of :class:`netzob.Common.Models.Vocabulary.Messages.AbstractMessage`

此函数可用于从我们收集的 PCAP 中提取消息,同时刺激我们的玩具协议实现。例如,以下代码根据从 PCAP 中提取的消息创建一个符号。一个符号代表所有共享相同句法和语义的消息。换句话说,符号是一组相似消息的抽象,从协议的角度来看,它们具有相同的影响。首先,所有从 PCAP 文件导入的消息都被分组到一个唯一的符号中。然后我们将在这个符号上应用不同的方法来识别协议的消息格式。

from netzob.all import *

# Import of two PCAP files representing two sessions of the protocol (i.e. two instances of a communication between the client and the server)
messages_session1 = PCAPImporter.readFile("target_src_v1_session1.pcap").values()
messages_session2 = PCAPImporter.readFile("target_src_v1_session2.pcap").values()
messages = messages_session1 + messages_session2

# Group the messages of the two sessions into a uniq symbol
symbol = Symbol(messages = messages)

# Display symbol content
print symbol 
Field                                                
-----------------------------------------------------
'CMDidentify#\x07\x00\x00\x00Roberto'                
'RESidentify#\x00\x00\x00\x00\x00\x00\x00\x00'       
'CMDinfo#\x00\x00\x00\x00'                           
'RESinfo#\x00\x00\x00\x00\x04\x00\x00\x00info'       
'CMDstats#\x00\x00\x00\x00'                          
'RESstats#\x00\x00\x00\x00\x05\x00\x00\x00stats'     
'CMDauthentify#\n\x00\x00\x00aStrongPwd'             
'RESauthentify#\x00\x00\x00\x00\x00\x00\x00\x00'     
'CMDencrypt#\x06\x00\x00\x00abcdef'                  
"RESencrypt#\x00\x00\x00\x00\x06\x00\x00\x00$ !&'$"  
"CMDdecrypt#\x06\x00\x00\x00$ !&'$"                  
'RESdecrypt#\x00\x00\x00\x00\x06\x00\x00\x00abcdef'  
'CMDbye#\x00\x00\x00\x00'                            
'RESbye#\x00\x00\x00\x00\x00\x00\x00\x00'            
'CMDidentify#\x04\x00\x00\x00fred'                   
'RESidentify#\x00\x00\x00\x00\x00\x00\x00\x00'       
'CMDinfo#\x00\x00\x00\x00'                           
'RESinfo#\x00\x00\x00\x00\x04\x00\x00\x00info'       
'CMDstats#\x00\x00\x00\x00'                          
'RESstats#\x00\x00\x00\x00\x05\x00\x00\x00stats'     
'CMDauthentify#\t\x00\x00\x00myPasswd!'              
'RESauthentify#\x00\x00\x00\x00\x00\x00\x00\x00'     
'CMDencrypt#\n\x00\x00\x00123456test'                
"RESencrypt#\x00\x00\x00\x00\n\x00\x00\x00spqvwt6'16"
"CMDdecrypt#\n\x00\x00\x00spqvwt6'16"                
'RESdecrypt#\x00\x00\x00\x00\n\x00\x00\x00123456test'
'CMDbye#\x00\x00\x00\x00'                            
'RESbye#\x00\x00\x00\x00\x00\x00\x00\x00'            
-----------------------------------------------------

应用带分隔符的格式分区

根据对所显示消息的快速浏览,该角色#出现在每条消息的中间时听起来很有趣。因此,我们推理过程的第一步是根据分隔符拆分每条消息#。如文档中所述,该函数splitDelimiter()起到以下作用:

def splitDelimiter(field, delimiter):
    """Split a field (or symbol) with a specific delimiter. The
    delimiter can be passed either as an ASCII, a Raw, an
    HexaString, or any objects that inherit from AbstractType.

    :param field : the field to consider when spliting
    :type: :class:`netzob.Common.Models.Vocabulary.AbstractField.AbstractField`
    :param delimiter : the delimiter used to split messages of the field
    :type: :class:`netzob.Common.Models.Types.AbstractType.AbstractType`

所以让我们#在函数中使用分隔符splitDelimiter()。我们可以稍后使用该_str_debug()方法显示获得的字段结构。此方法显示符号结构的 ASCII 表示,从而显示每个字段的定义。

# Apply a split by delimiter method on the symbol
Format.splitDelimiter(symbol, ASCII("#"))

# Display symbol structure
print symbol._str_debug()
Symbol
|--  Field-0
     |--   Alt
           |--   Data (Raw='CMDidentify' ((0, 88)))
           |--   Data (Raw='RESidentify' ((0, 88)))
           |--   Data (Raw='CMDinfo' ((0, 56)))
           |--   Data (Raw='RESinfo' ((0, 56)))
           |--   Data (Raw='CMDstats' ((0, 64)))
           |--   Data (Raw='RESstats' ((0, 64)))
           |--   Data (Raw='CMDauthentify' ((0, 104)))
           |--   Data (Raw='RESauthentify' ((0, 104)))
           |--   Data (Raw='CMDencrypt' ((0, 80)))
           |--   Data (Raw='RESencrypt' ((0, 80)))
           |--   Data (Raw='CMDdecrypt' ((0, 80)))
           |--   Data (Raw='RESdecrypt' ((0, 80)))
           |--   Data (Raw='CMDbye' ((0, 48)))
           |--   Data (Raw='RESbye' ((0, 48)))
|--  Field-sep-23
     |--   Alt
           |--   Data (ASCII=# ((0, 8)))
           |--   Data (Raw=None ((0, 0)))
|--  Field-2
     |--   Alt
           |--   Data (Raw='\x07\x00\x00\x00Roberto' ((0, 88)))
           |--   Data (Raw='\x00\x00\x00\x00\x00\x00\x00\x00' ((0, 64)))
           |--   Data (Raw='\x00\x00\x00\x00' ((0, 32)))
           |--   Data (Raw='\x00\x00\x00\x00\x04\x00\x00\x00info' ((0, 96)))
           |--   Data (Raw='\x00\x00\x00\x00\x05\x00\x00\x00stats' ((0, 104)))
           |--   Data (Raw='\n\x00\x00\x00aStrongPwd' ((0, 112)))
           |--   Data (Raw='\x06\x00\x00\x00abcdef' ((0, 80)))
           |--   Data (Raw="\x00\x00\x00\x00\x06\x00\x00\x00$ !&'$" ((0, 112)))
           |--   Data (Raw="\x06\x00\x00\x00$ !&'$" ((0, 80)))
           |--   Data (Raw='\x00\x00\x00\x00\x06\x00\x00\x00abcdef' ((0, 112)))
           |--   Data (Raw='\x04\x00\x00\x00fred' ((0, 64)))
           |--   Data (Raw='\t\x00\x00\x00myPasswd!' ((0, 104)))
           |--   Data (Raw='\n\x00\x00\x00123456test' ((0, 112)))
           |--   Data (Raw="\x00\x00\x00\x00\n\x00\x00\x00spqvwt6'16" ((0, 144)))
           |--   Data (Raw="\n\x00\x00\x00spqvwt6'16" ((0, 112)))
           |--   Data (Raw='\x00\x00\x00\x00\n\x00\x00\x00123456test' ((0, 144)))

关于分区消息,现在看起来像这样:

# Display partitionned messages
print symbol
'CMDidentify'   | '#' | '\x07\x00\x00\x00Roberto'                 
'RESidentify'   | '#' | '\x00\x00\x00\x00\x00\x00\x00\x00'        
'CMDinfo'       | '#' | '\x00\x00\x00\x00'                        
'RESinfo'       | '#' | '\x00\x00\x00\x00\x04\x00\x00\x00info'    
'CMDstats'      | '#' | '\x00\x00\x00\x00'                        
'RESstats'      | '#' | '\x00\x00\x00\x00\x05\x00\x00\x00stats'   
'CMDauthentify' | '#' | '\n\x00\x00\x00aStrongPwd'                
'RESauthentify' | '#' | '\x00\x00\x00\x00\x00\x00\x00\x00'        
'CMDencrypt'    | '#' | '\x06\x00\x00\x00abcdef'                  
'RESencrypt'    | '#' | "\x00\x00\x00\x00\x06\x00\x00\x00$ !&'$"  
'CMDdecrypt'    | '#' | "\x06\x00\x00\x00$ !&'$"                  
'RESdecrypt'    | '#' | '\x00\x00\x00\x00\x06\x00\x00\x00abcdef'  
'CMDbye'        | '#' | '\x00\x00\x00\x00'                        
'RESbye'        | '#' | '\x00\x00\x00\x00\x00\x00\x00\x00'        
'CMDidentify'   | '#' | '\x04\x00\x00\x00fred'                    
(...)    

根据关键字段聚类

现在我们对不同领域中符号的分解有了初步的近似,让我们尝试将一些消息重新组合在一起:这就是 Netzob 中聚类方法的目的。

在这个例子中,第一场似乎有趣,因为它包含了某种命令(的CMDencryptCMDidentify等等)。因此,让我们根据第一个字段对符号进行聚类(即对第一个字段具有相同值的消息进行分组)。我们使用clusterByKeyField()具有以下描述的函数:

def clusterByKeyField(field, keyField):
    """Create and return new symbols according to a specific key
    field.

    :param field: the field we want to split in new symbols
    :type field: :class:`netzob.Common.Models.Vocabulary.AbstractField.AbstractField`
    :param keyField: the field used as a key during the splitting operation
    :type field: :class:`netzob.Common.Models.Vocabulary.AbstractField.AbstractField`
    :raise Exception if something bad happens

在这里,我们使用该函数clusterByKeyField()从捕获的消息中生成符号列表:

# Apply a cluster by key field method on the symbol
symbols = Format.clusterByKeyField(symbol, symbol.fields[0])

# Display the resulting symbols
print "[+] Number of symbols after clustering: {0}".format(len(symbols))
print "[+] Symbol list:"
for keyFieldName, s in symbols.items():
    print "  * {0}".format(keyFieldName)

聚类算法产生 14 个不同的符号,其中每个符号在第一个字段中都有一个唯一值。

[+] Number of symbols after clustering: 14
[+] Symbol list:
  * RESdecrypt
  * RESbye
  * RESidentify
  * CMDbye
  * RESencrypt
  * CMDidentify
  * RESstats
  * CMDencrypt
  * RESauthentify
  * CMDdecrypt
  * CMDinfo
  * CMDauthentify
  * RESinfo
  * CMDstats

在每个符号的第三个字段上应用具有序列对齐的格式分区

在这一步,我们重新组合了具有相同目的的消息,并在三个字段中对每个消息进行了基本分解:命令字段、分隔符字段(即#)和第三个字段,它似乎具有可变内容的动态大小。现在让我们关注最后一个领域。具有动态大小的字段非常适合我们在 Netzob 中称为“序列对齐”的内容。此功能让我们将静态和动态子字段对齐在一起。为此,我们提供了splitAligned()具有以下文档的函数:

def splitAligned(field, useSemantic=True, doInternalSlick=False):
    """Split the specified field according to the variations of message bytes.
    Relies on a sequence alignment algorithm.
    (...)

在以下代码段中,我们希望通过序列对齐算法对齐每个符号的最后一个字段:

# Apply a format partitionment on the third field (the last one) of each symbol
for symbol in symbols.values():
    Format.splitAligned(symbol.fields[2], doInternalSlick=True)
    print "[+] Partitionned messages:"
    print symbol

对于符号CMDencrypt,最后一个字段的序列对齐产生以下格式,其中我们可以观察到\x00\x00\x00由两个变量字段包围的静态字段。最后一个字段似乎是我们想要加密的缓冲区,正如关键字段名称所暗示的(即CMDencrypt)。

(...)
[+] Partitionned messages:
'CMDencrypt' | '#' | '\n'   | '\x00\x00\x00' | '123456test'
'CMDencrypt' | '#' | '\x06' | '\x00\x00\x00' | 'abcdef'   
(...)

查找每个符号中的字段关系

现在让我们尝试在这些消息中找到关系。Netzob API 提供了静态函数RelationFinder.findOnSymbol(),它允许识别与同一符号相关的消息字段中的潜在关系,如文档中所述:

def findOnSymbol(symbol):
    """Find exact relations between fields in the provided
    symbol/field.

    :param symbol: the symbol in which we are looking for relations
    :type symbol: :class:`netzob.Common.Models.Vocabulary.AbstractField.AbstractField`
    """

以下代码段显示了如何在我们未知的协议上找到关系以及如何处理结果:

# For each symbol, find potential relationships between its fields
for symbol in symbols.values():
    rels = RelationFinder.findOnSymbol(symbol)

    print "[+] Relations found: "
    for rel in rels:
        print "  " + rel["relation_type"] + ", between '" + rel["x_attribute"] + "' of:"
        print "    " + str('-'.join([f.name for f in rel["x_fields"]]))
        p = [v.getValues()[:] for v in rel["x_fields"]]
        print "    " + str(p)
        print "  " + "and '" + rel["y_attribute"] + "' of:"
        print "    " + str('-'.join([f.name for f in rel["y_fields"]]))
        p = [v.getValues()[:] for v in rel["y_fields"]]
        print "    " + str(p)

关于下面的提取结果,我们发现了CMDencrypt一个字段的内容(第三个)和另一个字段的长度(最后一个,大概包含我们要加密的缓冲区)之间的符号关系。

(...)
[+] Relations found: 
  SizeRelation, between 'value' of:
    Field
    [['\n', '\x06']]
  and 'size' of:
    Field
    [['123456test', 'abcdef']]
(...)

将找到的关系应用于符号结构

所以我们刚刚找到了一个字段,它对应于下一个字段的大小。对于这个结果,我们可以修改消息格式来应用我们刚刚发现的关系。为此,我们创建了一个“大小”字段,其值取决于目标字段的内容。我们还指定了一个因素,基本上表示大小字段的值应该是缓冲区字段大小的八分之一(因为每个字段大小默认以位表示)。

# For each found relationships for each field, apply the result to the model
for symbol in symbols.values():
    rels = RelationFinder.findOnSymbol(symbol)

    for rel in rels:

        # Apply first found relationship
        rel = rels[0]
        rel["x_fields"][0].domain = Size(rel["y_fields"], factor=1/8.0)

    print "[+] Symbol structure:"
    print symbol._str_debug()

结果,CMDencrypt符号结构现在看起来像这样:

(...)
[+] Symbol structure:
Symbol_CMDencrypt
|--  Field-0
     |--   Data (ASCII=CMDencrypt ((0, 80)))
|--  Field-sep-23
     |--   Data (ASCII=# ((0, 8)))
|--  Field-2
     |--   Data (Raw=None ((0, None)))
|--  |--  Field
          |--   Size(['Field']) - Type:Raw=None ((8, 8))
|--  |--  Field
          |--   Data (Raw='\x00\x00\x00' ((0, 24)))
|--  |--  Field
          |--   Data (Raw=None ((0, 80)))
(...)

我们刚刚在交易品种上显示了结果CMDEncrypt,但同样的步骤可以应用于我们协议的其他交易品种。因此,我们能够检索每一个的完整定义。

好的,这就是消息格式推断的全部内容。当我们现在了解每个符号的结构时,让我们反转协议的状态机。

状态机推理

生成链式状态自动机

本教程的第一部分侧重于反转协议消息格式。我们现在将致力于反转状态机,即告诉消息/符号的授权序列的语法。在这一部分,我们通过学习观察到的消息序列来生成三种自动机。在 Netzob 中,消息序列由对象Session 表示。此外,当使用符号(它们是一组相似消息的抽象)时,抽象消息序列由抽象会话表示。因此,该对象用于推断状态机。

在本节中,我们将介绍基于捕获的 PCAP 文件生成自动机的三种方法。

根据我们学到的符号,我们将首先生成一个基本的自动机,它说明了从 PCAP 文件中提取的命令和响应的序列。对于发送的每条消息,这将创建一个到新状态的新转换,因此名为链式状态自动机

# Create a session of messages
session = Session(messages_session1)

# Abstract this session according to the inferred symbols
abstractSession = session.abstract(symbols.values())

# Generate an automata according to the observed sequence of messages/symbols
automata = Automata.generateChainedStatesAutomata(abstractSession, symbols.values())

# Print the dot representation of the automata
dotcode = automata.generateDotCode()
print dotcode

获得的自动机最终可以转换为 Dot 代码,以便呈现它的图形版本。

生成一个状态自动机

这一次,我们不是将 PCAP 转换为每个观察到的消息的状态序列,而是生成一个独特的状态,该状态接受任何观察到的发送消息以触发新的转换。为了响应每个发送的消息(例如CMDencrypt),我们期望一个特定的响应(例如REDencrypt)。

# Create a session of messages
session = Session(messages_session1)

# Abstract this session according to the inferred symbols
abstractSession = session.abstract(symbols.values())

# Generate an automata according to the observed sequence of messages/symbols
automata = Automata.generateOneStateAutomata(abstractSession, symbols.values())

# Print the dot representation of the automata
dotcode = automata.generateDotCode()
print dotcode

获得的自动机最终被转换为 Dot 代码,以呈现它的图形版本。

生成基于 PTA 的自动机

最后,我们转换从不同 PCAP 文件中获取的多个消息序列,以生成一个我们合并相同路径的自动机。底层合并策略称为前缀树接受器。

# Create sessions of messages
messages_session1 = PCAPImporter.readFile("target_src_v1_session1.pcap").values()
messages_session3 = PCAPImporter.readFile("target_src_v1_session3.pcap").values()

session1 = Session(messages_session1)
session3 = Session(messages_session3)

# Abstract this session according to the inferred symbols
abstractSession1 = session1.abstract(symbols.values())
abstractSession3 = session3.abstract(symbols.values())

# Generate an automata according to the observed sequence of messages/symbols
automata = Automata.generatePTAAutomata([abstractSession1, abstractSession3], symbols.values())

# Print the dot representation of the automata
dotcode = automata.generateDotCode()
print dotcode

获得的自动机最终被转换为 Dot 代码,以呈现它的图形版本。

流量生成和模糊测试

根据推断模型生成消息

我们现在对目标协议的格式消息和语法有了很好的了解。因此,让我们通过尝试与真实的服务器实现进行通信来使用此模型。

首先,让我们启动服务器,以便与它讨论。

$ cd src_v1/
$ ./server

Ready to read incomming messages

(...)

然后,我们创建一个 UDP 客户端,该客户端将通过交换从推断符号生成的消息与服务器(在 127.0.0.1:4242 上)进行通信。在 Netzob 中,参与者是参与与远程对等方通信的高级表示。该参与者能够发送和接收符合状态机(自动机)以及先前学习协议的消息格式(符号)的数据。为了将符号转换为具体消息,或者为了将接收到的具体消息转换为符号,使用了抽象层。该组件确保发送符号的专业化和接收消息的抽象化。

# Create a UDP client instance
channelOut = UDPClient(remoteIP="127.0.0.1", remotePort=4242)
abstractionLayerOut = AbstractionLayer(channelOut, symbols.values())
abstractionLayerOut.openChannel()

# Visit the automata for n iteration
state = automata.initialState
for n in xrange(8):
    state = state.executeAsInitiator(abstractionLayerOut)

我们在自动机中经历了八次迭代。

1454: [INFO] AbstractionLayer:openChannel: Going to open the communication channel...
1454: [INFO] AbstractionLayer:openChannel: Communication channel opened.
1454: [INFO] State:executeAsInitiator: Next transition: Open.
1454: [INFO] AbstractionLayer:openChannel: Going to open the communication channel...
1454: [INFO] AbstractionLayer:openChannel: Communication channel opened.
1454: [INFO] State:executeAsInitiator: Transition 'Open' leads to state: State 1.
1455: [INFO] State:executeAsInitiator: Next transition: Transition.
1455: [INFO] AbstractionLayer:writeSymbol: Going to specialize symbol: 'Symbol_CMDidentify' (id=dbea29b9-7e9f-4c2b-be14-625f675569f3).
1455: [INFO] AbstractionLayer:writeSymbol: Data generated from symbol 'Symbol_CMDidentify': 'CMDidentify#\x03\x00\x00\x00\xfc{\xdb'.
1456: [INFO] AbstractionLayer:writeSymbol: Going to write to communication channel...
1456: [INFO] AbstractionLayer:writeSymbol: Writing to commnunication channel donne..
1456: [INFO] AbstractionLayer:readSymbol: Going to read from communication channel...
1456: [INFO] AbstractionLayer:readSymbol: Received data: ''RESidentify#\x00\x00\x00\x00\x00\x00\x00\x00''
1457: [INFO] AbstractionLayer:readSymbol: Received symbol on communication channel: 'Symbol_RESidentify'
1457: [INFO] Transition:executeAsInitiator: Possible output symbol: 'Symbol_RESidentify' (id=49c24e1c-3751-412e-9f6a-f006a7de7492).
1457: [INFO] State:executeAsInitiator: Transition 'Transition' leads to state: State 2.
1457: [INFO] State:executeAsInitiator: Next transition: Transition.
1457: [INFO] AbstractionLayer:writeSymbol: Going to specialize symbol: 'Symbol_CMDinfo' (id=5eb47a57-eccf-4d06-8231-0b1ae87f96a7).
1458: [INFO] AbstractionLayer:writeSymbol: Data generated from symbol 'Symbol_CMDinfo': 'CMDinfo#\x00\x00\x00\x00'.
1458: [INFO] AbstractionLayer:writeSymbol: Going to write to communication channel...
1458: [INFO] AbstractionLayer:writeSymbol: Writing to commnunication channel donne..
1458: [INFO] AbstractionLayer:readSymbol: Going to read from communication channel...
1458: [INFO] AbstractionLayer:readSymbol: Received data: ''RESinfo#\x00\x00\x00\x00\x04\x00\x00\x00info''
1462: [INFO] AbstractionLayer:readSymbol: Received symbol on communication channel: 'Symbol_RESinfo'
1462: [INFO] Transition:executeAsInitiator: Possible output symbol: 'Symbol_RESinfo' (id=b41502e3-21ea-4cb9-9c1e-dc171f715685).
1462: [INFO] State:executeAsInitiator: Transition 'Transition' leads to state: State 3.
1462: [INFO] State:executeAsInitiator: Next transition: Transition.
(...)

关于真实服务器,我们可以看到接收到的消息格式良好,因为服务器能够解析它们并发送正确的响应。

$ ./server 

Ready to read incomming messages
-> Read: CMDidentify#.
   Command: CMDidentify
   Arg size: 2
   Arg content: ..
<- Send: 
   Return value: 0
   Size of data buffer: 0
   Data buffer: 
    ""

-> Read: CMDinfo#
   Command: CMDinfo
   Arg size: 0
<- Send: 
   Return value: 0
   Size of data buffer: 4
   Data buffer: 
   DATA: 69 6e 66 6f                                        "info"

-> Read: CMDstats#
   Command: CMDstats
   Arg size: 0
<- Send: 
   Return value: 0
   Size of data buffer: 5
   Data buffer: 
   DATA: 73 74 61 74 73                                     "stats"

-> Read: CMDauthentify#.
   Command: CMDauthentify
   Arg size: 6
   Arg content: ......
<- Send: 
   Return value: 0
   Size of data buffer: 0
   Data buffer: 
    ""

-> Read: CMDencrypt#.
   Command: CMDencrypt
   Arg size: 2
   Arg content: ..
<- Send: 
(...)

对特定符号进行一些模糊测试

最后,我们自愿扭曲CMDencrypt符号的消息格式,以尝试一些模糊测试。格式修改对应于缓冲区字段(即接收要加密的数据的字段)大小的扩展。

def send_and_receive_symbol(symbol):
    data = symbol.specialize()
    print "[+] Sending: {0}".format(repr(data))
    channelOut.write(data)
    data = channelOut.read()
    print "[+] Receiving: {0}".format(repr(data))

# Update symbol definition to allow a broader payload size
symbols["CMDencrypt"].fields[2].fields[2].domain = Raw(nbBytes=(10, 120))

for i in range(10):
    send_and_receive_symbol(symbols["CMDencrypt"])

我们可以看到 Netzob 仅发送带有可能为 long last 字段的 CMDencrypt 消息:

[+] Sending: 'CMDencrypt#6\x00\x00\x00&\xe0*\xb3\xa8A(\x0b\xd2yA\xb5\xb8\rw\x0fGi\xee\xb3\xd6\xb0<\xfc\xc0\xa7m\xbd\xbc\xde2~\xceE\xe5\xda@\xd4\xed\xed\xf2\xb4\xe7\t\xfbC\xbf\x05\xc6\xce\xfb\x83\xf2\x00'
(...)

在服务器部分,由于最后一个字段的解析错误,我们很快就会遇到分段错误。

$ gdb ./server
(gdb) run
Starting program: /home/fgy/travaux/netzob/git/netzob-resources/experimentations/tutorial_target/src_v1/server 

Ready to read incomming messages
(...)
-> Read: CMDencrypt#6
   Command: CMDencrypt
   Arg size: 54
   Arg content: &?*??A(
wGi???<???m???2~?E??@????????    ?C??

Program received signal SIGSEGV, Segmentation fault.
0x08048bc0 in api_encrypt (in=0x45ce7e32 <Address 0x45ce7e32 out of bounds>, len=3561020133, out=0xb4f2eded <Address 0xb4f2eded out of bounds>) at amo_api.c:80
80        tmpData[i] = (in[i] ^ key) % 0xff;

现在,你可以使用该工具来分析网络未知协议了。

 类似资料: