-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshanonHelpers.lua
More file actions
466 lines (388 loc) · 16.2 KB
/
shanonHelpers.lua
File metadata and controls
466 lines (388 loc) · 16.2 KB
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
-- Helpers Lua module with functions used by shanon.lua
--Module table
local M = {}
--We need our library here as well
local libAnonLua = require "libAnonLua"
--Helper to get raw bytes
--When multiple of the same field are present the field extractor returns a table
--The relativeStackPosition value is used to determine which of the fields to get
--This can happen when multiple same options are present OR multiple of the same protocol
function M.getRaw(tvb, fieldExtractor, relativeStackPosition)
local fieldInfo = { fieldExtractor() }
local returnValue
if fieldInfo[relativeStackPosition] == nil then
error("Error getting field " .. fieldExtractor.name .. " in the " .. relativeStackPosition .. ". instance of this protocol in the chain.")
else
returnValue = tvb:range(fieldInfo[relativeStackPosition].offset,fieldInfo[relativeStackPosition].len):bytes():raw()
end
return returnValue
end
--Similar to M.getRaw but doesn't throw an error if it can't retrieve a field
function M.getRawOptional(tvb, fieldExtractor, relativeStackPosition)
local fieldInfo = { fieldExtractor() }
local returnValue
if fieldInfo[relativeStackPosition] == nil then
--In this method if there is no value we return nil. That's because this field is optional and might not be present.
returnValue = nil
else
returnValue = tvb:range(fieldInfo[relativeStackPosition].offset,fieldInfo[relativeStackPosition].len):bytes():raw()
end
return returnValue
end
--Helper to get the value of a field
function M.getValue(fieldExtractor, relativeStackPosition)
local fieldInfo = { fieldExtractor() }
if fieldInfo[relativeStackPosition] == nil then
error("Error getting field " .. fieldExtractor.name .. " in the " .. relativeStackPosition .. ". instance of this protocol in the chain.")
end
return fieldInfo[relativeStackPosition].value
end
--Helper to get the value of a field that may not be present
function M.getValueOptional(fieldExtractor, relativeStackPosition)
local fieldInfo = { fieldExtractor() }
if fieldInfo[relativeStackPosition] == nil then
return nil
end
return fieldInfo[relativeStackPosition].value
end
--Get length bytes following a particular field
function M.getBytesAfterField(tvb, fieldExtractor, relativeStackPosition, length)
local fieldInfo = { fieldExtractor() }
if fieldInfo[relativeStackPosition] == nil then
error("Error getting " .. length .. " bytes after field: " .. fieldExtractor.name .. " in the " .. relativeStackPosition .. ". instance of this protocol in the chain.")
end
return tvb:range(fieldInfo[relativeStackPosition].offset + fieldInfo[relativeStackPosition].len, length):bytes():raw()
end
--Get length bytes at an offset from a particular field
function M.getBytesAfterFieldWithOffset(tvb, fieldExtractor, relativeStackPosition, offset, length)
local fieldInfo = { fieldExtractor() }
if fieldInfo[relativeStackPosition] == nil then
error("Error getting " .. length .. " bytes at offset " .. offset .. " bytes from end of field: " .. fieldExtractor.name .. " in the " .. relativeStackPosition .. ". instance of this protocol in the chain.")
end
return tvb:range(fieldInfo[relativeStackPosition].offset + fieldInfo[relativeStackPosition].len + offset, length):bytes():raw()
end
--Get all fields of a type within the provided boundaries. If expectedCount is provided, throw error if count doesn't match
function M.getAllWithinBoundariesRaw(tvb, fieldExtractor, leftBoundary, rightBoundary, expectedCount)
local fieldInfo = { fieldExtractor() }
local count = 0
local extractedValues = {}
for j, value in ipairs(fieldInfo) do
if value.offset > leftBoundary and value.offset <= rightBoundary then
count = count + 1
extractedValues[count] = M.getRaw(tvb, fieldExtractor, j)
elseif value.offset > rightBoundary then
break
end
end
if expectedCount ~= nil then
if expectedCount ~= count then
error("Error extracting field: " .. fieldExtractor.name .. "Expected " .. expectedCount .. " fields of this type but encountered " .. count)
end
end
return count, extractedValues
end
--Get length bytes after each field of the provided type within boundaries
--If expectedCount is provided, throw error if count doesn't match
function M.getBytesAfterFieldWithinBoundariesRaw(tvb, fieldExtractor, leftBoundary, rightBoundary, length, expectedCount)
local fieldInfo = { fieldExtractor() }
local count = 0
local extractedValues = {}
for j, value in ipairs(fieldInfo) do
local offset = value.offset + value.len
if offset > leftBoundary and offset <= rightBoundary then
count = count + 1
extractedValues[count] = tvb:range(offset, length):bytes():raw()
elseif offset > rightBoundary then
break
end
end
if expectedCount ~= nil then
if expectedCount ~= count then
error("Error extracting bytes after field: " .. fieldExtractor.name .. "Expected " .. expectedCount .. " fields of this type but encountered " .. count)
end
end
return count, extractedValues
end
--Get length bytes BEFORE each field of the provided type within boundaries
--If expectedCount is provided, throw error if count doesn't match
function M.getBytesBeforeFieldWithinBoundariesRaw(tvb, fieldExtractor, leftBoundary, rightBoundary, length, offset, expectedCount)
local fieldInfo = { fieldExtractor() }
local count = 0
local extractedValues = {}
for j, value in ipairs(fieldInfo) do
local fieldStart = value.offset - offset
if fieldStart > leftBoundary and fieldStart <= rightBoundary then
count = count + 1
extractedValues[count] = tvb:range(fieldStart, length):bytes():raw()
elseif fieldStart > rightBoundary then
break
end
end
if expectedCount ~= nil then
if expectedCount ~= count then
error("Error extracting bytes before field: " .. fieldExtractor.name .. "Expected " .. expectedCount .. " fields of this type but encountered " .. count)
end
end
return count, extractedValues
end
--Get the 1st field within the provided boundaries and throw an error if more than 1
function M.getOnlyOneWithinBoundariesRaw(tvb, fieldExtractor, leftBoundary, rightBoundary)
local count
local fields
count, fields = M.getAllWithinBoundariesRaw(tvb, fieldExtractor, leftBoundary, rightBoundary)
if count > 1 then
error("Error getting field: " .. fieldExtractor.name .. ". Expected 1, found: " .. count)
end
return fields[1]
end
--Get length bytes or all if length=nil remaining data after a single field within the provided boundaries. Throw an error if more than 1 field is present
function M.getBytesAfterOnlyOneWithinBoundaries(tvb, fieldExtractor, leftBoundary, rightBoundary, length)
local fieldInfo = { fieldExtractor() }
local count = 0
local offsets = {}
for j, value in ipairs(fieldInfo) do
if value.offset > leftBoundary and value.offset <= rightBoundary then
count = count + 1
offsets[count] = value.offset + value.len
elseif value.offset > rightBoundary then
break
end
end
if count > 1 then
error("Error getting bytes after field: " .. fieldExtractor.name .. ". Expected 1 field, found: " .. count)
end
if length ~= nil then
return tvb:range(offsets[1], length):bytes():raw()
else
return tvb:range(offsets[1]):bytes():raw()
end
end
--Helper to get remaining data
function M.getRest(tvb, fieldExtractor, relativeStackPosition)
local fieldInfo = { fieldExtractor() }
if fieldInfo[relativeStackPosition] == nil then
error("Error getting remainder of payload after field: " .. fieldExtractor.name .. " in the " .. relativeStackPosition .. ". instance of this protocol in the chain.")
end
return tvb:range(fieldInfo[relativeStackPosition].offset + fieldInfo[relativeStackPosition].len):bytes():raw()
end
--Helper to get rest from specific offset
function M.getRestFromOffset(tvb, offset)
--Get all the data past the offset
return tvb:range(offset):bytes():raw()
end
--Helper to split strings
function M.split(inputString, delimiter)
local result = {}
local tableLength = 0
for match in (inputString..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match)
tableLength = tableLength + 1
end
return result, tableLength
end
--Helper to count occurences of protocol in table
function M.countOccurences(inputTable, tableSize, protocolName, limit)
local count = 0
--Position to stop iterating
local loopStop
if limit ~= nil then
loopStop = limit
else
loopStop = tableSize
end
for i=1,loopStop do
if inputTable[i] == protocolName then
count = count + 1
end
end
return count
end
--For logging
--Log types
M.logError = 1
local logErrorTag = "[ERROR]"
M.logInfo = 2
local logInfoTag = "[INFO]"
M.logWarn = 3
local logWarnTag = "[WARN]"
--Helper to write log file
function M.writeLog(logType, logString)
logFile = io.open("shanon.log", "a+")
timestamp = os.date("%c")
if logType == M.logError then
logFile:write(logErrorTag .. "[" .. timestamp .. "]" .. logString .. "\n")
elseif logType == M.logInfo then
logFile:write(logInfoTag .. "[" .. timestamp .. "]" .. logString .. "\n")
elseif logType == M.logWarn then
logFile:write(logWarnTag .. "[" .. timestamp .. "]" .. logString .. "\n")
end
io.close(logFile)
end
--Warning that an anonymization policy is missing
function M.warnMissingPolicy(protocolName)
M.writeLog(M.logWarn, "Anonymization policy for " .. protocolName .. " not found. Using default!")
end
--Warning that a specific policy option is missing and defaults will be used
function M.warnUsingDefaultOption(protocolName, fieldName, defaultValue)
if type(defaultValue) ~= "string" then
defaultValue = "This default is a table and can not be printed neatly. Please look it up in the documentation!"
end
M.writeLog(M.logWarn, "Invalid or missing anonymization option \"" .. fieldName .. "\" for " .. protocolName .. "! Using default: " .. defaultValue)
end
--Function to end execution and report an error.
function M.crashWithError(logMessage, printMessage)
M.writeLog(M.logError, logMessage)
if printMessage ~= nil then
print(printMessage)
else
print(logMessage)
end
os.exit(-1)
end
--Function to end execution and report missing policy items
function M.crashMissingOption(protocolName, fieldName)
M.crashWithError("Invalid or missing anonymization policy option \"" .. fieldName .. "\" for protocol \"" .. protocolName .. ".\"")
end
--Function to end execution and report missing entire policy
function M.crashMissingPolicy(protocolName)
M.crashWithError("Missing policy: \"" .. protocolName .. "\"", "The anonymization policy for the protocol: \"" .. protocolName .. "\" was not found in the config file.")
end
--Helper to generate a zero payload of a specific length
--Naive algorithm, but we don't predict having to do this for megabytes of infomation
function M.generateZeroPayload(lengthBytes)
zeroByte = ByteArray.new("00"):raw()
result = ""
while(lengthBytes~=0) do
result = result .. zeroByte
lengthBytes = lengthBytes - 1
end
return result
end
--Helper to get the length of a string as a number of bytes
function M.getLengthAsBytes(string, byteCount, addToLength)
local length = 0
--Byte of zeroes used later to fill up the byte count of our output if necessary
local zeroByte = ByteArray.new("00"):raw()
if string ~= nil then
length = string:len()
else
length = 0
end
--If we need to add a fixed amount of bytes to length, such as when we have a known-lenght header being added on top of a payload
if addToLength ~= nil then
length = length + addToLength
end
--Get length as a hex value
local lengthHex = string.format("%x", length)
--Check if the hex value has an even number of digits and add a 0 to the start if not
if lengthHex:len() % 2 ~= 0 then
lengthHex = "0" .. lengthHex
end
--Length as an array of bytes
local lengthBytes = ByteArray.new(lengthHex):raw()
--Check if lengthBytes is long enough and if not add bytes to the start
--If it's longer we have an error
if lengthBytes:len() > byteCount then
error("Error recalculating length. Length takes up more bytes than the provided expected length. Bytes generated: " .. lengthBytes:len() .. "Bytes expected: " .. byteCount)
else
local difference = byteCount - lengthBytes:len()
while difference > 0 do
lengthBytes = zeroByte .. lengthBytes
difference = difference - 1
end
end
return lengthBytes
end
--Helper to turn SetValue_number values into bytes
function M.getSetValueBytes(setValueString, byteCount)
--Byte of zeroes used later to fill up the byte count of our output if necessary
local zeroByte = ByteArray.new("00"):raw()
--Split the setValueString into substrings
local setValueParameters, parameterCount = M.split(setValueString, "_")
local numberString = setValueParameters[2]
local numberValue = tonumber(numberString)
--Just a precaution. This should never happen because we validate the SetValue options with a lua expression when validating the config
if numberValue == nil then
numberValue = 0
error("Error in function getSetValueBytes in shanonHelpers. Function tonumber returned nil when converting string to number. This is a bug in Shanon itself.")
end
--Get number value as hex
local numberValueHex = string.format("%x", numberValue)
--Check if the hex value has an even number of digits and add a 0 to the start if not
if numberValueHex:len() % 2 ~= 0 then
numberValueHex = "0" .. numberValueHex
end
--Number value as an array of bytes
local numberValueBytes = ByteArray.new(numberValueHex):raw()
--Check if we ended up with enough bytes
if numberValueBytes:len() > byteCount then
error("Error converting SetValue option to byte array for insertion into protocol field. Conversion of numerical value produced " .. numberValueBytes:len() .. " bytes, but " .. byteCount .. "expected.")
else
local difference = byteCount - numberValueBytes:len()
while difference > 0 do
numberValueBytes = zeroByte .. numberValueBytes
difference = difference - 1
end
end
return numberValueBytes
end
--Helper to turn numerical value into N bytes
function M.numberToBytes(number, byteCount)
--Byte of zeroes used later to fill up the byte count of our output if necessary
local zeroByte = ByteArray.new("00"):raw()
--Get number value as hex
local numberValueHex = string.format("%x", number)
--Check if the hex value has an even number of digits and add a 0 to the start if not
if numberValueHex:len() % 2 ~= 0 then
numberValueHex = "0" .. numberValueHex
end
--Number value as an array of bytes
local numberValueBytes = ByteArray.new(numberValueHex):raw()
--Check if we ended up with enough bytes
if numberValueBytes:len() > byteCount then
error("Error converting number to byte array. Conversion of numerical value produced " .. numberValueBytes:len() .. " bytes, but " .. byteCount .. "expected.")
else
local difference = byteCount - numberValueBytes:len()
while difference > 0 do
numberValueBytes = zeroByte .. numberValueBytes
difference = difference - 1
end
end
return numberValueBytes
end
--Helpers for dealing with the config file
--Get the config file path
function M.configGetOutputPath(config)
if config ~= nil and config.outputFile ~= nil then
return config.outputFile
else
M.writeLog(M.logWarn, "No output file path provided in config, file will be stored in same folder as Shanon with the prefix shanon_output_ and the current date and time.")
return "shanon_output_" .. os.date("%d_%b_%Y_%H_%M") .. ".pcapng"
end
end
--Get the CryptoPAN key file
function M.configGetCryptoPANKeyFile(config)
if config ~=nil and config.cryptoPANKeyFile ~= nil then
return config.cryptoPANKeyFile
else
M.writeLog(M.logWarn, "No CryptoPAN key file path provided in config, file will be stored in same folder as Shanon with the prefix shanon_cyrptoPAN_key and the current date and time.")
return "shanon_cryptoPAN_key_" .. os.date("%d_%b_%Y_%H_%M") .. ".key"
end
end
--Parse a black marker string and return the direction and length
--This function does not validate the string so it needs to be validated first
function M.getBlackMarkerValues(blackMarkerString)
--Split the black marker into substrings
local blackMarkerParameters, parameterCount = M.split(blackMarkerString, "_")
local blackMarkerDirection
local blackMarkerLength
if blackMarkerParameters[2] == "MSB" then
blackMarkerDirection = libAnonLua.black_marker_MSB
else
blackMarkerDirection = libAnonLua.black_marker_LSB
end
blackMarkerLength = tonumber(blackMarkerParameters[3])
return blackMarkerDirection, blackMarkerLength
end
--Return the module table
return M