Android ptrace注入基础

一、Android ptrace注入基础

1.可执行文件建立

首先建立一个ELF可执行文件target,使用ndk-build进行编译

需要先在任意地方建立一个jni,然后在jni目录下建立Android.mk,Application.mk,target.c

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := target
LOCAL_SRC_FILES := target.c

include $(BUILD_EXECUTABLE)

Application.mk(ABI可以添加其他类型如x86)

APP_ABI := armeabi-v7a

target.c:

#include <stdio.h>
int count = 0;

void sevenWeapons(int number)
{
    char* str = "Hello,lzh!";
    printf("%s %d\n",str,number);
}

int main()
{
    while(1)
    {
        sevenWeapons(count);
        count++;
        sleep(1);
    }    
    return 0;
}

编写完成后,使用命令行进入jni目录,执行ndk-build
然后在jni的上一个目录就能看到一个libs目录,进入找到target,将其push到安卓中,赋予其权限,然后执行,就能看到如下情况

Hello,lzh! 0
Hello,lzh! 1
...

2. ptrace注入实现(一)

首先还是建立相关文件jni目录
Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := hook1
LOCAL_SRC_FILES := hook1.c

include $(BUILD_EXECUTABLE)

Application.mk

APP_ABI := armeabi-v7a

hook1.c

long getSysCallNo(int pid, struct pt_regs *regs)
{
    long scno = 0;
    scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
    if(scno == 0)
        return 0;

    if (scno == 0xef000000) {
        scno = regs->ARM_r7;
    } else {
        if ((scno & 0x0ff00000) != 0x0f900000) {
            return -1;
        }
        scno &= 0x000fffff;
    }
    return scno;    
}
void hookSysCallBefore(pid_t pid)
{
    struct pt_regs regs;
    int sysCallNo = 0;

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);    
    sysCallNo = getSysCallNo(pid, &regs);
    printf("Before SysCallNo = %d\n",sysCallNo);

    if(sysCallNo == __NR_write)
    {
        printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
    }
}
void hookSysCallAfter(pid_t pid)
{
    struct pt_regs regs;
    int sysCallNo = 0;

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);  
    sysCallNo = getSysCallNo(pid, &regs);

    printf("After SysCallNo = %d\n",sysCallNo);

    if(sysCallNo == __NR_write)
    {
        printf("__NR_write return: %ld\n",regs.ARM_r0);
    }

    printf("\n");
}
int main(int argc, char *argv[])
{
    if(argc != 2) {
        printf("Usage: %s <pid to be traced>\n", argv[0]);
        return 1;
    }

    pid_t pid;
    int status;
    pid = atoi(argv[1]);

    if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
    {
        printf("Trace process failed:%d.\n", errno);
        return 1;
    }

    ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

    while(1)
    {
        wait(&status);
        hookSysCallBefore(pid);
        ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

        wait(&status);
        hookSysCallAfter(pid);
        ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
    }

    ptrace(PTRACE_DETACH, pid, NULL, NULL);
    return 0;
}
hook1.c代码分析

getSysCallNo:

作用:获取system call编号

//获取system call编号
long getSysCallNo(int pid, struct pt_regs *regs)
{
    long scno = 0;
    //获取系统调用的SWI指令,这里一共有两个指令EABI,OABI,分别对应两个机器码
    scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
    if(scno == 0)
        return 0;
    //为EABI的时候,从r7中直接获取调用号
    if (scno == 0xef000000) {
        scno = regs->ARM_r7;
    } else {
        //为OABI的时候通过公式立即数(scno)=调用号 | 0x900000,先获取立即数,在计算出调用号
        if ((scno & 0x0ff00000) != 0x0f900000) {
            return -1;
        }
        scno &= 0x000fffff;
    }
    return scno;    
}

获取到system call的编号之后,我们可以进而获取到各个参数的值,我们可以看到第一个SysCallNo是162,也就是sleep函数。第二个SysCallNo是4,也就是write函数,因为printf本质就是调用write这个系统调用来完成的。

//hook system call前
void hookSysCallBefore(pid_t pid)
{
    struct pt_regs regs;
    int sysCallNo = 0;

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);    
    sysCallNo = getSysCallNo(pid, &regs);
    printf("Before SysCallNo = %d\n",sysCallNo);

    if(sysCallNo == __NR_write)
    {
        printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
    }
}
//hook system call后
void hookSysCallAfter(pid_t pid)
{
    struct pt_regs regs;
    int sysCallNo = 0;

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);  
    sysCallNo = getSysCallNo(pid, &regs);

    printf("After SysCallNo = %d\n",sysCallNo);

    if(sysCallNo == __NR_write)
    {
        printf("__NR_write return: %ld\n",regs.ARM_r0);
    }

    printf("\n");
}

编写完成后,还是执行ndk-build,得到文件push进安卓手机

然后执行重新执行target文件,再使用下面命令获取target的pid

adb shell "ps |grep "target""

在这里插入图片描述
获取到pid后,在执行hook1

./hook1 19140
hook2.c代码分析

还是hook之前的代码,代码如下,想了解原理就直接看注释吧
Applicaton.mk

#APP_OPTIM := release
APP_PLATFORM := android-15
APP_ABI := armeabi-v7a
NDK_TOOLCHAIN_VERSION=4.9
APP_PIE := false

Android.mk:

LOCAL_PATH := $(call my-dir)
APP_CFLAGS := -std=c++11
include $(CLEAR_VARS)
LOCAL_MODULE    := hook2
LOCAL_SRC_FILES := hook2.c

include $(BUILD_EXECUTABLE)

hook2.c

//获取system call编号
long getSysCallNo(int pid, struct pt_regs *regs)
{
    long scno = 0;
    scno = ptrace(PTRACE_PEEKTEXT, pid, (void *)(regs->ARM_pc - 4), NULL);
    if(scno == 0)
        return 0;

    if (scno == 0xef000000) {
        scno = regs->ARM_r7;
    } else {
        if ((scno & 0x0ff00000) != 0x0f900000) {
            return -1;
        }
        scno &= 0x000fffff;
    }
    return scno;    
}
void getdata(pid_t child, long addr,
             char *str, int len)
{   char *laddr;
    int i, j;
    union u {
            long val;//字符地址
            char chars[long_size];//字符
    }data;
    i = 0;
    j = len / long_size;//在arm32下long类型长度为4
    laddr = str;
	//先处理能除的部分
    while(i < j) {
        data.val = ptrace(PTRACE_PEEKDATA,
                          child, addr + i * 4,
                          NULL);//注入
        memcpy(laddr, data.chars, long_size);//将data.chars复制到laddr处
        ++i;
        laddr += long_size;//增加一个long的长度
    }//类似于链表
    j = len % long_size;
	//在处理剩下的
    if(j != 0) {
        data.val = ptrace(PTRACE_PEEKDATA,
                          child, addr + i * 4,
                          NULL);
        memcpy(laddr, data.chars, j);
    }
    str[len] = '\0';
}
//原理和get差不多,使用ptrace注入的方式打印
void putdata(pid_t child, long addr,
             char *str, int len)
{   char *laddr;
    int i, j;
    union u {
            long val;
            char chars[long_size];
    }data;
    i = 0;
    j = len / long_size;
    laddr = str;
    while(i < j) {
        memcpy(data.chars, laddr, long_size);
        ptrace(PTRACE_POKEDATA, child,
               addr + i * 4, data.val);
        ++i;
        laddr += long_size;
    }
    j = len % long_size;
    if(j != 0) {
        memcpy(data.chars, laddr, j);
        ptrace(PTRACE_POKEDATA, child,
               addr + i * 4, data.val);
    }
}
void modifyString(pid_t pid, long addr, long strlen)
{	//注意这里的strlen是地址的长度!
    char* str;
    str = (char *)calloc((strlen+1) * sizeof(char), 1);
    getdata(pid, addr, str, strlen);
    //reverse(str);
	str[0]='l';//将字符串的第一个字符改成'l'
	printf("Hook -------\n");
	printf("%s\n",str);
    putdata(pid, addr, str, strlen);
}
void hookSysCallBefore(pid_t pid)
{
    struct pt_regs regs;
    int sysCallNo = 0;

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);    
    sysCallNo = getSysCallNo(pid, &regs);
    //printf("Before SysCallNo = %d\n",sysCallNo);

    if(sysCallNo == __NR_write)
    {
        printf("__NR_write: %ld %p %ld\n",regs.ARM_r0,(void*)regs.ARM_r1,regs.ARM_r2);
		modifyString(pid, regs.ARM_r1, regs.ARM_r2);
    }
}
void hookSysCallAfter(pid_t pid)
{
    struct pt_regs regs;
    int sysCallNo = 0;

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);  
    sysCallNo = getSysCallNo(pid, &regs);

    printf("After SysCallNo = %d\n",sysCallNo);

    if(sysCallNo == __NR_write)
    {
        printf("__NR_write return: %ld\n",regs.ARM_r0);
    }

    printf("\n");
}
int main(int argc, char *argv[])
{
    if(argc != 2) {
        printf("Usage: %s <pid to be traced>\n", argv[0]);
        return 1;
    }

    pid_t pid;
    int status,errno;
    pid = atoi(argv[1]);

    if(0 != ptrace(PTRACE_ATTACH, pid, NULL, NULL))
    {
        printf("Trace process failed:%d.\n", errno);
        return 1;
    }

    ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

    while(1)
    {
        wait(&status);
        hookSysCallBefore(pid);
        ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

        //wait(&status);
        //hookSysCallAfter(pid);
        //ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
    }

    ptrace(PTRACE_DETACH, pid, NULL, NULL);
    return 0;
}

然后运行,效果如下:

在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
shim ptrace是一个用于操作进程的系统调用接口。它允许一个进程跟踪、控制和检查另一个进程的执行情况。通过shim ptrace,一个进程可以检查另一个进程的寄存器、内存和系统调用,并有能力修改它们的执行过程。 shim ptrace通常用于调试、监视和诊断进程。调试器可以使用shim ptrace来实现断点、单步执行和修改变量等调试功能。另外,shim ptrace还可以用于分析和检查进程的运行,例如查找进程中的内存泄漏、跟踪系统调用和信号处理。 在使用shim ptrace时,一个进程可以作为被跟踪进程,另一个进程则作为跟踪进程。跟踪进程使用ptrace系统调用来发送指令,而被跟踪进程则接收并执行指令。通过这种方式,跟踪进程可以获取被跟踪进程的状态信息,并对其进行操作。 对于被跟踪进程,它会在指令执行之前接收到跟踪进程发送的指令,并根据指令的要求进行操作。例如,跟踪进程可以用ptrace(PTRACE_PEEKDATA, pid, addr, data)来读取被跟踪进程中地址为addr的内存数据,并将结果保存在data中。类似地,跟踪进程也可以使用ptrace(PTRACE_POKEDATA, pid, addr, data)来修改被跟踪进程的内存值。 总之,shim ptrace是一个强大的工具,允许进程间相互跟踪、控制和修改执行过程。它在调试、监视和诊断进程方面扮演着重要角色,为开发人员提供了有效的方法来分析和改进程序的执行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值