└── decoder.py /decoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import logging 4 | import sys 5 | import argparse 6 | import os 7 | import struct 8 | 9 | 10 | logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) 11 | logger = logging.getLogger(__name__) 12 | 13 | BLOCK_HEADER_SIZE = 16 14 | BLOCK_END_MARKER_SIZE = 4 15 | 16 | 17 | def decrypt(data, key): 18 | # Apply Ceasar cypher on the data. 19 | 20 | # Pad the key to the size of the data. 21 | pad = len(data)//len(key) + 1 22 | key = key * pad 23 | key = key[:len(data)] 24 | 25 | # XOR the data and the key. 26 | decoded_data = b'' 27 | for (x,y) in zip(data, key): 28 | decoded_data += chr(x^y).encode("utf-8") 29 | return decoded_data 30 | 31 | 32 | def decode_archive(local_user, peer_user, archive_filename): 33 | logger.info("Decoding file [%s] of user [%s] with [%s].", archive_filename, local_user, peer_user) 34 | logger.debug("Opening file [%s] of size [%s] bytes.", archive_filename, os.path.getsize(archive_filename)) 35 | 36 | total_read = 0 37 | archive_file = open(archive_filename, "rb") 38 | while True: 39 | # Read blocke header. 40 | header_bytes = archive_file.read(BLOCK_HEADER_SIZE) 41 | if len(header_bytes) == 0: 42 | break 43 | timestamp, field2, field3, size = struct.unpack("@iiii", header_bytes) 44 | 45 | # Read the message of length specified in the header. 46 | data_bytes = archive_file.read(size) 47 | 48 | end_marker_bytes = archive_file.read(BLOCK_END_MARKER_SIZE) 49 | end_marker = struct.unpack("@i", end_marker_bytes) 50 | total_read += BLOCK_HEADER_SIZE + len(data_bytes) + BLOCK_END_MARKER_SIZE 51 | 52 | print(timestamp, field2, field3, decrypt(data_bytes, local_user.encode("utf-8")).decode("utf-8")) 53 | 54 | archive_file.close() 55 | 56 | 57 | def parse_messages_peer(local_user, peer_user, peer_tree, args): 58 | logger.debug("Parsing logs dir for user [%s] with peer [%s]", local_user, peer_user) 59 | for k, v in peer_tree.items(): 60 | # v should be a leaf of the tree (a file). 61 | if not v: 62 | # Rebuild the path to the file. 63 | dat_file = os.path.join(args.root, local_user, "Archive", "Messages", peer_user, k) 64 | if os.path.isfile(dat_file): 65 | decode_archive(local_user, peer_user, dat_file) 66 | 67 | 68 | def parse_messages(local_user, messages_tree, args): 69 | logger.debug("Parsing Messages dir for user [%s]", local_user) 70 | for k, v in messages_tree.items(): 71 | # Keys with None values are not profiles or are boken. 72 | if v: 73 | parse_messages_peer(local_user, k, v, args) 74 | 75 | 76 | def parse_archive(local_user, archive_tree, args): 77 | logger.debug("Parsing Archive dir for user [%s]", local_user) 78 | if "Messages" in archive_tree: 79 | messages = archive_tree.get("Messages") 80 | # If Messages dir has contents, parse them. 81 | if messages: 82 | parse_messages(local_user, messages, args) 83 | 84 | 85 | def parse_profile(local_user, profile_tree, args): 86 | logger.debug("Parsing tree for user [%s]", local_user) 87 | if "Archive" in profile_tree: 88 | # If Archive dir has contents, parse them. 89 | archive = profile_tree.get("Archive") 90 | if archive: 91 | parse_archive(local_user, archive, args) 92 | 93 | 94 | def parse_profiles(profiles, args): 95 | logger.debug("Parsing profiles [%s]", list(profiles.keys())) 96 | for k, v in profiles.items(): 97 | # Keys with None values are not profiles or are boken. 98 | if v: 99 | parse_profile(k, v, args) 100 | 101 | 102 | def parse_dir_tree(path): 103 | path = os.path.abspath(path) 104 | if os.path.isdir(path): 105 | dir_dict = {} 106 | for filename in os.listdir(path): 107 | sub_path = os.path.join(path, filename) 108 | dir_dict[filename] = parse_dir_tree(sub_path) 109 | return dir_dict 110 | else: 111 | return None 112 | 113 | 114 | def parse_args(): 115 | parser = argparse.ArgumentParser(description="Yahoo Messenger archive decoder.") 116 | parser.add_argument("root", help="Location of Profiles directory") 117 | parser.add_argument("--user", help="Decode archives for specific user.", default=None) 118 | parser.add_argument("--peer", help="Decode archives for peer.", default=None) 119 | args = parser.parse_args() 120 | 121 | return args 122 | 123 | 124 | def main(): 125 | args = parse_args() 126 | 127 | if os.path.isdir(args.root): 128 | profiles_path = os.path.abspath(args.root) 129 | print(profiles_path) 130 | logger.debug("Parsing Profiles directory: [%s]", profiles_path) 131 | dir_tree = parse_dir_tree(profiles_path) 132 | parse_profiles(dir_tree, args) 133 | else: 134 | logger.error("Specified Profiles directory does not exist.") 135 | sys.exit(1) 136 | 137 | 138 | if __name__ == "__main__": 139 | main() 140 | --------------------------------------------------------------------------------