Reparatur eines Sophos AP 15C Wireless Acess Points
Gegenstand dieses Beitrags ist ein Sophos AP 15C Wireless Access Point, der außer einer dauerhaft orange leuchtenden LED keinerlei Funktion hat.
Das Handbuch ist wenig hilfreich bei der Erklärung oder gar Beseitigung dieses Fehlerzustandes. Das bedeutet entweder das Ende dieses Gerätes, oder es wird abseits von Herstellervorgaben und Dokumentation eine Lösung herbeigeführt. Hierbei kommen notwendiger Weise Techniken und Werkzeuge zum Einsatz, die bei der Sicherheitsanalyse von Embedded Devices verwendet werden.
Schritt 1: Seriellen Zugang finden
Beim AP15C erübrigt sich die oft notwendige Suche:
Die serielle Schnittstelle ist als RJ45 Anschluss mit RS-232 Signalisierung nach außen geführt worden. Die Belegung des Console Anschlusses ist identisch zu Cisco Geräten, sodass ein entsprechendes serielles Kabel verwendet werden kann. Verbunden mit einem Terminalprogram und den Übertragungseinstellungen 115200 8N1 erscheint die folgende Ausgabe:
U-Boot 1.1.4-gbcb07c22-dirty (Mar 3 2016 - 09:08:21) ELX version: 1.0.0 7679WSC - Scorpion 1.0DRAM: sri Scorpion 1.0 ath_ddr_initial_config(178): (32bit) ddr2 init tap = 0x00000003 sri Scorpion 1.0 ath_ddr_initial_config(178): (16bit) ddr2 init tap = 0x00000003 Tap (low, high) = (0x1, 0x1f) Tap values = (0x10, 0x10, 0x10, 0x10) 128 MB Flash Manuf Id 0xc2, DeviceId0 0x20, DeviceId1 0x18 Flash [MX25L12845E] sectors: 256 Flash: 16 MB *** Warning *** : PCIe WLAN Module not found !!! In: serial Out: serial Err: serial Net: ath_gmac_enet_initialize... athrs_sgmii_res_cal: cal value = 0xe ath_gmac_enet_initialize: reset mask:c02200 Scorpion ---->8035 PHY* AR8035 PHY reg init : cfg1 0x80000000 cfg2 0x7114 eth0: 00:1a:8c:<entfernt> AR8035 found! [0:4]Phy ID 4d:d072 Port 0, Neg Success eth0 up eth0 Setting 0x18116290 to 0x458ba14f Hit any key to stop autoboot: 0 ## Booting image at 9f070000 ... Image Name: MIPS OpenWrt Linux-3.18.11 Created: 2018-10-02 15:45:39 UTC Image Type: MIPS Linux Kernel Image (gzip compressed) Data Size: 6896596 Bytes = 6.6 MB Load Address: 80060000 Entry Point: 80060000 Verifying Checksum at 0x9f070040 ...Bad Data CRC
Die Ursache für die Fehlfunktion ist somit klar: Das Abbild des Betriebssystem im Flash Speicher ist fehlerhaft und die Kontrolle der Prüfsumme durch den Bootloader scheitert.
Es spricht wenig dafür, dass ein schwerwiegender Hardwaredefekt vorliegt. Schon der Bootloader (Das U-Boot) wird aus dem Flash Speicher geladen und das Gerät zeigt bis zur Prüfsumme keine Fehlfunktion. Der AP wird voraussichtlich korrekt arbeiten, wenn das Abbild des Betriebssystems repariert werden kann.
Die naheliegende Lösung einer Rettung mit dem U-Boot ist für ein anderes Modell (AP55) in der Sophos Community beschrieben und benennt eine Quelle für passende Firmware Dateien: /etc/wireless/firmware auf jeder Sophos UTM. Eine wenig überraschende Erkenntnis, wenn man bedenkt, dass die Sophos UTM zentral die Firmware auf den angeschlossenen APs verwaltet und ggf. aktualisiert.
Backup des Flash
Bevor unwiederbringlich Teile des Flash gelöscht oder überschrieben werden, bietet es sich an, ein Backup zu haben. Auch hier ist das U-Boot hilfreich, wenn auch etwas widerstrebend. Die Extraktion von Firmware ist keine typische Anforderung bei der Entwicklung und dem Betrieb von Embedded Devices und die Extraktion gelingt nur unter Verwendung von Funktionen, die für diesen Zweck nicht gedacht sind.
An dieser Stelle ist es auch sinnvoll, den automatischen Start des Betriebssystems bis auf weiteres zu unterbinden. Die Prüfsumme stimmt nicht, und das U-Boot hängt sich nach der Kontrolle auf. Dieser Vorgang müsste also nach jedem Neustart mit einem Tastendruck innerhalb eines 2 Sekunden Zeitfensters unterbrochen werden. Die Umgebungsvariable „bootdelay“ wird hierfür auf den Wert -1 gesetzt:
ath> printenv bootdelay bootdelay=2 ath> setenv bootdelay -1 ath> saveenv Saving Environment to Flash... Un-Protected 1 sectors Erasing Flash...Erasing flash... First 0x4 last 0x4 sector size 0x10000 4 Erased 1 sectors Writing to Flash... write addr: 9f040000 done Protected 1 sectors ath> printenv bootdelay bootdelay=-1
Die Lage und Ausdehnung des verwendeten NOR Flash Speichers MX25L12845E (siehe Startausgabe des U-Boot) verrät das Kommando „bdinfo“:
ath> bdi boot_params = 0x87F7BFB0 memstart = 0x80000000 memsize = 0x08000000 flashstart = 0x9F000000 flashsize = 0x01000000 flashoffset = 0x00029BD4 ethaddr = 00:1A:8C:<entfernt> ip_addr = 192.168.99.9 baudrate = 115200 bps
Der Inhalt des Flash ist also ab Adresse 0x9F000000 in den Adressbereich der CPU eingeblendet und hat eine Ausdehnung von 0x01000000 Bytes oder 16 MiB.
Mit dem Kommando „md“ kann der Inhalt von Speicheradressen als Hex Dump angezeigt werden:
ath> md 9F000000 9f000000: 100000ff 00000000 100000fd 00000000 ................ 9f000010: 1000020d 00000000 1000020b 00000000 ................ ... 9f0000e0: 100001d9 00000000 100001d7 00000000 ................ 9f0000f0: 100001d5 00000000 100001d3 00000000 ................ ath>
Für den Abzug des Speichers wird Block für Block der Inhalt als Hexdump durch ein Programm ausgelesen und als Binärdaten auf die Festplatte geschrieben:
#!/usr/bin/env python3 import sys import serial import argparse import re # ============================================================================ # UBPROMPT = 'ath> ' # ============================================================================ # class ReadLine: def __init__(self, s): self.buffer = bytearray() self.sd = s def peek(self): return(self.buffer.decode('ascii')) def readln(self): n = self.buffer.find(b'\n') if n >= 0: ln = self.buffer[:n] self.buffer = self.buffer[n+1:] return(ln.decode('ascii')) while True: rd = self.sd.read(self.sd.in_waiting if self.sd.in_waiting else 1) if rd == b'': # Timeout return('') n = rd.find(b'\r') while n >= 0: rd = rd[:n] + rd[n+1:] n = rd.find(b'\r') self.buffer.extend(rd) n = self.buffer.find(b'\n') if n >= 0: ln = self.buffer[:n] self.buffer = self.buffer[n+1:] return(ln.decode('ascii')) # ============================================================================ # def getargs(args): parser = argparse.ArgumentParser(description="Dump flash content") parser.add_argument("-p", "--port", nargs='?', default="/dev/ttyUSB0", help="serial port (default: %(default)s)") parser.add_argument("file", nargs=1, help="file for flash dump") return(parser.parse_args(args)) # ============================================================================ # def main(sysargs): args = getargs(sysargs) try: sd = serial.Serial(port=args.port, baudrate=115200, timeout=1, write_timeout=1, exclusive=True) except: print("{0}".format(sys.exc_info()[1])) return(1) try: rl = ReadLine(sd) sd.write(b' \r\n') while True: ln = rl.readln() if ln == '': break if rl.peek() != UBPROMPT: print("Not connected to device") return(1) print("Connected to device, dumping flash") # U-Boot memory dump # # ath> md 9F000000 # 9f000000: 100000ff 00000000 100000fd 00000000 ................ # 9f000010: 1000020d 00000000 1000020b 00000000 ................ # ... # 9f0000e0: 100001d9 00000000 100001d7 00000000 ................ # 9f0000f0: 100001d5 00000000 100001d3 00000000 ................ # ath> fh = open(args.file[0], 'bw') sd.write(b'md 9F000000\r\n') while True: token = rl.readln().split(' ') if not re.match(r'^[0-9a-f]{8}:$', token[0]): continue for word in token[1:5]: fh.write(bytes.fromhex(word)) adr = int(token[0][:-1], 16) if adr == 0x9F000000 + 0x01000000 - 16: break if (adr & 0xFF) == 0xF0: # Block complete sd.write(b'\r\n') # Request next block if (adr & 0xFFFF) == 0x00F0: print('{0:.1f}%'.format((adr - 0x9F000000) * 100.0 / 0x01000000), end='') print('.', end='') sys.stdout.flush() fh.close() print('\nOperation complete') return(0) except serial.SerialTimeoutException: print("Write timeout") return except: print("{0}".format(sys.exc_info()[1])) return(1) if(__name__ == '__main__'): sys.exit(main(sys.argv[1:])) # # EOF
Die Vorgehensweise ist offensichtlich wenig effizient, und der komplette Vorgang dauert daher etwa 100 Minuten:
$ time ./flashdump ap15c-broken.bin Connected to device, dumping flash 0.0%.................................................................... ........................................................................ ........................................................................ ............................................0.4%........................ ... Operation complete real 99m6.674s user 25m18.996s sys 12m43.232s
Binwalk des Backups
Binwalk sucht innerhalb einer Datei nach Signaturen bekannter Dateitypen und erlaubt auch die Extraktion dieser Dateien für weitere Untersuchungen. Da jetzt ein kompletter Abzug des Flash-Speichers existiert, kann ein kurzer Blick nicht schaden. Für den Inhalt des Flash des AP15C zeigt binwalk:
$ binwalk ap15c-broken.bin DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 137648 0x219B0 U-Boot version string, "U-Boot 1.1.4-gbcb07c22-dirty (Mar 3 2016 - 09:08:21)" 137904 0x21AB0 CRC32 polynomial table, big endian 458752 0x70000 uImage header, header size: 64 bytes, header CRC: 0x320C648F, created: 2018-10-02 15:45:39, image size: 6896596 bytes, Data Address: 0x80060000, Entry Point: 0x80060000, data CRC: 0xAC0EF258, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: gzip, image name: "MIPS OpenWrt Linux-3.18.11" 458816 0x70040 gzip compressed data, maximum compression, has original file name: "vmlinux-initramfs-ap15c", from Unix, last modified: 2018-10-02 15:45:38 8388608 0x800000 uImage header, header size: 64 bytes, header CRC: 0xE32440BF, created: 2016-05-03 09:05:46, image size: 1521139 bytes, Data Address: 0x80002000, Entry Point: 0x80284B60, data CRC: 0x384EAED5, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "Linux Kernel Image" 8388672 0x800040 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 4644796 bytes 9961472 0x980000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 2464622 bytes, 78 inodes, blocksize: 131072 bytes, created: 2016-05-03 09:05:20 15138816 0xE70000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 924978 bytes, 25 inodes, blocksize: 131072 bytes, created: 2015-08-19 03:09:47 16073220 0xF54204 xz compressed data 16085496 0xF571F8 xz compressed data 16110476 0xF5D38C xz compressed data 16130738 0xF622B2 xz compressed data 16131156 0xF62454 xz compressed data 16131600 0xF62610 xz compressed data 16252928 0xF80000 JFFS2 filesystem, big endian
Bei genauer Betrachtung fällt auf, dass sich zwei Abbilder in dem Flash-Speicher befinden. Ein 6.6 MiB großes Abbild an der Adresse 0x70000 und ein 1.5 MiB großes Abbild an der Adresse 0x800000. Automatisch geladen wird das wesentlich größere Abbild an der Adresse 0x70000. Es kann sich aufgrund des Größenunterschieds bei dem unbekannten Abbild nicht um eine Installation mit vollem Funktionsumfang oder ein Werkzeug zur vollständigen Wiederherstellung des AP handeln. Es wäre interessant zu wissen, welche Funktionen das sekundäre Abbild bietet.
Alternatives Abbild starten
Vom U-Boot aufgerufen startet das zweite Abbild einwandfrei:
ath> bootm 0x9f800000 ## Booting image at 9f800000 ... Image Name: Linux Kernel Image Created: 2016-05-03 9:05:46 UTC Image Type: MIPS Linux Kernel Image (lzma compressed) Data Size: 1521139 Bytes = 1.5 MB Load Address: 80002000 Entry Point: 80284b60 Verifying Checksum at 0x9f800040 ...OK Uncompressing Kernel Image ... OK Starting kernel ... Linux version 3.2.9 (root@mark-OptiPlex-3010) (gcc version 4.3.6 (Buildroot 2013.05) ) #3 Tue May 3 17:05:41 CST 2016 bootconsole [early0] enabled CPU revision is: 00019750 (MIPS 74Kc) SoC: QCA955x ....
Allerdings präsentiert auf der seriellen Konsole eine äußerst eingeschränkte „Shell“:
************************************************************************ * ---ELX--- * ************************************************************************ KernelApp version: 1.0.0 build date: 2016/05/03 build time: 17:03:48 cmd> KernelApp version: 1.0.0 build date: 2016/05/03 build time: 17:03:48 cmd> ? ************************************************************************ * ---ELX--- * ************************************************************************ KernelApp version: 1.0.0 build date: 2016/05/03 build time: 17:03:48 cmd> help ************************************************************************ * ---ELX--- * ************************************************************************ KernelApp version: 1.0.0 build date: 2016/05/03 build time: 17:03:48 cmd> help games
Ohne einen Blick in die Firmware geht es an dieser Stelle nicht weiter. Hierzu wird das Abbild an Adresse 0x800000 mit der Größe 1521139 Bytes aus dem Abzug des Flash-Speichers herausgeschnitten und mit Binwalk ausgepackt:
$ dd if=ap15c-broken.bin of=second-uimg.bin bs=1 skip=8388608 count=1521139 $ binwalk -C /tmp/bw -Me second-uimg.bin
Als Terminalprozess wird das betreffende Program voraussichtlich in /etc/inittab angegeben sein, damit es durch den Init Prozess in einer Dauerschleife ausgeführt wird. In der Tat findet sich in /tmp/bw/_second-uimg.bin.extracted/_40.extracted/inittab:
console::sysinit:/sbin/system_init ttyS0::respawn:/sbin/console_term ttyS1::respawn:/sbin/cli console_port ::ctrlaltdel:/etc/reboot.sh ::shutdown:/etc/reboot.sh ::shutdown:/etc/reboot.sh /sbin/swapoff -a
Das Programm auf ttyS0, der ersten seriellen Schnittstelle, ist also /sbin/console_term. Eine Untersuchung dieses Programms mit „strings“ liefert interessante Ansätze für weitere Schritte:
$ strings console_term /lib/ld-uClibc.so.0 _init _fini ... ************************************************************************ * %16s * ************************************************************************ ---ELX--- KernelApp version: %s build date: %s build time: %s 1.0.0 2016/05/03 17:03:48 cmd> ediedi /bin/sh root /sbin/cli factory /sbin/factory_cli iloveedimax .shstrtab ...
Diese Ausgabe und der restliche Inhalt dieser Firmware legen nahe, dass es sich um eine Installation des Herstellers Edimax für Tests der Hardware handelt. Die Ähnlichkeit des AP15C zu ein, zwei anderen Geräten von Edimax bestärkt diesen Verdacht. Weiterhin verrät die obige Ausgabe:
************************************************************************ * ---ELX--- * ************************************************************************ KernelApp version: 1.0.0 build date: 2016/05/03 build time: 17:03:48 cmd> ediedi BusyBox v1.19.4 (2016-05-03 17:03:52 CST) built-in shell (ash) Enter 'help' for a list of built-in commands. # id uid=0 gid=0
Ausgestattet mit einer root Shell ist es nunmehr ein Leichtes mit den vorhandenen mtd-utils in dieser Installation das defekte Abbild im Flash zu ersetzen. Ausgangsdatei ist das AP15C.uimage einer Sophos UTM:
asg:/root # file /etc/wireless/firmware/AP15C.uimage /etc/wireless/firmware/AP15C.uimage: u-boot legacy uImage, MIPS OpenWrt Linux-3.18.11, Linux/MIPS, OS Kernel Image (gzip), 6896543 bytes, Tue May 29 15:11:22 2018, Load Address: 0x80060000, Entry Point: 0x80060000, Header CRC: 0x5E145AAF, Data CRC: 0x95CF1DFB
Zuerst braucht das LAN Interface auf dem AP15C eine IP:
# ifconfig br0 192.168.200.10/24
In /mnt/tmpfs ist genug Platz für die Firmware, die auf einem Webserver bereitgelegt wurde:
# cd /mnt/tmpfs/ # wget http://192.168.200.1/AP15C.uimage Connecting to 192.168.200.1 (192.168.200.1:80) # ls -l AP15C.uimage -rw-r--r-- 1 6896607 Jan 1 10:44 AP15C.uimage
Der Flash-Speicher des AP15C kennt keine Partitionstabelle wie eine Festplatte. Stattdessen sind Position und Größe der „Partitionen“ fest einprogrammiert. Die Werte finden sich in der Ausgabe des Kernels beim Start und in /proc/mtd:
# cat /proc/mtd dev: size erasesize name mtd0: 01000000 00010000 "ALL" mtd1: 00040000 00010000 "Bootloader" mtd2: 00010000 00010000 "Config" mtd3: 00010000 00010000 "ART" mtd4: 00010000 00010000 "ART2" mtd5: 00780000 00010000 "astaro_image" mtd6: 00010000 00010000 "astroa_dyn_cfg" mtd7: 00680000 00010000 "Kernel" mtd8: 00500000 00010000 "user" mtd9: 00100000 00010000 "manufacture" mtd10: 00080000 00010000 "storage"
Das Schreibziel ist das „astaro_image“ genannte mtd5 (Adresse 0x70000 im Flash).
Den Schreibvorgang erledigt flashcp aus den vorhandenen mtd-tools.
# flashcp -v AP15C.uimage /dev/mtd5 Erasing blocks: 106/106 (100%) Writing data: 6734k/6734k (100%) Verifying data: 6734k/6734k (100%)
Nach einem Neustart arbeitet der AP wieder einwandfrei:
ath> boot ## Booting image at 9f070000 ... Image Name: MIPS OpenWrt Linux-3.18.11 Created: 2018-05-29 13:11:22 UTC Image Type: MIPS Linux Kernel Image (gzip compressed) Data Size: 6896543 Bytes = 6.6 MB Load Address: 80060000 Entry Point: 80060000 Verifying Checksum at 0x9f070040 ...OK Uncompressing Kernel Image ... OK Starting kernel ... [ 0.000000] Linux version 3.18.11 (bamboo@ip-10-104-116-133) (gcc version 4.8.3 (OpenWrt/Linaro GCC 4.8-2014.04 unknown) ) #3 Tue May 29 13:11:08 UTC 2018 [ 0.000000] bootconsole [early0] enabled [ 0.000000] CPU0 revision is: 00019750 (MIPS 74Kc) [ 0.000000] SoC: Qualcomm Atheros QCA9558 ver 1 rev 0 ...
Flashen mit dem U-Boot
Wie bereits erwähnt, lässt sich die Firmware auch mit dem U-Boot neu schreiben. Wiederum muss das Abbild zuerst in den Hauptspeicher des Gerätes gelangen, um damit den Flash-Speicher zu beschreiben. Das Mittel der Wahl ist in diesem Fall TFTP.
ath> setenv ipaddr 192.168.200.10 ath> setenv serverip 192.168.200.1 ath> ping 192.168.200.1 Speed is 1000T dup 1 speed 1000 Using eth0 device host 192.168.200.1 is alive
Nachdem der TFTP Server erreichbar ist, kann das Abbild in den Hauptspeicher an Adresse 0x80060000 übertragen werden:
ath> tftpboot 0x80060000 AP15C.uimage Speed is 1000T Using eth0 device TFTP from server 192.168.200.1; our IP address is 192.168.200.10 Filename 'AP15C.uimage'. Load address: 0x80060000 Loading: ################################################################# ... ################################################################# ############################################### done Bytes transferred = 6896607 (693bdf hex)
Überprüfung der Checksumme:
ath> iminfo 0x80060000 ## Checking Image at 80060000 ... Image Name: MIPS OpenWrt Linux-3.18.11 Created: 2018-05-29 13:11:22 UTC Image Type: MIPS Linux Kernel Image (gzip compressed) Data Size: 6896543 Bytes = 6.6 MB Load Address: 80060000 Entry Point: 80060000 Verifying Checksum ... OK
Bevor das NOR Flash geschrieben werden kann, müssen die betroffenen Seiten des Speichers gelöscht werden. Dieser Vorgang setzt alle Bits in den betroffenen Speicherzellen auf den Wert ‚1‘. Dies ist notwendig, da der Schreibvorgang bei diesem Speichertyp nur einzelne Bits auf 0 setzen kann, aber nicht wieder zurück auf den Wert 1.
Die Löschanforderung ab Adresse 0x9f070000 für 6896607 bytes wird vom U-Boot automatisch gerundet auf ein Vielfaches der Seitengröße von 65536 bytes:
ath> erase 0x9f070000 +0x693bdf Erasing flash... First 0x7 last 0x70 sector size 0x10000 112 Erased 106 sectors
Das Abbild kann nunmehr von seiner Adresse im RAM (0x80060000) in den Flash-Speicher an Adresse 0x9f070000 kopiert werden.
ath> cp.b 0x80060000 0x9f070000 0x693bdf Copy to Flash... Copy 6896607 [0x693bdf] byte to Flash... write addr: 9f070000 done
Wiederum startet das System problemlos mit der neuen Firmware:
ath> boot ## Booting image at 9f070000 ... Image Name: MIPS OpenWrt Linux-3.18.11 Created: 2018-05-29 13:11:22 UTC Image Type: MIPS Linux Kernel Image (gzip compressed) Data Size: 6896543 Bytes = 6.6 MB Load Address: 80060000 Entry Point: 80060000 Verifying Checksum at 0x9f070040 ...OK Uncompressing Kernel Image ... OK Starting kernel ... [ 0.000000] Linux version 3.18.11 (bamboo@ip-10-104-116-133) (gcc version 4.8.3 (OpenWrt/Linaro GCC 4.8-2014.04 unknown) ) #3 Tue May 29 13:11:08 UTC 2018 [ 0.000000] bootconsole [early0] enabled [ 0.000000] CPU0 revision is: 00019750 (MIPS 74Kc) [ 0.000000] SoC: Qualcomm Atheros QCA9558 ver 1 rev 0
Automatischen Start reaktivieren
Nachdem der AP15C seine Firmware wieder einwandfrei lädt, kann der automatische Start des Betriebssystems durch das U-Boot wieder aktiviert werden, indem der Parameter bootdelay auf den Wert „2“ zurückgesetzt wird.
Abschließende Betrachtungen
Es sollte offensichtlich sein, dass diese Arbeiten ungeeignet sind für Geräte, die sich mit Support durch Sophos im produktiven Betrieb befinden. Zum einen ist jegliche Gewährleistung dahin und jeder Anspruch auf Herstellerunterstützung verwirkt, zum anderen ist die Hardware angezählt. Der Grund für die beschädigte Firmware könnte eine harmlose Unterbrechung während eines Updates gewesen sein, oder der Flash Speicher ist instabil und diese Reparatur wird nicht von Dauer sein. Ein Risiko, dass ich nur im Labor gerne hinnehme und dafür ein weiteres Testgerät habe.
Guten Tag,
wie heißt denn das Kabel genau?
Können Sie mir bitte Bezugsquellen nennen?
Grüße!
Sehr Cool. So lernt man.
Echt Cool was und wie man seine Ausbildung bei der TO macht,