Reverse-engineering part 10: with a little help from my extension
Previously in this series of articles, we delinked an executable produced by a standard linker back into a relocatable object file by using Ghidra and lots of Python script snippets. In this article we will do the same thing, but this time we will use a Ghidra extension to do all the hard work for us.
Installing the Ghidra delinker extension
My Ghidra delinker extension largely automates the process of delinking by:
- Providing an analyzer that synthesizes relocation entries for a program ;
- Providing exporters to slice out parts of a program as relocatable object files.
To install the extension, we’ll follow the instruction laid out in the README.md file.
First, download the ZIP file from the releases page.
Then, launch Ghidra and click on File > Install Extensions...
to bring up the extension list window (this article is written with the 0.1.0 version).
Click on the green “➕” button on the top-right corner with the tooltip Add extension
, then select the ZIP file.
The extension should appear in the list and be enabled:
Click OK
to close the extension list window.
Ghidra will show a pop-up requesting a restart for the installation to take effect.
Dismiss it and restart Ghidra.
Now that the extension is installed, open up ascii-table.elf
in the case study project.
Ghidra will prompt to configure newly-detected extension plugins, click Yes
.
Enable the RelocationTableSynthesizedPlugin
in the list by clicking on its checkbox:
Click OK
to close the plugin list window.
Delinking our case study the lazy way
Now that the extension is installed and configured, select the .sbss
, .sdata
, .rodata
and .data
sections in the program tree, then right-click on them and use Select Addresses
.
The address range 0x00400150-0x00410b20
should now be selected in the listing view.
We’ll invoke the extension’s analyzer on this selection by clicking on Analysis > One Shot > Relocation table synthesizer
.
Then, click on Window > Relocation Table (synthesized)
to display the recovered relocations:
Before exporting a subset of the program, we need to define what parts we want to export.
Go to the print_number
function in the listing view and remove it from the selection by holding Ctrl
and then dragging a left click from the start to the end of the function.
The selection should now cover the entire program except for the print_number
function.
To bring up the export dialog, hit the O
key (or click on File > Export Program...
).
Select the ELF Relocatable object
format, make sure the Selection Only
checkbox is checked and export the file as ascii-table.delinker.o
.
Binary patching through delinking
Like in the previous part, we can take a peek at this file using the toolchain:
$ mips-linux-gnu-objdump --wide --file-headers ascii-table.delinker.o
ascii-table.delinker.o: file format elf32-tradlittlemips
architecture: mips:3000, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x00000000
$ mips-linux-gnu-objdump --wide --section-headers ascii-table.delinker.o
ascii-table.delinker.o: file format elf32-tradlittlemips
Sections:
Idx Name Size VMA LMA File off Algn Flags
0 .text 000007b8 00000000 00000000 000005c0 2**4 CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .rodata 00000160 00000000 00000000 00000d80 2**4 CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
2 .sdata 00000008 00000000 00000000 00000ee0 2**4 CONTENTS, ALLOC, LOAD, DATA
3 .sbss 0000000c 00000000 00000000 00000000 2**4 ALLOC
$ mips-linux-gnu-nm --format sysv --line-numbers --no-sort ascii-table.delinker.o
Symbols from ascii-table.delinker.o:
Name Value Class Type Size Line Section
sys_getpid |00000000| T | FUNC|00000024| |.text
sys_kill |00000024| T | FUNC|00000024| |.text
sys_write |00000048| T | FUNC|00000024| |.text
_nolibc_memcpy_up |0000006c| T | FUNC|00000038| |.text
fileno |000000a4| T | FUNC|0000002c| |.text
__start |000000d0| T | FUNC|00000064| |.text
write |00000134| T | FUNC|00000048| |.text
fputc |0000017c| T | FUNC|00000064| |.text
putchar |000001e0| T | FUNC|0000002c| |.text
print_ascii_entry |0000020c| T | FUNC|0000013c| |.text
main |00000348| T | FUNC|000000c0| |.text
isalnum |00000414| T | FUNC|00000038| |.text
isalpha |0000044c| T | FUNC|00000038| |.text
iscntrl |00000484| T | FUNC|00000038| |.text
isdigit |000004bc| T | FUNC|00000038| |.text
isgraph |000004f4| T | FUNC|00000038| |.text
islower |0000052c| T | FUNC|00000038| |.text
isprint |00000564| T | FUNC|00000038| |.text
ispunct |0000059c| T | FUNC|00000038| |.text
isspace |000005d4| T | FUNC|00000038| |.text
isupper |0000060c| T | FUNC|00000038| |.text
isxdigit |00000644| T | FUNC|00000038| |.text
raise |00000684| T | FUNC|0000004c| |.text
memmove |000006d0| T | FUNC|00000058| |.text
memcpy |00000728| T | FUNC|00000028| |.text
memset |00000750| T | FUNC|00000030| |.text
abort |00000780| T | FUNC|00000038| |.text
s_ascii_properties |00000000| R | OBJECT|00000050| |.rodata
_ctype_ |00000050| R | OBJECT|00000101| |.rodata
COLUMNS |00000000| D | OBJECT|00000004| |.sdata
NUM_ASCII_PROPERTIES|00000004| D | OBJECT|00000004| |.sdata
errno |00000000| B | OBJECT|00000004| |.sbss
_auxv |00000004| B | OBJECT|00000004| |.sbss
environ |00000008| B | OBJECT|00000004| |.sbss
_gp | | U | NOTYPE| | |*UND* ascii-table.delinker.o:0
print_number | | U | NOTYPE| | |*UND* ascii-table.delinker.o:0
$ mips-linux-gnu-objdump --wide --reloc ascii-table.delinker.o
ascii-table.delinker.o: file format elf32-tradlittlemips
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
000000b8 R_MIPS_HI16 errno
000000c0 R_MIPS_LO16 errno
000000d0 R_MIPS_HI16 _gp
000000d4 R_MIPS_LO16 _gp
000000ec R_MIPS_HI16 environ
000000f0 R_MIPS_LO16 environ
00000108 R_MIPS_HI16 _auxv
0000010c R_MIPS_LO16 _auxv
00000120 R_MIPS_26 main
0000013c R_MIPS_HI16 sys_write
00000140 R_MIPS_LO16 sys_write
00000168 R_MIPS_HI16 errno
0000016c R_MIPS_LO16 errno
0000018c R_MIPS_HI16 fileno
00000190 R_MIPS_LO16 fileno
000001a8 R_MIPS_HI16 write
000001ac R_MIPS_LO16 write
000001ec R_MIPS_HI16 fputc
000001f0 R_MIPS_LO16 fputc
00000234 R_MIPS_HI16 print_number
00000238 R_MIPS_LO16 print_number
00000248 R_MIPS_HI16 putchar
0000024c R_MIPS_LO16 putchar
0000025c R_MIPS_HI16 isgraph
00000260 R_MIPS_LO16 isgraph
00000278 R_MIPS_HI16 putchar
0000027c R_MIPS_LO16 putchar
0000028c R_MIPS_HI16 putchar
00000290 R_MIPS_LO16 putchar
000002ac R_MIPS_HI16 putchar
000002b0 R_MIPS_LO16 putchar
000002c8 R_MIPS_HI16 putchar
000002cc R_MIPS_LO16 putchar
0000030c R_MIPS_HI16 putchar
00000310 R_MIPS_LO16 putchar
00000364 R_MIPS_HI16 putchar
00000368 R_MIPS_LO16 putchar
00000384 R_MIPS_GPREL16 COLUMNS
000003ac R_MIPS_HI16 s_ascii_properties
000003b0 R_MIPS_LO16 s_ascii_properties
000003bc R_MIPS_HI16 print_ascii_entry
000003c0 R_MIPS_LO16 print_ascii_entry
000003cc R_MIPS_GPREL16 COLUMNS
00000424 R_MIPS_HI16 _ctype_
00000428 R_MIPS_LO16 _ctype_
0000045c R_MIPS_HI16 _ctype_
00000460 R_MIPS_LO16 _ctype_
00000494 R_MIPS_HI16 _ctype_
00000498 R_MIPS_LO16 _ctype_
000004cc R_MIPS_HI16 _ctype_
000004d0 R_MIPS_LO16 _ctype_
00000504 R_MIPS_HI16 _ctype_
00000508 R_MIPS_LO16 _ctype_
0000053c R_MIPS_HI16 _ctype_
00000540 R_MIPS_LO16 _ctype_
00000574 R_MIPS_HI16 _ctype_
00000578 R_MIPS_LO16 _ctype_
000005ac R_MIPS_HI16 _ctype_
000005b0 R_MIPS_LO16 _ctype_
000005e4 R_MIPS_HI16 _ctype_
000005e8 R_MIPS_LO16 _ctype_
0000061c R_MIPS_HI16 _ctype_
00000620 R_MIPS_LO16 _ctype_
00000654 R_MIPS_HI16 _ctype_
00000658 R_MIPS_LO16 _ctype_
00000694 R_MIPS_HI16 sys_getpid
00000698 R_MIPS_LO16 sys_getpid
000006ac R_MIPS_HI16 sys_kill
000006b0 R_MIPS_LO16 sys_kill
00000730 R_MIPS_HI16 _nolibc_memcpy_up
00000734 R_MIPS_LO16 _nolibc_memcpy_up
00000788 R_MIPS_HI16 sys_getpid
0000078c R_MIPS_LO16 sys_getpid
000007a0 R_MIPS_HI16 sys_kill
000007a4 R_MIPS_LO16 sys_kill
RELOCATION RECORDS FOR [.rodata]:
OFFSET TYPE VALUE
00000000 R_MIPS_32 isgraph
00000008 R_MIPS_32 isprint
00000010 R_MIPS_32 iscntrl
00000018 R_MIPS_32 isspace
00000020 R_MIPS_32 ispunct
00000028 R_MIPS_32 isalnum
00000030 R_MIPS_32 isalpha
00000038 R_MIPS_32 isdigit
00000040 R_MIPS_32 isupper
00000048 R_MIPS_32 islower
Again, we’ll modify our executable by linking that object file with print_number_octal.o
:
$ mips-linux-gnu-gcc -EL -static -no-pie -nostdlib -o ascii-table.delinker.elf ascii-table.delinker.o print_number_octal.o
Finally, let’s observe the final product:
$ qemu-mipsel ascii-table.delinker.elf
0000 c 0040 p s 0100 @ gp ! 0140 ` gp !
0001 c 0041 ! gp ! 0101 A gp Aa U 0141 a gp Aa l
0002 c 0042 " gp ! 0102 B gp Aa U 0142 b gp Aa l
0003 c 0043 # gp ! 0103 C gp Aa U 0143 c gp Aa l
0004 c 0044 $ gp ! 0104 D gp Aa U 0144 d gp Aa l
0005 c 0045 % gp ! 0105 E gp Aa U 0145 e gp Aa l
0006 c 0046 & gp ! 0106 F gp Aa U 0146 f gp Aa l
0007 c 0047 ' gp ! 0107 G gp Aa U 0147 g gp Aa l
0010 c 0050 ( gp ! 0110 H gp Aa U 0150 h gp Aa l
0011 cs 0051 ) gp ! 0111 I gp Aa U 0151 i gp Aa l
0012 cs 0052 * gp ! 0112 J gp Aa U 0152 j gp Aa l
0013 cs 0053 + gp ! 0113 K gp Aa U 0153 k gp Aa l
0014 cs 0054 , gp ! 0114 L gp Aa U 0154 l gp Aa l
0015 cs 0055 - gp ! 0115 M gp Aa U 0155 m gp Aa l
0016 c 0056 . gp ! 0116 N gp Aa U 0156 n gp Aa l
0017 c 0057 / gp ! 0117 O gp Aa U 0157 o gp Aa l
0020 c 0060 0 gp A d 0120 P gp Aa U 0160 p gp Aa l
0021 c 0061 1 gp A d 0121 Q gp Aa U 0161 q gp Aa l
0022 c 0062 2 gp A d 0122 R gp Aa U 0162 r gp Aa l
0023 c 0063 3 gp A d 0123 S gp Aa U 0163 s gp Aa l
0024 c 0064 4 gp A d 0124 T gp Aa U 0164 t gp Aa l
0025 c 0065 5 gp A d 0125 U gp Aa U 0165 u gp Aa l
0026 c 0066 6 gp A d 0126 V gp Aa U 0166 v gp Aa l
0027 c 0067 7 gp A d 0127 W gp Aa U 0167 w gp Aa l
0030 c 0070 8 gp A d 0130 X gp Aa U 0170 x gp Aa l
0031 c 0071 9 gp A d 0131 Y gp Aa U 0171 y gp Aa l
0032 c 0072 : gp ! 0132 Z gp Aa U 0172 z gp Aa l
0033 c 0073 ; gp ! 0133 [ gp ! 0173 { gp !
0034 c 0074 < gp ! 0134 \ gp ! 0174 | gp !
0035 c 0075 = gp ! 0135 ] gp ! 0175 } gp !
0036 c 0076 > gp ! 0136 ^ gp ! 0176 ~ gp !
0037 c 0077 ? gp ! 0137 _ gp ! 0177 c
For the fifth time, we have successfully modified our case study to print the ASCII table in octal.
The files for this case study can be found here: case-study.tar.gz
Conclusion
We have successfully delinked parts of a normal executable back into a relocatable object file with a Ghidra extension and used it to make a new, modified executable. Unlike the previous times where we built up our object file with lots of Python script snippets and manual work, this extension automated the entire process in two easy steps, making the delinking technique practical for everyday reverse-engineering work.
This concludes the currently planned articles in this series. While this series served as an introduction to reverse-engineering, there are many more topics, techniques and use-cases out there to study and practice in this field that is as much art as it is science.