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...