wpa8630-hacking.md

1.The Infrastructure Problem: Reaching the Basement

Every home lab eventually hits a physical wall — or in my case, a physical floor. I needed to deploy a bare-metal Proxmox hypervisor in my basement. The location had stable AC power, but no Ethernet, and running new CAT6 wasn’t an option. The fallback was Powerline Communication (PLC) using the HomePlug AV2 standard.

However, bringing a hypervisor online isn’t just about getting a ping response; it requires strict network segmentation. My infrastructure architecture relies on 802.1Q VLANs to separate traffic:

  • VLAN 100: Management Network
  • VLAN 10: Main Network
  • VLAN 20: Development Network
  • VLAN 30: Guest Network

Here is the intended logical topology:

flowchart LR
    subgraph MainNet [Upstream Network]
        Core["Main Router"]
        PLC1["TL-PA8010P"]
        Core -- " 802.1Q Trunk<br>(VLAN 100, 10, 20, 30) " --> PLC1
    end

    Grid(("Home Electrical Wiring<br>AES-128 Encrypted NMK"))

    subgraph BasementLab [Basement Proxmox Lab]
        WPA["TL-WPA8630P<br>OpenWrt Mgmt: 192.168.100.6/24"]

        subgraph Proxmox [Proxmox Server]
            BMC["IPMI Dedicated Port<br>(VLAN 100)"]
            PVE["Proxmox Host & VMs<br>(802.1Q Trunk)"]
        end

        NAS["Standalone NAS<br>(VLAN 10)"]
        WiFi(("Wi-Fi AP<br>VLAN 10 / 20 / 30"))
        WPA -- " LAN 1 / Port 3<br>(Untagged PVID 100) " --> BMC
        WPA -- " LAN 0 / Port 4<br>(Trunk: 10, 20, 30) " --> PVE
        WPA -- " LAN 2 / Port 2<br>(Untagged PVID 10) " --> NAS
        WPA -. " Internal Bridge " .-> WiFi
    end
    PLC1 === Grid
    Grid === WPA

The Roadblock: Standard consumer PLC adapters—like the TP-Link TL-WPA8630P—are limited in capabilities. 802.1Q VLANs for network segmentation are not supported. The stock OEM firmware bridges all traffic into a single domain. It offers no way to assign Port VLAN IDs (PVIDs) and does not allow VLAN trunking.

To enable 802.1Q VLANs, I needed to get OpenWrt onto the device. Doing so for this specific device first required solving a hardware bottleneck.

2.Embedded Hacking: Taking Control of the Silicon

CAUTION

Powerline adapters operate directly on mains voltage (110V/230V), which may be lethal. Even after being unplugged, the internal capacitors remain charged for a while and can deliver a dangerous shock.

Do not power the disassembled board from the mains while working on it. As suggested by the OpenWrt Wiki for this device, if you need to power the board for live testing, inject power safely using the 3.3V serial header.

The TP-Link TL-WPA8630P v2 is powered by a highly capable Qualcomm QCA9563 SoC that has plenty of muscle to run OpenWrt, but it ships with a crippling hardware limitation: a tiny 8MB SPI flash chip.

A modern OpenWrt build equipped with necessary routing packages simply will not fit in 8MB. Because of this, official OpenWrt support for this device was dropped around OpenWrt version 21. To bring this device up to modern standards, I had two main tasks:

  1. Upgrade the physical memory chip to something bigger.
  2. Build custom OpenWrt firmware tailored to the new hardware constraints.

2.1. The Silicon Transplant and the Flash Dump

A core tenet of infrastructure engineering is always having a rollback plan. Because I didn’t want to blindly trust my manual SMD soldering abilities, I needed to capture the original flash contents before applying any heat to the board. This factory dump contains vital, device-specific data: the U-Boot bootloader (which holds compiled-in settings to properly initialize the RAM), as well as Wi-Fi calibration data required for the hardware to function.

I connected a CH341A USB SPI Programmer with a SOIC8 test clip directly to the factory chip while it was still on the motherboard. To rule out any data corruption caused by background SoC activity, I used flashrom on my Linux workstation to pull five separate dumps:

# Read the factory 8MB SPI flash chip (repeated 5 times)
flashrom -p ch341a_spi -r tplink_factory_8mb_on_board_take_1.bin
(...)

Next, I ran some basic sanity checks to ensure data integrity:

# Compare hashes between files to ensure the reads were identical
sha256sum tplink_factory_8mb_take*.bin

# Inspect the binary headers and extract human-readable text
binwalk tplink_factory_8mb_on_board_take_1.bin
strings tplink_factory_8mb_on_board_take_1.bin

Once I was confident in the backup, I desoldered the chip. Just to be certain, I put the desoldered chip directly into the programmer, took one final dump, and compared the hashes — they matched. Finally, I soldered a new 16MB Winbond 25Q128JVSQ chip that I had lying around onto the board.

2.2. Building a Custom Firmware

To utilize the newly expanded space, I needed firmware with a custom partition layout. Initially, I tried replacing the bootloader with a generic open-source alternative, but the board failed to boot—likely due to incorrect RAM initialization parameters. Rather than spending days decompiling the original bootloader to extract the exact RAM init parameters, I decided to build around it.

This meant keeping TP-Link’s proprietary U-Boot. The issue is TP-Link utilizes a “SafeLoader” mechanism that hardcodes partition boundaries and expects the OS kernel to be located at a very specific address. My workaround was to engineer a “split-flash” map: I would keep the first 8MB logically identical to satisfy U-Boot, but map the extra 8MB on the new chip as a secondary rootfs filesystem partition.

First, I pulled down the OpenWrt source and updated the feeds:

git clone https://git.openwrt.org/openwrt/openwrt.git
cd openwrt
./scripts/feeds update -a && ./scripts/feeds install -a

Even though official support was dropped, the device definition files for older hardware still exist in the repository. I modified the Device Tree (DTS) file for the 8MB chip to recognize my 16MB layout. I added the following block to the end of the partitions definition to map target/linux/ath79/dts/qca9563_tplink_tl-wpa8630p-v2.0-eu.dts the upper 8MB of the Winbond chip:

partition@800000 {
	label = "rootfs";
	reg = <0x800000 0x800000>;
};

Then, it was time to compile the OS:

# Select the target (ath79), subtarget (tiny), and WPA8630P profile
make menuconfig 

# Compile the custom firmware using all CPU cores
make -j$(nproc) V=s

The build process throws a size error at the very end, complaining that the firmware is too large for the target. This is expected. To bypass the build script’s limitations, I manually stitched the firmware parts together using dd to create the final 16MB payload:

# 1. Create a blank 16MB file padded with standard flash erasure bytes (0xFF)
tr '\000' '\377' < /dev/zero | dd of=custom_16mb.bin bs=1024 count=16384 iflag=fullblock

# 2. Drop the original 8MB factory dump (containing the bootloader & calibration) into the beginning
dd if=original_8mb.bin of=custom_16mb.bin bs=1024 count=8192 conv=notrunc

# 3. Inject the newly compiled OpenWrt kernel at the 256KB offset (where SafeLoader expects it)
dd if=build_dir/target-mips_24kc_musl/linux-ath79_tiny/tplink_tl-wpa8630p-v2.0-eu-kernel.bin of=custom_16mb.bin bs=1024 seek=256 conv=notrunc

# 4. Inject the compiled SquashFS root filesystem into the upper 8MB boundary
dd if=build_dir/target-mips_24kc_musl/linux-ath79_tiny/root.squashfs of=custom_16mb.bin bs=1024 seek=8192 conv=notrunc

# 5. Flash the fully assembled 16MB binary directly to the chip
flashrom -p ch341a_spi -w custom_16mb.bin

After flashing the chip and reassembling the unit, it booted correctly.

2.3.Resurrecting the Radios

Wi-Fi radios require calibration data to comply with FCC/CE transmission limits and to maintain signal stability. This data is stored in the ART (Atheros Radio Test) partition, officially labeled radio on this board.

While my custom 16MB image retained this partition, the 5GHz PCIe radio remained dead. The calibration data was physically there, but the OpenWrt ath10k driver was failing to read it correctly from the MTD block as a quick look in dmesg revealed.

To fix this, I had to manually extract the specific calibration block from the raw MTD partition and hot-patch it directly into the Linux firmware directory so the driver could load it during initialization:

# Extract the calibration block for the QCA9888 radio and put in the location the driver expects
dd if=/dev/mtd6 of=/lib/firmware/ath10k/QCA9888/hw2.0/board.bin bs=1 skip=20480 count=12064

# Reload the kernel modules to initialize the radio with the newly applied calibration
rmmod ath10k_pci
rmmod ath10k_core
modprobe ath10k_pci

3.Investigating Security of PLC

TL;DR: PLC security is sufficient for a home environment if you change the “password” aka the Network Membership Key (NMK).

Out of the box, most AV2 adapters ship with the same public key (HomePlugAV), making your network trivial to join or sniff from a neighboring apartment. I generated a custom, cryptographically secure AES-128 NMK and injected it directly into the chip’s NVRAM via the OpenWrt CLI, following the OpenWrt Wiki.

The primary vulnerability in PLC setups isn’t the protocol, but the software:

  1. Vendor Firmware: TP-Link has a poor track record for patching security flaws. Replacing the stock OS with OpenWrt effectively eradicates this attack surface.

  2. The QCA7550 SoC: The Powerline communication is handled by an independent Qualcomm SoC. My adapters shipped with an ancient firmware version: v2.2.3 (year 2016), which is vulnerable to CVE-2022-33265 (CVSS 9.8 — Critical).

While TP-Link has neglected these updates, the firmware is fortunately universal across QCA7550 devices and released by Qualcomm. I sourced the v2.13.0053 (year 2023) firmware (addressing the critical CVSS flaw) and manually backported it.

3.2.Implementation: Backporting the Baseband

To update the firmware without losing configuration, you must flash the new firmware executable (.nvm) with device’s unique Parameter Information Block (.pib). It is done using the plctool utility from the OpenWrt host.

# 1. Install management tools
apk update && apk add open-plc-utils-plctool open-plc-utils-plcrate open-plc-utils-chknvm

# 2. Baseline check (identifying the firmware)
plctool -a -i br-lan local

# 3. Backup unique hardware calibration (PIB)
plctool -i br-lan -p WPA8630P_factory.pib local

# 4. Flash the modern, patched firmware
# Note: Use the 'stripped' NVM for WPA8630P to fit limited flash boundaries
plctool -i br-lan -P /root/WPA8630P_factory.pib -N /root/QCA75XX-2.13.0053_stripped.nvm local

# 5. Update the other end (PA8010P) remotely
# CAUTION: Do not mix up PIBs between units. This will probably cause a brick.
#
# Make sure device is paired.
# Then repeat the procedure using remote device's MAC instead of 'local'. 

After flashing, a hard power cycle is required to initialize the new DSP logic.

4.Architecting the Network: VLAN Trunking in Silicon

With the PLC link secured and the firmware patched, the final step was to configure 802.1Q VLAN Trunking on the hardware switch. This allows the basement lab to carry isolated traffic over the same physical copper wire.

4.1.Internal Switch Mapping

The TL-WPA8630P uses an internal QCA8337N switch. To configure it correctly, you have to map the internal ports to the physical layout:

  • Port 0 (CPU): The internal interface to OpenWrt host.
  • Port 5 (PLC): The QCA7550 PLC interface.
  • Port 4 (LAN0): Configured as a Trunk for Proxmox.
  • Port 3 (LAN1): Access Port for IPMI (VLAN 100).
  • Port 2 (LAN2): Access Port for NAS (VLAN 10).

4.2.Configuration Script

I used the following uci commands to wipe the default switching state and define the new VLAN boundaries. This setup creates “Access Ports” at the hardware level by using PVIDs to force untagged traffic from specific devices into their respective VLANs.

# 1. Reset switch state
while uci -q delete network.@switch_vlan[0]; do :; done
while uci -q delete network.@switch_port[0]; do :; done

# 2. Define VLANs
# VLAN 10: Traffic (Trunk on LAN0 & PLC, Access on LAN2)
uci add network switch_vlan
uci set network.@switch_vlan[-1].device="switch0"
uci set network.@switch_vlan[-1].vlan="10"
uci set network.@switch_vlan[-1].ports="0t 2 4t 5t"

# VLAN 100: Management (Trunk on PLC, Access on LAN1)
uci add network switch_vlan
uci set network.@switch_vlan[-1].device="switch0"
uci set network.@switch_vlan[-1].vlan="100"
uci set network.@switch_vlan[-1].ports="0t 3 5t"

# 3. Port-Based VLAN IDs (PVID)
# LAN1 -> Management (VLAN 100)
uci add network switch_port
uci set network.@switch_port[-1].device='switch0'
uci set network.@switch_port[-1].port='3'
uci set network.@switch_port[-1].pvid='100'

# LAN2 -> Traffic (VLAN 10)
uci add network switch_port
uci set network.@switch_port[-1].device='switch0'
uci set network.@switch_port[-1].port='2'
uci set network.@switch_port[-1].pvid='10'

# 4. Host Management Interface & Firewall
uci set network.mgmt=interface
uci set network.mgmt.ifname='eth0.100'
uci set network.mgmt.proto='static'
uci set network.mgmt.ipaddr='192.168.100.6'
uci set network.mgmt.netmask='255.255.255.0'
uci set network.mgmt.gateway='192.168.100.1'
uci add_list network.mgmt.dns='192.168.100.1'

uci add firewall zone
uci set firewall.@zone[-1].name='mgmt'
uci set firewall.@zone[-1].network='mgmt'
uci set firewall.@zone[-1].input='ACCEPT'

uci commit network && uci commit firewall
/etc/init.d/network restart

5.Performance Tests and Conclusion

To quantify the impact of the v2.13.0053 backport, I conducted a series of 3-minutes iperf3 tests, comparing the factory firmware against the modern Qualcomm blob. Tests were performed in two scenarios: a “Synthetic Maximum” (both PLC endpoints connected to the same extension cord) and the “Real World” (basement to the second floor).

5.1.Scenario 1: Synthetic Maximum (Same Extension Cord)

This baseline eliminates house-wiring interference to test the raw throughput potential of the DSP logic.

FirmwareProtoUL [Mbps]DL [Mbps]
v2.2.3TCP411247
v2.13TCP432402
v2.2.3UDP457300
v2.13UDP497483

5.2.Scenario 2: Real World (Basement Deployment)

The adapters were moved to their target locations. Note that these tests were conducted on different days with varying electromagnetic noise profiles, v2.2.3 and v2.13 t2 - on weekday, v2.13 t1 - on weekend.

FirmwareProtoUL [Mbps]DL [Mbps]
v2.2.3TCP153185
v2.13 t1TCP146148
v2.13 t2TCP162207
v2.2.3UDP175213
v2.13 t1UDP168194
v2.13 t2UDP182244

Note: Throughput on firmware v2.13 was noticeably more stable during the 180-second test window.