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

推薦する

Vue プロジェクトで axios リクエストを使用する方法

目次1. インストール2. カプセル化に問題はない3. ファイルを作成する4. アドレス設定をリクエ...

史上最も簡単な MySQL データのバックアップと復元のチュートリアル (パート 2) (パート 37)

データのバックアップと復元パート3の詳細は次のとおりです基本的な概念:バックアップ、現在のデータまた...

jsはクリックしてカードを切り替える機能を実現します

この記事の例では、クリックしてカードを切り替える機能を実現するためのjsの具体的なコードを共有してい...

JavaScriptの浅いコピーと深いコピーについての簡単な説明

目次1. 直接譲渡2. 浅いコピー3. ディープコピー1. JSONオブジェクトメソッド2. 再帰コ...

Vueグローバルカスタム命令の実践 モーダルドラッグ

目次背景実装のアイデア成果を達成する背景最近取り組んでいるプロジェクトは、Vue2 で構築されたプロ...

Linux での Redis の永続性、マスター スレーブ同期、Sentinel の詳細な説明

1.0 Redis の永続性Redis はメモリ内データベースです。サーバー プロセスが終了すると、...

Docker に共通コンポーネント (mysql、redis) をインストールする方法

Dockerはmysqlをインストールします docker search mysql 検索 dock...

Vue 仮想リストの実例

目次序文デザイン成し遂げるまとめ序文最近は、いつも延々とスワイプしています。 Weibo をチェック...

キープアライブキャッシュをクリアする方法の詳細なグラフィック説明

目次オープニングシーンv-for を使用した直接レンダリングカスタムコンポーネントで直接レンダリング...

各 Nginx プロセスで開くことができるファイルの最大数を設定する方法

1. システム内で開いているファイルの最大数を確認する #現在のリソース制限設定を表示する ulim...

HTML、CSS、RSSフィードが正しいかどうかを確認する無料ツール

この種のエラーに対処するための 1 つの方法は、まずマークアップとスタイルシートを検証することです。...

17 個の JavaScript ワンライナー

目次1. DOMとBOM関連1. 要素にフォーカスがあるかどうかを確認する2. 要素の兄弟ノードをす...

フロントエンドの状態管理(パート2)

目次1. 再出発1.1. ストア(司書) 1.2. 状態(書籍) 1.3. アクション(図書貸出リス...

dockerfile-maven-plugin 使用ガイドの概要

目次pom 構成Setting.xml 構成ログインステータスログインが必要ですログインは必要ありま...

sbinディレクトリを生成せずにNginxをインストールするソリューション

エラーの説明: 1. Linux (CentOS 7 64) システムに Nginx (1.18.0...