summaryrefslogtreecommitdiffstats
path: root/nselib/msrpcperformance.lua
blob: 0656888ecfae2e7fa9ac0875680ffe6d936a7c89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
---
-- This module is designed to parse the <code>PERF_DATA_BLOCK</code> structure, which is
-- stored in the registry under HKEY_PERFORMANCE_DATA. By querying this structure, you can
-- get a whole lot of information about what's going on.
--
-- To use this from a script, see <code>get_performance_data</code>, it is the only
-- "public" function in this module.
--
-- My primary sources of information were:
-- * This 1996 journal by Matt Pietrek: <http://www.microsoft.com/msj/archive/S271.aspx>
-- * The followup article: <http://www.microsoft.com/msj/archive/S2A9.aspx>
-- * The WinPerf.h header file
--
-- And my primary inspiration was PsTools, specifically, <code>pstasklist.exe</code>.
--
--@author Ron Bowes <ron@skullsecurity.net>
--@copyright Same as Nmap--See https://nmap.org/book/man-legal.html
-----------------------------------------------------------------------

local msrpc = require "msrpc"
local msrpctypes = require "msrpctypes"
local stdnse = require "stdnse"
local string = require "string"
_ENV = stdnse.module("msrpcperformance", stdnse.seeall)

---Parses the title database, which is a series of null-terminated string pairs.
--
--@param data     The data being processed.
--@param pos      The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
--                              message), and a table representing the datatype, if any.
local function parse_perf_title_database(data, pos)
  local result = {}
  local i = 1

  repeat
    local number, name
    number, name, pos = string.unpack("<zz", data, pos)

    if(number == nil) then
      return false, "Couldn't parse the title database: end of string encountered early"
    elseif(tonumber(number) == nil) then -- Not sure if this actually happens, but it doesn't hurt to check
      stdnse.debug1("MSRPC: ERROR: Couldn't parse the title database: string found where number expected (%d: '%s')", i, number)
      return false, "Couldn't parse the title database"
    end

    result[tonumber(number)] = name
    i = i + 1
  until pos >= #data

  return true, pos, result
end

---Parses a PERF_DATA_BLOCK, which has the following definition (from "WinPerf.h" on Visual Studio 8):
--
--<code>
--  typedef struct _PERF_DATA_BLOCK {
--    WCHAR           Signature[4];       // Signature: Unicode "PERF"
--    DWORD           LittleEndian;       // 0 = Big Endian, 1 = Little Endian
--    DWORD           Version;            // Version of these data structures
--                                        // starting at 1
--    DWORD           Revision;           // Revision of these data structures
--                                        // starting at 0 for each Version
--    DWORD           TotalByteLength;    // Total length of data block
--    DWORD           HeaderLength;       // Length of this structure
--    DWORD           NumObjectTypes;     // Number of types of objects
--                                        // being reported
--    LONG            DefaultObject;      // Object Title Index of default
--                                        // object to display when data from
--                                        // this system is retrieved (-1 =
--                                        // none, but this is not expected to
--                                        // be used)
--    SYSTEMTIME      SystemTime;         // Time at the system under
--                                        // measurement
--    LARGE_INTEGER   PerfTime;           // Performance counter value
--                                        // at the system under measurement
--    LARGE_INTEGER   PerfFreq;           // Performance counter frequency
--                                        // at the system under measurement
--    LARGE_INTEGER   PerfTime100nSec;    // Performance counter time in 100 nsec
--                                        // units at the system under measurement
--    DWORD           SystemNameLength;   // Length of the system name
--    DWORD           SystemNameOffset;   // Offset, from beginning of this
--                                        // structure, to name of system
--                                        // being measured
--  } PERF_DATA_BLOCK, *PPERF_DATA_BLOCK;
--</code>
--
--@param data     The data being processed.
--@param pos      The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
--                              message), and a table representing the datatype, if any.
local function parse_perf_data_block(data, pos)
  local result = {}

  pos, result['Signature'] = msrpctypes.unicode_to_string(data, pos, 4, false)
  if(result['Signature'] ~= "PERF") then
    return false, "MSRPC: PERF_DATA_BLOCK signature is missing or incorrect"
  end

  pos, result['LittleEndian'] = msrpctypes.unmarshall_int32(data, pos)
  if(result['LittleEndian'] ~= 1) then
    return false, "MSRPC: PERF_DATA_BLOCK returned a non-understood endianness"
  end

  -- Parse the header
  pos, result['Version']          = msrpctypes.unmarshall_int32(data, pos)
  pos, result['Revision']         = msrpctypes.unmarshall_int32(data, pos)
  pos, result['TotalByteLength']  = msrpctypes.unmarshall_int32(data, pos)
  pos, result['HeaderLength']     = msrpctypes.unmarshall_int32(data, pos)
  pos, result['NumObjectTypes']   = msrpctypes.unmarshall_int32(data, pos)
  pos, result['DefaultObject']    = msrpctypes.unmarshall_int32(data, pos)
  pos, result['SystemTime']       = msrpctypes.unmarshall_SYSTEMTIME(data, pos)
  pos, result['PerfTime']         = msrpctypes.unmarshall_int64(data, pos)
  pos, result['PerfFreq']         = msrpctypes.unmarshall_int64(data, pos)
  pos, result['PerfTime100nSec']  = msrpctypes.unmarshall_int64(data, pos)
  pos = pos + 4 -- This value doesn't seem to line up, so add 4

  pos, result['SystemNameLength'] = msrpctypes.unmarshall_int32(data, pos)
  pos, result['SystemNameOffset'] = msrpctypes.unmarshall_int32(data, pos)

  -- Ensure that the system name is directly after the header. This technically shouldn't matter, but Microsoft's documentation
  -- (in WinPref.h) says that the actual object comes "after the PERF_DATA_BLOCK", so it doesn't make sense that the SystemName
  -- could be anywhere else.
  if(pos ~= result['SystemNameOffset'] + 1) then
    return false, "MSRPC: PERF_DATA_BLOCK has SystemName in the wrong location"
  end

  -- Read the system name from the next location (which happens to be identical to SystemNameOffset, on a proper system)
  pos, result['SystemName'] = msrpctypes.unicode_to_string(data, pos, result['SystemNameLength'] / 2, true)

  pos = pos + 4 -- Again, we end up not lined up so here we fix it

  return true, pos, result
end


---Parse a PERF_OBJECT_TYPE structure. From Microsoft's documentation:
--
--<code>
-- //
-- //  The _PERF_DATA_BLOCK structure is followed by NumObjectTypes of
-- //  data sections, one for each type of object measured.  Each object
-- //  type section begins with a _PERF_OBJECT_TYPE structure.
-- //
-- typedef struct _PERF_OBJECT_TYPE {
--     DWORD           TotalByteLength;    // Length of this object definition
--                                         // including this structure, the
--                                         // counter definitions, and the
--                                         // instance definitions and the
--                                         // counter blocks for each instance:
--                                         // This is the offset from this
--                                         // structure to the next object, if
--                                         // any
--     DWORD           DefinitionLength;   // Length of object definition,
--                                         // which includes this structure
--                                         // and the counter definition
--                                         // structures for this object: this
--                                         // is the offset of the first
--                                         // instance or of the counters
--                                         // for this object if there is
--                                         // no instance
--     DWORD           HeaderLength;       // Length of this structure: this
--                                         // is the offset to the first
--                                         // counter definition for this
--                                         // object
--     DWORD           ObjectNameTitleIndex;
--                                         // Index to name in Title Database
-- #ifdef _WIN64
--     DWORD           ObjectNameTitle;    // Should use this as an offset
-- #else
--     LPWSTR          ObjectNameTitle;    // Initially NULL, for use by
--                                         // analysis program to point to
--                                         // retrieved title string
-- #endif
--     DWORD           ObjectHelpTitleIndex;
--                                         // Index to Help in Title Database
-- #ifdef _WIN64
--     DWORD           ObjectHelpTitle;    // Should use this as an offset
-- #else
--     LPWSTR          ObjectHelpTitle;    // Initially NULL, for use by
--                                         // analysis program to point to
--                                         // retrieved title string
-- #endif
--     DWORD           DetailLevel;        // Object level of detail (for
--                                         // controlling display complexity);
--                                         // will be min of detail levels
--                                         // for all this object's counters
--     DWORD           NumCounters;        // Number of counters in each
--                                         // counter block (one counter
--                                         // block per instance)
--     LONG            DefaultCounter;     // Default counter to display when
--                                         // this object is selected, index
--                                         // starting at 0 (-1 = none, but
--                                         // this is not expected to be used)
--     LONG            NumInstances;       // Number of object instances
--                                         // for which counters are being
--                                         // returned from the system under
--                                         // measurement. If the object defined
--                                         // will never have any instance data
--                                         // structures (PERF_INSTANCE_DEFINITION)
--                                         // then this value should be -1, if the
--                                         // object can have 0 or more instances,
--                                         // but has none present, then this
--                                         // should be 0, otherwise this field
--                                         // contains the number of instances of
--                                         // this counter.
--     DWORD           CodePage;           // 0 if instance strings are in
--                                         // UNICODE, else the Code Page of
--                                         // the instance names
--     LARGE_INTEGER   PerfTime;           // Sample Time in "Object" units
--                                         //
--     LARGE_INTEGER   PerfFreq;           // Frequency of "Object" units in
--                                         // counts per second.
-- } PERF_OBJECT_TYPE, *PPERF_OBJECT_TYPE;
--</code>
--
--@param data           The data being processed.
--@param pos            The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
--                              message), and a table representing the datatype, if any.
local function parse_perf_object_type(data, pos)
  local result = {}

  pos, result['TotalByteLength']      = msrpctypes.unmarshall_int32(data, pos) -- Offset to the next object
  pos, result['DefinitionLength']     = msrpctypes.unmarshall_int32(data, pos) -- Offset to the first instance (or counter, if no instances)
  pos, result['HeaderLength']         = msrpctypes.unmarshall_int32(data, pos) -- Offset to the first counter definition
  pos, result['ObjectNameTitleIndex'] = msrpctypes.unmarshall_int32(data, pos) -- Index in the Title Database
  pos, result['ObjectNameTitle']      = msrpctypes.unmarshall_int32(data, pos) -- TODO: will this work with 64-bit?
  pos, result['ObjectHelpTitleIndex'] = msrpctypes.unmarshall_int32(data, pos) -- Index in the Help Database
  pos, result['ObjectHelpTitle']      = msrpctypes.unmarshall_int32(data, pos) -- TODO: will this workw ith 64-bit?
  pos, result['DetailLevel']          = msrpctypes.unmarshall_int32(data, pos)
  pos, result['NumCounters']          = msrpctypes.unmarshall_int32(data, pos) -- The number of counters in each counter block
  pos, result['DefaultCounter']       = msrpctypes.unmarshall_int32(data, pos)
  pos, result['NumInstances']         = msrpctypes.unmarshall_int32(data, pos) -- Numer of object instances for which counters are being returned
  pos, result['CodePage']             = msrpctypes.unmarshall_int32(data, pos) -- 0 if strings are in UNICODE, otherwise the Code Page
  --  if(result['CodePage'] ~= 0) then
  --    return false, string.format("Unknown Code Page for data: %d\n", result['CodePage'])
  --  end
  pos, result['PerfTime']             = msrpctypes.unmarshall_int64(data, pos) -- Sample time in "Object" units
  pos, result['PerfFreq']             = msrpctypes.unmarshall_int64(data, pos) -- Frequency of "Object" units in counts/second

  return true, pos, result
end


---Parse a PERF_COUNTER_DEFINITION structure. From Microsoft's documentation:
--
--<code>
--  //  There is one of the following for each of the
--  //  PERF_OBJECT_TYPE.NumCounters.  The Unicode names in this structure MUST
--  //  come from a message file.
--  typedef struct _PERF_COUNTER_DEFINITION {
--    DWORD           ByteLength;         // Length in bytes of this structure
--    DWORD           CounterNameTitleIndex;
--                                        // Index of Counter name into
--                                        // Title Database
--  #ifdef _WIN64
--    DWORD           CounterNameTitle;
--  #else
--    LPWSTR          CounterNameTitle;   // Initially NULL, for use by
--                                        // analysis program to point to
--                                        // retrieved title string
--  #endif
--    DWORD           CounterHelpTitleIndex;
--                                        // Index of Counter Help into
--                                        // Title Database
--  #ifdef _WIN64
--    DWORD           CounterHelpTitle;
--  #else
--    LPWSTR          CounterHelpTitle;   // Initially NULL, for use by
--                                        // analysis program to point to
--                                        // retrieved title string
--  #endif
--    LONG            DefaultScale;       // Power of 10 by which to scale
--                                        // chart line if vertical axis is 100
--                                        // 0 ==> 1, 1 ==> 10, -1 ==>1/10, etc.
--    DWORD           DetailLevel;        // Counter level of detail (for
--                                        // controlling display complexity)
--    DWORD           CounterType;        // Type of counter
--    DWORD           CounterSize;        // Size of counter in bytes
--    DWORD           CounterOffset;      // Offset from the start of the
--                                        // PERF_COUNTER_BLOCK to the first
--                                        // byte of this counter
--  } PERF_COUNTER_DEFINITION, *PPERF_COUNTER_DEFINITION;
--</code>
--
--@param data           The data being processed.
--@param pos            The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
--                              message), and a table representing the datatype, if any.
local function parse_perf_counter_definition(data, pos)
  local result = {}
  local initial_pos = pos

  pos, result['ByteLength']            = msrpctypes.unmarshall_int32(data, pos)
  pos, result['CounterNameTitleIndex'] = msrpctypes.unmarshall_int32(data, pos)
  pos, result['CounterNameTitle']      = msrpctypes.unmarshall_int32(data, pos)
  pos, result['CounterHelpTitleIndex'] = msrpctypes.unmarshall_int32(data, pos)
  pos, result['CounterHelpTitle']      = msrpctypes.unmarshall_int32(data, pos)
  pos, result['DefaultScale']          = msrpctypes.unmarshall_int32(data, pos)
  pos, result['DetailLevel']           = msrpctypes.unmarshall_int32(data, pos)
  pos, result['CounterType']           = msrpctypes.unmarshall_int32(data, pos)
  pos, result['CounterSize']           = msrpctypes.unmarshall_int32(data, pos)
  pos, result['CounterOffset']         = msrpctypes.unmarshall_int32(data, pos)

  pos = initial_pos + result['ByteLength']

  return true, pos, result
end

---Parse the actual counter value. This is a fairly simple function, it takes a counter
-- definition and pulls out data based on it.
--
-- Note: I don't think this is doing the 8-byte values right, I suspect that they're supposed
-- to be doubles.
--
--@param data           The data being processed.
--@param pos            The position within <code>data</code>.
--@param counter_definition The matching counter_definition.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
--                              message), and a table representing the datatype, if any.
local function parse_perf_counter(data, pos, counter_definition)
  local result

  if(counter_definition['CounterSize'] == 4) then
    pos, result = msrpctypes.unmarshall_int32(data, pos)
  elseif(counter_definition['CounterSize'] == 8) then
    pos, result = msrpctypes.unmarshall_int64(data, pos)
  else
    pos, result = msrpctypes.unmarshall_raw(data, pos, counter_definition['CounterSize'])
  end

  return true, pos, result
end

---Parse a PERF_INSTANCE_DEFINITION structure. From Microsoft's documentation:
--
--<code>
--  //  If (PERF_DATA_BLOCK.NumInstances >= 0) then there will be
--  //  PERF_DATA_BLOCK.NumInstances of a (PERF_INSTANCE_DEFINITION
--  //  followed by a PERF_COUNTER_BLOCK followed by the counter data fields)
--  //  for each instance.
--  //
--  //  If (PERF_DATA_BLOCK.NumInstances < 0) then the counter definition
--  //  structure above will be followed by only a PERF_COUNTER_BLOCK and the
--  //  counter data for that COUNTER.
--  typedef struct _PERF_INSTANCE_DEFINITION {
--    DWORD           ByteLength;         // Length in bytes of this structure,
--                                        // including the subsequent name
--    DWORD           ParentObjectTitleIndex;
--                                        // Title Index to name of "parent"
--                                        // object (e.g., if thread, then
--                                        // process is parent object type);
--                                        // if logical drive, the physical
--                                        // drive is parent object type
--    DWORD           ParentObjectInstance;
--                                        // Index to instance of parent object
--                                        // type which is the parent of this
--                                        // instance.
--    LONG            UniqueID;           // A unique ID used instead of
--                                        // matching the name to identify
--                                        // this instance, -1 = none
--    DWORD           NameOffset;         // Offset from beginning of
--                                        // this struct to the Unicode name
--                                        // of this instance
--    DWORD           NameLength;         // Length in bytes of name; 0 = none
--                                        // this length includes the characters
--                                        // in the string plus the size of the
--                                        // terminating NULL char. It does not
--                                        // include any additional pad bytes to
--                                        // correct structure alignment
--  } PERF_INSTANCE_DEFINITION, *PPERF_INSTANCE_DEFINITION;
--</code>
--
--@param data           The data being processed.
--@param pos            The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
--                              message), and a table representing the datatype, if any.
local function parse_perf_instance_definition(data, pos)
  local result = {}

  -- Remember where we started. I noticed that where the counter part starts can move around, so we have to
  -- determine it by adding ByteLength to the initial position
  local initial_pos = pos

  pos, result['ByteLength']             = msrpctypes.unmarshall_int32(data, pos)
  pos, result['ParentObjectTitleIndex'] = msrpctypes.unmarshall_int32(data, pos)
  pos, result['ParentObjectInstance']   = msrpctypes.unmarshall_int32(data, pos)
  pos, result['UniqueID']               = msrpctypes.unmarshall_int32(data, pos)
  pos, result['NameOffset']             = msrpctypes.unmarshall_int32(data, pos)
  pos, result['NameLength']             = msrpctypes.unmarshall_int32(data, pos)

  pos, result['InstanceName']           = msrpctypes.unicode_to_string(data, pos, result['NameLength'] / 2, true)

  pos = initial_pos + result['ByteLength']

  return true, pos, result
end

---Parse a PERF_COUNTER_BLOCK structure. From Microsoft's documentation:
--
--<code>
--  typedef struct _PERF_COUNTER_BLOCK {
--    DWORD           ByteLength;         // Length in bytes of this structure,
--                                        // including the following counters
--  } PERF_COUNTER_BLOCK, *PPERF_COUNTER_BLOCK;
--
--</code>
--
--@param data           The data being processed.
--@param pos            The position within <code>data</code>.
--@return (status, pos, result) The status (true if successful), the new position in <code>data</code> (or an error
--                              message), and a table representing the datatype, if any.
local function parse_perf_counter_block(data, pos)
  local result = {}

  pos, result['ByteLength'] = msrpctypes.unmarshall_int32(data, pos)

  return true, pos, result
end

---Retrieve the parsed performance data from the given host for the requested
-- object values.
--
-- To get a list of possible object values, leave 'objects' blank and look at
-- <code>result['title_database']</code> -- it'll contain a list of indexes
-- that can be looked up. These indexes are passed as a string or as a series
-- of space-separated strings (eg, "230" for "Process" and "238" for "Process"
-- and "Processor").
--
--@param host The host object
--@param objects [optional] The space-separated list of object numbers to
--               retrieve. Default: only retrieve the database.
function get_performance_data(host, objects)

  -- Create the SMB session
  local status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
  if(status == false) then
    return false, smbstate
  end

  -- Bind to WINREG service
  local status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
  if(status == false) then
    msrpc.stop_smb(smbstate)
    return false, bind_result
  end

  -- Open HKEY_PERFORMANCE_DATA
  local status, openhkpd_result = msrpc.winreg_openhkpd(smbstate)
  if(status == false) then
    msrpc.stop_smb(smbstate)
    return false, openhkpd_result
  end

  local status, queryvalue_result = msrpc.winreg_queryvalue(smbstate, openhkpd_result['handle'], "Counter 009")
  if(status == false) then
    msrpc.stop_smb(smbstate)
    return false, queryvalue_result
  end

  -- Parse the title database
  local pos = 1
  local status
  local result = {}
  status, pos, result['title_database'] = parse_perf_title_database(queryvalue_result['value'], pos)
  if(status == false) then
    msrpc.stop_smb(smbstate)
    return false, pos
  end
  result['title_database'][0] = "<null>"


  if(objects ~= nil and #objects > 0) then
    -- Query for the objects
    local status, queryvalue_result = msrpc.winreg_queryvalue(smbstate, openhkpd_result['handle'], objects)
    if(status == false) then
      msrpc.stop_smb(smbstate)
      return false, queryvalue_result
    end

    -- Parse the header
    pos = 1
    local status, data_block
    status, pos, data_block = parse_perf_data_block(queryvalue_result['value'], pos)
    if(status == false) then
      msrpc.stop_smb(smbstate)
      return false, pos
    end

    -- Move past the header
    pos = 1 + data_block['HeaderLength']

    -- Parse the data sections
    for i = 1, data_block['NumObjectTypes'], 1 do
      local object_start = pos

      local counter_definitions = {}
      local object_instances    = {}
      local counter_definitions = {}

      -- Get the type of the object (this is basically the class definition -- info about the object instances)
      local status, object_type
      status, pos, object_type = parse_perf_object_type(queryvalue_result['value'], pos)
      if(status == false) then
        msrpc.stop_smb(smbstate)
        return false, pos
      end

      -- Start setting up the result object
      --stdnse.debug1("Index = %d\n", object_type['ObjectNameTitleIndex'])
      local object_name = result['title_database'][object_type['ObjectNameTitleIndex']]
      result[object_name] = {}

      --stdnse.debug1("\n\nOBJECT: %s\n", object_name)
      --stdnse.debug1(" Counters: %d\n", object_type['NumCounters'])
      --stdnse.debug1(" Instances: %d\n", object_type['NumInstances'])
      --stdnse.debug1("-----------------\n")

      -- Bring the position to the beginning of the counter definitions
      pos = object_start + object_type['HeaderLength']

      -- Parse the counter definitions
      for j = 1, object_type['NumCounters'], 1 do
        status, pos, counter_definitions[j] = parse_perf_counter_definition(queryvalue_result['value'], pos)
        if(status == false) then
          msrpc.stop_smb(smbstate)
          return false, pos
        end
        --stdnse.debug1(" Counter definition #%2d: [%d bytes] %s\n", j, counter_definitions[j]['CounterSize'], result['title_database'][counter_definitions[j]['CounterNameTitleIndex']])
      end

      -- Bring the position to the beginning of the instances (or counters)
      pos = object_start + object_type['DefinitionLength']

      -- Check if we have any instances (sometimes we don't -- if we don't, the value returned is a negative)
      if (object_type['NumInstances'] & 0x80000000) == 0 then
        -- Parse the object instances and counters
        for j = 1, object_type['NumInstances'], 1 do
          local instance_start = pos

          -- Instance definition
          local status
          status, pos, object_instances[j] = parse_perf_instance_definition(queryvalue_result['value'], pos)
          if(status == false) then
            msrpc.stop_smb(smbstate)
            return false, pos
          end

          -- Set up the instance array
          local instance_name = object_instances[j]['InstanceName']
          result[object_name][instance_name] = {}

          -- Bring the pos to the start of the counter block
          pos = instance_start + object_instances[j]['ByteLength']

          --stdnse.debug1("\n  INSTANCE: %s\n", instance_name)
          --stdnse.debug1("  Length: %d\n",     object_instances[j]['ByteLength'])
          --stdnse.debug1("  NameOffset: %d\n", object_instances[j]['NameOffset'])
          --stdnse.debug1("  NameLength: %d\n", object_instances[j]['NameLength'])
          --stdnse.debug1("  --------------\n")

          -- The counter block
          local status, counter_block
          status, pos, counter_block = parse_perf_counter_block(queryvalue_result['value'], pos)
          if(status == false) then
            msrpc.stop_smb(smbstate)
            return false, pos
          end

          for k = 1, object_type['NumCounters'], 1 do
            -- Each individual counter
            local status, counter_result
            status, pos, counter_result = parse_perf_counter(queryvalue_result['value'], pos, counter_definitions[k])
            if(status == false) then
              msrpc.stop_smb(smbstate)
              return false, pos
            end

            local counter_name = result['title_database'][counter_definitions[k]['CounterNameTitleIndex']]
            --stdnse.debug1("    %s: %s\n", counter_name, counter_result)

            -- Save it in the result
            result[object_name][instance_name][counter_name] = counter_result
          end

          -- Bring the pos to the end of the next section
          pos = instance_start + object_instances[j]['ByteLength'] + counter_block['ByteLength']
        end
      else
        for k = 1, object_type['NumCounters'], 1 do
          -- Each individual counter
          local status, counter_result
          status, pos, counter_result = parse_perf_counter(queryvalue_result['value'], pos, counter_definitions[k])
          if(status == false) then
            msrpc.stop_smb(smbstate)
            return false, pos
          end

          local counter_name = result['title_database'][counter_definitions[k]['CounterNameTitleIndex']]
          --stdnse.debug1("    %s: %s\n", counter_name, counter_result)

          -- Save it in the result
          result[object_name][counter_name] = counter_result
        end
      end
    end

    -- Blank out the database
    result['title_database'] = nil
  end

  msrpc.stop_smb(smbstate)

  return true, result
end



return _ENV;