# This tool was developed by "https://github.com/ANSSI-FR/bmc-tools" - Thank you for your hardwork! from struct import pack, unpack from typing import AnyStr, Generic import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 class BMCContainer(Generic[AnyStr]): BIN_FILE_HEADER = b"RDP8bmp\x00" BIN_CONTAINER = b".BIN" BMC_CONTAINER = b".BMC" TILE_HEADER_SIZE = {BMC_CONTAINER: 0x14, BIN_CONTAINER: 0xC} STRIPE_WIDTH = 64 LOG_TYPES = ["[===]", "[+++]", "[---]", "[!!!]"] PALETTE = bytes( bytearray( ( 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, 0, 0, 0, 128, 128, 0, 128, 0, 0, 0, 128, 0, 128, 0, 128, 128, 0, 0, 192, 192, 192, 0, 192, 220, 192, 0, 240, 202, 166, 0, 0, 32, 64, 0, 0, 32, 96, 0, 0, 32, 128, 0, 0, 32, 160, 0, 0, 32, 192, 0, 0, 32, 224, 0, 0, 64, 0, 0, 0, 64, 32, 0, 0, 64, 64, 0, 0, 64, 96, 0, 0, 64, 128, 0, 0, 64, 160, 0, 0, 64, 192, 0, 0, 64, 224, 0, 0, 96, 0, 0, 0, 96, 32, 0, 0, 96, 64, 0, 0, 96, 96, 0, 0, 96, 128, 0, 0, 96, 160, 0, 0, 96, 192, 0, 0, 96, 224, 0, 0, 128, 0, 0, 0, 128, 32, 0, 0, 128, 64, 0, 0, 128, 96, 0, 0, 128, 128, 0, 0, 128, 160, 0, 0, 128, 192, 0, 0, 128, 224, 0, 0, 160, 0, 0, 0, 160, 32, 0, 0, 160, 64, 0, 0, 160, 96, 0, 0, 160, 128, 0, 0, 160, 160, 0, 0, 160, 192, 0, 0, 160, 224, 0, 0, 192, 0, 0, 0, 192, 32, 0, 0, 192, 64, 0, 0, 192, 96, 0, 0, 192, 128, 0, 0, 192, 160, 0, 0, 192, 192, 0, 0, 192, 224, 0, 0, 224, 0, 0, 0, 224, 32, 0, 0, 224, 64, 0, 0, 224, 96, 0, 0, 224, 128, 0, 0, 224, 160, 0, 0, 224, 192, 0, 0, 224, 224, 0, 64, 0, 0, 0, 64, 0, 32, 0, 64, 0, 64, 0, 64, 0, 96, 0, 64, 0, 128, 0, 64, 0, 160, 0, 64, 0, 192, 0, 64, 0, 224, 0, 64, 32, 0, 0, 64, 32, 32, 0, 64, 32, 64, 0, 64, 32, 96, 0, 64, 32, 128, 0, 64, 32, 160, 0, 64, 32, 192, 0, 64, 32, 224, 0, 64, 64, 0, 0, 64, 64, 32, 0, 64, 64, 64, 0, 64, 64, 96, 0, 64, 64, 128, 0, 64, 64, 160, 0, 64, 64, 192, 0, 64, 64, 224, 0, 64, 96, 0, 0, 64, 96, 32, 0, 64, 96, 64, 0, 64, 96, 96, 0, 64, 96, 128, 0, 64, 96, 160, 0, 64, 96, 192, 0, 64, 96, 224, 0, 64, 128, 0, 0, 64, 128, 32, 0, 64, 128, 64, 0, 64, 128, 96, 0, 64, 128, 128, 0, 64, 128, 160, 0, 64, 128, 192, 0, 64, 128, 224, 0, 64, 160, 0, 0, 64, 160, 32, 0, 64, 160, 64, 0, 64, 160, 96, 0, 64, 160, 128, 0, 64, 160, 160, 0, 64, 160, 192, 0, 64, 160, 224, 0, 64, 192, 0, 0, 64, 192, 32, 0, 64, 192, 64, 0, 64, 192, 96, 0, 64, 192, 128, 0, 64, 192, 160, 0, 64, 192, 192, 0, 64, 192, 224, 0, 64, 224, 0, 0, 64, 224, 32, 0, 64, 224, 64, 0, 64, 224, 96, 0, 64, 224, 128, 0, 64, 224, 160, 0, 64, 224, 192, 0, 64, 224, 224, 0, 128, 0, 0, 0, 128, 0, 32, 0, 128, 0, 64, 0, 128, 0, 96, 0, 128, 0, 128, 0, 128, 0, 160, 0, 128, 0, 192, 0, 128, 0, 224, 0, 128, 32, 0, 0, 128, 32, 32, 0, 128, 32, 64, 0, 128, 32, 96, 0, 128, 32, 128, 0, 128, 32, 160, 0, 128, 32, 192, 0, 128, 32, 224, 0, 128, 64, 0, 0, 128, 64, 32, 0, 128, 64, 64, 0, 128, 64, 96, 0, 128, 64, 128, 0, 128, 64, 160, 0, 128, 64, 192, 0, 128, 64, 224, 0, 128, 96, 0, 0, 128, 96, 32, 0, 128, 96, 64, 0, 128, 96, 96, 0, 128, 96, 128, 0, 128, 96, 160, 0, 128, 96, 192, 0, 128, 96, 224, 0, 128, 128, 0, 0, 128, 128, 32, 0, 128, 128, 64, 0, 128, 128, 96, 0, 128, 128, 128, 0, 128, 128, 160, 0, 128, 128, 192, 0, 128, 128, 224, 0, 128, 160, 0, 0, 128, 160, 32, 0, 128, 160, 64, 0, 128, 160, 96, 0, 128, 160, 128, 0, 128, 160, 160, 0, 128, 160, 192, 0, 128, 160, 224, 0, 128, 192, 0, 0, 128, 192, 32, 0, 128, 192, 64, 0, 128, 192, 96, 0, 128, 192, 128, 0, 128, 192, 160, 0, 128, 192, 192, 0, 128, 192, 224, 0, 128, 224, 0, 0, 128, 224, 32, 0, 128, 224, 64, 0, 128, 224, 96, 0, 128, 224, 128, 0, 128, 224, 160, 0, 128, 224, 192, 0, 128, 224, 224, 0, 192, 0, 0, 0, 192, 0, 32, 0, 192, 0, 64, 0, 192, 0, 96, 0, 192, 0, 128, 0, 192, 0, 160, 0, 192, 0, 192, 0, 192, 0, 224, 0, 192, 32, 0, 0, 192, 32, 32, 0, 192, 32, 64, 0, 192, 32, 96, 0, 192, 32, 128, 0, 192, 32, 160, 0, 192, 32, 192, 0, 192, 32, 224, 0, 192, 64, 0, 0, 192, 64, 32, 0, 192, 64, 64, 0, 192, 64, 96, 0, 192, 64, 128, 0, 192, 64, 160, 0, 192, 64, 192, 0, 192, 64, 224, 0, 192, 96, 0, 0, 192, 96, 32, 0, 192, 96, 64, 0, 192, 96, 96, 0, 192, 96, 128, 0, 192, 96, 160, 0, 192, 96, 192, 0, 192, 96, 224, 0, 192, 128, 0, 0, 192, 128, 32, 0, 192, 128, 64, 0, 192, 128, 96, 0, 192, 128, 128, 0, 192, 128, 160, 0, 192, 128, 192, 0, 192, 128, 224, 0, 192, 160, 0, 0, 192, 160, 32, 0, 192, 160, 64, 0, 192, 160, 96, 0, 192, 160, 128, 0, 192, 160, 160, 0, 192, 160, 192, 0, 192, 160, 224, 0, 192, 192, 0, 0, 192, 192, 32, 0, 192, 192, 64, 0, 192, 192, 96, 0, 192, 192, 128, 0, 192, 192, 160, 0, 240, 251, 255, 0, 164, 160, 160, 0, 128, 128, 128, 0, 0, 0, 255, 0, 0, 255, 0, 0, 0, 255, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 255, 0, 0, 255, 255, 255, 0, ) ) ) COLOR_BLACK = b"\x00" COLOR_WHITE = b"\xff" def __init__(self, verbose=False, count=0, old=False, big=False, width=64): self.bdat = b"" self.o_bmps = [] self.bmps = [] self.btype = b"" self.cnt = count self.fname: str = "" self.oldsave = old self.pal = False self.verb = verbose self.big = big self.STRIPE_WIDTH = width if count > 0: self.b_log("debug", True, f"At most {count} tiles will be processed.") if old: self.b_log("debug", True, "Old data will also be saved in separate files.") def b_log(self, ltype, verbose, lmsg): log_type = {"info": demisto.info, "debug": demisto.debug, "error": demisto.error} if verbose or self.verb: log_type[ltype](lmsg) return True def b_import(self, fname): if len(self.bdat) > 0: self.b_log("error", False, "Data is already waiting to be processed; aborting.") return False with open(fname, "rb") as f: self.bdat = f.read() if len(self.bdat) == 0: self.b_log("error", False, "Unable to retrieve file contents; aborting.") return False self.fname = fname self.btype = self.BMC_CONTAINER if self.bdat[: len(self.BIN_FILE_HEADER)] == self.BIN_FILE_HEADER: unpacked_bytes = unpack(" 0: o_bmp = b"" t_hdr = self.bdat[: self.TILE_HEADER_SIZE[self.btype]] key1, key2, t_width, t_height = unpack(" 0: if len(t_bmp) != t_width * t_height * bl // (64 * 64): self.b_log( "error", False, ( f"Uncompressed tile data seems bogus (uncompressed {len(t_bmp)} bytes " f"while expecting {t_width * t_height * bl // (64 * 64)}). " f"Discarding tile." ), ) t_bmp = b"" else: t_bmp = self.b_parse_rgb565(t_bmp) else: cf = t_len // (t_width * t_height) funcdict = { "4": self.b_parse_rgb32b, "3": self.b_parse_rgb24b, "2": self.b_parse_rgb565, } try: t_bmp = funcdict[str(cf)](self.bdat[len(t_hdr) : len(t_hdr) + cf * t_width * t_height]) if t_height != 64: o_bmp = self.PALETTE + self.bdat[len(t_hdr) + cf * t_width * t_height : len(t_hdr) + cf * 64 * 64] except KeyError: if cf == 1: self.pal = True t_bmp = self.PALETTE + self.bdat[len(t_hdr) : len(t_hdr) + cf * t_width * t_height] else: self.b_log("error", False, f"Unexpected bpp {8 * cf} found during processing; aborting.") bl = cf * 64 * 64 if len(t_bmp) > 0: self.bmps.append(t_bmp) if len(o_bmp) > 0: self.o_bmps.append(o_bmp) if len(self.bmps) % 100 == 0: self.b_log("debug", True, f"{len(self.bmps)} tiles successfully extracted so far.") self.bdat = self.bdat[len(t_hdr) + bl :] if self.cnt != 0 and len(self.bmps) == self.cnt: break self.b_log("debug", False, f"{len(self.bmps)} tiles successfully extracted in the end.") return True def b_parse_rgb565(self, data): d_out = b"" while len(data) > 0: pxl = unpack("> 8) & 0xF8) | ((pxl >> 13) & 0x07) gr = ((pxl >> 3) & 0xFC) | ((pxl >> 9) & 0x03) re = ((pxl << 3) & 0xF8) | ((pxl >> 2) & 0x07) d_out += bytearray((re, gr, bl, 255)) data = data[2:] return bytes(d_out) def b_parse_rgb32b(self, data): d_out = b"" d_buf = b"" while len(data) > 0: if self.btype == self.BIN_CONTAINER: d_buf += data[:3] + b"\xff" if len(d_buf) == 256: d_out = d_buf + d_out d_buf = b"" else: d_out += data[:3] + b"\xff" data = data[4:] return d_out def b_parse_rgb24b(self, data): d_out = b"" d_buf = b"" while len(data) > 0: if self.btype == self.BIN_CONTAINER: d_buf += data[:3] + b"\xff" if len(d_buf) == 256: d_out = d_buf + d_out d_buf = b"" else: d_out += data[:3] + b"\xff" data = data[3:] return d_out def b_unrle(self, data): if len(data) == 0: return (-1, 1, 0) x = ord(data[0:1]) if (x & 0xF0) == 0xF0: if x in [0xF5, 0xFB, 0xFC, 0xFF]: return (-1, 2, x) elif x in [0xFD, 0xFE]: return (x, 0, 1) elif x in [0xF9, 0xFA]: return (x, 8, 1) elif len(data) < 3: return (-1, 1, 0) else: c = unpack(" 0: cmd, rl, sz = self.b_unrle(data[:3]) if cmd == -1: if rl == 1: self.b_log("error", False, "Unexpected end of compressed stream. Skipping tile.") elif rl == 2: self.b_log("error", False, f"Unexpected decompression command encountered (0x{sz:02x}). Skipping tile.") else: self.b_log("error", False, "Unhandled case in decompression routine. Skipping tile.") return b"" data = data[sz:] if cmd in [0x00, 0xF0]: if len(d_out) < 64 * bbp: if bro == 0: d_out += fgc rl -= 1 d_out += (self.COLOR_BLACK * bbp) * rl else: if bro > 0: c = d_out[-64 * bbp :][:bbp] for i in range(bbp): d_out += bytearray((ord(c[i : i + 1]) ^ ord(fgc[i : i + 1]),)) rl -= 1 while rl > 0: d_out += d_out[-64 * bbp :][:bbp] rl -= 1 bro = len(d_out) // (64 * bbp) elif cmd in [0x20, 0xC0, 0xF1, 0xF6]: if cmd in [0xC0, 0xF6]: if len(data) < bbp: self.b_log("error", False, f"Unexpected end of compressed stream. Skipping tile. ({cmd}, {rl})") return b"" fgc = data[:bbp] data = data[bbp:] if len(d_out) < 64 * bbp: d_out += fgc * rl else: while rl > 0: c = d_out[-64 * bbp :][:bbp] for i in range(bbp): d_out += bytearray((ord(c[i : i + 1]) ^ ord(fgc[i : i + 1]),)) rl -= 1 elif cmd in [0xE0, 0xF8]: if len(data) < 2 * bbp: self.b_log("error", False, "Unexpected end of compressed stream. Skipping tile.") return b"" d_out += data[: 2 * bbp] * rl data = data[2 * bbp :] elif cmd in [0x60, 0xF3]: if len(data) < bbp: self.b_log("error", False, "Unexpected end of compressed stream. Skipping tile.") return b"" d_out += data[:bbp] * rl data = data[bbp:] elif cmd in [0x40, 0xD0, 0xF2, 0xF7, 0xF9, 0xFA]: if cmd in [0xD0, 0xF7]: if len(data) < bbp: self.b_log("error", False, "Unexpected end of compressed stream. Skipping tile.") return b"" fgc = data[:bbp] data = data[bbp:] if cmd == 0xF9: msk = b"\x03" ml = 1 elif cmd == 0xFA: msk = b"\x05" ml = 1 else: ml = (rl // 8) + 1 if rl % 8 != 0 else rl // 8 if len(data) < ml: self.b_log("error", False, "Unexpected end of compressed stream. Skipping tile.") return b"" msk = data[:ml] data = data[ml:] k = 0 while rl > 0: if (k % 8) == 0: m = ord(msk[k // 8 :][:1]) else: m = 0 demisto.debug(f"{k % 8} != 0 -> {m=}") b = m & (0x1 << (k % 8)) if len(d_out) < 64 * bbp: if b == 0: d_out += self.COLOR_BLACK * bbp else: d_out += fgc else: c = d_out[-64 * bbp :][:bbp] if b == 0: d_out += c else: for i in range(bbp): d_out += bytearray((ord(c[i : i + 1]) ^ ord(fgc[i : i + 1]),)) k += 1 rl -= 1 elif cmd in [0x80, 0xF4]: if len(data) < bbp * rl: self.b_log("error", False, "Unexpected end of compressed stream. Skipping tile.") return b"" d_out += data[: rl * bbp] data = data[rl * bbp :] elif cmd == 0xFD: d_out += self.COLOR_WHITE * bbp elif cmd == 0xFE: d_out += self.COLOR_BLACK * bbp else: self.b_log("error", False, f"Unhandled decompression command (0x{cmd:02x}). Skipping tile.") return b"" if cmd not in [0x00, 0xF0]: bro = -1 return d_out def b_export(self, name): self.fname = name # Removed tile creation from these lines self.b_log("info", False, f"Successfully exported {len(self.bmps)} files.") if self.big: pad: bytes = b"\xff" if not self.pal: pad *= 4 for i in range(len(self.bmps)): if self.pal: self.bmps[i] = self.bmps[i][len(self.PALETTE) :] while len(self.bmps[i]) != 64 * 64 * len(pad): self.bmps[i] += pad * 64 w: int = 64 * len(self.bmps) h = 64 if len(self.bmps) // self.STRIPE_WIDTH > 0: m = len(self.bmps) % self.STRIPE_WIDTH if m != 0: for _ in range(self.STRIPE_WIDTH - m): self.bmps.append(pad * 64 * 64) w = self.STRIPE_WIDTH * 64 h *= len(self.bmps) // self.STRIPE_WIDTH c_bmp = b"" if not self.pal else self.PALETTE if self.btype == self.BIN_CONTAINER: def collage_builder(x, a=self, PAD=len(pad), WIDTH=range(w // 64)): return b"".join( [ b"".join([a.bmps[a.STRIPE_WIDTH * (x + 1) - 1 - k][64 * PAD * j : 64 * PAD * (j + 1)] for k in WIDTH]) for j in range(64) ] ) else: def collage_builder(x, a=self, PAD=len(pad), WIDTH=range(w // 64)): return b"".join( [ b"".join([a.bmps[a.STRIPE_WIDTH * x + k][64 * PAD * j : 64 * PAD * (j + 1)] for k in WIDTH]) for j in range(64) ] ) c_bmp += b"".join(map(collage_builder, range(h // 64))) self.b_write(f"{self.fname[:-4]}_collage.bmp", self.b_export_bmp(w, h, c_bmp)) self.b_log("info", False, "Successfully exported collage file.") def b_export_bmp(self, width, height, data): if not self.pal: return ( b"BM" + pack("