Кому интересно, другой вариант ответа можно найти здесь.
Что здесь происходит?
Скорее всего, вы имеете дело с анти-отладочной техникой, подобной этой:
ptrace(PT_DENY_ATTACH, 0, NULL, 0);
Основная идея заключается в том, что только один процесс может ptrace
одновременно выполнять другой процесс, в частности, параметр PT_DENY_ATTACH
гарантирует, что трассировка завершается со статусом ENOTSUP
(45). См. man ptrace
о PT_DENY_ATTACH
:
Этот запрос является другой операцией, используемой отслеживаемым процессом; он позволяет процессу, который в настоящее время не отслеживается, отклонить будущие трассировки своим родителем. Все остальные аргументы игнорируются. Если процесс в настоящее время отслеживается, он завершится со статусом завершения ENOTSUP; в противном случае он устанавливает флаг, запрещающий будущие трассировки. Попытка родителя отследить процесс, установивший этот флаг, приведет к нарушению сегментации в родительском процессе.
Что касается 45, взгляните на /System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/errno.h
:
#define ENOTSUP 45 /* Operation not supported */
Как воспроизвести это?
Написать программу, которая демонстрирует такое же поведение, тривиально:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ptrace.h>
int main() {
printf("--- before ptrace()\n");
ptrace(PT_DENY_ATTACH, 0, NULL, 0);
perror("--- ptrace()");
printf("--- after ptrace()\n");
return 0;
}
Скомпилировать с:
clang -Wall -pedantic ptrace.c -o ptrace
Простой запуск приведет к успешному выходу, но попытка отладки даст следующий результат:
(lldb) r
Process 4188 launched: './ptrace' (x86_64)
--- before ptrace()
Process 4188 exited with status = 45 (0x0000002d)
Поскольку этот пример довольно маленький, можно перейти к инструкции syscall
:
(lldb) disassemble
libsystem_kernel.dylib`__ptrace:
0x7fff6ea1900c <+0>: xorq %rax, %rax
0x7fff6ea1900f <+3>: leaq 0x394f12f2(%rip), %r11 ; errno
0x7fff6ea19016 <+10>: movl %eax, (%r11)
0x7fff6ea19019 <+13>: movl $0x200001a, %eax ; imm = 0x200001A
0x7fff6ea1901e <+18>: movq %rcx, %r10
-> 0x7fff6ea19021 <+21>: syscall
0x7fff6ea19023 <+23>: jae 0x7fff6ea1902d ; <+33>
0x7fff6ea19025 <+25>: movq %rax, %rdi
0x7fff6ea19028 <+28>: jmp 0x7fff6ea10791 ; cerror
0x7fff6ea1902d <+33>: retq
0x7fff6ea1902e <+34>: nop
0x7fff6ea1902f <+35>: nop
(lldb) s
Process 3170 exited with status = 45 (0x0000002d)
Таким образом, именно код ядра убивает процесс, но без сигнала или надлежащего системного вызова exit
. (ПОКА это, и это все еще поражает меня.)
Какой системный вызов выполняется, определяется значением регистра EAX
, в данном случае 0x200001A
, что может показаться странным, поскольку номер системного вызова ptrace
равен всего 26 (0x1a
), см. syscalls.master
:
26 AUE_PTRACE ALL { int ptrace(int req, pid_t pid, caddr_t addr, int data); }
Немного покопавшись, я нашел syscall_sw.h
:
#define SYSCALL_CONSTRUCT_UNIX(syscall_number) \
((SYSCALL_CLASS_UNIX << SYSCALL_CLASS_SHIFT) | \
(SYSCALL_NUMBER_MASK & (syscall_number)))
Делая математику, результат 0x200001A
Почему dtruss
не отслеживает системный вызов ptrace
?
Использование dtruss
кажется хорошей идеей, но, к сожалению, он не сообщает о системном вызове ptrace
(насколько я понимаю, он не может этого сделать, поскольку в этом случае системный вызов ptrace
не возвращается).
К счастью, вы можете написать сценарий DTrace для регистрации системного вызова после его ввода (т. е. не после его возврата). Чтобы вызвать поведение, программа должна быть запущена с lldb
:
$ lldb ./ptrace
(lldb) process launch --stop-at-entry
Обратите внимание на PID:
sudo dtrace -q -n 'syscall:::entry /pid == $target/ { printf("syscall> %s\n", probefunc); }' -p $PID
Наконец continue
в lldb
, результат должен быть:
[...]
syscall> sysctl
syscall> csops
syscall> getrlimit
syscall> fstat64
syscall> ioctl
syscall> write_nocancel
syscall> ptrace
Возможные решения
Теперь было бы неплохо прерваться непосредственно перед системным вызовом ptrace
и найти программный код, который его вызывает, или просто пропустить его для текущего сеанса отладки (LLDB: thread jump -a ADDRESS
).
Конечно, можно было бы попытаться прервать вызов библиотеки ptrace
, но если это действительно так и попытка защиты от отладки скорее всего такова, что фактический вызов выполняется в блоке asm
, то указанная выше точка останова никогда не сработает.
Возможным решением может быть использование DTrace для установки точки останова перед системным вызовом, но для этого требуется отключить защиту целостности системы, поэтому я не пробовал.
В качестве альтернативы можно распечатать трассировку стека пользовательской области с помощью функции ustack()
:
sudo dtrace -q -n 'syscall:::entry /pid == $target && probefunc == "ptrace"/ { ustack(); }' -p $PID
person
cYrus
schedule
11.12.2017