打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
共享库注射
目录:
1 -- 介绍
2 -- injectso -- 共享库注射技术
3 -- injectso的工作步骤及实现方法
4 -- 目标进程调试函数
5 -- 符号解析函数
6 -- 一个简单的后门程序
7 -- 最后
8 -- 参考文献
一、 ** 介绍
本文介绍的是injectso技术,重点是使用现有技术去实际的完成一个injectso程序,而不是侧重于理论上的探讨。这里希望你在阅读这篇文章的时候对ELF、inject有一
定的了解,当然你也可以选择在看完本文之后再去翻看相关的资料,也许这样能使你更有针对性。需要说明的是,下面介绍的技术和给出的函数都是特定于X86下的linux
的,在其它环境下可能有一些需要改变的细节,但从基本的概念和步骤上讲应该是相同的。
[separator]
二、 ** injectso -- 共享库注射技术
使用injectso技术,我们可以注射共享库到一个运行期进程,这里注射的意思就是通过某种操作使我们的.so共享库在指定的进程中被装载,这样再配合上函数重定向或
其它技术,我们就可以捕获或改变目标进程的行为,可以做非常多的工作。同其它inject技术相比,injectso的一些优点是:
1. 简单 -- 仅仅通过C代码就可以完成所有的工作;
2. 扩展性好 -- 在基础代码完成之后,如果要对程序功能进行增加、修改,仅需改动.so共享库即可;
3. 干净 -- 对目标进程进行注射之后,不需要留下磁盘文件,使用的程序及共享库都可以删除;
4. 灵活 -- 我们可以使用它完成很多工作,例如:运行期补丁、后门程序等;
5. 目标服务不需要重新启动;
6. 无须改动二进制文件;
7. 可以通过pax, openwall等这样的核心补丁。
三、 ** injectso的工作步骤及实现方法
完成injectso需要以下几个步骤:
1. 关联到目标进程;
2. 发现装载共享库的函数,一般是_dl_open调用,我们将使用它装载我们的.so共享库
3. 装载指定的.so;
4. 做我们想做的,一般是通过函数重定向来完成我们需要的功能;
5. 脱离进程;
下面简单介绍一下这几个步骤的实现方法,由于我们是对其它进程进行操作,因此ptrace这个linux调试API函数将频繁的被我们使用,在<四>中,我将给出一些ptrace包装函数。
步骤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, &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_call中的这处:
pc = (void *) 0x41414140;
pra = ptrace_push(pid, &pc, sizeof(pc));
在这里,我们将无效页面地址0x41414140压入目标进程堆栈,这样当程序执行完我们指定的函数后,程序会产生错误中断,所以我们就又获得了对进程的控制权,可以继
续下面的操作。
看完这篇文章有何感觉? 已有 2 人表态啦!
0
2
0
0
0
0
0
0
0
0
0
感动
扯蛋
还行
愤怒
抓狂
看好
不错
献吻
支持
参考
湿了
标签分类:
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
应用程序热补丁(一): 几行代码构造免重启修复补丁
Android注入完全剖析
调试断点原理
linux下实现在程序运行时的函数替换(热补丁)
Linux系统调用与ptrace分析
Android中的so注入(inject)和挂钩(hook)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服