Files
Embedded-Hacking/WEEK05/WEEK05-01.md
2026-03-19 15:01:07 -04:00

8.9 KiB
Raw Permalink Blame History

Embedded Systems Reverse Engineering

Repository

Week 5

Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis

Non-Credit Practice Exercise 1: Analyze the Float Binary in Ghidra

Objective

Import and analyze the 0x000e_floating-point-data-type.bin binary in Ghidra to understand how the compiler handles floating-point variables, discover float-to-double promotion, and decode the IEEE 754 double-precision encoding of 42.5 from two 32-bit registers.

Prerequisites

  • Ghidra installed and configured
  • 0x000e_floating-point-data-type.bin binary available in your build directory
  • Understanding of IEEE 754 encoding from Week 5 Part 2
  • Basic Ghidra navigation skills from Weeks 3 and 4

Task Description

You will import the float binary into Ghidra, configure it for ARM Cortex-M33, resolve function names, discover that the compiler promotes float to double when passing to printf, and manually decode the 64-bit double 0x4045400000000000 field by field to confirm it represents 42.5.

Step-by-Step Instructions

Step 1: Start Ghidra and Create New Project
ghidraRun
  1. Click File ? New Project
  2. Select Non-Shared Project
  3. Click Next
  4. Enter Project Name: week05-ex01-floating-point
  5. Choose a project directory
  6. Click Finish
Step 2: Import the Binary
  1. Navigate to your file explorer
  2. Find Embedded-Hacking\0x000e_floating-point-data-type\build\0x000e_floating-point-data-type.bin
  3. Drag and drop the .bin file into Ghidra's project window
Step 3: Configure Import Settings

When the import dialog appears:

  1. Click the three dots () next to Language
  2. Search for: Cortex
  3. Select: ARM Cortex 32 little endian default
  4. Click OK

Now click Options… button:

  1. Change Block Name to: .text
  2. Change Base Address to: 10000000 (XIP flash base)
  3. Click OK

Then click OK on the main import dialog.

Step 4: Analyze the Binary
  1. Double-click the imported file in the project window
  2. When prompted "Analyze now?" click Yes
  3. Leave all default analysis options selected
  4. Click Analyze
  5. Wait for analysis to complete (watch bottom-right progress bar)
Step 5: Locate and Rename the Main Function

Look at the Symbol Tree panel on the left. Expand Functions.

From previous weeks, we know the boot sequence leads to main():

  1. Click on FUN_10000234
  2. Right-click ? Edit Function Signature
  3. Change to: int main(void)
  4. Click OK
Step 6: Resolve stdio_init_all and printf

Rename stdio_init_all:

  1. Click on FUN_10002f5c in the decompile window
  2. Right-click ? Edit Function Signature
  3. Change to: bool stdio_init_all(void)
  4. Click OK

Rename printf:

  1. Click on FUN_100030ec
  2. Right-click ? Edit Function Signature
  3. Change to: int __wrap_printf(char *format,...)
  4. Check the Varargs checkbox
  5. Click OK
Step 7: Observe the Decompiled Code

After resolving, the decompiled main should look like:

int main(void)
{
  undefined4 uVar1;
  undefined4 extraout_r1;
  undefined4 uVar2;
  undefined4 extraout_r1_00;
  
  stdio_init_all();
  uVar1 = DAT_1000024c;
  uVar2 = extraout_r1;
  do {
    __wrap_printf(DAT_10000250,uVar2,0,uVar1);
    uVar2 = extraout_r1_00;
  } while( true );
}

Critical observation: Where is float fav_num = 42.5? The compiler optimized it into constants!

Step 8: Identify the Register Pair

Look at the Listing window (assembly view) for main:

1000023a    00 24         movs    r4, #0x0
1000023c    03 4d         ldr     r5, [DAT_1000024c]     = 40454000h

Two values are being passed to printf:

  • r2 = 0x00000000 (low 32 bits)
  • r3 = 0x40454000 (high 32 bits)

Together they form a 64-bit double: 0x4045400000000000

Why a double? The C standard requires that float arguments to variadic functions like printf are promoted to double. So even though our variable is declared as float fav_num = 42.5, printf always receives a 64-bit double.

Step 9: Write Out the Binary Layout

Convert both registers to binary:

r3 (high 32 bits): 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000
r2 (low  32 bits): 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000

Map the 64-bit IEEE 754 fields:

Bit 63 (sign):           0
Bits 6252 (exponent):   10000000100
Bits 510 (mantissa):    0101010000000000...0000
Step 10: Decode the Sign Bit

Bit 63 of the double = bit 31 of r3:

r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000
                   ^
                   bit 31 = 0  ?  Positive number

IEEE 754 sign rule: 0 = Positive, 1 = Negative.

Step 11: Decode the Exponent

Extract bits 3020 from r3:

0x40454000:  0  10000000100  01010100000000000000
             ^  ^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^
           sign  exponent      mantissa (top 20)

Exponent bits: 10000000100 = 2¹° + 2² = 1024 + 4 = 1028

Subtract the double-precision bias (1023):

\text{real exponent} = 1028 - 1023 = \mathbf{5}
Step 12: Decode the Mantissa

High 20 bits of mantissa (from r3 bits 190):

0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Low 32 bits of mantissa (from r2):

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

With the implied leading 1:

1.010101 00000...
Step 13: Reconstruct the Final Value
1.010101_2 \times 2^5 = 101010.1_2

Convert to decimal:

Bit Power Value
1 25 32
0 24 0
1 8
0 0
1 2
0 0
1 2?¹ 0.5
32 + 8 + 2 + 0.5 = \mathbf{42.5} ?
Step 14: Find the Format String

In the Listing view, click on the data reference to locate:

s_fav_num:_%f_100034a8    ds    "fav_num: %f\r\n"

Note that the format specifier is %f, confirming this is a floating-point print call.

Step 15: Document Your Findings

Create a table of your observations:

Item Value Notes
Main function address 0x10000234 Entry point of program
Float value (original) 42.5 Declared as float
Double hex encoding 0x4045400000000000 Promoted to double for printf
r3 (high word) 0x40454000 Contains sign + exponent + mantissa top bits
r2 (low word) 0x00000000 All zeros — clean fractional part
Exponent (stored) 1028 Biased value
Exponent (real) 5 After subtracting bias 1023
Format string "fav_num: %f\r\n" Located at 0x100034a8
Float-to-double promotion Yes C standard for variadic functions

Expected Output

After completing this exercise, you should be able to:

  • Import and configure ARM binaries in Ghidra for float analysis
  • Explain why printf receives a double even when the variable is a float
  • Identify the register pair r2:r3 that holds a 64-bit double
  • Manually decode an IEEE 754 double from hex to decimal
  • Locate format strings in the binary

Questions for Reflection

Question 1: Why does the compiler promote a float to a double when passing it to printf?
Question 2: The low word (r2) is 0x00000000. What does this tell you about the fractional part of 42.5?
Question 3: What is the purpose of the exponent bias (1023) in IEEE 754 double-precision?
Question 4: If the sign bit (bit 63) were 1 instead of 0, what value would the double represent?

Tips and Hints

  • The Listing window shows raw assembly; the Decompile window shows reconstructed C
  • Double-click on a DAT_ reference to jump to the data constant
  • Use Python to verify: import struct; struct.pack('>d', 42.5).hex() gives 4045400000000000
  • Remember: r3 = high 32 bits (sign + exponent + top mantissa), r2 = low 32 bits (bottom mantissa)
  • The bias for doubles is always 1023; for floats it's 127

Next Steps

  • Proceed to Exercise 2 to patch this float value in Ghidra
  • Try computing the IEEE 754 encoding of other values like 3.14 or 100.0 by hand
  • Compare the 32-bit float encoding 0x422A0000 with the 64-bit double encoding 0x4045400000000000 — both represent 42.5

Additional Challenge

Find the data constant DAT_1000024c in the Listing view. What raw bytes are stored there? Remember that ARM is little-endian — the bytes in memory are in reverse order. Write out the byte order as it appears in memory vs. as a 32-bit value.