8

I'm wondering what are the options (aside from attempting disassembly/decompilation and reassembly/recompilation) one has when it comes to extracting a series of functions from one binary, compiled for a specific platform and architecture and including it into a separate program in order to invoke those same functions in a newly compiled program.

I've attempted the decompilation extraction route but the code produced by common tools (such as IDA Pro) is hardly compilable code, and the logic I'm looking at is obfuscated and complex to understand and rewrite/reproduce by hand.

I've seen about a dozen applications that were able to do this with the same mentioned functions I'm trying to extract and can confirm they all look to be exactly the same when decompiling all other executables, can't figure out a route to do the same.

Any pointers would be greatly appreciated.

  • 1
    Well, one way is to save the disassembled code of the functions in a .s or .asm file and compile/link your application with those files. This has 40% chance to work since most disassemblers do not produce assemblable/usable code (beware of bads and mixed data with instructions). – yaspr Dec 4 '14 at 10:16
  • It is also possible to use disassembled code as inlined assembly in manually crafted C functions. This will probably make global variables/functions handling easier. – w s Dec 4 '14 at 12:51
8

Assume you have some unknown object file. The source code of it was

#include <stdio.h>
#include <string.h>

#define NCHAR   26

void setkey(int i);
char *encrypt(char *s);
char keybuf[NCHAR];
char codepoint(char c);

int main(void) {
    char *secret;
    setkey(5);
    secret=strdup("Hello World");
    printf("%s\n", encrypt(secret));
}

void setkey(int key) {
    int i, val;
    val=key;
    for (i=0; i<NCHAR; i++) {
        keybuf[i]=val%NCHAR;
        val+=key;
    }
}

char *encrypt(char *s) {
    char *t=s;
    while (*s) {
        if (*s>='a' && *s<='z') {
            *s='a'+codepoint(*s-'a');
        } else if (*s>='A' && *s<='Z') {
            *s='A'+codepoint(*s-'A');
        }
        s++;
    }
    return t;
}

char codepoint(char c) {
    return keybuf[c];
}

The only thing you have is the object code. For reference, objdump -d original yields

Disassembly of section .text:

0000000000400490 <_start>:
  400490:   31 ed                   xor    %ebp,%ebp
...
000000000040057d <main>:
  40057d:   55                      push   %rbp
  40057e:   48 89 e5                mov    %rsp,%rbp
...
  4005b2:   c3                      retq   

00000000004005b3 <setkey>:
  4005b3:   55                      push   %rbp
  4005b4:   48 89 e5                mov    %rsp,%rbp
...
  4005ed:   48 98                   cltq   
  4005ef:   88 90 60 10 60 00       mov    %dl,0x601060(%rax)
  4005f5:   8b 45 ec                mov    -0x14(%rbp),%eax
...
  400606:   c3                      retq   

0000000000400607 <encrypt>:
  400607:   55                      push   %rbp
  400608:   48 89 e5                mov    %rsp,%rbp
...
  400677:   89 c7                   mov    %eax,%edi
  400679:   e8 21 00 00 00          callq  40069f <codepoint>
  40067e:   83 c0 41                add    $0x41,%eax
...
  40069e:   c3                      retq   

000000000040069f <codepoint>:
  40069f:   55                      push   %rbp
  4006a0:   48 89 e5                mov    %rsp,%rbp
...
  4006ac:   48 98                   cltq   
  4006ae:   0f b6 80 60 10 60 00    movzbl 0x601060(%rax),%eax
  4006b5:   5d                      pop    %rbp
  4006b6:   c3                      retq   

You want to use that code in your program.

To do this, you don't really have to understand or disassemble it, or make it compilable.

The only things you will need to find out, using IDA, a debugger, or your favourite tools, are:

  • The interesting part of the code is the part from 0x4005B3 to 0x4006B6.
  • This code references some variable at 0x601060 at two different locations, 0x4005ef and 0x4006ae. (The instructions need 2 resp. 3 bytes, so the addresses are at 0x4005b1 and 0x4006b1).
  • To use it, you have to call the function at 0x4005b3 with an integer first, then the function at 0x400607 with a string to get the encrypted string.

Let's convert the whole .text section into an array of characters, ready to be embedded into C code:

$ objdump -s -j .text original | grep '^ 4' | cut -d' ' -f3-6 | tr -d ' ' | sed 's/../0x&,/g'
0x31,0xed,0x49,0x89,0xd1,0x5e,0x48,0x89,0xe2,0x48,0x83,0xe4,0xf0,0x50,0x54,0x49,
...
0x41,0x5e,0x41,0x5f,0xc3,0x66,0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00,
0xf3,0xc3,

and write a new program to use it.

#include <stdio.h>
#include <string.h>

char code[]={
0x31,0xed,0x49,0x89,0xd1,0x5e,0x48,0x89,0xe2,0x48,0x83,0xe4,0xf0,0x50,0x54,0x49,
0xc7,0xc0,0x30,0x07,0x40,0x00,0x48,0xc7,0xc1,0xc0,0x06,0x40,0x00,0x48,0xc7,0xc7,
0x7d,0x05,0x40,0x00,0xe8,0xa7,0xff,0xff,0xff,0xf4,0x66,0x0f,0x1f,0x44,0x00,0x00,
0xb8,0x4f,0x10,0x60,0x00,0x55,0x48,0x2d,0x48,0x10,0x60,0x00,0x48,0x83,0xf8,0x0e,
0x48,0x89,0xe5,0x77,0x02,0x5d,0xc3,0xb8,0x00,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,
0xf4,0x5d,0xbf,0x48,0x10,0x60,0x00,0xff,0xe0,0x0f,0x1f,0x80,0x00,0x00,0x00,0x00,
0xb8,0x48,0x10,0x60,0x00,0x55,0x48,0x2d,0x48,0x10,0x60,0x00,0x48,0xc1,0xf8,0x03,
0x48,0x89,0xe5,0x48,0x89,0xc2,0x48,0xc1,0xea,0x3f,0x48,0x01,0xd0,0x48,0xd1,0xf8,
0x75,0x02,0x5d,0xc3,0xba,0x00,0x00,0x00,0x00,0x48,0x85,0xd2,0x74,0xf4,0x5d,0x48,
0x89,0xc6,0xbf,0x48,0x10,0x60,0x00,0xff,0xe2,0x0f,0x1f,0x80,0x00,0x00,0x00,0x00,
0x80,0x3d,0x19,0x0b,0x20,0x00,0x00,0x75,0x11,0x55,0x48,0x89,0xe5,0xe8,0x7e,0xff,
0xff,0xff,0x5d,0xc6,0x05,0x06,0x0b,0x20,0x00,0x01,0xf3,0xc3,0x0f,0x1f,0x40,0x00,
0x48,0x83,0x3d,0xc8,0x08,0x20,0x00,0x00,0x74,0x1e,0xb8,0x00,0x00,0x00,0x00,0x48,
0x85,0xc0,0x74,0x14,0x55,0xbf,0x20,0x0e,0x60,0x00,0x48,0x89,0xe5,0xff,0xd0,0x5d,
0xe9,0x7b,0xff,0xff,0xff,0x0f,0x1f,0x00,0xe9,0x73,0xff,0xff,0xff,0x55,0x48,0x89,
0xe5,0x48,0x83,0xec,0x10,0xbf,0x05,0x00,0x00,0x00,0xe8,0x24,0x00,0x00,0x00,0xbf,
0x44,0x07,0x40,0x00,0xe8,0xe7,0xfe,0xff,0xff,0x48,0x89,0x45,0xf8,0x48,0x8b,0x45,
0xf8,0x48,0x89,0xc7,0xe8,0x5e,0x00,0x00,0x00,0x48,0x89,0xc7,0xe8,0x9f,0xfe,0xff,
0xff,0xc9,0xc3,0x55,0x48,0x89,0xe5,0x89,0x7d,0xec,0x8b,0x45,0xec,0x89,0x45,0xfc,
0xc7,0x45,0xf8,0x00,0x00,0x00,0x00,0xeb,0x36,0x8b,0x4d,0xfc,0xba,0x4f,0xec,0xc4,
0x4e,0x89,0xc8,0xf7,0xea,0xc1,0xfa,0x03,0x89,0xc8,0xc1,0xf8,0x1f,0x29,0xc2,0x89,
0xd0,0x6b,0xc0,0x1a,0x29,0xc1,0x89,0xc8,0x89,0xc2,0x8b,0x45,0xf8,0x48,0x98,0x88,
0x90,0x60,0x10,0x60,0x00,0x8b,0x45,0xec,0x01,0x45,0xfc,0x83,0x45,0xf8,0x01,0x83,
0x7d,0xf8,0x19,0x7e,0xc4,0x5d,0xc3,0x55,0x48,0x89,0xe5,0x48,0x83,0xec,0x20,0x48,
0x89,0x7d,0xe8,0x48,0x8b,0x45,0xe8,0x48,0x89,0x45,0xf8,0xeb,0x71,0x48,0x8b,0x45,
0xe8,0x0f,0xb6,0x00,0x3c,0x60,0x7e,0x2c,0x48,0x8b,0x45,0xe8,0x0f,0xb6,0x00,0x3c,
0x7a,0x7f,0x21,0x48,0x8b,0x45,0xe8,0x0f,0xb6,0x00,0x83,0xe8,0x61,0x0f,0xbe,0xc0,
0x89,0xc7,0xe8,0x58,0x00,0x00,0x00,0x83,0xc0,0x61,0x89,0xc2,0x48,0x8b,0x45,0xe8,
0x88,0x10,0xeb,0x35,0x48,0x8b,0x45,0xe8,0x0f,0xb6,0x00,0x3c,0x40,0x7e,0x2a,0x48,
0x8b,0x45,0xe8,0x0f,0xb6,0x00,0x3c,0x5a,0x7f,0x1f,0x48,0x8b,0x45,0xe8,0x0f,0xb6,
0x00,0x83,0xe8,0x41,0x0f,0xbe,0xc0,0x89,0xc7,0xe8,0x21,0x00,0x00,0x00,0x83,0xc0,
0x41,0x89,0xc2,0x48,0x8b,0x45,0xe8,0x88,0x10,0x48,0x83,0x45,0xe8,0x01,0x48,0x8b,
0x45,0xe8,0x0f,0xb6,0x00,0x84,0xc0,0x75,0x84,0x48,0x8b,0x45,0xf8,0xc9,0xc3,0x55,
0x48,0x89,0xe5,0x89,0xf8,0x88,0x45,0xfc,0x0f,0xbe,0x45,0xfc,0x48,0x98,0x0f,0xb6,
0x80,0x60,0x10,0x60,0x00,0x5d,0xc3,0x66,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00,
0x41,0x57,0x41,0x89,0xff,0x41,0x56,0x49,0x89,0xf6,0x41,0x55,0x49,0x89,0xd5,0x41,
0x54,0x4c,0x8d,0x25,0x38,0x07,0x20,0x00,0x55,0x48,0x8d,0x2d,0x38,0x07,0x20,0x00,
0x53,0x4c,0x29,0xe5,0x31,0xdb,0x48,0xc1,0xfd,0x03,0x48,0x83,0xec,0x08,0xe8,0x25,
0xfd,0xff,0xff,0x48,0x85,0xed,0x74,0x1e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00,
0x4c,0x89,0xea,0x4c,0x89,0xf6,0x44,0x89,0xff,0x41,0xff,0x14,0xdc,0x48,0x83,0xc3,
0x01,0x48,0x39,0xeb,0x75,0xea,0x48,0x83,0xc4,0x08,0x5b,0x5d,0x41,0x5c,0x41,0x5d,
0x41,0x5e,0x41,0x5f,0xc3,0x66,0x66,0x2e,0x0f,0x1f,0x84,0x00,0x00,0x00,0x00,0x00,
0xf3,0xc3,
};

// we don't know how large it has to be. From the disassembly,
// we learned 26 bytes should be enough. Better make it larger and be
// on the safe side.
char buffer[1000];

int main(void) {

    void (*f1)(int)=(void *)(code-0x400490+0x4005b3);
    char* (*f2)(char *)=(void *)(code-0x400490+0x400607);

    *(int *)(code-0x400490+0x4005f1)=(int)(long)&buffer;
    *(int *)(code-0x400490+0x4006b1)=(int)(long)&buffer;

    (*f1)(5);
    printf("%s\n", (*f2)(strdup("Hello World")));
    printf("%s\n", (*f2)(strdup("Some other String")));
}

Since the original text segment started at 0x400490, and our functions started at 0x4005b3 and 0x400607, we calculate the new offsets from the start of the code array. And since we found two references to a buffer outside the original program, we patch them to point to our buffer instead. Note we don't have to do anything with the call from encrypt to codepoint, since the code is position-independent anyway (call the function at 0x21 bytes from here).

Now, don't forget to make the data/stack segment executable when we compile the program:

$ cc -zexecstack -g -o copy copy.c
$ ./copy
Oziix Lxmiu
Rxnz xwozm Rwmtsj

Of course, there's still some problems to solve:

  • You have to find everything that might get called from your functions, and include all of it into your code.
  • You have to find all references to data outside your code, and patch the pointers accordingly; if the data contains pointers, patch them as well.
  • If your code calls any library functions, you'll have to do the relocation. Fortunately, the relocation table of the (ELF/PE) binary shows you where to do this.

The upside of this approach is it can even work between operating systems, if they use the same processor architecture. You might need to write wrapper functions around the calls however, if the ABI is different.

If you want to get fancy, don't call the functions directly; embed an emulator engine in your program and have the emulator execute the code. This can help you a lot to detect if some code is missing, or where references to external data are. Once you get it running with an emulator, obviously, you can leave it in your final program, which may even enable you to use some ARM routines in your new PC program. Or vice versa.

You can download the files at https://mega.co.nz/#!8dR0TZhA!Z4DdQ07JCUzV5nJiJ79PZhHbiKDu9QZEw10IXr7ssuI

  • buffer address used by 0x4005ef should be 0x4005f1 not 0x4005b1 – Scy Apr 3 '17 at 15:13
1

The only project that claimed something like this possible (with significant amount of semi-automated and manual work) is McSema. See the source and examples, it may be useful.

  • Will check it out... so the outcome would still be binary/linkable code that I can compile against instead of decompiled source code correct? Any tips for making the code cross-platform? Would I be able to simply build my source for different platform/architectures linking to this single extracted binary? – Roberto Andrade Dec 4 '14 at 14:42
  • If you use machine code/assembly, there is no chance your code is portable. You'll have to convert the instructions yourself or use a binary translator (ex: Intel's Houdini which converts NEON to SSE). Or, you'll have to adapt decompiled code which sounds like a lot of work. – yaspr Dec 4 '14 at 17:08
  • @yaspr See their demos. Looks very impressive. – w s Dec 4 '14 at 18:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.