Re-compiling Decompiler Output
Edward J. SchwartzComputer Security Researcher4 min. read

Many people seem to be unaware that decompilers have a decompilation export feature, which is particularly beneficial when you are trying to parse or recompile decompiled code.

Ghidra

Here is a random function from /bin/ls that I decompiled using Ghidra, and simply copied the output from the decompilation window:

void FUN_0010b1b0(undefined8 *param_1,undefined8 *param_2)

{
  char *__s;
  char *__s_00;
  int iVar1;
  char *__s1;
  char *__s2;
  int *piVar2;
  
  __s = (char *)*param_2;
  __s1 = strrchr(__s,0x2e);
  __s_00 = (char *)*param_1;
  __s2 = strrchr(__s_00,0x2e);
  if (__s2 == (char *)0x0) {
    __s2 = "";
  }
  if (__s1 == (char *)0x0) {
    __s1 = "";
  }
  piVar2 = __errno_location();
  *piVar2 = 0;
  iVar1 = strcoll(__s1,__s2);
  if (iVar1 == 0) {
    strcoll(__s,__s_00);
    return;
  }
  return;
}

This code does not compile.

gcc -c a.c
a.c:1:19: error: unknown type name ‘undefined8’
    1 | void FUN_0010b1b0(undefined8 *param_1,undefined8 *param_2)
      |                   ^~~~~~~~~~
a.c:1:39: error: unknown type name ‘undefined8’
    1 | void FUN_0010b1b0(undefined8 *param_1,undefined8 *param_2)
      |                                       ^~~~~~~~~~

However, we can use Ghidra's decompiler exporter to decompile the function AND emit a header file that will define the types and declare the functions used in the decompiled code. Unfortunately, this is a little bit awkward to do for one function. I suggest the following process:

  1. In the CodeBrowser, select the function you want to decompile in the Listing (disassembly) window.
  2. In the CodeBrowser, navigate to FileExport Program.
  3. Select "C/C++" as the Format.
  4. Ensure that the "Selection Only" checkbox is checked.
  5. In the Options dialog, check both the "Create Header File (.h)" and "Create C File (.c)" options.
  6. Click OK.

This will create a decompiled source file ls.c containing type definitions for types used in the decompiled code, such as undefined8.

Export Program dialog
Export Program dialog

And if you try to compile the generated ls.c file, it will compile successfully:

gcc -c ls.c
ls.c: In function ‘FUN_0010b1b0’:
ls.c:630:10: warning: implicit declaration of function ‘strrchr’ [-Wimplicit-function-declaration]
  630 |   __s1 = strrchr(__s,0x2e);
      |          ^~~~~~~
ls.c:1:1: note: include ‘<string.h>’ or provide a declaration of ‘strrchr’
  +++ |+#include <string.h>
    1 | typedef unsigned char   undefined;
ls.c:630:10: warning: incompatible implicit declaration of built-in function ‘strrchr’ [-Wbuiltin-declaration-mismatch]
  630 |   __s1 = strrchr(__s,0x2e);
      |          ^~~~~~~
ls.c:630:10: note: include ‘<string.h>’ or provide a declaration of ‘strrchr’
ls.c:639:12: warning: implicit declaration of function ‘__errno_location’ [-Wimplicit-function-declaration]
  639 |   piVar2 = __errno_location();
      |            ^~~~~~~~~~~~~~~~
ls.c:639:10: warning: assignment to ‘int *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
  639 |   piVar2 = __errno_location();
      |          ^
ls.c:641:11: warning: implicit declaration of function ‘strcoll’ [-Wimplicit-function-declaration]
  641 |   iVar1 = strcoll(__s1,__s2);
      |           ^~~~~~~

Hurray!

This is slightly oversimplified. You can see the compiler's warnings that we did not have declarations for strrchr, __errno_location, and strcoll. Ghidra can generate these declarations, but didn't because we used the "Selection Only" option. To get these declarations, you can use an approach like the following:

  1. Use "Export Program" to export the entire program. Under the "Options" dialog, check only the "Create Header File (.h)" option.
  2. Use "Export Program" to export only the function you care about, but this time check only the "Create C File (.c)" option.
  3. Remove type definitions from the generated C file, and instead include the generated header file.
  4. Cross your fingers.

Export Program Options
Export Program Options

Unfortunately, this header file does not always compile. Here are some errors that I obtained when trying to compile the header for /bin/ls:

In file included from FUN_0010b1b0.c:1:
FUN_0010b1b0.h:671:6: warning: conflicting types for built-in function ‘__snprintf_chk’; expected ‘int(char *, long unsigned int,  int,  long unsigned int,  const char *, ...)’ [-Wbuiltin-declaration-mismatch]
  671 | void __snprintf_chk(void);
      |      ^~~~~~~~~~~~~~
FUN_0010b1b0.h:682:5: error: ‘sigaction’ redeclared as different kind of symbol
  682 | int sigaction(int __sig,sigaction *__act,sigaction *__oact);
      |     ^~~~~~~~~
FUN_0010b1b0.h:294:26: note: previous declaration of ‘sigaction’ with type ‘sigaction’
  294 | typedef struct sigaction sigaction, *Psigaction;
      |                          ^~~~~~~~~
FUN_0010b1b0.h:695:6: warning: conflicting types for built-in function ‘dcgettext’; expected ‘char *(const char *, const char *, int)’ [-Wbuiltin-declaration-mismatch]
  695 | void dcgettext(void);
      |      ^~~~~~~~~
[truncated]

This is probably because Ghidra's type manager includes multiple definitions for some types, such as sigaction. I have opened a Ghidra issue for these problems. Hopefully the Ghidra developers will commit to fixing these problems in the header file, since it would significantly improve the usability of the decompiler output.

Sigaction types in Ghidra Type Manager
Sigaction types in Ghidra Type Manager

Here is a ghidra-scala-loader script that can do the above steps for you from the command line.

Hex-Rays

IDA/Hex-Rays has a similar feature, but it is much more straightforward. Simply select FileCreate C File, and it will generate a C file that includes all the necessary type definitions and function declarations. It does include defs.h, which is a file that contains type definitions for Hex-Rays. You can find that in the Hex-Rays SDK or elsewhere.

Powered with by Gatsby 5.0