onsdag 30 augusti 2017

Hooka in i Netfilter

Detta är en fristående fortsättning på mitt förra inlägg där jag visade hur man skriver en enkel kärnmodul som kan laddas in i en aktiv Linuxkärna. Om du inte redan har koll på hur man kompilerar en modul som kan laddas in i din dators kärna rekommenderar jag att du börjar med mitt förra inlägg innan du tar dig an den här.

Efter du läst den här texten kommer du:

  • Ha bättre förståelse för hur nätverk fungerar i en Linuxmaskin
  • Kunna skriva moduler som hookar in i valfri del av routingprocessen och i och med det få kontroll och fri tillgång till alla datapaket och dess innehåll
Låter detta intressant? Vad bra, då kör vi!

Vi börjar med en kort överblick över Netfilter, som är det ramverk i Linuxkärnan vi kommer använda oss av idag.

IP-paketets fantastiska resa genom Linuxkärnan

Det finns två olika sätt för ett nytt IP-paket att dyka upp i kärnan. Antingen har den skapats på din dator, eller så kommer den in utifrån. Hur paketet hanteras av kärnan skiljer sig något åt mellan de två fallen, så jag kommer gå igenom båda. Jag kommer i tur och ordning presentera de hooks, eller 'krokar', som Netfilter erbjuder moduler att hooka in i. Om ni är vana användare av iptables så kommer ni känna igen er.

Ett paket kommer lastat

Paket som kommer in utifrån tas först emot av ett interface. Detta kan vara av typen Ethernet, Wi-Fi eller valfritt länkprotokoll. Modulen vi skriver lever på IP-lagret, så vi behöver faktiskt inte bry oss om alls hur paketet tog sig till oss (eller vilken väg den tar härifrån).

Det här initiala tillståndet kallas i Linuxvärlden för PREROUTING, det som händer innan kärnan har kollat upp hur paketet ska hanteras. Efter det här steget är det dags för paketet att routas. Detta innebär i stora drag att kärnan tar reda på om

  1. paketet är menat åt den lokala maskinen
  2. eller
  3. paketet är menat för en annan maskin och måste skickas vidare åt rätt håll.
För de flesta hemdatorer är alternativ 1 det klart vanligaste. Talar vi istället om en router så är det nästan uteslutande alternativ 2 som sker.
Nu banar vägen åt olika håll. Vi börjar med att undersöka alternativ 1.

Alternativ 1) Välkommen in

Detta steget kallas kort och gott för INPUT. Paketet packas upp och skickas vidare upp i nätverksstacken tills den förhoppningsvis slutligen når ett väntande program.

Alternativ 2) Du ska ditåt

Ska paketet till en annan host hamnar den istället i FORWARD. Innan den kommer hit har kärnan tagit reda på vilken väg paketet behöver skickas för att komma dit den ska. Detta betyder att kärnan vid det här laget har full koll på:
  • vilket interface paketet ska skickas ut genom.
  • om den behöver adresseras till någon next-hop-router.
Var inte orolig om du inte har koll på vad en 'next-hop' är, den kunskapen är inte viktig för det här blogginlägget.

I övrigt händer det inte så mycket spännande i FORWARD. Det är mest en transportsträcka för att komma fram till den slutgiltiga stationen som är..

POSTROUTING, där paketet skickas nedåt i nätverksstacken och till slut skickas iväg ut på ett interface för att fortsätta sin resa.

Ett nytt paket föds

Paket som skapas lokalt går igenom en liknande routningprocess som den efter PREROUTING, och skickas sedan till OUTPUT. Här passerar all lokalt genererad trafik, och härifrån finns bara en väg att gå, och det är till samma POSTROUTING som jag beskrev ovan.

Hooks i Netfilter

Nu har ni fått en väldigt kortfattad rundtur i Netfilter. Vi kommer nu använda oss av dessa hookar för att själva få total kontroll över hur paket skickas genom vår maskin.

Talk is cheap. Show me the code.

Precis som förra gången börjar vi med att skapa oss en projektmapp.
~$ mkdir myhook
~$ cd myhook
~/myhook$

Ni som läst mitt förra inlägg kommer känna igen sig i följande kodsnutt för myhook.c

Det enda nya här är KERN_INFO. Den säger att de meddelanden vi skriver ut endast är informativa. Vill vi uppmärksamma att något gått fel kan vi istället använda oss av KERN_WARN eller KERN_ERR. Samtliga nivåer finns listade här för den som är nyfiken.

Vårt projekt behöver även en make-fil. Dagens Makefile ser ut så här:

Nästan en exakt kopia av den vi använde senast. Testa gärna att bygga med make och städa upp med make clean för att se att allting fungerar. Återigen, om något känns oklart ber jag dig att läsa mitt förra inlägg innan du fortsätter med den här.

Än så länge har jag inte visat er något nytt, men det ska vi ändra på. Kolla här!

Lite mer kod än vi jobbat med hittills men fortfarande inga konstigheter.

Det första vi ser är my_hook_function som är den funktion som kommer köras för varje paket som färdas ut genom din dator. Parametern skb, som är en förkortning för 'socket buffer', innehåller all information kärnan har om paketet. För den nyfikne finns hela innehållet deklarerat här.

Just nu skriver vår hook bara ut ett spydigt meddelande och returnerar NF_DROP, vilket kommer leda till att kärnan slänger paketet utan att skicka ut den på något interface. Om vi istället vill släppa ett paket vidare returnerar vi NF_ACCEPT.

printk_ratelimit() är en väldigt händig funktion som ser till att vi inte fyller våra loggar för snabbt. Den kommer returnera 1 så länge vi inte försöker skriva fler än tio meddelanden till loggen under en femsekundersperiod.

Vi måste på något sätt berätta för Netfilter att vi finns och hur den kan använda oss. Detta gör vi genom att fylla i en struct av typen nf_hook_ops. Man ger den namnet på sin hook-funktion, vilket protokoll man är intresserad av, vilken kedja man vill hooka in och var i ordningen man vill ligga i den kedjan. Som ni kan se i koden ovan fyller vi in att vår hook-funktion är my_hook_funktion, att vi vill hooka in i kedjan för IPV4 i POST_ROUTING och att vi vill vara den första hooken som körs där.

I vår init-funktion registrerar vi vår hook i Netfilter, och i vår exit-funktion avregistrerar vi oss. Kollar ni i andra moduler i Linux källkod så kommer ni se att de flesta följer samma mall, mer eller mindre. Man registrerar sina tjänster i init-funktionen, och avregistrerar dem i exit.

Testa gärna att bygga modulen med make och ladda in den med insmod. Du bör snabbt märka att ditt internet slutar fungera. Ingen fara! Ladda bara ur din modul med rmmod så bör allt bli som vanligt igen.

Var det allt?

Visst är vår modul häftig, men så länge du inte bara är ute efter att spara på din internettrafik så är den är inte så användbar. Jag tänkte avsluta med att visa er en modul som gör något lite mer spännande. Den kommer undersöka varje paket och kolla om det är en HTTP-request till en viss domän, och i så fall blockera.

Koden utgår från att man har vissa kunskaper om IP, TCP och HTTP, men har ni inte det så borde ni ändå kunna hänga med. Koden ser ut som följer:

Hooken kollar att det är ett TCP-paket på väg mot port 80. Är det det så använder den kärnans färdigbyggda funktion strnstr och letar efter vår utvalda domän i paketets data. Hittar vi den så droppar vi paketet direkt. Effekten blir att personer som använder sig av din dator för att nå internet kommer kunna surfa som vanligt, så länge de inte försöker nå vår utvalda sida.

Nu har vi skrivit en fantastisk modul som FRA säkert gärna skulle lägga sina vantar på. Det är bara en liten sak som stör mig. Om vi vill ändra vilken sida vi blockerar så måste vi ändra i koden och kompilera om modulen, vilket känns som lite för mycket arbete. Lösningen på detta är väldigt enkel och kräver bara en extra rad kod:

Rad 11 gör att vi numera kan välja vilken sida vi vill blockera varje gång vi laddar in vår modul. Exempel:
~$ sudo insmod myhook.ko target=klockren.nu

Hur fantastisk vår modul än är så har den fortfarande vissa begränsningar. Den kan bara blockera en sida åt gången, och den kan inte hantera HTTPS-trafik, vilket utgör den större delen av vårt vardagliga internetsurfande idag. I nästa blogginlägg kommer jag visa hur vi kan göra vår modul mer flexibel genom att blanda in iptables och då kunna blockera ett godtyckligt antal domäner med hjälp av egna matchningar.

"Problemet" (vissa menar att det är hela poängen) med HTTPS är knivigare. Det finns sätt, och jag kommer gå igenom i alla fall en metod jag känner till för att filtrera HTTPS-trafik i framtida blogginlägg.

Vad kul att du hängde med hela vägen! Nu är det bara att fortsätta undersöka och experimentera fritt med din nya kärnmodul. Har du svårt att veta var du ska börja så kan starta med att kolla in hur Linuxkärnan jobbar med IP, TCP och UDP.

1 kommentar:

  1. Hej!
    Jag vill bara säga att de två första inläggen har varit väldigt intressanta och lärorika!
    Jag arbetar själv som utvecklare men har inte vetat att man kan såpass enkelt lägga till moduler till kärnan i runtime!
    Jag kommer definitivt att testa detta själv vid tillfälle och noggrant läsa framtida inlägg!

    /David

    SvaraRadera