lldb находит точку выхода приложения

Я отлаживаю приложение, которое предположительно имеет меры по борьбе с отладкой, установка точек останова и сигналов остановки для выхода из приложения не останавливает выход приложения,

$ lldb App 
(lldb) target create "App"
error: Invalid fde/cie next entry offset of 0x43029a18 found in cie/fde at 0x1404
Current executable set to 'App' (x86_64).
(lldb) br s -n exit
Breakpoint 1: 3 locations.
(lldb) br s -n _exit
Breakpoint 2: where = libsystem_kernel.dylib`__exit, address = 0x00000000000167a8
(lldb) br s -n _Exit
Breakpoint 3: where = libsystem_c.dylib`_Exit, address = 0x000000000005ed8b
(lldb) process launch -stop-at-entry
Process 17849 stopped
* thread #1: tid = 0xb9ebc, 0x00007fff5fc01000 dyld`_dyld_start, stop reason = signal SIGSTOP
    frame #0: 0x00007fff5fc01000 dyld`_dyld_start
dyld`_dyld_start:
->  0x7fff5fc01000 <+0>: popq   %rdi
    0x7fff5fc01001 <+1>: pushq  $0x0
    0x7fff5fc01003 <+3>: movq   %rsp, %rbp
    0x7fff5fc01006 <+6>: andq   $-0x10, %rsp
Process 17849 launched: '/Users/admin/Downloads/App.app/Contents/MacOS/App' (x86_64)
(lldb) process handle -p false -s true
Do you really want to update all the signals?: [y/N] y
NAME         PASS   STOP   NOTIFY
===========  =====  =====  ======
SIGHUP       false  true   true 
... [removed for brevity]
(lldb) c
Process 17849 resuming
Process 17849 exited with status = 45 (0x0000002d) 
(lldb)

Как приложение может выйти, не вызывая никаких сигналов, выхода, _exit или _Exit?

Есть ли способ в lldb запустить процесс, а после выхода «вернуться», чтобы увидеть, где он вышел?

Есть ли способ для lldb регистрировать каждую инструкцию по сборке и т. д. (например, когда она ломается), чтобы вы могли отследить ее после выхода?


person Zimm3r    schedule 26.03.2016    source источник


Ответы (1)


Кому интересно, другой вариант ответа можно найти здесь.


Что здесь происходит?

Скорее всего, вы имеете дело с анти-отладочной техникой, подобной этой:

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
comment
Вау, довольно рецензия. Я обязательно посмотрю, было ли это тем, что я испытал, и, надеюсь, отчитаюсь. - person Zimm3r; 24.12.2017