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 スレーブのデータバックアップ方法のチュートリアル

推薦する

Docker の MySQL コンテナのタイムゾーン問題の修正

序文Ahhang が Springboot プロジェクトを開発していたとき、フロントエンドから検証コ...

Linux インストール MySQL5.6.24 使用手順

Linux インストール MySQL ノート1. MySQL データベース サーバーをインストールす...

クリック範囲を拡大する入力チェックボックスを実装する方法

XML/HTML コードコンテンツをクリップボードにコピー< div style = &quo...

Bootstrap 3.0 の特殊効果の学習ノート(表示と非表示、フローティングの除去、閉じるボタンなど)

この記事の主な内容は次のとおりです。 1. 閉じるボタン2.キャレット3. フローティングを素早く設...

マークアップ言語 - リスト

標準化されたデザインソリューション - マークアップ言語とスタイルマニュアルWeb 標準ソリューショ...

Linux での MySQL のインストールに関する詳細なチュートリアル

1. MySQLサービスをシャットダウンする# service mysqld stop 2. rpm...

MySQL 8.0 のメモリ関連パラメータの概要

理論的には、MySQL によって使用されるメモリ = グローバル共有メモリ + max_connec...

Mybatis での動的 SQL ステートメント分析

この記事は主にMybatisでの動的SQL文の解析について紹介します。この記事のサンプルコードは非常...

ユーザーがフォームを繰り返し送信するのを防ぐ方法の概要

重複したフォーム送信は、マルチユーザー Web アプリケーションで最も一般的で厄介な問題です。重複送...

ウェブサイトに天気予報を挿入する方法

天気予報をウェブサイトに挿入すると、次のような効果が得られます。次のコードを挿入する必要があります:...

HTML に画像が存在しない場合にデフォルトの画像を表示する方法の例

画像リンク <img src="" /> jsを使用してURLが有効...

JavaScript の new 演算子の原理と例の詳細な説明

新しい用途new の機能は、コンストラクターを通じてインスタンス オブジェクトを作成することです。イ...

MySQL の if 関数の正しい使い方の詳細な説明

今日私が書こうとしている内容では、プログラムは 7 時間近く実行され、データベースに 1,000 万...

デジタルテーブル特殊効果を実現するネイティブJS

この記事では、ネイティブ JS で実装されたデジタル時計エフェクトを紹介します。エフェクトは次のとお...

div を下から上にスライドさせる CSS3 の例

1. まず、CSS3 のターゲット セレクターを使用し、a タグを使用して id セレクターを指定し...