VirtualBox

source: vbox/trunk/src/bldprogs/VBoxEditElf.cpp@ 108495

Last change on this file since 108495 was 108495, checked in by vboxsync, 6 weeks ago

bldprogs,Config.kmk,Makefile.kmk: Introduce VBoxEditElf tool to edit ELF binaries. Currently supports deleting and chanign the runpath only, bugref:10874

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.2 KB
Line 
1/* $Id: VBoxEditElf.cpp 108495 2025-03-10 13:00:08Z vboxsync $ */
2/** @file
3 * VBoxEditElf - Simple ELF binary file editor.
4 */
5
6/*
7 * Copyright (C) 2025 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <stdio.h>
33#include <string.h>
34#include <stdlib.h>
35
36#include <iprt/assert.h>
37#include <iprt/file.h>
38#include <iprt/getopt.h>
39#include <iprt/initterm.h>
40#include <iprt/mem.h>
41#include <iprt/message.h>
42#include <iprt/stream.h>
43#include <iprt/string.h>
44#include <iprt/types.h>
45#include <iprt/formats/elf.h>
46
47
48/*********************************************************************************************************************************
49* Structures and Typedefs *
50*********************************************************************************************************************************/
51
52
53/*********************************************************************************************************************************
54* Global Variables *
55*********************************************************************************************************************************/
56/** @name Options
57 * @{ */
58static enum
59{
60 kVBoxEditElfAction_Nothing,
61 kVBoxEditElfAction_DeleteRunpath,
62 kVBoxEditElfAction_ChangeRunpath
63} g_enmAction = kVBoxEditElfAction_Nothing;
64static const char *g_pszInput = NULL;
65/** Verbosity level. */
66static int g_cVerbosity = 0;
67/** New runpath. */
68static const char *g_pszRunpath = NULL;
69/** @} */
70
71
72
73static RTEXITCODE deleteRunpath(const char *pszInput)
74{
75 RT_NOREF(pszInput);
76
77 RTFILE hFileElf = NIL_RTFILE;
78 int rc = RTFileOpen(&hFileElf, pszInput, RTFILE_O_OPEN | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE);
79 if (RT_FAILURE(rc))
80 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Filed to open file '%s': %Rrc\n", pszInput, rc);
81
82 /* Only support for 64-bit ELF files currently. */
83 Elf64_Ehdr Hdr;
84 rc = RTFileReadAt(hFileElf, 0, &Hdr, sizeof(Hdr), NULL);
85 if (RT_FAILURE(rc))
86 {
87 RTFileClose(hFileElf);
88 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read ELF header from '%s': %Rrc\n", pszInput, rc);
89 }
90
91 if ( Hdr.e_ident[EI_MAG0] != ELFMAG0
92 || Hdr.e_ident[EI_MAG1] != ELFMAG1
93 || Hdr.e_ident[EI_MAG2] != ELFMAG2
94 || Hdr.e_ident[EI_MAG3] != ELFMAG3)
95 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid ELF magic (%.*Rhxs)", sizeof(Hdr.e_ident), Hdr.e_ident);
96 if (Hdr.e_ident[EI_CLASS] != ELFCLASS64)
97 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid ELF class (%.*Rhxs)", sizeof(Hdr.e_ident), Hdr.e_ident);
98 if (Hdr.e_ident[EI_DATA] != ELFDATA2LSB)
99 return RTMsgErrorExit(RTEXITCODE_FAILURE, "ELF endian %x is unsupported", Hdr.e_ident[EI_DATA]);
100 if (Hdr.e_version != EV_CURRENT)
101 return RTMsgErrorExit(RTEXITCODE_FAILURE, "ELF version %x is unsupported", Hdr.e_version);
102
103 if (sizeof(Elf64_Ehdr) != Hdr.e_ehsize)
104 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Elf header e_ehsize is %d expected %d!", Hdr.e_ehsize, sizeof(Elf64_Ehdr));
105 if ( sizeof(Elf64_Phdr) != Hdr.e_phentsize
106 && ( Hdr.e_phnum != 0
107 || Hdr.e_type == ET_DYN
108 || Hdr.e_type == ET_EXEC))
109 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Elf header e_phentsize is %d expected %d!", Hdr.e_phentsize, sizeof(Elf64_Phdr));
110 if (sizeof(Elf_Shdr) != Hdr.e_shentsize)
111 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Elf header e_shentsize is %d expected %d!", Hdr.e_shentsize, sizeof(Elf64_Shdr));
112
113 /* Find dynamic section. */
114 Elf64_Phdr Phdr;
115 bool fFound = false;
116 for (uint32_t i = 0; i < Hdr.e_phnum; i++)
117 {
118 rc = RTFileReadAt(hFileElf, Hdr.e_phoff + i * sizeof(Phdr), &Phdr, sizeof(Phdr), NULL);
119 if (RT_FAILURE(rc))
120 {
121 RTFileClose(hFileElf);
122 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read ELF program header header from '%s': %Rrc\n", pszInput, rc);
123 }
124 if (Phdr.p_type == PT_DYNAMIC)
125 {
126 if (!Phdr.p_filesz)
127 {
128 RTFileClose(hFileElf);
129 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Dynmic section in '%s' is empty\n", pszInput);
130 }
131 fFound = true;
132 break;
133 }
134 }
135
136 if (!fFound)
137 return RTMsgErrorExit(RTEXITCODE_FAILURE, "ELF binary '%s' doesn't contain dynamic section\n", pszInput);
138
139 Elf64_Dyn *paDynSh = (Elf64_Dyn *)RTMemAllocZ(Phdr.p_filesz);
140 if (!paDynSh)
141 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate %zu bytes of memory for dynamic section of '%s'\n", Phdr.p_filesz, pszInput);
142
143 rc = RTFileReadAt(hFileElf, Phdr.p_offset, paDynSh, Phdr.p_filesz, NULL);
144 if (RT_FAILURE(rc))
145 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read ELF program header header from '%s': %Rrc\n", pszInput, rc);
146
147 /* Remove all DT_RUNPATH entries and padd the remainder with DT_NULL. */
148 uint32_t idx = 0;
149 for (uint32_t i = 0; i < Phdr.p_filesz / sizeof(Elf64_Dyn); i++)
150 {
151 paDynSh[idx] = paDynSh[i];
152 if (paDynSh[i].d_tag != DT_RPATH && paDynSh[i].d_tag != DT_RUNPATH)
153 idx++;
154 }
155
156 while (idx < Phdr.p_filesz / sizeof(Elf64_Dyn))
157 {
158 paDynSh[idx].d_tag = DT_NULL;
159 paDynSh[idx].d_un.d_val = 0;
160 idx++;
161 }
162
163 /* Write the result. */
164 rc = RTFileWriteAt(hFileElf, Phdr.p_offset, paDynSh, Phdr.p_filesz, NULL);
165 if (RT_FAILURE(rc))
166 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to write updated ELF dynamic section for '%s': %Rrc\n", pszInput, rc);
167
168 RTMemFree(paDynSh);
169 RTFileClose(hFileElf);
170 return RTEXITCODE_SUCCESS;
171}
172
173
174static RTEXITCODE changeRunpathEntry(RTFILE hFileElf, const char *pszInput, Elf64_Ehdr *pHdr, Elf64_Xword offInStrTab, const char *pszRunpath)
175{
176 /* Read section headers to find the string table. */
177 size_t const cbShdrs = pHdr->e_shnum * sizeof(Elf64_Shdr);
178 Elf64_Shdr *paShdrs = (Elf64_Shdr *)RTMemAlloc(cbShdrs);
179 if (!paShdrs)
180 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate %zu bytes of memory for section headers of '%s'\n", cbShdrs, pszInput);
181
182 int rc = RTFileReadAt(hFileElf, pHdr->e_shoff, paShdrs, cbShdrs, NULL);
183 if (RT_FAILURE(rc))
184 {
185 RTMemFree(paShdrs);
186 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read %zu bytes of section headers from '%s': %Rrc\n", cbShdrs, pszInput, rc);
187 }
188
189 uint32_t idx;
190 for (idx = 0; idx < pHdr->e_shnum; idx++)
191 {
192 if (paShdrs[idx].sh_type == SHT_STRTAB)
193 break;
194 }
195
196 if (idx == pHdr->e_shnum)
197 {
198 RTMemFree(paShdrs);
199 return RTMsgErrorExit(RTEXITCODE_FAILURE, "ELF binary '%s' does not contain a string table\n", pszInput);
200 }
201
202 size_t const cbStrTab = paShdrs[idx].sh_size;
203 RTFOFF const offStrTab = paShdrs[idx].sh_offset;
204 RTMemFree(paShdrs);
205
206 if (offInStrTab >= cbStrTab)
207 return RTMsgErrorExit(RTEXITCODE_FAILURE, "String table offset of runpath entry is out of bounds: got %#RX64, maximum is %zu\n", offInStrTab, cbStrTab - 1);
208
209 /* Read the string table. */
210 char *pbStrTab = (char *)RTMemAllocZ(cbStrTab + 1); /* Force a zero terminator. */
211 if (!pbStrTab)
212 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate %zu bytes of memory for string table of '%s'\n", cbStrTab + 1, pszInput);
213
214 rc = RTFileReadAt(hFileElf, offStrTab, pbStrTab, cbStrTab, NULL);
215 if (RT_FAILURE(rc))
216 {
217 RTMemFree(pbStrTab);
218 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read %zu bytes of the string table from '%s': %Rrc\n", cbStrTab, pszInput, rc);
219 }
220
221 /* Calculate the maximum number of characters we can replace. */
222 char *pbStr = &pbStrTab[offInStrTab];
223 size_t cchMax = strlen(pbStr);
224 while ( &pbStr[cchMax + 1] < &pbStrTab[cbStrTab]
225 && pbStr[cchMax] == '\0')
226 cchMax++;
227
228 size_t const cchNewRunpath = strlen(pszRunpath);
229 if (cchMax < cchNewRunpath)
230 {
231 RTMemFree(pbStrTab);
232 return RTMsgErrorExit(RTEXITCODE_FAILURE, "New runpath '%s' is too long to overwrite current one, maximum length is: %zu\n", cchNewRunpath, cchMax);
233 }
234
235 memcpy(&pbStr[cchMax], pszRunpath, cchNewRunpath);
236 rc = RTFileReadAt(hFileElf, offStrTab, pbStrTab, cbStrTab, NULL);
237 RTMemFree(pbStrTab);
238 if (RT_FAILURE(rc))
239 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Writing altered string table failed: %Rrc\n", rc);
240
241 return RTEXITCODE_SUCCESS;
242}
243
244
245static RTEXITCODE changeRunpath(const char *pszInput, const char *pszRunpath)
246{
247 RT_NOREF(pszInput);
248
249 RTFILE hFileElf = NIL_RTFILE;
250 int rc = RTFileOpen(&hFileElf, pszInput, RTFILE_O_OPEN | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE);
251 if (RT_FAILURE(rc))
252 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Filed to open file '%s': %Rrc\n", pszInput, rc);
253
254 /* Only support for 64-bit ELF files currently. */
255 Elf64_Ehdr Hdr;
256 rc = RTFileReadAt(hFileElf, 0, &Hdr, sizeof(Hdr), NULL);
257 if (RT_FAILURE(rc))
258 {
259 RTFileClose(hFileElf);
260 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read ELF header from '%s': %Rrc\n", pszInput, rc);
261 }
262
263 if ( Hdr.e_ident[EI_MAG0] != ELFMAG0
264 || Hdr.e_ident[EI_MAG1] != ELFMAG1
265 || Hdr.e_ident[EI_MAG2] != ELFMAG2
266 || Hdr.e_ident[EI_MAG3] != ELFMAG3)
267 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid ELF magic (%.*Rhxs)", sizeof(Hdr.e_ident), Hdr.e_ident);
268 if (Hdr.e_ident[EI_CLASS] != ELFCLASS64)
269 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid ELF class (%.*Rhxs)", sizeof(Hdr.e_ident), Hdr.e_ident);
270 if (Hdr.e_ident[EI_DATA] != ELFDATA2LSB)
271 return RTMsgErrorExit(RTEXITCODE_FAILURE, "ELF endian %x is unsupported", Hdr.e_ident[EI_DATA]);
272 if (Hdr.e_version != EV_CURRENT)
273 return RTMsgErrorExit(RTEXITCODE_FAILURE, "ELF version %x is unsupported", Hdr.e_version);
274
275 if (sizeof(Elf64_Ehdr) != Hdr.e_ehsize)
276 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Elf header e_ehsize is %d expected %d!", Hdr.e_ehsize, sizeof(Elf64_Ehdr));
277 if ( sizeof(Elf64_Phdr) != Hdr.e_phentsize
278 && ( Hdr.e_phnum != 0
279 || Hdr.e_type == ET_DYN
280 || Hdr.e_type == ET_EXEC))
281 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Elf header e_phentsize is %d expected %d!", Hdr.e_phentsize, sizeof(Elf64_Phdr));
282 if (sizeof(Elf_Shdr) != Hdr.e_shentsize)
283 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Elf header e_shentsize is %d expected %d!", Hdr.e_shentsize, sizeof(Elf64_Shdr));
284
285 /* Find dynamic section. */
286 Elf64_Phdr Phdr;
287 bool fFound = false;
288 for (uint32_t i = 0; i < Hdr.e_phnum; i++)
289 {
290 rc = RTFileReadAt(hFileElf, Hdr.e_phoff + i * sizeof(Phdr), &Phdr, sizeof(Phdr), NULL);
291 if (RT_FAILURE(rc))
292 {
293 RTFileClose(hFileElf);
294 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read ELF program header header from '%s': %Rrc\n", pszInput, rc);
295 }
296 if (Phdr.p_type == PT_DYNAMIC)
297 {
298 if (!Phdr.p_filesz)
299 {
300 RTFileClose(hFileElf);
301 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Dynmic section in '%s' is empty\n", pszInput);
302 }
303 fFound = true;
304 break;
305 }
306 }
307
308 if (!fFound)
309 return RTMsgErrorExit(RTEXITCODE_FAILURE, "ELF binary '%s' doesn't contain dynamic section\n", pszInput);
310
311 Elf64_Dyn *paDynSh = (Elf64_Dyn *)RTMemAllocZ(Phdr.p_filesz);
312 if (!paDynSh)
313 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate %zu bytes of memory for dynamic section of '%s'\n", Phdr.p_filesz, pszInput);
314
315 rc = RTFileReadAt(hFileElf, Phdr.p_offset, paDynSh, Phdr.p_filesz, NULL);
316 if (RT_FAILURE(rc))
317 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to read ELF program header header from '%s': %Rrc\n", pszInput, rc);
318
319 /* Look for the first DT_RUNPATH entry and rewrite it. */
320 for (uint32_t i = 0; i < Phdr.p_filesz / sizeof(Elf64_Dyn); i++)
321 {
322 if ( paDynSh[i].d_tag == DT_RPATH
323 || paDynSh[i].d_tag == DT_RUNPATH)
324 {
325 RTEXITCODE rcExit = changeRunpathEntry(hFileElf, pszInput, &Hdr, paDynSh[i].d_un.d_val, pszRunpath);
326 RTMemFree(paDynSh);
327 RTFileClose(hFileElf);
328 return rcExit;
329 }
330 }
331
332 RTMemFree(paDynSh);
333 RTFileClose(hFileElf);
334 return RTMsgErrorExit(RTEXITCODE_FAILURE, "No DT_RPATH or DT_RUNPATH entry found in '%s'\n", pszInput);
335}
336
337
338/**
339 * Display usage
340 *
341 * @returns success if stdout, syntax error if stderr.
342 */
343static RTEXITCODE usage(FILE *pOut, const char *argv0)
344{
345 fprintf(pOut,
346 "usage: %s --input <input binary> [options and operations]\n"
347 "\n"
348 "Operations and Options (processed in place):\n"
349 " --verbose Noisier.\n"
350 " --quiet Quiet execution.\n"
351 " --delete-runpath Deletes all DT_RUNPATH entries.\n"
352 " --change-runpath <new runpath> Changes the first DT_RUNPATH entry to the new one.\n"
353 , argv0);
354 return pOut == stdout ? RTEXITCODE_SUCCESS : RTEXITCODE_SYNTAX;
355}
356
357
358/**
359 * Parses the arguments.
360 */
361static RTEXITCODE parseArguments(int argc, char **argv)
362{
363 /*
364 * Option config.
365 */
366 static RTGETOPTDEF const s_aOpts[] =
367 {
368 /* dtrace w/ long options */
369 { "--input", 'i', RTGETOPT_REQ_STRING },
370 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
371 /* our stuff */
372 { "--delete-runpath", 'd', RTGETOPT_REQ_NOTHING },
373 { "--change-runpath", 'c', RTGETOPT_REQ_STRING },
374 };
375
376 RTGETOPTUNION ValueUnion;
377 RTGETOPTSTATE GetOptState;
378 int rc = RTGetOptInit(&GetOptState, argc, argv, &s_aOpts[0], RT_ELEMENTS(s_aOpts), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST);
379 AssertReleaseRCReturn(rc, RTEXITCODE_FAILURE);
380
381 /*
382 * Process the options.
383 */
384 while ((rc = RTGetOpt(&GetOptState, &ValueUnion)) != 0)
385 {
386 switch (rc)
387 {
388 case 'h':
389 return usage(stdout, argv[0]);
390
391 case 'i':
392 if (g_pszInput)
393 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Input file is already set to '%s'", g_pszInput);
394 g_pszInput = ValueUnion.psz;
395 break;
396
397 case 'v':
398 g_cVerbosity++;
399 break;
400
401 case 'd':
402 g_enmAction = kVBoxEditElfAction_DeleteRunpath;
403 break;
404
405 case 'c':
406 g_enmAction = kVBoxEditElfAction_ChangeRunpath;
407 g_pszRunpath = ValueUnion.psz;
408 break;
409
410 case 'V':
411 {
412 /* The following is assuming that svn does it's job here. */
413 static const char s_szRev[] = "$Revision: 108495 $";
414 const char *psz = RTStrStripL(strchr(s_szRev, ' '));
415 RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz);
416 return RTEXITCODE_SUCCESS;
417 }
418
419 /*
420 * Errors and bugs.
421 */
422 default:
423 return RTGetOptPrintError(rc, &ValueUnion);
424 }
425 }
426
427 /*
428 * Check that we've got all we need.
429 */
430 if (g_enmAction == kVBoxEditElfAction_Nothing)
431 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No action specified (--delete-runpath or --change-runpath)");
432 if (!g_pszInput)
433 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No input file specified (--input)");
434
435 return RTEXITCODE_SUCCESS;
436}
437
438
439int main(int argc, char **argv)
440{
441 int rc = RTR3InitExe(argc, &argv, 0);
442 if (RT_FAILURE(rc))
443 return 1;
444
445 RTEXITCODE rcExit = parseArguments(argc, argv);
446 if (rcExit == RTEXITCODE_SUCCESS)
447 {
448 /*
449 * Take action.
450 */
451 if (g_enmAction == kVBoxEditElfAction_DeleteRunpath)
452 rcExit = deleteRunpath(g_pszInput);
453 else if (g_enmAction == kVBoxEditElfAction_ChangeRunpath)
454 rcExit = changeRunpath(g_pszInput, g_pszRunpath);
455 }
456
457 return rcExit;
458}
459
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette