Немного устранения неисправностей показывает следующее:
- Конечно, ни одна из пользовательских программ не перестала использовать
read()
. Они все еще продолжают это называть.
- Нет "изоляции памяти". Таблица системных вызовов успешно модифицируется во время инициализации модуля, и указатель на
sys_read()
успешно заменяется указателем на hacked_read_test()
.
- Когда модуль загружен, системный вызов
read()
работает так, как если бы он был исходным.
- Изменение поведения произошло между ядрами
4.16
и 4.16.2
(то есть между 1 апреля 2018 и 12 апреля 2018 ).
Учитывая это, у нас есть довольно узкий список коммитов для проверки, и изменения, скорее всего, будут внесены в механизм системных вызовов. Ну, похоже, этот коммит - это то, что мы ищем (и еще немного).
Важной частью этого коммита является то, что он изменяет сигнатуры функций, определенных SYSCALL_DEFINEx
, так что они принимают указатель на struct pt_regs вместо аргументов syscall, то есть sys_read(unsigned int fd, char __user * buf, size_t count)
становится sys_read(const struct pt_regs *regs)
, Это означает, что hacked_read_test(unsigned int fd, char *buf, size_t count)
больше не является действительной заменой sys_read()
!
Итак, с новыми ядрами вы заменяете sys_read(const struct pt_regs *regs)
на hacked_read_test(unsigned int fd, char *buf, size_t count)
. Почему это не приводит к сбою, а вместо этого работает так, как если бы это был оригинальный sys_read()
? Рассмотрим упрощенную версию hacked_read_test()
еще раз:
unsigned long hacked_read_test( unsigned int fd, char *buf, size_t count ) {
if ( fd != 0 ) {
return original_read( fd, buf, count );
} else {
// ...
}
}
Хорошо. Первый аргумент функции передается через регистр %rdi
. Вызывающий sys_read()
помещает указатель на struct pt_regs
в %rdi
и выполняет вызов. Поток выполнения идет внутрь hacked_read_test()
, и первый аргумент fd
проверяется на отсутствие нуля . Учитывая, что этот аргумент содержит действительный указатель вместо дескриптора файла, это условие выполняется успешно, и поток управления переходит непосредственно к original_read()
, который получает значение fd
(т.е. фактически указатель на struct pt_regs
) в качестве первого аргумента , который, в свою очередь, затем успешно используется, как и предполагалось. Итак, поскольку ядро 4.16.2
ваше hacked_read_test()
эффективно работает следующим образом:
unsigned long hacked_read_test( const struct pt_regs *regs ) {
return original_read( regs );
}
Чтобы убедиться в этом, вы можете попробовать альтернативную версию hacked_read_test()
:
unsigned long hacked_read_test( void *ptr ) {
if ( ptr != 0 ) {
info( "invocation of hacked_read_test(): 1st arg is %d (%p)", ptr, ptr );
return original_read( ptr );
} else {
return -EINVAL;
}
}
После компиляции и insmod
этой версии вы получаете следующее:
invocation of hacked_read_test(): 1st arg is 35569496 (00000000c3a0dc9e)
Вы можете создать рабочую версию hacked_read_test()
, но кажется, что реализация будет зависеть от платформы, так как вам придется извлечь аргументы из соответствующих полей регистра regs
(для x86_84
это %rdi
, %rsi
и %rdx
для 1-го, 2-го и 3-го аргументов системного вызова соответственно).
Ниже приведена рабочая x86_64
реализация (проверено на ядре 4.19
).
#include <asm/ptrace.h>
// ...
unsigned long ( *original_read ) ( const struct pt_regs *regs );
// ...
unsigned long hacked_read_test( const struct pt_regs *regs ) {
unsigned int fd = regs->di;
char *buf = (char*) regs->si;
unsigned long r = 1;
if ( fd != 0 ) { // fd == 0 --> stdin (sh, sshd)
return original_read( regs );
} else {
icounter++;
if ( icounter % 1000 == 0 ) {
info( "test2 icounter = %ld\n", icounter );
info( "strlen( debug_buffer ) = %ld\n", strlen( debug_buffer ) );
}
r = original_read( regs );
strncat( debug_buffer, buf, 1 );
if ( strlen( debug_buffer ) > BUFFER_SIZE - 100 )
debug_buffer[0] = '\0';
return r;
}
}