linux下實現在程式執行時的函式替換(熱補丁)
宣告:以下的程式碼成果,是參考了網上的injso技術,在本文的最後會給出地址,同時非常感謝injso技術原作者的分享。
但是injso文章中的程式碼存在一些問題,所以後面出現的程式碼是經過作者修改和檢測的。也正因為這些錯誤,加深了我的學習深度。
最近因為在學習一些除錯的技術,但是很少有提到如何在函式執行時實現函式替換的。
為什麼會想到這一點?因為在學習除錯時,難免會看到一些核心方面的除錯技術,核心中的除錯有一個kprobe,很強大,可以實現執行時的函式替換。其原理就是hook,鉤子,但是學習了這個kprobe之後會發現,kprobe內部有檢測所要鉤的函式是不是屬於核心空間,必須是核心函式才能實現替換。而實際上,我的工作大部分還是在應用層的,所以想要實現應用程式的熱補丁技術。
一些基礎的知識這邊的就不展開了,需要的基礎有,elf檔案格式,ptrace,waitpid,應用程式間通訊時的訊號,彙編。
- 1、elf檔案載入過程
elf簡單地說是由以下四部分組成的,elf檔案頭,program header和section header,內容。其中program header是執行時使用的,而section header並不會被載入程序序執行空間,但他們可以在編譯時被指定該段的載入地址等資訊,當然一般這個連結指令碼.lds是由gcc預設的。
第一步,載入elf檔案頭,檢驗檔案型別版本等,重要的是找到program header的地址和header的個數,如果聯結器指令碼是預設的,那麼elf檔案頭會被載入在0x804800地址處。
第二步,載入program header,接著掃描program header,找到一個型別為PT_INTERP的program header,這個header裡面放著的是有關直譯器的地址,這時候將直譯器程式的elf檔案頭載入進來。一般是這樣:
INTERP 0x000134 0x08048134 0x08048134 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
第三步,掃描program header,如果型別為PT_LOAD,則將該段載入進來。
第四步,判斷是否需要直譯器程式,如果需要,把直譯器程式載入進來,並把程式入口設定為直譯器程式的地址。否則是應用程式本身的入口。反彙編為_start標號。
第五步,設定命令列傳入的引數等應用程式需要的資訊。
第六步,直譯器程式開始執行,載入程式需要的庫,填寫重定向符號表中的地址資訊。
- 2.elf檔案動態連結過程
上一步,直譯器程式根據program header已經將應用程式的段都載入進記憶體了,接下來再掃描program header,找到型別為PT_DYNAMIC,這裡麵包含了很多由section header描述的內容,包括重定向表,符號表,字串表等等。直譯器需要這個段描述的一些資訊。
DT_NEEDED描述了所需要的動態庫名稱,DT_REL描述了重定位表地址,DT_JMPREL描述了重定位表地址(這個表是懶惰連結使用的),DT_PLTGOT全域性偏移表地址。
此時直譯器程式就可以根據所需要的動態庫,將其載入進記憶體。每一個被載入進來的庫的相關資訊會被記錄在link_map結構中,這個結構是一個連結串列,儲存了所有的動態資訊。
其中,全域性偏移表got,got[0]儲存了PT_DYNAMIC的起始地址,got[1]儲存link_map的地址,而link_map中就可以找到PT_DYNAMIC的起始地址,和下一個或者上一個共享檔案或者可執行檔案的link_map地址。
DT_REL這個重定向表中的符號必須在此時就被解析完成。
而DT_JMPREL這個重定向表中的符號可以在執行時再解析。
所有的庫和符號全部解析完成之後,直譯器程式就會把控制權交給可執行檔案的_start。程式開始執行。
- 3.替換函式和被替換函式
被替換程式原始碼。
#include <stdio.h>
#include <time.h>
int main()
{
while(1){
sleep(10);
printf("%d : original\n",time(0));
}
}
替換新庫程式碼。
#include <stdio.h>
int newmyprint()
{
write(1,"hahahahahahaha",14);
return 0;
}
夠簡單明瞭吧,如果替換成功,目標程式將會一直輸出“哈哈哈哈哈哈”。
- 4.功能函式
ptrace相關程式碼:
/* 讀程序暫存器 */
void ptrace_readreg(int pid, struct user_regs_struct *regs) { if(ptrace(PTRACE_GETREGS, pid, NULL, regs)) printf("*** ptrace_readreg error ***\n"); /*printf("ptrace_readreg\n"); printf("%x\n",regs->ebx); printf("%x\n",regs->ecx); printf("%x\n",regs->edx); printf("%x\n",regs->esi); printf("%x\n",regs->edi); printf("%x\n",regs->ebp); printf("%x\n",regs->eax); printf("%x\n",regs->xds); printf("%x\n",regs->xes); printf("%x\n",regs->xfs); printf("%x\n",regs->xgs); printf("%x\n",regs->orig_eax); printf("%x\n",regs->eip); printf("%x\n",regs->xcs); printf("%x\n",regs->eflags); printf("%x\n",regs->esp); printf("%x\n",regs->xss);*/ }
/* 寫程序暫存器 */
void ptrace_writereg(int pid, struct user_regs_struct *regs)
{
/*printf("ptrace_writereg\n");
printf("%x\n",regs->ebx);
printf("%x\n",regs->ecx);
printf("%x\n",regs->edx);
printf("%x\n",regs->esi);
printf("%x\n",regs->edi);
printf("%x\n",regs->ebp);
printf("%x\n",regs->eax);
printf("%x\n",regs->xds);
printf("%x\n",regs->xes);
printf("%x\n",regs->xfs);
printf("%x\n",regs->xgs);
printf("%x\n",regs->orig_eax);
printf("%x\n",regs->eip);
printf("%x\n",regs->xcs);
printf("%x\n",regs->eflags);
printf("%x\n",regs->esp);
printf("%x\n",regs->xss);
if(ptrace(PTRACE_SETREGS, pid, NULL, regs))
printf("*** ptrace_writereg error ***\n");
}
/* 關聯到程序 */
void ptrace_attach(int pid)
{
if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("ptrace_attach");
exit(-1);
}
waitpid(pid, NULL, /*WUNTRACED*/0);
ptrace_readreg(pid, &oldregs);
}
/* 程序繼續 */
void ptrace_cont(int pid)
{
int stat;
if(ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("ptrace_cont");
exit(-1);
}
/* while(!WIFSTOPPED(stat))
waitpid(pid, &stat, WNOHANG); */
}
/* 脫離程序 */
void ptrace_detach(int pid)
{
ptrace_writereg(pid, &oldregs);
if(ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) {
perror("ptrace_detach");
exit(-1);
}
}
/* 寫指定程序地址 */
void ptrace_write(int pid, unsigned long addr, void *vptr, int len)
{
int count;
long word;
count = 0;
while(count < len) {
memcpy(&word, vptr + count, sizeof(word));
word = ptrace(PTRACE_POKETEXT, pid, addr + count, word);
count += 4;
if(errno != 0)
printf("ptrace_write failed\t %ld\n", addr + count);
}
}
/* 讀指定程序 */
int ptrace_read(int pid, unsigned long addr, void *vptr, int len)
{
int i,count;
long word;
unsigned long *ptr = (unsigned long *)vptr;
i = count = 0;
//printf("ptrace_read addr = %x\n",addr);
while (count < len) {
//printf("ptrace_read addr+count = %x\n",addr + count);
word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
while(word < 0)
{
if(errno == 0)
break;
//printf("ptrace_read word = %x\n",word);
perror("ptrace_read failed");
return 2;
}
count += 4;
ptr[i++] = word;
}
return 0;
}
/*
在程序指定地址讀一個字串
*/
char * ptrace_readstr(int pid, unsigned long addr)
{
char *str = (char *) malloc(64);
int i,count;
long word;
char *pa;
i = count = 0;
pa = (char *)&word;
while(i <= 60) {
word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
count += 4;
if (pa[0] == 0) {
str[i] = 0;
break;
}
else
str[i++] = pa[0];
if (pa[1] == 0) {
str[i] = 0;
break;
}
else
str[i++] = pa[1];
if (pa[2] ==0) {
str[i] = 0;
break;
}
else
str[i++] = pa[2];
if (pa[3] ==0) {
str[i] = 0;
break;
}
else
str[i++] = pa[3];
}
return str;
}
/*
將指定資料壓入程序堆疊並返回堆疊指標
*/
void * ptrace_push(int pid, void *paddr, int size)
{
unsigned long esp;
struct user_regs_struct regs;
ptrace_readreg(pid, &regs);
esp = regs.esp;
esp -= size;
esp = esp - esp % 4;
regs.esp = esp;
ptrace_writereg(pid, &regs);
ptrace_write(pid, esp, paddr, size);
return (void *)esp;
}
/*
在程序內呼叫指定地址的函式
*/
void ptrace_call(int pid, unsigned long addr)
{
void *pc;
struct user_regs_struct regs;
int stat;
void *pra;
pc = (void *) 0x41414140;
pra = ptrace_push(pid, &pc, sizeof(pc));
ptrace_readreg(pid, &regs);
regs.eip = addr;
ptrace_writereg(pid, &regs);
ptrace_cont(pid);
//while(WIFSIGNALED(stat))
// waitpid(pid, &stat, WNOHANG);
}
這裡面的東西我就不展開了,對ptrace的學習,請自行man。
/* 因為應用程式可能不存在hash表,所以通過讀取原始檔的section header獲取符號表的入口數, 其實是被誤導了,但也學習了hash表的作用,用來快速查詢符號表中的資訊和字串表中的資訊 */
/*int getnchains(int pid,unsigned long base_addr) { printf("getnchains enter \n"); Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr)); Elf32_Shdr *shdr = (Elf32_Shdr *)malloc(sizeof(Elf32_Shdr)); unsigned long shdr_addr; int i = 0; int fd; char filename[1024] = {0}; ptrace_read(pid, base_addr, ehdr, sizeof(Elf32_Ehdr)); shdr_addr = base_addr + ehdr->e_shoff; //printf("getnchains ehdr->e_shoff\t %p\n", ehdr->e_shoff); snprintf(filename, sizeof(filename), "/proc/%d/exe", pid); fd = open(filename, O_RDONLY); if (lseek(fd, ehdr->e_shoff, SEEK_SET) < 0) exit(-1); /*while(i<ehdr->e_shnum) { read(fd, shdr, ehdr->e_shentsize); printf("getnchains i = %d\n",i); printf("getnchains shdr->sh_type = %x\n",shdr->sh_type); printf("getnchains shdr->sh_name = %x\n",shdr->sh_name); printf("getnchains shdr->sh_size = %x\n",shdr->sh_size); printf("getnchains shdr->sh_entsize = %x\n",shdr->sh_entsize); i++; } while(shdr->sh_type != SHT_SYMTAB) read(fd, shdr, ehdr->e_shentsize); nchains = shdr->sh_size/shdr->sh_entsize; //printf("getnchains shdr->sh_type = %d\n",shdr->sh_type); //printf("getnchains shdr->sh_name = %d\n",shdr->sh_name); //printf("getnchains shdr->sh_size = %d\n",shdr->sh_size); //printf("getnchains shdr->sh_entsize = %d\n",shdr->sh_entsize); //printf("getnchains nchains = %x\n",nchains); close(fd); free(ehdr); free(shdr); printf("getnchains exit \n"); } */
/*
取得指向link_map連結串列首項的指標
*/
struct link_map * get_linkmap(int pid)
{
Elf32_Ehdr *ehdr = (Elf32_Ehdr *) malloc(sizeof(Elf32_Ehdr));
Elf32_Phdr *phdr = (Elf32_Phdr *) malloc(sizeof(Elf32_Phdr));
Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
Elf32_Word got;
struct link_map *map = (struct link_map *)malloc(sizeof(struct link_map));
int i = 1;
unsigned long tmpaddr;
ptrace_read(pid, IMAGE_ADDR, ehdr, sizeof(Elf32_Ehdr));
phdr_addr = IMAGE_ADDR + ehdr->e_phoff;
printf("phdr_addr\t %p\n", phdr_addr);
ptrace_read(pid, phdr_addr, phdr, sizeof(Elf32_Phdr));
while(phdr->p_type != PT_DYNAMIC)
ptrace_read(pid, phdr_addr += sizeof(Elf32_Phdr), phdr,sizeof(Elf32_Phdr));
dyn_addr = phdr->p_vaddr;
printf("dyn_addr\t %p\n", dyn_addr);
ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));
while(dyn->d_tag != DT_PLTGOT) {
tmpaddr = dyn_addr + i * sizeof(Elf32_Dyn);
//printf("get_linkmap tmpaddr = %x\n",tmpaddr);
ptrace_read(pid,tmpaddr, dyn, sizeof(Elf32_Dyn));
i++;
}
got = (Elf32_Word)dyn->d_un.d_ptr;
got += 4;
//printf("GOT\t\t %p\n", got);
ptrace_read(pid, got, &map_addr, 4);
printf("map_addr\t %p\n", map_addr);
map = map_addr;
//ptrace_read(pid, map_addr, map, sizeof(struct link_map));
free(ehdr);
free(phdr);
free(dyn);
return map;
}
/*
取得給定link_map指向的SYMTAB、STRTAB、HASH、JMPREL、PLTRELSZ、RELAENT、RELENT資訊
這些地址資訊將被儲存到全域性變數中,以方便使用
*/
void get_sym_info(int pid, struct link_map *lm)
{
Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
unsigned long dyn_addr;
//printf("get_sym_info lm = %x\n",lm);
//printf("get_sym_info lm->l_ld's offset = %x\n",&((struct link_map *)0)->l_ld);
//printf("get_sym_info &lm->l_ld = %x\n",&(lm->l_ld));
//dyn_addr = (unsigned long)&(lm->l_ld);
//進入被跟蹤程序獲取動態節的地址
ptrace_read(pid,&(lm->l_ld) , &dyn_addr, sizeof(dyn_addr));
ptrace_read(pid,&(lm->l_addr) , &link_addr, sizeof(dyn_addr));
ptrace_read(pid, dyn_addr, dyn, sizeof(Elf32_Dyn));
//if(link_addr == 0)
// getnchains(pid,IMAGE_ADDR);
/*else
getnchains(pid,link_addr);*/
while(dyn->d_tag != DT_NULL){
//printf("get_sym_info dyn->d_tag = %x\n",dyn->d_tag);
//printf("get_sym_info dyn->d_un.d_ptr = %x\n",dyn->d_un.d_ptr);
switch(dyn->d_tag)
{
case DT_SYMTAB:
symtab = dyn->d_un.d_ptr;
break;
case DT_STRTAB:
strtab = dyn->d_un.d_ptr;
break;
/*case DT_HASH://可能不存在雜湊表,此時nchains是錯誤的,這個值可以通過符號表得到
//printf("get_sym_info hash table's addr = %x\n",dyn->d_un.d_ptr);
//printf("get_sym_info symtbl's entry = %x\n",(dyn->d_un.d_ptr) + 4);
ptrace_read(pid, (dyn->d_un.d_ptr) + 4,&nchains, sizeof(nchains));
break;*/
case DT_JMPREL:
jmprel = dyn->d_un.d_ptr;
break;
case DT_PLTRELSZ:
totalrelsize = dyn->d_un.d_val;
break;
case DT_RELAENT:
relsize = dyn->d_un.d_val;
break;
case DT_RELENT:
relsize = dyn->d_un.d_val;
break;
case DT_REL:
reldyn = dyn->d_un.d_ptr;
break;
case DT_RELSZ:
reldynsz = dyn->d_un.d_val;
break;
}
ptrace_read(pid, dyn_addr += sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
}
//printf("get_sym_info link_addr = %x\n",link_addr);
//printf("get_sym_info symtab = %x\n",symtab);
//printf("get_sym_info relsize = %x\n",relsize);
//printf("get_sym_info reldyn = %x\n",reldyn);
//printf("get_sym_info totalrelsize = %x\n",totalrelsize);
//printf("get_sym_info jmprel = %x\n",jmprel);
//printf("get_sym_info nchains = %x\n",nchains);
//printf("get_sym_info strtab = %x\n",strtab);
nrels = totalrelsize / relsize;
nreldyns = reldynsz/relsize;
//printf("get_sym_info nreldyns = %d\n",nreldyns);
//printf("get_sym_info nrels = %d\n",nrels);
free(dyn);
printf("get_sym_info exit\n");
}
/*
在指定的link_map指向的符號表查詢符號,它僅僅是被上面的find_symbol使用
*/
unsigned long find_symbol_in_linkmap(int pid, struct link_map *lm, char *sym_name)
{
Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym));
int i = 0;
char *str;
unsigned long ret;
int flags = 0;
get_sym_info(pid, lm);
do{
if(ptrace_read(pid, symtab + i * sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym)))
return 0;
i++;
//printf("find_symbol_in_linkmap sym->st_name = %x\tsym->st_size = %x\tsym->st_value = %x\n",sym->st_name,sym->st_size,sym->st_value);
//printf("find_symbol_in_linkmap Elf32_Sym's size = %d\n",sizeof(Elf32_Sym));
//printf("\nfind_symbol_in_linkmap sym->st_name = %x\n",sym->st_name);
if (!sym->st_name && !sym->st_size && !sym->st_value)//全為0是符號表的第一項
continue;
//printf("\nfind_symbol_in_linkmap strtab = %x\n",strtab);
str = (char *) ptrace_readstr(pid, strtab + sym->st_name);
//printf("\nfind_symbol_in_linkmap str = %s\n",str);
//printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value);
if (strcmp(str, sym_name) == 0) {
printf("\nfind_symbol_in_linkmap str = %s\n",str);
printf("\nfind_symbol_in_linkmap sym->st_value = %x\n",sym->st_value);
free(str);
if(sym->st_value == 0)//值為0代表這個符號本身就是重定向的內容
continue;
flags = 1;
//str = ptrace_readstr(pid, (unsigned long)lm->l_name);
//printf("find_symbol_in_linkmap lib name [%s]\n", str);
//free(str);
break;
}
free(str);
}while(1);
if (flags != 1)
ret = 0;
else
ret = link_addr + sym->st_value;
free(sym);
return ret;
}
/*
解析指定符號
*/
unsigned long find_symbol(int pid, struct link_map *map, char *sym_name)
{
struct link_map *lm = map;
unsigned long sym_addr;
char *str;
unsigned long tmp;
//sym_addr = find_symbol_in_linkmap(pid, map, sym_name);
//return 0;
//if (sym_addr)
// return sym_addr;
//printf("\nfind_symbol map = %x\n",map);
//ptrace_read(pid,(char *)map+12,&tmp,4);
//lm = tmp;
//printf("find_symbol lm = %x\n",lm);
//ptrace_read(pid, (unsigned long)map->l_next, lm, sizeof(struct link_map));
sym_addr = find_symbol_in_linkmap(pid, lm, sym_name);
while(!sym_addr ) {
ptrace_read(pid, (char *)lm+12, &tmp, 4);//獲取下一個庫的link_map地址
if(tmp == 0)
return 0;
lm = tmp;
//printf("find_symbol lm = %x\n",lm);
/*str = ptrace_readstr(pid, (unsigned long)lm->l_name);
if(str[0] == '/0')
continue;
printf("[%s]\n", str);
free(str);*/
if ((sym_addr = find_symbol_in_linkmap(pid, lm, sym_name)))
break;
}
return sym_addr;
}
/* 查詢符號的重定位地址 */
unsigned long find_sym_in_rel(int pid, char *sym_name)
{
Elf32_Rel *rel = (Elf32_Rel *) malloc(sizeof(Elf32_Rel));
Elf32_Sym *sym = (Elf32_Sym *) malloc(sizeof(Elf32_Sym));
int i;
char *str;
unsigned long ret;
struct link_map *lm;
lm = map_addr;
//get_dyn_info(pid);
do{
get_sym_info(pid,lm);
ptrace_read(pid, (char *)lm+12, &lm, 4);
//首先查詢過程連線的重定位表
for(i = 0; i< nrels ;i++) {
ptrace_read(pid, (unsigned long)(jmprel + i * sizeof(Elf32_Rel)),
rel, sizeof(Elf32_Rel));
if(ELF32_R_SYM(rel->r_info)) {
ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));
str = ptrace_readstr(pid, strtab + sym->st_name);
if (strcmp(str, sym_name) == 0) {
if(sym->st_value != 0){
free(str);
continue;
}
modifyflag = 1;
free(str);
break;
}
free(str);
}
}
if(modifyflag == 1)
break;
//沒找到的話,再找在連結時就重定位的重定位表
for(i = 0; i< nreldyns;i++) {
ptrace_read(pid, (unsigned long)(reldyn+ i * sizeof(Elf32_Rel)),
rel, sizeof(Elf32_Rel));
if(ELF32_R_SYM(rel->r_info)) {
ptrace_read(pid, symtab + ELF32_R_SYM(rel->r_info) *
sizeof(Elf32_Sym), sym, sizeof(Elf32_Sym));
str = ptrace_readstr(pid, strtab + sym->st_name);
if (strcmp(str, sym_name) == 0) {
if(sym->st_value != 0){
free(str);
continue;
}
modifyflag = 2;
free(str);
break;
}
free(str);
}
}
if(modifyflag == 2)
break;
}while(lm);
//printf("find_sym_in_rel flags = %d\n",flags);
if (modifyflag == 0)
ret = 0;
else
ret = link_addr + rel->r_offset;
//printf("find_sym_in_rel link_addr = %x\t sym->st_value = %x\n",link_addr , sym->st_value);
free(rel);
free(sym);
return ret;
}
/*
在程序自身的映象中(即不包括動態共享庫,無須遍歷link_map連結串列)獲得各種動態資訊
*/
/*void get_dyn_info(int pid)
{
Elf32_Dyn *dyn = (Elf32_Dyn *) malloc(sizeof(Elf32_Dyn));
int i = 0;
ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
i++;
while(dyn->d_tag){
switch(dyn->d_tag)
{
case DT_SYMTAB:
//puts("DT_SYMTAB");
symtab = dyn->d_un.d_ptr;
break;
case DT_STRTAB:
strtab = dyn->d_un.d_ptr;
//puts("DT_STRTAB");
break;
case DT_JMPREL:
jmprel = dyn->d_un.d_ptr;
//puts("DT_JMPREL");
//printf("jmprel\t %p\n", jmprel);
break;
case DT_PLTRELSZ:
totalrelsize = dyn->d_un.d_val;
//puts("DT_PLTRELSZ");
break;
case DT_RELAENT:
relsize = dyn->d_un.d_val;
//puts("DT_RELAENT");
break;
case DT_RELENT:
relsize = dyn->d_un.d_val;
//puts("DT_RELENT");
break;
}
ptrace_read(pid, dyn_addr + i * sizeof(Elf32_Dyn), dyn, sizeof(Elf32_Dyn));
i++;
}
nrels = totalrelsize / relsize;
free(dyn);
}*/
/*void call_dl_open(int pid, unsigned long addr, char *libname)
{
void *pRLibName;
struct user_regs_struct regs;
/*
先找個空間存放要裝載的共享庫名,我們可以簡單的把它放入堆疊
pRLibName = ptrace_push(pid, libname, strlen(libname) + 1);
/* 設定引數到暫存器
ptrace_readreg(pid, &regs);
regs.eax = (unsigned long) pRLibName;
regs.ecx = 0x0;
regs.edx = RTLD_LAZY;
ptrace_writereg(pid, &regs);
/* 呼叫_dl_open
ptrace_call(pid, addr);
puts("call _dl_open ok");
}*/
/*#define RTLD_LAZY 0x00001
#define RTLD_NOW 0x00002
#define RTLD_BINDING_MASK 0x3
#define RTLD_NOLOAD 0x00004
#define RTLD_DEEPBIND 0x00008
#define RTLD_GLOBAL 0x00100
#define RTLD_LOCAL 0
#define RTLD_NODELETE 0x01000 */
void call__libc_dlopen_mode(int pid, unsigned long addr, char *libname)
{
void *plibnameaddr;
//printf("call__libc_dlopen_mode libname = %s\n",libname);
//printf("call__libc_dlopen_mode addr = %x\n",addr);
//將需要載入的共享庫地址壓棧
plibnameaddr = ptrace_push(pid, libname, strlen(libname) + 1);
ptrace_push(pid,&mode,sizeof(int));
ptrace_push(pid,&plibnameaddr,sizeof(plibnameaddr));
/* 呼叫__libc_dlopen_mode */
ptrace_call(pid, addr);
}
void call_printf(int pid, unsigned long addr, char *string)
{
void *paddr;
paddr = ptrace_push(pid, string, strlen(string) + 1);
ptrace_push(pid,&paddr,sizeof(paddr));
ptrace_call(pid, addr);
}
作者所做的修改,讀者可以對比文章最後的連線中的程式碼。
這邊對於程式的具體解釋,就不具體展開了。
需要注意的是,原來是採用_dl_open的方式載入庫函式,但是ld庫並沒有這個符號匯出。而libc庫中匯出了一個可以載入庫的__libc_dlopen_mode函式。
- 5.主函式
先說一下流程,
a.獲取被跟蹤程序的link_map地址
b.根據link_map給出的資訊,搜尋符號表,遍歷每一個link_map中的符號表,直到找到想要找的符號。這裡是printf或者__libc_dlopen_mode函式
c.將庫路徑包括庫名稱傳遞給呼叫__libc_dlopen_mode的函式,該函式即call__libc_dlopen_mode會把__libc_dlopen_mode函式需要的引數,路徑和載入方式壓棧,在讓被跟蹤進
程開始執行之前,壓入一個非法地址,當__libc_dlopen_mode返回時返回到一個非法地址時,就會發生中斷,此時跟蹤程序可以waitpid跟蹤到。好,設定暫存器,並讓被跟蹤程序開
始執行。開啟庫之後,被跟蹤程序因中斷而被跟蹤程序再次獲得控制權。
d.再一次根據之前儲存的link_map資訊,當然完全可以直接用上一次搜尋結果結束之後的link_map往後找,因為新庫一定在最後,但是本文還是從頭開始找,找到新庫中的
newmyprint地址。
e.還是根據link_map資訊查詢printf的重定向地址,在rel.dyn節中,有關這個rel.dyn和rel.plt等節之間的關係,可以看我的其他博文。
f.將newmyprint的地址填入printf的重定向地址。
g.將被跟蹤程序原先的暫存器設定回去,釋放控制。
h.被跟蹤程序開始輸出“哈哈哈哈哈”。
上原始碼:
int main(int argc, char *argv[])
{
int pid;
struct link_map *map;
char sym_name[256];
unsigned long sym_addr;
unsigned long new_addr,old_addr,rel_addr;
int status = 0;
char libpath[1024];
char oldfunname[128];
char newfunname[128];
//mode = atoi(argv[2]);
if(argc < 5){
printf("usage : ./injso pid libpath oldfunname newfunname\n");
exit(-1);
}
/* 從命令列取得目標程序PID*/
pid = atoi(argv[1]);
/* 從命令列取得新庫名稱*/
memset(libpath,0,sizeof(libpath));
memcpy(libpath,argv[2],strlen(argv[2]));
/* 從命令列取得舊函式的名稱*/
memset(oldfunname,0,sizeof(oldfunname));
memcpy(oldfunname,argv[3],strlen(argv[3]));
/* 從命令列取得新函式的名稱*/
memset(newfunname,0,sizeof(newfunname));
memcpy(newfunname,argv[4],strlen(argv[4]));
printf("main pid = %d\n",pid);
printf("main libpath : %s\n",libpath);
printf("main oldfunname : %s\n",oldfunname);
printf("main newfunname : %s\n",newfunname);
/* 關聯到目標程序*/
ptrace_attach(pid);
/* 得到指向link_map連結串列的指標 */
map = get_linkmap(pid); /* get_linkmap */
sym_addr = find_symbol(pid, map, "printf");
printf("found printf at addr %p\n", sym_addr);
if(sym_addr == 0)
goto detach;
call_printf(pid,sym_addr,"injso successed\n");
waitpid(pid,&status,0);
printf("status = %x\n",status);
/*ptrace_writereg(pid, &oldregs);
ptrace_cont(pid);
waitpid(pid,&status,0);
//printf("status = %x\n",status);
//ptrace_readreg(pid, &oldregs);
//oldregs.eip = 0x8048414;
//ptrace_writereg(pid, &oldregs);
ptrace_cont(int pid)(pid);
ptrace_detach(pid);
exit(0);*/
/* 發現__libc_dlopen_mode,並呼叫它 */
sym_addr = find_symbol(pid, map, "__libc_dlopen_mode"); /* call _dl_open */
printf("found __libc_dlopen_mode at addr %p\n", sym_addr);
if(sym_addr == 0)
goto detach;
call__libc_dlopen_mode(pid, sym_addr,libpath); /* 注意裝載的庫地址 */
//while(1);
waitpid(pid,&status,0);
/* 找到新函式的地址 */
strcpy(sym_name, newfunname); /* intercept */
sym_addr = find_symbol(pid, map, sym_name);
printf("%s addr\t %p\n", sym_name, sym_addr);
if(sym_addr == 0)
goto detach;
/* 找到舊函式在重定向表的地址 */
strcpy(sym_name, oldfunname);
rel_addr = find_sym_in_rel(pid, sym_name);
printf("%s rel addr\t %p\n", sym_name, rel_addr);
if(rel_addr == 0)
goto detach;
/* 找到用於儲存read地址的指標 */
//strcpy(sym_name, "oldread");
//old_addr = find_symbol(pid, map, sym_name);
//printf("%s addr\t %p\n", sym_name, old_addr);
/* 函式重定向 */
puts("intercept..."); /* intercept */
//ptrace_read(pid, rel_addr, &new_addr, sizeof(new_addr));
//ptrace_write(pid, old_addr, &new_addr, sizeof(new_addr));
//rel_addr = 0x8048497;如果是靜態地址,也就是未匯出該符號地址,那麼只能通過反彙編先找到該函式被呼叫的地方,將這個地方的跳轉地址修改
if(modifyflag == 2)
sym_addr = sym_addr - rel_addr - 4;
printf("main modify sym_addr = %x\n",sym_addr);
ptrace_write(pid, rel_addr, &sym_addr, sizeof(sym_addr));
puts("injectso ok");
detach:
printf("prepare to detach\n");
ptrace_detach(pid);
return 0;
}
這裡面有一個很重要的地方,如果不先在目標程序中呼叫printf就不能夠呼叫__lib_dlopen_mode成功,這個原因很奇怪,根據當時的core檔案來看崩潰在了下面的這個函式,原因是_dl_open_hook這個全域性變數為0,但實際上執行過printf之後,這個_dl_open_hook還是0。這個有待後續檢驗。
void * __libc_dlsym (void *map, const char *name) { struct do_dlsym_args args; args.map = map; args.name = name; #ifdef SHARED if (__builtin_expect (_dl_open_hook != NULL, 0)) return _dl_open_hook->dlsym (map, name); #endif return (dlerror_run (do_dlsym, &args) ? NULL : (void *) (DL_SYMBOL_ADDRESS (args.loadbase, args.ref))); }
執行結果:
[email protected]:injso# ./test
1467364356 : original
injso successed
hahahahahahahahahahahahahaha
- 6.如何替換未匯出符號的地址
被替換函式原始碼:
#include <stdio.h> //int fun2(); int fun1() { printf("fun1\n"); // fun2(); } int main() { signed int i = 0x40011673 ; i = i - 0x4001172d ; printf("i = %x\n",i); while(1){ i = fun1(); sleep(10); } return 1; }
這個怎麼來替換fun1函式的地址呢?
首先反彙編得到main的機器碼,如下,
08048468 <main>:
8048468: 55 push %ebp
8048469: 89 e5 mov %esp,%ebp
804846b: 83 e4 f0 and $0xfffffff0,%esp
804846e: 83 ec 20 sub $0x20,%esp
8048471: c7 44 24 1c 73 16 01 movl $0x40011673,0x1c(%esp)
8048478: 40
8048479: 81 6c 24 1c 2d 17 01 subl $0x4001172d,0x1c(%esp)
8048480: 40
8048481: b8 75 85 04 08 mov $0x8048575,%eax
8048486: 8b 54 24 1c mov 0x1c(%esp),%edx
804848a: 89 54 24 04 mov %edx,0x4(%esp)
804848e: 89 04 24 mov %eax,(%esp)
8048491: e8 ce fe ff ff call 8048364 <[email protected]>
8048496: e8 b9 ff ff ff call 8048454 <fun1>
804849b: 89 44 24 1c mov %eax,0x1c(%esp)
804849f: c7 04 24 0a 00 00 00 movl $0xa,(%esp)
80484a6: e8 c9 fe ff ff call 8048374 <[email protected]>
80484ab: eb e9 jmp 8048496 <main+0x2e>
80484ad: 90 nop
80484ae: 90 nop
80484af: 90 nop
可以看到在地址0x8048496處的機器碼是跳轉到fun1函式的,那麼這個ffffffb9就是call的運算元,運算元地址0x8048497,也就是說把這個地址中的數值改掉就可以了,有關這個call或者jmp的地址計算可以檢視我的另外一篇博文。
有關這個如何跳轉的方法,已經在主函式的程式碼中給出了,但是被我註釋掉了,大家感興趣的話,可以自己試試。
效果:
[email protected]:lib2lib# ./a.out
i = ffffff46
fun1
injso successed
hahahahahahaha^C
這裡面的無關程式碼,大家仔細看,是為了證明call的函式地址計算方式的。
- 7.總結
那麼講到現在的話,已經實現了不管函式符號是否匯出都可以實現執行時替換的程式碼。
這裡面主要的技術是,elf檔案格式,執行時載入的過程,跳轉地址的計算,執行時連結的過程,也就是plt表(當然這個也可以從我的另一篇博文中看到)。
比較遺憾的是有關那個奔潰,有網友如果找到了原因,請回復下,3q。當然我也會自己再研究下。
最後補上全域性變數和標頭檔案:
#include <stdio.h> #include <string.h> #include <elf.h> #include <sys/types.h> #include <stdio.h> #include <sys/ptrace.h> #include <sys/wait.h> #include <sys/errno.h> #include <sys/user.h> #include <link.h> #include <sys/stat.h> #include <fcntl.h> #include <bits/dlfcn.h> #define IMAGE_ADDR 0x08048000 int mode = 2; struct user_regs_struct oldregs; Elf32_Addr phdr_addr; Elf32_Addr dyn_addr; Elf32_Addr map_addr; Elf32_Addr symtab; Elf32_Addr strtab; Elf32_Addr jmprel; Elf32_Addr reldyn; Elf32_Word reldynsz; Elf32_Word totalrelsize; Elf32_Word relsize; unsigned long link_addr; int nrels; int nreldyns; //int nchains; int modifyflag = 0; /*char libpath[128] = "/mnt/hgfs/svnroot/test/injectsov2/prj_linux/so.so";*/
- 8.修正
針對在呼叫__libc_dlopen_mode函式之前需要呼叫printf的問題,終於讓我在晚上解決了。
首先,我嘗試了呼叫其他函式而不是printf函式,發現效果一樣,包括第一次是呼叫__libc_dlopen_mode,第二次對該函式的呼叫都可以成功。
其次,那麼現在問題就集中在了這兩個__libc_dlopen_mode呼叫之間的差別在哪裡,程式段肯定是一致的,棧也是一致的,而堆空間未使用,還有一個重要的因素,那就是暫存器。
最後,發現在呼叫__libc_dlopen_mode前,有四個暫存器不同,分別是eax,orig_eax,eflags和esp。我一開始認為,通用暫存器eax和orig_eax不會對程式的執行造成影響。但是通過實驗,僅調一次__libc_dlopen_mode,部分暫存器賦正確執行時的值,發現對eax和orig_eax被賦於正確執行時的值時,程式可以正常執行,而且不僅僅必須是一種值,比如eax可以是0,1,0xffffffff,很多值都可以,但是被賦予0xfffffdfc和0xfffffdff等值時會失敗,試驗過並不是因為d這一位決定的,0xfffffdf0或者d00是可以執行成功的。
(gdb) disassemble __libc_dlopen_mode
Dump of assembler code for function __libc_dlopen_mode:
0x00232640 <+0>: push %ebp
0x00232641 <+1>: mov %esp,%ebp
0x00232643 <+3>: sub $0x1c,%esp
0x00232646 <+6>: mov %ebx,-0x8(%ebp)
0x00232649 <+9>: mov 0x8(%ebp),%eax
0x0023264c <+12>: call 0x144a0f
0x00232651 <+17>: add $0x519a3,%ebx
0x00232657 <+23>: mov 0xc(%ebp),%edx
0x0023265a <+26>: mov %esi,-0x4(%ebp)
0x0023265d <+29>: mov %eax,-0x14(%ebp)
0x00232660 <+32>: mov %edx,-0x10(%ebp)
0x00232663 <+35>: mov 0x354c(%ebx),%esi
0x00232669 <+41>: test %esi,%esi
在實驗中,還發現對eax賦於不正確的值時,當時忘了記了,還讓程式跑飛了。崩了,但是新庫已經載入上了。所以這個函式替換還是有一定的風險,或者說libc庫本身存在一定的bug。
所以現在問題找到了,在於eax和orig_eax上,但是對__libc_dlopen_mode反彙編發現,eax在函式開頭就被賦予了通過棧傳遞的引數2的值,所以eax不應該影響程式的執行,但實際上影響了,這一點讓我覺得很奇怪,如果有任何網友對這個原因知曉的話,麻煩回覆,萬分感謝。
linux共享庫注射地址:http://www.docin.com/p-634172083.html
__simple原創