-
2021-05-26 07:37:23
Linux ptrace 简介
2017-06-15 Thursday
ptrace() 是一个由 Linux 内核提供的系统调用,允许一个用户态进程检查、修改另一个进程的内存和寄存器,通常用在类似 gdb、strace 的调试器中,用来实现断点调试、系统调用的跟踪。
你想过怎么实现对系统调用的拦截吗?你尝试过通过改变系统调用的参数来愚弄你的系统 kernel 吗?你想过调试器是如何使运行中的进程暂停并且控制它吗?
这里简单介绍如何使用该接口。
简介
在执行系统调用之前,内核会先检查当前进程是否处于被 “跟踪” traced 状态,如果是,内核暂停当前进程并将控制权交给跟踪进程,使跟踪进程得以察看或者修改被跟踪进程的寄存器。
其中函数的声明如下:
#include
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
ptrace有四个参数:
1). enum __ptrace_request request:指示了ptrace要执行的命令。
2). pid_t pid: 指示ptrace要跟踪的进程。
3). void *addr: 指示要监控的内存地址。
4). void *data: 存放读取出的或者要写入的数据。
如下是一个示例,父进程会 fork 出了一个子进程,然后跟踪它。
#include
#include
#include
#include
#include
#include
#include
#define log_info(...) do { printf("info : " __VA_ARGS__); putchar('\n'); } while(0);
#define log_error(...) do { printf("error: " __VA_ARGS__); putchar('\n'); } while(0);
int main(void)
{
pid_t pid;
int status, insyscall = 0;
long orax, rax, params[3];
struct user_regs_struct regs;
pid = fork();
if (pid < 0) {
log_error("fork failed, %s.", strerror(errno));
exit(EXIT_FAILURE); /* 1 */
} else if (pid == 0){
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execlp("/usr/bin/ls", "ls", NULL);
exit(EXIT_SUCCESS);
}
log_info("current pid %d, child pid %d.", getpid(), pid);
while (1) {
wait(&status);
//wait4(pid, &sta, 0, &ru);
if (WIFEXITED(status)) {
log_info("child exited with %d.", status);
break;
}
orax = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);
if (orax == SYS_write) {
if (insyscall == 0) {
insyscall = 1;
params[0] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDI, NULL);
params[1] = ptrace(PTRACE_PEEKUSER, pid, 8 * RSI, NULL);
params[2] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDX, NULL);
log_info("write called with %ld, %ld, %ld.",
params[0], params[1], params[2]);
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
log_info("write called with %lld, %lld, %lld.",
regs.rdi, regs.rsi, regs.rdx);
} else {
insyscall = 0;
rax = ptrace(PTRACE_PEEKUSER, pid, 8 * RAX, NULL);
log_info("write returned with %ld.", rax);
}
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}
return 0;
}
进程状态切换
在子进程中调用 exec 函数之前,首先通过 PTRACE_TRACEME 参数告知内核允许等待其它进程跟踪,那么在执行到 execve() 函数时会将控制权交换给父进程。
此时父进程在使用 wait() 函数来等待来自内核的通知,现在它得到了通知,那么接下看可以开始察看子进程都作了些什么,比如看看寄存器的值之类。
查看状态
在进入系统调用时,内核会将 eax 设置为系统调用号,那么此时可以使用 PTRACE_PEEKUSER 获取对应的参数,然后再通过PTRACE_CONT 使子进程继续运行。
其中实际的系统调用号可以在头文件 asm/unistd_64.h 中查看;在使用 PTRACE_PEEKUSER 时入参通过 ORIG_RAX 查看,而返回值通过 RAX 查看。
PTRACE_SYSCALL 类似于 PTRACE_CONT ,不过在接下来系统调用的入参或者出参时仍然会阻塞。
示例 2 获取一个具体系统调用的参数,实际上也就是如何获取寄存器中的值,这里采用了两种方式,一种与上述获取系统调用号方式一致,另外一种是通过一次调用直接获取。
注意,不同的系统调用获取参数的方式也有所区别,详细可以查看 man 2 syscall 中的 Architecture calling conventions 内容。
例如,对于 x86_64 中的 write() 来说,其声明如下:
ssize_t write(int fd, const void *buf, size_t count);
那么对应的系统调用号为 rax ,三个入参 fd buf count 分别对应了寄存器 rdi rsi rdx 三个。
示例
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define log_info(...) do { printf("info : " __VA_ARGS__); putchar('\n'); } while(0);
#define log_error(...) do { printf("error: " __VA_ARGS__); putchar('\n'); } while(0);
void reverse(pid_t pid, long addr, int len)
{
char *buff, tmp;
long i, j, value, offset;
union {
long value;
char chars[sizeof(long)];
} data;
buff = (char *)malloc(len + 1);
if (buff == NULL)
exit(1);
for (i = 0, offset = 0; i < len / (long)sizeof(long); i++, offset += sizeof(long)) {
value = ptrace(PTRACE_PEEKDATA, pid, addr + offset, NULL);
memcpy(buff + offset, &value, sizeof(long));
}
if (len % sizeof(long)) {
value = ptrace(PTRACE_PEEKDATA, pid, addr + offset, NULL);
memcpy(buff + offset, &value, len % sizeof(long));
}
buff[len] = 0;
for(i = 0, j = len - 2; i <= j; ++i, --j) {
tmp = buff[i];
buff[i] = buff[j];
buff[j] = tmp;
}
for (i = 0, offset = 0; i < len / (long)sizeof(long); i++, offset += sizeof(long)) {
memcpy(data.chars, buff + offset, sizeof(long));
ptrace(PTRACE_POKEDATA, pid, addr + offset, data.value);
}
if (len % sizeof(long)) {
memcpy(data.chars, buff + offset, sizeof(long));
ptrace(PTRACE_POKEDATA, pid, addr + offset, data.value);
}
free(buff);
}
int main()
{
pid_t pid;
int status, insyscall = 0;
long orax, rax, params[3];
pid = fork();
if (pid < 0) {
log_error("fork failed, %s.", strerror(errno));
exit(EXIT_FAILURE); /* 1 */
} else if (pid == 0){
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execlp("/usr/bin/ls", "ls", NULL);
exit(EXIT_SUCCESS);
}
log_info("current pid %d, child pid %d.", getpid(), pid);
while (1) {
wait(&status);
//wait4(pid, &sta, 0, &ru);
if (WIFEXITED(status)) {
log_info("child exited with %d.", status);
break;
}
orax = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL);
if (orax == SYS_write) {
if (insyscall == 0) {
insyscall = 1;
params[0] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDI, NULL); /* fd */
params[1] = ptrace(PTRACE_PEEKUSER, pid, 8 * RSI, NULL); /* buff */
params[2] = ptrace(PTRACE_PEEKUSER, pid, 8 * RDX, NULL); /* count */
log_info("write called with %ld, %ld, %ld.",
params[0], params[1], params[2]);
reverse(pid, params[1], params[2]);
} else {
insyscall = 0;
rax = ptrace(PTRACE_PEEKUSER, pid, 8 * RAX, NULL);
log_info("write returned with %ld.", rax);
}
}
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
}
return 0;
}
如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^
支付宝打赏
微信打赏
更多相关内容 -
python-ptrace:python-ptrace是ptrace库的Python绑定
2021-05-05 04:56:00python-ptrace python-ptrace是使用以Python编写的ptrace(Linux,BSD和Darwin系统调用以跟踪进程)的调试器。 python-ptrace是在GNU GPLv2许可下用Python编写的开源项目。 它支持Python 3.6及更高版本。 -
ptrace注入实例
2021-01-01 10:12:29ptrace注入实例代码, -
ptrace
2021-12-06 15:20:19sys/ptrace.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <sys/reg.h> int main(int argc, char *argv[]) { pid_t pid = fork(); if(pid < 0) { ...#include <stdio.h> #include <sys/ptrace.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <sys/reg.h> int main(int argc, char *argv[]) { pid_t pid = fork(); if(pid < 0) { printf("fork failed\n"); exit(-1); }else if(pid == 0){ ptrace(PTRACE_TRACEME, 0, NULL, NULL); execve(argv[1], NULL, NULL); } else { int status; int bit; long num; long ret; wait(&status); if(WIFEXITED(status)) { return 0; } num = ptrace(PTRACE_PEEKUSER, pid, ORIG_RAX * 8, NULL); printf("system call num = %ld\n", num); ptrace(PTRACE_SYSCALL, pid, NULL, NULL); while(1){ wait(&status); if(WIFEXITED(status)) return 0; if(bit){ num = ptrace(PTRACE_PEEKUSER, pid, ORIG_RAX * 8, NULL); printf("system call num = %ld", num); bit = 0; }else { ret = ptrace(PTREACE_PEEKUSER, pid, RAX * 8, NULL); printf("system call return = %ld\n", ret); bit = 1; } ptrace(PTRACE_SYSCALL, pid, NULL, NULL); } } }
-
ptrace安卓程序注入例子
2018-02-11 22:38:07安卓程序ptrace注入例子,送给想研究手游注入的同学 使用JNI注入 -
dlinject:在没有ptrace的情况下将共享库(即任意代码)注入实时linux进程
2021-02-04 15:14:02在不使用ptrace的情况下,将共享库(即任意代码)注入实时linux进程中。 受和启发。 用法 .___.__ .__ __ __ __| _/| | |__| ____ |__| ____ _____/ |_ ______ ___.__. / __ | | | | |/ \ | |/ __ \_/ ___\ __\ ... -
iOS安全防护系列之ptrace反调试与汇编调用系统方法详解
2020-08-27 05:41:16主要给大家介绍了关于iOS安全防护系列之ptrace反调试与汇编调用系统方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
Linux下Ptrace()调用的安全分析.pdf
2021-09-07 00:10:54Linux下Ptrace()调用的安全分析.pdf -
5.ptrace注入与zygote区别和联系.V2EE
2020-02-28 20:05:23ptrace注入与zygote区别和联系.V2EEptrace注入与zygote区别和联系.V2EE -
论文研究-Linux下Ptrace()调用的安全分析.pdf
2019-07-22 20:30:36对Linux下的系统调用Ptrace()所拥有的进程跟踪和控制调试功能进行了分析;结合内核漏洞的具体实例研究其对系统可能造成的安全威胁;最后就病毒技术中的一项关键技术——隐藏,讨论了Ptrace()在Linux病毒隐藏技术中的... -
Linux ptrace 的实现
2021-12-04 20:35:05前言:ptrace 是 Linux 内核提供的非常强大的系统调用,通过 ptrace 可以实现进程的单步调试和收集系统调用情况。比如 strace 和 gdb 都是基于 ptrace 实现的,strace 可以显示进程调用了哪些系统调用,gdb 可以实现...前言:ptrace 是 Linux 内核提供的非常强大的系统调用,通过 ptrace 可以实现进程的单步调试和收集系统调用情况。比如 strace 和 gdb 都是基于 ptrace 实现的,strace 可以显示进程调用了哪些系统调用,gdb 可以实现对进程的调试。本文介绍这些工具的底层 ptrace 是如何实现的。这里选用了 1.2.13 的早期版本,原理是类似的,新版内核代码过多,没必要陷入过多细节中。
1 进程调试
ptrace 系统调用的实现中包含了很多功能,首先来看一下单步调试的实现。通过 ptrace 实现单步调试的方式有两种。
- 父进程执行 fork 创建一个子进程,通过 ptrace 设置子进程为 PF_PTRACED 标记,然后执行 execve 加载被调试的程序。
- 通过 ptrace attach 到指定的 pid 完成对进程的调试(控制)。
首先看一下第一种的实现。
1.1 方式1
pid_t pid = fork(); // 子进程 if (pid == 0) { ptrace(PTRACE_TRACEME,0,NULL,NULL); // 加载被调试的程序 execve(argv[1], NULL, NULL); }
执行 fork 创建子进程后,通过 ptrace 的 PTRACE_TRACEME 指示操作系统设置子进程为被调试(设置 PF_PTRACED 标记)。 来看一下这一步操作系统做了什么事情。
asmlinkage int sys_ptrace(long request, long pid, long addr, long data) { if (request == PTRACE_TRACEME) { current->flags |= PF_PTRACED; return 0; } }
这一步非常简单,接着看 execve 加载程序到内存执行时又是如何处理的。
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs) { // 加载程序 for (fmt = formats ; fmt ; fmt = fmt->next) { int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary; retval = fn(&bprm, regs); } }
do_execve 逻辑非常复杂,不过我们只关注需要的就好。do_execve 通过钩子函数加载程序,我们看看 formats 是什么。
struct linux_binfmt { struct linux_binfmt * next; int *use_count; int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); int (*load_shlib)(int fd); int (*core_dump)(long signr, struct pt_regs * regs); }; static struct linux_binfmt *formats = &aout_format; int register_binfmt(struct linux_binfmt * fmt) { struct linux_binfmt ** tmp = &formats; if (!fmt) return -EINVAL; if (fmt->next) return -EBUSY; while (*tmp) { if (fmt == *tmp) return -EBUSY; tmp = &(*tmp)->next; } *tmp = fmt; return 0; }
可以看到 formats 是一个链表。可以通过 register_binfmt 函数注册节点。那么谁调用了这个函数呢?
struct linux_binfmt elf_format = { NULL, NULL, load_elf_binary, load_elf_library, NULL }; int init_module(void) { register_binfmt(&elf_format); return 0; }
所以最终调用了 load_elf_binary 函数加载程序。同样我们只关注相关的逻辑。
if (current->flags & PF_PTRACED) send_sig(SIGTRAP, current, 0);
load_elf_binary 中会判断如果进程设置了 PF_PTRACED 标记,那么会给当前进程发送一个 SIGTRAP 信号。接着看信号处理函数的相关逻辑。
if ((current->flags & PF_PTRACED) && signr != SIGKILL) { current->exit_code = signr; // 修改当前进程(被调试的进程)为暂停状态 current->state = TASK_STOPPED; // 通知父进程 notify_parent(current); // 调度其他进程执行 schedule(); }
所以程序被加载到内存后,根本没有机会执行就直接被修改为暂停状态了,接下来看看 notify_parent 通知父进程干什么。
void notify_parent(struct task_struct * tsk) { // 给父进程发送 SIGCHLD 信号 if (tsk->p_pptr == task[1]) tsk->exit_signal = SIGCHLD; send_sig(tsk->exit_signal, tsk->p_pptr, 1); wake_up_interruptible(&tsk->p_pptr->wait_chldexit); }
父进程收到信号后,可以通过 sys_ptrace 控制子进程,sys_ptrace 还提供了很多功能,比如读取子进程的数据。
// pid 为子进程 id num = ptrace(PTRACE_PEEKUSER, pid, ORIG_RAX * 8, NULL);
这个就不展开了,主要是内存的校验和数据读取。这里讲一下 PTRACE_SINGLESTEP 命令,这个命令控制子进程单步执行的。
case PTRACE_SINGLESTEP: { /* set the trap flag. */ long tmp; child->flags &= ~PF_TRACESYS; // 设置 eflags 的单步调试 flag tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) | TRAP_FLAG; put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); // 修改子进程状态为可执行 child->state = TASK_RUNNING; child->exit_code = data; return 0; }
PTRACE_SINGLESTEP 让子进程重新进入运行状态,但是有一个很关键的是,设置好了单步调试 flag。我们看看 trap flag 是什么。
A trap flag permits operation of a processor in single-step mode. If such a flag is available, debuggers can use it to step through the execution of a computer program.
也就是说,子进程执行一个指令后,就会被中断,然后系统会给被调试进程发送 SIGTRAP 信号。同样,被调试进程在信号处理函数里,通知父进程,从而控制权又回到了父进程手中,如此循环。
1.2 方式2
除了开始时通过 ptrace 设置进程调试,也可以通过 ptrace 动态设置调试进程的能力,具体是通过 PTRACE_ATTACH 命令实现的。
if (request == PTRACE_ATTACH) { // 设置被调试标记 child->flags |= PF_PTRACED; // 设置和父进程的关系 if (child->p_pptr != current) { REMOVE_LINKS(child); child->p_pptr = current; SET_LINKS(child); } // 给被调试进程发送 SIGSTOP 信号 send_sig(SIGSTOP, child, 1); return 0; }
前面已经分析过,信号处理函数里会设置进程为暂停状态,然后通知主进程,主进程就可以控制子进程,具体和前面流程一样。
2 跟踪系统调用
ptrace 处理追踪进程执行过程之外,还可以实现跟踪系统调用。具体是通过 PTRACE_SYSCALL 命令实现。
case PTRACE_SYSCALL: case PTRACE_CONT: { long tmp; // 设置 PF_TRACESYS 标记 if (request == PTRACE_SYSCALL) child->flags |= PF_TRACESYS; child->exit_code = data; child->state = TASK_RUNNING; // 清除 trap flag 标记 tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~TRAP_FLAG; put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); return 0; }
看起来很简单,就是设置了一个新的标记 PF_TRACESYS。看看这个标记有什么用。
// 调用 syscall_trace 函数 1: call _syscall_trace movl ORIG_EAX(%esp),%eax // 调用系统调用 call _sys_call_table(,%eax,4) movl %eax,EAX(%esp) # save the return value movl _current,%eax movl errno(%eax),%edx negl %edx je 1f movl %edx,EAX(%esp) orl $(CF_MASK),EFLAGS(%esp) # set carry to indicate error // 调用 syscall_trace 函数 1: call _syscall_trace
可以看到在系统调用的前后都有一个 syscall_trace 的逻辑,所以在系统调用前和后,我们都可以做点事情。来看看这个函数做了什么。
asmlinkage void syscall_trace(void) { // 暂停子进程,通知父进程,并调度其他进程执行 current->exit_code = SIGTRAP; current->state = TASK_STOPPED; notify_parent(current); schedule(); }
这里的逻辑就是把逻辑切换到主进程中,然后主进程就可以通过命令获取被调试进程的系统调用信息。下面是一个追踪进程所有系统调用的例子。
/* use ptrace to find all system call that call by certain process */ #include <sys/ptrace.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> #include <stdio.h> #include <sys/reg.h> int main(int argc, char *argv[]) { pid_t pid = fork(); if (pid < 0) { printf("fork failed"); exit(-1); } else if (pid == 0) { // set state of child process to PTRACE ptrace(PTRACE_TRACEME,0,NULL,NULL); // child will change to stopped state when in execve call, then send the signal to parent char **args = (char **)malloc(sizeof(char *) * argc); for (int i = 1; i < argc; i++) { args[i - 1] = argv[i]; } args[argc - 1] = NULL; execve(argv[1], args, NULL); free(args); } else { int status; int bit = 1; long num; long ret; // wait for child wait(&status); if(WIFEXITED(status)) return 0; // this is for execve call which will not return, and for os of 64-it => ORIG_RAX * 8 or os of 32-it => ORIG_EAX * 4 num = ptrace(PTRACE_PEEKUSER, pid, ORIG_RAX * 8, NULL); printf("system call num = %ld\n", num); ptrace(PTRACE_SYSCALL, pid, NULL, NULL); while(1) { wait(&status); if(WIFEXITED(status)) return 0; // for enter system call if(bit) { num = ptrace(PTRACE_PEEKUSER, pid, ORIG_RAX * 8, NULL); printf("system call num = %ld", num); bit = 0; } else { // for return of system call ret = ptrace(PTRACE_PEEKUSER, pid, RAX*8, NULL); printf("system call return = %ld \n", ret); bit = 1; } // let this child process continue to run until call next system call ptrace(PTRACE_SYSCALL,pid,NULL,NULL); } } }
总结
ptrace 功能复杂而强大,理解它的原理对理解其他技术和工具都非常有意义,本文大概做了一个介绍,有兴趣的同学可以自行查看源码。
-
【ptrace注入】linux下ptrace注入器的实现
2021-06-04 01:49:37} bool ptrace_continue() { if (ptrace(PTRACE_CONT, targetPid, NULL, NULL) ) { printf("KKptrace->attach():ptrace continue error, pid:%d", targetPid); return false; } return true; } bool detach() { //...[Asm] 纯文本查看 复制代码#include "iostream"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class Utils
{
public:
static vector uint64ToByteList(uint64_t value)
{
vector byteList;
for (size_t i = 0; i < 8; i++)
{
int tempValue = value;
tempValue = value << (8 * (7 - i));
tempValue = tempValue >> 8 * 8;
byteList.push_back(tempValue);
}
return byteList;
}
static vector uint32ToByteList(uint32_t value, int number = 4)
{
vector byteList;
if (number <= 0 || number > 4)
{
return byteList;
}
for (size_t i = 0; i < number; i++)
{
int tempValue = value;
if (i == 0)
{
tempValue = value << 24;
}
if (i == 1)
{
tempValue = value << 16;
}
if (i == 2)
{
tempValue = value << 8;
}
tempValue = tempValue >> 24;
byteList.push_back(tempValue);
}
return byteList;
}
static vector uint16ToByteList(uint32_t value)
{
vector byteList;
int tempValue = 0;
tempValue = value << 8 >> 8;
byteList.push_back(tempValue);
tempValue = value >> 8;
byteList.push_back(tempValue);
return byteList;
}
void static print_byteList_hexArray(vector data)
{
for (const auto& item : data)
{
cout << std::hex << (int)item << " ";
}
cout << endl;
}
string static byteListToString(vector data)
{
std::string res;
res.insert(res.begin(), data.begin(), data.end());
return res;
}
void static print_regs(struct user_regs_struct* reg_addr)
{
printf("--------------------------\n");
printf("print regs\n");
printf("rax=0x%016" PRIx64 "\n", reg_addr->rax);
printf("rcx=0x%016" PRIx64 "\n", reg_addr->rcx);
printf("rdx=0x%016" PRIx64 "\n", reg_addr->rdx);
printf("rbx=0x%016" PRIx64 "\n", reg_addr->rbx);
printf("rsp=0x%016" PRIx64 "\n", reg_addr->rsp);
printf("rbp=0x%016" PRIx64 "\n", reg_addr->rbp);
printf("rsi=0x%016" PRIx64 "\n", reg_addr->rsi);
printf("rdi=0x%016" PRIx64 "\n", reg_addr->rdi);
printf("rip=0x%016" PRIx64 "\n", reg_addr->rip);
printf("--------------------------\n");
}
};
class KKptrace
{
private:
pid_t targetPid = 0;
const char localLibcPath[39] = "/usr/lib/x86_64-linux-gnu/libc-2.32.so";
void* getLibcBaseAddr(pid_t pid, const char* localLibcPath) {
char filepath[512];
void* moduleBaseAddr = NULL;
snprintf(filepath, 512, "/proc/%d/maps", pid);
FILE* f = fopen(filepath, "r");
char line[512];
while (!feof(f)) {
memset(line, 0, 512);
fgets(line, 512, f);
// printf(line);
string lineStr = line;
if (lineStr.find(localLibcPath) != -1)
{
int index = lineStr.find("-");
string addrStr = lineStr.substr(0, index);
stringstream ss;
//puts(lineStr.c_str());
ss << hex << addrStr.c_str();
ss >> moduleBaseAddr;
break;
}
}
if (moduleBaseAddr == NULL)
{
cout << "getLibcBaseAddr() error,moduleName=" << localLibcPath << endl;
}
fclose(f);
return moduleBaseAddr;
}
void* getRemoteFuncAddr(void* localFuncAddr, const char* libcName, pid_t remotePid = 0, void* remoteLibcBaseAddr = NULL) {
void* remoteFuncAddr;
if (remotePid == 0)
{
remotePid = targetPid;
}
if (remoteLibcBaseAddr == NULL)
{
remoteLibcBaseAddr = getLibcBaseAddr(remotePid, libcName);
}
void* localLibcBaseAddr = getLibcBaseAddr(getpid(), libcName);
if (localLibcBaseAddr==NULL || remoteLibcBaseAddr==NULL)
{
return NULL;
}
remoteFuncAddr = (void*)(uint64_t)localFuncAddr - (uint64_t)localLibcBaseAddr + (uint64_t)remoteLibcBaseAddr;
return remoteFuncAddr;
}
public:
// attachment is premise of all opeartions
bool isAttachSuccess = false;
struct user_regs_struct callBefore_regs;
bool attach(pid_t pid)
{
targetPid = pid;
isAttachSuccess = false;
int status = 0;
if (ptrace(PTRACE_ATTACH, targetPid, NULL, 0) < 0)
{
printf("KKptrace->attach():attach process error, pid:%d\n", targetPid);
return false;
}
printf("KKptrace->attach(): process success pid:%d\n", targetPid);
waitpid(targetPid, &status, WUNTRACED);
isAttachSuccess = true;
return true;
}
bool ptrace_continue()
{
if (ptrace(PTRACE_CONT, targetPid, NULL, NULL) < 0)
{
printf("KKptrace->attach():ptrace continue error, pid:%d", targetPid);
return false;
}
return true;
}
bool detach()
{
//kill(targetPid, SIGSTOP);
//waitpid(targetPid, NULL, 0);
if (ptrace(PTRACE_DETACH, targetPid, NULL, 0) < 0)
{
printf("KKptrace->attach():detach process error, pid:%d\n", targetPid);
return false;
}
return true;
}
uint32_t memoryRead_uint32(void* addr)
{
if (!isAttachSuccess)
{
return 0;
}
uint32_t re = ptrace(PTRACE_PEEKTEXT, targetPid, addr, 0);
return re;
}
vector memoryRead_bytes(void* addr, int number)
{
int i = 0;
uint32_t tempValue = 0;
vector reByteList;
vector tempByteList;
for (i = 0; i < number / 4; i++)
{
tempValue = memoryRead_uint32(addr + i * 4);
//cout << "readMemory_bytes.for->tempValue=" << std::hex << tempValue << endl;
tempByteList = Utils::uint32ToByteList(tempValue, 4);
reByteList.insert(reByteList.end(), tempByteList.begin(), tempByteList.end());
}
if (number % 4)
{
tempValue = memoryRead_uint32(addr + i * 4);
tempByteList = Utils::uint32ToByteList(tempValue, number % 4);
reByteList.insert(reByteList.end(), tempByteList.begin(), tempByteList.end());
}
return reByteList;
}
bool memoryWrite_string(void* addr, string data)
{
if (data.size() == 0)
{
return false;
}
vector byteList;
for (size_t i = 0; i < data.size(); i++)
{
byteList.push_back(data[i]);
}
byteList.push_back(0);
return memoryWrite_bytes(addr, byteList);
}
bool memoryWrite_chars(void* addr, const char* data)
{
vector byteList;
for (size_t i = 0; data[i] != 0; i++)
{
byteList.push_back(data[i]);
}
byteList.push_back(0);
return memoryWrite_bytes(addr, byteList);
}
bool memoryWrite_bytes(void* addr, vector data)
{
if (!isAttachSuccess)
{
return false;
}
uint64_t writeValue = 0;
void* writeAddr = addr;
size_t i;
for (i = 0; i < data.size() / 8; i++)
{
writeValue = 0;
for (size_t j = 0; j < 8; j++)
{
uint64_t tempValue = 0;
tempValue = data[i * 8 + j];
tempValue = tempValue << (8 * j);
writeValue = writeValue + tempValue;
}
writeAddr = addr + 8 * i;
if (ptrace(PTRACE_POKETEXT, targetPid, writeAddr, writeValue) < 0)
{
return false;
}
}
int yu = data.size() % 8;
if (yu)
{
writeValue = 0;
writeAddr = addr + 8 * i;
// 未对齐64bit的情况,需要先取值再覆盖。
vector readByteList = memoryRead_bytes(writeAddr, 8);
for (size_t j = yu; j < 8; j++)
{
uint64_t tempValue = readByteList[j];
tempValue = tempValue << 8 * j;
writeValue = writeValue + tempValue;
}
for (size_t j = 0; j < yu; j++)
{
uint64_t tempValue = 0;
tempValue = data[i * 8 + j];
tempValue = tempValue << (8 * j);
writeValue = writeValue + tempValue;
}
if (ptrace(PTRACE_POKETEXT, targetPid, writeAddr, writeValue) < 0)
{
return false;
}
}
return true;
}
bool memoryWrite_int64(void* addr, int64_t value)
{
return memoryWrite_bytes(addr, Utils::uint64ToByteList(value));
}
bool memoryWrite_uint64(void* addr, uint64_t value)
{
return memoryWrite_bytes(addr, Utils::uint64ToByteList(value));
}
bool memoryWrite_int32(void* addr, int32_t data)
{
return memoryWrite_bytes(addr, Utils::uint32ToByteList(data));
}
bool memoryWrite_uint32(void* addr, uint32_t data)
{
return memoryWrite_bytes(addr, Utils::uint32ToByteList(data));
}
bool memoryWrite_int16(void* addr, int16_t data)
{
return memoryWrite_bytes(addr, Utils::uint16ToByteList(data));
}
bool memoryWrite_uint16(void* addr, uint16_t data)
{
return memoryWrite_bytes(addr, Utils::uint16ToByteList(data));
}
bool memoryWrite_int8(void* addr, int8_t data)
{
vector byteList{ (uint8_t)data };
return memoryWrite_bytes(addr, byteList);
}
bool memoryWrite_uint8(void* addr, uint8_t data)
{
vector byteList{ data };
return memoryWrite_bytes(addr, byteList);
}
bool getRegs(struct user_regs_struct* regs_addr)
{
if (ptrace(PTRACE_GETREGS, targetPid, NULL, regs_addr) < 0)
{
printf("KKptrace->getRegs(): fail\n");
return false;
}
return true;
}
void regsPrint()
{
struct user_regs_struct regs;
getRegs(®s);
Utils::print_regs(®s);
}
bool setRegs(struct user_regs_struct* regs_addr, bool isPrintSetBeforeAndAfter = false)
{
struct user_regs_struct regs;
if (isPrintSetBeforeAndAfter)
{
puts("set before");
Utils::print_regs(regs_addr);
}
if (ptrace(PTRACE_SETREGS, targetPid, NULL, regs_addr) < 0)
{
printf("KKptrace->setRegs(): fail\n");
return false;
}
if (isPrintSetBeforeAndAfter)
{
puts("set before");
getRegs(®s);
Utils::print_regs(®s);
}
return true;
}
void call_begin()
{
// 1、获取并保留当前寄存器环境,以便函数执行完毕后恢复程序正常流程
getRegs(&callBefore_regs);
}
void call_end(bool isWaitToRun=false)
{
// 将寄存器恢复到call之前的状态
setRegs(&callBefore_regs);
int state = 0;
ptrace_continue();
if (isWaitToRun)
{
waitpid(targetPid, &state, WUNTRACED);
}
}
bool call(void* localFunc, vector paramList, int64_t* reValue=NULL,const char* libcName = NULL)
{
string libcNameStr = "libc-2.32.so";
if (libcName == NULL || string(libcName).size() == 0)
{
libcName = libcNameStr.c_str();
}
void* funcAddr = getRemoteFuncAddr((void*)localFunc, libcName);
if (funcAddr==NULL)
{
return false;
}
int64_t re = call_funcAddr(funcAddr, paramList);
if (reValue)
{
*reValue = re;
}
return true;
}
int64_t call_funcAddr(void* targetFuncAddr, vector paramList)
{
struct user_regs_struct regs_ori;
struct user_regs_struct regs;
getRegs(®s_ori);
memcpy(®s, ®s_ori, sizeof(regs_ori));
// 获取当前寄存器环境
// 多于6个参数通过栈传递
regs.rsp -= sizeof(void*);
size_t errRet = 0;
// 返回地址弄成0导致错误停止
memoryWrite_int64((void*)regs.rsp, 0);
// 前6个参数 通过寄存器传参
switch (paramList.size()) {
case 6:
regs.r9 = paramList[5];
case 5:
regs.r8 = paramList[4];
case 4:
regs.rcx = paramList[3];
case 3:
regs.rdx = paramList[2];
case 2:
regs.rsi = paramList[1];
case 1:
regs.rdi = paramList[0];
break;
}
// 参数>6个,后续参数需要压栈。
if (paramList.size() > 6) {
regs.rsp -= (paramList.size() - 6) * sizeof(long);
for (size_t i = 6; i < paramList.size(); i++)
{
memoryWrite_int64((void*)regs.rsp + i * 8, paramList[6 + i]);
}
}
// 通过修改RIP,修改CPU执行位置
regs.rax = 0;
regs.rip = (unsigned long long) targetFuncAddr;
setRegs(®s);
int state = 0;
ptrace_continue();
waitpid(targetPid, &state, WUNTRACED);
getRegs(®s);
uint64_t reValue = regs.rax;
return reValue;
}
/**
* [url=home.php?mod=space&uid=190858]@brief[/url] 将模块注入
* [url=home.php?mod=space&uid=952169]@Param[/url] modulePath 模块路径,例如./test.so
* @param funcName 函数名,默认为空,为空则只load so文件 不执行
* @param isRestore 注入后是否恢复程序正常运行,默认恢复;若设置不恢复,则需要手动调用call_end();
* @param isRestore 注入后是否恢复程序正常运行,默认恢复;若设置不恢复,则需要手动调用call_end();
*
* [url=home.php?mod=space&uid=155549]@Return[/url] 返回说明
* -false fail
* -true succeed
*/
bool inject(const char* modulePath, const char *funcName=NULL,bool isRestore = true,int64_t* reValue=NULL)
{
bool isCallSucess = false;
vector paramList;
call_begin();
// 分配内存地址
// mmap(0,0x100,可读可写可执行,…)
paramList.clear();
paramList.push_back(0);
paramList.push_back(0x100);
paramList.push_back(PROT_READ | PROT_WRITE | PROT_EXEC);
paramList.push_back(MAP_ANONYMOUS | MAP_PRIVATE);
paramList.push_back(0);
paramList.push_back(0);
int64_t remoteBuffer;
call((void*)mmap, paramList,&remoteBuffer);
if (remoteBuffer == -1)
{
printf("mmap() fail");
return false;
}
printf("mmap sucess addr=%p\n", remoteBuffer);
char realPath[500];
realpath(modulePath, realPath);
memoryWrite_chars((void*)remoteBuffer, realPath);
// 动态加载so文件
// libcAddr=dlopen(realPath,RTLD_NOW|RTLD_GLOBAL)
paramList.clear();
paramList.push_back(remoteBuffer);
paramList.push_back(RTLD_NOW | RTLD_GLOBAL);
int64_t moduleHandle;
isCallSucess = call((void*)dlopen, paramList, &moduleHandle, "libdl-2.32.so");
if (!isCallSucess)
{
return false;
}
printf("moduleHandle=%p\n", moduleHandle);
if (!moduleHandle)
{
paramList.clear();
int64_t dlerrorReAddr;
call((void*)dlerror, paramList, &dlerrorReAddr,"libdl-2.32.so");
printf("dlopen() error,text=");
paramList.push_back(dlerrorReAddr);
call((void*)puts, paramList);
return false;
}
// 如果函数名为空,则只load so 不执行函数;
if (funcName==NULL || string(funcName).size()==0)
{
return true;
}
memoryWrite_chars((void*)remoteBuffer, funcName);
// 获取函数地址
// libcAddr=dlsym(libcAddr,funcName)
paramList.clear();
paramList.push_back(moduleHandle);
paramList.push_back(remoteBuffer);
int64_t funcAddr;
isCallSucess = call((void*)dlsym, paramList,&funcAddr, "libdl-2.32.so");
if (!isCallSucess)
{
return false;
}
printf("func addr=%p\n", funcAddr);
if (!funcAddr)
{
printf("dlsym() error");
return false;
}
// 执行已经加载成功的模块中的函数
paramList.clear();
int64_t re = call_funcAddr((void*)funcAddr, paramList);
if (reValue)
{
*reValue = re;
}
if (isRestore)
{
call_end();
}
return true;
}
};
int main()
{
KKptrace* kkptrace = new KKptrace();
pid_t forkPid = fork();
if (forkPid == 0)
{
char* argv[] = {};
execv("/home/ubuntu/Desktop/android_learningRoad/ptrace_reject/plsInjectMe", NULL);
return 0;
}
sleep(1);
cout << "childPid=" << forkPid << endl;
//pid_t forkPid;
//cin >> forkPid;
if (!kkptrace->attach(forkPid))
{
return 0;
}
int64_t re;
kkptrace->inject("/home/ubuntu/Desktop/android_learningRoad/ptrace_reject/libtest.so", "testEntry", true, &re);
cout << "injectAfterCall_re=" << re << endl;
puts("pls input any key exit");
getchar();
return 0;
}
-
【Android 逆向】ptrace 函数 ( C 标准库 ptrace 函数简介 | ptrace 函数真实作用 )
2021-10-29 13:03:26一、C 标准库 ptrace 函数简介、 二、ptrace 函数真实作用 -
Linux ptrace详细分析系列(一)
2021-05-16 11:40:16原标题:Linux ptrace详细分析系列(一) 本文为看雪论坛优秀文章看雪论坛作者ID:有毒备注:文章中使用的Linux内核源码版本为Linux 5.9,使用的Linux版本为Linux ubuntu 5.4.0-65-generic一、简述ptrace系统调用提供... -
Ptrace 详解
2019-10-02 09:26:19Ptrace 详解 引子: 1.在Linux系统中,进程状态除了我们所熟知的TASK_RUNNING,TASK_INTERRUPTIBLE,TASK_STOPPED等,还有一个TASK_TRACED。这表明这个进程处于什么状态? 2.strace可以方便的帮助我们记录进程所... -
ptrace使用和调试
2021-06-03 14:00:53Linux ptrace系统调用详解:利用 ptrace 设置硬件断点 <<软件调试>> 张银奎 2. ptrace函数原型 enum __ptrace_request { PTRACE_TRACEME = 0, //被调试进程调用 PTRACE_PEEKDATA = 2, //查看内存 ... -
利用ptrace设计一个简单的debugger调试器
2022-02-22 15:59:291. 程序的设计思路 1.1 设计思路 本次设计实现的debugger针对被调试进程主要实现了6项功能: 可以读取被调试进程CPU所有寄存器的值 可以对被调试进程进行单步调试 ...long ptrace(enum __ptrace_request request, pid_t -
系统调用ptrace和进程跟踪
2022-01-16 13:21:53为方便应用软件的开发和调试,从Unix的早期版本开始就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用ptrace。通过ptrace,一个进程可以动态地读写另一个进程的内存和寄存器,包括其指令空间、数据... -
【ptrace】android下ptrace注入器的实现
2021-03-11 11:08:02KKPtrace是我写的ptrace注入器类的名称,注入器将会实现进程附加、内存读、内存写、寄存器读写等操作。 目前已实现功能: 进程 进程附加 取消附加 继续运行 内存读 int32 byteList(字节数组,Vector<... -
Linux内核ptrace机制原理
2021-05-21 09:27:49Linux内核ptrace机制原理 -
Linux ptrace系统调用详解:利用 ptrace 设置硬件断点
2021-01-03 21:28:07《GDB调试之ptrace实现原理》 《C语言程序调用栈:backtrace+backtrace_symbols+backtrace_symbols_fd》 《strace实现原理:ptrace系统调用》 《Linux环境无文件渗透执行ELF:memfd_create、ptrace》 《利用... -
Android ptrace函数的实现
2021-06-07 03:02:44首先看sys/ptrace.h/bionic/libc/include/sys/ptrace.h我们在调用的时候使用的是PTRACE_的导出符号,glibc也导出了PT_开头的符号。PTRACE_开头的符号定义在/bionic/libc/kernel/uapi/linux/ptrace.h中都是int型的... -
如何利用Ptrace拦截和模拟Linux系统调用
2021-05-26 07:37:36写在前面的话ptrace(2)这个系统调用一般都跟调试离不开关系,它不仅是类Unix系统中本地调试器监控实现的主要机制,而且它还是strace系统调用常用的实现方法。ptrace()系统调用函数提供了一个进程(the “tracer”)... -
通过ptrace跟踪进程
2021-06-10 12:12:591. 任务环境与目标 1.1 实验机器 Ubuntu 20.04 64位 1.2 任务目标 ...ptrace的函数原型为:long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); 那么有关本任务中涉及到的一些re -
cpp-ptrace是process和trace的简写直译为进程跟踪
2019-08-16 06:06:39ptrace是process和trace的简写,直译为进程跟踪。它提供了一种使父进程得以监视和控制其子进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现系统调用的跟踪和断点调试。 -
Python-ptracer一个用于Python程序的基于ptrace跟踪的库
2019-08-10 18:48:26ptracer -- 一个用于Python程序的基于ptrace跟踪的库 -
GDB原理之ptrace实现原理
2021-02-21 18:49:44POKETEXT 4 #define PTRACE_POKEDATA 5 #define PTRACE_POKEUSR 6 #define PTRACE_CONT 7 #define PTRACE_KILL 8 #define PTRACE_SINGLESTEP 9 #define PTRACE_ATTACH 0x10 #define PTRACE_DETACH 0x11 #define ... -
【Android 逆向】ptrace 函数 ( ptrace 函数族 | 进程附着 | 进程脱离 | 进程数据读写权限 | 进程对应的...
2021-10-29 14:44:51一、ptrace 函数族、 1、进程附着、 2、进程脱离、 3、进程数据读写权限、 4、进程对应的主线程寄存器读写、 5、单步调试、 6、继续向后执行、 二、ptrace 函数族状态转换、