Search code examples
linuxelfglibcdynamic-linking

Why editing .fini_array doesn't change course of program?


I'm trying to see how editing of .fini_array will cause change in program flow but it seems that changing this entry cause nothing to change. my main is:

void cleanup() __attribute__((destructor));
int main() {
    printf("In main\n");
    exit(1);
}
void cleanup(){
    printf("Cleanning Up\n");
}
void fini_exec(){
    printf("FINI\n");
}

running nm test: 000000000000119e T fini_exec

while cleenup: 0000000000001187 T cleanup

running objdump -s -j .fini_array test :

Contents of section .fini_array:
 3db0 20110000 00000000 87110000 00000000   ...............

using a hex editor and going to byte of 2db0 which i obtained from readelf and editing 87110000 to 9e110000(indianese)after edit running objdump again

Contents of section .fini_array:
 3db0 20110000 00000000 9e110000 00000000   ...............

then saving and re-run still cause cleanup to run which i don't know why. isn't .fini_array is supposed to be run in backward followed by .fini but it seems that there is another reference for cleanup somewhere else that's causing fini_exec to not run.


Solution

  • I made two separate executables a.out.1 and a.out.2 respectively with void cleanup() __attribute__((destructor)); and void fini_exec() __attribute__((destructor));:

    #include <stdio.h>
    #include <stdlib.h>
    
    void cleanup() __attribute__((destructor));
    //void fini_exec() __attribute__((destructor));
    
    int main() {
      printf("In main\n");
      exit(1);
    }
    
    void cleanup(){
      printf("Cleanning Up\n");
    }
    
    void fini_exec(){
      printf("FINI\n");
    }
    

    I generated the hexadecimal dumps with:

    $ hexdump a.out.1 > a.out.1.hex
    $ hexdump a.out.2 > a.out.2.hex
    

    I compared the hexadecimal dumps and could see that the offsets (0x119e and 0x1187) of the functions tagged as destructors appear in 2 places (offsets 0x580 and 0x2db8) :

    $ diff a.out.1.hex a.out.2.hex 
    55,56c55,56
    < 0000360 0003 0000 4e47 0055 5b7f a631 6798 4fc1
    < 0000370 d865 85fc 1cc6 c187 eaab 06ab 0004 0000
    ---
    > 0000360 0003 0000 4e47 0055 4bec d49a 80fc b24e
    > 0000370 0d44 2dd5 81c9 82cb fe22 f498 0004 0000
    89c89
    < 0000580 119e 0000 0000 0000 4008 0000 0000 0000
    ---
    > 0000580 1187 0000 0000 0000 4008 0000 0000 0000
    173c173
    < 0002db0 1120 0000 0000 0000 119e 0000 0000 0000
    ---
    > 0002db0 1120 0000 0000 0000 1187 0000 0000 0000
    

    So, I wrote a simple patcher tool which takes as parameters an executable name and a suite of offsets where to patch bytes (i.e. open(executable), lseek(offset), write(byte), close(executable)):

    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    int main(int ac, char *av[])
    {
    int            fd;
    char          *fname = av[1];
    unsigned char  byte;
    int            offset;
    int            i;
    int            rc;
    
      if (!fname) {
        fprintf(stderr, "Usage: %s {offseth byteh...}\n", av[0]);
        return 1;
      }
    
      fd = open(fname, O_RDWR);
      if (fd < 0) {
        fprintf(stderr, "open(%s): '%m' (%d)\n", fname, errno);
        return 1;
      }
    
      i = 2;
      while (av[i] && av[i + 1]) {
    
        offset = strtol(av[i], NULL, 0);
        byte = strtol(av[i + 1], NULL, 0);
    
        printf("Patching @0x%x: 0x%02x\n", offset, byte);
        rc = lseek(fd, offset, SEEK_SET);
        if (rc != offset) {
          fprintf(stderr, "lseek(%s): '%m' (%d)\n", fname, errno);
          return 1;
        }
    
        rc = write(fd, &byte, 1);
        if (rc != 1) {
          fprintf(stderr, "write(%s): '%m' (%d)\n", fname, errno);
          return 1;
        }
    
        i += 2;
    
      } // End while
    
      close(fd);
    
      return 0;
    
    } // main
    

    When I use it to patch both places, the destructor is changed and works as expected:

    $ gcc patch.c -o patch
    $ patch ./a.out.1 0x580 0x87 0x2db8 0x87 # cleanup()'s offset (0x1187)
    Patching @0x580: 0x87
    Patching @0x2db8: 0x87
    $ ./a.out.1
    In main
    Cleanning Up
    $ ./patch ./a.out.1 0x580 0x9e 0x2db8 0x9e # fini_exec()'s offset (0x119e)
    Patching @0x580: 0x9e
    Patching @0x2db8: 0x9e
    $ ./a.out.1
    In main
    FINI