├── .gitignore ├── bootlogo ├── logo.raw ├── find_offset.py └── patch_bootlogo.py ├── extract_bl ├── firmware.enc ├── stm32f103xc_app.ld ├── main.c └── Makefile ├── .gitmodules ├── datasheets ├── ST7735S_v1.1.pdf └── KD018QQTBN009-RT-STARTEK.pdf ├── disable_lock ├── README.md └── disable_lock.py ├── encryption ├── README.md └── encryption.py ├── bandedges ├── channel_file_AU.json ├── channel_file_default.json ├── README.md └── patch_edges.py ├── extract_bl_main ├── stm32f429_app.ld ├── Makefile └── main.c ├── display_bringup ├── stm32f103xc_app.ld ├── Makefile └── main.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | firmware.* 2 | -------------------------------------------------------------------------------- /bootlogo/logo.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHamradioFirmware/G90Tools/HEAD/bootlogo/logo.raw -------------------------------------------------------------------------------- /extract_bl/firmware.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHamradioFirmware/G90Tools/HEAD/extract_bl/firmware.enc -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/libopencm3"] 2 | path = lib/libopencm3 3 | url = https://github.com/libopencm3/libopencm3 4 | -------------------------------------------------------------------------------- /datasheets/ST7735S_v1.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHamradioFirmware/G90Tools/HEAD/datasheets/ST7735S_v1.1.pdf -------------------------------------------------------------------------------- /datasheets/KD018QQTBN009-RT-STARTEK.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenHamradioFirmware/G90Tools/HEAD/datasheets/KD018QQTBN009-RT-STARTEK.pdf -------------------------------------------------------------------------------- /disable_lock/README.md: -------------------------------------------------------------------------------- 1 | # Patch flash locking in bootloader 2 | 3 | The bootloader contains code that will enable readout protection (lock level 1) after it has finished flashing the application code. 4 | 5 | This patch will replace this call with NOPs. -------------------------------------------------------------------------------- /encryption/README.md: -------------------------------------------------------------------------------- 1 | Encrypts/decrypts `xgf` firmware files. 2 | 3 | # Usage 4 | ## Decryption 5 | ``` 6 | # given an encrypted firmware fw_encrypted.xgf, decrypt to fw_plaintext.bin 7 | encryption.py decrypt fw_encrypted.xgf fw_plaintext.bin 8 | ``` 9 | 10 | ## Encryption 11 | ``` 12 | # given a plaintext firmware fw_plaintext.bin, encrypt to fw_encrypted.xgf 13 | encryption.py encrypt fw_plaintext.bin fw_encrypted.xgf 14 | ``` 15 | -------------------------------------------------------------------------------- /bandedges/channel_file_AU.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"low":1.8, "high": 1.875, "enabled": true}, 3 | {"low":3.5, "high": 3.7, "enabled": true}, 4 | {"low":3.776, "high": 3.8, "enabled": true}, 5 | {"low":7, "high": 7.3, "enabled": true}, 6 | {"low":10.1, "high": 10.15, "enabled": true}, 7 | {"low":14, "high": 14.35, "enabled": true}, 8 | {"low":18.068, "high": 18.168, "enabled": true}, 9 | {"low":21, "high": 21.45, "enabled": true}, 10 | {"low":24.89, "high": 24.99, "enabled": true}, 11 | {"low":28, "high": 29.7, "enabled": true} 12 | ] -------------------------------------------------------------------------------- /bandedges/channel_file_default.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"low":1.8, "high": 2, "enabled": true}, 3 | {"low":3.5, "high": 4, "enabled": true}, 4 | {"low":5.3305, "high": 5.405, "enabled": true}, 5 | {"low":7, "high": 7.3, "enabled": true}, 6 | {"low":10.1, "high": 10.15, "enabled": true}, 7 | {"low":14, "high": 14.35, "enabled": true}, 8 | {"low":18.068, "high": 18.168, "enabled": true}, 9 | {"low":21, "high": 21.45, "enabled": true}, 10 | {"low":24.89, "high": 24.99, "enabled": true}, 11 | {"low":28, "high": 29.7, "enabled": true} 12 | ] -------------------------------------------------------------------------------- /bandedges/README.md: -------------------------------------------------------------------------------- 1 | Updates the bandedges for firmware 1.74 only. 2 | 3 | ``` 4 | 'Usage: patch_edges.py 5 | ``` 6 | Remember to encrypt the firmware after running this with `encryption/encryption.py` 7 | 8 | JSON syntax is as follows (must have exactly 10 channels): 9 | 10 | ``` 11 | [ 12 | {"low":1.8, "high": 1.875, "enabled": true}, 13 | {"low":3.5, "high": 3.7, "enabled": true}, 14 | {"low":3.776, "high": 3.8, "enabled": true}, 15 | {"low":7, "high": 7.3, "enabled": true}, 16 | {"low":10.1, "high": 10.15, "enabled": true}, 17 | {"low":14, "high": 14.35, "enabled": true}, 18 | {"low":18.068, "high": 18.168, "enabled": true}, 19 | {"low":21, "high": 21.45, "enabled": true}, 20 | {"low":24.89, "high": 24.99, "enabled": true}, 21 | {"low":28, "high": 29.7, "enabled": true} 22 | ] 23 | ``` 24 | -------------------------------------------------------------------------------- /extract_bl_main/stm32f429_app.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the libopencm3 project. 3 | * 4 | * Copyright (C) 2015 Karl Palsson 5 | * 6 | * This library is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | */ 19 | 20 | /* Define memory regions. */ 21 | MEMORY 22 | { 23 | rom (rx) : ORIGIN = 0x08004000, LENGTH = 1008K 24 | ccm (rwx) : ORIGIN = 0x10000000, LENGTH = 64K 25 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 192K 26 | } 27 | 28 | /* Include the common ld script. */ 29 | INCLUDE cortex-m-generic.ld 30 | -------------------------------------------------------------------------------- /bandedges/patch_edges.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import struct 5 | 6 | def main(): 7 | if len(sys.argv) != 3: 8 | print(f'Usage: {sys.argv[0]} ') 9 | print(f' note: only use on firmware 1.74') 10 | print(f' note: exactly 10 channels must be defined') 11 | return 12 | 13 | decrypted_firmware = sys.argv[1] 14 | 15 | with open(sys.argv[2], "r") as channel_file: 16 | channels = json.loads(channel_file.read()) 17 | if len(channels) != 10: 18 | raise ValueError("Must have 10 channels") 19 | 20 | channel_data = b'' 21 | for channel in channels: 22 | print(f'Low: {channel["low"]} High: {channel["high"]} Enabled: {channel["enabled"]}') 23 | channel_struct = struct.pack('iii', int(channel['low'] * 1000000), int(channel['high'] * 1000000), int(channel['enabled'])) 24 | channel_data += channel_struct 25 | 26 | with open(decrypted_firmware, 'r+b') as f: 27 | f.seek(0x20120, os.SEEK_SET) 28 | f.write(bytes(channel_data)) 29 | 30 | print('done: band edges patched') 31 | 32 | if __name__ == '__main__': 33 | main() 34 | -------------------------------------------------------------------------------- /extract_bl/stm32f103xc_app.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the libopencm3 project. 3 | * 4 | * Copyright (C) 2015 Karl Palsson 5 | * 6 | * This library is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | */ 19 | 20 | /* Linker script for STM32F103xC, 256K flash, 48k RAM. */ 21 | 22 | /* Define memory regions. */ 23 | MEMORY 24 | { 25 | rom (rx) : ORIGIN = 0x08004000, LENGTH = 240K 26 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 48K 27 | } 28 | 29 | /* Include the common ld script. */ 30 | INCLUDE ../lib/libopencm3/lib/cortex-m-generic.ld 31 | -------------------------------------------------------------------------------- /display_bringup/stm32f103xc_app.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the libopencm3 project. 3 | * 4 | * Copyright (C) 2015 Karl Palsson 5 | * 6 | * This library is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library. If not, see . 18 | */ 19 | 20 | /* Linker script for STM32F103xC, 256K flash, 48k RAM. */ 21 | 22 | /* Define memory regions. */ 23 | MEMORY 24 | { 25 | rom (rx) : ORIGIN = 0x08004000, LENGTH = 240K 26 | ram (rwx) : ORIGIN = 0x20000000, LENGTH = 48K 27 | } 28 | 29 | /* Include the common ld script. */ 30 | INCLUDE ../lib/libopencm3/lib/cortex-m-generic.ld 31 | -------------------------------------------------------------------------------- /disable_lock/disable_lock.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import struct 5 | import hashlib 6 | 7 | def main(): 8 | if len(sys.argv) != 2: 9 | print(f'Usage: {sys.argv[0]} ') 10 | print(f' note: only use on firmware 1.74') 11 | return 12 | 13 | decrypted_firmware = sys.argv[1] 14 | 15 | with open(decrypted_firmware, 'r+b') as f: 16 | m = hashlib.sha256() 17 | m.update(f.read()) 18 | h = m.hexdigest() 19 | if h == 'e3654a811235a4eed139209df3636ee44324f8c9aab3e88141de93b40cf29706': 20 | print('Already patched') 21 | exit() 22 | elif h == '22c8b4a01f711063c254ce256ba19be864bb79c94894afb0655d870a9e0fa208': 23 | # Display Unit bootloader 24 | offset = 0x188e 25 | elif h == '7988d7fb5ba5a2738f1df57ad8a6cba6e625a66346de7ee1a91c4683a5480455': 26 | # Main Unit bootloader 27 | print('TODO: Main unit bootloader is not supported yet.') 28 | exit() 29 | else: 30 | print('Unsupported binary.') 31 | exit() 32 | 33 | f.seek(offset, os.SEEK_SET) 34 | f.write(bytes(b'\x00\xb0') * 11) 35 | 36 | print('done: Lock disabled') 37 | 38 | if __name__ == '__main__': 39 | main() 40 | -------------------------------------------------------------------------------- /bootlogo/find_offset.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def find_offsets(logo_file, firmware_file): 4 | try: 5 | # Read the binary content of the files 6 | with open(logo_file, 'rb') as logo_f: 7 | logo = logo_f.read() 8 | 9 | with open(firmware_file, 'rb') as firmware_f: 10 | firmware = firmware_f.read() 11 | 12 | # Initialize the starting position and a list to store offsets 13 | start = 0 14 | offsets = [] 15 | 16 | # Search for all occurrences of the logo in the firmware 17 | while True: 18 | offset = firmware.find(logo, start) 19 | if offset == -1: 20 | break 21 | offsets.append(offset) 22 | start = offset + 1 # Move start position to the next byte after the found offset 23 | 24 | if offsets: 25 | # Print all offsets in decimal and hexadecimal 26 | for offset in offsets: 27 | print(f"Logo found at offset: {offset} (decimal), 0x{offset:X} (hexadecimal)") 28 | else: 29 | print("Logo not found in firmware.") 30 | 31 | except FileNotFoundError as e: 32 | print(f"Error: {e}") 33 | 34 | if __name__ == "__main__": 35 | # Ensure two arguments are provided 36 | if len(sys.argv) != 3: 37 | print("Usage: python find_offsets.py ") 38 | sys.exit(1) 39 | 40 | logo_file = sys.argv[1] 41 | firmware_file = sys.argv[2] 42 | 43 | # Call the function to search for the logo 44 | find_offsets(logo_file, firmware_file) 45 | -------------------------------------------------------------------------------- /extract_bl/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | static void initUsart(void) { 12 | // Enable peripheral clock 13 | rcc_periph_clock_enable(RCC_USART3); 14 | 15 | // Setup USART3 on PB10 16 | gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO10); 17 | 18 | // Handle the baud settings 19 | usart_set_mode(USART3, USART_MODE_TX); 20 | usart_set_baudrate(USART3, 115200); 21 | usart_set_databits(USART3, 8); 22 | usart_set_parity(USART3, USART_PARITY_NONE); 23 | usart_set_stopbits(USART3, USART_STOPBITS_1); 24 | usart_set_flow_control(USART3, USART_FLOWCONTROL_NONE); 25 | usart_enable(USART3); 26 | } 27 | 28 | size_t _write(int fd, char *ptr, int len) 29 | { 30 | int i = 0; 31 | 32 | if (fd > 2) { 33 | return -1; 34 | } 35 | 36 | while (*ptr && (i < len)) { 37 | usart_send_blocking(USART3, *ptr); 38 | i++; 39 | ptr++; 40 | } 41 | return i; 42 | } 43 | 44 | int main(void) 45 | { 46 | // There is an 8MHz external clock 47 | rcc_clock_setup_in_hse_8mhz_out_72mhz(); 48 | uint8_t *flash = (uint8_t *) 0x08000000; 49 | 50 | // Dump only the bootloader 51 | const uint32_t dump_size = 0x4000; 52 | 53 | // Uncomment to dump the whole flash 54 | // const uint32_t dump_size = 0x40000; 55 | 56 | initUsart(); 57 | 58 | while (true) { 59 | for (uint32_t i = 0; i < (1 << 18); i++) { 60 | __asm__("nop"); 61 | } 62 | 63 | for (uint32_t i = 0; i < dump_size; i++) { 64 | uint8_t c = flash[i]; 65 | 66 | if (i % 16 == 0) { 67 | printf("%08X: ", i); 68 | } 69 | 70 | printf("%02X", c); 71 | 72 | if (i % 16 == 15) { 73 | printf("\n"); 74 | } else { 75 | printf(" "); 76 | } 77 | } 78 | 79 | while (true) { 80 | __asm__("nop"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /display_bringup/Makefile: -------------------------------------------------------------------------------- 1 | 2 | PREFIX ?= arm-none-eabi- 3 | CC = $(PREFIX)gcc 4 | OBJCOPY = $(PREFIX)objcopy 5 | 6 | OPENOCD ?= openocd 7 | LIBOPENCM3_ROOT = ../lib/libopencm3 8 | 9 | FP_FLAGS = -msoft-float 10 | ARCH_FLAGS = -mcpu=cortex-m3 -mthumb $(FP_FLAGS) 11 | 12 | LD_SCRIPT = stm32f103xc_app.ld 13 | 14 | LDFLAGS = -L$(LIBOPENCM3_ROOT)/lib 15 | LDFLAGS += --static -nostartfiles 16 | LDFLAGS += -T$(LD_SCRIPT) 17 | 18 | CFLAGS = $(ARCH_FLAGS) 19 | CFLAGS += -std=c11 -g3 -O0 20 | CFLAGS += -I$(LIBOPENCM3_ROOT)/include 21 | CFLAGS += -fno-common -ffunction-sections -fdata-sections 22 | CFLAGS += -DSTM32F1 23 | 24 | LDLIBS = -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group 25 | LDLIBS += -lopencm3_stm32f1 26 | 27 | FIRMWARE_ELF = firmware.elf 28 | FIRMWARE_BIN = firmware.bin 29 | FIRMWARE_HEX = firmware.hex 30 | FIRMWARE_ENC = firmware.enc 31 | 32 | all: $(FIRMWARE_ELF) $(FIRMWARE_BIN) $(FIRMWARE_HEX) 33 | 34 | $(FIRMWARE_ELF): main.c $(LIBOPENCM3_ROOT)/lib/libopencm3_stm32f1.a 35 | $(CC) $(CFLAGS) $(LDFLAGS) main.c $(LDLIBS) -o firmware.elf 36 | 37 | $(LIBOPENCM3_ROOT)/Makefile: 38 | git submodule update --init 39 | 40 | $(LIBOPENCM3_ROOT)/lib/libopencm3_%.a: $(LIBOPENCM3_ROOT)/Makefile 41 | $(MAKE) -C $(LIBOPENCM3_ROOT) TARGETS=stm32/f1 42 | 43 | $(FIRMWARE_BIN): $(FIRMWARE_ELF) 44 | $(OBJCOPY) -O binary $< $@ 45 | 46 | $(FIRMWARE_HEX): $(FIRMWARE_ELF) 47 | $(OBJCOPY) -O ihex $< $@ 48 | 49 | $(FIRMWARE_ENC): $(FIRMWARE_BIN) 50 | python ../encryption/encryption.py $(KEY) encrypt $(FIRMWARE_BIN) $(FIRMWARE_ENC) 51 | 52 | flash-encrypted: $(FIRMWARE_ENC) 53 | g90updatefw $(FIRMWARE_ENC) $(TTYUSB) 54 | 55 | flash: $(FIRMWARE_ELF) 56 | $(OPENOCD) -f interface/jlink.cfg -c "transport select swd" -f target/stm32f1x.cfg -c "program $(FIRMWARE_ELF) verify reset exit" 57 | 58 | clean: 59 | $(RM) $(FIRMWARE_ELF) $(FIRMWARE_BIN) $(FIRMWARE_HEX) $(FIRMWARE_ENC) 60 | 61 | distclean: clean 62 | $(MAKE) -C $(LIBOPENCM3_ROOT) clean 63 | 64 | .PHONY: all flash clean distclean $(FIRMWARE_ENC) 65 | -------------------------------------------------------------------------------- /extract_bl_main/Makefile: -------------------------------------------------------------------------------- 1 | 2 | PREFIX ?= arm-none-eabi- 3 | CC = $(PREFIX)gcc 4 | OBJCOPY = $(PREFIX)objcopy 5 | 6 | OPENOCD ?= openocd 7 | LIBOPENCM3_ROOT = ../lib/libopencm3 8 | 9 | FP_FLAGS = -mfloat-abi=hard -mfpu=fpv4-sp-d16 10 | ARCH_FLAGS = -mthumb -mcpu=cortex-m4 $(FP_FLAGS) 11 | 12 | LD_SCRIPT = stm32f429_app.ld 13 | 14 | LDFLAGS = -L$(LIBOPENCM3_ROOT)/lib 15 | LDFLAGS += --static -nostartfiles 16 | LDFLAGS += -T$(LD_SCRIPT) 17 | 18 | CFLAGS = $(ARCH_FLAGS) 19 | CFLAGS += -std=c11 -g3 -Os 20 | CFLAGS += -I$(LIBOPENCM3_ROOT)/include 21 | CFLAGS += -fno-common -ffunction-sections -fdata-sections 22 | CFLAGS += -DSTM32F4 23 | 24 | LDLIBS = -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group 25 | LDLIBS += -lopencm3_stm32f4 26 | 27 | FIRMWARE_ELF = firmware.elf 28 | FIRMWARE_BIN = firmware.bin 29 | FIRMWARE_HEX = firmware.hex 30 | FIRMWARE_ENC = firmware.enc 31 | 32 | all: $(FIRMWARE_ELF) $(FIRMWARE_BIN) $(FIRMWARE_HEX) 33 | 34 | $(FIRMWARE_ELF): main.c $(LIBOPENCM3_ROOT)/lib/libopencm3_stm32f4.a 35 | $(CC) $(CFLAGS) $(LDFLAGS) main.c $(LDLIBS) -o firmware.elf 36 | 37 | $(LIBOPENCM3_ROOT)/Makefile: 38 | git submodule update --init 39 | 40 | $(LIBOPENCM3_ROOT)/lib/libopencm3_%.a: $(LIBOPENCM3_ROOT)/Makefile 41 | $(MAKE) -C $(LIBOPENCM3_ROOT) TARGETS=stm32/f4 42 | 43 | $(FIRMWARE_BIN): $(FIRMWARE_ELF) 44 | $(OBJCOPY) -O binary $< $@ 45 | 46 | $(FIRMWARE_HEX): $(FIRMWARE_ELF) 47 | $(OBJCOPY) -O ihex $< $@ 48 | 49 | $(FIRMWARE_ENC): $(FIRMWARE_BIN) 50 | python ../encryption/encryption.py $(KEY) encrypt $(FIRMWARE_BIN) $(FIRMWARE_ENC) 51 | 52 | flash-encrypted: $(FIRMWARE_ENC) 53 | g90updatefw $(FIRMWARE_ENC) $(TTYUSB) 54 | 55 | flash: $(FIRMWARE_ELF) 56 | $(OPENOCD) -f interface/cmsis-dap.cfg -c "transport select swd" -f target/stm32f4x.cfg -c "program $(FIRMWARE_ELF) verify reset exit" 57 | 58 | clean: 59 | $(RM) $(FIRMWARE_ELF) $(FIRMWARE_BIN) $(FIRMWARE_HEX) $(FIRMWARE_ENC) 60 | 61 | distclean: clean 62 | $(MAKE) -C $(LIBOPENCM3_ROOT) clean 63 | 64 | .PHONY: all flash clean distclean 65 | -------------------------------------------------------------------------------- /extract_bl/Makefile: -------------------------------------------------------------------------------- 1 | 2 | PREFIX ?= arm-none-eabi- 3 | CC = $(PREFIX)gcc 4 | OBJCOPY = $(PREFIX)objcopy 5 | 6 | OPENOCD ?= openocd 7 | LIBOPENCM3_ROOT = ../lib/libopencm3 8 | 9 | FP_FLAGS = -msoft-float 10 | ARCH_FLAGS = -mcpu=cortex-m3 -mthumb $(FP_FLAGS) 11 | 12 | LD_SCRIPT = stm32f103xc_app.ld 13 | 14 | LDFLAGS = -L$(LIBOPENCM3_ROOT)/lib 15 | LDFLAGS += --static -nostartfiles 16 | LDFLAGS += -T$(LD_SCRIPT) 17 | 18 | CFLAGS = $(ARCH_FLAGS) 19 | CFLAGS += -std=c11 -g3 -O0 20 | CFLAGS += -I$(LIBOPENCM3_ROOT)/include 21 | CFLAGS += -fno-common -ffunction-sections -fdata-sections 22 | CFLAGS += -DSTM32F1 23 | 24 | LDLIBS = -Wl,--start-group -lc -lgcc -lnosys -Wl,--end-group 25 | LDLIBS += -lopencm3_stm32f1 26 | 27 | FIRMWARE_ELF = firmware.elf 28 | FIRMWARE_BIN = firmware.bin 29 | FIRMWARE_HEX = firmware.hex 30 | FIRMWARE_ENC = firmware.enc 31 | 32 | all: $(FIRMWARE_ELF) $(FIRMWARE_BIN) $(FIRMWARE_HEX) 33 | 34 | $(FIRMWARE_ELF): main.c $(LIBOPENCM3_ROOT)/lib/libopencm3_stm32f1.a 35 | $(CC) $(CFLAGS) $(LDFLAGS) main.c $(LDLIBS) -o firmware.elf 36 | 37 | $(LIBOPENCM3_ROOT)/Makefile: 38 | git submodule update --init 39 | 40 | $(LIBOPENCM3_ROOT)/lib/libopencm3_%.a: $(LIBOPENCM3_ROOT)/Makefile 41 | $(MAKE) -C $(LIBOPENCM3_ROOT) TARGETS=stm32/f1 42 | 43 | $(FIRMWARE_BIN): $(FIRMWARE_ELF) 44 | $(OBJCOPY) -O binary $< $@ 45 | 46 | $(FIRMWARE_HEX): $(FIRMWARE_ELF) 47 | $(OBJCOPY) -O ihex $< $@ 48 | 49 | $(FIRMWARE_ENC): $(FIRMWARE_BIN) 50 | python ../encryption/encryption.py $(KEY) encrypt $(FIRMWARE_BIN) $(FIRMWARE_ENC) 51 | 52 | flash-encrypted: $(FIRMWARE_ENC) 53 | g90updatefw $(FIRMWARE_ENC) $(TTYUSB) 54 | 55 | flash-encrypted-prebuilt: 56 | g90updatefw $(FIRMWARE_ENC) $(TTYUSB) 57 | .PHONY: flash-encrypted-prebuilt 58 | 59 | flash: $(FIRMWARE_ELF) 60 | $(OPENOCD) -f interface/jlink.cfg -c "transport select swd" -f target/stm32f1x.cfg -c "program $(FIRMWARE_ELF) verify reset exit" 61 | 62 | clean: 63 | $(RM) $(FIRMWARE_ELF) $(FIRMWARE_BIN) $(FIRMWARE_HEX) $(FIRMWARE_ENC) 64 | 65 | distclean: clean 66 | $(MAKE) -C $(LIBOPENCM3_ROOT) clean 67 | 68 | .PHONY: all flash clean distclean 69 | -------------------------------------------------------------------------------- /extract_bl_main/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | static void clock_setup(void) 12 | { 13 | rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_168MHZ]); 14 | 15 | rcc_periph_clock_enable(RCC_GPIOA); 16 | rcc_periph_clock_enable(RCC_USART1); 17 | } 18 | 19 | static void usart_setup(void) 20 | { 21 | usart_set_baudrate(USART1, 115200); 22 | usart_set_databits(USART1, 8); 23 | usart_set_stopbits(USART1, USART_STOPBITS_1); 24 | usart_set_mode(USART1, USART_MODE_TX); 25 | usart_set_parity(USART1, USART_PARITY_NONE); 26 | usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE); 27 | 28 | usart_enable(USART1); 29 | } 30 | 31 | static void gpio_setup(void) 32 | { 33 | /* Setup GPIO pins for USART1 transmit. */ 34 | gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO9); 35 | 36 | gpio_mode_setup(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO8); 37 | 38 | /* Setup USART1 TX pin as alternate function. */ 39 | gpio_set_af(GPIOA, GPIO_AF7, GPIO9); 40 | } 41 | 42 | size_t _write(int fd, char *ptr, int len) 43 | { 44 | int i = 0; 45 | 46 | if (fd > 2) { 47 | return -1; 48 | } 49 | 50 | while (*ptr && (i < len)) { 51 | usart_send_blocking(USART1, *ptr); 52 | i++; 53 | ptr++; 54 | } 55 | return i; 56 | } 57 | 58 | int main(void) 59 | { 60 | uint8_t buf[32]; 61 | int i, j; 62 | 63 | clock_setup(); 64 | gpio_setup(); 65 | usart_setup(); 66 | 67 | // Dump only the bootloader 68 | const uint32_t dump_size = 0x4000; 69 | uint8_t *flash = (uint8_t *) 0x08000000; 70 | 71 | for (uint32_t i = 0; i < dump_size; i++) { 72 | uint8_t c = flash[i]; 73 | 74 | // Blink the blue led 75 | if (i % 0x100 == 0) { 76 | gpio_toggle(GPIOA, GPIO8); 77 | } 78 | 79 | if (i % 16 == 0) { 80 | printf("%08X: ", i); 81 | } 82 | 83 | printf("%02X", c); 84 | 85 | if (i % 16 == 15) { 86 | printf("\n"); 87 | } else { 88 | printf(" "); 89 | } 90 | } 91 | 92 | while (true) { 93 | __asm__("nop"); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /bootlogo/patch_bootlogo.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import os 3 | import sys 4 | import hashlib 5 | 6 | offsets = { 7 | "486d8c1cb135ef1cbdaa15d50ad28d54fd99ac711dd9763c2d49b48d818b7ac6": 0x20854, # G90_DispUnit_FW_V1.74final.xgf.decrypt 8 | "0e9023ecc41d017d96143fa955c5ac74646bf7ad4f6347a359e013e5160c9fc2": 0x20530, # G90_DispUnit_FW_V1.75final2020090501.xgf.decrypt 9 | "9a23e1c36afd2348f43157cbbf62235e6e1070ef807ded3926b18320ce1f22b7": 0x1C6F8, # G90_DispUnit_Fw_V1.80.xgf.decrypt 10 | } 11 | 12 | def main(): 13 | if len(sys.argv) != 3: 14 | print(f"Usage: {sys.argv[0]} ") 15 | print(f" note: converts image to black and white before patching") 16 | return 17 | 18 | new_bootlogo_image = sys.argv[1] 19 | decrypted_firmware = sys.argv[2] 20 | 21 | img = Image.open(new_bootlogo_image).convert("L") 22 | img_data = img.load() 23 | 24 | width, height = img.size 25 | if width != 48 or height != 48: 26 | print(f"new bootlogo image width and height must be 48x48 (was {width}x{height})") 27 | return 28 | 29 | img_bytes = [] 30 | for y in range(48): 31 | for x in range(48 // 8): 32 | b = 0 33 | for i in range(8): 34 | pixel = img_data[x * 8 + i, y] 35 | bit = 1 if pixel == 255 else 0 36 | b = (b << 1) | bit 37 | img_bytes.append(b) 38 | 39 | with open(decrypted_firmware, "r+b") as f: 40 | data = f.read() 41 | 42 | m = hashlib.sha256() 43 | m.update(data) 44 | h = m.hexdigest() 45 | 46 | if h in offsets: 47 | offset = offsets[h] 48 | print(f"Found matching hash {h}, using offset 0x{offset:x}") 49 | f.seek(offset, os.SEEK_SET) 50 | f.write(bytes(img_bytes)) 51 | 52 | print("done: new bootlogo image patched") 53 | else: 54 | print(f"Unknown hash {h}") 55 | print("error: Offset unknown for provided firmware (use find_offset.py to try to find the offset)") 56 | 57 | if __name__ == "__main__": 58 | main() 59 | -------------------------------------------------------------------------------- /encryption/encryption.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from Crypto.Cipher import AES 3 | 4 | def do_encrypt(cipher, args): 5 | with args.fw_plaintext as f_in, args.fw_encrypted as f_out: 6 | while f_in.readable(): 7 | block_in = f_in.read(16) 8 | block = cipher.encrypt(block_in.ljust(16, b'\x00')) 9 | f_out.write(block) 10 | if len(block_in) != 16: 11 | break 12 | print(f'success: encrypted firmware written to {f_out.name}') 13 | 14 | def do_decrypt(cipher, args): 15 | with args.fw_encrypted as f_in, args.fw_plaintext as f_out: 16 | f_out.write(cipher.decrypt(f_in.read())) 17 | print(f'success: plaintext firmware written to {f_out.name}') 18 | 19 | def main(): 20 | parser = argparse.ArgumentParser(description='xiegu g90 firmware encryption/decryption utility') 21 | parser.add_argument('key', help='256-bit AES key in hex') 22 | 23 | subparsers = parser.add_subparsers(title='mode', dest='mode', required=True) 24 | 25 | parser_encrypt = subparsers.add_parser('encrypt', help='encrypt a plaintext firmware file') 26 | parser_encrypt.add_argument( 27 | 'fw_plaintext', type=argparse.FileType('rb'), 28 | help='path to read the plaintext firmware file from' 29 | ) 30 | parser_encrypt.add_argument( 31 | 'fw_encrypted', type=argparse.FileType('wb'), 32 | help='path to write the encrypted firmware file to' 33 | ) 34 | parser_encrypt.set_defaults(mode_func=do_encrypt) 35 | 36 | parser_decrypt = subparsers.add_parser('decrypt', help='decrypt an encrypted firmware file') 37 | parser_decrypt.add_argument( 38 | 'fw_encrypted', type=argparse.FileType('rb'), 39 | help='path to read the encrypted firmware file from' 40 | ) 41 | parser_decrypt.add_argument( 42 | 'fw_plaintext', type=argparse.FileType('wb'), 43 | help='path to write the plaintext firmware file to' 44 | ) 45 | parser_decrypt.set_defaults(mode_func=do_decrypt) 46 | 47 | args = parser.parse_args() 48 | 49 | try: 50 | key = bytes.fromhex(args.key) 51 | assert len(key) == 256 // 8 52 | except: 53 | print('error: could not parse provided key as a 256-bit hexidecimal number') 54 | return 55 | 56 | cipher = AES.new(key, AES.MODE_ECB) 57 | args.mode_func(cipher, args) 58 | 59 | if __name__ == '__main__': 60 | main() 61 | -------------------------------------------------------------------------------- /display_bringup/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /* 11 | Pinout for the screen KD018QQTBN009 12 | Screen uses a ST7735S controller 13 | Resolution: 128 x 160, 16b colors 14 | 15 | FLEX MCU Function Peripheral 16 | 31 17 PA3 WR(SPI-RS) 17 | 29 20 PA4 Reset SPI1_NSS 18 | 28 21 PA5 Clock SPI1_SCK 19 | 26 23 PA7 Serial in D0 (32R) SPI1_MOSI 20 | 2-3 62 PB9 Backlight 21 | */ 22 | 23 | static void initUsart(void) { 24 | // Enable peripheral clock 25 | rcc_periph_clock_enable(RCC_USART3); 26 | 27 | // Setup USART3 on PB10 28 | gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO10); 29 | 30 | // Handle the baud settings 31 | usart_set_mode(USART3, USART_MODE_TX); 32 | usart_set_baudrate(USART3, 115200); 33 | usart_set_databits(USART3, 8); 34 | usart_set_parity(USART3, USART_PARITY_NONE); 35 | usart_set_stopbits(USART3, USART_STOPBITS_1); 36 | usart_set_flow_control(USART3, USART_FLOWCONTROL_NONE); 37 | usart_enable(USART3); 38 | } 39 | 40 | size_t _write(int fd, char *ptr, int len) 41 | { 42 | int i = 0; 43 | 44 | if (fd > 2) { 45 | return -1; 46 | } 47 | 48 | while (*ptr && (i < len)) { 49 | usart_send_blocking(USART3, *ptr); 50 | i++; 51 | ptr++; 52 | } 53 | return i; 54 | } 55 | 56 | void initGPIO(void) 57 | { 58 | rcc_periph_clock_enable(RCC_GPIOA); 59 | rcc_periph_clock_enable(RCC_GPIOB); 60 | 61 | gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO3); 62 | gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO4); 63 | gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO5); 64 | gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO7); 65 | 66 | gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO9); 67 | 68 | gpio_set(GPIOA, GPIO3); 69 | gpio_set(GPIOB, GPIO9); 70 | 71 | gpio_clear(GPIOA, GPIO4); 72 | gpio_clear(GPIOA, GPIO5); 73 | gpio_clear(GPIOA, GPIO7); 74 | } 75 | 76 | int main(void) 77 | { 78 | // There is an 8MHz external clock 79 | rcc_clock_setup_in_hse_8mhz_out_72mhz(); 80 | 81 | initUsart(); 82 | initGPIO(); 83 | 84 | for (uint32_t i = 0; i < (1 << 18); i++) { 85 | __asm__("nop"); 86 | } 87 | 88 | printf("Boot!\n"); 89 | 90 | while (true) { 91 | __asm__("nop"); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tools and guides for analyzing Xiegu G90 firmware 2 | 3 | This repository contains tools and guides for working with the firmware for the Xiegu G90 HF radio. 4 | 5 | ## Getting started 6 | 7 | Please check out the [wiki](https://github.com/OpenHamradioFirmware/G90Tools/wiki)! 8 | 9 | ## Hardware architecture 10 | 11 | The Xiegu G90 is built with a detachable display unit and a main unit. 12 | 13 | Main unit: 14 | - STM32F429ZGT6 15 | 16 | Display unit: 17 | - STM32F103RCT6 18 | 19 | ## Firmware key extraction 20 | 21 | The bootloader seems to be very similar in both units. It is able to read an encrypted firmware over UART, then decrypt and write it to the flash. 22 | 23 | The firmware is encrypted with is AES256 ECB. 24 | 25 | > :warning: The microcontroller has flash readout protection enabled, which means that if you try to disable it, the flash will be erased and your unit will be bricked (unless you can program a bootloader that you have recovered before). 26 | 27 | The key can be extracted with the following method: 28 | - Connect an SWD debugger (ST-Link, CMSIS-DAP, J-Link etc). 29 | - Reset the device without attaching the debugger. 30 | - Load a firmware over UART, e.g. using g90updatefw: 31 | ``` 32 | # Use any official firmware 33 | FIRMWARE=G90_MainUnit_FW_V1.74final.xgf 34 | 35 | # Use the appropriate tty for your system 36 | TTYUSB=/dev/ttyUSB0 37 | 38 | g90updatefw $FIRMWARE $TTYUSB 39 | ``` 40 | - While the upload is running, attach openocd and dump the ram: 41 | ``` 42 | # Use the appropriate interface that matches your SWD debugger 43 | #INTERFACE=cmsis-dap.cfg 44 | #INTERFACE=stlink.cfg 45 | 46 | openocd -f interface/$INTERFACE -f target/stm32f1x.cfg -c "init; dump_image ramdump.bin 0x20000000 0x50000" 47 | ``` 48 | - Run [findaes](https://sourceforge.net/projects/findaes/) to find the key. 49 | ``` 50 | # Download https://sourceforge.net/projects/findaes/files/latest/download 51 | unzip findaes-1.2.zip 52 | cd findaes-1.2 53 | make 54 | ./findaes ramdump.bin 55 | ``` 56 | 57 | To encrypt and decrypt firmware, see [encryption.py](encryption/encryption.py). 58 | 59 | ## Bootloader extraction 60 | 61 | To extract the bootloader firmware: 62 | - Build and flash the extract_bl firmware: 63 | ``` 64 | cd extract_bl 65 | make KEY=... TTYUSB=/dev/ttyUSB0 flash-encrypted 66 | ``` 67 | - Log the uart output to a file: `cat /dev/ttyUSB0 > bl.hex` 68 | - Power cycle the device. 69 | - Wait for the dump to complete. 70 | - Convert hex to bin: `xxd -g 1 -r < bl.hex > bl.bin` 71 | 72 | Alternatively, an already encrypted binary is included and can be flashed to the target directly to simplify things. If going this route, simply run `make TTYUSB=/dev/ttyUSB0 flash-encrypted-prebuilt` from the `extract_bl` directory. Run the steps above to extract the bootloader binary. Then the key can be recovered from the dump like so: `dd if=bl.bin skip=7496 bs=1 count=32 2> /dev/null > key.bin`. 73 | 74 | ## External tools 75 | 76 | - [DaleFarnsworth/g90updatefw](https://github.com/DaleFarnsworth/g90updatefw) 77 | 78 | ## Disclaimer 79 | 80 | > :warning: Warning :warning: 81 | 82 | There is no warranty provided. Any damage caused by using any of these tools is your own responsibility. 83 | 84 | ## License 85 | 86 | TBD. 87 | --------------------------------------------------------------------------------