Kernel Linux: exemplo de enganchamento de chamadas do sistema

Eu estou tentando escrever algum código de teste simples como uma demonstração de ligar a tabela de chamada do sistema.

“sys_call_table” não é mais exportado na versão 2.6, então eu estou apenas pegando o endereço do arquivo System.map, e eu posso ver que está correto (Olhando através da memory para o endereço que encontrei, eu posso ver os pointers para o chamadas do sistema).

No entanto, quando eu tento modificar essa tabela, o kernel dá um “Oops” com “não é possível manipular a solicitação de paginação do kernel no endereço virtual c061e4f4” e a máquina é reinicializada.

Este é o CentOS 5.4 executando 2.6.18-164.10.1.el5. Existe algum tipo de proteção ou eu apenas tenho um bug? Eu sei que vem com o SELinux, e eu tentei colocá-lo no modo permissivo, mas não faz diferença

Aqui está meu código:

#include  #include  #include  #include  void **sys_call_table; asmlinkage int (*original_call) (const char*, int, int); asmlinkage int our_sys_open(const char* file, int flags, int mode) { printk("A file was opened\n"); return original_call(file, flags, mode); } int init_module() { // sys_call_table address in System.map sys_call_table = (void*)0xc061e4e0; original_call = sys_call_table[__NR_open]; // Hook: Crashes here sys_call_table[__NR_open] = our_sys_open; } void cleanup_module() { // Restore the original call sys_call_table[__NR_open] = original_call; } 

Eu finalmente encontrei a resposta sozinho.

http://www.linuxforums.org/forum/linux-kernel/133982-cannot-modify-sys_call_table.html

O kernel foi alterado em algum momento para que a tabela de chamada do sistema seja somente leitura.

cypherpunk:

Mesmo que seja tarde, mas a solução pode interessar aos outros também: No arquivo entry.S você encontrará: Código:

 .section .rodata,"a" #include "syscall_table_32.S" 

sys_call_table -> ReadOnly Você precisa compilar o Kernel new se quiser “hackear” com sys_call_table …

O link também tem um exemplo de alteração da memory para ser gravável.

nasekomoe:

Oi pessoal. Obrigado por respostas. Eu resolvi o problema há muito tempo, modificando o access a páginas de memory. Eu implementei duas funções que fazem isso para o meu código de nível superior:

 #include  #ifdef KERN_2_6_24 #include  int set_page_rw(long unsigned int _addr) { struct page *pg; pgprot_t prot; pg = virt_to_page(_addr); prot.pgprot = VM_READ | VM_WRITE; return change_page_attr(pg, 1, prot); } int set_page_ro(long unsigned int _addr) { struct page *pg; pgprot_t prot; pg = virt_to_page(_addr); prot.pgprot = VM_READ; return change_page_attr(pg, 1, prot); } #else #include  int set_page_rw(long unsigned int _addr) { return set_memory_rw(_addr, 1); } int set_page_ro(long unsigned int _addr) { return set_memory_ro(_addr, 1); } #endif // KERN_2_6_24 

Aqui está uma versão modificada do código original que funciona para mim.

 #include  #include  #include  #include  #include  #include  void **sys_call_table; asmlinkage int (*original_call) (const char*, int, int); asmlinkage int our_sys_open(const char* file, int flags, int mode) { printk("A file was opened\n"); return original_call(file, flags, mode); } int set_page_rw(long unsigned int _addr) { struct page *pg; pgprot_t prot; pg = virt_to_page(_addr); prot.pgprot = VM_READ | VM_WRITE; return change_page_attr(pg, 1, prot); } int init_module() { // sys_call_table address in System.map sys_call_table = (void*)0xc061e4e0; original_call = sys_call_table[__NR_open]; set_page_rw(sys_call_table); sys_call_table[__NR_open] = our_sys_open; } void cleanup_module() { // Restore the original call sys_call_table[__NR_open] = original_call; } 

Obrigado Stephen, sua pesquisa aqui foi útil para mim. Eu tive alguns problemas, porém, como eu estava tentando isso em um kernel 2.6.32, e recebendo WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted) seguido por um OOPS do kernel sobre não ser capaz de gravar no endereço de memory.

O comentário acima da linha mencionada afirma:

 // People should not be passing in unaligned addresses 

O seguinte código modificado funciona:

 int set_page_rw(long unsigned int _addr) { return set_memory_rw(PAGE_ALIGN(_addr) - PAGE_SIZE, 1); } int set_page_ro(long unsigned int _addr) { return set_memory_ro(PAGE_ALIGN(_addr) - PAGE_SIZE, 1); } 

Observe que isso ainda não define a página como leitura / gravação em algumas situações. A function static_protections() , que é chamada dentro de set_memory_rw() , remove o sinalizador _PAGE_RW se:

  • Está na área da BIOS
  • O endereço está dentro .rodata
  • CONFIG_DEBUG_RODATA está definido e o kernel está definido como somente leitura

Descobri isso depois de depurar por que ainda “não consegui manipular a solicitação de paginação do kernel” ao tentar modificar o endereço das funções do kernel. Eu finalmente consegui resolver esse problema encontrando a input da tabela de páginas para o endereço e configurando-a manualmente para gravável. Felizmente, a function lookup_address() é exportada na versão 2.6.26+. Aqui está o código que eu escrevi para fazer isso:

 void set_addr_rw(unsigned long addr) { unsigned int level; pte_t *pte = lookup_address(addr, &level); if (pte->pte &~ _PAGE_RW) pte->pte |= _PAGE_RW; } void set_addr_ro(unsigned long addr) { unsigned int level; pte_t *pte = lookup_address(addr, &level); pte->pte = pte->pte &~_PAGE_RW; } 

Finalmente, enquanto a resposta de Mark é tecnicamente correta, isso causará problemas quando executado dentro do Xen. Se você quiser desativar a proteção contra gravação, use as funções read / write cr0. Eu macro eles assim:

 #define GPF_DISABLE write_cr0(read_cr0() & (~ 0x10000)) #define GPF_ENABLE write_cr0(read_cr0() | 0x10000) 

Espero que isso ajude alguém que se depara com essa questão.

Observe que o seguinte também funcionará em vez de usar change_page_attr e não pode ser depreciado:

 static void disable_page_protection(void) { unsigned long value; asm volatile("mov %%cr0,%0" : "=r" (value)); if (value & 0x00010000) { value &= ~0x00010000; asm volatile("mov %0,%%cr0": : "r" (value)); } } static void enable_page_protection(void) { unsigned long value; asm volatile("mov %%cr0,%0" : "=r" (value)); if (!(value & 0x00010000)) { value |= 0x00010000; asm volatile("mov %0,%%cr0": : "r" (value)); } } 

Se você está lidando com o kernel 3.4 e posterior (ele também pode trabalhar com kernels anteriores, eu não testei) eu recomendaria uma maneira mais inteligente de adquirir o local da tabela chamada de sistema.

Por exemplo

 #include  #include  static unsigned long **p_sys_call_table; /* Aquire system calls table address */ p_sys_call_table = (void *) kallsyms_lookup_name("sys_call_table"); 

É isso aí. Sem endereços, funciona bem com todos os núcleos que testei.

Da mesma forma que você pode usar uma function Kernel não exportada do seu módulo:

 static int (*ref_access_remote_vm)(struct mm_struct *mm, unsigned long addr, void *buf, int len, int write); ref_access_remote_vm = (void *)kallsyms_lookup_name("access_remote_vm"); 

Apreciar!