A short blog on how to install and run the latest version of OpenWRT using QEMU, on a machine with Apple M1. This is similar to my previous blog post on How to build a Debian MIPS image on QEMU.

This guide uses the OpenWRT ARMv8 edition, which runs nicely on a Apple M1 chip. It also covers how to install the LuCI web management interface.

Download and Install

Select and download the necessary files from the link below. I will be using OpenWRT version 23.05.3 based on ARMv8. These can be found on the official website here.

You only need the following files:

  • generic-initramfs-kernel.bin (for recovery mode)
  • openwrt-armsr-armv8-generic-squashfs-combined.img
  • u-boot.bin (found here)

Also make sure the qemu software is actually installed. On macOS systems, you could use brew install qemu, this will install various systems (not necessary), for example my machines supports:

qemu-system-aarch64       qemu-system-hppa          qemu-system-microblazeel  qemu-system-nios2         qemu-system-riscv64       qemu-system-sparc
qemu-system-alpha         qemu-system-i386          qemu-system-mips          qemu-system-or1k          qemu-system-rx            qemu-system-sparc64
qemu-system-arm           qemu-system-loongarch64   qemu-system-mips64        qemu-system-ppc           qemu-system-s390x         qemu-system-tricore
qemu-system-avr           qemu-system-m68k          qemu-system-mips64el      qemu-system-ppc64         qemu-system-sh4           qemu-system-x86_64
qemu-system-cris          qemu-system-microblaze    qemu-system-mipsel        qemu-system-riscv32       qemu-system-sh4eb         qemu-system-xtensa

This guide uses qemu-system-aarch64, which supports ARMv8 CPU.


Run the following command inside the folder where both u-boot.bin and openwrt-armsr-armv8... are located. This will start the emulator and begin the initial boot process.

qemu-system-aarch64 -cpu cortex-a72 -m 1024 -M virt,highmem=off -nographic \
-bios u-boot.bin \
-drive file=openwrt-armsr-armv8-generic-squashfs-combined.img,format=raw,if=virtio \
-device virtio-net,netdev=net0 -netdev user,id=net0,net=,hostfwd=tcp:,hostfwd=tcp: \
-device virtio-net,netdev=net1 -netdev user,id=net1,net=

The options are explained below:

  • -cpu cortex-a72 use a specific ARM CPU type
  • -m 1024 use 1GB of RAM
  • -M virt,highmem=off type of machine
  • -nographic no window (use terminal)
  • -bios u-boot.bin use bootloader file
  • -drive file=... use the .img file as disk drive
  • -device virtio-net,netdev=net0 set up a network device
    • set network address range net=
    • enable host port forwarding with hostfwd=tcp::1122-:22

When you run this command, you should be presented with a shell in your terminal. If you do not see any output, you can try to append -serial stdio to get to standard output.

Here is an example of what you should see:

Press [ENTER] to use the shell.

Web interface

The default installation of OpenWRT does NOT come with a web interface. To get that installed, you can use the working command-line interface to update the package cache, and then install it. I will be using the LuCI package.

While logged-in shell, type:

opkg update
opkg install luci

You’ll notice a new process called uhttpd is now running on port 80.

root@OpenWrt:~# netstat -tupan
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0  *               LISTEN      2206/dnsmasq
tcp        0      0    *               LISTEN      1370/dropbear
tcp        0      0 *               LISTEN      2206/dnsmasq
tcp        0      0    *               LISTEN      6805/uhttpd

This is the web management service.

The QEMU options from earlier hostfwd=tcp::1122-:22,hostfwd=tcp::8080-:80, will create the required forwarding rules, which will allow us to access these services from the host system. I have used these ports 1122 (SSH) and 8080 (LuCI).

Note: To not expose all interfaces, you can set it to forward to localhost only. For example, using this command hostfwd=tcp:,hostfwd=tcp:, which forwards two ports.

Here’s an example of the OpenWRT LuCI web interface from the host system:

Notice the model linux,dummy-virt, indicating that it’s not running on real hardware.

Recovery mode

To enter OpenWRT recovery mode you would have to append -kernel along with the intitramfs file to the qemu command. In this mode, the file system will be mounted as read-only. Here is a complete example:

qemu-system-aarch64 -cpu cortex-a72 -m 1024 -M virt,highmem=off -nographic \
-bios u-boot.bin \
-kernel openwrt-armsr-armv8-generic-initramfs-kernel.bin \
-drive file=openwrt-armsr-armv8-generic-squashfs-combined.img,format=raw,if=virtio \
-device virtio-net,netdev=net0 -netdev user,id=net0,net=,hostfwd=tcp:,hostfwd=tcp: \
-device virtio-net,netdev=net1 -netdev user,id=net1,net=


A really nice feature of qemu is that you can attach a gdb debugger, and step through a process. This is extremely useful for identifying difficult bugs and exploit development.

These options are:

-S              freeze CPU at startup (use 'c' to start execution)
                the guest without waiting for gdb to connect; use -S too
-s              shorthand for -gdb tcp::1234

For example, a screenshot from another project where I had a x86 vm running on my M1 system, which I attached to using lldb with the option gdb-remote localhost:1234:

alt text