
但问题在于,一个进程一次只能够绑定一个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
}
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)