hey, glad you found this place. since you're here, i'll try to lay everything out clearly — the goal is that you can actually use this information. recently i got interested in pushing code into the firmware and looking at how it behaves down there. today we're not going to cover the full persistence mechanism, but we will walk through the injection methods i actually used and what happened with each of them.
hardware setup
first things first — you need real hardware. yes, you can use a VM with OVMF (and honestly it's a fine option for initial testing), but for seeing how payloads behave on actual silicon, i prefer physical boards. in this writeup i ended up using two motherboards — i'll explain why later.
the primary board was an ASUS P8H61M LX3 R2.0. not a bad choice — almost everything needed was there. the rest of the rig:
| cpu | Intel Pentium G2020 |
| dram | 2 GB DDR3-1333 |
| psu | 450W (garbage, but it works) |
| hdd | 500 GB Toshiba |
| bios | 2012 vintage AMI firmware |
method 1 — shellcode injection
this one has no real tricks to it. the approach: find a string or byte sequence inside a firmware module that the module doesn't strictly need for correct operation, and overwrite those bytes with your payload. before the injection, write your asm, assemble it to raw bytes, and then you're ready to patch.
to do this, load your firmware image into
UEFITool — and make sure it's a dump you pulled
from the board itself, not a file downloaded from the vendor site.
flashing a modified image built from a downloaded ROM(Read-only memory) has a real chance
of producing a brick. dump it yourself.
i targeted the SmiFlash module. the name is self-explanatory —
it holds SMM privileges even when it shows up as a DXE driver in the tree.
to verify the injection was actually running, i wrote a shellcode that
calls the PC speaker in a tight loop.
To inject code into the body of an existing function, we need to open the
file itself with a binary editor. But to edit, we need to know where to
write our payload, so I use ida pro for analysis. In this program, we
need to find the function we defined in the file and check if there's
free space for code injection in the same section (for example, .rdata).
After that, we insert our code into the empty bytes and note the address
where it's located.
Next, we need to find the location in the original function from which control will be transferred to our code. Typically, we select the beginning of the function or a section containing suitable instructions. It's important to preserve the original bytes that we'll overwrite so that program execution can continue correctly. Then we replace the selected instructions with a jump (jmp) to our code. This way, when executed, the program will first access our insertion point. The required logic is executed inside it, after which we return back - either to the next instruction after the substitution, or by restoring the saved bytes and switching back to the original execution flow.
after patching the bytes, replace the module back into UEFITool, reflash, and boot. the result: an infinite beep loop. what's interesting is that the beeping was extremely fast — no audible gaps, just a continuous high-pitched tone. that's because SMM handles the code in sync with the processor clock, not in human-scale time. you get ultrasonic beeping instead of the discrete clicks you'd expect.
method 2 — building a proper payload
by now this feels like "the basics," but the first few times i did this it was genuinely disorienting. the approach here: replace the body of original firmware functions with your own code.
to do that you first need an actual payload. i used EDK II —
specifically the UDK2014 branch, for full compatibility with
a 2012-era board. newer branches might work too, but i didn't test them.
(the board has a 2012 BIOS, so... come on.)
after writing and compiling, you have a .efi file — the driver body.
i kept targeting SmiFlash since it doesn't affect system startup.
but first attempt: black screen. no POST, nothing. as if there was no BIOS at all.
tried a different module — same result.
then i ran an experiment: took a module called AsusFptDxe and replaced
its body with the content of SmiFlash. board posted fine.
so: swapping a native module's body under a different module's identity bypasses
whatever integrity checks exist and the board boots. useful to know.
spent a week trying to get my code running as both DXE and SMM.
nothing worked — just a black screen every time. then i remembered that
people had modded similar boards to add NVMe support where it wasn't
native. looked at that method, and it used a tool called MMTool.
it has a bunch of features but i only needed replace and insert.
the key realization: firmware modules aren't bare .efi files — they
live inside FFS (Firmware File System) containers. to get your
payload to load, it needs to look right from the firmware's perspective.
that means building a proper FFS container yourself.
i assembled the container body, used GenSec to create a correct
DEPEX (Dependency Expression is a section that describes the
dependencies of a UEFI driver and determines under what conditions it should
be loaded and executed; usually contains logical conditions related to protocol
availability), copied it from an already working driver, brought everything to
the desired form, and then pasted it through MMTool as a new driver.
three beeps, logo on screen. code is running.
debugging — the COM port problem
getting the payload to run is one thing. actually seeing what it's doing is another. i looked at debug options and settled on the COM port / serial approach.
problem: the P8H61M LX3 R2.0 doesn't have the COM header populated
on the PCB. the SuperIO controller is there, there's even a hidden BIOS option
(found via AMIBCE), but the physical pins aren't soldered.
so i went with a PCI COM card — two-port, the cheapest one available —
plus a serial cable and a USB-to-COM adapter.
in Linux (Arch, manual stty setup) the signal came through fine.
in UEFI: nothing useful. the card has a non-standard controller that needs
specific initialization before it will do anything.
spent two weeks trying to make it work in UEFI shell. wrote a lot of test code.
the core issue: LSR (Line Status Register)
during UART init kept returning 0xFF or 0x4D —
corrupted data or device busy. manual register writes via MMIO, manual PCI BAR
configuration — none of it helped.
two weeks lost. and there was a second problem lurking: even if i'd gotten the serial link working, SMM code has its own initialization requirements that might have silently swallowed the log output during early boot anyway. the timing just doesn't align with when the serial driver is available.
decision: swap the board. got a P8H61M-Pro, which has a native
COM header and more interfaces in general. pulled a fresh dump, rewrote the
DEPEX for the new board, rebuilt the FFS container, injected, booted.
closing thoughts
that was the "small" expedition into firmware injection and hardware debugging. what looked like a straightforward task turned into a multi-week hardware debugging session — two boards, a pile of failed COM configurations, and a healthy disrespect for vendor documentation. the main takeaways:
— always dump the firmware yourself, never flash from a vendor download
— bare .efi won't load without proper FFS wrapping
— cheap PCI serial cards are essentially broken in UEFI without custom drivers
— native COM headers are worth the extra few euros on the board
hope this was useful. more to come when i have something worth writing down.
2026-04-05 // H61 platform series // log_000