/***************************************************************************/
/*                                                                         */
/*  intercept.c - Optional functions to allow injectso injected libraries  */
/*                to easily intercept calls to shared library functions    */
/*                                                                         */
/*  Copyright (C) 2000 Shaun Clowes                                        */
/*                                                                         */
/*  This program is free software; you can redistribute it and/or modify   */
/*  it under the terms of version 2 of the GNU General Public License as   */
/*  published by the Free Software Foundation;                             */
/*                                                                         */
/*  This program is distributed in the hope that it will be useful,        */
/*  but WITHOUT ANY WARRANTY; without even the implied warranty of         */
/*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          */
/*  GNU General Public License for more details.                           */
/*                                                                         */
/*  You should have received a copy of the GNU General Public License      */
/*  along with this program; if not, write to the Free Software            */
/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.              */
/*                                                                         */
/***************************************************************************/

#include <config.h>
#include <stdio.h>
#include <elf.h>
#include <link.h>
#include <stdarg.h>
#include <intercept.h>

/* Defines                                                                 */
#define RT_REL      0     /* PLT relocation entries have implicit addends    */
#define RT_RELA   1     /* PLT relocation entires have explicit addends    */
#if CPU_SPARC
   #define UNRESOLVED   0xffffffff /* Unresolved PLT entry                 */
   #define NOP            0x01000000 /* Sparc NOP instruction                */
   #define BOT10        0x000003ff /* Bottom 10 bits of a field            */
   #define DISP22       0x3FFFFF   /* Disp22 field of Sparc opcode         */
   #define IMM22        DISP22     /* Imm22 field of Sparc opcode          */
   #define SIMM13         0x1fff     /* Simm13 field of Sparc opcode         */
   #define SETHIG1      0x03000000 /* sethi  %hi(imm22), %g1 Sparc opcode  */
   #define JMPG1        0x81C06000 /* jmp  %g1 + simm13 Sparc opcode       */
   #define BRANCHREL    0x30800000 /* b,a  disp32 Sparc opcode             */
   #define ISRESOLVED(y) (((unsigned long *) y)[2] != NOP)
   #define JMPADDR(y) \
      ((unsigned long) ((((unsigned long *) y)[1] & IMM22) << 10) + \
       (unsigned long) (((unsigned long *) y)[2] & SIMM13))
#endif

/* Private Function Prototypes                                             */
void intercept_debug(char *sFmt, ...);
int intercept_init();
int intercept_override(SIntercept *ptEntry);
Elf32_Dyn *intercept_find_dynamic_entry(Elf32_Dyn *ptBase, Elf32_Sword d_tag);
Elf32_Rel *intercept_find_rel(char *sSym);
#if CPU_SPARC
   unsigned long intercept_read_branch(void *pvPltEntry);
   int intercept_patch_jump(void *pvPltEntry, void *pvNewAddr);
#endif

/* Globals                                                                 */
int iInterceptDebug = 0;
int iRelType;              /* PLT relocation type (RT_REL and RT_RELA)     */
void *ptBaseRel;           /* Base address of PLT relocations              */
unsigned int uTotalRelSz;  /* Total size of PLT relocation section         */
unsigned int uRelSz;       /* Size of each relocation entry                */
unsigned int uNoRel;       /* Number of relocations                        */
Elf32_Sym *ptBaseSym;      /* Base address of the symbol table             */
char *sBaseStr;            /* Base address of the strings table            */
#if CPU_SPARC
   void *pvPltTrampoline = 0x0; /* Address of the dynamic linker           */
                                /* trampoline in the PLT                   */
#endif

/* intercept_begin - This is the function that injectso attempts to call   */
/* after having injected a library (by default). It is given a pointer to  */
/* the dynamic segment of the executable which should be more than enough  */
/* information to fiddle with the executable.                              */
int intercept_begin(Elf32_Dyn *ptElfDyn) {
   int i;
   struct r_debug *ptDebug;
   Elf32_Dyn *ptFoundDyn;
   Elf32_Rel *ptRel;
   char *sSym;

   intercept_debug("Received intercept begin with %p\n", ptElfDyn);

   /* Dump the contents of the dynamic section, debugging                  */
   if (iInterceptDebug)
   {
      intercept_debug("Contents of dynamic section:\n");
      i = 0;
      ptDebug = NULL;
      while (ptElfDyn[i].d_tag != DT_NULL) {
         intercept_debug("Tag %d Value 0x%08x\n", 
                         ptElfDyn[i].d_tag, ptElfDyn[i].d_un.d_val);
         i++;
      }
   }

   /* Get the base address of the symbol table                             */
   ptFoundDyn = intercept_find_dynamic_entry(ptElfDyn, DT_SYMTAB);
   if (!ptFoundDyn) {
      intercept_debug("Could not find symbol table, exiting\n");
      return(0);
   }
   intercept_debug("Symbol table is at %p\n", (void *) ptFoundDyn->d_un.d_ptr);
   ptBaseSym = (Elf32_Sym *) ptFoundDyn->d_un.d_ptr;

   /* Get the base address of the string table                             */
   ptFoundDyn = intercept_find_dynamic_entry(ptElfDyn, DT_STRTAB);
   if (!ptFoundDyn) {
      intercept_debug("Could not find string table, exiting\n");
      return(0);
   }
   intercept_debug("String table is at %p\n", (void *) ptFoundDyn->d_un.d_ptr);
   sBaseStr = (char *) ptFoundDyn->d_un.d_ptr;

   /* Determine what type of PLT relocation entries are in this file       */
   ptFoundDyn = intercept_find_dynamic_entry(ptElfDyn, DT_PLTREL);
   if (!ptFoundDyn) {
      intercept_debug("Could not find type of PLT relocation entries, exiting\n");
      return(0);
   }
   iRelType = (ptFoundDyn->d_un.d_val == DT_REL ? RT_REL : RT_RELA);
   intercept_debug("PLT relocations are %s entries\n", 
                   (iRelType == RT_REL ? "REL" : "RELA"));

   /* Find the size (in bytes) of the PLT relocations                      */
   ptFoundDyn = intercept_find_dynamic_entry(ptElfDyn, DT_PLTRELSZ);
   intercept_debug("Size of relocation table is %u\n", ptFoundDyn->d_un.d_val);
   uTotalRelSz = ptFoundDyn->d_un.d_val;

   /* Now find the address of the relocations                              */
   ptFoundDyn = intercept_find_dynamic_entry(ptElfDyn, DT_JMPREL);
   intercept_debug("Relocations are at %p\n", (void *) ptFoundDyn->d_un.d_ptr);
   ptBaseRel = (void *) ptFoundDyn->d_un.d_ptr;
 
   /* Get the size of each relocation to determine how many relocations    */
   /* are present                                                          */
   ptFoundDyn = intercept_find_dynamic_entry(ptElfDyn, (iRelType ? DT_RELAENT : DT_RELENT));
   uRelSz = ptFoundDyn->d_un.d_val;
   uNoRel = uTotalRelSz / uRelSz;
   intercept_debug("There are %d relocations in this file\n", uNoRel);

   /* Loop through all the relocations (debug)                             */
   if (iInterceptDebug)
   {
      ptRel = (Elf32_Rel *) ptBaseRel;  /* We treat all relocations as     */
                                        /* having implicit addends since   */
                                        /* we're not interested in any     */
                                        /* addends                         */
      intercept_debug("Dump of PLT relocations:\n");
      for (i = 0; i < uNoRel; i++) {
         if (ELF32_R_SYM(ptRel->r_info))
            sSym = sBaseStr + ptBaseSym[(ELF32_R_SYM(ptRel->r_info))].st_name;
         else
            sSym = "";
         intercept_debug("Offset %p Type %d Name %s\n", (void *) ptRel->r_offset,
                         ELF32_R_TYPE(ptRel->r_info), sSym);
         ptRel = (Elf32_Rel *) ((char *) ptRel + uRelSz);
      }
   }

   /* Call intercept_init to find all the required relocations etc         */
   intercept_init();

   /* Now actually patch the PLT to intercept the functions                */
   intercept_override(NULL);

   intercept_debug("Leaving intercept_begin\n");
   return(0);
}

/* intercept_init - Processes the pptInterceptFuncs array to find all the  */
/* relocation entries and other initialization required                    */
int intercept_init() {
   int i = 0;
   Elf32_Rel *ptRel;
   SIntercept *ptInterceptFunc = 0x0;

   intercept_debug("Searching for functions to be intercepted:\n");
   /* Roll through the list of functions to be intercepted and find their  */
   /* relocation entries (if they exist at all)                            */
   while (pptInterceptFuncs[i]) {
      ptInterceptFunc = pptInterceptFuncs[i];
      i++;

      if (ptInterceptFunc->iFlags & int_ignore)
         continue;

      /* We use intercept_find_relocation to find the relocation for the   */
      /* symbol. It always returns a REL object, not a RELA object since   */
      /* the addend should always be 0 for function relocations. If the    */
      /* addend isn't 0 it won't return that relocation at all.            */
      ptRel = intercept_find_rel(ptInterceptFunc->sFuncName);
      if (!ptRel) {
         intercept_debug("Could not find relocation for function %s\n",
                         ptInterceptFunc->sFuncName);
         ptInterceptFunc->pvRelAddr = 0x0;
         continue;
      }
      ptInterceptFunc->pvRelAddr = (void *) ptRel->r_offset;
      intercept_debug("Found relocation for function %s at %p which points "
                      "to %p\n", ptInterceptFunc->sFuncName, ptRel,
                      ptInterceptFunc->pvRelAddr);
   }

   return(0);
}

/* intercept_override - Patch the PLT to redirect the functions in         */
/* tInterceptFuncs (if ptEntry = NULL) or one specified function as        */
/* specified                                                               */
int intercept_override(SIntercept *ptEntry) {
   SIntercept *ptInterceptFunc = 0x0, *ptNextInterceptFunc = 0x0;
   int iInterceptEntry = 0;
   int iOneShot = 0;
   void *pvOldAddr;

   intercept_debug("Overriding functions nominated for interception:\n");
   /* Roll through the list of functions to be intercepted                 */
   if (!ptEntry)
      ptNextInterceptFunc = pptInterceptFuncs[iInterceptEntry];
   else
      ptNextInterceptFunc = ptEntry;

   while ((ptInterceptFunc = ptNextInterceptFunc) && 
          (!iOneShot)) {
      ptNextInterceptFunc = pptInterceptFuncs[++iInterceptEntry];

      if ((ptInterceptFunc->iFlags & int_ignore) ||
          (ptInterceptFunc->pvRelAddr == 0x0))
         continue;

      /* Don't loop if we were only processing one override                */
      if (ptEntry)
         iOneShot = 1;
      
#if CPU_IA32
      /* The relocation address simply points to an address in the GOT     */
      /* where the resolved address of the function is stored, all we      */
      /* have to do is modify that address to point to where we want       */
      if (*((void **) ptInterceptFunc->pvRelAddr) == ptInterceptFunc->pvNewFunc) {
         intercept_debug("Function %s is currently patched correctly, no action required\n",
                         ptInterceptFunc->sFuncName);
         continue;
      }

      pvOldAddr = *((void **) ptInterceptFunc->pvRelAddr);
      intercept_debug("Redirecting %s from %p to %p\n", 
                      ptInterceptFunc->sFuncName, 
                      pvOldAddr, 
                      ptInterceptFunc->pvNewFunc);

      if (ptInterceptFunc->ppvOldAddr)
         *(ptInterceptFunc->ppvOldAddr) = pvOldAddr;

      *((void **) ptInterceptFunc->pvRelAddr) = ptInterceptFunc->pvNewFunc;
#elif CPU_SPARC
      /* Its nowhere near as easy on Sparc since the relocation points     */
      /* to a PLT routine which we have to properly analyse                */

      /* PLT routines come in two basic forms, one for a symbol that has   */
      /* not yet been lazily resolved, the other for when the symbol has   */
      /* been resolved.                                                    */
      /* Unresolved: sethi  %hi(0x21000), %g1                              */
      /*             b,a   0x20744 <_PROCEDURE_LINKAGE_TABLE_>             */
      /*             nop                                                   */
      /* Resolved:   sethi  %hi(0x21000), %g1                              */
      /*             sethi  %hi(0x500b2400), %g1                           */
      /*             jmp  %g1 + 0x78 ! 0x500b2478 <__sleep>                */
      /* (Although in Solaris there is a 4th instruction which is always   */
      /* a nop)                                                            */
      if (!ISRESOLVED(ptInterceptFunc->pvRelAddr)) {
         /* The relocation hasn't yet been lazily resolved and instead     */
         /* points to the dynamic linker trampoline at the start of the    */
         /* PLT                                                            */
         intercept_debug("Symbol %s has not been resolved\n", ptInterceptFunc->sFuncName);

         if (!pvPltTrampoline) {
            /* Work out the address of the trampoline (we only need to do  */
            /* this once)                                                  */
            /* TODO: We should check this is a branch always with annul    */
            /* instruction instead of assuming                             */
            pvPltTrampoline = (void *) intercept_read_branch(ptInterceptFunc->pvRelAddr);
            intercept_debug("PLT trampoline found at 0x%08x\n", pvPltTrampoline);   
         }

         /* We just set the old address of the function to be 0xffffffff   */
         /* to indicate the entry hasn't been resolved. The injected       */
         /* library must be very careful to correct the PLT entry before   */
         /* attempting to call the old routine. This is easily achieved    */
         /* by calling intercept_fix_unresolved() passing the address of   */
         /* the tInterceptFuncs array entry                                */
         pvOldAddr = (void *) UNRESOLVED;
      } else {
         /* The relocation has been resolved correctly                     */
         pvOldAddr = (void *) JMPADDR(ptInterceptFunc->pvRelAddr);
         intercept_debug("Symbol %s has been resolved to 0x%08x\n", 
                         ptInterceptFunc->sFuncName, pvOldAddr);
      }
   
      /* Now patch the PLT entry                                           */
      if (pvOldAddr == ptInterceptFunc->pvNewFunc) {
         intercept_debug("Function %s is currently patched correctly, no action required\n",
                         ptInterceptFunc->sFuncName);
         continue;
      }

      if (ptInterceptFunc->ppvOldAddr) 
         *(ptInterceptFunc->ppvOldAddr) = pvOldAddr;

      /* if (pvOldAddr != (void *) UNRESOLVED) */
         intercept_patch_jump(ptInterceptFunc->pvRelAddr, ptInterceptFunc->pvNewFunc);

#endif
   }

   return(0);
}

#if 0
   /* There are many different approaches to calling through to the        */
   /* function we've overridden. One is simply not to do so and            */
   /* just call the relevant function, e.g in a function that              */
   /* overrides sleep(), just call sleep(). This is an effective           */
   /* way and it effectively bypasses the processes PLT. One other         */
   /* method is to store a pointer to the functions GOT entry and          */
   /* another pointer to the old functions location, when first            */
   /* overriding the function the value of the GOT entry is stored         */
   /* in the second pointer which is then called to access the old         */
   /* function. After calling the old function the program should          */
   /* check the value now in the GOT entry, if it isn't the address        */
   /* of the overriding function the function has just been lazy           */
   /* bound so the address in the GOT is now the address of the            */
   /* overridden function. The later method is performed here              */
   /* Note that by saving the resolved address (which we can call later    */
   /* instead of the PLT routine we prevent having to go through the       */
   /* following:                                                           */
   /*  1. Do what we need to do                                            */
   /*  2. Restore PLT entry                                                */
   /*  3. Call PLT entry                                                   */
   /*  4. Overwrite PLT entry                                              */
   /* Except on Sparc where we need to do that if the PLT entry has not    */
   /* been resolved (since it sets a register to allow the resolver to     */
   /* work)                                                                */
#endif

Elf32_Dyn *intercept_find_dynamic_entry(Elf32_Dyn *ptBaseDyn, Elf32_Sword d_tag) {
   int i;

   i = 0;
   while (ptBaseDyn[i].d_tag != DT_NULL) {
      if (ptBaseDyn[i].d_tag == d_tag)
         return(&(ptBaseDyn[i]));
      i++;
   }

   return(NULL);
}

/* intercept_find_rel() - Find the relocation for a specified              */
/* symbol. This function always returns a pointer to an                    */
/* implicit addend relocation (even if what it points to is                */
/* an excplicit addend relocation) because function                        */
/* relocations shouldn't have addends (and if the addend                   */
/* isn't 0 they will not be returned by this function)                     */
Elf32_Rel *intercept_find_rel(char *sSym) {
   int i;
   void *pvRel;
   char *sFoundSym;

   pvRel = ptBaseRel;
   i = 0;
   while (i < uNoRel) {
      /* Is this relocation the one we're looking for? */
      if (ELF32_R_SYM(((Elf32_Rel *) pvRel)->r_info)) {
         sFoundSym = sBaseStr + ptBaseSym[ELF32_R_SYM(((Elf32_Rel *) pvRel)->r_info)].st_name;
         if (!strcmp(sSym, sFoundSym)) {
            if ((iRelType == RT_RELA) &&
                (((Elf32_Rela *) pvRel)->r_addend))
               intercept_debug("Symbol %s found, but addend is set to %d :(\n",
                               sSym, ((Elf32_Rela *) pvRel)->r_addend);
            else
               return((Elf32_Rel *) pvRel);
         }
      }

      /* On to the next relocation                                         */
      pvRel = (void *) ((char *) pvRel + uRelSz); 
      i++;
   }

   return(NULL);
}

#if CPU_SPARC
/* intercept_fix_unresolved() - On Sparc when we patch the PLT entry for   */
/* functions that have not yet been lazily bound we effectively make it    */
/* impossible to call the old function except by restoring the old PLT     */
/* entry (which points to the dynamic linker trampoline)                   */
int intercept_fix_unresolved(SIntercept *ptEntry) {
   SIntercept *ptInterceptFunc = 0x0, *ptNextInterceptFunc = 0x0;
   long lOffset;
   int iOneShot = 0;
   int iInterceptEntry = 0;
   unsigned long *pulBranchInstruction;
   unsigned long *pulNopInstruction;

   intercept_debug("Fixing PLT entries for unresolved functions:\n");
   /* Roll through the list of functions to be intercepted                 */
   if (!ptEntry)
      ptNextInterceptFunc = pptInterceptFuncs[iInterceptEntry];
   else
      ptNextInterceptFunc = ptEntry;

   while ((ptInterceptFunc = ptNextInterceptFunc) && 
          (iOneShot == 0) && 
          (ptInterceptFunc->sFuncName)) {
      ptNextInterceptFunc = pptInterceptFuncs[++iInterceptEntry];

      if ((ptInterceptFunc->iFlags & int_ignore) ||
          (ptInterceptFunc->pvRelAddr == 0x0) ||
          (!ptInterceptFunc->ppvOldAddr) ||
          ((*ptInterceptFunc->ppvOldAddr) != (void *) UNRESOLVED))
         continue;

      /* If a specific entry was specified we don't want to loop           */
      if (ptEntry)
         iOneShot = 1;

      if (!pvPltTrampoline) {
         intercept_debug("Argh! intercept_fix_unresolved called for unresolved "
                         "function %s but we don't know the address of the "
                         "PLT trampoline\n", ptInterceptFunc->sFuncName);
         continue;
      }

      /* We need to restore the PLT entry to look like this:               */
      /*             sethi  %hi(0x21000), %g1                              */
      /*             b,a   0x20744 <_PROCEDURE_LINKAGE_TABLE_>             */
      /*             nop                                                   */

      pulBranchInstruction = &(((unsigned long *) ptInterceptFunc->pvRelAddr)[1]);
      pulNopInstruction = &(((unsigned long *) ptInterceptFunc->pvRelAddr)[2]);

      *pulNopInstruction = NOP;
      lOffset = (long) pvPltTrampoline - (long) pulBranchInstruction;
      lOffset = lOffset >> 2; /* Divide by 4                               */
      *pulBranchInstruction = BRANCHREL | (lOffset & DISP22);

      /* Now set the old address to the address of the good PLT entry      */
      *(ptInterceptFunc->ppvOldAddr) = ptInterceptFunc->pvRelAddr;

      intercept_debug("Restored PLT entry for %s\n", ptInterceptFunc->sFuncName);

      /* If the user only wanted to fix one function's PLT break here      */
      if (ptEntry)
         break;
   }

   return(0);
}

/* intercept_read_branch() - Work out the target address of an             */
/* unconditional branch in a Plt routine for Sparc                         */
unsigned long intercept_read_branch(void *pvPltEntry) {
   unsigned long ulInstruction;
   long lDisplacement;

   ulInstruction = ((unsigned long *) pvPltEntry)[1];
   lDisplacement = ulInstruction & DISP22;
   
   /* Sign extend the displacement                                         */
   lDisplacement = (lDisplacement << (sizeof(lDisplacement) * 8 - 22)) >>
                   (sizeof(lDisplacement) * 8 - 22);

   /* Multiply by 4 (the field is specified in words)                      */
   lDisplacement = lDisplacement * 4;

   /* Now add to the location of the instruction to get the address to     */
   /* which the address refers                                             */
   return (((unsigned long) ((char *) pvPltEntry + 4)) + lDisplacement);
}

/* intercept_patch_jump() - Patch a PLT entry to jump to a new address     */
int intercept_patch_jump(void *pvPltEntry, void *pvNewAddr) {
   unsigned long *pulSetHiInstruction;
   unsigned long *pulJmpInstruction;

   pulSetHiInstruction = &(((unsigned long *) pvPltEntry)[1]);
   pulJmpInstruction = &(((unsigned long *) pvPltEntry)[2]);

   *pulSetHiInstruction = SETHIG1 | (((unsigned long) pvNewAddr >> 10) & IMM22);
   *pulJmpInstruction = JMPG1 | (((unsigned long) pvNewAddr & BOT10) & SIMM13);

   return(0);
}
#endif

/* intercept_debug() - If debugging is on (iInterceptDebug == 1) print a   */
/* debugging message on stderr                                             */
void intercept_debug(char *sFmt, ...) {
   va_list tAp;

   if (!iInterceptDebug)
      return;

   va_start(tAp, sFmt);

   fputs("intercept_debug: ", stderr);

   vfprintf(stderr, sFmt, tAp);

   return;
}
   
