ネットエージェント株式会社 オフィシャルブログ

長谷川陽介

たまにはハードウェア工作を楽しもう!

 こんにちは。ネットエージェント株式会社、研究開発部の長谷川です。今日は少し低レイヤの話ということで簡単なハードウェア工作を楽しもうと思います。

-----

■ ディスプレイを回転させたい!
 デュアルディスプレイの便利さはみなさんご存じのとおりだと思いますが、PDFやWordなどでA4サイズの文書を表示させているときなどに「ディスプレイを縦向きに表示させたい!」と思うことはありませんか? 私は普段からそう感じており、一時期、サブで使用しているディスプレイを縦向きに使用(表示)して使っていたのですが、日常的な作業では、やはり縦よりも横のほうが向いていることもあり、なかなか思ったほど気持ちよく作業できる環境ではありませんでした。
 というわけで、もう少し気軽に縦横を変えられる仕組みを実現しようと思い、ディスプレイ回転ツールを自作しました。

■ 材料
 今回使用した材料は以下になります。
  1. 液晶ディスプレイ用アーム(ARM-41C)
  2. スイッチ(5Aマイクロスイッチ)
  3. USB-シリアル変換ケーブル(USB-シリアル変換ケーブルUC-SGT)
  4. シリアルケーブル
  5. みんな大好きフリスクケース
 まず必要なのは液晶ディスプレイ用のアームです。一般的な液晶ディスプレイ用アームは、視野に対する角度などは細かく調整できますが、ディスプレイそのものの縦横を回転できるものは少ないです。それでも、丹念に探すと液晶の縦横回転機構を備えたものもそれなりの価格で見つかります。今回調達したものは、ライブクリエータ社のARM-41Cというもので、ヨドバシカメラで7000円を切る価格で手に入りました。
 次にスイッチですが、これはディスプレイの縦横を検出するために使用します。どのようなものでも構いませんが、今回はおもちゃ売り場で売っていたタミヤの5Aマイクロスイッチを使用しました。
 また、スイッチのON/OFFをPCに取り込むために、シリアル通信(いわゆるRS-232C)を利用することにし、今時のPCだとCOMポートがついていないため、USB経由でCOMポートを利用可能にするために、エレコムのUSB-シリアル変換ケーブルUC-SGTを使いました。
 あとは、クロスでもストレートでも構いませんので、スイッチに接続するために使用するシリアルケーブル。そして、スイッチを固定する台座として、みんな大好きフリスクケース。それ以外にもネジや両面テープなど適宜用意しておくと便利です。

■ ハードウェア工作
 fig1まずは、フリスクケース2個を振り子になるようネジで固定し、回転したときにスイッチが押されるようにスイッチも固定します。振り子側は、きちんとスイッチを押さえられるように中にコインを重りとして入れています(左図)。
 fig2シリアルケーブルのDTR、DSRをスイッチにハンダ付けします。また、ケーブルの自重でスイッチやハンダ付け部分が痛まないようにケースにケーブルを固定しておきます(右図)。
 fig3スイッチ部分ができあがれば、液晶ディスプレイ背面に両面テープでスイッチ機能付きフリスクケースを取り付けます。しっかりとスイッチが押されるように少し斜めに取り付けるのがコツです。縦、横とディスプレイの向きを変えたときにきちんとスイッチがON/OFFされるか確認します(左図-横、右図-縦)。
 fig4これで、ハードウェアの準備は完了です。あまりお金をかけない簡単な工作で、ディスプレイの向きに応じてシリアルポートのDTR-DSRがON/OFFされる装置が出来上がりました。

■ ソフトウェアの準備
 ハードウェアを作成したので、次はソフトウェアを組みます。
 ディスプレイの向きはシリアルポートのDSR信号を監視することで把握でき、監視するコードは、概ね以下のようになります(監視コードはサービスとして動作させています)。

HANDLE hComm;
OVERLAPPED ov;
DWORD dwCommState;

/* 略 */

EscapeCommFunction( hComm, SETDTR ); // DTR信号をON
SetCommMask( hComm, EV_DSR ); // DSR信号の変化を監視
ResetEvent( ov.hEvent ); // 非同期I/Oのためイベントをリセット

WaitCommEvent( hComm, &dwCommState, &ov ); // DSRの監視スタート
WaitForSingleObject( ov.hEvent, 30000 ); // イベント発生まで待機

 ディスプレイ表示の向きを変えるには、以下のようなコードで実現できます(エラー処理等は省略)。

int iDevNum = 1; // Display No.2
DISPLAY_DEVICE d;
DEVMODE dm;
int w;

ZeroMemory( &d, sizeof( d ) );
d.cb = sizeof( d );

EnumDisplayDevices( NULL, iDevNum, &d, 0 ) );
EnumDisplaySettings( d.DeviceName, ENUM_CURRENT_SETTINGS, &dm ) );

w = dm.dmPelsHeight;
dm.dmPelsHeight = dm.dmPelsWidth;
dm.dmPelsWidth = w;
dm.dmDisplayOrientation = DMDO_90; // 90度回転

ChangeDisplaySettingsEx( d.DeviceName, &dm, NULL, CDS_UPDATEREGISTRY, NULL );

 全ソースコードはGitHub上に置いてあります。興味がある方は参照してください。
 サービスとしてシリアルポートを監視していると、ユーザのディスプレイ(デスクトップ)とは直接対話できないため、シリアルポートからの信号を受け取った時点でログオンしているユーザのトークンを用いてCreateProcessAsUserを使ってディスプレイ表示の向きを変えるためのプログラムを起動しています。

■ さいごに
fig5 このように、ディスプレイの向きを検出するスイッチと、それに応じて表示の向きを変えるプログラムを作成することによって、ディスプレイの向きを物理的に変えることで表示も連動して変わり、非常に快適になりました(左図:縦表示)。
 ソフトウェアエンジニアにとって、ハードウェア工作は若干敷居が高そうに思えるかもしれませんが、ソフトウェアと同じくらい面白く、また出来ることの幅、可能性も広がると思います。興味があればぜひとも挑戦してみてください。
 それにしてもフリスクケースはちょっとした工作にはとても便利ですね。Enjoy!

第1回 Win32 API 探検隊

 こんにちは。ネットエージェント株式会社、研究開発部の長谷川です。
 今回は、ちょっと軽めのネタということで、MSDN Library を眺めていて見つけた、少し変わった Windows API 関数を紹介したいと思います。

■ 使い道がありそうで見つからない PathIsExe
 Shell32.dll に含まれる PathIsExe 関数は、引数で指定したファイルが実行可能なファイルかどうかを、ファイル名の拡張子を見て判断します。例えば、"*.exe" や "*.cmd" といった拡張子であれば TRUE が返ります。
 Shell32 に含まれるということで、Explorer のようなシェルやシェル拡張などを書いている場合には(もしかすると)利用するかも知れませんが、一般のアプリケーションを開発する場合にはあまりお世話になる機会はなさそうです。
 同じ Shell32.dll には、PathIsSlow という面白い関数もあります。これは、指定したパスが遅延の大きなネットワークドライブなどであった場合に TRUE を返すようです。
 また、Shlwapi.dll にはこれらとよく似た名前で、PathIsURL という関数もあります。引数で指定された文字列がURLの書式に合致すれば TRUE を返すようですが、"http:" や "ftp:" だけでなく、"+-:" や "..:" などでも TRUE が返ってくるというおもしろ仕様なので、大抵の場合は自分で似たような関数を用意しなければなりません。

■ ちょっと便利な StrFormatByteSize
 Shlwapi.dll に含まれる StrFormatByteSize 関数一族は、数値を KB や MB といった単位付きのサイズ文字列に変換する関数です。
 例えば、23506 という数値を StrFormatByteSize64 に渡すと、"22.9KB" という、人間に読みやすい形に単位を変換して書式化した文字列を得られます。ファイルやメモリのサイズを表示する場合に、自分で書いてもたいしたコード量にはなりませんが、覚えておくとちょっとだけ便利です。
 さて、MSDN Library には、以下の5種類の関数の説明が掲載されています。
  1. StrFormatByteSize64
  2. StrFormatByteSizeA
  3. StrFormatByteSizeEx
  4. StrFormatByteSizeW
  5. StrFormatByteKBSize
 ぱっと見て気がつくのは、ANSI バージョンである StrFormatByteSizeA と Unicodeバージョンである StrFormatByteSizeW がそれぞれ別々に定義されている点です。実は、StrFormatByteSizeA と StrFormatByteSizeW は、単純に ANSI 版と Unicode 版という関係ではなく、ANSI 版が DWORD で 32 ビットまでのサイズにしか対応していないのに対して、Unicode 版は LONGLONG で 64 ビットのサイズに対応しています。また、StrFormatByteSize64 という関数は、ANSI 版である StrFormatByteSize64A しか定義されておらず、Unicode 環境では StrFormatByteSizeW が呼び出されます。
 使いそうであまり使わず、それでいて自分で作ってもたいしたコードでもないような関数が用意されていて、にも関わらずチグハグ感が満載というあたり、いかにも Windows らしいですね(編注:そんなWindowsが大好きなんです!)。

■ 末尾の Ex が 2 段重ねの関数
 Windows API で、あとから機能が拡張されたために名前の末尾に Ex のつく関数はたくさんありますが、EnumCalendarInfoExExEnumDateFormatsExExLogOnUserExExW は、末尾の Ex が 2 個重ねられた名前となっています。あと 3 年くらいしたら、ExExEx などになるのでしょうか。
 ちなみに、Win32 API で現存するもっとも長い関数名は、おそらく ConvertSecurityDescriptorToStringSecurityDescriptor あるいは ConvertStringSecurityDescriptorToSecurityDescriptor の 51 文字だそうです。IDE やエディタの入力支援がなければ、何度も打つのはうんざりしてしまいそうです。

■ APIとしては高機能すぎる SendARP
 Iphlpapi.dll に含まれる SendARP 関数は、その名前の通りコマンドラインの arp コマンドのように、ARP リクエストを送信します。なかなかアプリケーションから ARP を送ることは少ないと思いますし、ARP 自体が小さいとはいえ単一のプログラムとして成り立つくらいのものであり、それを API関数 として用意しているというのはちょっとビックリです。

■ 指定したアドレスのメモリの読み書き権限を調べる IsBadReadPtr / IsBadWritePtr
 Kernel32.dllに含まれる IsBadReadPtr 関数や IsBadWritePtr 関数は、引数として指定されたアドレスのメモリが読み書きできるかを調べる関数です(Vista以降ではサポートされていません)。サードパーティ DLL において渡されたパラメータが適切なものかどうかを判断するといった利用が想定されていたようですが、普通に考えると SEH を利用してコード全体を保護するほうが効率が良さそうです。

■ さいごに
 最後にちょっとしたTIPSを。MSDN Libraryは、表示の設定を "Classic"、"Lightweight"、"ScriptFree" から選択できますが、URLにおいて ( ) 内に "classic"、"lightweight"、"loband" などを指定することでも表示を切り替えられます。例えば http://msdn.microsoft.com/en-us/library/aa363858(VS.85,classic).aspx にアクセスすると classic 表示に、http://msdn.microsoft.com/en-us/library/aa363858(VS.85,loband).aspx にアクセスすると ScriptFree の表示に切り替わります。覚えておくとちょっと便利ですね。

ここまでできる! node.js に見る記号プログラミング


 こんにちは。ネットエージェント株式会社、研究開発部の長谷川です。先日、弊社の愛甲、サイバー大学の園田道夫氏とともに「非公式セキュリティキャンプ・キャラバン」と称して北海道情報セキュリティ勉強会(セキュポロ)に講師として参加してきました。例年であれば、セキュリティ&プログラミングキャンプの事業の一環として公式なキャラバンが開催されるのですが、今年は様々な事情で公式なキャラバンの開催が難しく講師としても心苦しく思っていたところ、ちょうどセキュポロのみなさんから勉強会の打診を頂きましたので、機会に乗じて非公式ながら「セキュリティキャンプ・キャラバン」という形を取らせて頂いたのでした。

-----

 さて、今日のテーマは流行りの node.js です。node.js は高効率な Web アプリケーションを、書き慣れた JavaScript で実現するための、サーバサイドで動く JavaScript 実装です。「最近、node.js も話題なので少しは触っておかないとな…」などと考えていたところに、愛甲から突然電話がかかってきました。
 電話の内容は「今、node.js で []()+! の6種類の記号だけの JavaScript を書こうとしてるんだけど、うまく動かないんだよね」というものでした。愛甲といえば、普段から「0と1しか興味ない」と公言しているほどのバイナリアンであり、そんな彼が0も1も使わない JavaScript を書いているということに少し衝撃を受けた私は、全く node.js を触ったことがないにも関わらず「いや、多分なんとかなると思うので、やってみます」と返事をしてしまい、node.js の世界に飛び込んだのでした。
 とりあえず、記号だけで JavaScript を動かす基本形として、まずは以下のようなコードを考えます。

Function( Function( "return\"console.log(1)\"" )() )()

 これは、無名関数内で console.log を使って「1」というメッセージを表示するコードですが、Number.constructor が Function であることを利用すると、以下のように書き変えられます。

(0).constructor.constructor( 
(0).constructor.constructor(
"return\"console.log(1)\""
)()
)()

 さらに、メソッドの呼び出しを連想配列形式に置き換えます。

(0)["constructor"]["constructor"](
(0)["constructor"]["constructor"](
"return'console.log(1)'"
)()
)()

 数字の 0 は、+[] で生成できます。また、「constructor」「return」という文字列は以下のように各文字を生成して + で連結します。

"c" : ([].filter + [])[3]         // "function filter() { 
// [native code] }"[3]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"n" : (undefined+[])[1] // "undefined"[1]
"s" : (false + [])[3] // "false"[3]
"t" : (true + [])[0] // "true"[0]
"r" : (true + [])[1] // "true"[1]
"u" : (undefined + [])[0] // "undefined"[0]
"c" : ([].filter + [])[3] // "function filter() {
// [native code] }"[3]
"t" : (true + [])[0] // "true"[0]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"r" : (true + [])[1] // "true"[1]

"r" : (true + [])[1]              // "true"[1]
"e" : (true + [])[3] // "true"[3]
"t" : (true + [])[0] // "true"[0]
"u" : (undefined + [])[0] // "undefined"[0]
"r" : (true + [])[1] // "true"[1]
"n" : (undefined+[])[1] // "undefined"[1]

 「filter」という文字列やtrue、false、undefinedといった値、数値なども、同様な方法で生成します。

"f" : (false + [])[0]             // "false"[0]
"i" : (false + undefined)[10] // "falseundefined"[10]
"l" : (false + [])[2] // "false"[2]
"t" : (true + [])[0] // "true"[0]
"e" : (true + [])[3] // "true"[3]
"r" : (true + [])[1] // "true"[1]

true      : !![]
false : ![]
undefined : [][[]]
0 : +[]
1 : +!+[]
2 : !+[]+!+[]
3 : !+[]+!+[]+!+[]
10 : +!+[]+[+[]]

 「constructor」「return」の各文字列や数値が []()!+ の記号だけで生成できましたので、あとは「'console.log(1)'」という、実行するコード部分を記号で生成すれば任意のコードを記号だけで書けそうです。
 JavaScript では、文字列定数は "\101\102\103" のように ASCII コードを \ に続けて8進数で記述することで、US-ASCII の範囲内の文字であれば表現できます。これを利用すると、「'console.log(1)'」という部分は「'\143\157\156\163\157\154\145\56\154\157\147\050\061\051'」になります。任意の数字は []()!+ の6種の記号から自由に生成できますので、あとは「\」と「'」の2文字を生成すればいいことになりますが、さてどうしよう…、というところで実は行き詰ったわけですが、node.js をいろいろ触っていると、console.dir が利用できることに気が付きました。console.dir( console.dir + []) を実行してみると、次のような文字列が返ってきます。

function (object) {
var util = require('util');
process.stdout.write(util.inspect(object) + '\n');
}

 うまい具合に、(consile.dir+[])[97] と (console.dir+[])[41] に「\」と「'」が存在していました。そして、「console」「dir」は次のように記号だけで書けます。

"c" : ([].filter + [])[3]         // "function filter() { 
// [native code] }"[3]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"n" : (undefined+[])[1] // "undefined"[1]
"s" : (false + [])[3] // "false"[3]
"o" : (true + [].filter + [])[10] // "truefunction filter() {
// [native code] }"[10]
"l" : (false + [])[2] // "false"[2]
"e" : (true + [])[3] // "true"[3]

"d" : (undefined+[])[2]           // "undefined"[2]
"i" : (false + undefined)[10] // "falseundefined"[10]
"r" : (true + [])[1] // "true"[1]

 これで、任意のコードを []()!+ の6種類だけで書けることがわかりましたので、実際に「console.log(1)」を記号だけで書いてみると、こんなふうになります。たった1行が4万文字を超えるスクリプトになってしまいました(笑)。
 この方法で、node.js らしくhttpサーバも記号だけに変換してみようと思ったのですが、require や module などが Function() 内ではうまく動かず結局断念しました。require のみグローバルなコンテキストから引数で渡してやることで、ほとんどの部分を記号だけで書いた JavaScript による http サーバはこちらになります(ブラウザによっては表示に時間がかかりますのでご注意ください)。require なども記号だけで実現する、何かよい方法があればこっそり教えてください。
 ということで、記号だらけですっかり混乱してしまった頭をすっきりさせるために、今日も飲みに行くのでした。

難読化JavaScriptで利用可能なテクニック

 こんにちは。ネットエージェント株式会社、研究開発部の長谷川です。本年もよろしくお願い致します。今回は、昨年11月に行いました NetAgent Security Contest 2010 の Level7 JavaScript で使用した JavaScript の難読化に関連するトピックスについて書かせていただこうと思います。

-----

NASecConPic15 今回の問題は、難読化されたJavaScriptコード中に埋め込まれた未使用変数に代入される文字列が解答となる、というものでした。
 JavaScriptの難読化手法については、実際の攻撃コードに使用されるものから、非実用的な研究目的のものまで様々な方法があります。今回の問題を作成するにあたっては、公知の難読化手法だけでは面白みに欠ける一方、特定のブラウザを使用していなければ解読さえできない、というほど凝ったものにならないよう、バランスに苦慮しながら出題しました(編注:実際、Level7 については難易度調整のため、2度の作り直しが行われました)。
 難読化は、32種類の記号のみで構成される JavaScript を生成する jjencode と呼ばれるエンコーダと、さらにそれを改良した JSF*ck とを組み合わせて、難解な記号だけの JavaScript コードを HTML 内に埋め込みました。
 JSF*ck は、任意のJavaScriptを [ ] ( ) ! + の6種類の記号だけに変換するエンコーダであり、今回の NetAgent Security Contenst 2010 に合わせて開発しました。例えば、alert(1) という JavaScript コードを、 [ ] ( ) + ! の6文字だけに変換すると以下になります。今回は、このコードの動作原理についての説明は割愛しますが、興味のある方はコードを追いかけてみるのも面白いかと思います。

<script>
(+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]
]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+
!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])
[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]
)[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+
[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])
[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!
+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![
]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+
[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[]
)[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+
!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!
+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]
+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+
[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[
])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][
[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+
[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!+[]+[]
[(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[
])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+(!![
]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[!+[]+!+[]+
!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][([][(![]+[])[+[]]+([![]]+[][
[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+
[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+
([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[
])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+
(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+
[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+
(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+
[]+!+[]]+(!![]+[])[+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+
[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[
]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!
+[]+!+[]]]+[+!+[]]+([][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(
![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[
+!+[]]]+[])[!+[]+!+[]+!+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[
]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+
(!+[]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+
!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[
]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!+[]+[])[+[]]+(!+[
]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[
+[]]+(!+[]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+
!+[]]+(!+[]+[])[+[]]+(!+[]+[])[!+[]+!+[]+!+[]]+(!+[]+[])[+!+[]]])[+!+[
]+[+[]]]+(!![]+[])[+!+[]]]+[])[[+!+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]]])()
</script>

 さて、前述のように記号だけの JavaScript は比較的簡単に書けますが、せっかくの機会なので、それ以外にもマイナーなテクニックを使っておきたいということで、文字列を JavaScript として実行するために、通常なら eval 関数を使うところを、Firefox 独自実装の crypto.generateCRMFRequest メソッドを利用しました。

<script>
crypto.generateCRMFRequest(
'CN=0',0,0,null,'alert("Hello, World")',384,null,
'rsa-dual-use')
</script>

 このように、crypto オブジェクトを経由することで eval のように文字列として与えた引数を JavaScript のコードとして実行できます。これは、Firefox 独自の機能ですが、Firefox 以外のブラウザにおいても引数部分の文字列を解析すれば解答を導き出せるということで、今回の NetAgent Security Contenst 2010 において使用することにしました。
 このように、JavaScript において「文字列を JavaScript として実行する」という機能は、実は様々な方法があり、よく知られている代表的なものだけでも以下のようなものがあります。

var js = "alert(1)";
eval(js); // もっともオーソドックスな方法
window[ "eval" ](js); // windowオブジェクトのevalメソッド
// evalを文字列として表記
Function(js)(); // 文字列から無名関数を生成して実行
alert.constructor("alert(1)")(); // alert.constructor==Functionを利用
Number.constructor("alert(1)")(); // Number.constructor==Functionを利用
setTimeout(js);
setInterval(js);

 これら以外にも、DOM を経由して <script> 要素などを追加する方法や、location に "javascript:" スキームを代入する方法もあります。さらに、Internet Explorer では JavaScript 内の文字列を別の言語(VBScript等)として実行する機能も備わっています。execScript の第2引数、setTimeout、setIntervalの第3引数は実行する言語です。

var vbs = "MsgBox(1)"
execScript(vbs, "vbscript");
setTimeout(vbs, 0, "vbscript");
setInterval(vbs, 0, "vbscript");

 さらにさらに、JavaScript、VBScript 以外にも、それらをMicrosoft製の難読化エンコーダである Windows Script Encoder でエンコードした JScript.Ecnode や VBScript.Encode も指定可能です。

// 難読化されたJScript "alert(1)"
var js = "#@~^CAAAAA==C^+.D`8#mgIAAA==^#~@";
// 難読化されたVBScript "MsgBox(1)"
var vbs = "#@~^CQAAAA==\\ko$K6vF#0gIAAA==^#~@";
javascript:execScript(js, "jscript.encode");
javascript:execScript(vbs, "vbscript.encode");
javascript:setTimeout(js, 0, "jscript.encode");
javascript:setInterval(vbs, 0, "vbscript.encode");

 Windows Script Encoder による難読化は、デコーダの実装も複数存在するため、元のコードを復元することもできますが、JavaScript 中に VBScript のような他の言語が混在することや、それを呼び出す JavaScript 自体を他の手法で難読化可能なことなどを踏まえると、実際の解読には相応の手間がかかります。
 ただ、このような凝った難読化手法というのはブラウザに依存してしまうため、一般的なソフトウェア開発において簡単に利用するのは難しいですが、研究目的、あるいは今回の NetAgent Security Contest 2010 での出題のようなパズル的な楽しみとしての使用であれば、まだまだ開拓の余地がある分野です。みなさんも、おもしろい JavaScript の書き方を見つけた場合にはぜひ教えて頂けると幸いです。

IE8+jQueryによるクロスドメイン通信とXDomainRequestラッパーの作成

 こんにちは、ネットエージェント株式会社、研究開発部の長谷川です。
 さっそくですが、みなさんは「Advent Calendar」をご存じでしょうか? Advent Calendar と言えば、一般的には、クリスマス(12月25日)までの残り日数をカウントダウンするカレンダーを思い浮かべるかもしれませんが、ここで紹介する Advent Calendar とは、様々な業界、技術方面で活躍されているプログラマ有志が、毎日交代で1つずつ技術的なトピックスを紹介する技術系Webイベントのことです。12月1日より運用が開始され、入門者向けから実用的なもの、さらにはマニアックな研究的トピックまで、幅広い記事が日々更新されています。
 今回は JavaScript Advent Calendar の10日目の記事として、当ブログで「Internet Explorer 8 と jQuery を組み合わせた場合のクロスドメイン通信の方法」について解説したいと思います。

-----

 まずは、XMLHttpRequest によるクロスドメイン通信を解説します。現在、Mozilla Firefox や Google Chrome、Apple Safari では XMLHttpRequest Level 2 への対応が進んでおり、以下のコードを書くことでクロスドメインでの通信が可能です。

if( window.XMLHttpRequest ){
var xhr = new XMLHttpRequest();
xhr.open( "get", "http://remote.example.com/data.txt", true );
xhr.onreadystatechange = function(){
if( xhr.readyState == 4 && xhr.status == 200 ){
alert( xhr.responseText );
}
}
xhr.send( null );
}

 このように、リクエストを発行するJavaScript側は、これまで自ドメインを対象に通信していたのと全く同じコードを使用し、他ドメインへ接続できます。この際、他ドメイン側(サーバ側)では、Access-Control-Allow-Origin レスポンスヘッダによって、リモートからのアクセスの許可 or 拒否を決定します。

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: text/plain; charset=utf-8

[テキストデータ]

 上記の例では「全てのアクセス元からの接続を許可」を意味する * を指定していますが、アクセス元に応じて、より細かく許可 or 拒否を設定できます。
 次の例では、example.jp から発行された XMLHttpRequest に対してのみ、リソースへのアクセスを許可しています。

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://example.jp

 一方、IE8 および IE9 beta では、クロスドメインでの Ajax を実現する機能として、XMLHttpRequest Level 2 ではなく XDomainRequest という機能を実装しています。いまのところ、IE9正式版で XMLHttpRequest Level 2 をサポートするという話も聞こえてきていません。
 XDomainRequest は、サーバ側の Access-Control-Allow-Origin レスポンスヘッダに応じてリモートのリソースへのアクセスを制御するという点は XMLHttpRequest と同様ですが、呼び出し方法やプロパティなどは XMLHttpRequest とは互換性がありません。

if( window.XDomainRequest ){
var xdr = new XDomainRequest();
xdr.onerror = function(){
alert( "error" );
}
xdr.onload = function(){
alert( xdr.contentType + "\n" + xdr.responseText );
}
xdr.open( "get", "http://remote.example.com/data.txt" );
xdr.send( null );
}

 このように、IE8 以降では XMLHttpRequest とは非互換な形でクロスドメイン通信を実現する方法が実装されているため、XMLHttpRequest を使用したライブラリではクロスドメイン通信が行えません。

 次に jQuery でのクロスドメイン通信を見ていきましょう。JavaScriptのライブラリとしてもっとも普及している jQuery でも、ajax メソッドにおいて dataType に "json" や "text" 等を指定した場合には XMLHttpRequest が使用されますので、前述の通り Firefox や Chrome、Safari ではクロスドメインでの通信が可能ですが、IE8、IE9 では残念ながらできません。
 そこで、jQuery.ajax を使用してクロスドメイン通信を可能にするため、IEの XDomainRequest を XMLHttpRequest のように見せかけるラッパーオブジェクトを用意してやることにしましょう。
 実際に書くコードは以下の通りです。エラー処理やリクエスト先が同一ドメインかどうかの判定などは省略しています。

function hookXhr()
{
var xhr = function(){
this.readyState = 0; // uninitialized
this.responseText = "";
this.status = "";
this.onreadstatechange = undefined;
var xdr = new XDomainRequest();

xdr.onprogress = function(){
var f;
this.xhr.readyState = 2; // loaded
if( this.xhr && ( f = this.xhr.onreadystatechange ) ){
f.apply( this.xhr );
}
};

xdr.onload = function(){
var f;
this.xhr.readyState = 3; // interactive
if( this.xhr && ( f = this.xhr.onreadystatechange ) ){
f.apply( this.xhr );
}
this.xhr.responseText = xdr.responseText;
this.xhr.readyState = 4; // complete
this.xhr.status = "200";
if( f ){
f.apply( this.xhr );
}
};

this.open = function( method, url, async ){
return xdr.open( method, url, async );
readyState = 1;
}
this.send = function( body ){
xdr.send( body );
};
this.setRequestHeader = function( headerName, headerValue ){
};
this.getResponseHeader = function( headerName ){
if( headerName.match( /^Content\-Type$/i ) ){
return xdr.contentType;
}else{
return "";
}
};
xdr.xhr = this;
return this;
};
return new xhr();
}

 このような、XMLHttpRequestをラッピングする関数を用意しておき、jQuery.ajax の呼び出しにおいて、こちらを使用するようパラメータ xhr を与えます。

$.ajax( {
url : "http://remote.example.com/data.txt",
dataType: "text",
xhr : window.XDomainRequest ? hookXhr : undefined,
// XMLHttpRequest の生成に、hookXhr が呼び出される。
success : function( txt, status, xhr ){
alert( txt );
}
} );

 通常の ajax の使用では、あまり xhr パラメータは使用しませんが、この例のように XMLHttpRequest オブジェクトを生成する関数を指定することで、本来の XMLHttpRequest ではなく、XDomainRequest を使用したラッパーオブジェクトを使用できます。
 ちなみに、話は少しそれますが、IE9 beta および IE9 platform preview 7 においては XDomainRequest を使用した場合に responseText プロパティの末尾にゴミが付加されてしまうバグがあります。どうやら、コンテンツが utf-8 等で書かれている場合に、responseText の「文字数」としてコンテンツの「バイト数」分だけ領域を確保してしまうようで、そのため、実際の文字数より大きなメモリ領域になり、末尾にゴミがついた状態となるようです。もちろんレポートしておきましたので、IE9正式リリース版では修正されると思いますが…。

 では最後に、このラッパーを使用して、JavaScript によって JavaScript Advent Calendar およびPerlコミュニティ(JPerl)の Advent Calendar の記事タイトルを一覧表示させるサンプルページを紹介します(http://utf-8.jp/public/js2010.html)。このページでは、サーバ側の応答に Access-Control-Allow-Origin を付与させるため、Hideki Yamamura 氏 の作成された allow-any-origin.appspot.com というサイトをプロキシのように使用しています。

-----

 このように、JavaScript の進歩に伴い、これまで以上にブラウザ上だけで実現できるアプリケーションの幅が広がってきました。できることならば、ブラウザの独自実装が減り、この記事のようなバッドノウハウを考えなくても、効率的にアプリケーションを書くことができればいいですね。
記事検索
月別アーカイブ
QRコード
QRコード