ptrace注入详解

简介: ptrace注入分析

1.ptrace基础

函数原型

long ptrace(enum __ptrace_request request, pid_t pid, void *addr,void *data);

参数详解

主要是参数一

PTRACE_ATTACH,表示附加到指定远程进程

PTRACE_DETACH,表示从指定远程进程分离

PTRACE_GETREGS,表示读取远程进程当前寄存器环境

PTRACE_SETREGS,表示设置远程进程的寄存器环境

PTRACE_CONT,表示使远程进程继续运行

PTRACE_PEEKTEXT,从远程进程指定内存地址读取一个word大小的数据

PTRACE_POKETEXT,往远程进程指定内存地址写入一个word大小的数据

在注入需求下都是应用这两个参数的PTRACE_ATTACH、PTRACE_DETACH

参数二 需要附加注入的pid数据

参数三、参数四 都直接用NULL填充就可以

2.ptrace注入的步骤

1.调用ptrace系统函数进行附加到远程进程

2.保存寄存器的环境数据

3.通过调mmap系统函数进行分配内存空间

4.想附加的进程写入模块名称和要执行的函数名称

5.调用dlopen系统函数进行打开注入的模块

6.调用dlsym系统函数获取需调用函数的地址信息

7.远程调用被注入模块的函数

8.恢复寄存器的环境数据

9.调用ptrace系统函数进行和附加进程进行剥离

3.ptrace注入源码实现

3.1附加进程

/*******************
 函数功能n:   使用ptrace函数 Attach到指定远程进程
 参数:        pid表示远程进程的ID
 返回值:      返回0表示attach成功,返回-1表示失败
********************/  
int ptrace_attach(pid_t pid)    
{
  int status = 0;
    if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {    
        LOGD("attach process error, pid:%d", pid);    
        return -1;    
    }    
  LOGD("attach process pid:%d", pid);          
    waitpid(pid, &status , WUNTRACED);        
    return 0;    
} 

3.2剥离进程

/******************
  函数功能n:    使用ptrace函数 DETACH到剥离进程
  参数:         pid表示远程进程的ID
  返回值:       返回0表示DETACH成功,返回-1表示失败
******************/    
int ptrace_detach(pid_t pid)    
{    
    if (ptrace(PTRACE_DETACH, pid, NULL, 0) < 0) {    
        LOGD("detach process error, pid:%d", pid);     
        return -1;    
    }    
  LOGD("detach process pid:%d", pid);  
    return 0;    
}
复制代码

3.2ptrace注入so功能

/**************************
  函数功能: 通过远程直接调用dlopen\dlsym的方法ptrace注入so模块到远程进程中
  参数1:    pid表示远程进程的pid,
  参数2:LibPath为被远程注入的so模块路径,
  参数3:FunctionName为远程注入的模块后调用的函数
  参数4::FuncParameter指向被远程调用函数的参数(若传递字符串,需要先将字符串写入到远程进程空间中),NumParameter为参数的个数
  Return:  返回0表示注入成功,返回-1表示失败
**********************/ 
int inject_remote_process(pid_t pid, char *LibPath, char *FunctionName, long *FuncParameter, long NumParameter)
{
  int iRet = -1;
  struct pt_regs CurrentRegs, OriginalRegs;  // CurrentRegs表示远程进程中当前的寄存器值,OriginalRegs存储注入前的寄存器值,方便恢复
  void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;   // 远程进程中需要调用函数的地址
  void *RemoteMapMemoryAddr, *RemoteModuleAddr, *RemoteModuleFuncAddr; // RemoteMapMemoryAddr为远程进程空间中映射的内存基址,RemoteModuleAddr为远程注入的so模块加载基址,RemoteModuleFuncAddr为注入模块中需要调用的函数地址
  long parameters[6];  
  // Attach远程进程
  if (ptrace_attach(pid) == -1)
    return iRet;
  // 获取远程进程的寄存器值
  if (ptrace_getregs(pid, &CurrentRegs) == -1)
  {
    ptrace_detach(pid);
    return iRet;
  }
  // 保存远程进程空间中当前的上下文寄存器环境
  memcpy(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs)); 
  // 获取mmap函数在远程进程中的地址
  mmap_addr = GetRemoteFuncAddr(pid, libc_path, (void *)mmap);
  LOGD("mmap RemoteFuncAddr:0x%lx", (long)mmap_addr);
  // 设置mmap的参数
  // void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);
    parameters[0] = 0;  // 设置为NULL表示让系统自动选择分配内存的地址    
    parameters[1] = 0x1000; // 映射内存的大小    
    parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;  // 表示映射内存区域可读可写可执行   
    parameters[3] =  MAP_ANONYMOUS | MAP_PRIVATE; // 建立匿名映射    
    parameters[4] = 0; //  若需要映射文件到内存中,则为文件的fd  
    parameters[5] = 0; //文件映射偏移量  
  // 调用远程进程的mmap函数,建立远程进程的内存映射
  if (ptrace_call(pid, (long)mmap_addr, parameters, 6, &CurrentRegs) == -1)
  {
    LOGD("Call Remote mmap Func Failed");
    ptrace_detach(pid);
    return iRet;
  }
  // 获取mmap函数执行后的返回值,也就是内存映射的起始地址
  RemoteMapMemoryAddr = (void *)ptrace_getret(&CurrentRegs);
  LOGD("Remote Process Map Memory Addr:0x%lx", (long)RemoteMapMemoryAddr);
  // 分别获取dlopen、dlsym、dlclose等函数的地址
  dlopen_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlopen);
  dlsym_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlsym);
  dlclose_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlclose);
  dlerror_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlerror);
  // 将要加载的so库路径写入到远程进程内存空间中
  if (ptrace_writedata(pid, RemoteMapMemoryAddr, LibPath, strlen(LibPath) + 1) == -1)
  {
    ptrace_detach(pid);
    return iRet;
  }
  // 设置dlopen的参数,返回值为模块加载的地址
  // void *dlopen(const char *filename, int flag);
  parameters[0] = (long)RemoteMapMemoryAddr;
  parameters[1] = RTLD_NOW| RTLD_GLOBAL;
  if (ptrace_call(pid, (long)dlopen_addr, parameters, 2, &CurrentRegs) == -1)
  {
    ptrace_detach(pid);
    return iRet;
  }
  // RemoteModuleAddr为远程进程加载注入模块的地址
  RemoteModuleAddr = (void *)ptrace_getret(&CurrentRegs);
  if ((long)RemoteModuleAddr == 0x0)   // dlopen 错误
  {
    LOGD("dlopen error");
    if (ptrace_call(pid, (long)dlerror_addr, parameters, 0, &CurrentRegs) == -1)
    {
      LOGD("Call Remote dlerror Func Failed");
      ptrace_detach(pid);
      return iRet;
    }
    char *Error = (void *)ptrace_getret(&CurrentRegs);
    char LocalErrorInfo[1024] = {0};
    ptrace_readdata(pid, Error, LocalErrorInfo, 1024);
    ptrace_detach(pid);
    return iRet;
  } 
  // 将so库中需要调用的函数名称写入到远程进程内存空间中
  if (ptrace_writedata(pid, RemoteMapMemoryAddr + strlen(LibPath) + 2, FunctionName, strlen(FunctionName) + 1) == -1)
  {
    ptrace_detach(pid);
    return iRet;
  }
  // 设置dlsym的参数,返回值为远程进程内函数的地址
  // void *dlsym(void *handle, const char *symbol);
  parameters[0] = (long)RemoteModuleAddr;
  parameters[1] = (long)(RemoteMapMemoryAddr + strlen(LibPath) + 2);
  if (ptrace_call(pid, (long)dlsym_addr, parameters, 2, &CurrentRegs) == -1)
  {
    LOGD("Call Remote dlsym Func Failed");
    ptrace_detach(pid);
    return iRet;
  } 
  // RemoteModuleFuncAddr为远程进程空间内获取的函数地址
  RemoteModuleFuncAddr = (void *)ptrace_getret(&CurrentRegs);
  if (ptrace_call(pid, (long)RemoteModuleFuncAddr, FuncParameter, NumParameter, &CurrentRegs) == -1)
  {
    LOGD("Call Remote injected Func Failed");
    ptrace_detach(pid);
    return iRet;
  } 
  if (ptrace_setregs(pid, &OriginalRegs) == -1)
  {
    LOGD("Recover reges failed");
    ptrace_detach(pid);
    return iRet;    
  }
  ptrace_getregs(pid, &CurrentRegs);
  if (memcmp(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs)) != 0)
  {
    LOGD("Set Regs Error");
  }
  //剥离
  if (ptrace_detach(pid) == -1)
  {
    LOGD("ptrace detach failed");
    return iRet;
  }
  return 0;
}


相关文章
|
15天前
|
Rust 关系型数据库 C语言
使用uftrace跟踪bpf程序的执行
使用uftrace跟踪bpf程序的执行
|
10月前
|
API 开发工具
【Pintos】实现自定义 UserProg 系统调用 | 添加 syscall-nr 系统调用号 | 编写新的参数调用宏
【Pintos】实现自定义 UserProg 系统调用 | 添加 syscall-nr 系统调用号 | 编写新的参数调用宏
97 0
|
安全 API Windows
3.1 DLL注入:常规远程线程注入
动态链接库注入技术是一种特殊的技术,它允许在运行的进程中注入DLL动态链接库,从而改变目标进程的行为。DLL注入的实现方式有许多,典型的实现方式为远程线程注入,该注入方式的注入原理是利用了`Windows`系统中提供的`CreateRemoteThread()`这个API函数,该函数第四个参数是准备运行的线程,我们将`LoadLibrary()`函数填入其中,这样就可以执行远程进程中的`LoadLibrary()`函数,进而将我们自己准备的DLL加载到远程进程空间中执行,DLL在被装载后则会自动执行初始化部分。
124 0
|
Windows
3.2 DLL注入:远程APC异步注入
APC(Asynchronous Procedure Call)异步过程调用是一种`Windows`操作系统的核心机制,它允许在进程上下文中执行用户定义的函数,而无需创建线程或等待OS执行完成。该机制适用于一些频繁的、短暂的或非常细微的操作,例如改变线程优先级或通知线程处理任务。在`APC机制`中,当某些事件发生时(例如文件IO,网络IO或定时器触发),这些事件将被操作系统添加到一个`APC队列`中,该队列绑定到执行线程。在下一次发生`ALERTABLE`的事件时(例如调用SleepEx或SignalObjectAndWait时),OS将弹出`APC函数`并在执行线程上下文中调用该函数,并在执
97 0
|
网络协议 安全 API
驱动开发:内核远程线程实现DLL注入
在笔者上一篇文章`《内核RIP劫持实现DLL注入》`介绍了通过劫持RIP指针控制程序执行流实现插入DLL的目的,本章将继续探索全新的注入方式,通过`NtCreateThreadEx`这个内核函数实现注入DLL的目的,需要注意的是该函数在微软系统中未被导出使用时需要首先得到该函数的入口地址,`NtCreateThreadEx`函数最终会调用`ZwCreateThread`,本章在寻找函数的方式上有所不同,前一章通过内存定位的方法得到所需地址,本章则是通过解析导出表实现。
4924 0
|
Windows
3.4 DLL注入:全局消息钩子注入
SetWindowHookEx 是`Windows`系统的一个函数,可用于让一个应用程序安装全局钩子,但读者需要格外注意该方法安装的钩子会由操作系统注入到所有可执行进程内,虽然该注入方式可以用于绕过游戏保护实现注入,但由于其属于全局注入所以所有的进程都会受到影响,而如果想要解决这个问题,则需要在`DllMain()`也就是动态链接库开头位置进行判断,如果是我们所需操作的进程则执行该DLL模块内的功能,如果不是则自动跳过不执行任何操作即可实现指定进程的注入方式。
98 0
|
Shell
驱动开发:内核ShellCode线程注入
还记得`《驱动开发:内核LoadLibrary实现DLL注入》`中所使用的注入技术吗,我们通过`RtlCreateUserThread`函数调用实现了注入DLL到应用层并执行,本章将继续探索一个简单的问题,如何注入`ShellCode`代码实现反弹Shell,这里需要注意一般情况下`RtlCreateUserThread`需要传入两个最重要的参数,一个是`StartAddress`开始执行的内存块,另一个是`StartParameter`传入内存块的变量列表,而如果将`StartParameter`地址填充为`NULL`则表明不传递任何参数,也就是只在线程中执行`ShellCode`代码,利用
354 1
驱动开发:内核LoadLibrary实现DLL注入
远程线程注入是最常用的一种注入技术,在应用层注入是通过`CreateRemoteThread`这个函数实现的,该函数通过创建线程并调用 `LoadLibrary` 动态载入指定的DLL来实现注入,而在内核层同样存在一个类似的内核函数`RtlCreateUserThread`,但需要注意的是此函数未被公开,`RtlCreateUserThread`其实是对`NtCreateThreadEx`的包装,但最终会调用`ZwCreateThread`来实现注入,`RtlCreateUserThread`是`CreateRemoteThread`的底层实现。
2542 1
Qt通过QProcess启动进程并传递命令行参数
Qt通过QProcess启动进程并传递命令行参数
636 0
|
网络协议
驱动开发:内核RIP劫持实现DLL注入
本章将探索内核级DLL模块注入实现原理,DLL模块注入在应用层中通常会使用`CreateRemoteThread`直接开启远程线程执行即可,驱动级别的注入有多种实现原理,而其中最简单的一种实现方式则是通过劫持EIP的方式实现,其实现原理可总结为,挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,并把相关的指令机器码和数据拷贝到里面去,然后直接修改目标进程EIP使其强行跳转到我们拷贝进去的相关机器码位置,执行相关代码后,然后再次跳转回来执行原始指令集。
382 0