How can I sign the executable file and verify its integrity, using ELF executable file and RSA?

495 Views Asked by At

I am studying about ELF file and code integrity, trying to make a related C code that works on Linux, uses gcc for command "make". With this code, I tried to achieve two features: First, signing executable sections. With the executable file(which is made by my code and Makefile) signtool and the RSA private key(produced by $ openssl genrsa -out private_key.pem 2048), I am trying to sign the executable file with using the command $ ./signtool sign -e <path to executable file> -k <path to private_key.pem>. During the procedure, I am going to put the signature in a new section called .signature. Second, verifying code integrity. With the signed executable file from previous feature and the RSA public key(produced by $ openssl rsa -in private_key.pem -out public_key.pem -pubout), I am trying to perform code integrity check(verifying the signature) using the command $ ./signtool verify -e <path to signed executable file> -k <path to public_key.pem>. If the check succeeds, OK will be printed; NOT_OK if the check fails, and NOT_SIGNED if the given executable has not been signed.

With these goals, I wrote a C code and a Makefile, but even though I give the executable,a properly signed ELF executable file to verify, it prints "NOT_OK"

Below is my C code, for the features that I've explained:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libelf.h>
#include <gelf.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <elf.h>

#define MAX_SECTION_NAME_SIZE 256

char* readSection(char* fileName, char* sectionName) {
    int fd;
    char *name, *buffer = NULL;
    struct stat st;
    uint8_t *mem;
    Elf *e;
    Elf64_Ehdr *ehdr = NULL;
    Elf64_Shdr *shdr = NULL;
    fd = open(fileName, O_RDONLY);
    e = elf_begin(fd, ELF_C_READ, NULL);
    fstat(fd, &st);
    mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    ehdr = (Elf64_Ehdr *)mem;
    shdr = (Elf64_Shdr *)&mem[ehdr->e_shoff];

    int shnum = ehdr->e_shnum;
    Elf64_Shdr *sh_strtab = &shdr[ehdr->e_shstrndx];
    const char *const sh_strtab_p = mem + sh_strtab->sh_offset;
    for (int i = 0; i < shnum; ++i) {
        if (!strcmp(sh_strtab_p + shdr[i].sh_name, sectionName)) {
            buffer = malloc(shdr[i].sh_size);
            if (!buffer) {
                fprintf(stderr, "Memory allocation failed\n");
                exit(1);
            }
            memcpy(buffer, &mem[shdr[i].sh_offset], shdr[i].sh_size);
            break;
        }
    }
    if (!buffer) {
        if (!strcmp(sectionName, ".signature")) {
            printf("NOT_SIGNED\n");
            exit(0);
        } else {
            fprintf(stderr, "Section not found: %s\n", sectionName);
            exit(1);
        }
    }
    close(fd);
    return buffer;
}

int add_section(char *infilepath, unsigned char *buf, size_t buf_len) {
    int ret = 0;
    char *outfilepath = NULL;
    FILE *fp = NULL;

    if (access(infilepath, F_OK) != 0) {
        ret = -4;
        goto clean;
    }

    if (buf == NULL) {
        ret = -2;
        goto clean;
    }

    if (buf_len == 0) {
        ret = -3;
        goto clean;
    }

    char *ext = "-signed";
    outfilepath = malloc(strlen(infilepath) + strlen(ext) + 1);
    if (outfilepath == NULL) {
        ret = -1;
        goto clean;
    }
    strcpy(outfilepath, infilepath);
    strcat(outfilepath, ext);

    fp = fopen("/tmp/signtool-sig", "w");
    if (fp == NULL) {
        ret = -5;
        goto clean;
    }
    if (fwrite(buf, buf_len, 1, fp) != 1) {
        ret = -6;
        goto clean;
    }
    fflush(fp);
    fclose(fp);
    fp = NULL;

    ret = execlp("objcopy", "objcopy", "--add-section", ".signature=/tmp/signtool-sig", "--set-section-flags", ".signature=noload,readonly", infilepath, outfilepath, (char *)NULL);
    if (ret < 0) {
        goto clean;
    }

clean:
    if (outfilepath) {
        free(outfilepath);
    }
    if (fp) {
        fclose(fp);
    }
    if (ret < 0) {
        fprintf(stderr, "Error: %d\n", ret);
    }
    return ret;
}

int main(int argc, char* argv[]){
    int iin, ikey;
    for(int i=2; i < 5;i+=2){
        if(!strcmp(argv[i], "-e")) iin = i+1;
        else if(!strcmp(argv[i], "-k")) ikey = i+1;
    }
    FILE* pemkey = fopen(argv[ikey], "r");
    if(!strcmp(argv[1], "sign")){
        char* in = readSection(argv[iin], ".text");
        RSA *rsa = RSA_new();
        PEM_read_RSAPrivateKey(pemkey, &rsa, NULL, NULL);
        char encrypt[RSA_size(rsa)*2];
        int integrity = RSA_private_encrypt(strlen(in), in, encrypt, rsa, RSA_PKCS1_PADDING);
        add_section(argv[iin], encrypt, RSA_size(rsa));
    }else{
        char* in = readSection(argv[iin], ".signature");
        RSA *rsa = RSA_new();
        PEM_read_RSA_PUBKEY(pemkey, &rsa, NULL, NULL);
        char decrypt[RSA_size(rsa)];
        int integrity = RSA_public_decrypt(RSA_size(rsa), in, decrypt, rsa, RSA_PKCS1_PADDING);
        char* text = readSection(argv[iin], ".text");
        printf("%d", integrity);
        printf("%s", text);
        if(integrity != -1){
            if(!strncmp(decrypt, text, strlen(text))){
                printf("OK\n");
            }else{
                printf("NOT_OK\n");
            }
        }
        else{
            printf("NOT_OK\n");
        }
    }
    return 0;
}

And below is the Makefile that I've used for the make command:

signtool: signtool.o
    gcc -o $@ signtool.o -lelf -lcrypto

signtool.o: signtool.c

With the C code, Makefile, appropriate private & public RSA key generated by the method I've introduced earlier, and a ELF executable file, I expected the result like this: For the signing, a signed ELF executable file will be given to me. and as for the verifying, with the right signed ELF executable file, "OK" will be printed. But as I proceeded with my code, although the signing looks like it works well, the verifying does not work as I think: It prints "NOT_OK" regardless of the integrity of the given file. What should I do?? PLEASE HELP...

0

There are 0 best solutions below