#include<stdio.h>
#include<unistd.h>
#include<elf.h>
#include<fcntl.h>
#include<stdlib.h>
//暂定为4096,即默认被插入的代码大小 < 4096
#define pageSize 4096

Elf64_Addr program_head_vaddr;
Elf64_Xword program_head_size;
Elf64_Off entry_section_offset;
Elf64_Xword entry_section_size;

void workOutAddr(int value, int arr[]) {
  int a = value/(16*16);
  int b = value%(16*16);
  int a2 = a / (16*16);
  int b2 = a % (16*16);

  arr[0] = b;
  arr[1] = b2;
  arr[2] = a2;
}

void insert(Elf64_Addr new_entry, Elf64_Addr orig_entry, int origfile, int dirSecTail) {
  //需要修改跳转指令中的目的地址为原入口地址
  int ori_arr[3];
  workOutAddr(orig_entry, ori_arr);

  //计算出数据的地址,扔到将插入的代码中
  int dataEntry = new_entry + 45;// 73;
  int data_arr[3];
  workOutAddr(dataEntry, data_arr);

  //机器码。作用是创建一个文件写入字符串
  //其中0x5*是将压栈与弹栈保护其中原始内容
  char binary[] = {
      0x50,  //
      0x57,  //
      0x56,  //
      0x52,  //
      0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00,
      0x48, 0xc7, 0xc6, data_arr[0], data_arr[1], data_arr[2], 0x00,
      0x48, 0xc7, 0xc7, 0x01, 0x00, 0x00, 0x00,
      0x48, 0xc7, 0xc2, 0x0c, 0x00, 0x00, 0x00,
      0x0f, 0x05,
      0x5a,
      0x5e,
      0x5f,
      0x58,

      //跳转指令
      0xbd, ori_arr[0], ori_arr[1], ori_arr[2], 0x00, 0xff, 0xe5,

      //数据区域
      0x68, 0x65, 0x6c, 0x6c, 0x6f,
      0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a, 0x00
  };

  //将机器码插入
  printf("插入代码\n");
  lseek(origfile, dirSecTail, 0);
  write(origfile, binary, sizeof(binary));

  //对齐
  printf("对齐代码\n");
  char tmp[pageSize - sizeof(binary)] = {0};
  write(origfile, tmp, pageSize-sizeof(binary));
}

int main(int argc,char **argv){
  /**
   * 读取ELF文件头信息
   */
  //存储器
  char elf_ehdr[sizeof(Elf64_Ehdr)];
  Elf64_Ehdr * p_ehdr=(Elf64_Ehdr *)elf_ehdr;

  printf("打开文件并读取ELF头信息\n");
  int origfile = open(argv[1], O_RDWR);
  read(origfile, elf_ehdr, sizeof(elf_ehdr));
  //保存原入口地址
  Elf64_Addr orig_entry = p_ehdr -> e_entry;

  /**
   * 读取并修改程序头
   */
  //存储器
  char elf_phdr[sizeof(Elf64_Phdr)];
  Elf64_Phdr * p_phdr = (Elf64_Phdr *)elf_phdr;
  int perProHeadSize = sizeof(elf_phdr);

  int flag = 0, i;
  int programHeadNum = (int)p_ehdr -> e_phnum;
  for(i = 0; i < programHeadNum; i++){
    read(origfile,elf_phdr,sizeof(Elf64_Phdr));
    if(flag == 0 && \
        p_phdr -> p_vaddr <= orig_entry && \
        (p_phdr -> p_vaddr + p_phdr -> p_filesz) > orig_entry){
      //找到程序段入口
      printf("找到了目标程序段\n");
      flag = 1;
      program_head_vaddr=p_phdr->p_vaddr;
      program_head_size=p_phdr->p_filesz;

      p_phdr -> p_filesz += pageSize;
      p_phdr -> p_memsz += pageSize;

      lseek(origfile, -perProHeadSize, 1);
      write(origfile, elf_phdr, perProHeadSize);
    } else if(flag != 0){
      p_phdr-> p_offset += pageSize;
      lseek(origfile, -perProHeadSize, 1);
      write(origfile, elf_phdr, perProHeadSize);
    }
  }

  /**
   * 读取并修改节头
   */
  //将节头向下移
  //要先获得 节区的数量 以及 单个节区头部的大小
  int sectionNum = (int)p_ehdr -> e_shnum;
  int perSecHeadSize = p_ehdr -> e_shentsize;
  int secHeadSize = sectionNum * perSecHeadSize;
  //存储器
  char elf_shdr[sizeof(Elf64_Shdr)];
  Elf64_Shdr * p_shdr=(Elf64_Shdr *)elf_shdr;

  int  s_i;
  int dirSecHeadOff;
  Elf64_Addr new_entry;
  for(i = sectionNum - 1; i >= 0; i--) {
    lseek(origfile, p_ehdr -> e_shoff + i*perSecHeadSize, 0);
    read(origfile, elf_shdr, sizeof(elf_shdr));
    if(p_shdr->sh_addr+p_shdr->sh_size == program_head_vaddr+program_head_size) {
      entry_section_offset = p_shdr->sh_offset;
      entry_section_size = p_shdr->sh_size;
      new_entry = p_shdr->sh_addr + p_shdr->sh_size;
      s_i = i;

      dirSecHeadOff = p_ehdr -> e_shoff + i * sizeof(elf_shdr);
      printf("找到了目标节区的节区头的偏移:%04x\n", dirSecHeadOff);
      p_shdr -> sh_size += pageSize;
    } else {
      //改其offset值
      p_shdr -> sh_offset += pageSize;
    }

    lseek(origfile, pageSize - sizeof(elf_shdr), 1);
    write(origfile, elf_shdr, sizeof(elf_shdr));

    if(s_i != 0) {
      break;
    }
  }

  int dirSecTail = entry_section_offset + entry_section_size;
  printf("找到了目标节区的末尾:%04x\n", dirSecTail);
  int dirSecTail_dirSecHeadOff = dirSecHeadOff - dirSecTail;
  printf("找到了从 目标节区末尾 到目标节区头 的长度:%04x\n", dirSecTail_dirSecHeadOff);
  //这段内容保存着从 目标节区末尾 到目标节区头 的内容
  char con[dirSecTail_dirSecHeadOff];
  lseek(origfile, dirSecTail, 0);
  read(origfile, con, dirSecTail_dirSecHeadOff);

  //将指针移到目标节区尾部,写入这一大段内容
  printf("将这一段下移\n");
  lseek(origfile, dirSecTail + pageSize, 0);
  write(origfile, con, dirSecTail_dirSecHeadOff);
  insert(new_entry, orig_entry, origfile, dirSecTail);

  /**
   * 修改ELF头
   */
  printf("修改ELF头\n");
  p_ehdr -> e_entry = new_entry;
  p_ehdr -> e_shoff += pageSize;
  lseek(origfile, 0, 0);
  write(origfile, elf_ehdr, sizeof(elf_ehdr));

  close(origfile);

}