LINUX共享库
类似Windows系统中的动态链接库,Linux中也有相应的共享库用以支持代码的复用。Windows中为*.dll,而Linux中为*.so。下 面详细介绍如何创建、使用Linux的共享库。
一个例子:
#include
int sayhello( void )
{
printf("hello form sayhello function!/n");
return 0;
}
void saysomething(char * str)
{
printf("%s/n",str);
}
用下面的命令可以生成共享库
# gcc -fpic -shared sayhello.c -o libsay.so
解释:-f 后面跟一些编译选项,PIC 是 其中一种,表示生成位置无关代码(Position Independent Code)
实现方法
步骤1 — 关联进程
简单的调用ptrace(PTRACE_ATTACH,…)即可以关联到目标进程,但此后我们还需调用waitpid()函数等待目标进程暂停,以便我们进行后续操作。详见中给出的ptrace_attach()函数。
步骤2 — 发现_dl_open
通过遍历动态连接器使用的link_map结构及其指向的相关链表,我们可以完成_dl_open的符号解析工作,关于通过link_map解析符号在phrack59包的p59_08(见参考文献)中有详细的描述。
步骤3 — 装载.so
由于在2中我们已经找到_dl_open的地址,所以我们只需将此函数使用的参数添入相应的寄存器,并将进程的eip指向_dl_open即可,在此过程中还需做一些其它操作,具体内容见中的call_dl_open和ptrace_call函数。
步骤4 — 函数重定向
我们需要做的仅仅是找到相关的函数地址,用新函数替换旧函数,并将旧函数的地址保存。其中涉及到了PLT和RELOCATION,关于它们的详细内容你应该看ELF规范中的介绍,在中的函数中有PLT和RELOCATION的相关操作,而且在最后的例子中,我们将实现函数重定向。关于函数重定向,相关资料很多,这里不再多介绍。
步骤5 — 脱离进程
简单的调用ptrace(PTRACE_DETACH,…)可以脱离目标进程。
目标进程调试函数
在linux中,如果我们要调试一个进程,可以使用ptrace API函数,为了使用起来更方便,我们需要对它进行一些功能上的封装。
在p59_08中作者给出了一些对ptrace进行封装的函数,但是那太少了,在下面我给出了更多的函数,这足够我们使用了。要注意在这些函数中我并未进行太多的错误检测,但做为一个例子使用,它已经能很好的工作了,在最后的例子中你将能看到这一点
/* 关联到进程 */
void ptrace_attach(int pid)
{
if(ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
perror("ptrace_attach");
exit(-1);
}
waitpid(pid, NULL, WUNTRACED);
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);
}
}
/* 读指定进程 */
void 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;
while (count < len) {
word = ptrace(PTRACE_PEEKTEXT, pid, addr + count, NULL);
count += 4;
ptr[i++] = word;
}
}
/*
在进程指定地址读一个字符串
*/
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_readreg(int pid, struct user_regs_struct *regs)
{
if(ptrace(PTRACE_GETREGS, pid, NULL, regs))
printf("*** ptrace_readreg error ***\n");
}
/* 写进程寄存器 */
void ptrace_writereg(int pid, struct user_regs_struct *regs)
{
if(ptrace(PTRACE_SETREGS, pid, NULL, regs))
printf("*** ptrace_writereg error ***\n");
}
/*
将指定数据压入进程堆栈并返回堆栈指针
*/
void * ptrace_push(int pid, void *paddr, int size)
{
unsigned long esp;
struct user_regs_struct regs;
ptrace_readreg(pid, ®s);
esp = regs.esp;
esp -= size;
esp = esp - esp % 4;
regs.esp = esp;
ptrace_writereg(pid, ®s);
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, ®s);
regs.eip = addr;
ptrace_writereg(pid, ®s);
ptrace_cont(pid);
while(!WIFSIGNALED(stat))
waitpid(pid, &stat, WNOHANG);
}