MS Email Header: x-ms-exchange-organization-aadsender-creationtime

Hello,
In a Microsoft email header, I find:

x-ms-exchange-organization-aadsender-creationtime: 637903162460000000

This looks like a filetime but converts to year 3622. I must be doing something wrong. Is there a re-ordering or some other step needed?

python code:
import datetime

‘’’ Define the timestamp and epoch’‘’

timestamp = 637903162460000000

epoch = datetime.datetime(1601, 1, 1)

‘’‘Calculate the seconds since the epoch’‘’

seconds_since_epoch = timestamp / 10**7

‘’’ Add seconds to epoch to get the actual time’‘’

actual_time = epoch + datetime.timedelta(seconds=seconds_since_epoch)

print(“Datetime:”, actual_time)

There is another Microsoft time unit that might be relevant here:

This would be the number of 100-nanosecond intervals that have elapsed since 12:00:00 AM, January 1, 0001 (as opposed to 1601 for FILETIME).

In your example, 637903162460000000 Ticks would represent 6/8/2022 8:17:26 PM—1,600 years earlier than your calculation.

I am not sure if X-MS-Exchange-Organization-AADSender-CreationTime represents a timestamp in Ticks, but it is a possibility.

Thanks Arman! This date is much closer to the date on the email in plain text but it should be Nov. 8th not June 8th. Time is correct too. I’m guessing this X-MS-Exchange-Organization-AADSender-CreationTime field is not the email creation time. An email from Nov. 14th has almost the same value: 637903163330000000 (333 vs. 246) - a minute ahead.

Any other hidden field I can search for with email creation time? dkim is not present. There is a boundary field:

boundary=“000_BL3PR05MB91876F050DC78386F78CABA6993F9BL3PR05MB9187namp

I cannot point you to official documentation on that header. But, I believe the implication is that the header reflects the creation time of the Azure Active Directory object representing the sender of the message. So, I would expect the X-MS-Exchange-Organization-AADSender-CreationTime value to be earlier than the sent date of the email.

If you have the message in a MAPI container, I would start by reviewing all available MAPI properties—especially the ones that are of a date/time type. Some other properties such as Conversation Index may be available in the headers and may provide information on the timing of the email. You may also find other hidden timestamps in the Content-IDs, etc.

I don’t find the conversation index in my msg file. There is a thread-Index:

Thread-Index: AQHY760sLnptc6Vq6EuT1OpV3bqRc64uscNwgAaz4GA=

but the above format is not the same as the conversation index. This date is 2097?

Take a look here:

Thanks Arman. The free tool is never emailed so I’m not sure if that works for this thread-id or not. I’ve tried to decode manually but get odd results where dates are either to early (1830’s) or to distant in the future.

For example this code below gives me 1830’s dates:

 import base64, struct, datetime

   def parse_thread_index(index):

    s = base64.b64decode(index)
    print(s)

    guid = struct.unpack('>IHHQ', s[6:22])
    guid = '{%08X-%04X-%04X-%04X-%12X}' % (guid[0], guid[1], guid[2], (guid[3] >> 48) & 0xFFFF, guid[3] & 0xFFFFFFFFFFFF)

    f = struct.unpack('>Q', s[:6] + b'\0\0')[0]
    ts = [datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=f//10)]

    for n in range(22, len(s), 5):
        f = struct.unpack('>I', s[n:n+4])[0]
        ts.append(ts[-1] + datetime.timedelta(microseconds=(f<<18)//10))

    return guid, ts

with this output:


string decoded:  b'\x01\x01\xd8\xef\xad,.zms\xa5j\xe8K\x93\xd4\xeaU\xdd\xba\x91s\xae.\xb1\xc3p\x80\x06\xb3\xe0`'
timestamps:  [datetime.datetime(1830, 12, 28, 20, 14, 52, 876390), datetime.datetime(1833, 6, 2, 11, 45, 22, 506546), datetime.datetime(1835, 3, 16, 4, 27, 12, 890878)]
guid:  {2E7A6D73-A56A-E84B-93D4-EA55DDBA9173}

I would move your start point one byte forward:

f = struct.unpack('>Q', s[1:7] + b'\0\0')[0]

That helped get the first email correct. I still have a timespan error as the second and third email times are off. They should be much closer to the first. New code below:

import base64, struct, datetime

def parse_thread_index(index):

s = base64.b64decode(index)
print("string decoded: ",s)

guid = struct.unpack('>IHHQ', s[6:22])
print("guid: ", guid)
guid = '{%08X-%04X-%04X-%04X-%12X}' % (guid[0], guid[1], guid[2], (guid[3] >> 48) & 0xFFFF, guid[3] & 0xFFFFFFFFFFFF)

f = struct.unpack('>Q', s[1:7] + b'\0\0')[0]
print("f: ", f)
print("f//10: ", f//10)
ts = [datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=f//10)]

for n in range(23, len(s), 5):
    f = struct.unpack('>I', s[n:n+4])[0]
    print(" F:" , f)
    ts.append(ts[-1] + datetime.timedelta(microseconds=(f<<18) //10))

return guid, ts

returns:

string decoded: b’\x01\x01\xd8\xef\xad,.zms\xa5j\xe8K\x93\xd4\xeaU\xdd\xba\x91s\xae.\xb1\xc3p\x80\x06\xb3\xe0`’
guid: (779775347, 42346, 59467, 10652396673151177075)
f: 133119716057022464
f//10: 13311971605702246
F: 783401840
F: 112451680
timestamps: [datetime.datetime(2022, 11, 3, 17, 53, 25, 702246), datetime.datetime(2023, 6, 29, 10, 26, 54, 896742), datetime.datetime(2023, 8, 2, 13, 17, 48, 216934)]
guid: {2E7A6D73-A56A-E84B-93D4-EA55DDBA9173}

Blockquote
The free tool is never emailed so I’m not sure if that works for this thread-id or not.

I finally received the free tool but it doesn’t like the tread-index value below.

AQHY760sLnptc6Vq6EuT1OpV3bqRc64uscNwgAaz4GA=

returns: Please enter a valid Hex or Base64 conversation index value.

Makes sense—the free tool from Meridian is limited to the type of Conversation Index values described in that blog post.