Web、サーバ、ソフトウェア、バグ・脆弱性 などの情報を何人かで集まって書いていく IT/Web情報系ブログ

NEXON Game Manager(NGM)の持つ問題点と危険性

投稿日:   最終更新日:2015/01/22  投稿者:xx2zz

この記事では NEXON Game Manager(以下NGM) が抱える問題点とその危険性について書く。

以下の情報は NGM 1, 0, 1, 2 を基にしている。
ただし、実行結果は 1, 0, 1, 4 でも変わっていないことを確認している。

はじめに

NGMとは

NGM は NEXON のゲームタイトルをプレイするためにインストールを要求されるプログラムで、exe形式で配布されたセットアッププログラムを実行することで、ブラウザにプラグインとして組み込まれる。

記事を作成している2015年1月12日現在では、PC版の多く(全部…?)のタイトルでNGMのインストールを求められ、NGMなしにはゲームの起動が行えないようになっている。

スポンサーリンク

プラグインとは

ここでいうブラウザのプラグインとは、「拡張」「エクステンション」と呼ばれるものではなく、NPAPIというIE以外の主要ブラウザが共通して持つインタフェースを通じてブラウザ内で外部のネイティブコードを実行する仕組みのことである。

その実態はDLLで、ブラウザの制限を受けないため普通のネイティブアプリケーション同様どんなこともできる。

NGM のDLLは以下の場所にインストールされる。
C:\ProgramData\NexonJP\NGM\npNxGameJP.dll

インストールされたプラグインはブラウザで有効になっていれば、Webページ側からJavaScript経由でそのプラグインが持つ機能を呼び出すことができる。(詳細は後述)

※IEはNPAPIの代わりにActiveXという仕組みを持っていて、NGMもActiveX用のものがあるのかもしれないが、解析・検証したのはNPAPI版のほうだけなのでActiveXについては今回書いていない。

NGMが持つ機能

ブラウザのプラグイン情報(about:plugins)からNGMは以下の2つのMIMEタイプを持つことがわかる。

  • application/x-npnxgame-jp
  • application/x-npnxminfo-jp

また、OllyDbgでDLLを読み込みと参照文字列一覧とブラウザからプラグインを呼び出したときに実行されるルーチンから、以下の機能をJavaScriptオブジェクトのメソッドまたはプロパティを通して呼び出せると推測できる。

NGMが呼び出せると思われる機能

このうち、Lunch~GameDatasはゲームのインストール及び起動に関する機能であることが、 デバッガ上での追跡と実際にhtmlから呼び出してみてわかった。

これらの機能もいずれ詳細な解析をしたいが、今回はその名前から実行したマシンに関する何らかの情報を取得できると思われる以下の2つの機能を対象に詳細な解析を行うことにした。

  • GenerateMID
  • GetPCName

NGMの呼び出し方

プラグインの読み込み(起動)

プラグインの読み込み(起動)

プラグインを呼び出すには通常objectまたはembedタグを記述し、type属性にそのプラグインに対応したMIMEタイプを指定する。

実際にhtmlからembedタグを記述してNGMを呼び出してみると、GenerateMID, GetPCNameに対応するMIMEタイプは”application/x-npnxminfo-jp”であることがわかった。

以下のJavaScript関数を実行すると動的にembedタグを生成しNGMを読み込める。

function create_np_object() {
	navigator.plugins.refresh(false);
	
	var embed = document.createElement('embed');
	embed.id              = 'ngm';
	embed.type            = 'application/x-npnxminfo-jp';
	embed.pluginspage     = 'pop_ngm_guide.html';
	embed.style.visiblity = 'hidden';
	embed.style.height    = '0px';
	embed.style.width     = '0px';

	document.getElementsByTagName('body')[0].appendChild(embed);
	
	npobj = document.getElementById('ngm');

	return npobj;
}

各機能の呼び出し

JavaScriptからembed要素を取得し、その取得したelement オブジェクトのメソッドまたはプロパティとして当該プラグインが持つ各機能を呼び出すことができる。

GenerateMID, GetPCNameはメソッドで、戻り値として文字列が返るので以下のようなコードで呼び出す。

npobj = document.getElementById('ngm'); // 上のJavaScriptで生成したembed要素
var mid = npobj.GenerateMID();
var pcname = npobj.GetPCName();

この2つの機能を実際に呼び出し、戻り値を確認すると、GetPCNameではその名の通り実行したPCのコンピュータ名が、GenerateMIDは実行毎に異なる23バイトの謎の文字列が得られることがわかった。

また、NGMはプラグイン側で実行ドメインの制限を行っていないようで、 NEXON以外のドメインから各メソッドを実行することができた。

謎の23バイト

前述のとおり、GetPCNameに関しては戻り値がそのまんまコンピュータ名なのでどのような機能か自明だった。
しかし、GenerateMIDに関しては謎の文字列が得られるだけで、その文字列にどのような情報が含まれているかまでは分からなかった。

そこで、npNxGameJP.dll内のGenerateMIDが呼ばれたときに実行されるルーチンを追って当該文字列の生成フローを調べた。

MACアドレスの取得

謎の32バイトの生成フロー

まず、Win32APIのGetAdaptersInfoを使ってMACアドレスのリストを取得している。

その中からGetBestInterfaceで218.145.45.80との通信に使用するインタフェース、つまりNICのMACアドレスのみを得ている。

当該ルーチンをCで再現すると以下のような形になる。

 // a1はこの関数の引数
    SizePointer = 10240;
    pdwBestIfIndex = 0;
    if ( GetAdaptersInfo(&AdapterInfo, &SizePointer) )
    {
      result = 0;
    }
    else
    {
      addr = inet_addr("218.145.45.80");
      if ( GetBestInterface(addr, &pdwBestIfIndex) )
      {
        result = 0;
      }
      else
      {
        adpter = &AdapterInfo;
        do
        {
          if ( adpter->Index == pdwBestIfIndex )
          {
            macaddr = (int)adpter->Address;
            *(_DWORD *)a1 = *(_DWORD *)&adpter->Address[0];
            *(_WORD *)(a1 + 4) = *(_WORD *)(macaddr + 4);
            return 1;
          }
          adpter = adpter->Next;
        }
        while ( adpter );
        result = 0;
      }
    }

ボリュームシリアル番号の取得

次にGetVolumeInformationAでCドライブのボリュームシリアル番号を取得している。

ボリュームシリアル番号を取得

Cによる再現コードは以下のとおり。

 if ( GetSystemDirectoryA(&pszPath, 0x104u)
    && (drive_number = PathGetDriveNumberA(&pszPath), drive_number >= 0)
    && (VolumeSerialNumber = 0,
        MaximumComponentLength = 0,
        FileSystemFlags = 0,
        sub_10004750((char *)&RootPathName, 4, "%C:\\", drive_number + 65),
        GetVolumeInformationA(
          &RootPathName,
          &VolumeNameBuffer,
          0x104u,
          &VolumeSerialNumber,
          &MaximumComponentLength,
          &FileSystemFlags,
          &FileSystemNameBuffer,
          0x104u)) )
  {
    if ( VolumeSerialNumber )
      result = VolumeSerialNumber;
    else
      result = 1;
  }
  else
  {
    result = 0;
  }

謎のファイルの読み込み

次にC:\ProgramData\Nexon\Common\nxmk.datのファイルを開いて何やら読み込もうとしているが、現在このファイルは存在しないため戻り値は常に「0」になっている。

nxmk.datの読み込み

エンコード

エンコード

最後に、これまでに取得した3つの値 MACアドレス(6B) ボリュームシリアル番号(4B) 「0」(4B) 合計12バイト(以下では入力Iと記す)を次の手順でエンコード(暗号化)している。

  1. 1バイトの乱数A,B,Cを生成する
  2. Bの上位3ビットと下位5ビットを反転した値1バイトを配列Rの1番目に格納する
  3. Aの上位2ビットと下位4ビットを反転した値1バイトを配列Rの2番目に格納する
  4. Cの上位1ビットと下位7ビットを反転した値1バイトを配列Rの3番目に格納する
  5. Aの値をインデックスとして100131E8h~100139E8hに格納されている値2バイトを取得する(この値をK1,K2とする)
  6. K1(の値がなくなったらK2)から下位4ビットを取り出しその値をNとする
  7. 入力Iから1バイト取り出し、その値とBの排他的論理和を取り、その結果を配列RのN+4番目に格納する
  8. 5,6の操作を入力Iがなくなるまで(14バイト分)繰り返す
  9. 17バイトの配列Rを改変Base64(後述)で23バイトのASCII文字列に変換する

改変Base64は処理手順そのものは普通のBase64と同じだが、以下の点が異なる。

  • 参照テーブルに「vdNkqhCaTg2eDw45z_XMxHfY1bQo6i~EcJ9ljUZG8tVn0FArBR7LyPsWmpIOuK3S」を使用する
    • Base64では通常「ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/」を使用する
  • 変換後の文字列が4の倍数にならない場合は通常「=」記号を使用してパディングを行うが、それを行わない

GenerateMIDの実行毎に値が異なるのは、格納順と排他的論理和に使う値に乱数を用いているためである。
また、上記の手順1で生成した乱数Cは配列Rの3バイト目に格納しているだけでその他の操作に使用されていない。

Cの再現コードは以下のとおり。

v13 = 0;
  v15 = 0;
  v16 = 0;
  v17 = 0;
  v2 = rand() & 0xF;
  v22 = rand() & 0xF0 | v2;
  v3 = rand() & 0xF;
  v25 = rand() & 0xF0 | v3;
  v4 = rand() & 0xF;
  v26 = rand() & 0xF0 | v4;
  v13[0] = (unsigned __int8)(((signed int)v22 >> 5) | (unsigned __int8)(8 * v22));
  v13[1] = ((signed int)v25 >> 6) | 4 * v25;
  v13[2] = (unsigned __int8)(((signed int)v26 >> 7) | (unsigned __int8)(2 * v26));
  v27 = dword_100131E8[2 * v22];
  v28 = dword_100131EC[2 * v22];
  v19 = v27 & 7;
  for ( i = 0; i < 0xE; ++i )
  {
    v21 = (v27 & 0xF) + 3;
    if ( (signed int)(unsigned __int8)((v27 & 0xF) + 3) > 17 )
      return 0;
    v23 = *(_BYTE *)(i + a1);
    v6 = v25;
    v23 ^= v25;
    *((_BYTE *)&v13 + v21) = v23;
    LOBYTE(v6) = 4;
    v27 = unknown_libname_4(v6, v28);
    v28 = v7;
    v19 = v23 & 7;
  }
  v24 = 136;
  v11 = 0;
  v9 = 0;
  while ( v9 < v24 )
  {
    v29 = v9 >> 3;
    v30 = v9 & 7;
    if ( 8 - v30 <= 6 )
      v8 = 8 - v30;
    else
      v8 = 6;
    v31 = 6 - v8;
    v20 = (*((_BYTE *)&v13 + v29) << v30) & 0xFF;
    v20 = (signed int)v20 >> 2;
    if ( 6 - v8 > 0 )
      v20 |= (signed int)*((_BYTE *)&v13 + v29 + 1) >> (8 - v31);
    if ( (signed int)v20 > 64 )
      return 0;
    v12[v11] = byte_10013128[v20];
    v9 += 6;
    ++v11;
  }
  v12[v11] = 0;
  sub_10004000(a2, 24, v12); // memcpy
  return 1;
}

GenerateMIDで生成される謎の文字列の正体がわかったので、この理解であっていることを確かめるため、エンコードと逆の手順で元の値(MACアドレス、ボリュームシリアル番号)を取り出すデコーダを作り確認を行った。

作成したデコーダにGenerateMIDで生成された23バイトの文字列を入れると、確かに自PCのMACアドレスとCドライブのボリュームシリアル番号が得られた。

GenerateMIDのデコード

NGMの挙動についてのまとめ(要約)

NGMの機能について

  • NGMはexe形式で配布されたセットアッププログラムを実行することでインストールされるブラウザのプラグインである
  • NGMは「application/x-npnxgame-jp」「application/x-npnxminfo-jp」という2つのMIMEタイプを持つ(about:pluginsページで確認できる)
    • 前者では「Lunch」~「GameDatas」のメソッド・プロパティが、後者では「GenerateMID」「GetPCName」メソッドが使用可能と思われる
    • 前者については未検証。後者については今回の解析により確定
    • いずれのMIMEタイプも「GetVersion」メソッドを持ち、それぞれで戻り値が異なる。(1.0と1.2)
    • 前者はおそらくゲームのインストール・起動に使われるものだが、今回詳細な解析は行っていない
  • NGM側ではドメインなどによる呼び出し側の制限を行っていない
  • そのため、ブラウザでプラグインの実行が許可されていればNEXON以外のページからでもNGMを使用できる
  • それぞれのメソッドはJavaScriptから呼び出すことができ、その結果(戻り値)はJavaScript側で自由に使用できる
  • 「GetPCName」ではそのPCの「コンピュータ名」が取得できる(戻り値は生の文字列)
  • 「GenerateMID」ではそのPCの「MACアドレス」「Cドライブのボリュームシリアル番号」が取得できる(戻り値は暗号化されている)
    • 暗号化は公開鍵暗号ではなくバイト単位の共通鍵方式なので、NGMのDLLを解析して鍵を得ればその文字列は誰でも復号化できる
    • NGMの解析なしに暗号化された文字列だけ見ても出現文字が64個あることから改変Base64が使われていることぐらいしかおそらく推測できない
    • つまり、NEXONまたは第三者がGenerateMIDで取得した文字列をXHRなどで外部へ送信しても、そのパケットを見ただけでは何の情報が送られているか分からない

NGMの実際の使用例について

現在、NEXON内で「GenerateMID」「GetPCName」を使用しているページ(.jsファイル含む)は確認できていない。
(そもそも十分調べていない)

archive.orgで過去に”「GenerateMID」を呼び出す関数”が記述されたjsファイルがあったことは確認できる。

ただし、実際にそれが使用されていた(NEXONがMIDを取得していた)かどうかは不明。
つまり、プラグイン側には実装されているが、実際には使用されていない可能性がある。

何が問題か

NEXONにNICのMACアドレス、ボリュームシリアル番号を取得される(された)可能性がある。

しかし、MACアドレス、ボリュームシリアル番号をNEXON自身が取得することについては利用規約に記述があるようなので、行為そのものもの是非はともかく、ユーザの同意を得ている形になっている。

NEXON以外の第三者にMACアドレス、ボリュームシリアル番号を取得される(された)可能性がある。

どのような場合にどのような可能性があるか

  • Firefox, Google Chromeなどのプラグイン実行がデフォルトでブロックされるブラウザを使用している場合
    • NEXONのドメインでNGMの実行を許可している場合、NEXON自身にMACアドレス等が取得される可能性がある
    • NEXONのドメインでNGMの実行を許可していて、当該ドメインにXSS脆弱性がある場合に第三者に取得される可能性がある
    • NEXON以外のドメインでNGMの実行を許可した場合、第三者に取得される可能性がある
  • Firefox, Google Chromeなどでプラグイン実行がデフォルトでブロックされない時代からNGMをインストールしていた場合
    • NEXON自身にMACアドレス等が取得されていた可能性がある
    • NEXON以外のドメインで第三者にMACアドレス等が取得されていた可能性がある
  • IE以外で上記以外のブラウザを使用している場合
    • NEXON自身にMACアドレス等が取得される(されていた)可能性がある
    • NEXON以外のドメインで第三者にMACアドレス等が取得される(されたいた)可能性がある

Firefoxに関してはNEXON側からNGMを「常に有効化する」設定に変えるようアナウンスされている。

こういった質問をしてくるユーザに対しては便利な回答かもしれないが、これではアナウンスに従ったユーザがNEXON以外のサイトでも知らないうちにMACアドレスが取得される可能性がある。

2015/01/22 追記
Firefoxでの対策記事。
NGMのFirefoxでのClick-to-Play設定について

現在以下のブラウザではデフォルトでNGMがブロックされず、特にユーザに通知なく実行できることを確認している。

  • Safari 5.1.7 (Windows版)
  • Opera 26.0.1656.60

他にもあった気がするけど再検証するの面倒だからとりあえずこれだけ記載。

MACアドレス・ボリュームシリアル番号が外に漏れた場合にどのような危険があるか

例えば、Wi-Fiでインターネットを使用している場合、そのクライアントのMACアドレスとある程度居場所が絞り込める情報がセットで漏れた場合、無線のパケットをキャプチャすることで通信場所を特定される可能性がある。

NEXONコネクト利用者でGPS情報の送信を許可しているとまさしくセットになる。
(取得・保存しているかは別として)

MACアドレスもしくはボリュームシリアル番号を基にクライアントPCの識別をしているサービス・サイトがあった場合に、NGMで取得した他人のMACアドレスに偽装して認証の回避やabuse行為を行うことで他ユーザをBANさせるなども可能。

そもそもNEXONが生のMACアドレスを取得する必要があるのか

IPアドレスに依らないクライアントの識別のためにマシン固有の値を使用したい需要は理解できる。
ただし、それに生のMACアドレスを使う必要は全くなく、ハッシュ化した値を用いればよい。

また、そのような機能をプラグインに実装する場合は自身のサイト(ドメイン)以外で使用できないようドメイン制限を掛けるか、公開鍵で暗号化を行い秘密鍵を持たない第三者には生の値が得られないようにすべきである。

では、脆弱性と言えるか

利用規約に記述があることから、NEXON自身にMACアドレス等が取得されることにユーザは同意していたとしても、プラグインがデフォルトでブロックされないブラウザを使用している場合にNEXON以外の第三者にも取得され得るのは(おそらくそのような場合を想定していない)実装の問題であり、それは脆弱性と言えなくもない。

今後も危険か

ChromeはNPAPIの廃止を決定している。
主要ブラウザでNPAPIが使えなくなればMACアドレスを取得するようなプラグインは動かすことができなくなり、NGMもそのとき寿命を迎えることになるはず。

最後に

同様のケース

オンラインゲームのログインやスタートページではNGMのようにまずプラグインをインストールさせそのプラグインを通じてマシンの情報を取得したり、ゲームのインストール・スタートをさせる手法は他が運営するサイトでも使われている。

NGM以外に詳細な解析をしたプラグインはないが、GameOnのポータルサイト(http://www.pmang.jp/)では過去にプラグイン経由で生のMACアドレスを取得していたことを確認している(現在は使われていない)

プラグインは本来ブラウザの機能を拡張する汎用的なプログラムであるはずなので、主要ブラウザではドメインごとに実行を許可・禁止する仕様になっていなかった。

だからこそ、ユーザは利用しようとするプラグインが安全であるか、持っている機能を把握し、インストールは慎重に行う必要があった。

しかし、現状はこのとおり、自サービスのためだけのプラグインをユーザに具体的な説明もなくインストールさせているケースが多数ある。

そして、他のドメインで使用されることが望ましくないプラグインであっても、実行ドメインの制限がプラグインで正しくなされているか一般のユーザが検証するのは容易ではない。

そのようなプラグインを安易にユーザにインストールさせようとする(主にオンラインゲーム関連の)サービスはいかがなものかと思う。

未来について

すでにGoogle ChromeはNPAPIの廃止を決定しているし、Firefoxも同様の方針のようなので、数年以内にはこのような心配はいらなくなるはず(たぶん)

- NGM ,

Comment

  1. cialis_tadalafil より:

    Thanks for the purpose of offering this kind of awesome data.

Message

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事