ホームページ >運用・保守 >Linuxの運用と保守 >Linuxの写真とは何ですか
Linux では、pic の中国語の意味は「位置に依存しないコード」を意味します。これは、コードがどのアドレスにロードされても正常に実行できることを意味します。 PIC は、位置に依存しない共有ライブラリを生成するために使用されます。いわゆる位置に依存しないとは、共有ライブラリのコード セグメントが読み取り専用であり、コード セグメントに格納されることを意味します。複数のプロセスは、このコード セグメントを同時に共有できます。コピーすること。
#このチュートリアルの動作環境: linux7.3 システム、Dell G3 コンピューター。
Linux では、pic の正式名は「Position Independent Code」で、中国語で「位置に依存しないコード」を意味します。
1. プログラムの仮想アドレス空間と場所に関連するコードの概要
Linux プロセスがディスクからメモリにロードされて実行されると、カーネルはプロセスに仮想アドレス空間を割り当てます。仮想アドレス空間は領域のブロック (セグメント) に分割されます。最も重要な領域は次のとおりです。 1 - アプリケーション仮想アドレス空間の説明
カーネル アドレス空間はすべてのアプリケーションで同じです。アドレス空間のこの部分にはアプリケーションから直接アクセスできません。この記事の焦点はカーネル アドレス空間ではなく、アプリケーションのいくつかの重要なセグメントに焦点を当てます。
表 1 - アプリケーションの重要なセグメントの説明
システムでアドレスのランダム化が有効になっていない場合 (ASLR - アドレス空間レイアウトのランダム化、アドレスのランダム化は後で行われます) Linux では、上記の表の各セグメントのアドレス空間が固定アドレスに配置されます。
Linux X86_64 マシン上で各セグメントのアドレスがどのように配置されているかを確認するために、実際にプログラムを作成して、注目するセグメントをカバーする次のようなプログラムを作成します。図 2 - 仮想アドレス空間デモンストレーション プログラム
コンパイル
gcc -o addr_test addr_test.c -static(ここでは、位置関連のコードをデモンストレーションするために静的リンクが使用されています)特徴) このプログラムを3回実行すると、すべてのアドレスが固定値であることがわかります。これは、ASLR 機能がオンになっていない場合、システムはプログラムの仮想アドレス空間をランダムに割り当てず、プログラムのすべてのアドレスが固定ルールに従って生成されるためです。
図 3 - 固定セグメント アドレスの分布
objdump コマンドを使用して逆アセンブルした後、グローバル変数と関数呼び出しへのアクセスについては、アセンブリが住所はすべて固定されているため、このようなコードを位置関連と呼びます。
図 4 - 位置関連のコード アセンブリ ステートメントの例
この種のコードは、アドレスがハードコーディングされているため、次の場所にのみロードできます。ロードアドレスが変更されると、コード内でアクセスする変数や関数のアドレスが固定されるため、ロードアドレスが変更された後はプログラムが正常に実行できなくなります。
固定アドレス方式は単純ですが、動的ライブラリのサポートなどの一部の高度な機能を実装できません。ダイナミック ライブラリのコードは、mmap() システム コールを通じてプロセスの仮想アドレス空間にマッピングされますが、異なるプロセスでは、同じダイナミック ライブラリによってマッピングされる仮想アドレスは不確かです。動的ライブラリの実装に位置関連コードを使用すると、任意のアドレスで実行するという目的を達成できなくなるため、位置非依存コード PIC の概念を導入する必要があります。 さらに、アドレスのランダム化機能がオンになっていないシステムでは、プログラムの各セグメントのアドレスが固定されているため、ハッカーによる攻撃が容易になることがわかります (興味のある学生は検索できます) Ret2shellcode または Ret2libc 攻撃の場合)、現時点では、保護のために ASLR とともに PIE の概念を導入する必要があります。 2. 位置独立コード PIC とダイナミック ライブラリの実装PIC 位置独立コードとは、コードがどのアドレスであっても関係ないことを意味します。でロードされ、正常に実行できます。 gcc オプションに -fPIC を追加すると、関連するコードが生成されます。 PIC は、位置に依存しない共有ライブラリを生成するために使用されます。いわゆる位置に依存しないとは、共有ライブラリのコード セグメントが読み取り専用であり、コード セグメントに格納されることを意味します。複数のプロセスでこのコードを共有できます。セグメントを同時に作成します。コピーが必要です。ライブラリ内の変数(グローバル変数およびスタティック変数)はGOTテーブル経由でアクセスし、ライブラリ内の関数はPLT→GOT→関数ロケーション経由でアクセスします。 Linux で共有ライブラリをコンパイルする場合、-fPIC パラメータを追加する必要があります。追加しないと、リンク中にエラー メッセージが表示されます (このエラーは AMD64 マシンでのみ発生するという情報もありますが、私の Inter マシンでも発生しました)。
重要なポイント #1
- コード セグメントとデータ セグメントの間のオフセットコード セグメントとデータ セグメントの間のオフセットは、リンクアウト中にリンカによって与えられます。 PICにとって重要です。リンカが各オブジェクト ファイルのすべての p を結合すると、リンカは各 p のサイズとそれらの間の相対位置を完全に認識します。図 5 - コード セグメントとデータ セグメントのオフセットの例
上の図に示すように、この例では TEXT と DATA が互いに近接しています。 、DATA と TEXT が隣接しているかどうかに関係なく、リンカはこれら 2 つのセグメントのオフセットを知ることができます。このオフセットに基づいて、DATA セグメントの開始アドレスに対する TEXT セグメント内の任意の命令の相対オフセットを計算できます。上の図に示すように、TEXT セグメントがどの仮想アドレスに配置されるかに関係なく、mov 命令が TEXT 内の 0xe0 オフセットにあると仮定すると、DATA セグメントの相対オフセット位置は次のようになります。 TEXT セグメント - mov 命令は TEXT 内部オフセット = 0xXXXXE000 - 0xXXXX00E0 = 0xDF20
キーポイント #2 - X86
If での命令の相対オフセットの計算処理に相対位置を使用すると、コードを位置に依存せずに作成できることがわかります。しかし、X86 プラットフォームでは、mov 命令はデータ参照に絶対アドレスを必要とするので、どうすればよいでしょうか?
「キーポイント 1」の説明から判断すると、現在の命令のアドレスがわかれば、データ セグメントのアドレスを計算できます。 X86 プラットフォームには現在の命令ポインター レジスタ IP の値を取得する命令はありません (RIP は X64 上で直接アクセスできます) が、ちょっとしたトリックで取得できます。疑似コードの一部を見てみましょう:
図 6 - X86 プラットフォーム取得命令アドレス アセンブリ
このコードを実際に実行すると、次のことが起こります。 happens :
CPU が呼び出し STUB を実行すると、次の命令のアドレスがスタックに保存され、実行のためにラベル STUB にジャンプします。
STUBの命令はpop ebxなので、「pop ebx」命令のアドレスがスタックからebxレジスタにポップされ、IPレジスタの値が取得されます。 . .
1. グローバル オフセット テーブル GOT
前の点を理解した後、X86 独立データ参照で位置を実装する方法を見てみましょう。 、この機能はグローバル オフセット テーブル (GOT) を通じて実装されます。
GOT はデータ p に保存されるテーブルであり、多くの住所フィールド (エントリ) が記録されます。変数を参照したい命令があるとしますが、絶対アドレスを直接使用するのではなく、GOT内のエントリを参照します。データ p 内の GOT テーブルのアドレスは明確であり、GOT のエントリには変数の絶対アドレスが含まれます。
図 7 - コードアドレスと GOT テーブルエントリの関係
上図に示すように、「キーポイント 1」と「キーポイント」に従って、 2" では、まず現在の IP の値を取得し、次に GOT テーブルの絶対アドレスを計算できます。GOT テーブル内の変数のアドレス エントリのオフセットも既知であるため、位置に依存しないデータ アクセスを実現できます。
絶対アドレス mov 命令の擬似コードを例として取り上げます (X86 プラットフォーム):
図 8 - 位置関連の mov 命令の例
これを位置に依存しないコードに変換したい場合は、さらにいくつかの手順が必要です。
図 9 - 位置に依存しない mov 命令と次のコードを組み合わせた例GOT
上記の手順により、アドレスに依存しないコードで変数にアクセスできるようになります。しかし、別の疑問があります。GOT テーブルに格納されている VAR_ADDR 値はどのようにして実際の絶対アドレスになるのでしょうか?
libtest.so とグローバル変数 g_var があると仮定し、readelf -r libtest.so を渡すと、次の出力が表示されます
図 10 - rel.dyn セグメントのグローバル変数リダイレクトの説明フィールド
ダイナミック ローダーは、rel.dyn セグメントを解析し、リダイレクト タイプが R_386_GLOB_DAT であることを確認すると、次の処理を実行します: シンボル g_var を実現します。アドレス値をオフセット 0x1fe4 に設定します (つまり、Sym.Value の値を実際のアドレス値に置き換えます)
#2. 関数呼び出しの位置に依存しない実装 #理論的には、関数の PIC 実装もデータ参照 GOT テーブルと同じように位置に依存しません。関数のアドレスを直接使用する代わりに、GOT を検索することで関数の実際の絶対アドレスが見つかります。しかし実際には、関数の PIC 特性はこれを行わず、実際の状況はさらに複雑です。データ参照と同じ方法に従って、まず遅延バインディングという概念を見てみてはいかがでしょうか。
ダイナミック ライブラリ関数の場合、プログラムのアドレス空間にロードされるまで関数の実際のアドレスは不明です。ダイナミック ローダーがこれらの問題を処理し、実際のアドレスを解決します。このプロセスはバインディングと呼ばれます。ローダーは特別なテーブル検索および置換操作を実行する必要があるため、バインディング アクションには時間がかかります。
如果动态库有成百上千个函数接口,而实际的进程只用到了其中的几十个接口,如果全部都在加载的时候进行绑定操作,没有意义并且非常耗时。因此提出了延迟绑定的概念,程序只有在使用到对应接口时才实时地绑定接口地址。
因为有了延迟绑定的需求,所以函数的PIC实现和数据访问的PIC有所区别。为了实现延迟绑定,就额外增加了一个间接表PLT(过程链接表)。
PLT搭配GOT实现延迟绑定的过程如下:
第一次调用函数
图11 - 首次调用PIC函数时PLT,GOT关系
首先跳到PLT表对应函数地址PLT[n],然后取出GOT中对应的entry。GOT[n]里保存了实际要跳转的函数的地址,首次执行时此值为PLT[n]的prepare resolver的地址,这里准备了要解析的函数的相关参数,然后到PLT[0]处调用resolver进行解析。
resolver函数会做几件事情:
(1)解析出代码想要调用的func函数的实际地址A
(2)用实际地址A覆盖GOT[n]保存的plt_resolve_addr的值
(3)调用func函数
首次调用后,上图的链接关系会变成下图所示:
图12 - 首次调用PIC函数后PLT,GOT关系
随后的调用函数过程,就不需要再走resolver过程了
三、位置无关可执行程序PIE
PIE,全称Position Independent Executable。2000年早期及以前,PIC用于动态库。对于可执行程序来讲,仍然是使用绝对地址链接,它可以使用动态库,但程序本身的各个segment地址仍然是固定的。随着ASLR的出现,可执行程序运行时各个segment的虚拟地址能够随机分布,这样就让攻击者难以预测程序运行地址,让缓存溢出攻击变得更困难。OS在使能ASLR的时候,会检查可执行程序是否是PIE的可执行程序。gcc选项中添加-fPIE会产生相关代码。
四、Linux ASLR机制和PIE的关系
ASLR的全称为 Address Space Layout Randomization。在Linux 2.6.12 中被引入到 Linux 系统,它将进程的某些虚拟地址进行随机化,增大了入侵者预测目的地址的难度,降低应用程序被攻击成功的风险。
在Linux系统上,ASLR有三个级别
表2 - ASLR级别描述
ASLR的级别通过两种方式配置:
echo level > /proc/sys/kernel/randomize_va_space
或
sysctl -w kernel.randomize_va_space=level
例子:
echo 0 > /proc/sys/kernel/randomize_va_space 关闭地址随机化
或
sysctl -w kernel.randomize_va_space=2 最大级别的地址随机化
我们还是以文章开头的那个程序来说明ASLR在不同级别下时如何表现的,首先在ASLR关闭的情况下,相关地址不变,输出如下:
图13 - ASLR=0时虚拟地址空间分配情况
我们把ASLR级别设置为1,运行两次,看看结果:
图14 - ASLR=1时虚拟地址空间分配情况
可以看到STACK和MMAP的地址发生了变化。堆、数据段、代码段仍然是固定地址。
接下来我们把ASLR级别设置为2,运行两次,看看结果:
图15 - ASLR=2,PIE不启用时虚拟地址空间分配情况
可以看到此时堆的地址也发生了变化,但是我们发现BSS,DATA,TEXT段的地址仍然是固定的,不是说ASLR=2的时候,是完全随机化吗?
这里就引出了PIE和ASLR的关系了。从上面的实验可以看出,如果不对可执行文件做一些特殊处理,ASLR即使在设置为完全随机化的时候,也仅能对STACK,HEAP,MMAP等运行时才分配的地址空间进行随机化,而可执行文件本身的BSS,DATA,TEXT等没有办法随机化。结合文章前面讲到的PIE相关知识,我们也很容易理解这一点,因为编译和链接过程中,如果没有PIE的选项,生成的可执行文件里都是位置相关的代码。如果OS不管这一点,ASLR=2时也将BSS,DATA,TEXT等随意排布,可想而知程序根本不能正常运行起来。
明白了原因,我们在编译时加入PIE选项,然后在ASLR=2时重新运行一下看看结果如何
图16 - ASLR=2,PIE启用时虚拟地址空间分配情况
ASLR=2 で PIE をオンにすると、各セグメントの仮想アドレスが完全にランダム化されることがわかります。
関連する推奨事項: 「Linux ビデオ チュートリアル 」
以上がLinuxの写真とは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。