目前绝大部分app都会频繁的使用syscall去获取设备指纹和做一些反调试,使用常规方式过反调试已经非常困难了,使用内存搜索svc指令已经不能满足需求了,开始学习了一下通过ptrace/ptrace配合seccomp来解决svc反调试难定位难绕过等问题。
Linux 2.6.12中的导入了第一个版本的seccomp,通过向/proc/PID/seccomp接口中写入“1”来启动通过滤器只支持几个函数。
使用其他系统调用就会收到信号(SIGKILL)退出。测试代码如下:
可以看到执行到22行就结束了没执行到 Eed.
Seccomp-BPF(Berkeley Packet Filter)是Linux内核中的一种安全机制,用于限制进程对系统调用的访问权限。它主要用于防止恶意软件对系统的攻击,提高系统的安全性。
Seccomp-BPF使用BPF(Berkeley Packet Filter)技术来实现系统调用过滤,可以使用BPF程序指定哪些系统调用可以被进程访问,哪些不能。BPF程序由一组BPF指令组成,可以在系统调用执行之前对其进行检查,以决定是否允许执行该系统调用。
Seccomp-BPF提供了两种模式:白名单模式和黑名单模式。白名单模式允许所有系统调用,除非明确指定不允许的系统调用。黑名单模式禁止所有系统调用,除非明确指定允许的系统调用。这两种模式的选择取决于您的实际需求。
Seccomp-BPF提供了一个钩子函数,在系统调用执行之前会进入到这个函数,对系统调用进行检查,如果BPF程序允许执行该系统调用,则进程可以继续执行,否则会抛出一个异常。
Seccomp-BPF程序 接收以下结构作为输入参数:
在这种情况下,seccomp-BPF 程序将允许使用 O_RDONLY 参数打开第一个调用 , 但是在使用 O_WRONLY | O_CREAT 参数调用 open 时终止程序。
将getpid()的实现改为mkdir()的实现。主要是通过ptrace函数来跟踪子进程,获取其寄存器中的信息,然后根据需求替换对应的系统调用。
看一下main函数这里设置了跟踪openat系统调用子进程请求父进程附加 父进程开启ptrace+seccomp。
下面来解释一下bpf结构,BPF 被定义为一种虚拟机 (VM),它具有一个数据寄存器或累加器、一个索引寄存器和一个隐式程序计数器 (PC)。它的“汇编”指令被定义为具有以下格式的结构:
有累加器,跳转等待码(操作码),jt和jf是跳转指令中使用的程序计数器的增量,而k是一个辅助值,其用法取决于代码编号。
BPFs有一个可寻址空间,其中的数据在网络情况下是一个数据包数据报,对于seccomp有一下结构:
所以bpfs在seccomp中做的是对这些数据进行操作并返回一个值告诉内核下一步做什么,比如:
详细见文档:seccomp文档
现在我们可以根据系统调用号和参数进行过滤,bpf过滤器被定义为一个sock_filter结构,其中每条都是一个bpf指令。
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 中断,而不是因为系统调用。
这里就很简单了获取到svc的信号后读取x8寄存器判断是否为openat的系统调用号,这里只对file_to_avoid进行了替换,看一下最终效果:
可以看到不仅只对openat进行了监控也成功的将了第一次打开的文件
/data/local/tmp/tuzi.txt修改为了/data/local/tmp/tuzi1.txt。
demo地址 github
完结撒花!
proot
基于ptrace的Android系统调用跟踪&hook工具
SVC的TraceHook沙箱的实现&无痕Hook实现思路
ptrace を使用して seccomp による制限を回避してみる
ptrace(2) — Linux manual page
Seccomp and Seccomp-BPF
深入浅出 eBPF
read(),write(),_exit(),sigreturn()
read(),write(),_exit(),sigreturn()
void configure_seccomp() {
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
if
(argc <
3
) {
printf(
"Usage:\n\t%s <input path> <output_path>\n"
, argv[
0
]);
return
-
1
;
}
printf(
"Starting test seccomp Y/N?"
);
char c
=
getchar();
if
(c
=
=
'y'
|| c
=
=
'Y'
) configure_seccomp();
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
ssize_t read_bytes;
char
buffer
[
1024
];
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
close(infd);
close(outfd);
}
printf(
"End!\n"
);
return
0
;
}
void configure_seccomp() {
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
if
(argc <
3
) {
printf(
"Usage:\n\t%s <input path> <output_path>\n"
, argv[
0
]);
return
-
1
;
}
printf(
"Starting test seccomp Y/N?"
);
char c
=
getchar();
if
(c
=
=
'y'
|| c
=
=
'Y'
) configure_seccomp();
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
ssize_t read_bytes;
char
buffer
[
1024
];
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
close(infd);
close(outfd);
}
printf(
"End!\n"
);
return
0
;
}
简单指令集
小型指令集
所有的命令大小相一致
实现过程简单、快速
只有分支向前指令
程序是有向无环图(DAGs),没有循环
易于验证程序的有效性
/
安全性
简单的指令集⇒可以验证操作码和参数
可以检测死代码
程序必须以 Return 结束
BPF过滤器程序仅限于
4096
条指令
简单指令集
小型指令集
所有的命令大小相一致
实现过程简单、快速
只有分支向前指令
程序是有向无环图(DAGs),没有循环
易于验证程序的有效性
/
安全性
简单的指令集⇒可以验证操作码和参数
可以检测死代码
程序必须以 Return 结束
BPF过滤器程序仅限于
4096
条指令
Conditional JMP(条件判断跳转)
当匹配条件为真,跳转到true指定位置
当 匹配条件为假,跳转到false指定位置
跳转偏移量最大
255
JMP(直接跳转)
跳转目标是指令偏移量
跳转 偏移量最大
255
Load(数据读取)
读取程序参数
读取指定的
16
位内存地址
Store(数据存储)
保存数据到指定的
16
位内存地址中
支持的运算
+
-
*
/
& | ^ >> << !
返回值
SECCOMP_RET_ALLOW
-
允许继续使用系统调用
SECCOMP_RET_KILL
-
终止系统调用
SECCOMP_RET_ERRNO
-
返回设置的errno值
SECCOMP_RET_TRACE
-
通知附加的ptrace(如果存在)
SECCOMP_RET_TRAP
-
往进程发送 SIGSYS信号
最多只能有
4096
条命令
不能出现循环
Conditional JMP(条件判断跳转)
当匹配条件为真,跳转到true指定位置
当 匹配条件为假,跳转到false指定位置
跳转偏移量最大
255
JMP(直接跳转)
跳转目标是指令偏移量
跳转 偏移量最大
255
Load(数据读取)
读取程序参数
读取指定的
16
位内存地址
Store(数据存储)
保存数据到指定的
16
位内存地址中
支持的运算
+
-
*
/
& | ^ >> << !
返回值
SECCOMP_RET_ALLOW
-
允许继续使用系统调用
SECCOMP_RET_KILL
-
终止系统调用
SECCOMP_RET_ERRNO
-
返回设置的errno值
SECCOMP_RET_TRACE
-
通知附加的ptrace(如果存在)
SECCOMP_RET_TRAP
-
往进程发送 SIGSYS信号
最多只能有
4096
条命令
不能出现循环
/
*
*
*
struct seccomp_data
-
the
format
the BPF program executes over.
*
@nr: the system call number
*
@arch: indicates system call convention as an AUDIT_ARCH_
*
value
*
as defined
in
<linux
/
audit.h>.
*
@instruction_pointer: at the time of the system call.
*
@args: up to
6
system call arguments always stored as
64
-
bit values
*
regardless of the architecture.
*
/
struct seccomp_data {
int
nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[
6
];
};
/
*
*
*
struct seccomp_data
-
the
format
the BPF program executes over.
*
@nr: the system call number
*
@arch: indicates system call convention as an AUDIT_ARCH_
*
value
*
as defined
in
<linux
/
audit.h>.
*
@instruction_pointer: at the time of the system call.
*
@args: up to
6
system call arguments always stored as
64
-
bit values
*
regardless of the architecture.
*
/
struct seccomp_data {
int
nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[
6
];
};
void configure_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_write,
0
,
1
),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open,
0
,
3
),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[
1
]))),
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)
};
struct sock_fprog prog
=
{
.
len
=
(unsigned short)(sizeof(
filter
)
/
sizeof (
filter
[
0
])),
.
filter
=
filter
,
};
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
ssize_t read_bytes;
char
buffer
[
1024
];
if
(argc <
3
) {
printf(
"Usage:\n\tdup_file <input path> <output_path>\n"
);
return
-
1
;
}
printf(
"Ducplicating file '%s' to '%s'\n"
, argv[
1
], argv[
2
]);
configure_seccomp();
/
/
配置seccomp
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
}
close(infd);
close(outfd);
return
0
;
}
void configure_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_write,
0
,
1
),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_open,
0
,
3
),
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, args[
1
]))),
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)
};
struct sock_fprog prog
=
{
.
len
=
(unsigned short)(sizeof(
filter
)
/
sizeof (
filter
[
0
])),
.
filter
=
filter
,
};
printf(
"Configuring seccomp\n"
);
prctl(PR_SET_NO_NEW_PRIVS,
1
,
0
,
0
,
0
);
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}
int
main(
int
argc, char
*
argv[]) {
int
infd, outfd;
ssize_t read_bytes;
char
buffer
[
1024
];
if
(argc <
3
) {
printf(
"Usage:\n\tdup_file <input path> <output_path>\n"
);
return
-
1
;
}
printf(
"Ducplicating file '%s' to '%s'\n"
, argv[
1
], argv[
2
]);
configure_seccomp();
/
/
配置seccomp
printf(
"Opening '%s' for reading\n"
, argv[
1
]);
if
((infd
=
open
(argv[
1
], O_RDONLY)) >
0
) {
printf(
"Opening '%s' for writing\n"
, argv[
2
]);
if
((outfd
=
open
(argv[
2
], O_WRONLY | O_CREAT,
0644
)) >
0
) {
while
((read_bytes
=
read(infd, &
buffer
,
1024
)) >
0
)
write(outfd, &
buffer
, (ssize_t)read_bytes);
}
}
close(infd);
close(outfd);
return
0
;
}
void die (const char
*
msg)
{
perror(msg);
exit(errno);
}
void attack()
{
int
rc;
syscall(SYS_getpid, SYS_mkdir,
"dir"
,
0777
);
}
int
main()
{
int
pid;
struct user_regs_struct regs;
switch( (pid
=
fork()) ) {
case
-
1
: die(
"Failed fork"
);
case
0
:
ptrace(PTRACE_TRACEME,
0
, NULL, NULL);
kill(getpid(), SIGSTOP);
attack();
return
0
;
}
waitpid(pid,
0
,
0
);
while
(
1
) {
int
st;
ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
if
(waitpid(pid, &st, __WALL)
=
=
-
1
) {
break
;
}
if
(!(WIFSTOPPED(st) && WSTOPSIG(st)
=
=
SIGTRAP)) {
break
;
}
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
printf(
"orig_rax = %lld\n"
, regs.orig_rax);
if
(regs.rax !
=
-
ENOSYS) {
continue
;
}
if
(regs.orig_rax
=
=
SYS_getpid) {
regs.orig_rax
=
regs.rdi;
regs.rdi
=
regs.rsi;
regs.rsi
=
regs.rdx;
regs.rdx
=
regs.r10;
regs.r10
=
regs.r8;
regs.r8
=
regs.r9;
regs.r9
=
0
;
ptrace(PTRACE_SETREGS, pid, NULL, ®s);
}
}
return
0
;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2023-1-11 14:54
被王麻子本人编辑
,原因: