Previously in this series of articles, we used Ghidra to reverse-engineer and perform a binary patch on our case study. In this article we will craft an executable artifact from our case study in an unusual manner, one in which all of the information needed to delink it is already inside the Ghidra database.

This article is written using an unmodified Ghidra 10.2 instance for demonstrative purposes only. Do not actually perform this at home with real artifacts ; instead, refer to this article that shows how to automate all of this sanely.

Reinventing the linker, poorly

Recall the build workflow of our case study, shown all the way back in part 2:

In the last step of the build, the linker takes the object files and then:

  • lays out their sections in memory ;
  • computes the final addresses of all symbols ;
  • applies all the relocations within the sections according to the symbols’ addresses.

After this process, the relocations are discarded and the symbols are stripped, leaving only the relocated sections in the final executable. Or at least, that is usually what happens when using a traditional linker.

Ghidra, the poor man’s linker with training wheels

Ghidra works within a virtualized address space, similar to that of a conventional user-space process. When importing an ELF file, Ghidra must load it into this address space before it can be analyzed. In order to analyze relocatable and/or dynamically-linked executables, Ghidra can process relocations inside an ELF file and import shared libraries if instructed so, just like how a dynamic linker would.

That being said, Ghidra is not meant to be general-purpose linker ; our case study is made of three object files and Ghidra does not allow importing multiple ELF object files into one program. However, it can import and load one ELF object file, which we’ll (ab)use to our advantage here.

We can instruct the ELF toolchain to merge multiple object files into one:

$ mips-linux-gnu-ld -EL -r ascii-table.o ctype.o libstd.o -o ascii-table.relocatable.o

The output is an object file that contains all of the input object files, combined into one. Note that this object file has no undefined symbols and is therefore self-sufficient:

$ mips-linux-gnu-objdump --wide --file-headers ascii-table.relocatable.o

ascii-table.relocatable.o:     file format elf32-tradlittlemips
architecture: mips:isa32r2, flags 0x00000011:
start address 0x00000000

$ mips-linux-gnu-objdump --wide --section-headers ascii-table.relocatable.o

ascii-table.relocatable.o:     file format elf32-tradlittlemips

Idx Name            Size      VMA       LMA       File off  Algn  Flags
  0 .MIPS.abiflags  00000018  00000000  00000000  00000038  2**3  CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_SAME_SIZE
  1 .reginfo        00000018  00000000  00000000  00000050  2**2  CONTENTS, ALLOC, LOAD, READONLY, DATA, LINK_ONCE_SAME_SIZE
  2 .text           000003b0  00000000  00000000  00000070  2**4  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  3 .text.startup   000000a0  00000000  00000000  00000420  2**2  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  4 .rodata         00000170  00000000  00000000  000004c0  2**4  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
  5 .data           00000000  00000000  00000000  00000630  2**4  CONTENTS, ALLOC, LOAD, DATA
  6 .bss            00000000  00000000  00000000  00000630  2**4  ALLOC
  7 .comment        00000078  00000000  00000000  00000630  2**0  CONTENTS, READONLY
  8 .pdr            00000220  00000000  00000000  000006a8  2**2  CONTENTS, RELOC, READONLY
  9 .note.GNU-stack 00000000  00000000  00000000  000008c8  2**0  CONTENTS, READONLY
 10 .debug_aranges  00000068  00000000  00000000  000008c8  2**0  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 11 .debug_info     00000805  00000000  00000000  00000930  2**0  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 12 .debug_abbrev   0000033f  00000000  00000000  00001135  2**0  CONTENTS, READONLY, DEBUGGING, OCTETS
 13 .debug_line     0000040d  00000000  00000000  00001474  2**0  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 14 .debug_frame    000001a8  00000000  00000000  00001884  2**2  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 15 .debug_str      00000444  00000000  00000000  00001a2c  2**0  CONTENTS, READONLY, DEBUGGING, OCTETS
 16 .debug_loc      0000048d  00000000  00000000  00001e70  2**0  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 17 .debug_ranges   000001b0  00000000  00000000  000022fd  2**0  CONTENTS, RELOC, READONLY, DEBUGGING, OCTETS
 18 .gnu.attributes 00000010  00000000  00000000  000024ad  2**0  CONTENTS, READONLY
 19 .mdebug.abi32   00000000  00000000  00000000  000024bd  2**0  CONTENTS, READONLY
$ mips-linux-gnu-nm --format sysv --line-numbers --no-sort ascii-table.relocatable.o

Symbols from ascii-table.relocatable.o:

Name                  Value   Class        Type         Size     Line  Section

s_ascii_properties  |00000004|   R  |            OBJECT|00000050|     |.rodata  /home/jblbeurope/Documents/samples/ascii-table.c:11
NUM_ASCII_PROPERTIES|00000054|   R  |            OBJECT|00000004|     |.rodata  /home/jblbeurope/Documents/samples/ascii-table.c:10
islower             |0000026c|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:111
ispunct             |000002c4|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:121
isspace             |000002f0|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:126
isxdigit            |00000348|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:136
write               |00000380|   T  |              FUNC|00000010|     |.text    /home/jblbeurope/Documents/samples/libstd.c:12
__start             |00000398|   T  |              FUNC|00000018|     |.text    /home/jblbeurope/Documents/samples/libstd.c:44
print_number        |00000000|   T  |              FUNC|0000008c|     |.text    /home/jblbeurope/Documents/samples/ascii-table.c:26
isupper             |0000031c|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:131
isalpha             |000001bc|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:91
main                |00000000|   T  |              FUNC|000000a0|     |.text.startup    /home/jblbeurope/Documents/samples/ascii-table.c:57
isgraph             |00000240|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:106
isalnum             |00000190|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:86
isprint             |00000298|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:116
print_ascii_entry   |0000008c|   T  |              FUNC|000000fc|     |.text    /home/jblbeurope/Documents/samples/ascii-table.c:37
COLUMNS             |00000000|   R  |            OBJECT|00000004|     |.rodata  /home/jblbeurope/Documents/samples/ascii-table.c:24
isdigit             |00000214|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:101
exit                |00000390|   T  |              FUNC|00000008|     |.text    /home/jblbeurope/Documents/samples/libstd.c:27
iscntrl             |000001e8|   T  |              FUNC|0000002c|     |.text    /home/jblbeurope/Documents/samples/ctype.c:96
_ctype_             |00000060|   R  |            OBJECT|00000101|     |.rodata  /home/jblbeurope/Documents/samples/ctype.c:49
$ mips-linux-gnu-objdump --wide --reloc ascii-table.relocatable.o
ascii-table.relocatable.o:     file format elf32-tradlittlemips

OFFSET   TYPE              VALUE 
00000058 R_MIPS_26         write
000000b4 R_MIPS_26         print_number
000000c8 R_MIPS_26         write
000000d0 R_MIPS_26         isgraph
000000f0 R_MIPS_26         write
0000010c R_MIPS_26         write
00000170 R_MIPS_26         write
0000019c R_MIPS_HI16       _ctype_
000001a4 R_MIPS_LO16       _ctype_
000001c8 R_MIPS_HI16       _ctype_
000001d0 R_MIPS_LO16       _ctype_
000001f4 R_MIPS_HI16       _ctype_
000001fc R_MIPS_LO16       _ctype_
00000220 R_MIPS_HI16       _ctype_
00000228 R_MIPS_LO16       _ctype_
0000024c R_MIPS_HI16       _ctype_
00000254 R_MIPS_LO16       _ctype_
00000278 R_MIPS_HI16       _ctype_
00000280 R_MIPS_LO16       _ctype_
000002a4 R_MIPS_HI16       _ctype_
000002ac R_MIPS_LO16       _ctype_
000002d0 R_MIPS_HI16       _ctype_
000002d8 R_MIPS_LO16       _ctype_
000002fc R_MIPS_HI16       _ctype_
00000304 R_MIPS_LO16       _ctype_
00000328 R_MIPS_HI16       _ctype_
00000330 R_MIPS_LO16       _ctype_
00000354 R_MIPS_HI16       _ctype_
0000035c R_MIPS_LO16       _ctype_
000003a0 R_MIPS_26         main
000003a8 R_MIPS_26         exit

OFFSET   TYPE              VALUE 
00000008 R_MIPS_HI16       s_ascii_properties
00000018 R_MIPS_LO16       s_ascii_properties
00000048 R_MIPS_26         print_ascii_entry
0000006c R_MIPS_26         write

OFFSET   TYPE              VALUE 
00000004 R_MIPS_32         isgraph
0000000c R_MIPS_32         isprint
00000014 R_MIPS_32         iscntrl
0000001c R_MIPS_32         isspace
00000024 R_MIPS_32         ispunct
0000002c R_MIPS_32         isalnum
00000034 R_MIPS_32         isalpha
0000003c R_MIPS_32         isdigit
00000044 R_MIPS_32         isupper
0000004c R_MIPS_32         islower

We then import that combined object file into Ghidra (with an image base of 0x10054, for reasons that will be explained later), which will effectively link it into an executable. However, unlike a traditional linker Ghidra will keep all of the information which is usually discarded. For example, clicking Window > Relocation Table allows us to take a peek at the relocation table:

We can see all the relocations that Ghidra processed as part of the loading process. Crucially, Ghidra also recorded the original, unrelocated bytes of the artifact in this table, inside the Original Bytes column of the relocations. These are our training wheels for the delinking technique: despite the linking process done to this object file, all of the information from the original object file is still in the Ghidra database.

To be fair, this is a rather unusual way to link an executable. To convince ourselves that we indeed have a working executable in front of us, we can try to execute it. However, this will require a bit more work to achieve this.

Squeezing a working executable out of Ghidra’s linker

To be able to run an ELF executable, we need the following:

  • A valid ELF header of type ET_EXEC with an entry point ;
  • A list of ELF program headers (or segments) ;
  • The actual bytes referenced by the program headers.

We have the bytes since Ghidra loaded the object file into memory and linked it, but we’re missing the rest because Ghidra isn’t actually supposed to be used as a linker. We’ll have to fill in the missing pieces by hand ; we’ll start with the ELF header. Click Window > Memory Map to display the memory map of the program:

Each entry in this table is a block of memory currently loaded into the program. Most are sections taken straight from the ELF file, some like _elfHeader or _elfSectionHeaders are ELF file structures. We’ll add a new block that we can use to hold the executable ELF header we are about to craft. Click on the green cross with the tooltip Add a new block to memory and fill in the dialog as follows:

We now have a place where we can write our own ELF header. Double-click on elfHeader.executable in the Program Trees panel and retype the whole fragment as Elf32_Ehdr. The block was initialized with zeroes, we’ll patch the following values by right-clicking them in the Listing panel and selecting Patch Data (or hitting Ctrl-Shift-H):

Field Value
e_ident_magic_num 0x7f
e_ident_magic_str 0x45 0x4c 0x46 (ELF)
e_ident_class 1 (ELFCLASS32)
e_ident_data 1 (ELFDATA2LSB)
e_ident_version 1 (EVCURRENT)
e_type 2 (ET_EXEC)
e_machine 8 (EM_MIPS)
e_version 1 (EVCURRENT)
e_entry 0x0001041c (__start)
e_phoff 52 (e_ehsize)
e_ehsize 52
e_phentsize 32
e_phnum 1

We’ve written by hand an ELF header for an executable file, which has one program header placed right after the ELF header. We need that program header (or segment) in order to instruct the program loader to correctly load our executable into memory, but we haven’t crafted it yet.

We’ll create another memory block named elfProgramHeaders.executable of size 32 bytes to hold the program header. Sadly there is no definition for a program header in the data type library (the object file doesn’t have program headers so Ghidra didn’t instantiate the type), but we can still type the individual fields and patch in the data:

Field Offset Size Value
p_type 0 4 1 (PT_LOAD)
p_offset 4 4 0 (start of file)
p_vaddr 8 4 0x0010000 (start of program)
p_paddr 12 4 0x0010000 (p_vaddr)
p_filesz 16 4 1604 (size of program)
p_memsz 20 4 1604 (p_filesz)
p_flags 24 4 0x5 (PF_R|PF_X)
p_align 28 4 0x10000 (align to 64 KiB)

Note that this segment includes the ELF header and the program headers. We used an image base of 0x10054 when importing the object file, to make room for the ELF header and the program headers at the beginning of the segment. This isn’t strictly necessary, but segments must be page-aligned ; if the segment starts loading from file offset 0, then we do not need to worry about dealing with alignment and proper padding when concatenating the file.

Now that we have all of the pieces for our executable, we need to export and assemble these into an actual executable file. Select the following address ranges (using Select > Bytes...) and export the selection as a binary file by hitting the O key (or clicking on File > Export Program...):

Start Size File name
elfHeader.executable:00000000 0x36 ascii-table.relocatable.elfHeader.bin
elfProgramHeaders.executable:00000000 0x20 ascii-table.relocatable.elfProgramHeaders.bin
0010054 0x5f0

Then, concatenate the parts into a valid executable file:

$ cat ascii-table.relocatable.elfHeader.bin             \
        ascii-table.relocatable.elfProgramHeaders.bin   \                \
    > ascii-table.relocatable.elf
$ chmod +x ascii-table.relocatable.elf

It’s executable, it’s executable!

We can take a peek at this file using the toolchain:

$ mips-linux-gnu-objdump --wide --file-headers ascii-table.relocatable.elf

ascii-table.relocatable.elf:     file format elf32-tradlittlemips
architecture: mips:3000, flags 0x00000102:
start address 0x0001041c

$ mips-linux-gnu-objdump --wide --section-headers ascii-table.relocatable.elf

ascii-table.relocatable.elf:     file format elf32-tradlittlemips

Idx Name          Size      VMA       LMA       File off  Algn  Flags
$ mips-linux-gnu-nm --format sysv --line-numbers --no-sort ascii-table.relocatable.elf

Symbols from ascii-table.relocatable.elf:

Name                  Value   Class        Type         Size     Line  Section

mips-linux-gnu-nm: ascii-table.relocatable.elf: no symbols
$ mips-linux-gnu-objdump --wide --reloc ascii-table.relocatable.elf

ascii-table.relocatable.elf:     file format elf32-tradlittlemips

When compared to the original ascii-table.elf artifact, there is a lot of information missing from this file. This is because most of it isn’t actually necessary to run an executable: the program loader only cares about the ELF header and the program headers. We simply did not synthesize that superfluous information when creating the ELF file structures by hand.

Nevertheless, this is a valid executable and as such can be run:

$ qemu-mipsel ascii-table.relocatable.elf 
0000     c              0020    p s             0040 @ gp  !            0060 ` gp  !     
0001     c              0021 ! gp  !            0041 A gp   Aa U        0061 a gp   Aa  l
0002     c              0022 " gp  !            0042 B gp   Aa U        0062 b gp   Aa  l
0003     c              0023 # gp  !            0043 C gp   Aa U        0063 c gp   Aa  l
0004     c              0024 $ gp  !            0044 D gp   Aa U        0064 d gp   Aa  l
0005     c              0025 % gp  !            0045 E gp   Aa U        0065 e gp   Aa  l
0006     c              0026 & gp  !            0046 F gp   Aa U        0066 f gp   Aa  l
0007     c              0027 ' gp  !            0047 G gp   Aa U        0067 g gp   Aa  l
0008     c              0028 ( gp  !            0048 H gp   Aa U        0068 h gp   Aa  l
0009     cs             0029 ) gp  !            0049 I gp   Aa U        0069 i gp   Aa  l
000a     cs             002a * gp  !            004a J gp   Aa U        006a j gp   Aa  l
000b     cs             002b + gp  !            004b K gp   Aa U        006b k gp   Aa  l
000c     cs             002c , gp  !            004c L gp   Aa U        006c l gp   Aa  l
000d     cs             002d - gp  !            004d M gp   Aa U        006d m gp   Aa  l
000e     c              002e . gp  !            004e N gp   Aa U        006e n gp   Aa  l
000f     c              002f / gp  !            004f O gp   Aa U        006f o gp   Aa  l
0010     c              0030 0 gp   A d         0050 P gp   Aa U        0070 p gp   Aa  l
0011     c              0031 1 gp   A d         0051 Q gp   Aa U        0071 q gp   Aa  l
0012     c              0032 2 gp   A d         0052 R gp   Aa U        0072 r gp   Aa  l
0013     c              0033 3 gp   A d         0053 S gp   Aa U        0073 s gp   Aa  l
0014     c              0034 4 gp   A d         0054 T gp   Aa U        0074 t gp   Aa  l
0015     c              0035 5 gp   A d         0055 U gp   Aa U        0075 u gp   Aa  l
0016     c              0036 6 gp   A d         0056 V gp   Aa U        0076 v gp   Aa  l
0017     c              0037 7 gp   A d         0057 W gp   Aa U        0077 w gp   Aa  l
0018     c              0038 8 gp   A d         0058 X gp   Aa U        0078 x gp   Aa  l
0019     c              0039 9 gp   A d         0059 Y gp   Aa U        0079 y gp   Aa  l
001a     c              003a : gp  !            005a Z gp   Aa U        007a z gp   Aa  l
001b     c              003b ; gp  !            005b [ gp  !            007b { gp  !     
001c     c              003c < gp  !            005c \ gp  !            007c | gp  !     
001d     c              003d = gp  !            005d ] gp  !            007d } gp  !     
001e     c              003e > gp  !            005e ^ gp  !            007e ~ gp  !     
001f     c              003f ? gp  !            005f _ gp  !            007f     c       

We can observe that this Frankenstein’s monster of an executable file does work and we are treated to the familiar sight of our ASCII table of our case study, just like with the original ascii-table.elf.

The files for this case study can be found here: case-study.tar.gz


We have crafted an executable artifact based on our case study which still has all of its original, pre-linkage information in its Ghidra database. Next time, no more stalling: we will actually delink this executable back into a relocatable object file.