Wie der Linux-Kernel in den Prozessadressraum eindringt und den Prozessspeicher ändert

Wie der Linux-Kernel in den Prozessadressraum eindringt und den Prozessspeicher ändert

Die Isolierung von Prozessadressräumen ist ein bemerkenswertes Merkmal moderner Betriebssysteme. Auch dies ist eine Besonderheit, die es von „alten“ Betriebssystemen unterscheidet.

Die Isolierung des Prozessadressraums bedeutet, dass Prozess P1 nicht beliebig auf den Speicher von Prozess P2 zugreifen kann, sofern der Speicher nicht als gemeinsam genutzt deklariert ist.

Das ist ganz einfach zu verstehen. Ich gebe Ihnen ein Beispiel.

Wir wissen, dass es in der Gesellschaft primitiver Wilder kein Familienkonzept gibt. Alle Ressourcen werden innerhalb des Stammes geteilt, und jeder Wilde kann jederzeit und auf jede Art mit jedem anderen Wilden interagieren. Dies ist bei Betriebssystemen wie DOS der Fall, bei denen der Speicheradressraum nicht isoliert ist. Prozesse können frei auf den Speicher anderer Prozesse zugreifen.

Später, mit der Entstehung des Familienkonzepts, wurden die Familienressourcen isoliert und die Menschen konnten nicht mehr in die Häuser anderer Leute einbrechen. Man konnte nicht mehr in die Häuser anderer Leute eindringen und deren Sachen auf einfache Art und Weise mitnehmen, es sei denn, der Eigentümer erlaubte es. Nachdem das Betriebssystem in den modernen Modus gewechselt war, verfügte der Prozess auch über ein familienähnliches Konzept.

Doch das Konzept der Familie ist virtuell; die Menschen halten sich lediglich an die Vereinbarungen und zerstören nicht die Familien anderer Leute. Das Haus fungiert als physische Infrastruktur, die die Familie schützt. In einem Betriebssystem entspricht das Home dem virtuellen Adressraum und das Haus der Seitentabelle.

Nachbarn können nicht in Ihr Haus einbrechen, die Polizei hingegen schon. Aus triftigen Gründen können dies auch Regierungsbeamte tun. Die sogenannten privilegierten Verwaltungsbehörden können die Häuser gewöhnlicher Menschen betreten und den Besitz der Familie anrühren, sofern sie dafür ausreichende Gründe haben. Bei einem Betriebssystem ist dies die Aufgabe des Kernels, und der Kernel kann auf den Adressraum jedes Prozesses zugreifen.

Natürlich wird der Kernel nicht ohne Grund in die Häuser anderer Leute einbrechen, genauso wie die Polizei nicht ohne Grund in die Häuser anderer Leute einbrechen wird.

Sie können den Kernel jedoch dazu veranlassen, dies absichtlich zu tun, um etwas Unrechtes zu tun.

Probieren wir es aus und schauen uns zunächst ein Programm an:

//test.c
// gcc test.c -o test
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>

int main()
{
  char* Adresse = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  strcpy(Adresse, "Zhejiang Wenzhou Pixie Shi");

  printf("Adresse: %lu pid:%d\n", Adresse, getpid());

  printf("vorher:%s \n", Adresse);

 getchar();

  printf("nach:%s\n", Adresse);

  gebe 0 zurück;
}

Die Ausgabe dieses Programms ist sehr einfach. Sowohl davor als auch danach wird „Zhejiang wenzhou pixie shi“ ausgegeben. Aber wir möchten diesen Satz ändern. Was sollen wir tun? Wenn der Testprozess die Änderungen nicht selbst vornimmt, können wir natürlich nichts tun. Aber wir können den Kernel zwingen, Änderungen vorzunehmen, als würden wir in ein Privathaus einbrechen.

Als nächstes schreibe ich ein Kernelmodul:

//test.c
// make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` Module
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/module.h>

statische int pid = 1;
module_param(pid, int, 0644);

statische vorzeichenlose lange Adresse = 0;
module_param(Adresse, lang, 0644);

// Das Finden der Seitentabelle eines Prozesses basierend auf seiner virtuellen Adresse ist gleichbedeutend damit, die Adresse des Hauses der Familie zu finden und dann einzubrechen!
statisches pte_t* get_pte(Struktur task_struct *task, vorzeichenlose lange Adresse)
{
 pgd_t* pgd;
 pud_t* pud;
 pmd_t* pmd;
 pte_t* pte;
 Struktur mm_Struktur *mm = Aufgabe->mm;

 pgd = pgd_offset(mm, Adresse);
 wenn(pgd_none(*pgd) || pgd_bad(*pgd))
 gibt NULL zurück;

 pud = pud_offset(pgd, Adresse);
 wenn(pud_none(*pud) || pud_bad(*pud))
 gibt NULL zurück;

 pmd = pmd_offset(pud, Adresse);
 wenn(pmd_none(*pmd) || pmd_bad(*pmd))
 gibt NULL zurück;

 pte = pte_offset_kernel(pmd, Adresse);
 wenn (pte_none(*pte))
 gibt NULL zurück;

 Rückkehr pte;
}

statische int test_init(void)
{
 Struktur task_struct *task;
 pte_t* pte;
 Strukturseite*Seite;

 // Diese Familie suchen task = pid_task(find_pid_ns(pid, &init_pid_ns), PIDTYPE_PID);
 // Finden Sie heraus, wo diese Familie lebt, wenn (! (pte = get_pte (task, addr)))
 Rückgabe -1;

 Seite = pte_Seite(*pte);
 // Mit Gewalt einbrechen addr = page_address(page);
 //sdajgdoiewhgikwnsviwgvwgvw
 strcpy(addr, (char *)"Regenflutungswasser wird nicht dick!");
 // Nachdem die Arbeit erledigt ist, gehen Sie und verbergen Sie Ihre Erfolge und Ihren Ruhm. return 0;
}

statischer void test_exit(void)
{
}

modul_init(test_init);
modul_exit(test_exit);
MODULE_LICENSE("GPL");

Komm, lass es uns versuchen:

[root@10 Seitenersetzung]# ./test
Adresse: 140338535763968 PID: 9912
vorher:Zhejiang Wenzhou Pixie Shi

An dieser Stelle laden wir das Kernelmodul test.ko

[root@10 test]# insmod test.ko pid=9912 addr=140338535763968
[root@10-Test]

Drücken Sie im Testvorgang die Eingabetaste:

[root@10 Seitenersetzung]# ./test
Adresse: 140338535763968 PID: 9912
vorher:Zhejiang Wenzhou Pixie Shi

nachher: ​​Regen, Hochwasser wird nicht fett!
[root@10 Seite_ersetzen]

Offenbar wurde die Aussage „In Wenzhou, Zhejiang werden Ihre Lederschuhe nass“ in „Wenn es regnet und Sie durchnässt werden, werden Sie nicht dick“ geändert.

Schauen Sie sich die Funktion get_pte des obigen Kernelmoduls genau an. Um diese Funktion richtig schreiben zu können, müssen Sie ein gewisses Verständnis der MMU der Maschine haben, auf der sich der Prozess befindet, den Sie zerstören möchten, z. B. ob es sich um ein 32-Bit-System oder ein 64-Bit-System handelt und ob es sich um eine 3-stufige, 4-stufige oder 5-stufige Seitentabelle handelt. Das…

Der Spaß an Linux liegt darin, dass Sie es selbst erledigen oder es von jemand anderem erledigen lassen können. Beispielsweise kann die durch den Seitentabelleneintrag der virtuellen Adresse eines Prozesses angegebene physische Seite direkt abgerufen werden.

Gibt es eine solche API? Ja, vergessen Sie nicht, dass alles eine Datei ist. Im Proc-Dateisystem gibt es eine solche Datei:

/proc/$pid/pagemap

Wenn wir diese Datei lesen, erhalten wir den Seitentabelleneintrag der virtuellen Adresse des Prozesses. Die folgende Abbildung stammt aus dem Kernel-Dokument:

Documentation/vm/pagemap.txt

Der virtuelle Adressraum gilt pro Prozess, während der physische Adressraum von allen Prozessen gemeinsam genutzt wird. Mit anderen Worten: Physische Adressen sind global.

Schreiben Sie nun gemäß der Erklärung in Documentation/vm/pagemap.txt ein Programm, um die globale physikalische Adresse einer beliebigen virtuellen Adresse eines beliebigen Prozesses abzurufen:

// getphys.c
// gcc getphys -o getphys
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
 int fd;
 int pid;
 unsignierter langer Pte;
 vorzeichenlose lange Adresse;
 vorzeichenlose lange phy_addr;
 char procbuf[64] = {0};

 pid = atoi(argv[1]);
 Adresse = atol(argv[2]);

 sprintf(procbuf, "/proc/%d/pagemap", pid);

 fd = öffnen(procbuf, O_RDONLY);
 size_t Offset = (Adresse/4096) * Größe von (unsigned long);
 lseek(fd, Offset, SEEK_SET);

 lesen(fd, &pte, sizeof(unsigned long));

 phy_addr = (pte & ((((unsigned long)1) << 55) - 1))*4096 + addr%4096;
 printf("phy Adresse:%lu\n", phy_addr);

 gebe 0 zurück;
}

Dann ändern wir das Kernelmodul:

#include <linux/module.h>

statische vorzeichenlose lange Adresse = 0;
module_param(Adresse, lang, 0644);

statische int test_init(void)
{
 strcpy(phys_to_virt(addr), (char *)"Regenflutwasser wird nicht dick!");
 gebe 0 zurück;
}

statischer void test_exit(void)
{
}

modul_init(test_init);
modul_exit(test_exit);

MODULE_LICENSE("GPL");

Führen Sie zuerst „test“ aus, verwenden Sie dann die Ausgabe von „test“ als Eingabe für „getphys“ und anschließend die Ausgabe von „getphys“ als Eingabe für das Kernelmodul „test.ko“. Fertig. Erinnerst du dich? Ist das nicht der Stil der Weiterleitung mehrerer Programme?

Geben Sie eine physische Adresse ein und ändern Sie diese, das ist alles. Der Vorgang des Abrufens der Seitentabelle über die virtuelle Adresse wurde durch das Lesen und Analysieren der Pagemap-Datei im Benutzermodus ersetzt.

Das Obige ist der vollständige Inhalt dieses Artikels. Ich hoffe, er wird für jedermanns Studium hilfreich sein. Ich hoffe auch, dass jeder 123WORDPRESS.COM unterstützen wird.

Das könnte Sie auch interessieren:
  • Überprüfung mit Valgrind unter Linux (um Speicherlecks vorzubeugen)
  • Detaillierte Erläuterung zum Ausführen von JMeter unter einem Linux-System und zur Optimierung des lokalen Speichers
  • So erweitern Sie den Linux-Swap-Speicher
  • Python3 überwacht die CPU-, Festplatten- und Speichernutzung sowie den Öffnungsstatus jedes Ports von Windows- und Linux-Systemen. Detaillierte Codebeispiele
  • So überwachen Sie die Linux-Speichernutzung mit einem Bash-Skript
  • Linux-System zum Anzeigen von CPU, Maschinenmodell, Speicher und anderen Informationen
  • Methoden zur Optimierung von Oracle-Datenbanken mit großen Speicherseiten unter Linux
  • Detaillierte Erläuterung der Speicherverwaltungsarchitektur des Linux-Kernels
  • Verwenden Sie ein C-Programm, um Informationen zur Speichernutzung eines Prozesses unter einem Linux-System auszugeben
  • Lösen Sie das Problem der Speichererschöpfung, das durch zu viele PHP-FPM-Prozesse unter Linux verursacht wird
  • Python überwacht den Linux-Speicher und schreibt in MongoDB (empfohlen)
  • Detaillierte Erläuterung des Linux-Speicherdeskriptorbeispiels mm_struct
  • Detaillierte Erläuterung des Implementierungsmechanismus für gemeinsam genutzten Speicher unter Linux
  • So überprüfen Sie die Speichernutzung unter Linux

<<:  Vue implementiert eine Online-Vorschau von PDF-Dateien (mithilfe von pdf.js/iframe/embed)

>>:  Tutorial zur MySQL-Datensicherungsmethode mit Multi-Master und One-Slave

Artikel empfehlen

MySQL-Datenbank implementiert MMM-Hochverfügbarkeitsclusterarchitektur

Konzept MMM (Master-Master-Replikationsmanager fü...

Verständnis des synchronen oder asynchronen Problems von setState in React

Inhaltsverzeichnis 1. Ist setState synchron? asyn...

So ändern Sie das Passwort des Root-Benutzers in MySQL

Methode 1: Verwenden Sie den Befehl SET PASSWORD ...

Analyse und Beschreibung von Netzwerkkonfigurationsdateien unter Ubuntu-System

Ich bin heute auf ein seltsames Netzwerkproblem g...

Die Verwendung und der Unterschied zwischen JavaScript-Pseudo-Array und Array

Pseudo-Arrays und Arrays In JavaScript sind mit A...

Vue implementiert Beispielcode für Links- und Rechtsgleiteffekte

Vorwort Die bei der persönlichen tatsächlichen En...

Wie lang ist eine Funktion in js?

Inhaltsverzeichnis Vorwort Warum Wie viel kostet ...

Tutorial zur MySQL-Optimierung: Große Paging-Abfrage

Inhaltsverzeichnis Hintergrund LIMIT-Optimierung ...

Detaillierte Erläuterung des Lesevorgangs für Nginx-Anforderungsheaderdaten

Im vorherigen Artikel haben wir erklärt, wie ngin...

Hbase – Erste Schritte

1. HBase-Übersicht 1.1 Was ist HBase? HBase ist e...