cglibcfunction-interposition

create a minimal XZ-backdoor


I'd like to see a minimal example of the XZ backdoor.

As I understand it:

So sth like this (does not work yet!)

main.c (acting as sshd)

#include <stdio.h>

void critical_function() {
    printf("critical_function uncompromised.\n");
}

int main() {
    critical_function();
    return 0;
}

backdoor.c

#include <stdio.h>

void compromised_critical_function() {
    printf("critical_function COMPROMISED.\n");
}

void *resolve_critical_function() {
    return compromised_critical_function;
}

__attribute__((visibility("default")))
void critical_function() __attribute__((ifunc("resolve_critical_function")));
gcc-13 -o prog main.c
gcc-13 -shared -fPIC -o libbackdoor.so backdoor.c
LD_PRELOAD=./libbackdoor.so ./prog

this prints critical_function uncompromised. but I'd like it to print critical_function COMPROMISED.


Solution

  • I don’t think this works with symbols that are statically combined into the binary. Those symbols are defined in the binary’s text section and are resolved statically:

    nm prog | grep critical_function
    
                     T critical_function
    

    To make the symbol lookup happen at runtime by the dynamic link editor (ld.so), you need to leave the symbol undefined (U in the nm nomenclature). You can do this by extracting critical_function into its own translation unit and compile it as a shared library that is linked to your binary:

    main.c:

    void critical_function(void);
    
    int main(void) {
        critical_function();
    }
    

    func.c:

    #include <stdio.h>
    
    void critical_function(void) {
        printf("critical_function uncompromised.\n");
    }
    

    backdoor.c:

    #include <stdio.h>
    
    void compromised_critical_function(void) {
        printf("critical_function COMPROMISED.\n");
    }
    
    void *resolve_critical_function(void) {
        return compromised_critical_function;
    }
    
    __attribute__((visibility("default")))
    void critical_function(void) __attribute__((ifunc("resolve_critical_function")));
    

    Compiled via (Makefile):

    CFLAGS += -fPIC
    
    all: prog libbackdoor.so
    
    prog: main.o libfunc.so
        $(CC) -o $@ $+
    
    lib%.so: %.o
        $(CC) -shared -o $@ $+
    

    And execute via:

    LD_PRELOAD=./libbackdoor.so LD_LIBRARY_PATH=$PWD ./prog
    
    critical_function COMPROMISED.
    

    For completeness, you don’t need to use the IFUNC mechanism here to achieve this; IFUNC is useful to compute the function address dynamically at runtime, but we could just override it unconditionally:

    backdoor.c (alternative)

    #include <stdio.h>
    
    void critical_function(void) {
        printf("critical_function COMPROMISED.\n");
    }