diff --git a/firmware-driver/updater/8PSK-to-USB2_Rev.2_2.10.4_updater.zip b/firmware-driver/updater/8PSK-to-USB2_Rev.2_2.10.4_updater.zip new file mode 100644 index 0000000..3d16fca Binary files /dev/null and b/firmware-driver/updater/8PSK-to-USB2_Rev.2_2.10.4_updater.zip differ diff --git a/firmware-driver/updater/GPreset_v.1.5.exe b/firmware-driver/updater/GPreset_v.1.5.exe new file mode 100644 index 0000000..9c1b334 Binary files /dev/null and b/firmware-driver/updater/GPreset_v.1.5.exe differ diff --git a/firmware-driver/updater/GPreset_ver.1.5.zip b/firmware-driver/updater/GPreset_ver.1.5.zip new file mode 100644 index 0000000..9068d9c Binary files /dev/null and b/firmware-driver/updater/GPreset_ver.1.5.zip differ diff --git a/firmware-driver/updater/ReadMe.txt b/firmware-driver/updater/ReadMe.txt new file mode 100644 index 0000000..20d8454 --- /dev/null +++ b/firmware-driver/updater/ReadMe.txt @@ -0,0 +1,18 @@ +This firmware update utility will upgrade 8pSK-toUSB2 Rev.2 adapter firmware to ver.2.10.4 + +############################################################################################################## +# +# Make sure that genpix 8pSK-toUSB2 Rev.2 adapter is the ONLY genpix device connected to PC during firmware upgrade. +# Disconnect all other genpix devices. +# +# 8PSK module has to be connected to adapter during firmware upgrade procedure. +# +# Do NOT switch the PC power off during firmware upgrade procedure. +# Do NOT plug/unplug any USB devices during firmware upgrade procedure. +# +############################################################################################################## + +8pSK-toUSB2 Rev.2 adapter firmware updater runs in Windows x32/x64 environment only (Windows XP and higher). + +To start firmware upgrade: +double-click on Rev2_update_2_10_4.exe file, and follow the instruction on the screen. diff --git a/firmware-driver/updater/Rev2_update_2_10_4.exe b/firmware-driver/updater/Rev2_update_2_10_4.exe new file mode 100644 index 0000000..afd8541 Binary files /dev/null and b/firmware-driver/updater/Rev2_update_2_10_4.exe differ diff --git a/firmware-driver/updater/SW1_2_13_x_updater.zip b/firmware-driver/updater/SW1_2_13_x_updater.zip new file mode 100644 index 0000000..94c0c40 Binary files /dev/null and b/firmware-driver/updater/SW1_2_13_x_updater.zip differ diff --git a/firmware-driver/updater/SW1_update_2_13_x.exe b/firmware-driver/updater/SW1_update_2_13_x.exe new file mode 100644 index 0000000..4c34758 Binary files /dev/null and b/firmware-driver/updater/SW1_update_2_13_x.exe differ diff --git a/firmware-dump/rev2_v210_fw_1_c2.bin b/firmware-dump/rev2_v210_fw_1_c2.bin new file mode 100644 index 0000000..0e870d2 Binary files /dev/null and b/firmware-dump/rev2_v210_fw_1_c2.bin differ diff --git a/firmware-dump/rev2_v210_fw_1_flat.bin b/firmware-dump/rev2_v210_fw_1_flat.bin new file mode 100644 index 0000000..b3be376 Binary files /dev/null and b/firmware-dump/rev2_v210_fw_1_flat.bin differ diff --git a/firmware-dump/sw1_v213_fw_1_c2.bin b/firmware-dump/sw1_v213_fw_1_c2.bin new file mode 100644 index 0000000..e78eacd Binary files /dev/null and b/firmware-dump/sw1_v213_fw_1_c2.bin differ diff --git a/firmware-dump/sw1_v213_fw_1_flat.bin b/firmware-dump/sw1_v213_fw_1_flat.bin new file mode 100644 index 0000000..f140a13 Binary files /dev/null and b/firmware-dump/sw1_v213_fw_1_flat.bin differ diff --git a/firmware-dump/sw1_v213_fw_1_full64k.bin b/firmware-dump/sw1_v213_fw_1_full64k.bin new file mode 100644 index 0000000..affeabd Binary files /dev/null and b/firmware-dump/sw1_v213_fw_1_full64k.bin differ diff --git a/firmware-dump/sw1_v213_fw_2_c2.bin b/firmware-dump/sw1_v213_fw_2_c2.bin new file mode 100644 index 0000000..af50581 Binary files /dev/null and b/firmware-dump/sw1_v213_fw_2_c2.bin differ diff --git a/firmware-dump/sw1_v213_fw_2_flat.bin b/firmware-dump/sw1_v213_fw_2_flat.bin new file mode 100644 index 0000000..e8e0fab Binary files /dev/null and b/firmware-dump/sw1_v213_fw_2_flat.bin differ diff --git a/firmware-dump/sw1_v213_fw_2_full64k.bin b/firmware-dump/sw1_v213_fw_2_full64k.bin new file mode 100644 index 0000000..15528dc Binary files /dev/null and b/firmware-dump/sw1_v213_fw_2_full64k.bin differ diff --git a/firmware-dump/sw1_v213_fw_3_c2.bin b/firmware-dump/sw1_v213_fw_3_c2.bin new file mode 100644 index 0000000..0ff9b93 Binary files /dev/null and b/firmware-dump/sw1_v213_fw_3_c2.bin differ diff --git a/firmware-dump/sw1_v213_fw_3_flat.bin b/firmware-dump/sw1_v213_fw_3_flat.bin new file mode 100644 index 0000000..87465ac Binary files /dev/null and b/firmware-dump/sw1_v213_fw_3_flat.bin differ diff --git a/firmware-dump/sw1_v213_fw_3_full64k.bin b/firmware-dump/sw1_v213_fw_3_full64k.bin new file mode 100644 index 0000000..0b50677 Binary files /dev/null and b/firmware-dump/sw1_v213_fw_3_full64k.bin differ diff --git a/tools/wine_memdump.py b/tools/wine_memdump.py new file mode 100644 index 0000000..4dd498b --- /dev/null +++ b/tools/wine_memdump.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 +""" +Run a Windows PE under Wine and dump its process memory after unpacking. + +Launches the EXE, waits for it to unpack, then reads /proc/PID/mem +guided by /proc/PID/maps to capture the unpacked code and data sections. +Searches the dump for FX2 firmware signatures. +""" +import subprocess +import time +import os +import sys +import signal +import struct +import re +import glob + + +def find_wine_pid(exe_basename, timeout=10): + """Find the Wine process PID by looking for the .exe in /proc.""" + deadline = time.time() + timeout + while time.time() < deadline: + for pid_dir in glob.glob('/proc/[0-9]*'): + try: + cmdline = open(f'{pid_dir}/cmdline', 'rb').read() + if exe_basename.lower().encode() in cmdline.lower(): + pid = int(os.path.basename(pid_dir)) + # Skip if it's our own python process + if pid == os.getpid(): + continue + return pid + except (PermissionError, FileNotFoundError, ProcessLookupError): + continue + time.sleep(0.2) + return None + + +def dump_process_memory(pid, output_dir): + """Dump all readable memory regions of a process.""" + maps_path = f'/proc/{pid}/maps' + mem_path = f'/proc/{pid}/mem' + + regions = [] + try: + with open(maps_path, 'r') as f: + for line in f: + parts = line.split() + addr_range = parts[0] + perms = parts[1] + # Only dump readable regions + if 'r' not in perms: + continue + start_s, end_s = addr_range.split('-') + start = int(start_s, 16) + end = int(end_s, 16) + size = end - start + # Skip huge regions (> 64MB) and tiny ones + if size > 64 * 1024 * 1024 or size < 64: + continue + pathname = parts[5].strip() if len(parts) > 5 else "" + regions.append((start, end, perms, pathname)) + except (PermissionError, FileNotFoundError) as e: + print(f" Cannot read maps: {e}") + return None + + print(f" Found {len(regions)} readable memory regions") + + all_data = bytearray() + region_info = [] + + try: + with open(mem_path, 'rb') as mem: + for start, end, perms, pathname in regions: + size = end - start + try: + mem.seek(start) + chunk = mem.read(size) + offset_in_dump = len(all_data) + all_data.extend(chunk) + region_info.append({ + 'va_start': start, + 'va_end': end, + 'perms': perms, + 'pathname': pathname, + 'dump_offset': offset_in_dump, + 'size': len(chunk) + }) + except (OSError, ValueError): + pass + except PermissionError as e: + print(f" Cannot read mem: {e}") + print(" Try running with sudo or as the same user as Wine") + return None + + # Save full dump + dump_file = os.path.join(output_dir, 'wine_memdump.bin') + with open(dump_file, 'wb') as f: + f.write(all_data) + print(f" Saved {len(all_data)} bytes to {dump_file}") + + # Save region map + map_file = os.path.join(output_dir, 'wine_memdump_regions.txt') + with open(map_file, 'w') as f: + for r in region_info: + f.write(f"0x{r['va_start']:08X}-0x{r['va_end']:08X} " + f"{r['perms']:5s} dump_off=0x{r['dump_offset']:08X} " + f"size=0x{r['size']:06X} {r['pathname']}\n") + print(f" Saved region map to {map_file}") + + return all_data, region_info + + +def search_firmware(data, region_info): + """Search dumped memory for FX2 firmware signatures.""" + print(f"\n{'=' * 50}") + print("Searching for firmware signatures...") + print(f"{'=' * 50}") + + # 1. C2 EEPROM header with Genpix VID + print("\n[1] C2 EEPROM headers (C2 C0 09 03 02):") + c2_genpix = bytes([0xC2, 0xC0, 0x09, 0x03, 0x02]) + pos = 0 + while True: + idx = data.find(c2_genpix, pos) + if idx < 0: + break + region = find_region(region_info, idx) + ctx = bytes(data[idx:idx + 32]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + # Parse the full C2 header + if idx + 8 <= len(data): + vid = data[idx + 1] | (data[idx + 2] << 8) + pid = data[idx + 3] | (data[idx + 4] << 8) + did = data[idx + 5] | (data[idx + 6] << 8) + config = data[idx + 7] + print(f" VID=0x{vid:04X} PID=0x{pid:04X} DID=0x{did:04X} Config=0x{config:02X}") + pos = idx + 1 + + # 2. FX2 RAM clear init sequence + print("\n[2] FX2 init sequence (78 7F E4 F6 D8 FD 75 81):") + fx2_init = bytes([0x78, 0x7F, 0xE4, 0xF6, 0xD8, 0xFD, 0x75, 0x81]) + pos = 0 + while True: + idx = data.find(fx2_init, pos) + if idx < 0: + break + region = find_region(region_info, idx) + ctx = bytes(data[max(0, idx - 4):idx + 16]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + pos = idx + 1 + + # 3. Partial RAM clear pattern + print("\n[3] RAM clear pattern (78 7F E4 F6 D8 FD):") + ram_clear = bytes([0x78, 0x7F, 0xE4, 0xF6, 0xD8, 0xFD]) + pos = 0 + hits = 0 + while True: + idx = data.find(ram_clear, pos) + if idx < 0: + break + region = find_region(region_info, idx) + ctx = bytes(data[max(0, idx - 4):idx + 16]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + hits += 1 + if hits >= 10: + break + pos = idx + 1 + + # 4. LJMP at what could be code address 0x0000 (start of firmware) + # Look for 02 XX XX where XX XX is 0x0100-0x3FFF + print("\n[4] C2 load records (LEN_H LEN_L 00 00 02 = record at addr 0x0000):") + for off in range(len(data) - 8): + rec_len = (data[off] << 8) | data[off + 1] + if 0x0100 <= rec_len <= 0x4000: + if data[off + 2] == 0x00 and data[off + 3] == 0x00 and data[off + 4] == 0x02: + target = (data[off + 5] << 8) | data[off + 6] + if 0x0100 <= target <= 0x3FFF: + # Check if this looks like a valid C2 record chain + region = find_region(region_info, off) + ctx = bytes(data[off:off + 16]) + # Also check 8 bytes before for C2 header + has_c2_header = (off >= 8 and data[off - 8] == 0xC2) + header_note = " ** C2 HEADER 8 BYTES BEFORE! **" if has_c2_header else "" + print(f" 0x{off:08X} (VA: {region}): len={rec_len} " + f"addr=0x0000 LJMP 0x{target:04X} -- {ctx.hex(' ')}{header_note}") + + # 5. Known VID/PID bytes near potential firmware data + print("\n[5] VID 0x09C0 references:") + vid_bytes = b'\xC0\x09' + pos = 0 + hits = 0 + while True: + idx = data.find(vid_bytes, pos) + if idx < 0: + break + # Check if followed by PID within 4 bytes + if idx + 4 < len(data): + nearby = data[idx:idx + 8] + if b'\x03\x02' in nearby: + region = find_region(region_info, idx) + ctx = bytes(data[max(0, idx - 4):idx + 16]) + print(f" 0x{idx:08X} (VA: {region}): {ctx.hex(' ')}") + hits += 1 + if hits >= 20: + break + pos = idx + 1 + + # 6. Search for the firmware version string "2.13" + print("\n[6] Version strings:") + for pattern in [b'2.13', b'2.06', b'2.10', b'SkyWalker', b'Genpix', + b'8PSK', b'EEPROM', b'firmware', b'I2C']: + pos = 0 + while True: + idx = data.find(pattern, pos) + if idx < 0: + break + region = find_region(region_info, idx) + # Get surrounding context as ascii + start = max(0, idx - 16) + end = min(len(data), idx + 48) + ctx_bytes = bytes(data[start:end]) + ctx_ascii = ctx_bytes.decode('ascii', errors='replace') + ctx_ascii = re.sub(r'[^\x20-\x7e]', '.', ctx_ascii) + print(f" 0x{idx:08X} (VA: {region}): '{ctx_ascii}'") + pos = idx + 1 + + # 7. Look for USB vendor request setup patterns + # The updater will set bRequest=0x83 (I2C_WRITE) or 0xA0 to write firmware + print("\n[7] USB transfer setup (IOCTL/vendor request patterns):") + # WinUSB_ControlTransfer uses WINUSB_SETUP_PACKET: + # RequestType(1), Request(1), Value(2), Index(2), Length(2) + # For vendor OUT: RequestType=0x40, Request=0x83/0xA0 + for req_type, req, desc in [(0x40, 0xA0, "FX2 RAM write"), + (0x40, 0x83, "I2C_WRITE"), + (0x40, 0x84, "I2C_READ")]: + pattern = bytes([req_type, req]) + pos = 0 + hits_count = 0 + while True: + idx = data.find(pattern, pos) + if idx < 0: + break + # Check if followed by reasonable wValue/wIndex + if idx + 8 <= len(data): + wval = struct.unpack_from(' 0 and wlen < 0x4000: + region = find_region(region_info, idx) + print(f" 0x{idx:08X} ({desc}): " + f"ReqType=0x{req_type:02X} Req=0x{req:02X} " + f"wVal=0x{wval:04X} wIdx=0x{widx:04X} wLen=0x{wlen:04X} " + f"(VA: {region})") + hits_count += 1 + if hits_count >= 10: + break + pos = idx + 1 + + +def find_region(region_info, dump_offset): + """Find the VA region for a given dump offset.""" + for r in region_info: + if r['dump_offset'] <= dump_offset < r['dump_offset'] + r['size']: + va = r['va_start'] + (dump_offset - r['dump_offset']) + return f"0x{va:08X} [{r['pathname'] or 'anon'}]" + return "unknown" + + +def main(): + import argparse + parser = argparse.ArgumentParser(description="Wine memory dump for firmware extraction") + parser.add_argument('exe', help='Windows PE executable to run under Wine') + parser.add_argument('-o', '--output-dir', default='.', + help='Output directory for dumps') + parser.add_argument('--wait', type=float, default=3.0, + help='Seconds to wait after launch for unpacking (default: 3)') + parser.add_argument('--skip-launch', action='store_true', + help='Skip launching Wine, just attach to existing process') + args = parser.parse_args() + + exe_path = os.path.abspath(args.exe) + exe_basename = os.path.basename(exe_path) + os.makedirs(args.output_dir, exist_ok=True) + + wine_proc = None + pid = None + + if not args.skip_launch: + print(f"Launching {exe_basename} under Wine...") + # Use WINEDEBUG=-all to reduce noise + env = os.environ.copy() + env['WINEDEBUG'] = '-all' + wine_proc = subprocess.Popen( + ['wine', exe_path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env + ) + print(f" Wine wrapper PID: {wine_proc.pid}") + + # Wait for the actual .exe process to appear + print(f" Waiting {args.wait}s for unpacking...") + time.sleep(args.wait) + + # Find the Windows process PID + print(f" Looking for {exe_basename} process...") + pid = find_wine_pid(exe_basename, timeout=5) + if pid is None: + # Try looking for wine-preloader or wine64-preloader + print(" Couldn't find by exe name, searching all wine processes...") + for pid_dir in glob.glob('/proc/[0-9]*'): + try: + cmdline = open(f'{pid_dir}/cmdline', 'rb').read() + if b'wine' in cmdline.lower() and pid_dir != f'/proc/{os.getpid()}': + p = int(os.path.basename(pid_dir)) + if wine_proc and p == wine_proc.pid: + continue + print(f" Found wine process PID {p}: {cmdline[:100]}") + except: + pass + + if pid is None and wine_proc: + pid = wine_proc.pid + print(f" Using Wine wrapper PID: {pid}") + + if pid: + print(f"\n Target PID: {pid}") + result = dump_process_memory(pid, args.output_dir) + if result: + data, region_info = result + search_firmware(data, region_info) + else: + print(" ERROR: Could not find process") + + # Cleanup + if wine_proc: + print("\nTerminating Wine process...") + try: + wine_proc.terminate() + wine_proc.wait(timeout=5) + except: + wine_proc.kill() + + +if __name__ == '__main__': + main()