[原创]seccomp-bpf+ptrace实现修改系统调用原理(附demo)
2022-12-13 11:43 7671
目前绝大部分app都会频繁的使用syscall去获取设备指纹和做一些反调试,使用常规方式过反调试已经非常困难了,使用内存搜索svc指令已经不能满足需求了,开始学习了一下通过ptrace/ptrace配合seccomp来解决svc反调试难定位难绕过等问题。
Linux 2.6.12中的导入了第一个版本的seccomp,通过向/proc/PID/seccomp接口中写入“1”来启动通过滤器只支持几个函数。
1 |
|
使用其他系统调用就会收到信号(SIGKILL)退出。测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
可以看到执行到22行就结束了没执行到 Eed.
Seccomp-BPF(Berkeley Packet Filter)是Linux内核中的一种安全机制,用于限制进程对系统调用的访问权限。它主要用于防止恶意软件对系统的攻击,提高系统的安全性。
Seccomp-BPF使用BPF(Berkeley Packet Filter)技术来实现系统调用过滤,可以使用BPF程序指定哪些系统调用可以被进程访问,哪些不能。BPF程序由一组BPF指令组成,可以在系统调用执行之前对其进行检查,以决定是否允许执行该系统调用。
Seccomp-BPF提供了两种模式:白名单模式和黑名单模式。白名单模式允许所有系统调用,除非明确指定不允许的系统调用。黑名单模式禁止所有系统调用,除非明确指定允许的系统调用。这两种模式的选择取决于您的实际需求。
Seccomp-BPF提供了一个钩子函数,在系统调用执行之前会进入到这个函数,对系统调用进行检查,如果BPF程序允许执行该系统调用,则进程可以继续执行,否则会抛出一个异常。
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Seccomp-BPF程序 接收以下结构作为输入参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
在这种情况下,seccomp-BPF 程序将允许使用 O_RDONLY 参数打开第一个调用 , 但是在使用 O_WRONLY | O_CREAT 参数调用 open 时终止程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
将getpid()的实现改为mkdir()的实现。主要是通过ptrace函数来跟踪子进程,获取其寄存器中的信息,然后根据需求替换对应的系统调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
|
看一下main函数这里设置了跟踪openat系统调用子进程请求父进程附加 父进程开启ptrace+seccomp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
|
下面来解释一下bpf结构,BPF 被定义为一种虚拟机 (VM),它具有一个数据寄存器或累加器、一个索引寄存器和一个隐式程序计数器 (PC)。它的“汇编”指令被定义为具有以下格式的结构:
1 2 3 4 5 6 |
|
有累加器,跳转等待码(操作码),jt和jf是跳转指令中使用的程序计数器的增量,而k是一个辅助值,其用法取决于代码编号。
BPFs有一个可寻址空间,其中的数据在网络情况下是一个数据包数据报,对于seccomp有一下结构:
1 2 3 4 5 6 7 |
|
所以bpfs在seccomp中做的是对这些数据进行操作并返回一个值告诉内核下一步做什么,比如:
1 2 |
|
详细见文档:seccomp文档
现在我们可以根据系统调用号和参数进行过滤,bpf过滤器被定义为一个sock_filter结构,其中每条都是一个bpf指令。
1 2 3 4 |
|
BPF_STMT和BPF_JUMP是两个填充sock_filter结构的简单红。他在参数上有所不同。其中包括BPF_JUMP中的跳跃偏移量。在两种情况下。第一个参数都是操作码,作为助记符帮助:例如,第一个参数是使用绝对寻址(BPF_ABS) 将一个字 (BPF_W) 加载到累加器 (BPF_LD) 中。
第一条指令是要求VM将呼叫号码加载nr到累加器。第二条将与openat的系统调用号进行比较。如果他们相等(pc + o),则要求vm不修改计数器。因此运行第三条指令,否则跳转到PC+1,这是第四条指令(当执行到这条指令时,pc已经指向第三条指令)。因此如果这是一个开放的系统调用,我们将返回SECCOMP_RET_TRACE,这会调用跟踪器否则返回SECCOMP_RET_ALLOW,这将会让没有被跟踪的系统调用直接执行。
然后是第一次调用 prctl 设置PR_SET_NO_NEW_RPIVS,这会阻止子进程拥有比父进程更多的权限。他使用PR_SET_SECCOMP选择设置seccomp过滤器,不是root用户也可以使用,之后使用openat系统调用进行打开文件等操作。
父进程我设置了PTRACE_O_TRACESECCOMP 选项,当过滤器返回 SECCOMP_RET_TRACE 并将事件信号发送给跟踪器时,跟踪器将停止。此函数的另一个变化是我们不再需要设置 PTRACE_O_TRACESYSGOOD,因为我们被 seccomp 中断,而不是因为系统调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
这里就很简单了获取到svc的信号后读取x8寄存器判断是否为openat的系统调用号,这里只对file_to_avoid进行了替换,看一下最终效果:
可以看到不仅只对openat进行了监控也成功的将了第一次打开的文件
/data/local/tmp/tuzi.txt修改为了/data/local/tmp/tuzi1.txt。
demo地址 github
完结撒花!
ptrace を使用して seccomp による制限を回避してみる
看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~
最后于 2022-12-13 21:25 被王麻子本人编辑 ,原因: