GAMEMAX IceBurg LCD Linux bridge: sender + tray app
Reverse-engineered Linux driver for the LCD panel of the GAMEMAX IceBurg Digital AIO cooler (HID 5131:2007). Includes: - src/gamemax_report.py pure-stdlib sender: reads hwmon/proc sensors and streams the 65-byte HID report the firmware understands (no deps) - src/gamemax_gui.py PySide6 tray app: animated monitor, fan/temperature channel pickers, autostart, blank mode, single instance, crash log - packaging/ AppImage build script, udev rule, systemd user unit - tools/ IL dump helpers used to read the vendor MSIL - docs/ full reverse-engineering analysis - DISCLAIMER.md Linux-compatibility project, not affiliated with vendor Co-authored-by: REA (managed-code analysis)
This commit is contained in:
1 parent
b083919692
commit
f58a011f4d
15 files changed
+1893
No files matched your search
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dump MSIL of methods in a .NET/mixed-mode assembly with resolved operands.
|
||||
|
||||
Usage:
|
||||
ildump.py <assembly> list <regex>
|
||||
ildump.py <assembly> dump <regex>
|
||||
ildump.py <assembly> callers <regex>
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
import dnfile
|
||||
from dncil.cil.body.reader import read_method_body_from_bytes
|
||||
|
||||
TBL = {0x01: "TypeRef", 0x02: "TypeDef", 0x04: "Field", 0x06: "MethodDef",
|
||||
0x0A: "MemberRef", 0x1B: "TypeSpec", 0x2B: "MethodSpec", 0x70: "String"}
|
||||
CALLISH = {"call", "callvirt", "ldftn", "newobj", "ldvirtftn", "jmp"}
|
||||
|
||||
|
||||
def type_name(pe, row):
|
||||
if row is None:
|
||||
return "?"
|
||||
for attr in ("TypeName", "Name"):
|
||||
if hasattr(row, attr) and getattr(row, attr):
|
||||
ns = getattr(row, "TypeNamespace", "") or ""
|
||||
return str(f"{ns}.{getattr(row, attr)}").lstrip(".")
|
||||
return "?"
|
||||
|
||||
|
||||
def build_maps(pe):
|
||||
md = pe.net.mdtables
|
||||
names = {}
|
||||
for i, row in enumerate(md.MethodDef.rows):
|
||||
names[0x06000000 | (i + 1)] = str(row.Name)
|
||||
for i, row in enumerate(md.MemberRef.rows):
|
||||
parent = "?"
|
||||
try:
|
||||
parent = type_name(pe, row.Class.row) if row.Class else "?"
|
||||
except Exception:
|
||||
pass
|
||||
names[0x0A000000 | (i + 1)] = f"{parent}::{str(row.Name)}"
|
||||
for i, row in enumerate(md.Field.rows):
|
||||
names[0x04000000 | (i + 1)] = str(row.Name)
|
||||
for tbl, base in (("TypeDef", 0x02000000), ("TypeRef", 0x01000000)):
|
||||
for i, row in enumerate(getattr(md, tbl).rows):
|
||||
names[base | (i + 1)] = type_name(pe, row)
|
||||
return names
|
||||
|
||||
|
||||
def decode(pe, row, maps):
|
||||
raw = pe.get_data(row.Rva, 12 + 65536)
|
||||
body = read_method_body_from_bytes(raw)
|
||||
out = []
|
||||
for ins in body.instructions:
|
||||
op = getattr(ins.opcode, "name", str(ins.opcode)).lower()
|
||||
operand = ins.operand
|
||||
val = getattr(operand, "value", operand)
|
||||
text = ""
|
||||
if isinstance(val, int) and val > 0x02000000:
|
||||
text = maps.get(val, f"0x{val:08x}")
|
||||
if val >> 24 == 0x70:
|
||||
try:
|
||||
text = repr(pe.net.user_strings.get(val & 0xFFFFFF))
|
||||
except Exception:
|
||||
text = "<bad string>"
|
||||
elif val is not None:
|
||||
text = str(val)
|
||||
out.append((ins.offset, op, text))
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
path, cmd, pattern = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
pe = dnfile.dnPE(path)
|
||||
maps = build_maps(pe)
|
||||
rx = re.compile(pattern)
|
||||
md = pe.net.mdtables
|
||||
rows = [(0x06000000 | (i + 1), row) for i, row in enumerate(md.MethodDef.rows)]
|
||||
targets = [(t, r) for t, r in rows if rx.search(str(r.Name))]
|
||||
if cmd == "list":
|
||||
for t, r in targets:
|
||||
print(f"{t:08x} {str(r.Name)}")
|
||||
return
|
||||
if cmd == "callers":
|
||||
for t, r in rows:
|
||||
try:
|
||||
for off, op, text in decode(pe, r, maps):
|
||||
if op in CALLISH and rx.search(text):
|
||||
print(f"{str(r.Name)} @il+{off}: {op} {text}")
|
||||
except Exception:
|
||||
continue
|
||||
return
|
||||
for t, r in targets:
|
||||
print(f"=== {t:08x} {str(r.Name)}")
|
||||
try:
|
||||
for off, op, text in decode(pe, r, maps):
|
||||
print(f" +{off:4} {op:16} {text}")
|
||||
except Exception as e:
|
||||
print(f" <decode failed: {e}>")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find which IL methods reference a string stored in the native data section.
|
||||
|
||||
Resolves: string bytes -> RVA -> FieldRva row -> Field token -> IL instructions.
|
||||
Usage: string_refs.py <assembly> <needle> [--context N]
|
||||
"""
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import dnfile
|
||||
|
||||
CALLISH = {"call", "callvirt", "ldftn", "newobj", "ldsflda", "ldsfld", "ldstr", "ldtoken"}
|
||||
|
||||
|
||||
def rva_of_offset(pe, off):
|
||||
for s in pe.sections:
|
||||
start = s.PointerToRawData
|
||||
if start <= off < start + s.SizeOfRawData:
|
||||
return s.VirtualAddress + (off - start)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
path, needle = sys.argv[1], sys.argv[2]
|
||||
data = open(path, "rb").read()
|
||||
pe = dnfile.dnPE(path)
|
||||
hits = []
|
||||
for enc in ("ascii", "utf-16-le"):
|
||||
b = needle.encode(enc)
|
||||
hits += [m.start() for m in re.finditer(re.escape(b), data)]
|
||||
rvas = {rva_of_offset(pe, h) for h in hits} - {None}
|
||||
print(f"needle {needle!r}: {len(hits)} occurrences, rvas: {[hex(r) for r in rvas]}")
|
||||
|
||||
fields = {} # field token -> FieldRva rva
|
||||
for i, row in enumerate(pe.net.mdtables.FieldRva.rows):
|
||||
fields[0x04000000 | (i + 1)] = row.Rva
|
||||
matching = {t: r for t, r in fields.items() if r in rvas}
|
||||
print("matching field tokens:", {hex(t): hex(r) for t, r in matching.items()})
|
||||
|
||||
names = {}
|
||||
for i, row in enumerate(pe.net.mdtables.Field.rows):
|
||||
names[0x04000000 | (i + 1)] = str(row.Name)
|
||||
# any string whose RVA sits inside the string's own blob (same field)
|
||||
related = set(matching)
|
||||
for t, r in fields.items():
|
||||
if any(r <= rv < r + len(needle) + 64 for rv in rvas):
|
||||
related.add(t)
|
||||
print("related fields:", {hex(t): names[t] for t in sorted(related)})
|
||||
|
||||
from dncil.cil.body.reader import read_method_body_from_bytes
|
||||
maps = {}
|
||||
for i, row in enumerate(pe.net.mdtables.MethodDef.rows):
|
||||
maps[0x06000000 | (i + 1)] = str(row.Name)
|
||||
for i, row in enumerate(pe.net.mdtables.MemberRef.rows):
|
||||
maps[0x0A000000 | (i + 1)] = str(row.Name)
|
||||
|
||||
for i, row in enumerate(pe.net.mdtables.MethodDef.rows):
|
||||
try:
|
||||
raw = pe.get_data(row.Rva, 12 + 65536)
|
||||
body = read_method_body_from_bytes(raw)
|
||||
except Exception:
|
||||
continue
|
||||
insns = body.instructions
|
||||
for j, ins in enumerate(insns):
|
||||
op = getattr(ins.opcode, "name", str(ins.opcode)).lower()
|
||||
if op not in CALLISH:
|
||||
continue
|
||||
val = getattr(ins.operand, "value", ins.operand)
|
||||
if val in related:
|
||||
print(f"\n{str(row.Name)} @ +{ins.offset}: {op} {names.get(val, val)}")
|
||||
for k in insns[max(0, j - 6):j + 10]:
|
||||
kop = getattr(k.opcode, "name", "?").lower()
|
||||
kval = getattr(k.operand, "value", k.operand)
|
||||
txt = maps.get(kval, kval) if isinstance(kval, int) and kval > 0x02000000 else (kval if kval is not None else "")
|
||||
mark = " <<<" if k is ins else ""
|
||||
print(f" +{k.offset:6} {kop:14} {txt}{mark}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user