Может ли ptrace определить, использовал ли системный вызов x86 64-битный или 32-битный ABI?

Я пытаюсь использовать ptrace для отслеживания всех системных вызовов, выполняемых отдельным процессом, будь то 32-разрядный (IA-32) или 64-разрядный (x86-64). Мой трассировщик будет работать на 64-битной установке x86 с включенной эмуляцией IA-32, но в идеале он сможет отслеживать как 64-битные, так и 32-битные приложения, в том числе если 64-битное приложение разветвляет и выполняет 32-битный процесс. .

Проблема в том, что, поскольку 32-разрядные и 64-разрядные номера системных вызовов различаются, мне нужно знать, является ли процесс 32-разрядным или 64-разрядным, чтобы определить, какой системный вызов он использовал, даже если у меня есть номер системного вызова. Кажется, есть несовершенные методы, такие как проверка /proc/<pid>/exec или (как это делает strace) размер структуры регистров, но ничего надежного .

Это усложняет тот факт, что 64-битные процессы могут выйти из длительного режима для непосредственного выполнения 32-битного кода. Они также могут делать 32-разрядные int $0x80 системные вызовы, которые, конечно же, используют 32-разрядные номера системных вызовов. Я не доверяю отслеживаемым процессам использовать эти уловки, поэтому хочу их правильно определять. И я независимо проверил, что по крайней мере в последнем случае ptrace видит 32-битные номера системных вызовов и назначения регистров аргументов, а не 64-битные.

Я покопался в исходном коде ядра и наткнулся на флаг TS_COMPAT в _ 4_, который выглядит как установить всякий раз, когда 32-разрядный системный вызов выполняется 64-разрядным процессом. Единственная проблема в том, что я понятия не имею, как получить доступ к этому флагу из пользовательского пространства, и возможно ли это вообще.

Я также подумал о том, чтобы прочитать %cs и сравнить его с $0x23 или $0x33, вдохновленный этим методом для переключения разрядности в запущенном процессе. Но это обнаруживает только 32-битные процессы, не обязательно 32-битные системные вызовы (сделанные с int $0x80) от 64-битного процесса. Он также хрупок, поскольку основан на недокументированном поведении ядра.

Наконец, я заметил, что архитектура x86 имеет немного длинного режима в регистре включения расширенных функций MSR. Но ptrace не имеет возможности прочитать MSR из трассируемой программы, и я чувствую, что чтение ее из моего трассировщика будет неадекватным, потому что мой трассировщик всегда работает в долгом режиме.

Я в растерянности. Возможно, я мог бы попробовать и использовать один из этих приемов, на данный момент я склоняюсь к методу %cs или /proc/<pid>/exec, но мне нужно что-то надежное, что действительно будет различать 32-битные и 64-битные системные вызовы. Как может процесс, использующий ptrace в x86-64, который обнаружил, что его трассировщик выполнил системный вызов, надежно определить, был ли этот системный вызов выполнен с помощью 32-битного (int $0x80) или 64-битного (syscall) ABI? Есть ли другой способ для пользовательского процесса получить эту информацию о другом процессе, для отслеживания которого он авторизован?


person ameed    schedule 24.11.2018    source источник
comment
Предлагается исправление ядра, чтобы добавить запрос PTRACE_GET_SYSCALL_INFO, чтобы помочь с этим.   -  person Mark Plotnick    schedule 29.11.2018
comment
@MarkPlotnick, это выглядит великолепно, спасибо, что указали на это! Если он будет объединен, я мог бы использовать этот запрос в качестве альтернативы устаревшему решению.   -  person ameed    schedule 30.11.2018
comment
Обновление: PTRACE_GET_SYSCALL_INFO находится в Linux 5.3.   -  person Mark Plotnick    schedule 23.10.2019


Ответы (1)


Интересно, что я не осознавал, что не существует очевидного более умного способа, который strace мог бы использовать для правильного декодирования int 0x80 из 64-битных процессов. (Над этим работают, см. этот ответ, где приведены ссылки на предлагаемое исправление ядра для добавления PTRACE_GET_SYSCALL_INFO в API ptrace. strace 4.26 уже поддерживает его на исправленных ядрах.)

Обновление: теперь поддерживается обнаружение системных вызовов IDK, основная версия ядра которой добавила эту функцию. Я тестировал Arch Linux с версией ядра 5.5 и strace версии 5.5.

например этот исходный код NASM собран в статический исполняемый файл:

mov eax, 4
int 0x80
mov eax, 60
syscall

дает этот след: nasm -felf64 foo.asm && ld foo.o && strace ./a.out

execve("./foo", ["./foo"], 0x7ffcdc233180 /* 51 vars */) = 0
strace: [ Process PID=1262249 runs in 32 bit mode. ]
write(0, NULL, 0)                       = 0
strace: [ Process PID=1262249 runs in 64 bit mode. ]
exit(0)                                 = ?
+++ exited with 0 +++

strace выводит сообщение каждый раз, когда системный вызов использует другую битность ABI, чем раньше. Обратите внимание, что сообщение о работает в 32-битном режиме совершенно неверно; он просто использует 32-битный ABI из 64-битного режима. «Режим» имеет особое техническое значение для x86-64, и это не это.


Со старыми ядрами

В качестве обходного пути, я думаю, вы могли бы дизассемблировать код в RIP и проверить, была ли это инструкция syscall (0F 05 < / a>) или нет, потому что ptrace позволяет читать память целевого процесса.

Но для случая использования безопасности, такого как запрет некоторых системных вызовов, это будет уязвимо для состояния гонки: другой поток в процессе системного вызова может перезаписать syscall байтов на int 0x80 после их выполнения, но до того, как вы сможете просмотреть их с помощью ptrace.


Это нужно делать только в том случае, если процесс работает в 64-битном режиме, в противном случае доступен только 32-битный ABI. Если это не так, вам не нужно проверять. (Страница vdso потенциально может использовать 32-битный режим syscall на процессорах AMD, которые его поддерживают, но не sysenter. Отсутствие проверки в первую очередь для 32-битных процессов позволяет избежать этого углового случая.) Я думаю, вы говорите, что у вас есть надежный способ хотя бы обнаружить это.

(Я не использовал API ptrace напрямую, только инструменты вроде strace, которые его используют. Так что я надеюсь, что этот ответ имеет смысл.)

person Peter Cordes    schedule 24.11.2018
comment
Мне не приходило в голову проверить код операции в% rip /% eip, но это имеет смысл! Спасибо за понимание. И я ценю упоминание об этом угловом случае - я надеялся, что смогу использовать сканирование кода операции в качестве основного метода, но похоже, что мне нужно сначала проверить разрядность процесса, прежде чем делегировать сканирование кода операции для 64-битных процессов. . В любом случае спасибо за помощь! - person ameed; 25.11.2018
comment
Итак, для ясности - вы говорите, что на странице vDSO 32-битного процесса (но не на странице 64-битного процесса) инструкция syscall использует 32-битный ABI? Это интересное несоответствие. - person ameed; 25.11.2018
comment
@ameed: 32-битный режим AMD syscall - это, по сути, другая инструкция, даже несмотря на то, что он имеет ту же мнемонику и тот же код операции, что и 64-битный syscall. Очевидно, он не может использовать R11d в устаревшем режиме на чистом 32-битном процессоре, потому что этот регистр не существует. Однако сторона ядра отличается в режиме совместимости от устаревшего режима, и IIRC Linux даже не использует его в устаревшем режиме, потому что он слишком плохо спроектирован, чтобы его можно было использовать. Но он будет в режиме совместимости, если sysenter недоступен. Системный вызов или sysenter в 32-разрядной версии Linux? - person Peter Cordes; 25.11.2018
comment
@ameed: см. также Что произойдет, если вы используете 32-битный int 0x80 Linux ABI в 64-битном коде? для ссылок на Linux. entry_64_compat.S (32-битные точки входа ABI в 64-битное ядро) и entry_32.S (32-битные точки входа ABI в 32-битное ядро). В частности, - person Peter Cordes; 25.11.2018
comment
Я посмотрю на них. Спасибо! - person ameed; 25.11.2018