Resource / Blogs /

Bare-metal Firmware Emulation – An Introduction To Unicorn Engine

A hands-on introduction to Unicorn Engine, the QEMU-based CPU emulator, showing how to emulate ARM instructions in Python as the first step toward bare-metal firmware emulation.
By
Hariprasad
May 18, 2022
8 min
Get Tested
Device, firmware and APIs scoped as one system.
Talk to an Expert
White arrow pointing diagonally upward to the right on a black square background.White arrow pointing diagonally upward to the right on a black square background.

Key Takeaways

  • Firmware emulation gives better debugging than running code on real hardware.
  • Emulation lets you feed unpredictable input, which makes fuzzing practical.
  • Unicorn is a lightweight, multi-architecture CPU emulator built on QEMU.
  • Keystone Engine converts ARM assembly into machine code ready to emulate.
  • Set up emulation with mem_map, mem_write and reg_write before execution.
  • emu_start runs the code between defined start and end addresses.
  • Unicorn's memory, register and hook APIs make it a base for custom emulators.

Hello everyone, in this blog series we will look into firmware emulation of bare-metal devices with the help of a CPU emulator called Unicorn.

You may wonder, why go through the hassle of emulating firmware?

Well, emulation can assist in the security analysis of a device as it can provide better debugging capabilities as compared to running the device on actual hardware. It can also help in providing the firmware with unpredictable input that in turn is helpful for testing methods like fuzzing. Or you might not have a spare microcontroller lying around considering the chip shortage crisis

This blog series will be split into 3 parts:

  • Part 1 : Introduction to unicorn engine
  • Part 2 : STM32 microcontroller internals
  • Part 3 : Emulating a basic firmware

So let’s get started…

What is Unicorn Engine?

Before we look into how to emulate a firmware, let’s first familiarize ourselves with the emulation framework.

Unicorn is a lightweight multi-platform, multi-architecture CPU emulator framework based on QEMU. It is an instruction emulator where we feed the code to the engine and it will execute it. The setup for emulation and rules of how the binary should be emulated should be defined by the user.

Let’s look at how we can perform a basic instruction emulation.

I will be using ARM architecture as it is one of the most commonly used architectures in embedded systems. We will try to emulate instructions that change the value of registers.

Let’s try to move a value into the r0 register using the mov instruction and try to use arithmetic instructions like add on the register r1.

So, first, we need to generate machine code for the corresponding assembly instructions. We can use keystone engine to achieve this. Keystone engine is a multi-architecture assembler framework. We can use that to convert the ARM assembly code to machine code.

For that, we need to initialize the keystone engine object with ARM architecture.

from keystone import *
ks = Ks( KS_ARCH_ARM , KS_MODE_ARM)

The asm() returns a tuple with the machine code as the first element in a list form and the number of instructions assembled. Since the machine code is in list form we will use bytes() to convert to string of machine code.

bytes(ks.asm("mov r0,#0x15nadd r1, r2, r3")[0])
b'x15x00xa0xe3x03x10x82xe0'

Now that we have the code to emulate, we will start defining the architecture of the Unicorn.

uc = Uc( UC_ARCH_ARM , UC_MODE_ARM )

Next, we have to allocate memory for emulation. For this, we need to specify the starting address and the size of the allocated memory using the mem_map function. For this example, we will use 0x10000 and an allocated size of 2MB

ADDRESS = 0x10000
uc.mem_map( ADDRESS , 2 * 1024 * 1024 )

Now let’s copy our code to the allocated memory. For that, we will use the mem_write function.

ARM_CODE = b'x15x00xa0xe3x03x10x82xe0'
uc.mem_write( 0x10000 , ARM_CODE )  

Now we can initialize the registers with some values using the reg_write function. You can specify the corresponding register constants UC_<ARCH NAME>_REG_<REG NAME>. You

uc.reg_write( UC_ARM_REG_R0 , 0x01 )
uc.reg_write( UC_ARM_REG_R2 , 0x23 )
uc.reg_write( UC_ARM_REG_R3 , 0x45 )

Now that we have set up everything for the emulation we can start the emulation with the emu_start function. We will specify the addresses where the emulation starts and ends

uc.emu_start( ADDRESS , ADDRESS + len(ARM_CODE) )

Let’s put all of this together in a script and see what is the result

from unicorn import *
from unicorn.arm_const import *

ARM_CODE = b'x15x00xa0xe3x03x10x82xe0'
ADDRESS = 0x10000

print('[*] emulating code')
try:

 uc = Uc( UC_ARCH_ARM , UC_MODE_ARM ) 
    uc.mem_map( ADDRESS , 2 * 1024 * 1024 )  
    uc.mem_write( ADDRESS , ARM_CODE ) 

    uc.reg_write( UC_ARM_REG_R0 , 0x01 ) 
    uc.reg_write( UC_ARM_REG_R2 , 0x23 ) 
    uc.reg_write( UC_ARM_REG_R3 , 0x45 ) 

    uc.emu_start( ADDRESS , ADDRESS + len(ARM_CODE) ) 
    print('[*] emulation completen[*] printing register values') 

    r0 = uc.reg_read( UC_ARM_REG_R0 ) 
    r1 = uc.reg_read( UC_ARM_REG_R1 ) 
    print( '>>> r0 = 0x%xn>>> r1 = 0x%x' %( r0 , r1 ) ) 

except UcError as e: 
    print('ERROR: %s' % e) 
The output:

$ python test.py  
[*] emulating code 
[*] emulation complete 
[*] printing register values 
>>> r0 = 0x15 
>>> r1 = 0x68

We have now emulated basic ARM instructions and the output is as expected.

Conclusion

That’s all folks! This blog was aimed at giving a basic introduction to instruction emulation with the Unicorn engine. Unicorn engine is a framework that we can use and build our own custom emulator on top of it. It can emulate a chunk of binary data if it is able to define the initial setup for the emulation. It also provides the functionality of read/write from memory and registers and the functionality of executing custom scripts during emulation is really helpful for binary analysis. You can explore more about emulating with the Unicorn engine here. In the next blog, we will next look into the internal workings of an STM microcontroller. I hope you found this blog interesting, see you in the next blog

References

https://www.unicorn-engine.org/

https://www.unicorn-engine.org/docs/tutorial.html

https://github.com/unicorn-engine/unicorn/blob/master/bindings/python/sample_arm.py

https://eternal.red/2018/unicorn-engine-tutorial/

‍

Get Tested
Device, firmware and APIs scoped as one system.
Talk to an Expert
White arrow pointing diagonally upward to the right on a black square background.White arrow pointing diagonally upward to the right on a black square background.
Author
Hariprasad
Ex-Bandit
Red arrow pointing diagonally upward to the right.Red arrow pointing diagonally upward to the right.

Keep Reading

For Security Leaders
Agentic AI Security: The Hidden Attack Surface Beyond Prompt Injection
August 25, 2026
10 min
For Security Leaders
Research & disclosures
Binwalk Path Traversal Vulnerability: Turning Firmware Analysis into Code Execution
August 26, 2026
8 min
Guides & tutorials
For Security Leaders
An Introduction to Smali
August 26, 2026
8 min
Dark scene with vertical thin orange lines resembling distant illuminated bars or streaks against a black background and a faint horizontal red glow near the bottom.