如何利用Ptrace拦截和模拟Linux系统调用

如何利用Ptrace拦截和模拟Linux系统调用,第1张

这里的“拦截”我指的是tracer能够改变系统调用参数,改变系统调用的返回值,甚至屏蔽特定的系统调用。这也就意味着,一个tracer将能够完全实现自己的系统调用,这就非常有趣了,也就是说,一个tracer将可以模拟出一整套 *** 作系统机制,而且这一切都不需要内核提供任何其他帮助。

但问题在于,一个进程一次只能够绑定一个tracer,因此我们无法在调试进程(GDB)的过程中模拟出一套外部 *** 作系统,而另一个问题就是模拟系统调用将耗费更多的资源开销。

在这篇文章中,我将主要讨论x86-64架构下的Linux Ptrace,并且我还会使用到一些特定的Linux扩展。除此之外,我可能会忽略错误检查,但最终发布的完整源码将会解决这些问题。

推荐http://blog.chinaunix.net/u2/67414/showart_1716467.html去学习用法,有例子

在用户模式中,虽然只有一个函数可用,即ptrace(int _request, pid_t _pid, caddr_t _addr, int _data),但是这个函数能做所有的事情!如果你愿意,也可以花费几个小时来编写自己的小调试器,以解决特定的问题。

ptrace函数的_request参数是最重要的一个参数,因为它确定你将做什么。BSD和Linux的头文件使用不同的定义,这使得将ptrace应用从一个平台移植到另一个平台变得很复杂。默认地,我们使用BSD头文件中的定义。

r PT_TRACE_ME(PTRACE_TRACEME)将当前进程切换到停止状态。它通常总是与fork/exec一起使用,虽然也能遇到自我追踪的应用程序。对于每一个进程,PT_TRACE_ME只能被调用一次。追踪一个正被追踪的进程是会失败的(另一个较不重要的结果是进程不能追踪它自己。如果要这样做,应该首先从自身派生一个进程)。大量的反调试技术都是以这一事实为基础的。为了克服这类技术,必须使用绕过ptrace的调试器。一个信号被发送到正被调试的进程,并将该进程切换到停止状态,该进程可以使用从父进程上下文中调用的PT_CONTINUE和PT_STEP命令从停止状态退出。wait函数会延迟父进程的执行,直到被调试的进程切换为停止状态或者终止为止(终止时,返回值为1407)。其他的所有参数都被忽略。

r PT_ATTACH(PTRACE_ATTACH)将进程标志为pid的运行进程切换为停止状态,在这种情形下,调试器进程成为“父进程”。其他的所有参数都被忽略。进程必须具有与调试进程相同的用户标志(UID),并且不能是setuid/setduid进程(否则就要用root来调试)。

r PT_DETACH(PTRACE_DETACH)停止进程标志为pid进程(由PT_ATTACH和PT_TRACE_ME指定)的调试,并继续其常态运行。其他的所有参数都被忽略。

r PT_CONTINUE(PTRACE_CONT)继续进程标志为pid的被调试进程的执行,而不中断与调试器进程的通信。如果addr == 1(在Linux中为0),从上次停止的地址继续执行;否则,从指定的地址继续执行。参数_data指定发送到被调试进程的信号数量(零说明没有信号)。

r PT_STEP(PTRACE_SINGLESTEP)进行进程标志为pid的进程的单步执行,即执行下一条机器指令并切换为停止状态(在i386中,这是根据设置追踪标志来实现的,虽然有些“黑客”函数库使用硬件断点)。BSD要求将参数addr置为1,而Linux要求将该参数置为0。其他的所有参数都被忽略。

r PT_READ_I和PT_READ_D(PTRACE_PEEKTEXT和PTRACE_PEEKDATA)分别从代码区和正被调试进程的地址空间区读取机器字。在许多当代的平台中,这两个指令是等价的。ptrace函数接收目标地址addr,并返回读到的结果。

r PT_WRITE_I和PR_READ_D(PTRACE_POKETEXT和PTRACE_POKEDATA)将由_data传入的机器字写入addr所指定的地址。

r PT_GETREGS,PT_GETFPREGS和PT_GETDBREGS(PTRACE_GETREGS,PTRACE_ FPREGS和PT_GETFPXREGS)将一般用途寄存器、段寄存器和调试寄存器的值读入到地址由_addr指针所指定的调试器进程的内存区中。只有i386平台接收这些与系统相关的命令。寄存器结构的描述放在头文件machine/reg.h文件中。

r PT_SETREGS,PT_SETFPREGS和PT_SETDBREGS(PTRACE_SETREGS,PTRACE_ SETFPREGS和PT_SETFPXREGS)通过拷贝由_addr指针所指定的内存区域的内容来设置被调试进程的寄存器的值。

r PT_KILL(PTRACE_KILL)将sigkill发送到被调试进程,以终止其执行。

有啊,一切顺序逻辑,都有被hook的可能。 下面是一个linux上的hook的实例

截获write系统调用:

#ifndef MODULE

#define MODULE

#endif

                                                                              

#ifndef __KERNEL__

#define __KERNEL__

#endif 

#include <linux/init.h>

#include <linux/module.h>

#include <linux/version.h>

#include <linux/kernel.h>

#include <asm/unistd.h>

#include <linux/slab.h>

/*

#include <sys/types.h>

#include <asm/fcntl.h>

#include <linux/malloc.h>

#include <linux/types.h>

#include <linux/string.h>

#include <linux/fs.h>

#include <asm/errno.h> 

#include <sys/syscall.h>

*/ 

MODULE_LICENSE("GPL")

struct descriptor_idt

{

        unsigned short offset_low

        unsigned short ignore1

        unsigned short ignore2

        unsigned short offset_high

}

static struct {

        unsigned short limit

        unsigned long base

}__attribute__ ((packed)) idt48

static unsigned int SYS_CALL_TABLE_ADDR

void **sys_call_table

int base_system_call

int (*orig_write)(unsigned int fd,char *buf,unsigned int count)

unsigned char opcode_call[3]={0xff,0x14,0x85}

int match(unsigned char *source)

{

        int i

        for(i=0i<3i++){

                if(source[i] != opcode_call[i])

                        return 0

        }

        return 1

}

int get_sys_call_table(void)

{

        int i,j

        unsigned char *ins=(unsigned char *)base_system_call

        unsigned int sct

                                                                              

        for(i=0i<100i++){

                if(ins[i]==opcode_call[0]){

                        if(match(ins+i)){

                                sct=*((unsigned int *)(ins+3+i))

                                printk(KERN_ALERT "sys_call_tabl's address is

0x%X\n",sct)

                                return sct

                        }

                }

        }

                                                                              

        printk(KERN_ALERT "can't find the address of sys_call_table\n")

        return -1

}

int hacked_write(unsigned int fd,char *buf,unsigned int count)

 char *hide="hello"

 if(strstr(buf,hide)!=NULL){

  printk(KERN_ALERT "find name.\n")

  return count

 }

 else{

  return orig_write(fd,buf,count)

 }

}

int init_module(void)

{

        __asm__ volatile ("sidt %0": "=m" (idt48))

        struct descriptor_idt *pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80)

        base_system_call = (pIdt80->offset_high<<16 | pIdt80->offset_low)

        printk(KERN_ALERT "system_call address at 0x%x\n",base_system_call)

 SYS_CALL_TABLE_ADDR=get_sys_call_table()

 sys_call_table=(void **)SYS_CALL_TABLE_ADDR

 orig_write=sys_call_table[__NR_write]

 sys_call_table[__NR_write]=hacked_write

        return 0

}

void cleanup_module()

{

 sys_call_table[__NR_write]=orig_write

}


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/6228271.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-03-18
下一篇2023-03-18

发表评论

登录后才能评论

评论列表(0条)

    保存