首页
社区
课程
招聘
[原创]【从源码过反调试】三、源码实现一些过检测(文件过滤,进程隐藏,notify bypass,mincore bypass)
2023-5-31 17:34 8386

[原创]【从源码过反调试】三、源码实现一些过检测(文件过滤,进程隐藏,notify bypass,mincore bypass)

2023-5-31 17:34
8386

重发调整了格式

syscall 入口

所有的参数都是syscall传进内核区分参数,指令

 

参数:设置/移除pid,设置/移除 文件过滤字符串 设置/移除 nofity bypass文件路径

 

指令:开关文件过滤,物理地址读写内存,进程隐藏开关。nofity文件过滤

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
enum CmdDataType
 
{
 
    SET_TARGET_PID = 1 ,
 
    SET_SELF_PID = 2,
 
    REMOVE_TARGET_PID = 4,
 
    REMOVE_SELF_PID = 8,
 
    SET_FILTER_STR = 16,
 
    REMOVE_FILTER_STR = 32,
 
    SET_NOTIFY_PATH_STR = 64,
 
    REMVOE_NOTIFY_PATH_STR = 128
 
};
 
 
 
enum CmdSwitchType
 
{
 
    FILTER_FILE = 0,
 
    GLOABAL_PID,
 
    READ_MEMORY,
 
    WRITE_MEMORY,
 
    HIDE_PROCESS,
 
    REMOVE_HIDE_PROCESS,
 
    NOTIFY
 
};

目标管理

用户层传参数、指令,保存到内核。

 

开启各种功能最先做的还是选定目标了,设置target_pid开启各种过检测,设置spid保护自己的进程

文件过滤

过通用文件检测,主要用来过一些模拟器检查。可以自定义返回错误码

 

在getname_flags里增加判断,我测试过几个点,这个位置是文件访问的几个syscall(open, access, stat)都会走的,在这里比较合适,不过不知道有没有遗漏其他的。

 

修改源码fs/namei.c

 

判断要访问的文件是否在被保护字符串列表里。

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
struct filename *
 
getname_flags(const char __user *filename, int flags, int *empty)
 
{
 
 
 
        // ... 前面省略
 
 
 
    /* The empty path is special. */
 
    if (unlikely(!len)) {
 
        if (empty)
 
            *empty = 1;
 
        if (!(flags & LOOKUP_EMPTY)) {
 
            putname(result);
 
            return ERR_PTR(-ENOENT);
 
        }
 
    }
 
 
 
    result->uptr = filename;
 
    result->aname = NULL;
 
    if (is_target() && is_str_in_filter_array(result->uptr, &ecode))
 
    {
 
        // 如果在过滤列表
 
        printk("[AntiLog] dont access me result:%d\n", ecode);
 
        return ERR_PTR(ecode);
 
    }
 
    audit_getname(result);
 
    return result;
 
}

字符串比较

为了优化性能,这里我没有用全部字符串比较,从传参这里加了限定,比较从start,到end,取16个字节, 在内核里直接用异或计算,把O(n)的时间复杂度降低到了O1。小于16个字节的字符串使用strcmp。kernel里的strcmp是O(n)并没有做优化,glibc里的倒是有优化 但用不了。

 

最初第一反应是用哈希表,但内核里没有实现比较完善的哈希表,字符串求hash,碰撞函数这些都要自己写,想来过于麻烦就没用这种方式。

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
for (i = 0; i < filter_array_lens; i++) {
 
        if (filter_array[i].str_lens < 16) {
 
            // cmp 通配符查找
 
            memcpy(substr_str, str, filter_array[i].str_lens);
 
            memset(substr_str + filter_array[i].str_lens, 0,
 
                   16 - filter_array[i].str_lens);
 
 
 
            if (strcmp(substr_str, filter_array[i].str_content) == 0) {
 
                if (filter_array[i].is_user_custom_res) {
 
                    *res_errcode = filter_array[i].custom_res;
 
                    printk("use errcode :%d", filter_array[i].custom_res);
 
                } else {
 
                    *res_errcode = -ENOENT;
 
                }
 
                return true;
 
            }
 
 
 
        } else if (filter_array[i].str_lens >= 16 && dst_str_len >= 16) {
 
            // cmp by xor
 
            __uint128_t dst_hash = *(__uint128_t *)(&str[filter_array[i].start_pos]);
 
            if ((dst_hash ^ filter_array[i].str_hash) == 0) {
 
                if (filter_array[i].is_user_custom_res) {
 
                    *res_errcode = filter_array[i].custom_res;
 
                } else {
 
                    *res_errcode = -ENOENT;
 
                }
 
                return true;
 
            }
 
        } else if (filter_array[i].str_lens >= 16 && dst_str_len < 16) {
 
            continue;
 
        }
 
    }

notify访问bypass

检测不多说了,bypass思路和文件过滤相同,主要是找对位置

 

include/linux/fsnotify.h

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
78
79
80
81
82
83
84
85
86
87
88
89
static inline int fsnotify_file(struct file *file, __u32 mask)
 
{
 
    const struct path *path;
 
    if(is_target() && is_file_fsnotify_block(file))
 
    {
 
        printk("[AntiLog] done notify me\n");
 
        return 0;
 
    }
 
    path = &file->f_path;
 
 
 
    if (file->f_mode & FMODE_NONOTIFY)
 
        return 0;
 
 
 
    return fsnotify_parent(path->dentry, mask, path, FSNOTIFY_EVENT_PATH);
 
}
 
 
 
// file -> user_path cmp syscall_user_path
 
int is_file_fsnotify_block(struct file *file)
 
{
 
    char *tmp;
 
    char *path;
 
    int str_pos;
 
 
 
    if (fsnotify_block_switch == false) {
 
        return -1;
 
    }
 
    if (file == NULL) {
 
        return -2;
 
    }
 
    tmp = (char *)__get_free_page(GFP_KERNEL);
 
    if (tmp == NULL) {
 
        return -3;
 
    }
 
    path = d_path(&file->f_path, tmp, PAGE_SIZE);
 
    if (IS_ERR(path)) {
 
        printk("[AntiLog]d_path error:%p\n", path);
 
        return -4;
 
    }
 
 
 
    str_pos = filepath_str_in_array_pos(path);
 
 
 
    free_page((unsigned long)tmp);
 
 
 
    return str_pos >= 0 ? str_pos : -5;
 
}

进程隐藏

无论是ps -A 或者是ls /proc/... 这种方式都无法枚举到自己进程

 

还是要找对位置,这两种方式如果用strace跟踪下,会发现核心是调用readdir,那就在内核这里加过滤即可

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
/* for the /proc/ directory itself, after non-process stuff has been done */
 
int proc_pid_readdir(struct file *file, struct dir_context *ctx)
 
{
 
    // 省略前面
 
    for (iter = next_tgid(ns, iter);
 
         iter.task;
 
         iter.tgid += 1, iter = next_tgid(ns, iter)) {
 
 
 
        char name[10 + 1];
 
        unsigned int len;
 
 
 
        if(is_process_hide_by_pid(iter.tgid))
 
        {
 
            printk("[AntiLog] done access my process\n");
 
            continue;
 
        }
 
 
 
        cond_resched();
 
        if (!has_pid_permissions(fs_info, iter.task, HIDEPID_INVISIBLE))
 
            continue;
 
 
 
        len = snprintf(name, sizeof(name), "%u", iter.tgid);
 
        ctx->pos = iter.tgid + TGID_OFFSET;
 
        if (!proc_fill_cache(file, ctx, name, len,
 
                     proc_pid_instantiate, iter.task, NULL)) {
 
            put_task_struct(iter.task);
 
            return 0;
 
        }
 
    }
 
    ctx->pos = PID_MAX_LIMIT + TGID_OFFSET;
 
    return 0;
 
}

内存读写bypass mincore

思路大家都是一样的,实现的方法是有些差异

 

读之前检查pagefault

 

读物理地址

 

本来也是打算用页表一级一级的算过去,将va转换到pa,但是linux5.10,安卓12.1.0,x86_64模拟器下,五级页表转换怎么都算不对,都是调用的内核提供的函数,p*d_offset(), 算出来的也不对。不知道为何算出的pmd == p4d,往下都是错的。

 

后来研究了一下linux sparse内存模型。用这种方式转了物理地址。

 

https://zhuanlan.zhihu.com/p/220068494

 

(没测试驱动内是否可以用,就像__pa宏的注释不应该在驱动中使用一样,page_to_phys宏在驱动中不一定可用)。

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
unsigned long get_phsy_addr_by_task(struct mm_struct* mm, unsigned long vaddr)
 
{
 
    struct page *pages;
 
    unsigned long paddr;
 
    int ret;
 
 
 
    ret = get_user_pages(vaddr, 1 , FOLL_FORCE, &pages, NULL);
 
    if(ret != 1)
 
    {
 
        printk(KERN_ERR "get user pages() failed\n");
 
 
 
                // 对于没有提交到物理地址的内存页,get_page这里会报错,所以在这里不读就可以过mincore了。
 
        if(ret == -EFAULT)
 
        {
 
            printk(KERN_INFO "page fault occured\n");
 
        }
 
        return -EFAULT;
 
    }
 
 
 
    paddr = page_to_phys(pages) | (vaddr & ~PAGE_MASK);
 
 
 
    printk(KERN_INFO "[AntiLog] vaddr:%p, paddr:%p\n", vaddr, paddr);
 
    put_page(pages);
 
    return paddr;
 
}

之后再把物理地址map到内核虚拟地址,读写即可

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
read = 0;
 
while (count > 0) {
 
 
 
        sz = size_inside_page(p, count);
 
kvaddr = ioremap(p, sz);
 
 
 
if(!kvaddr)
 
{
 
    printk(KERN_ERR "ioremap failed\n");
 
    return -ENOMEM;
 
}
 
 
 
        if (buf) {
 
 
 
                if (cmd_type == READ_MEMORY) {
 
                        memcpy_fromio(buf, kvaddr, sz);
 
                        printk("Read To Buffer:%s Size:%d, Src:%s", buf, sz, kvaddr);
 
                }
 
                if (cmd_type == WRITE_MEMORY) {
 
                        printk("Before Write To Addr:%p Content:%s", kvaddr, buf);
 
                        memcpy_toio(kvaddr, buf, sz);
 
                        printk("After Write To Addr:%p Content:%s", kvaddr, buf);
 
                }
 
        }
 
 
 
iounmap(kvaddr);
 
kvaddr = NULL;
 
 
 
        buf += sz;
 
        count -= sz;
 
        read += sz;
 
}

[培训]《安卓高级研修班(网课)》月薪三万计划

收藏
点赞8
打赏
分享
最新回复 (5)
雪    币: 16429
活跃值: (59381)
能力值: (RANK:125 )
在线值:
发帖
回帖
粉丝
Editor 2023-6-4 15:12
2
0
建议若文章不是太长,尽量发同一帖里,也可以分1楼,2楼,3楼等其他楼层
雪    币: 18610
活跃值: (27811)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-6-4 16:39
3
1
mark
雪    币: 488
活跃值: (1144)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
xxxlion 2023-6-5 11:26
4
0
Editor 建议若文章不是太长,尽量发同一帖里,也可以分1楼,2楼,3楼等其他楼层
5k+的字还短吗,这代码都是一行行敲出来的
雪    币: 514
活跃值: (810)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
寻梦之璐 2024-3-4 19:52
5
0
xxxlion 5k+的字还短吗,这代码都是一行行敲出来的
你这几篇感觉确实可以合并成一篇的。。
雪    币: 1046
活跃值: (76)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_plkucays 2024-3-16 04:13
6
0
大神能帮忙解决风控的问题吗
游客
登录 | 注册 方可回帖
返回