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