app攻防-SVC的终极奥义Ptrace+Seccomp

admin 2025年2月12日22:50:26评论14 views字数 8996阅读29分59秒阅读模式

作者:yueji0j1anke

首发于公号:剑客古月的安全屋

字数:4234

阅读时间:    30min

声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。合法渗透,本文章内容纯属虚构,如遇巧合,纯属意外

目录

  • 前言

  • 前置知识

  • 测试demo

  • 原理探索

  • 深度利用ptrace

  • Frida-Seccomp

0x00 前言

最近遇到了一些在svc层开发的app,需要进行hook,无奈只好回来重新学ptrace

很多大厂会对open、read等函数通过svc重写实现系统调用,此时我们想要hook系统调用只能通过定位svc指令所在位置进行inline hook,整个过程很容易被检测到,有什么简单而实用的hook方式吗?

有的兄弟,有的!

文章参考:

https://bbs.kanxue.com/thread-277544.htm

0x01 前置知识

1.ptrace

什么是ptrace?

ptrace是linux提供的调试函数,gdb、ida等工具都是以此为基础去开发设计

ptrace允许一个进程监控和控制另一个进程的执行

#include <sys/ptrace.h>       
longptrace(enum__ptrace_requestrequestpid_tpidvoid*addrvoid*data);

一共有四个参数:

  • request: 表示要执行的操作类型。//反调试会用到PT_DENY_ATTACH,调试会用到PTRACE_ATTACH

  • pid: 要操作的目标进程ID

  • addr: 要监控的目标内存地址

  • data: 保存读取出或者要写入的数据详情请参看man手册https://man7.org/linux/man-pages/man2/ptrace.2.html

2.Seccomp

类似于我们前面讲的selinux,但这里Seccomp是一个内核安全模块,可以使进程限制可以进行的系统调用数量,提高进程的安全性。同时还提供了轻量级的进程隔离方式。

而其主要工作流程我们也需要了解->在进程中使用prctl()系统调用来指定一个过滤规则集,该规则集定义了进程允许使用的系统调用的类型和参数

该规则详情如下

SECCOMP_RET_ALLOW:允许系统调用通过。
SECCOMP_RET_KILL_PROCESS:杀死整个进程,即结束进程。
SECCOMP_RET_TRAP:禁止并强制引发 SIGSYS 信号(与 SIGILL、SIGABRT 类似)。
SECCOMP_RET_TRACE:允许并将事件传递给跟踪器(tracee)。
SECCOMP_RET_KILL_THREAD:杀死线程,即终止当前线程。
SECCOMP_RET_KILL:与 SECCOMP_RET_KILL_THREAD 含义相同,只是别名。
SECCOMP_RET_ERRNO:返回一个 errno 错误码。
SECCOMP_RET_USER_NOTIF:通知用户空间的监听程序,即向用户态传递信息。
SECCOMP_RET_LOG:记录事件到内核日志中。

这里借助网上的demo给大家讲解一下

voidconfigure_seccomp()
{
// struct sock_filter filter[] = {
//     BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
//     BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 3),
//     BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[2]))),
//     BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, O_RDONLY, 0, 1),
//     BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
//     BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
// };

structsock_filterfilter[] = {
BPF_STMT(BPF_LD|BPF_W|BPF_ABS, (offsetof(structseccomp_datanr))), //L1
//从 seccomp 数据中读取当前被调用的系统调用号
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K__NR_openat02),//L2
//比较上一步加载的系统调用号与 __NR_openat 是否相等
//相等的话,继续往下执行,跳转0步
//不相等,跳转2步,到L5执行,SECCOMP_RET_ALLOW允许执行
BPF_STMT(BPF_LD|BPF_W|BPF_ABS, (offsetof(structseccomp_dataargs[2]))),//L3
//读取arg2
BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_KO_RDONLY01),//L4
//arg2是不是O_RDONLY
//如果是,则SECCOMP_RET_ALLOW允许执行
//如果不是,则跳转1步,SECCOMP_RET_KILL进程终止
BPF_STMT(BPF_RET|BPF_KSECCOMP_RET_ALLOW),//L5
BPF_STMT(BPF_RET|BPF_KSECCOMP_RET_KILL)//L6
    };
//open()函数底层会调用__NR_openat,而不是


structsock_fprogprog= {
        .len= (unsignedshort)(sizeof(filter/sizeof(filter[0])),
        .filter=filter,
    };

printf("Configuring seccompn");
prctl(PR_SET_NO_NEW_PRIVS1000);
prctl(PR_SET_SECCOMPSECCOMP_MODE_FILTER&prog);
}

代码大致意思

1. 从seccomp数据中读取当前被调用的系统调用号
2. 检测系统调用号是否为__NR_openat(即系统对"openat"的调用)
3. 读取系统调用的第三个参数(args[2])
4. 该参数是否为O_RDONLY标识(只读模式)
5. 如果判断成功,允许执行openat,如果失败,则直接kill掉

这里有的demo大家可以自行尝试一下

https://github.com/callrsp/attachment/blob/master/svc-learn/2.seccomp_linux_amd64/demo.c
app攻防-SVC的终极奥义Ptrace+Seccomp

3.ptrace+Seccomp

可能看到这里有些师傅已经明白了。

ptrace 可以监控系统调用,Seccomp可以对系统调用进行规则过滤,那这不就相当于if else + frida的一个hook逻辑吗

比如说系统要读取一个文件,我们是否能实现讲他重定向到另一个文件呢

https://github.com/callrsp/attachment/blob/master/svc-learn/3.ptrace-seccomp-linux_amd64/demo.c

这个代码很好实现了相关逻辑

子进程启动后,通过ptrace(PTRACE_TRACEME,...)设置自身为被跟踪状态,然后使用prctl配置seccomp规则以便后续的系统调用监控。
使用kill(getpid(), SIGSTOP);实现暂停,等待父进程设置信号处理。

随后通过seccomp配置一个规则来监控openat系统调用。
当openat调用发生时,该进程会触发一个SECCOMP事件,并通知父进程。

子进程在受到监控情况下,执行打开、读取文件的系统调用。
ptrace工具允许父进程在捕捉到openat系统调用时,检查子进程试图访问的文件名,并用不同的文件路径替换它。

父进程使用waitpid()监控子进程状态变更。
当子进程触发SECCOMP_RET_TRACE时,ptrace允许父进程取得控制。
ptrace(PTRACE_GETREGS, ...)获取子进程寄存器状态,以取得当前系统调用编号。
使用read_filename_from_regs()和putdata_to_regs()来读取并修改子进程试图打开的文件名。

如果试图打开特定文件(如 /home/kali/tmp/flag.txt),则会重定向到另一个文件(如 /home/kali/tmp/hacker)。

看这里demo

app攻防-SVC的终极奥义Ptrace+Seccomp

0x02 测试demo

注意这里

app攻防-SVC的终极奥义Ptrace+Seccomp

需要两个根级目录

这里我直接用的框架去进行学习

首先配置好cmake

编译

app攻防-SVC的终极奥义Ptrace+Seccomp

随后进行测试

./Inject -p 13569 -so /data/local/tmp/libHook.so -symbols hello
app攻防-SVC的终极奥义Ptrace+Seccomp

google和小米手机都测试成功,兼容性还蛮不错的,值得学习

app攻防-SVC的终极奥义Ptrace+Seccomp

0x03 原理探索

核心其实就在于该函数

int inject_remote_process(pid_t pid, char *LibPath, char *FunctionName, char *FlagSELinux)

在该函数中,会帮忙处理selinux安全机制

参数

  • pid: 被注入的目标进程ID。

  • LibPath: 被注入的共享库路径。

  • FunctionName: 注入的共享库中需要调用的函数名。

  • FlagSELinux: 标识 SELinux 状态,以便在注入完成后恢复原状态。

步骤

附加到目标进程: 使用 ptrace_attach 附加到目标进程,便于控制和修改目标进程的执行。

  1. 获取和保存寄存器状态: 拿到当前的寄存器状态,并保存原始状态以便后续恢复。

  2. 查找函数地址: 获取目标进程中必要函数(mmapdlopendlsymdlclose 和 dlerror)的地址。

  3. 内存映射: 调用目标进程的 mmap 函数分配一块内存,用于存储共享库路径及其它所需数据。

  4. 写入共享库路径: 将输入的 LibPath 写入到目标进程的内存。

  5. 调用 dlopen: 加载共享库并获得其返回地址(即模块加载地址)。

  6. 错误处理: 如果 dlopen 返回空指针,调用 dlerror 获取错误信息。

  7. 调用 dlsym: 查找并调用目标进程中的函数(如果给出了 FunctionName)。

  8. 恢复寄存器: 恢复原先保存的寄存器状态,这样目标进程可以继续正常执行。

  9. 分离进程: 使用 ptrace_detach 分离,停止对目标进程的控制。

除此之外

app攻防-SVC的终极奥义Ptrace+Seccomp

这里查找地址可以任意替换,比如dlopen,dlsym

0x04 利用ptrace hook内核方法打印目标进程

我们现在机子是mac,因此还是采用cmake去进行编译

其实我们学习框架,就是要打造出属于我们自己的ptrace框架,用顺手这样方便hook

这里给出我的hook demp

intmain(intargcchar*argv[])
{
if (argc!=2) {
fprintf(stderr"Usage: %s <PID>n"argv[0]);
return1;
    }

pid_tchild=atoi(argv[1]);
intiRet=-1;

if (ptrace_attach(child!=0) {
returniRet;
    }

longorig_raxrax;
intstatus;
structuser_regs_structregs;

intsyscall=0;
char*str;

while (1) {
ptrace(PTRACE_SYSCALLchildNULLNULL);
wait(&status);
if(WIFEXITED(status)) {
break;
        }

orig_rax=ptrace(PTRACE_PEEKUSERchild8*ORIG_RAXNULL); // 保存的系统调用号,进行的是当前系统调用号的获取
printf("系统调用号 %ld ->  系统调用名 %s n"orig_raxfind_syscall_symbol(orig_rax));
printf("=====================================n");

//        if (orig_rax == 0) {
//
//        }

    }

ptrace_detach(child);
}

编译好后,执行

app攻防-SVC的终极奥义Ptrace+Seccomp

可以直接hook住内核系统调用的方法名,下边更近一步,打印参数

app攻防-SVC的终极奥义Ptrace+Seccomp

在安卓平台写好配置文件编译即可

export ANDROID_NDK=/Users//Library/Android/sdk/ndk/27.0.12077973

export PATH=$PATH:$ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin

$ANDROID_NDK/ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk

0x05 Frida-Seccomp

接下来,版本迭代到了我们的

https://github.com/Abbbbbi/Frida-Seccomp

如何快速配合我们的frida进行捕捉和修改

->写Seccomp规则

如果是目标调用, 那么返回 SECCOMP_RET_TRAP, 其它调用允许执行.

SECCOMP_RET_TRAP会触发一个 SIGSYS 信号(与 SIGILL、SIGABRT 类似)。同时拒绝系统调用.

原作者使用frida自带的Process.setExceptionHandler函数, 即可捕获异常SIGSYS, 并在自己写的回调中进行数据处理而且frida提供了cmodule模块~巧妙,巧妙!

在so加载时,直接hook之前流程

Interceptor.attach(Module.findExportByName(null"android_dlopen_ext"), 
{
onEnter(args
    {
if (g_lpf_cm_install_seccomp_filter==null
        {
hook_init();//第一次加载so时,就初始化init()
        }
    }
})

Hook_init函数对应如下

//CModule已经初始化好了
functionhook_init() 
{
//初始化,需要在主线程初始化且需要一个比较早的时机,frida脚本自己创建的一个C线程(没被安装seccomp规则)

//CModule层,函数获取
g_lpf_cm_thread_syscall_tvar=newNativeFunction(cm.pthread_syscall_create"pointer", [])()  //创建一个没有被seccomp过滤的线程
//woc,这里不仅仅获取了thread_syscall_t,而且还调用了pthread_syscall_create.此刻已有线程被创建

g_lpf_cm_findSoinfoByAddr=newNativeFunction(cm.findSoinfoByAddr"pointer", ["pointer"])
g_lpg_cm_get_base=newNativeFunction(cm.get_base"uint64", ["pointer"])
g_lpf_cm_get_size=newNativeFunction(cm.get_size"size_t", ["pointer"])
g_lpf_cm_call_task=newNativeFunction(cm.call_task"pointer", ["pointer""pointer""int"])
g_lpf_cm_install_seccomp_filter=newNativeFunction(cm.install_seccomp_filter"int", ['uint32'])
g_lpf_cm_lock=newNativeFunction(cm.lock"int", ["pointer"])
g_lpf_cm_unlock=newNativeFunction(cm.unlock"int", ["pointer"])

// 异常处理 捕获seccomp异常 <-- SECCOMP_RET_TRAP
Process.setExceptionHandler(function (details
    {
constcurrent_off=details.context.pc-4//指向当前异常发生的pc, 本来pc是指向异常的下一个位置
// 010000d4 是4字节

// 判断是否是seccomp导致的异常 读取opcode 010000d4 == svc 0
if (details.message=="system error"
&&details.type=="system"
&&utils_hex(ptr(current_off).readByteArray(4)) =="010000d4"//机器码 D4000001 `svc     #0x0`
        {
//进入SVC异常处理

// 上锁避免多线程问题
// g_lpf_cm_lock(g_lpf_cm_thread_syscall_tvar); //源代码感觉写得有问题
// 获取x8寄存器中的调用号
constnr_syscall_id=details.context.x8.toString(10);
letloginfo="n=================="
loginfo+=`nSVC[${syscall_enum_infos[nr_syscall_id][1]}|${nr_syscall_id}] ==> PC:${utils_addrToString(current_off)}Pid:${Process.id}Tid:${Process.getCurrentThreadId()}`
// 构造线程syscall调用参数
constargs=Memory.alloc(7*8)
args.writePointer(details.context.x8)
letargs_reg_arr= {}
for (letindex=0index<6index++
            {
//eval 动态执行js代码
eval(`args.add(8 * (index + 1)).writePointer(details.context.x${index})`)//分别获取当前寄存器参数 x0,x1,x2,x3,x4,x5,x6,写入开辟的c内存中
eval(`args_reg_arr["arg${index}"] = details.context.x${index}`//同时写入js的变量中
            }
// 获取手动堆栈信息
loginfo+="n"+utils_stacktrace(ptr(current_off), details.context.fpdetails.context.sp).map(utils_addrToString).join('n')
// 打印传参
loginfo+="nargs = "+JSON.stringify(args_reg_arr)
// 调用线程syscall 赋值x0寄存器
details.context.x0=g_lpf_cm_call_task(g_lpf_cm_thread_syscall_tvarargs0);//传递线程函数? 寄存器参数, 
loginfo+="nret = "+details.context.x0.toString()
// 打印信息
utils_call_thread_log(loginfo)
// 解锁
// g_lpf_cm_unlock(g_lpf_cm_thread_syscall_tvar)
returntrue;
        }
returnfalse;
    })
// openat的调用号
g_lpf_cm_install_seccomp_filter(Target_NR); //开启线程过滤
}

核心逻辑

  1. 通过C模块的NativeFunction进行初始化

    • CModule已经初始化,包括对pthread和其它C函数的包装。

    • pthread_syscall_create用于创建一个不受Seccomp过滤的线程。这是为了能够在受Seccomp保护的程序中执行某些系统调用。初始化过程中,这些函数在CModule中定义,Frida以其NativeFunction进行包装。

  2. 捕获异常并处理Seccomp触发

    • 锁定执行线程。

    • 读取当前指令和寄存器状态,包括x8中存储的系统调用号。

    • 保存上下文和参数以便后续适配处理。

    • 在同一个未被Seccomp限制的线程中执行系统调用。

    • 打印和记录信息以便调试(如调用堆栈、参数等)。

    • 解锁执行线程。

    • Process.setExceptionHandler()用于定义一个异常处理器,可以捕获程序运行时的各种异常。

    • 该处理器专用于捕获Seccomp返回的SECCOMP_RET_TRAP异常。

    • 如果触发了Seccomp异常(通过判断

      details.message

      和机器码):

  3. 对特定系统调用进行Seccomp过滤

    • 通过g_lpf_cm_install_seccomp_filter(Target_NR);开启Seccomp过滤,也就是限制当前进程的系统调用,只允许过滤器表中允许的调用。

项目效果可以参照

https://github.com/Abbbbbi/Frida-Seccomp
app攻防-SVC的终极奥义Ptrace+Seccomp

这里我在回调函数增加了一些内容,就可以打印出收集的文件信息

app攻防-SVC的终极奥义Ptrace+Seccomp

市面上大多数的svc hook工具其实都只能实现对应的检测功能

后续将研究研究推出svc层重定向工具。

原文始发于微信公众号(剑客古月的安全屋):app攻防-SVC的终极奥义Ptrace+Seccomp

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月12日22:50:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   app攻防-SVC的终极奥义Ptrace+Seccomphttps://cn-sec.com/archives/3729496.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息