Jądro Linuksa: przykład hakowania wywołań systemowych
Próbuję napisać prosty kod testowy jako demonstrację podpięcia tabeli wywołań systemowych.
"sys_call_table" nie jest już eksportowany w 2.6, więc po prostu pobieram adres z systemu.plik mapy, i widzę, że jest poprawny(przeglądając pamięć pod adresem, który znalazłem, widzę wskaźniki do wywołań systemowych).
Jednak, gdy próbuję zmodyfikować tę tabelę, jądro daje "Oops" z " nie można obsłużyć żądania stronicowania jądra pod adresem wirtualnym c061e4f4 " i maszyna uruchamia się ponownie.
Tu CentOS 5.4 działa 2.6.18-164.10.1.el5. Czy jest jakaś Ochrona, czy mam tylko pluskwę? Wiem, że pochodzi z SELinux i próbowałem umieścić go w trybie permissive, ale to nie robi różnicy
Oto Mój kod:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
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;
}
4 answers
W końcu sam znalazłem odpowiedź.
Http://www.linuxforums.org/forum/linux-kernel/133982-cannot-modify-sys_call_table.html
W pewnym momencie jądro zostało zmienione tak, że tablica wywołań systemowych jest tylko do odczytu.
cypherpunk:
Nawet jeśli jest późno, ale rozwiązanie może zainteresować również innych: w wejście.Plik S znajdziesz: Kod:
.section .rodata,"a" #include "syscall_table_32.S"
Sys_call_table - > ReadOnly musisz skompiluj jądro nowy jeśli chcesz "hack" wokół z sys_call_table...
Link ma również przykład zmiany pamięci do zapisu.
nasekomoe:
Cześć wszystkim. Dzięki za odpowiedzi. I rozwiązałem problem dawno temu przez modyfikowanie dostępu do stron pamięci. I posiadają zaimplementowane dwie funkcje, które to dla mojego kodu górnego poziomu:#include <asm/cacheflush.h> #ifdef KERN_2_6_24 #include <asm/semaphore.h> 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 <linux/semaphore.h> 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
Oto zmodyfikowana wersja oryginalnego kodu, który działa dla mnie.
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/unistd.h>
#include <asm/semaphore.h>
#include <asm/cacheflush.h>
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;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-20 18:17:24
Dzięki Stephen, twoje badania były dla mnie pomocne. Miałem jednak kilka problemów, ponieważ próbowałem tego na jądrze 2.6.32 i otrzymałem WARNING: at arch/x86/mm/pageattr.c:877 change_page_attr_set_clr+0x343/0x530() (Not tainted)
, a następnie jądro OOPS o braku możliwości zapisu na adres pamięci.
Komentarz powyżej wspomnianej linii stwierdza:
// People should not be passing in unaligned addresses
Następujący zmodyfikowany kod działa:
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);
}
Zauważ, że to nadal nie ustawia strony jako odczyt/zapis w niektórych sytuacjach. Funkcja static_protections()
, która jest wywoływana wewnątrz set_memory_rw()
, usuwa flagę _PAGE_RW
, Jeśli:
- Jest w obszarze BIOS-u
Adres jest w środku .rodata
- CONFIG_DEBUG_RODATA jest ustawione, a jądro jest ustawione na tylko do odczytu
Odkryłem to po debugowaniu, dlaczego wciąż mam "nie mogę obsłużyć żądania stronicowania jądra", gdy próbuję zmodyfikować adres funkcji jądra. W końcu udało mi się rozwiązać ten problem, znajdując wpis w tabeli strony dla adresu osobiście i ręcznie ustawiając go na zapisywalny. Na szczęście, funkcja lookup_address()
jest eksportowana w wersji 2.6.26+. Oto kod, który napisałem, aby to zrobić:
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;
}
Wreszcie, chociaż odpowiedź marka jest technicznie poprawna, to będzie problem, gdy zostanie uruchomiony w Xen. Jeśli chcesz wyłączyć write-protect, użyj funkcji cr0 do odczytu/zapisu. I makro je tak:
#define GPF_DISABLE write_cr0(read_cr0() & (~ 0x10000))
#define GPF_ENABLE write_cr0(read_cr0() | 0x10000)
Mam nadzieję, że to pomoże każdemu, kto natknie się na to pytanie.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-19 03:47:22
Zauważ, że następujące działania będą również działać zamiast używania change_page_attr i nie mogą być zamortyzowane:
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));
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-10-24 00:13:13
Jeśli masz do czynienia z jądrem 3.4 i nowszym (może też działać z wcześniejszymi jądrami, nie testowałem go) polecam mądrzejszy sposób na pozyskanie lokalizacji wywołania systemowego.
Na przykład
#include <linux/module.h>
#include <linux/kallsyms.h>
static unsigned long **p_sys_call_table;
/* Aquire system calls table address */
p_sys_call_table = (void *) kallsyms_lookup_name("sys_call_table");
To wszystko. Brak adresów, działa dobrze z każdym jądrem, które przetestowałem.
W ten sam sposób możesz użyć nie wyeksportowanej funkcji jądra z twojego modułu:
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");
Smacznego!Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-29 09:02:05