■
いやー、かなりの期間(約9年半)さぼってました。
何となく仕事の関係で、調べた事を残しておきたくて書いてます。
さて何の件かというと、パスワードのクラックにかかる時間です。
仕事の関係でパスワードクラックってどれくらい時間がかかるものなのか調べていたら
IPAさんが掲載していた2008年のレポート(https://www.ipa.go.jp/security/txt/2008/10outline.html)が見つかりました。
でも、このレポートは10年前のもので、H/Wの性能は高くなっているはずだと他を当たってみたのですが、
出てくるのは、かなり高性能なマシンでのレポートだったことから現実的じゃなかったので、
最終的に自分でプログラムを作成する事にしました。
取り敢えず手元の環境で作ろうと調べていて、NTLMハッシュだと、HDDからツールでPWファイルを引っ張ってきて
そのデータと比較してブルートフォースアタックでクラックできそうなので、その方法を再現するように
C#を使ってプログラミングしました。
時間計測の方法としては、NTLMハッシュ(MD4のハッシュ)のクラックを想定し、IPAさんの結果と比べられるように、
同種の文字種と桁数から発生する可能性のあるハッシュ値をブルートフォースで全件チェックするロジックです。
実際にしたことは、例えば4桁の英小文字であれば、"aaaa"から"aaab"、"aaac"から"zzzz"まで
の456,976種のハッシュ値を生成し、ランダムに発生させた5桁MD4のハッシュ値(基本的には一致
しないはずのハッシュ値)と比較するのにかかった時間を計測しました。
環境は、
CPU:Intel® Core™ i7-3770 @ 3.4GHz (4Core, 8Thread) / Mem: 12GByte
使用OS:Windows 7 Professional SP1 64bit
と言った感じです。
結果は、
文字種 | 4桁 | 6桁 | 8桁 | 10桁 |
---|---|---|---|---|
英小文字:26種 | 1秒未満 | 約40秒 | 約8時間 | 約227日 |
英小文字+数字:36種 | 1秒未満 | 約5分 | 約4.5日 | 約16年 |
英大小文字+数字:62種 | 約2秒 | 約2時間 | 約315日 | 約3,317年 |
英大小文字+数字+記号:93種 | 約10秒 | 約22時間 | 約22年 | 約18.6万年 |
と言った感じです。
なんだかぱっとしなかったので、再度ネットを検索して回ることに、
そこで見つかったのが、"GPUを使ったハックツールだと速い"というのがあったので、早速試してみる事に・・・。
今回は、hashcatの64bit版(http://hashcat.net/からダウンロード)を使いました。
(さすがにGPUのプログラミングまでは手が出なかった。)
とは言っても、はじめの環境には使えるGPUを積んだカードがなかったため、
GPU: NVIDIA® GeForce® GTX750 (CUDA Core 512)
を中古(\8,000-)で購入し、前述の環境へセッティング
結果は、
文字種 | 6桁 | 8桁 | 9桁 | 10桁 |
---|---|---|---|---|
英小文字:26種 | 数秒 | 約50秒 | 約20分 | 約9時間 |
英小文字+数字:36種 | 数秒 | 約11分 | 約6.5時間 | 約10日 |
英大小文字+数字:62種 | 約18秒 | 約14時間 | 約11日 | 約6年 |
英大小文字+数字+記号:95種 | 約3分 | 約18日 | 約5年 | 約430年 |
と言った感じです。
GTX750は最新のグラボではないけど、一般的に言われている8桁の英文字(大小)と数字のパスワードだと、
約14時間で解析できてしまうというのはいかがなものかと・・・。
今回のクラックではNTLMのMD4ハッシュだったので、ノートPCなどパスワードが入っているドライブを
盗まれなければ大丈夫かもしれないけど、今後はもう少し長めのパスワードにしないと危ないって事が分かった。
久々の更新
今回はプログラムではなくって、Wiresharkを使っていて覚えておきたいことを書くことにします。
仕事の関係で、Wiresharkのキャプチャデータをもらったのですが、それがなんと1M毎に分割された6000個以上もあるファイル群。
この中から必要なデータを探して処理をするなんて至難の業なので、マージが出来ないかと検索していたら・・・、ちゃんとあるじゃないですか。
さすがかゆ良いところに手が届く設計がされていること。
mergecap.exeコマンドを使うことで、複数のキャプチャファイルをマージできると書いてある。
コマンドラインで、
mergecap.exe -w 出力先ファィル名 マージ元ファイル ・・・
として実行すると、マージ元ファイルを全てマージした結果が出力先ファィル名に書出されました。
この先他にもありそうなので、今回の作業で必要な処理をコマンドラインで実行できないかと模索することにします。
FStringクラスの使用方法で
昨日公開したFStringクラスですが、説明文中で使用したサンプルのプログラムで冗長な部分があったので修正します。
/* * 指定したインターフェースに設定されたアドレスを取得する * param : インターフェースの番号(int) * * return : address_list */ address_list AddrInfo::setAddressList(int ifIndex) { address_list result; String ipaddress; String netmask; String bcastaddress; long retCode = NO_ERROR; DWORD dwSize = 0; GetIpAddrTable(NULL, &dwSize, 0); PMIB_IPADDRTABLE pIPAddrTable = (MIB_IPADDRTABLE *)malloc(dwSize); memset(pIPAddrTable, 0, dwSize); if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == NO_ERROR) { for (unsigned int num = 0; num < pIPAddrTable->dwNumEntries; num++) { if (ifIndex == pIPAddrTable->table[num].dwIndex) { ここ==> ipaddress = String((_TCHAR *)FString(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr))); ここ==> netmask = String((_TCHAR *)FString(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwMask))); bcastaddress = getBcastAddr(pIPAddrTable->table[num].dwAddr, pIPAddrTable->table[num].dwMask, pIPAddrTable->table[num].dwBCastAddr); AddrInfo info = AddrInfo(ipaddress, netmask, bcastaddress, pIPAddrTable->table[num].wType); result.push_back(info); } } } free(pIPAddrTable); return result; }
_bstr_tクラスをそのまま置換えちゃったので、
ipaddress = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr))); || V ipaddress = String((_TCHAR *)FString(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr)));
と何やら複雑なキャストがされていますが、後から色々とチェックしていて、
ipaddress = (String)FString(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr));
で良いことが分かりました。
_bstr_tクラスでは、"string"や"wstring"にキャストするオペレータがなく、キャストできるのが"char*"か"wchar_t*"になるために、String化する場合には、一旦_bstr_tを"char*"か"wchar_t*"にキャストしてStringを構成させる方法だったのですが、FStringでは直接"string"や"wstring"にキャストするオペレータを用意したので、(_TCHAR *)へのキャストとStringの作成が不要だったのでした。
実際には、ipaddressがStringクラスなのでキャストも不要ですが、一応明示的に付けてあります。
MicroSoftの_bstr_tクラスをシミュレート
最後に日記を書いたあとの作業が、社外秘の情報が多くて日記に書けない内容だったので、ずーっと空いてしまいました。
今回は文字コード変換のロジックをまかなうクラスを作ってみました。
以前作ったプログラムで、UNICODE文字セットでも、MBCS文字セットでも動作するプログラミングをするために、文字セットに依存しそうなロジック中では、"_bstr_t"というMSのクラスを利用していました。
例えば、
/* * 指定したインターフェースに設定されたアドレスを取得する * param : インターフェースの番号(int) * * return : address_list */ address_list AddrInfo::setAddressList(int ifIndex) { address_list result; String ipaddress; String netmask; String bcastaddress; long retCode = NO_ERROR; DWORD dwSize = 0; GetIpAddrTable(NULL, &dwSize, 0); PMIB_IPADDRTABLE pIPAddrTable = (MIB_IPADDRTABLE *)malloc(dwSize); memset(pIPAddrTable, 0, dwSize); if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == NO_ERROR) { for (unsigned int num = 0; num < pIPAddrTable->dwNumEntries; num++) { if (ifIndex == pIPAddrTable->table[num].dwIndex) { ここ==> ipaddress = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr))); ここ==> netmask = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwMask))); bcastaddress = getBcastAddr(pIPAddrTable->table[num].dwAddr, pIPAddrTable->table[num].dwMask, pIPAddrTable->table[num].dwBCastAddr); AddrInfo info = AddrInfo(ipaddress, netmask, bcastaddress, pIPAddrTable->table[num].wType); result.push_back(info); } } } free(pIPAddrTable); return result; }
ただ"_bstr_t"クラスはMSのクラスで、Linuxへはこのまま移行できないので、この"_bstr_t"クラスをシミュレートする感じのクラスを作って見ました。
100%シミュレートするクラスを作るのは面倒だったので、自分の使ってるロジック中で利用される機能を中心に構築する事にしました。
クラスの名前は"FString"(ちょっとべた過ぎるかも・・・)にしています。
ソースプログラムは、
FString.h
#include <locale.h> #include <tchar.h> #include <string> using std::string; using std::wstring; #ifdef _UNICODE typedef std::wstring String; #else typedef std::string String; #endif // _UNICODE #define _LOCALE_JAPANESE _T("Japanese_Japan.932") class FString { public: FString(); FString(string str); FString(wstring wstr); FString(const char *str); FString(const wchar_t *wstr); ~FString(); FString& operator=(const char *from); FString& operator=(const wchar_t *from); FString& operator+=(FString& from); FString& operator+=(const char *from); FString& operator+=(const wchar_t *from); operator const char*(); operator char*(); operator string(); operator const wchar_t*(); operator wchar_t*(); operator wstring(); string getString(); wstring getWString(); const char *getCharBuff(); const wchar_t *getWCharBuff(); static void setLocale(String loc); static wstring strToWstr(string str); static string wstrToStr(wstring wstr); private: wstring wchar_string; string char_string; }; // class FString
それと、FStringのクラスボディは、
FString.cpp
#include "FString.h" static String own_loc = _LOCALE_JAPANESE; // Constructor FString::FString() { } // Constructor // param : 初期化文字列(string) FString::FString(string str) { wchar_string = strToWstr(str); char_string.clear(); } // Constructor // param : 初期化文字列(wstring) FString::FString(wstring wstr) { wchar_string = wstr; char_string.clear(); } // Constructor // param : 初期化文字列(char*) FString::FString(const char *str) { wchar_string = strToWstr(string(str)); char_string.clear(); } // Constructor // param : 初期化文字列(wchar_t*) FString::FString(const wchar_t *wstr) { wchar_string = wstring(wstr); char_string.clear(); } // Destructor FString::~FString() { wchar_string.clear(); char_string.clear(); } /* * "=(代入)"オペレータの処理 */ FString& FString::operator=(const char *from) { wchar_string = strToWstr(string(from)); char_string.clear(); return *this; } /* * "=(代入)"オペレータの処理 */ FString& FString::operator=(const wchar_t *from) { wchar_string = wstring(from); char_string.clear(); return *this; } /* * "+=(追加)"オペレータの処理 */ FString& FString::operator+=(FString& from) { wchar_string.append(from.getWString()); char_string.clear(); return *this; } /* * "+=(追加)"オペレータの処理 */ FString& FString::operator+=(const char *from) { wstring ap = strToWstr(string(from)); wchar_string.append(ap); char_string.clear(); return *this; } /* * "+=(追加)"オペレータの処理 */ FString& FString::operator+=(const wchar_t *from) { wchar_string.append(from); char_string.clear(); return *this; } /* * "const char*(リダイレクト)"オペレータの処理 */ FString::operator const char*() { return getCharBuff(); } /* * "char*(リダイレクト)"オペレータの処理 */ FString::operator char*() { return (char *)getCharBuff(); } /* * "string(リダイレクト)"オペレータの処理 */ FString::operator string() { getString(); return char_string; } /* * "const wchar_t*(リダイレクト)"オペレータの処理 */ FString::operator const wchar_t*() { return getWCharBuff(); } /* * "wchar_t*(リダイレクト)"オペレータの処理 */ FString::operator wchar_t*() { return (wchar_t *)getWCharBuff(); } /* * "wstring(リダイレクト)"オペレータの処理 */ FString::operator wstring() { return wchar_string; } /* * 登録文字列を取得する * * return : string */ string FString::getString() { if (char_string.empty() && !wchar_string.empty()) { char_string = wstrToStr(wchar_string); } return char_string; } /* * 登録文字列を取得する * * return : char* */ const char *FString::getCharBuff() { getString(); return char_string.c_str(); } /* * 登録文字列を取得する * * return : wstring */ wstring FString::getWString() { return wchar_string; } /* * 登録文字列を取得する * * return : wchar_t* */ const wchar_t *FString::getWCharBuff() { return wchar_string.c_str(); } /* * Localeを設定する * * param : Locale(String) */ void FString::setLocale(String loc) { own_loc = loc; } /* * stringをwstringに変換する * * param : オリジナル文字列(string) * * return : wstring */ wstring FString::strToWstr(string str) { wstring result; if (!str.empty()) { _TCHAR *org_loc = _tcsdup(_tsetlocale(LC_ALL, NULL)); _tsetlocale(LC_ALL, own_loc.c_str()); size_t from_len = str.length(); char *from_value = (char *)calloc(from_len+1, sizeof(char)); memcpy(from_value, str.c_str(), from_len); size_t to_len = mbstowcs(NULL, from_value, from_len); wchar_t *buffer = (wchar_t *)calloc(to_len+1, sizeof(wchar_t)); mbstowcs(buffer, str.c_str(), from_len); free(from_value); result = wstring(buffer); free(buffer); _tsetlocale(LC_ALL, org_loc); free(org_loc); } return result; } /* * wstringをstringに変換する * * param : オリジナル文字列(wstring) * * return : string */ string FString::wstrToStr(wstring wstr) { string result; if (!wstr.empty()) { _TCHAR *org_loc = _tcsdup(_tsetlocale(LC_ALL, NULL)); _tsetlocale(LC_ALL, own_loc.c_str()); size_t from_len = wstr.length(); wchar_t *from_value = (wchar_t *)calloc(from_len+1, sizeof(wchar_t)); memcpy(from_value, wstr.c_str(), from_len*sizeof(wchar_t)); size_t to_len = wcstombs(NULL, from_value, from_len); char *buffer = (char *)calloc(to_len+1, sizeof(char)); /* * 本当なら"wcstombs(buffer, from_value, from_len);"で事が足りるはずなんですが、 * なぜか、ロケールに"japanese"を指定して第一パラメータに値を指定すると途中まで * しか変換されないケースがあるので、あえて一文字づつ変換させています */ size_t pos = 0; for (size_t i = 0; i < from_len; i++) { int len = wctomb(&buffer[pos], from_value[i]); if (len < 0) break; pos += len; } free(from_value); result = string(buffer); free(buffer); _tsetlocale(LC_ALL, org_loc); free(org_loc); } return result; }
テストで、
int _tmain(int argc, _TCHAR* argv[]) { wcout.imbue(locale("japanese")); BString::setLocale(_LOCALE_JAPANESE); BString mbs = BString("漢字のデータ"); const wchar_t *mbs_buff = (const wchar_t*)mbs; wstring wcs_ref = mbs; mbs += L"−追加"; wstring wcs_val = mbs.getWString(); wcout << L"WString : " << wcs_val << endl; BString wcs = BString(wcs_val); string mbs_val = wcs.getString(); cout << "String : " << mbs_val << endl; return 0; }
のロジックが動作する事を確認しました。
最初に提示した以前のロジックとの変更点としては、
/* * 指定したインターフェースに設定されたアドレスを取得する * param : インターフェースの番号(int) * * return : address_list */ address_list AddrInfo::setAddressList(int ifIndex) { address_list result; String ipaddress; String netmask; String bcastaddress; long retCode = NO_ERROR; DWORD dwSize = 0; GetIpAddrTable(NULL, &dwSize, 0); PMIB_IPADDRTABLE pIPAddrTable = (MIB_IPADDRTABLE *)malloc(dwSize); memset(pIPAddrTable, 0, dwSize); if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == NO_ERROR) { for (unsigned int num = 0; num < pIPAddrTable->dwNumEntries; num++) { if (ifIndex == pIPAddrTable->table[num].dwIndex) { ここ==> ipaddress = String((_TCHAR *)FString(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr))); ここ==> netmask = String((_TCHAR *)FString(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwMask))); bcastaddress = getBcastAddr(pIPAddrTable->table[num].dwAddr, pIPAddrTable->table[num].dwMask, pIPAddrTable->table[num].dwBCastAddr); AddrInfo info = AddrInfo(ipaddress, netmask, bcastaddress, pIPAddrTable->table[num].wType); result.push_back(info); } } } free(pIPAddrTable); return result; }
になります。
テストしましたが、何とか動作しています。
余談で、FString.cppのロジック中にある、"string FString::wstrToStr(wstring wstr)"メソッドのロジック中にもコメントしたんですが、localeを"japanese"にすると、
size_t to_len = wcstombs(NULL, from_value, from_len);
のロジックは問題なく動作して、from_valueが"漢字のデータ"を設定した時のto_lenの値は12になるのですが、
char *buffer = (char *)calloc(to_len+1, sizeof(char)); size_t to_len = wcstombs(buffer, from_value, from_len);
のロジックでは、from_valueが"漢字のデータ"を設定した時のto_lenの値が6になり、bufferには"漢字の"までしか変換されませんでした。
色々と試してみたのですが、原因が特定できなかったので、
char *buffer = (char *)calloc(to_len+1, sizeof(char)); size_t pos = 0; for (size_t i = 0; i < from_len; i++) { int len = wctomb(&buffer[pos], from_value[i]); if (len < 0) break; pos += len; }
として回避しています。
LinuxでDiskの情報を取得する
久しぶりに日記の更新です。
前回Windows系の情報収集等のプログラミングをやって来ていたんですが、先週はLinux系の情報収集に翻弄されていました。
その第一弾はDisk情報の取得で、WindowsだとWMIを使って比較的簡単に取得できたんですが、Linuxの方は取得するのに苦労してしまいました。
次のプログラムは、/proc/partitions情報からLinuxが認識している区画情報を取得して、その区画情報を元にDisk情報の入っているsuper_blockを探し出して取得する手順を撮って取っています。
Diskの情報としては、ext2以外にext3、FAT、NTFSなどがありますが、今回はext2(JDB,ext3もext2として)の情報を取得することだけを念頭においてプログラミングしています。
#include <uuid/uuid.h> #include <linux/ext2_fs.h> #include <fcntl.h> #define _SUPERBLOCK_BUFF_SIZE 65536 char uuid_buf[64]; char line[1024]; char devid[128]; char devname[128]; unsigned char *io_buf = (unsigned char *)malloc(_SUPERBLOCK_BUFF_SIZE); int ma, mi; FILE *proc = fopen("/proc/partitions", "r"); unsigned long long sz; if (proc != 0) { while (fgets(line, sizeof(line), proc)) { if (sscanf(line, " %d %d %llu %127[^\n ]", &ma, &mi, &sz, devid) == 4) { if (sz <= 1) continue; if (!isdigit(devid[strlen(devid)-1])) continue; sprintf(devname, "/dev/%s", devid); int fd = open(devname, O_RDONLY | O_NONBLOCK); if (fd) { unsigned loc = lseek(fd, 0, SEEK_SET); if (loc >= 0) { read(fd, io_buf, _SUPERBLOCK_BUFF_SIZE); size_t off = 0; while (off+sizeof(ext2_super_block) < _SUPERBLOCK_BUFF_SIZE) { ext2_super_block *sb = (ext2_super_block*)&io_buf[off]; if (sb->s_magic == EXT2_SUPER_MAGIC) { memset(uuid_buf, 0, 64); uuid_unparse(sb->s_uuid, uuid_buf); printf("Device %s : LABEL=%s : %s", devname, sb->s_volume_name, uuid_buf); if (strncmp(devid, "md", 2) == 0) { printf(" : DriveRaidDisk.\n"); } else { printf(" : DriveLocalDisk.\n"); } break; } off += 1024; } } close(fd); } } } fclose(proc); }
変換するロケールをjapanese以外にも出来るかな
昨日作ったJavaの文字化けを修正するプログラムですが、一方的にJapaneseしか扱っていなかったので、新しいLocaleをパラメータで渡すように変更しました。
また、クラスの名前もJapaneseだったのをCharSetに、メソッド名もFixCharsetをchangeLocaleに変更してみました。
先ずはJavaのクラスの方で
CharSet.java
package charset; public class CharSet { public CharSet() { } public static native String changeLocale(String Original, String newLocale); }
changeLocaleメソッドの第2パラメータに新しいロケールを指定できるようにしました。
次にCharSetクラスに対応したJNIプログラムの作成で、
これもCharSet.cppに名前を変更して、
#include <jni.h> #include "charset_CharSet.h" #include <locale> using std::locale; JNIEXPORT jstring JNICALL Java_charset_CharSet_changeLocale(JNIEnv *env, jclass cls, jstring original, jstring new_loc) { /* * Java文字列を一旦MBCS文字列に変換する */ wchar_t *words = (wchar_t *)env->GetStringChars(original, NULL); size_t mbs_len = wcstombs(NULL, words, wcslen(words)); char *mbs_value = (char *)calloc(mbs_len+1, sizeof(char)); mbs_len = wcstombs(mbs_value, words, wcslen(words)); env->ReleaseStringChars(original, (const jchar *)words); /* * Localeを取得する */ words = (wchar_t *)env->GetStringChars(new_loc, NULL); size_t loc_len = wcstombs(NULL, words, wcslen(words)); char *loc_value = (char *)calloc(loc_len+1, sizeof(char)); loc_len = wcstombs(loc_value, words, wcslen(words)); env->ReleaseStringChars(new_loc, (const jchar *)words); /* * MBCS文字列をLocal=new_locで再度Unicode文字列に変換し、結果をJava文字列に設定する */ locale new_locale(loc_value); locale old_locale = locale::global(new_locale); size_t wcs_len = mbstowcs(NULL, (const char *)mbs_value, mbs_len); wchar_t *wcs_value = (wchar_t *)calloc(wcs_len, sizeof(wchar_t)); wcs_len = mbstowcs(wcs_value, (const char *)mbs_value, mbs_len); jstring wcs_string = env->NewString((const jchar *)((wchar_t *)wcs_value), (jsize)wcs_len); locale::global(old_locale); free(mbs_value); free(loc_value); return wcs_string; }
としました。
変更点としては単純に追加された第2パラメータで設定された情報をlocale new_localeに渡すように変更しました。
ただこのプログラム自体は、japanese以外のロケールは試していませんので、あしからず。
NetworkInterface.getDisplayName()で発生する文字化けをチェック
Javaのjava.net.NetworkInterfaceにある、getDisplayName()メソッドで文字化けするために、IPHLPAPIを使って新たにクラスを作るということを以前していましたが、何故文字化けするのか調査しようと思って、簡単なテストプログラムを作ってみました。
Javaのjava.net.NetworkInterfaceのロジックとしては、jdk1.6のソースコードからjdk6-src\j2se\src\windows\native\java\net\NetworkInterface.cを参考にして作っています。
テスト用のJavaプログラムとして、
NetworkInfo.java
package charset; import java.util.Enumeration; public class NetworkInfo { private int index = 0; private String description_jdk = null; private String description_cvt = null; private String description_loc = null; private String description_bstr = null; static { System.loadLibrary("CharSet"); init(); } public NetworkInfo() { } public int getIndex() { return index; } public String getDescriptionJDK() { return description_jdk; } public String getDescriptionCVT() { return description_cvt; } public String getDescriptionLOC() { return description_loc; } public String getDescriptionBSTR() { return description_bstr; } public static void main(String[] args) { try { Enumeration<java.net.NetworkInterface> ifs = java.net.NetworkInterface.getNetworkInterfaces(); while (ifs.hasMoreElements()) { java.net.NetworkInterface intf = ifs.nextElement(); System.out.println("Interface : "); System.out.println(" Name : "+intf.getName()); System.out.println(" Description : "+intf.getDisplayName()); } } catch (Exception e) {} NetworkInfo[] nis = NetworkInfo.getInterfaces(); for (int i = 0; i < nis.length; i++) { NetworkInfo ni = nis[i]; System.out.println("Nic : "+ni.getIndex()); System.out.println(" DescriptionJDK : "+ni.getDescriptionJDK()); System.out.println(" DescriptionCVT : "+ni.getDescriptionCVT()); System.out.println(" DescriptionLOC : "+ni.getDescriptionLOC()); System.out.println(" DescriptionBSTR : "+ni.getDescriptionBSTR()); } } private static native void init(); public static native NetworkInfo[] getInterfaces(); }
JNIを呼び出すことになるので、いつものようにコンパイルした後にjavahでヘッダファイルを作りました。
いつもだとIncludeや変数情報などはプログラムファイルと別にヘッダファイルを用意するんですが、今回はテスト用に単純なアプリケーションなので、ヘッダ情報等もまとめてC++のJNIプログラム1ファイルで作っちゃいました。
#include <windows.h> #include <tchar.h> #include <comdef.h> #include <iphlpapi.h> #pragma comment(lib, "iphlpapi.lib") #include <jni.h> #include "charset_NetworkInfo.h" #include <locale> using std::locale; jclass ni_class; /* NetworkInfo */ jmethodID ni_ctor; /* NetworkInfo() */ jfieldID ni_indexID; /* NetworkInfo.index */ jfieldID ni_description_jdkID; /* NetworkInfo.description_jdk */ jfieldID ni_description_cvtID; /* NetworkInfo.description_cvt */ jfieldID ni_description_locID; /* NetworkInfo.description_loc */ jfieldID ni_description_bstrID; /* NetworkInfo.description_bstr */ JNIEXPORT void JNICALL Java_charset_NetworkInfo_init(JNIEnv *env, jclass cls) { ni_class = (jclass)env->NewGlobalRef(cls); ni_indexID = env->GetFieldID(ni_class, "index", "I"); ni_description_jdkID = env->GetFieldID(ni_class, "description_jdk", "Ljava/lang/String;"); ni_description_cvtID = env->GetFieldID(ni_class, "description_cvt", "Ljava/lang/String;"); ni_description_locID = env->GetFieldID(ni_class, "description_loc", "Ljava/lang/String;"); ni_description_bstrID = env->GetFieldID(ni_class, "description_bstr", "Ljava/lang/String;"); ni_ctor = env->GetMethodID(ni_class, "<init>", "()V"); } JNIEXPORT jobjectArray JNICALL Java_charset_NetworkInfo_getInterfaces(JNIEnv *env, jclass cls) { jobjectArray ni_list = NULL; DWORD dwSize = 0; GetIfTable(NULL, &dwSize, true); PMIB_IFTABLE pIfTable = (MIB_IFTABLE *)calloc(dwSize, 1); if (pIfTable != NULL) { if (GetIfTable(pIfTable, &dwSize, true) == NO_ERROR) { jsize ni_count = (jsize)pIfTable->dwNumEntries; if (ni_count > 0) { ni_list = env->NewObjectArray(ni_count, ni_class, NULL); for (unsigned long i = 0; i < pIfTable->dwNumEntries; i++) { MIB_IFROW pInterface = pIfTable->table[i]; jobject netifObj = env->NewObject(ni_class, ni_ctor); env->SetIntField(netifObj, ni_indexID, pInterface.dwIndex); /* * XPでの表示をテストするために、 * jdk6-src\j2se\src\windows\native\java\net\NetworkInterface.c * の582行目付近にある以下のロジックを参照して、Unicodeモードでない方の処理を流用しました。 * * if (ifs->dNameIsUnicode) { * displayName = (*env)->NewString(env, (PWCHAR)ifs->displayName, wcslen ((PWCHAR)ifs->displayName)); * } else { * displayName = (*env)->NewStringUTF(env, ifs->displayName); * } */ jstring jdk_string = env->NewStringUTF((const char *)pInterface.bDescr); env->SetObjectField(netifObj, ni_description_jdkID, jdk_string); /* * JavaにUnicodeの文字列を設定するところで、MBCS文字列からUnicodeへの変換処理をcの関数 * mbstowcsを利用して変換してみました。 */ size_t wcs_len = mbstowcs(NULL, (const char *)pInterface.bDescr, strlen((const char *)pInterface.bDescr)); wchar_t *cvt_value = (wchar_t *)calloc(wcs_len, sizeof(wchar_t)); wcs_len = mbstowcs(cvt_value, (const char *)pInterface.bDescr, strlen((const char *)pInterface.bDescr)); jstring cvt_string = env->NewString((const jchar *)((wchar_t *)cvt_value), (jsize)wcs_len); env->SetObjectField(netifObj, ni_description_cvtID, cvt_string); /* * JavaにUnicodeの文字列を設定するところで、MBCS文字列からUnicodeへの変換処理をcの関数 * mbstowcsを利用して変換してみました。 * 変換時にプログラムのロケールを一時的に"japanese"にしてみました。 */ locale jpn("japanese"); locale old_locale = locale::global(jpn); wcs_len = mbstowcs(NULL, (const char *)pInterface.bDescr, strlen((const char *)pInterface.bDescr)); wchar_t *loc_value = (wchar_t *)calloc(wcs_len, sizeof(wchar_t)); wcs_len = mbstowcs(loc_value, (const char *)pInterface.bDescr, strlen((const char *)pInterface.bDescr)); jstring loc_string = env->NewString((const jchar *)((wchar_t *)loc_value), (jsize)wcs_len); locale::global(old_locale); env->SetObjectField(netifObj, ni_description_locID, loc_string); /* * 変換処理は_bstr_tに任せてみました。 */ _bstr_t bstr_value = _bstr_t((char *)pInterface.bDescr); jstring bstr_string = env->NewString((const jchar *)((wchar_t *)bstr_value), (jsize)bstr_value.length()); env->SetObjectField(netifObj, ni_description_bstrID, bstr_string); env->SetObjectArrayElement(ni_list, i, netifObj); } } } free(pIfTable); } return ni_list; }
Javaのプログラムを実行した結果(一部抜粋)は、
・・・ Interface : Name : eth1 Description : TAP-Win32 Adapter V9 - ?p?P?b?g ?X?P?W ・・・ Nic : 3 DescriptionJDK : TAP-Win32 Adapter V9 - ?p?P?b?g ?X?P?W DescriptionCVT : TAP-Win32 Adapter V9 - ?p?P?b?g ?X?P?W???[?? ?~?j?|?[?g DescriptionLOC : TAP-Win32 Adapter V9 - パケット スケジューラ ミニポート DescriptionBSTR : TAP-Win32 Adapter V9 - パケット スケジューラ ミニポート ・・・
となり、内部的な文字コードの変換における問題って感じですね。
内部コードのロケールの問題らしいので、かなり強引ですが文字コードを変換しちゃうメソッドを作ってしまいました。
クラス名はJapaneseとかなり即物的な名前にしました。・・・名前を考えるのが面倒だったので。
Japanese.java
package charset; public class Japanese { public Japanese() { } public static native String FixCharset(String Original); }
今までと同様に、これをコンパイルしてヘッダファイルを作ってから、
Japanese.cpp
#include <jni.h> #include "charset_Japanese.h" #include <locale> using std::locale; JNIEXPORT jstring JNICALL Java_charset_Japanese_FixCharset(JNIEnv *env, jclass cls, jstring original) { /* * Java文字列を一旦MBCS文字列に変換する */ wchar_t *words = (wchar_t *)env->GetStringChars(original, NULL); size_t mbs_len = wcstombs(NULL, words, wcslen(words)); char *mbs_value = (char *)calloc(mbs_len+1, sizeof(char)); mbs_len = wcstombs(mbs_value, words, wcslen(words)); env->ReleaseStringChars(original, (const jchar *)words); /* * MBCS文字列をLocal=japaneseで再度Unicode文字列に変換し、結果をJava文字列に設定する */ locale jpn("japanese"); locale old_locale = locale::global(jpn); size_t wcs_len = mbstowcs(NULL, (const char *)mbs_value, mbs_len); wchar_t *wcs_value = (wchar_t *)calloc(wcs_len, sizeof(wchar_t)); wcs_len = mbstowcs(wcs_value, (const char *)mbs_value, mbs_len); jstring wcs_string = env->NewString((const jchar *)((wchar_t *)wcs_value), (jsize)wcs_len); locale::global(old_locale); free(mbs_value); return wcs_string; }
を作ってテストしてみました。
テストプログラムを
NetworkInfo[] nis = NetworkInfo.getInterfaces(); for (int i = 0; i < nis.length; i++) { NetworkInfo ni = nis[i]; System.out.println("Nic : "+ni.getIndex()); System.out.println(" DescriptionJDK : "+ni.getDescriptionJDK()); System.out.println(" DescriptionCVT : "+ni.getDescriptionCVT()); System.out.println(" DescriptionLOC : "+ni.getDescriptionLOC()); System.out.println(" DescriptionBSTR : "+ni.getDescriptionBSTR()); System.out.println(" Converted : "+Japanese.FixCharset(ni.getDescriptionJDK())); }
に変更して実行した結果が、
Nic : 3 DescriptionJDK : TAP-Win32 Adapter V9 - ?p?P?b?g ?X?P?W DescriptionCVT : TAP-Win32 Adapter V9 - ?p?P?b?g ?X?P?W???[?? ?~?j?|?[?g DescriptionLOC : TAP-Win32 Adapter V9 - パケット スケジューラ ミニポート DescriptionBSTR : TAP-Win32 Adapter V9 - パケット スケジューラ ミニポート Converted : TAP-Win32 Adapter V9 - パケット スケジ
になりました。
"パケット スケジ"で切れているのは、Javaが保有している元々の文字列が"?p?P?b?g ?X?P?W"で切れている関係です。
env->NewStringUTF((const char *)pInterface.bDescr);
の内部処理で切れちゃったみたいですね。
あくまでも想像ですが、pInterface.bDescrの値として
offset 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0 54 41 50 2d 57 69 6e 33 32 20 41 64 61 70 74 65 TAP-Win32 Adapte 16 72 20 56 39 20 2d 20 83 70 83 50 83 62 83 67 20 r V9 - .p.P.b.g 32 83 58 83 50 83 57 83 85 81 5b 83 89 20 83 7e 83 .X.P.W...[.. .~. 48 6a 83 7c 81 5b 83 67 00 j.|.[.g.
が設定されているので、NewStringUTF関数の中の処理で、offsetが38byteから0x83,0x85と0x80以上の値が連続したために内部の変換処理でエラー等の不具合が発生したのかもしれません。
これに関しては、Javaのソースコードを追いかける気にならなかったので、あくまでも想像ベースですけど。