[TXT]     [HOME]     [TOOLS]     [GAMES]     [RSS]        [ABOUT ME]    [GITHUB]

.-----------------------------------------------------------------------------.
|               radare2: Working with not-so-valid x86-64 ELFs                |
'-----------------------------------------------------------------------------'
updated: 2025-07-29


Learning objectives:
- How to work with not-so-valid ELF binaries in radare2.
- How to fix the loading address in radare2.
- How to fix the entry point in radare2.
- How to work with raw binaries in radare2.

 __ ____   /|  /|         \'~'/             __---.   _     .--'--.      /\*/\
(_)'\ |   / | / |         (o o)    -----.  |     |  | |    '-.   |     /(o o)\
    | |   \ | \ |          \./          |  '--.  |__| |    __|   '-->    (_)
   _|_|_   \|  \|         ELF 32        |_____|       |___|             ELF 64

===[ Intro ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In this article, we'll look at how to work with ELF files that don't follow the standard ELF structure but are still valid Linux executables, using radare2. Tested on the latest release of radare2:
radare2 5.9.8 33900 @ linux-x86-64
commit: 4eb49d5ad8c99eaecc8850a2f10bad407067c898
Date:   2024-11-19 12:38:30 +0100

===[ Wrong Loading Address ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

What's the problem with ELF loading in radare2? Let's look at an example from [ref1] ([ref2]). The 'elf64-80_bytes' file has overlapping ELF and Program headers. When this file is loaded into radare2, it gets confused and incorrectly identifies it as an x86-32 binary:
$ r2 ./elf64-80_bytes

[0x00000001]> i ~^arch,bits
arch     x86
bits     32
If that's the case, where does radare2 map the binary, and what is the entry point? Since it thinks the binary is 32-bit, it treats 64-bit values as 32-bit => effectively shifting and truncating the loading address 'p_vaddr' from '0x0700000000' to '0x00010000', and the entry point from '0x0700000001' to '0x1':
[0x00010000]> iS
[Sections]
nth paddr       size vaddr       vsize perm type name
―――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00000000  0x50 0x00010000   0x50 -rwx ---- uphdr

[0x00000001]> f ~entry0
0x00000001 1 entry0
Unfortunately, we can't just run radare2 with an explicit architecture, like 'r2 -a x86 -b 64', and be done with it. radare2 still reads values from the ELF header. These options affect disassembly (which we also need), but mismatched headers can still break symbol resolution, relocations, imports, and so on.

===[ Manual File Remapping ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Fortunately, radare2 is flexible enough to let us manually remap the file to the correct address. But first things first: we should prevent radare2 from performing any analysis on the file. When we run 'r2' with the '-n' flag radare2 won't try to parse the ELF header, guess the entry point, and so on.
$ r2 -n ./elf64-80_bytes

[0x00000000]> i
fd       3
file     elf64-80_bytes
size     0x50
humansz  80
mode     r-x
iorw     false
block    0x400
In this mode, it maps the file to address 0, so we need to change that. From [ref1], we know the binary should be mapped at '0x0700000000', so let's map it there:
[0x00000000]> om
* 1 fd: 3 +0x00000000 0x00000000 - 0x0000004f r-x
        ^             ^-- This is where the binary is mapped.
        '--- We need this file descriptor to remap the file.

[0x00000000]> om-1
                 ^--- Remove the mapping, but keep the fd open.

[0x00000000]> om 3 0x0000000700000000 $s
                 ^ ^                  ^--- and map the entire file
                 | '-- to this address
              map fd 3

[0x00000000]> om
- 1 fd: 3 +0x00000000 0x700000000 - 0x70000004f r-x
We also know that the entry point is at '0x0700000000 + 1', so let's fix that as well:
[0x00000000]> f entry0=0x0000000700000000 + 1
We're almost done (if we had run 'r2 -n -a x86 -b 64', we'd be done, but let's say we didn't). However, there might still be a problem with the assembly. (Without '-n', there's definitely a problem and even with '-n', issues can arise if the architecture we're running on doesn't match the binary's architecture.)
[0x00000000]> s entry0

[0x700000001]> pd 3
            ;-- entry0:
            0x700000001      45             inc ebp
            0x700000002      4c             dec esp
            0x700000003      46             inc esi
This is not the code we expect: it shows 32-bit x86 instructions ('4X' are the "REX" prefixes in x86-64). We can either run radare2 with architecture specific arguments (as shown above), or manually set the 'asm.arch' and 'asm.bits' options:
[0x700000001]> e asm.arch = x86

[0x700000001]> e asm.bits = 64

[0x700000001]> pd 3
            ;-- entry0:
            0x700000001      454c46b20d     mov dl, 0xd                ; 13
            0x700000006      5e             pop rsi
            0x700000007      5e             pop rsi

===[ Beware of Analysis ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Once we repair the entry point, we can even happily run radare2's auto analysis:
[0x00000001]> aa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze all functions arguments/locals (afva@@@F)
'aa' is effectively an alias for 'af@@ sym.* ; af@entry0 ; afva', and it's not that handy when there are no symbols ('af@@ sym.*') or real functions with arguments ('afva'). So in the end, the only useful command is 'af @ entry0' and that we can just run ourselves:
[0x700000001]> af @ entry0
Now we can use the "print disassemble function" command, 'pdf', and see only the useful code and no more garbage bytes from the ELF headers. Here's a slightly modified 'elf64-80_bytes' that jumps between possible code sections [ref3]:
[0x700000001]> pdf
            ;-- rip:
┌ 33: entry0 ();
│           0x700000001      454c46b20d     mov dl, 0xd
│           0x700000006      5e             pop rsi
│           0x700000007      5e             pop rsi
│           0x700000008      b001           mov al, 1
│           0x70000000a      89c7           mov edi, eax
│           0x70000000c      0f05           syscall
│       ┌─< 0x70000000e      eb04           jmp 0x700000014
..
│       │   ; CODE XREF from entry0 @ 0x70000000e(x)
│       └─> 0x700000014      90             nop
│           0x700000015      90             nop
│           0x700000016      eb18           jmp 0x700000030
..
│           ; CODE XREF from entry0 @ 0x700000016(x)
│           0x700000030      90             nop
│           0x700000031      90             nop
│           0x700000032      90             nop
│           0x700000033      90             nop
│           0x700000034      eb12           jmp 0x700000048
..
│           ; CODE XREF from entry0 @ 0x700000034(x)
│           0x700000048      31c0           xor eax, eax
│           0x70000004a      89c7           mov edi, eax
│           0x70000004c      b03c           mov al, 0x3c
└           0x70000004e      0f05           syscall
This is pretty good, but when we're jumping out of order in the assembly above, the r2 graph view 'VV' becomes quite useful. Here's another modified version of 'elf64-80_bytes' where we jump to "random" positions [ref4]:
[0x700000001]> VV
           ┌────────────────────┐
           │ [0x700000001]      │
           │   ;-- rip:         │
           │ 33: entry0 ();     │
           │ ; 13               │
           │ mov dl, 0xd        │
           │ pop rsi            │
           │ pop rsi            │
           │ mov al, 1          │
           │ mov edi, eax       │
           │ syscall            │
           │ jmp 0x700000030    │
           └────────────────────┘
               v
               │
    ┌──────────┘
    │
┌──────────────────────────────────────────┐
│  0x700000030 [oc]                        │
│ ; CODE XREF from entry0 @ 0x70000000e(x) │
│ nop                                      │
│ nop                                      │
│ nop                                      │
│ nop                                      │
│ jmp 0x700000014                          │
└──────────────────────────────────────────┘
    v
    │
    │
┌──────────────────────────────────────────┐
│  0x700000014 [ob]                        │
│ ; CODE XREF from entry0 @ 0x700000034(x) │
│ nop                                      │
│ nop                                      │
│ jmp 0x700000048                          │
└──────────────────────────────────────────┘
    v
    │
    │
┌──────────────────────────────────────────┐
│  0x700000048 [od]                        │
│ ; CODE XREF from entry0 @ 0x700000016(x) │
│ xor eax, eax                             │
│ mov edi, eax                             │
│ ; '<'                                    │
│ ; 60                                     │
│ mov al, 0x3c                             │
│ syscall                                  │
└──────────────────────────────────────────┘
Static analysis of the not-so-valid ELF binaries is much more straightforward and comfy now.

===[ r2 Script ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

For completeness' sake, here's an r2 script for quicker analysis: ----------------------------[ small_elf_remap.rr2 ]----------------------------
om-1
om 3 0x0000000700000000 $s
e asm.arch = x86
e asm.bits = 64
f entry0=0x0000000700000000 + 1
s entry0
af
pdf
------------------------------------------------------------------------------- Here's how to run it:
$ r2 -n -i small_elf.rr2 ./elf64-80_bytes

┌ 23: entry0 ();
│           0x700000001      454c46b20d     mov dl, 0xd                ; 13
│           0x700000006      5e             pop rsi
│           0x700000007      5e             pop rsi
│           0x700000008      b001           mov al, 1
│           0x70000000a      89c7           mov edi, eax
│           0x70000000c      0f05           syscall
│       ┌─< 0x70000000e      eb38           jmp 0x700000048
...

===[ OUTRO ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

radare2 is a great open source command line tool. Even though it has many quirks, I love it. It's excellent when a quick look at a binary is needed. Unfortunately, working with radare2 is not for the faint of heart. Have fun and hack on!

===[ References ]~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[ref1] https://research.h4x.cz/html/2025/2025-08-01--touching_small_elfs.html [ref2] https://research.h4x.cz/data/2025/elf64-80_bytes.nasm [ref3] https://research.h4x.cz/data/2025/elf64-80_bytes-jumps.nasm [ref4] https://research.h4x.cz/data/2025/elf64-80_bytes-out_of_order_jumps.nasm