エントリー
せっかちな、これらの叙情的な余談は読むことができません。少し前に、「Linux上のプログラムがインターネット(または特定のホスト)にアクセスしないようにするにはどうすればよいか」という考えに立ち会いました。 この考えは私を訪問し、私自身のビジネスでさらに飛びました。 そして今日
、askdev.ruの RSSリーダー
で質問を
1つ受け取りました 。 ああ! はい、これはまさに私が考えていたものです! 人を助け、同時に問題を自分で理解する必要があります。
解決策のヒントがあるかどうかを確認するためにGoogleに登りました。 そこから、しばらくの間、定期的な
iptablesを実行することが不可能になったことを学びました。人々は
AppArmorの方向を見るように勧めています。 「
AppArmorを学びたいという
強い欲求に燃えて」私はさらに検索を開始
し、ENTスペシャリストに関するメッセージを偶然偶然見つけました。
方法
接続機能を独自の機能に「置き換える」ことで構成され、接続機能を許可し、実際の機能の要求を「スキップ」するかどうかを決定し、エラーを返します。 尊敬されているChaoserのメッセージでは、ソケットの低レベルブロックが使用されていましたが、サーバーアドレスやポートに応じて決定を下すことはできませんでした。 これは私には不向きでした。1つのポート(80番目)のアクセスのみ拒否する必要がありました。
straceで
telnetを実行すると、すぐに適切な犠牲者、つまり
接続機能が
見つかりました。
straceは次のように説明しました。
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("87.250.251.3")}, 16) = 0
この説明は、IPアドレス、ポート、接続タイプ(AF_INET)など、必要なすべてのコンポーネントがあることを明確に示しています。
さあ、始めましょう。
解決策
このメソッドを実装するには、アプリケーションの起動時に他のライブラリよりも早く読み込まれるライブラリを作成し、そのライブラリで定義された(および他のライブラリ用に設計された)関数をインターセプトします。
まず、すべてのコードをレイアウトし、すべてを個別に分析します。
- #define _GNU_SOURCE
- #include <dlfcn.h>
- #include <stdio.h>
- #include <sys / types.h>
- #include <sys / socket.h>
- #include <netinet / in .h>
- #include <arpa / inet.h>
- #include <errno.h>
- static int (* real_connect)( int sockfd、 const struct sockaddr * addr、
- socklen_t addrlen)= 0;
- int connect( int sockfd、 const struct sockaddr * addr、
- socklen_t addrlen)
- {
- printf( "NF_DEBUG:--------------------------------------------- -\ n " );
- int sa_family = addr-> sa_family;
- printf( "NF_DEBUG:アドレスファミリ:%d(AF_INET =%d)\ n" 、sa_family、AF_INET);
- if (sa_family == AF_INET)
- {
- struct sockaddr_in * addr_in =( struct sockaddr_in *)(addr);
- struct in_addr sin_addr = addr_in-> sin_addr;
- uint16_t sin_port = addr_in-> sin_port;
- uint16_t sin_port_h = ntohs(sin_port);
- printf( "NF_DEBUG:IP:%s \ n" 、inet_ntoa(sin_addr));
- printf( "NF_DEBUG:ポート:%d \ n" 、sin_port_h);
- if (sin_port_h == 80)
- {
- printf( "NF_DEBUG:Rejected!\ n" );
- printf( "NF_DEBUG:--------------------------------------------- -\ n " );
- errno = ENETUNREACH;
- return -1;
- }
- }
- if (!real_connect)
- real_connect = dlsym(RTLD_NEXT、 "connect" );
- printf( "NF_DEBUG:Accepted \ n" );
- printf( "NF_DEBUG:--------------------------------------------- -\ n " );
- return real_connect(sockfd、addr、addrlen);
- }
*このソースコードは、 ソースコードハイライターで強調表示されました。
コード解析
上記のプログラムの動作を理解している人は、このセクションを安全にスキップできます。 どの行が何をするかを説明します。 これは初心者に役立つはずです。 Cをよく知っている人は、これからいくつかの点にしか興味がないかもしれません。1〜8行目はプリプロセッサディレクティブであり、興味深いものはありません。
dlsym関数に必要なGNU拡張機能を接続するディレクティブ
#define _GNU_SOURCEを 除きます。
行10〜11では、「実際の」接続関数へのポインターを宣言します。 私たちにとっては、
real_connectと呼ばれ
ます 。 関数の説明は
man connectから取得されます。
行13は、アプリケーションが呼び出す新しい
接続関数で始まり、この関数はこのアプリケーションをスキップするかどうかを決定します。 その説明は元の
接続と完全に一致しており、同じマニュアルから取られています。
必要なすべてのデータ(および私が
ここで取得した説明)を含む構造の17行目で、アドレスタイプを取得します。
アドレスタイプがAF_INET(20行目)の場合、つまり、アプリケーションがIPv4を介して「外部に要求」する場合、フィルタリング(22行目から37行目)を使用します。
接続をフィルタリングするには、
addr構造体を
sockaddr_in型に変換する必要があります。これにより、必要なフィールドにアクセスできます。 これは22行目で発生します。
次に、24〜25行目で、それぞれアドレスとポートの値を取得します。 26行目では、ポート番号のバイトの順序を変更し、接続中に使用される形式からポート番号の通常の形式(80、110、25)を取得します。
28行目では、結果のアドレスがテキスト形式に縮小されて表示され、29行目にポート番号が表示されます。
次に、31行目で、ポート番号がブロックする番号に対応しているかどうかを確認し、該当する場合は報告し(33〜34行目)、エラー番号を設定します(35行目。この場合、「ネットワークが利用できません」エラーが発生します)エラーを返します(36行目)。
すべてが正常な場合、つまり、許可されたポートまたは許可されたタイプのアドレスのいずれかを使用してアプリケーションがアクセスした場合、「実際の」
接続関数のアドレス(エントリポイント)を取得します(42行目)。
real_connectがグローバル変数であることを忘れて)、すべてが正常であることを書き(
44〜45行目)、制御を「実際の」
接続関数に転送します。
「実際の」
接続関数のアドレスを受け取ると、
RTLD_NEXTパラメーターを使用して、最初の(私たちの)からではなく、次のロードされたライブラリからこのアドレスを取得します。
使用する
ライブラリのコンパイル:
gcc -fPIC -shared -Wl,-soname,nonet.so -o nonet.so nonet.c
次のように目的のアプリケーションを実行します。
LD_PRELOAD=/< >/nonet.so <>
たとえば、ライブラリが
/ tmpにある場合、次の行はインターネットにアクセスせずに(もちろん、ポート80を介してのみ)
firefoxを起動できます。
LD_PRELOAD=/tmp/nonet.so firefox
おわりに
この方法は、普遍性と普遍性を主張するものではなく、ノーベル賞の記事でもありません。 ここでは、私にとって興味深い2つのトピックを取り上げましたが、特定のプログラムへのネットワークアクセスの制限とLinuxでの関数呼び出しのインターセプトが他の誰かにとっても興味深いものになることを願っています。
ご清聴ありがとうございました!
PS私はaskdev.ruからデジタルからコードを盗んだ不誠実な人とは見なされないように、デジタルと私は同じ人であることに言及すべきだと思います。
UPD habercheloveka
peter23を招待していただき、ありがとうございます。 ブログの投稿をLinux for Allに移動しました。