Writeup for a CTF Challenge Based on the MMS Industrial Protocol

Summary0x01 Challenge. A substation uses IEC 61850 for data collection between the station and bay layers. Analyze the capture, learn the MMS protocol, and find the hidden flag. File: question_1531222544_JYvFGmLP49PFC0R2.pcap.zip. 0x02 Background. MMS is the Manufacturing Message Specification…

ICS Security

0x01 Challenge Description

Figure 1
Figure 1

Challenge: a substation uses IEC 61850 to collect data between the station and bay layers. Analyze the network capture, understand MMS, and find the hidden flag.

File: question_1531222544_JYvFGmLP49PFC0R2.pcap.zip; download from the Baidu Cloud link below.

0x02 Background

MMS, the Manufacturing Message Specification, is an industrial-control communication protocol defined by ISO/IEC 9506.

This article does not cover MMS in full; it only introduces the packet structures needed here.

For more detail, see the MMS article linked through Baidu Cloud at the end.

1、Initiate

TEXT
MMSPdu Received ::=
A8 25 80 02 08 00 81 01 05 82 01 05 83 01 05 A4 16 80 01 01 81 03 05 F8 00 82 0C 03 EE 19 00 18 00 02 00 00 00 FD 18

Tag Length Value -- Tag definition

--------------------------------------------------------------------------------------------------

[8] A8  25  -- initiate-RequestPDU,

    A8 = 1010 1000;

    bit7,6 = Tag type, 00 = Universal tag, 10 = Context specific

    bit5 = , 0 = Primitive, 1 =  Contructed

    (

         00 0 = INTEGER, BITSTRING, BOOLEAN

         00 1 = SEQUENCE, SEQUENCE OF

         10 0 = IMPLICIT

         10 1 = IMPLICIT SEQUENCE, IMPLICIT SEQUENCE OF

     )

    bit4-0 = Value, for primitive Universal tags, value defined in ASN.1, other use [x] in MMS.


{

        [0] 80  02  08 00 -  localDetailCalling (maxProposedMMSPduSize) = 2048 bytes

        [1] 81 01 05  --  proposedMaxServOutstandingCalling

        [2] 82 01 05  --   proposedMaxServOutstandingCalled

        [3] 83 01 05  --  proposedDataStructureNestingLevel

        [4] A4 16 --  mmsInitRequestDetail

    {

            [0] 80 01 01  --  proposedVersionNumber, MMS ISO IS 9506

            [1] 81 03 05 F8 00  --  proposedParameterCBB,

            BitString(11 bits used)

            05 = indicate number of unused bit

            F8 00

            {

                    str1 (bit 0 / array support / MSB of F8)  --  supported

                    str2 (bit 1 / structure support)  --  supported

                    vnam (bit 2 / named variable support)  --  supported

                    valt(bit 3 /alternate access support)  --  supported

                    vadr(bit 4/ unnamed variable support)  --  supported

                    viscera(bit 5/ scattered access support)  --  not-supported

                    toy(bit 6/ third party operations support)  --  not-supported

                    villas(bit 7/ named variable list support)  --  not-supported

                    real(bit 8 / ASN.1 real data type support)  --  not-supported

                    ache(bit 9/ acknowledge event conditionsupport)  --  not-supported

                    chi(bit 10 / condition event support)  --  not-supported

            }

    [2] 82 0C 03 EE 19 00 18 00 02 00 00 00 FD 18 --  servicesSupportedCalling, see ISO/IEC-9506

    }

}

2、Initiate-Response

TEXT

MMSPdu Received ::=

A9 25 80 02 08 00 81 01  05 82 01 05 83 01 05 A416 80 01 01 81 03 05 F8  00 82 0C 03 EE 19 00 18 00 02 00 00 00 FD 18


[9] A9 25  --  Initiate-ResponsePDU

{

        [0] 80 02 08 00  --  localDetailCalled

        [1] 81 01 05  --  negotiatedMaxServOutstandingCalling

        [2] 82 01 05  --  negotiatedMaxServOutstandingCalled

        [3] 83 01 05  --  negotiatedDataStructureNestingLevel

        [4] A4 16

        {

                [0] 80 01 01  --  negotiatedVersionNumber

                [1] 81 03 05 F8 00  --  negotiatedParameterCBB

                [2] 82 0C 03 EE 19 00 18 00 02 00 00 00 FD 18  --  servicesSupportedCalled

        }

}

3、Identify

TEXT
MMSPdu Received ::=

A0 05 02 01 01 82 00


[0] A0 05  --  Confirmed-RequestPDU

{

    02 01 01  --  invokeID

    ( 02 = 000 0 0010, Universal Primitive tag, Tag Value = 2 = Integer. )

    [2] 82 00  --  ConfirmedServiceRequest, 82 = Request identify

}

where, invokeID ::=01
4、Identify-Response
MMSPdu Received ::=

A1 2A 02 01 01 A2 25 80  0B 53 49 53 43 4F 2C 2049 6E 63 2E 81 10 41 58  53 34 2D 4D 4D 53 2D 3133 32 2D 30 31 38 82 04  32 2E 30 30

[1] A1 2A  --  Confirmed-ResponsePDU

{

    02 01 01  --  invokeID, Integer

    [2] A2 25  --  ConfirmedServiceResponse, A2 = Response identify.

    {

            [0] 80 0B 53 49 53 43 4F 2C 20 49 6E 63 2E  --  vendorName

            [1] 81 10 41 58 53 34 2D 4D 4D 53 2D 31 33 32 2D 30 31 38  --   modelName

            [2] 82 04 32 2E 30 30  --  revision

    }

}


where,

invokeID::=01 note: matching of response to  is done by matching invokeID with response invokeID.

vendorName::="SISCO, Inc"

modelName::="AXS4-MMS-132-018"

revision::="2.00"

check [MMS and ASN.1 Encoding] page 10.
5、Read-Request
MMSPdu Received ::=

A0 1E 02 01 0A A4 19 A1  17 A0 15 30 13 A0 11 80 0F 66 65 65 64 65 72 31  5F 33 5F 70 68 61 73 65


[0]  A0 1E  --  ConfirmedRequestPDU

{

    02 01 0A  --  invokeID

    [4] A4 19  --  ConfirmedServiceRequest, A4 = Read

    {

        [1] A1 17  --  variableAccessSpecification

        {

            [0] 30 13  --  listOfVariable

            (30 = 00 1 10000, Universal Constructed)

            {

                [0] A0 11  --  variableSpecification

                {

                    [0] 80 0F 66 65 65 64 65 72 31 5f 33 5f 70 68 61 73 65  --  name

                }

            }

        }

    }

}

where,

invokeID::=0A

Identifier (name of variable to read)::="feeder1_3_phase"

6、Read -Response

TEXT
Assume

    typedef struct var_def

    {

        int a;

        int b;

    } VAR_DEF;

    VAR_DEF feeder1_3_phase;

MMS Data Production


    Data ::= CHOICE

    {

            [1] IMPLICIT SEQUENCE OF,-- arrayed data

            [2] IMPLICIT SEQUENCE OF,-- structured data

            [3] IMPLICIT BOOLEAN,

            [4] IMPLICIT  BIT STRING,

            [5] IMPLICIT INTEGER,-- signed int

            [6] IMPLICIT INTEGER,-- unsigned int

            [7] IMPLICIT Floating Point,

            [9] IMPLICIT OCTET STRING,

            [10] IMPLICIT VisibleString,

            [11] IMPLICIT GeneralizedTime,

            [12] IMPLICIT TimeofDay,

            [13] IMPLICIT INTEGER,-- BCD

            [14] IMPLICIT BIT STRING,-- boolean array

            [15] IMPLICIT OBJECT IDENTIFIER

    }


The encoded structure of the encoded data can be determined via VAR_DEF


        VAR_DEF::=          TAG

        -------------------------------------------

        struct {               A2

            inta;              85

            intb;              85

        }


MMSPdu Received ::=

A1 0F 02 01 0A A4 0A A1  08 A2 06 85 01 00 85 0100

[1] A1 0F  --  ConfirmedResponsePDU

{

    02 01 0A  --  invokeID

    [4] A4 0A  --  ConfirmedServiceResponse, A4 = Read

    {

        [1] OF A1 08  --  listOfAccessResult

            {
                A2 06  --  success, Data of struct

                {
                    85 01 00  --  int a;
                    85 01 00  --  int b;
            }

        }

    }

}

where,

invokeID::=0A

value of a::=00, value of b::=00

Three MMS packet types are shown here.

Analyzing these three packet types reveals several MMS protocol structures:

Initiate

initiate-RequestPDU contains:

  • localDetailCalling
  • proposedMaxServOutstandingCalling
  • proposedMaxServOutstandingCalled
  • proposedDataStructureNestingLevel
  • mmsInitRequestDetail
  • proposedVersionNumber
  • proposedParameterCBB
  • servicesSupportedCalling and related information

Initiate-ResponsePDU contains:

  • localDetailCalled
  • negotiatedMaxServOutstandingCalling
  • negotiatedMaxServOutstandingCalled
  • negotiatedDataStructureNestingLevel
  • negotiatedVersionNumber
  • negotiatedParameterCBB
  • servicesSupportedCalled and related information

Identify:

Confirmed-RequestPDU contains:

  • invokeID
  • tag
  • ConfirmedServiceRequest and related information

Confirmed-ResponsePDU contains:

  • invokeID
  • ConfirmedServiceResponse
  • vendorName
  • modelName
  • revision and related information

Read:

ConfirmedRequestPDU contains:

  • invokeID
  • ConfirmedServiceRequest
  • variableAccessSpecification
  • listOfVariable
  • variableSpecification
  • name and related information

ConfirmedResponsePDU contains:

  • invokeID
  • ConfirmedServiceResponse
  • listOfAccessResult and related information

This is only a simplified explanation. The three packet types above do not cover every MMS protocol data unit or structure.

For deeper study, download GBT16720.2-2005, Industrial Automation Systems—Manufacturing Message Specification, Part 2: Protocol Specification, from the Baidu Cloud link below.

0x03 Solution

For a packet-analysis CTF challenge, the first step is to search for the keyword 'flag'.

Figure 2
Figure 2

Searching for keywords finds flag.txt. Search again for that value near

TEXT
1771 146.535714 192.168.2.112 192.168.2.53 MMS 110 513 confirmed-RequestPDU

This reveals:

Figure 3
Figure 3

The packet above is a RequestPDU with this request content:

TEXT
MMS
    confirmed-RequestPDU
        invokeID: 513
        confirmedServiceRequest: fileOpen (72)
            fileOpen
                fileName: 1 item
                    FileName item: flag.txt
                initialPosition: 0

This corresponds to the RequestPDU structure.

The corresponding ResponsePDU must exist.

The data we need is not the fileOpen (72) stream.

fileOpen tells us only the name of the file being opened.

The file list contains flag.txt, and the operation we need is fileRead or fileWrite.

The flag.txt entry is at line 1771, so filter for the ResponsePDU of fileRead or fileWrite.

The flag should appear within the following records after line 1771.

Since fileOpen is 72, one might expect 73 to be fileRead, 74 fileWrite, and 75 fileClose. However:

Figure 4
Figure 4
Figure 5
Figure 5
Figure 6
Figure 6

73 is indeed fileRead, but 74 is fileClose and 75 returns empty content, confirming that no write operation exists.

Apply the filter mms.confirmedServiceRequest == 73 and search after line 1771 for fileData from a read of flag.txt.

Figure 7
Figure 7

After line 1771, the nearest relevant entry is line 1800, a RequestPDU. Locate the matching ResponsePDU with invokeID 527:

Figure 8
Figure 8

The fileData value is 363138353040313032.

The corresponding ASCII text is 61850@102.

This is the final flag.

0x04 Conclusion

The elegant solution requires only the structures of Confirmed-RequestPDU and Confirmed-ResponsePDU in MMS. Find the matching service and solve it with a short script:

TEXT
import pyshark

def flag():
    try:
        captures = pyshark.FileCapture("question_1531222544_JYvFGmLP49PFC0R2.pcap")
        flag_frsm = False
        flag_frsm_id = None
        flag_read = False
        for capture in captures:
            for pkt in capture:
                if pkt.layer_name == "mms":
                    # file open
                    if hasattr(pkt, "confirmedservicerequest") and int(pkt.confirmedservicerequest) == 72:
                        if hasattr(pkt, "filename_item"):
                            filename_items = pkt.filename_item.fields
                            for f in filename_items:
                                file_name = str(f.get_default_value())
                                if file_name == "flag.txt":
                                    flag_frsm = True
                    if hasattr(pkt, "confirmedserviceresponse") and int(pkt.confirmedserviceresponse) == 72 and flag_frsm:
                        # print(pkt.field_names)
                        if hasattr(pkt, "frsmid"):
                            flag_frsm_id = pkt.frsmid
                        flag_frsm = False
                    # file read
                    if hasattr(pkt, "confirmedservicerequest") and int(pkt.confirmedservicerequest) == 73 and flag_frsm_id:
                        if hasattr(pkt, "fileread"):
                            if str(pkt.fileread) == str(flag_frsm_id):
                                flag_read = True
                        flag_frsm_id = None
                    if hasattr(pkt, "confirmedserviceresponse") and int(pkt.confirmedserviceresponse) == 73 and flag_read:
                        if hasattr(pkt, "filedata"):
                            data = str(pkt.filedata).replace(":", "")
                            print(hex_to_ascii(data))
                        flag_read = False
    except Exception as e:
        print(e)


def hex_to_ascii(data):
    data = data.decode("hex")
    flags = []
    for d in data:
        _ord = ord(d)
        if (_ord > 0) and (_ord < 128):
            flags.append(chr(_ord))
    return ''.join(flags)


if __name__ == '__main__':
flag()

Representative industrial protocols include MMS, Modbus, DNP3, MELSEC-Q, S7, and EtherNet/IP.

Challenges can be designed in many ways, but familiarity with the protocol makes them much easier to solve.

I had only just begun studying ICS security, so corrections are welcome. Anyone interested is also welcome to study ICS security together.

0x04 References

MMS Packet Analysis Example

2018 Industrial Information Security Skills Competition, Northeast Region: Industrial Network Data Analysis Writeup

ICS_CTF Contest

===================== Links:

Link:https://pan.baidu.com/s/1wI_1fErCAvvQIF_DYPIOiQ Password: 9mb8