首页
社区
课程
招聘
[原创]通过xnuspy hook 内核ptrace 函数绕过反调试
发表于: 2024-6-5 21:40 13973

[原创]通过xnuspy hook 内核ptrace 函数绕过反调试

2024-6-5 21:40
13973

昨天实践了修改kernproc 结构体绕过反调试.今天突然想到是不是直接内核hook 更方便点儿

0x01先阅读自己手机系统iPhone 7 的 14.1对应的xnu开源代码里面的ptrace代码

  • xnu-xnu-7195.50.7.100.1/bsd/kern/mach_process.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
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
int
ptrace(struct proc *p, struct ptrace_args *uap, int32_t *retval)
{
    struct proc     *t; /* target process */
    task_t          task;
    thread_t        th_act;
    struct uthread  *ut;
    int tr_sigexc = 0;
    int error = 0;
    int stopped = 0;
 
    AUDIT_ARG(cmd, uap->req);
    AUDIT_ARG(pid, uap->pid);
    AUDIT_ARG(addr, uap->addr);
    AUDIT_ARG(value32, uap->data);
 
    if (uap->req == PT_DENY_ATTACH) {
#if (DEVELOPMENT || DEBUG) && !defined(XNU_TARGET_OS_OSX)
        if (PE_i_can_has_debugger(NULL)) {
            return 0;
        }
#endif
        proc_lock(p);
        if (ISSET(p->p_lflag, P_LTRACED)) {
            proc_unlock(p);
            KERNEL_DEBUG_CONSTANT(BSDDBG_CODE(DBG_BSD_PROC, BSD_PROC_FRCEXIT) | DBG_FUNC_NONE,
                p->p_pid, W_EXITCODE(ENOTSUP, 0), 4, 0, 0);
            exit1(p, W_EXITCODE(ENOTSUP, 0), retval);
 
            thread_exception_return();
            /* NOTREACHED */
        }
        SET(p->p_lflag, P_LNOATTACH);
        proc_unlock(p);
 
        return 0;
    }
 
    if (uap->req == PT_FORCEQUOTA) {
        if (kauth_cred_issuser(kauth_cred_get())) {
            t = current_proc();
            OSBitOrAtomic(P_FORCEQUOTA, &t->p_flag);
            return 0;
        } else {
            return EPERM;
        }
    }
 
    /*
     *  Intercept and deal with "please trace me" request.
     */
    if (uap->req == PT_TRACE_ME) {
retry_trace_me: ;
        proc_t pproc = proc_parent(p);
        if (pproc == NULL) {
            return EINVAL;
        }
#if CONFIG_MACF
        /*
         * NB: Cannot call kauth_authorize_process(..., KAUTH_PROCESS_CANTRACE, ...)
         *     since that assumes the process being checked is the current process
         *     when, in this case, it is the current process's parent.
         *     Most of the other checks in cantrace() don't apply either.
         */
        struct proc_ident p_ident = proc_ident(p);
        struct proc_ident pproc_ident = proc_ident(pproc);
        kauth_cred_t pproc_cred = kauth_cred_proc_ref(pproc);
 
        proc_rele(pproc);
        error = mac_proc_check_debug(&pproc_ident, pproc_cred, &p_ident);
        kauth_cred_unref(&pproc_cred);
 
        if (error != 0) {
            return error;
        }
        if (proc_find_ident(&pproc_ident) == PROC_NULL) {
            return ESRCH;
        }
#endif
        proc_lock(p);
        /* Make sure the process wasn't re-parented. */
        if (p->p_ppid != pproc->p_pid) {
            proc_unlock(p);
            proc_rele(pproc);
            goto retry_trace_me;
        }
        SET(p->p_lflag, P_LTRACED);
        /* Non-attached case, our tracer is our parent. */
        p->p_oppid = p->p_ppid;
        proc_unlock(p);
        /* Child and parent will have to be able to run modified code. */
        cs_allow_invalid(p);
        cs_allow_invalid(pproc);
        proc_rele(pproc);
 
        return error;
    }
    if (uap->req == PT_SIGEXC) {
        proc_lock(p);
        if (ISSET(p->p_lflag, P_LTRACED)) {
            SET(p->p_lflag, P_LSIGEXC);
            proc_unlock(p);
            return 0;
        } else {
            proc_unlock(p);
            return EINVAL;
        }
    }
 
    /*
     * We do not want ptrace to do anything with kernel or launchd
     */
    if (uap->pid < 2) {
        return EPERM;
    }
 
    /*
     *  Locate victim, and make sure it is traceable.
     */
    if ((t = proc_find(uap->pid)) == NULL) {
        return ESRCH;
    }
 
    AUDIT_ARG(process, t);
 
    task = t->task;
    if (uap->req == PT_ATTACHEXC) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        uap->req = PT_ATTACH;
        tr_sigexc = 1;
    }
    if (uap->req == PT_ATTACH) {
#pragma clang diagnostic pop
        int             err;
 
#if !defined(XNU_TARGET_OS_OSX)
        if (tr_sigexc == 0) {
            error = ENOTSUP;
            goto out;
        }
#endif
 
        if (kauth_authorize_process(proc_ucred(p), KAUTH_PROCESS_CANTRACE,
            t, (uintptr_t)&err, 0, 0) == 0) {
            /* it's OK to attach */
            proc_lock(t);
            SET(t->p_lflag, P_LTRACED);
            if (tr_sigexc) {
                SET(t->p_lflag, P_LSIGEXC);
            }
 
            t->p_oppid = t->p_ppid;
            /* Check whether child and parent are allowed to run modified
             * code (they'll have to) */
            proc_unlock(t);
            cs_allow_invalid(t);
            cs_allow_invalid(p);
            if (t->p_pptr != p) {
                proc_reparentlocked(t, p, 1, 0);
            }
 
            proc_lock(t);
            if (get_task_userstop(task) > 0) {
                stopped = 1;
            }
            t->p_xstat = 0;
            proc_unlock(t);
            psignal(t, SIGSTOP);
            /*
             * If the process was stopped, wake up and run through
             * issignal() again to properly connect to the tracing
             * process.
             */
            if (stopped) {
                task_resume(task);
            }
            error = 0;
            goto out;
        } else {
            error = err;
            if (error == ESRCH) {
                /*
                 * The target 't' is not valid anymore as it
                 * could not be found after the MAC check.
                 */
                return error;
            }
            /* not allowed to attach, proper error code returned by kauth_authorize_process */
            if (ISSET(t->p_lflag, P_LNOATTACH)) {
                psignal(p, SIGSEGV);
            }
            goto out;
        }
    }
 
    /*
     * You can't do what you want to the process if:
     *  (1) It's not being traced at all,
     */
    proc_lock(t);
    if (!ISSET(t->p_lflag, P_LTRACED)) {
        proc_unlock(t);
        error = EPERM;
        goto out;
    }
 
    /*
     *  (2) it's not being traced by _you_, or
     */
    if (t->p_pptr != p) {
        proc_unlock(t);
        error = EBUSY;
        goto out;
    }
 
    /*
     *  (3) it's not currently stopped.
     */
    if (t->p_stat != SSTOP) {
        proc_unlock(t);
        error = EBUSY;
        goto out;
    }
 
    /*
     *  Mach version of ptrace executes request directly here,
     *  thus simplifying the interaction of ptrace and signals.
     */
    /* proc lock is held here */
    switch (uap->req) {
    case PT_DETACH:
        if (t->p_oppid != t->p_ppid) {
            struct proc *pp;
 
            proc_unlock(t);
            pp = proc_find(t->p_oppid);
            if (pp != PROC_NULL) {
                proc_reparentlocked(t, pp, 1, 0);
                proc_rele(pp);
            } else {
                /* original parent exited while traced */
                proc_list_lock();
                t->p_listflag |= P_LIST_DEADPARENT;
                proc_list_unlock();
                proc_reparentlocked(t, initproc, 1, 0);
            }
            proc_lock(t);
        }
 
        t->p_oppid = 0;
        CLR(t->p_lflag, P_LTRACED);
        CLR(t->p_lflag, P_LSIGEXC);
        proc_unlock(t);
        goto resume;
 
    case PT_KILL:
        /*
         *  Tell child process to kill itself after it
         *  is resumed by adding NSIG to p_cursig. [see issig]
         */
        proc_unlock(t);
#if CONFIG_MACF
        error = mac_proc_check_signal(p, t, SIGKILL);
        if (0 != error) {
            goto resume;
        }
#endif
        psignal(t, SIGKILL);
        goto resume;
 
    case PT_STEP:                   /* single step the child */
    case PT_CONTINUE:               /* continue the child */
        proc_unlock(t);
        th_act = (thread_t)get_firstthread(task);
        if (th_act == THREAD_NULL) {
            error = EINVAL;
            goto out;
        }
 
        /* force use of Mach SPIs (and task_for_pid security checks) to adjust PC */
        if (uap->addr != (user_addr_t)1) {
            error = ENOTSUP;
            goto out;
        }
 
        if ((unsigned)uap->data >= NSIG) {
            error = EINVAL;
            goto out;
        }
 
        if (uap->data != 0) {
#if CONFIG_MACF
            error = mac_proc_check_signal(p, t, uap->data);
            if (0 != error) {
                goto out;
            }
#endif
            psignal(t, uap->data);
        }
 
        if (uap->req == PT_STEP) {
            /*
             * set trace bit
             * we use sending SIGSTOP as a comparable security check.
             */
#if CONFIG_MACF
            error = mac_proc_check_signal(p, t, SIGSTOP);
            if (0 != error) {
                goto out;
            }
#endif
            if (thread_setsinglestep(th_act, 1) != KERN_SUCCESS) {
                error = ENOTSUP;
                goto out;
            }
        } else {
            /*
             * clear trace bit if on
             * we use sending SIGCONT as a comparable security check.
             */
#if CONFIG_MACF
            error = mac_proc_check_signal(p, t, SIGCONT);
            if (0 != error) {
                goto out;
            }
#endif
            if (thread_setsinglestep(th_act, 0) != KERN_SUCCESS) {
                error = ENOTSUP;
                goto out;
            }
        }
resume:
        proc_lock(t);
        t->p_xstat = uap->data;
        t->p_stat = SRUN;
        if (t->sigwait) {
            wakeup((caddr_t)&(t->sigwait));
            proc_unlock(t);
            if ((t->p_lflag & P_LSIGEXC) == 0) {
                task_resume(task);
            }
        } else {
            proc_unlock(t);
        }
 
        break;
 
    case PT_THUPDATE:  {
        proc_unlock(t);
        if ((unsigned)uap->data >= NSIG) {
            error = EINVAL;
            goto out;
        }
        th_act = port_name_to_thread(CAST_MACH_PORT_TO_NAME(uap->addr),
            PORT_TO_THREAD_NONE);
        if (th_act == THREAD_NULL) {
            error = ESRCH;
            goto out;
        }
        ut = (uthread_t)get_bsdthread_info(th_act);
        if (uap->data) {
            ut->uu_siglist |= sigmask(uap->data);
        }
        proc_lock(t);
        t->p_xstat = uap->data;
        t->p_stat = SRUN;
        proc_unlock(t);
        thread_deallocate(th_act);
        error = 0;
    }
    break;
    default:
        proc_unlock(t);
        error = EINVAL;
        goto out;
    }
 
    error = 0;
out:
    proc_rele(t);
    return error;
}
  • 通过阅读源码发现只需要修改第二个参数的结构体属性uap->req 就可以达到绕过目的 (uap->req == PT_DENY_ATTACH)

定位内核ptrace函数偏移地址

image

分析第二个参数ptrace_args的内部结构

让我们详细分析 struct ptrace_args 在内存中的布局。在 64 位系统上,考虑到内存对齐和每个成员的大小,下面是详细的内存布局。

假设 pid_t 是 4 字节,user_addr_t 是 8 字节。在 64 位系统上,指针类型通常是 8 字节并且需要对齐到 8 字节边界。

首先,结构体定义如下:

1
2
3
4
5
6
7
struct ptrace_args {
    int req;         // 4 bytes
    void * unknow[4];        //和实际内存的中偏移不符.实际pid 是+8,所以加了unknow填充
    pid_t pid;       // 4 bytes
    user_addr_t addr;// 8 bytes (on a 64-bit system)
    int data;        // 4 bytes
};

内存布局和对齐分析:

  1. int req:

    • 大小: 4 bytes
    • 对齐: 4 bytes
    • 偏移: 0
    • 结束偏移: 4
  2. pid_t pid:

    • 大小: 4 bytes
    • 对齐: 4 bytes
    • 偏移: 4(紧接在 req 之后)
    • 结束偏移: 8
  3. user_addr_t addr:

    • 大小: 8 bytes
    • 对齐: 8 bytes
    • 由于 addr 需要 8 字节对齐,而当前偏移 8 已经对齐到 8 字节边界,因此无需填充
    • 偏移: 8
    • 结束偏移: 16
  4. int data:

    • 大小: 4 bytes
    • 对齐: 4 bytes
    • 偏移: 16(紧接在 addr 之后)
    • 结束偏移: 20

由于数据对齐要求,编译器可能会在结构体的末尾添加填充以确保结构体大小是对齐的。因此,整个结构体的大小是 24 字节(4 + 4 + 8 + 4 + 4(填充))。


[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2024-6-5 21:47 被yuzhouheike编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (7)
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-6-5 22:29
0
雪    币: 1542
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
xnuspy 要怎么用?
2024-8-16 09:08
0
雪    币: 734
活跃值: (758)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
没看懂,现在不是只HOOK ptrace不行了吗
2024-8-27 09:10
0
雪    币: 38
活跃值: (2388)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
5
63byte xnuspy 要怎么用?[em_16]

可以看看xnuspy 的 readme

最后于 2024-8-27 10:02 被yuzhouheike编辑 ,原因:
2024-8-27 10:00
0
雪    币: 38
活跃值: (2388)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
6
zhusg 没看懂,现在不是只HOOK ptrace不行了吗
你指的是什么不行?
2024-8-27 10:06
0
雪    币: 351
活跃值: (576)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
能出一篇怎么找内核符号偏移的文章吗
2024-10-12 17:16
0
雪    币: 4851
活跃值: (7154)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
阁下不是大佬才不信
2024-10-12 18:20
0
游客
登录 | 注册 方可回帖
返回