Linuxカーネルがプロセスアドレス空間に侵入し、プロセスメモリを変更する方法

Linuxカーネルがプロセスアドレス空間に侵入し、プロセスメモリを変更する方法

プロセス アドレス空間の分離は、現代のオペレーティング システムの注目すべき機能です。これは、「古い」オペレーティング システムと区別する特徴でもあります。

プロセス アドレス空間の分離とは、メモリが共有されていると宣言されていない限り、プロセス P1 がプロセス P2 のメモリに任意の方法でアクセスできないことを意味します。

これは非常に理解しやすいです。例を挙げてみましょう。

原始的な野蛮社会には家族という概念がないことは周知の事実です。すべての資源は部族内で共有され、すべての野蛮人はいつでも、どんな形でも他の野蛮人と交流することができます。これは、メモリ アドレス空間が分離されていない DOS などのオペレーティング システムの場合です。プロセスは他のプロセスのメモリに自由にアクセスできます。

その後、家族という概念が出現し、家族の資源は隔離され、他人の家に侵入できなくなりました。所有者の許可がない限り、他人の家に入り込んで他人の物を勝手に持ち去ることはできなくなりました。オペレーティング システムがモダン モードに移行した後、プロセスにもファミリーに似た概念が生まれました。

しかし、家族という概念は仮想的なものであり、人々は合意に従うだけで、他人の家族を破壊しないのです。家は家族を守る物理的なインフラストラクチャとして機能します。オペレーティング システムでは、ホームは仮想アドレス空間に類似しており、ハウスはページ テーブルです。

隣人はあなたの家に侵入できませんが、警察はできますし、正当な理由があれば政府当局も侵入できます。いわゆる特権管理機関は、正当な理由がある限り、一般人の家に立ち入り、家族の所有物に触れることができる。オペレーティング システムの場合、これはカーネルが実行できることであり、カーネルは任意のプロセスのアドレス空間にアクセスできます。

もちろん、警察が理由もなく他人の家に侵入しないのと同じように、カーネルも理由もなく他人の家に侵入することはありません。

ただし、カーネルに意図的にこれを実行させて、不正な操作を実行させることもできます。

まずは試しにプログラムを見てみましょう:

//テスト.c
// gcc テスト.c -o テスト
#include <stdio.h>
#include <stdlib.h>
#include <文字列.h>
#include <unistd.h>
#include <sys/mman.h>

int メイン()
{
  char* addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  strcpy(addr, "浙江省温州市ピクシーシ");

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

  printf("before:%s \n", addr);

 getchar();

  printf("after:%s\n", addr);

  0を返します。
}

このプログラムの出力は非常にシンプルです。前後ともに「浙江温州妖精市」が出力されます。しかし、この文を変更したいのですが、どうすればよいでしょうか?当然ですが、テスト プロセス自体が変更しない場合は、私たちにできることは何もありません... しかし、民家に侵入するのと同じように、カーネルに強制的に変更させることはできます。

次にカーネルモジュールを記述します。

//テスト.c
// make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` モジュール
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/module.h>

静的int pid = 1;
モジュールパラメータ(pid, int, 0644);

静的符号なしロングアドレス = 0;
module_param(アドレス、long、0644);

// 仮想アドレスに基づいてプロセスのページ テーブルを見つけることは、家族の家の住所を見つけて侵入することと同じです。
静的 pte_t* get_pte(構造体 task_struct *タスク、符号なしロングアドレス)
{
 pgd_t* pgd;
 pud_t* プッド;
 pmd_t* pmd;
 pte_t* pte;
 構造体 mm_struct *mm = task->mm;

 pgd = pgd_offset(mm, アドレス);
 pgd_none(*pgd) の場合 || pgd_bad(*pgd))
 NULL を返します。

 pud = pud_offset(pgd、アドレス);
 if(pud_none(*pud) || pud_bad(*pud))
 NULL を返します。

 pmd = pmd_offset(pud、アドレス);
 pmd_none(*pmd) の場合 || pmd_bad(*pmd))
 NULL を返します。

 pte = pte_offset_kernel(pmd、アドレス);
 (pte_none(*pte) の場合)
 NULL を返します。

 pte を返します。
}

静的 int test_init(void)
{
 構造体task_struct *タスク;
 pte_t* pte;
 構造体ページ*ページ;

 // このファミリを検索 task = pid_task(find_pid_ns(pid, &init_pid_ns), PIDTYPE_PID);
 // この家族がどこに住んでいるか調べる if(!(pte = get_pte(task, addr)))
 -1 を返します。

 ページ = pte_page(*pte);
 // 強制的に侵入 addr = page_address(page);
 //sdajgdoiewhgikwnsviwgvwgvw
 strcpy(addr, (char *)"雨で水が溢れても太りません!");
 // 作業が完了したら、実績と名声を隠して退出します。 return 0;
}

静的void test_exit(void)
{
}

モジュールを初期化します。
モジュールを終了(テスト終了)します。
MODULE_LICENSE("GPL");

さあ、試してみましょう:

[ルート@10 ページ置換]# ./test
アドレス: 140338535763968 pid:9912
前:浙江省温州市ピクシーシ

この時点でカーネルモジュールtest.koをロードします。

[root@10 テスト]# insmod test.ko pid=9912 addr=140338535763968
[root@10 テスト]#

テストプロセスで Enter キーを押します。

[ルート@10 ページ置換]# ./test
アドレス: 140338535763968 pid:9912
前:浙江省温州市ピクシーシ

after:雨で洪水になっても水は増えません!
[ルート@10 ページ置換]#

どうやら、「浙江省温州市では革靴が濡れる」が「雨が降ってびしょ濡れになると太らない」に変わったようだ。

上記のカーネルモジュールの get_pte 関数をよく見てください。この関数を正しく記述するには、破壊したいプロセスが配置されているマシンの MMU について、32 ビットシステムか 64 ビットシステムか、3 レベルページテーブルか 4 レベルページテーブルか 5 レベルページテーブルかなど、ある程度理解している必要があります。これ…

Linux の楽しさは、自分で行うことも、他の人にやってもらうこともできるという点にあります。たとえば、プロセスの仮想アドレスのページ テーブル エントリによって示される物理ページを直接取得できます。

そのようなAPIはありますか?はい、すべてがファイルであることを忘れないでください。proc ファイル システムには、次のようなファイルがあります。

/proc/$pid/pagemap

このファイルを読み取ると、プロセスの仮想アドレスのページ テーブル エントリが取得されます。次の図はカーネル ドキュメントから引用したものです。

Documentation/vm/pagemap.txt

仮想アドレス空間はプロセスごとに存在しますが、物理アドレス空間はすべてのプロセスで共有されます。つまり、物理アドレスはグローバルです。

ここで、Documentation/vm/pagemap.txt の説明に従って、任意のプロセスの任意の仮想アドレスのグローバル物理アドレスを取得するプログラムを作成します。

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

int main(int argc, char **argv)
{
 整数データ;
 整数 pid;
 符号なしロング pte;
 符号なしロングアドレス;
 符号なしロング phy_addr;
 char procbuf[64] = {0};

 pid = atoi(argv[1]);
 引数はatol[2]です。

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

 fd = open(procbuf, O_RDONLY);
 size_t オフセット = (addr/4096) * sizeof(unsigned long);
 lseek(fd, オフセット, SEEK_SET);

 読み取り(fd、&pte、sizeof(unsigned long));

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

 0を返します。
}

次に、カーネル モジュールを変更します。

#include <linux/module.h>

静的符号なしロングアドレス = 0;
module_param(アドレス、long、0644);

静的 int test_init(void)
{
 strcpy(phys_to_virt(addr), (char *)"雨で水が溢れても太りません!");
 0を返します。
}

静的void test_exit(void)
{
}

モジュールを初期化します。
モジュールを終了(テスト終了)します。

MODULE_LICENSE("GPL");

最初に test を実行し、次に test の出力を getphys の入力として使用し、次に getphys の出力をカーネル モジュール test.ko の入力として使用すれば完了です。覚えていますか?これは複数のプログラムをパイプするスタイルではないでしょうか?

実際の住所を入力して変更するだけです。仮想アドレスを介してページ テーブルを取得する操作は、ユーザー モードでページマップ ファイルを読み取って解析することに置き換えられました。

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Linux で Valgrind を使ってチェックする (メモリ リークを防ぐため)
  • Linux システムで jmeter を実行し、ローカル メモリを最適化する方法の詳細な説明
  • Linux スワップメモリ​​を拡張する方法
  • Python3は、WindowsおよびLinuxシステムのCPU、ハードディスク、メモリ使用量、各ポートのオープン状態を監視します。詳細なコード例
  • Bash スクリプトを使用して Linux のメモリ使用量を監視する方法
  • CPU、マシンモデル、メモリなどの情報を表示するLinuxシステム
  • Linux で大容量メモリ ページを持つ Oracle データベースを最適化する方法
  • Linuxカーネルのメモリ管理アーキテクチャの詳細な説明
  • Linux システムでプロセスのメモリ使用量情報を出力するには、C プログラムを使用します。
  • Linux で php-fpm プロセスが多すぎるために発生するメモリ不足の問題を解決する
  • Python は Linux メモリを監視し、MongoDB に書き込みます (推奨)
  • Linux メモリ記述子 mm_struct の例の詳細な説明
  • Linux共有メモリ実装メカニズムの詳細な説明
  • Linux でメモリ使用量を確認する方法

<<:  Vue は PDF ファイルのオンライン プレビューを実装します (pdf.js/iframe/embed を使用)

>>:  MySQL マルチマスターと 1 スレーブのデータバックアップ方法のチュートリアル

推薦する

js キャンバスで円形の水のアニメーションを実現

この記事の例では、円形の水のアニメーションを実現するためのキャンバスの具体的なコードを参考までに共有...

MySQL インデックスクエリ最適化スキルを習得するための記事

序文この記事では、DBA がいないチームが参考にできるように、MySQL の一般的な使用に関するヒン...

VMware で VMware ツールをインストールしてもインストール ファイルが表示されない問題を解決する方法

VMware ツールは VMware の使用に非常に便利です。そのため、VMware ツールをインス...

Python で pymysql モジュールを使用して MySQL データベースに接続する

pymysqlをインストールするpip install pymysql 2|0pymysqlの使用2...

CSS でフロートとマージンを混合するサンプルコード

最近の勉強で、GitHub でレイアウトの練習をいくつか見つけたのですが、レイアウトにまったく慣れて...

シンプルなカルーセルの最も完全なコード分析を実装するJavaScript(ES6オブジェクト指向)

この記事では、シンプルなカルーセルを実装するためのJavaScriptの具体的なコードを参考までに紹...

表のセルの内容が超過した場合に省略記号効果を表示する(実装コード)

例示するフロントエンド開発では、セルの幅を制限し、コンテンツが制限を超える部分に省略記号を表示する必...

ウェブインターフェースデザインでウェブサイトのスタイルガイドを作成する方法(画像とテキスト付き)

スタイル ガイドとは何でしょうか? 簡単に言えば、ストーリーを伝える方法を説明するドキュメントです。...

繰り返し送信、繰り返し更新、バックオフ防止に関する問題と解決策の分析

1つ。序文<br />この種の質問は、どの専門掲示板でも見かけます。Google で検索...

LinuxでifconfigとaddrがIPアドレスを表示できない問題を解決する

1. 仮想マシンにLinuxシステムをインストールし、仮想マシンを起動し、rootとパスワードを入力...

クロスブラウザ開発体験のまとめ(I)HTMLタグ

ページにDOCTYPEを追加するブラウザによってタグやスタイルシートの解釈が異なるため、さまざまなブ...

docker デプロイメントの実装手順 lnmp-wordpress

目次1. 実験環境2. Dockerソースをインストールする3. Dockerをインストールする4....

MySQLスローログクエリの詳細な説明

遅いログクエリ機能スロー ログ クエリの主な機能は、設定された時間しきい値を超える SQL ステート...

Nginx をインストールして複数のドメイン名を設定する方法

Nginx のインストールCentOS 6.x yum にはデフォルトで nginx ソフトウェア ...

MySQL は重複データを削除して最小の ID ソリューションを維持します

オンラインで検索して重複データを削除し、ID が最小のデータだけを残します。方法は次のとおりです。 ...