一步一步走进 Linux HOOK API(一)
最近我查阅很多参考资料.发现对于讲述 Linux HOOK API 的资料是很少,让我们这些新人难以去
走进 Linux HOOK 大门.在这里我将全面的讲述 Linux HOOK API 的全部实现过程,这个过程中我
也遇到很多坎坷,所以在这么写下这份教程.让大家都来进入 HOOK 的神秘世界.
不要认为 HOOK API 是 windows 的专利(PS.其实我以前就是这么认为的.哈哈....),其实在 Linux 中
也有 HOOK API 这样的技术,只是实现起来相对比较麻烦,首先今天主要带大家认识的是 ELF 文件,
在 Linux 中,ELF 文件主要是应用在可执行文件,重定位文件,可执行文件动态连接库。首先来看一
下 ELF Head 的定义:
PS.我们这里主要针对的是 32 位平台.有关 64 位平台相关定义请参阅/usr/include/elf.h
#define EI_NIDENT (16)
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;
e_ident: 这个成员,是 ELF 文件的第一个成员,该成员是个数字,根据上面的宏可以看出,这个程序
是个 16 字节的数据.该成员的前 4 个字节依次是 0x7F,0x45,0x4c,0x46,也 就是"\177ELF"。这是
ELF 文件的标志,任何一个 ELF 文件这四个字节都完全相同。
为了让我们更方便的使用 ELF 数据在 elf.h 中对上述数据进行了宏定义.如下:
#define EI_MAG0 0 /* File identification byte 0 index */
#define ELFMAG0 0x7f /* Magic number byte 0 *
#define EI_MAG1 1 /* File identification byte 1 index */
#define ELFMAG1 'E' /* Magic number byte 1 */
#define EI_MAG2 2 /* File identification byte 2 index */
#define ELFMAG2 'L' /* Magic number byte 2 */
#define EI_MAG3 3 /* File identification byte 3 index */
#define ELFMAG3 'F' /* Magic number byte 3 */
/* Conglomeration of the identification bytes, for easy testing as a word. */
#define ELFMAG "\177ELF"
#define SELFMAG 4
第四个字节表示 ELF 格式,1:32 位 2:64 位
#define EI_CLASS 4 /* File class byte index */
#define ELFCLASSNONE 0 /* Invalid class */
#define ELFCLASS32 1 /* 32-bit objects */
#define ELFCLASS64 2 /* 64-bit objects */
#define ELFCLASSNUM 3
第五个字节表示数据编码格式,1:小端模式 2:大端模式
#define EI_DATA 5 /* Data encoding byte index */
#define ELFDATANONE 0 /* Invalid data encoding */
#define ELFDATA2LSB 1 /* 2's complement, little endian */
#define ELFDATA2MSB 2 /* 2's complement, big endian */
#define ELFDATANUM 3
第六个字节表示文件版本,该值目前必须为 1
#define EV_CURRENT 1 /* Current version */
第七个字节表示操作系统标识:
#define EI_OSABI 7 /* OS ABI identification */
#define ELFOSABI_NONE 0 /* UNIX System V ABI */
#define ELFOSABI_SYSV 0 /* Alias. */
#define ELFOSABI_HPUX 1 /* HP-UX */
#define ELFOSABI_NETBSD 2 /* NetBSD. */
#define ELFOSABI_LINUX 3 /* Linux. */
#define ELFOSABI_SOLARIS 6 /* Sun Solaris. */
#define ELFOSABI_AIX 7 /* IBM AIX. */
#define ELFOSABI_IRIX 8 /* SGI Irix. */
#define ELFOSABI_FREEBSD 9 /* FreeBSD. */
#define ELFOSABI_TRU64 10 /* Compaq TRU64 UNIX. */
#define ELFOSABI_MODESTO 11 /* Novell Modesto. */
#define ELFOSABI_OPENBSD 12 /* OpenBSD. */
#define ELFOSABI_ARM 97 /* ARM */
#define ELFOSABI_STANDALONE 255
/*Standalone(embedded) application */
第八个字节表示 ABI 版本
#define EI_ABIVERSION 8 /* ABI version */
第九个字节表示 e_ident 中从哪开始之后未使用.
#define EI_PAD 9 /* Byte index of padding bytes */
e_type: 这个成员是 ELF 文件的类型:
1:表示此文件是重定位文件.
2:表示可执行文件.
3:表示此文件是一个动态连接库。
e_machine: 这个成员表示机器版本.具体定义参与 elf.h (篇幅问题,太长了)
e_version: 这个成员表示 ELF 文件版本,为 1
e_entry: 这个成员表示可执行文件的入口虚拟地址。此字段指出了该文件中第一条可执 行
机器指令在进程被正确加载后的内存地址!ELF 可执行文件只能被加载到固定位 置.
e_phoff: 这个成员表示程序头(Program Headers)在 ELF 文件中的偏移量。如果程序头 不
存在此值为 0。
e_shoff: 这个成员表示节头(Section Headers:)在 ELF 文件中的偏移量。如果节头不存 在
此值为 0。
e_flags: 这个成员表示处理器标志.
e_ehsize: 这个成员描述了“ELF 头”自身占用的字节数。
e_phentsize: 该成员表示程序头中的每一个结构占用的字节数。程序头也叫程序头表,可
以 被看做一个在文件中连续存储的结构数组,数组中每一项是一个结构,此字段 给出了这
个结构占用的字节大小。
e_phoff: 指出程序头在 ELF 文件中的起始偏移。
e_phnum: 此字段给出了程序头中保存了多少个结构。如果程序头中有 3 个结构则程序头
在文件中占用了 3×e_phentsize 个字节的大小。
e_shentsize: 节头中每个结构占用的字节大小。节头与程序头类似也是一个结构数组,关
于 这两个结构的定义将分别在讲述程序头和节头的时候给出。
e_shnum: 节头中保存了多少个结构。
e_shstrndx: 这是一个整数索引值。节头可以看作是一个结构数组,用这个索引值做为此
数 组的下标,它在节头中指定的一个结构进一步给出了一个“字符串表”的信息,而这 个字
符串表保存着节头中描述的每一个节的名称,包括字符串表自己也是其中的一 个节。
示例代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
int g_File
= 0;
void
*g_pData
= NULL;
void * Map(char* szFileName)
{
g_File = open(szFileName, O_RDWR);
if (g_File < 0)
{
g_File = 0;
return NULL;
}
struct stat status;
fstat(g_File, &status);
g_pData = mmap(0, status.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, g_File, 0);
if (MAP_FAILED != g_pData) {
return g_pData;
}
close(g_File);
g_pData = NULL;
g_File = 0;
return NULL;
}
void displayEhdr(Elf32_Ehdr *ehdr)
{
printf("Magic:");
int i = 0;
for(i = 0; i < EI_NIDENT;i++){
printf(" %02x",ehdr->e_ident[i]);
}
printf("\n");
printf("Version:
0x%x\n",ehdr->e_version);
printf("Entry point address:
0x%x\n",ehdr->e_entry);
printf("Start of program headers: %d (bytes into file)\n",ehdr->e_phoff);
printf("Start of section headers: %d (bytes into file)\n",ehdr->e_shoff);
printf("Flags:
%d\n",ehdr->e_flags);
printf("Size of this header:
%d (bytes)\n",ehdr->e_ehsize);
printf("Size of program headers: %d (bytes)\n",ehdr->e_phentsize);
printf("Number of program headers:
%d\n",ehdr->e_phnum);
printf("Size of section headers: %d (bytes)\n",ehdr->e_shentsize);
printf("Number of section headers:
%d\n",ehdr->e_shnum);
printf("Section header string table index:
%d\n",ehdr->e_shstrndx);
}
int main(int argc,char *argv[])
{
}
if(argc != 2){
printf("parameter error\n");
exit(0);
}
Elf32_Ehdr *ehdr = (Elf32_Ehdr *)Map(argv[1]);
if(ehdr == NULL){
perror("Map Error\n");
exit(0);
}
displayEhdr(ehdr);
一步一步走进 Linux HOOK API(二)
从上一篇的 ELF Head 之后,想必很多读者已经对 ELF 文件开始感觉不是遥不可及了,今天这一节,
主要是讲程序头(Program Headers),程序头主要是从加载执行的角度来看的,很多人想那里面到底
是什么东西呢,其实程序头就是一个结构数组,每一个头保存着对应的不同的数据,有的数据是告
诉系统把我放入内存,有的数据时告诉系统我是变量.等等...在系统加载程序的时候就要通过该程
序头来加载不同的段.
下面就来看一下程序头的结构体:
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;
p_type: 段的类型
在 elf.h 头文件中,有很详细的说明段的类型,比如 PT_LOAD 表示加载的程序段
p_offset: 文件偏移
该段在文件中的偏移。这个偏移是相对于整个文件的。
p_vaddr: 加载后的虚拟地址
该段加载后在进程空间中占用的内存起始地址。
p_paddr: 该段的物理地址
这个字段被忽略,因为在多数现代操作系统下物理地址是进程无法触及的。
p_filesz: 该段在文件中占用的字节大小
有些段可能在文件中不存在但却占用一定的内存空间,此时这个字段为 0。
p_memsz: 该段在内存中占用的字节大小
有些段可能仅存在于文件中而不被加载到内存,此时这个字段为 0。
p_flags: 段的属性
表示该段的读写执行等属性.elf.h 文件中的定义是
#define PF_X (1 << 0) /* Segment is executable */
#define PF_W (1 << 1) /* Segment is writable */
#define PF_R (1 << 2) /* Segment is readable */
#define PF_MASKOS 0x0ff00000 /* OS-specific */
#define PF_MASKPROC 0xf0000000 /* Processor-specific */
p_align: 对齐
现代操作系统都使用虚拟内存为进程序提供更大的空间,分页技术功不可没,页就成了最小
的内存分配单位,不足一页的按一页算。所以加载程序数据一般也从一页的起始地址开始,
这就属于对齐。
示例代码:
typedef struct _SegmentType_
{
unsigned int type;
char *typeName;
}SegmentType;
SegmentType segTyoe[] = {
{0,"NULL"},{1,"LOAD"},
{2,"DYNAMIC"},{3,"INTERP"},
{4,"NOTE"},{5,"SHLIB"},
{6,"PHDR"},{7,"TLS"},
{8,"NUM"},{0x60000000,"LOOS"},
{0x6474e550,"GNU_EH_FRAME"},{0x6474e551,"PT_GNU_STACK"},
{0x6474e552,"PT_GNU_RELRO"},{0x6ffffffa,"PT_SUNWBSS"},
{0x6ffffffb,"PT_SUNWSTACK"},{0x6fffffff,"PT_HISUNW"},
{0x70000000,"PT_HIOS"},{0x7fffffff,"PT_HIPROC"},