下級生2の検証(3)
<Top Page>
<専用掲示板>

§「OS破壊バグ」(?)の解析

 ぶっちゃけ、サポートに連絡してノンプロ版バイナリ貰えば万事解決です、ちゃんちゃん♪((c)百鬼君)
 ……てなことで実はFAなんだが、それでは目的が達成できていないので解析に入る。

 Fadpu16E.sysがロックされるのはゲーム起動からWindows終了までの間。起動時に読み込まれるドライバではない以上、このドライバそのものが起動阻害を起こすわけではない、と言うことだ。
 「OS破壊」が仮に起きたとしても、それはブルースクリーンが起きたためにシステムファイルかハードウェアが損傷を受け、それがOSの起動不能、という事態を招くに過ぎない。
 つまり、直接の原因ではない以上、「OS破壊バグ」という呼称は的外れである、と言える
 blogの記述だけを根拠に、バグだバグだ、と騒ぐ連中、自分で少しでも挙動を確かめた事があるのか?
 SecuROMの仕様上(理由は後述する)、危険度が大きいのはディジーチェーンが可能なI/F、つまりSCSI、IEEE1394といったシステムだ。普通にIDEとATAPIだけで組んでいるシステムの場合にはあまり問題にならない普通のユーザーにはほぼ無縁の話と言って良いくらいだ。
 但し、ドライブ監視ソフトやパケットライトソフト、回転制御ツール等のユーティリティとの相性が起きうることは否定しない。(やはり理由は後述する)

 が、ブルースクリーンが起き、その際にシステムファイルやハードウェアに損傷を与える事は充分ありうることだ。例えば、重要なファイルにアクセス中にシステムがストールする場合だ。
 「ブルースクリーンの事例であってOS破壊の事例ではない、だから捏造だ」と言い張る連中、お前らバカか、と言いたい。
 ブルースクリーンとOSの破壊はイコールではないが、別物でもない単にタイミングと確率の問題でしかないのだ。ブルースクリーンの原因がSecuROMにあり、そのブルースクリーンがOSの破壊をもたらしたならば、それは副次的ではあるにせよ、SecuROMが引き起こしたものであることに違いはないのだ。
 確かに「OS破壊『バグ』」の実証ではないが、ンな事言ってるようでは、言葉遊びに興じているガキと違いはない。いや、スレを無駄に消費しないだけ、ガキの方がマシだ。
 ブルースクリーンが起きる、と言う事の危険度は、いきなり電源が落ちる、と言う事と等価であるという事実から目を背けてはならない。
 わかっていなくて言っているなら只の馬鹿。解っていて言っているならタチの悪い荒らしだろう。
 大抵のPCの入門書に、「電源の切り方」がわざわざ書いてある意味をもう一度考えてみることだ。

 従って、下級生2が「OS破壊」について無実である事を証明するには、SecuROMがブルースクリーンを引き起こす訳では無い事を証明しなければならない
 本来ならFadpu16E.sysの逆アセリストを全て掲載すれば足りるのだが、ンな事をしたら俺がSonyから著作権法違反で訴えられることになる。
 だから要点のみの掲載に留めさせてもらう。なお、これから後の説明を理解するためにはWindowsDDKの知識が必要となる。MSDNに解説はあるが、日本語訳はされていない。
 ひょっとしたら俺自身、誤訳をしたことで誤った認識をしている部分が無いとも限らないが、その場合は掲示板かメールでのご指摘をお願いする。
 なお、説明が理解できない連中は「捏造」だの「バグ」だの騒がずに大人しくしていろ。指摘、反論は具体的に、理由を添えて行わないと意味が無い。

 まずはインポートされているAPIの部分。

+++++++++++++++++++ IMPORTED FUNCTIONS ++++++++++++++++++
Number of Imported Modules =    2 (decimal)

   Import Module 001: ntoskrnl.exe
   Import Module 002: HAL.dll

+++++++++++++++++++ IMPORT MODULE DETAILS +++++++++++++++

   Import Module 001: ntoskrnl.exe

 Addr:0C40C7FC hint(02BA) Name: ObfDereferenceObject
 Addr:00000000 hint(013D) Name: IoGetDeviceObjectPointer
 Addr:C7FC4D8B hint(0364) Name: RtlInitUnicodeString
 Addr:00001041 hint(0418) Name: ZwClose
 Addr:558B0000 hint(0454) Name: ZwReadFile
 Addr:1C428BFC hint(0448) Name: ZwQueryInformationFile
 Addr:83FC4589 hint(041D) Name: ZwCreateFile
 Addr:7500FC7D hint(0163) Name: IoRegisterDriverReinitialization
 Addr:5DE58BDD hint(0195) Name: IofCompleteRequest
 Addr:EC8B55C3 hint(0168) Name: IoRegisterShutdownNotification
 Addr:08458B51 hint(011B) Name: IoCreateDevice
 Addr:FE78E850 hint(0125) Name: IoDeleteDevice
 Addr:4589FFFF hint(0127) Name: IoDeleteSymbolicLink
 Addr:FC7D83FC hint(0212) Name: KeWaitForSingleObject
 Addr:8B307400 hint(0194) Name: IofCallDriver
 Addr:7983FC4D hint(010D) Name: IoBuildSynchronousFsdRequest
 Addr:27740110 hint(01C2) Name: KeInitializeEvent
 Addr:8BFC558B hint(0361) Name: RtlInitAnsiString
 Addr:45030C42 hint(0452) Name: ZwQueryValueKey
 Addr:FC4D8B0C hint(0439) Name: ZwOpenKey
 Addr:8B0C4189 hint(02FE) Name: RtlAppendUnicodeToString
 Addr:458BFC55 hint(046E) Name: ZwWriteFile
 Addr:0C4A8BFC hint(01DB) Name: KeQuerySystemTime
 Addr:7204483B hint(0134) Name: IoFreeWorkItem
 Addr:FC558B0A hint(015C) Name: IoQueueWorkItem
 Addr:011042C7 hint(0105) Name: IoAllocateWorkItem
 Addr:8B000000 hint(024F) Name: MmMapLockedPagesSpecifyCache
 Addr:E58BFC45 hint(030A) Name: RtlCompareMemory
 Addr:0008C25D hint(03BD) Name: RtlUnicodeStringToAnsiString
 Addr:83EC8B55 hint(02FA) Name: RtlAnsiStringToUnicodeString
 Addr:458B08EC hint(03DA) Name: RtlxAnsiStringToUnicodeSize
 Addr:F3E85008 hint(0270) Name: NlsMbCodePageTag
 Addr:89FFFFFD hint(0317) Name: RtlCopyUnicodeString
 Addr:45C7F845 hint(0337) Name: RtlEqualUnicodeString
 Addr:000000FC hint(02FD) Name: RtlAppendUnicodeStringToString
 Addr:F87D8300 hint(03BF) Name: RtlUnicodeStringToInteger
 Addr:8B127400 hint(036E) Name: RtlIntegerToUnicodeString
 Addr:7983F84D hint(0450) Name: ZwQuerySymbolicLinkObject
 Addr:09750110 hint(043D) Name: ZwOpenSymbolicLinkObject
 Addr:8BF8558B hint(0144) Name: IoGetRelatedDeviceObject
 Addr:45890842 hint(02B5) Name: ObReferenceObjectByHandle
 Addr:FC458BFC hint(017C) Name: IoSetThreadHardErrorMode
 Addr:C25DE58B hint(02D4) Name: PsGetCurrentProcessId
 Addr:8B550004 hint(0451) Name: ZwQuerySystemInformation
 Addr:08EC83EC hint(00FA) Name: InterlockedIncrement
 Addr:5008458B hint(0132) Name: IoFreeIrp
 Addr:FFFDB9E8 hint(01F9) Name: KeSetEvent
 Addr:FC4589FF hint(01B2) Name: KeGetCurrentThread
 Addr:00FC7D83 hint(0103) Name: IoAllocateIrp
 Addr:4D8B5074 hint(01C8) Name: KeInitializeSpinLock
 Addr:187983FC hint(0133) Name: IoFreeMdl
 Addr:8B117400 hint(0265) Name: MmUnlockPages
 Addr:428BFC55 hint(024E) Name: MmMapLockedPages
 Addr:FC4D8B18 hint(0257) Name: MmProbeAndLockPages
 Addr:891C518B hint(0104) Name: IoAllocateMdl
 Addr:0CEB1C50 hint(047A) Name: _except_handler3
 Addr:8BFC458B hint(0267) Name: MmUnmapLockedPages
 Addr:0D891C48 hint(034C) Name: RtlFreeAnsiString
 Addr:00019870 hint(049B) Name: memcpy
 Addr:83FC558B hint(0047) Name: ExFreePool
 Addr:74001C7A hint(003A) Name: ExAllocatePoolWithTag
 Addr:FC458B0F hint(010B) Name: IoBuildDeviceIoControlRequest
 Addr:8B1C488B hint(049D) Name: memset

   Import Module 002: HAL.dll

 Addr:0004C25D hint(0052) Name: KfReleaseSpinLock
 Addr:51EC8B55 hint(0046) Name: KeQueryPerformanceCounter
 Addr:FFFE2CE8 hint(0044) Name: KeGetCurrentIrql
 Addr:FC4589FF hint(004C) Name: KeStallExecutionProcessor
 Addr:00FC7D83 hint(004F) Name: KfAcquireSpinLock

+++++++++++++++++++ EXPORTED FUNCTIONS ++++++++++++++++++
Number of Exported Functions = 0000 (decimal)

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 

 ntoskrnl.exeとHAL.dllをインポートしているが、user32.dllとKERNEL32.DLLはインポートしていない。また、外部にエクスポートしているAPIもない。
 つまり、このファイルは見かけだけではなく、完全にカーネルレベルのデバイスドライバとしての属性を持っている事になる。
 また、インポートされたAPIを見てみると、ZwXxxルーチンを使ってのファイルアクセスすら行っていることがわかる。ZwXxxルーチンとは、カーネルモードでファイル操作を行うためのAPIであり、その呼び出しパラメータが正しいかどうかの検証は一切行われない。
 この他、デバイスオブジェクトの作成やIRQLの操作、CPUの一時停止などを行っている。

 ところで、SecuROMの動作原理をご存知だろうか?通常と記録密度を変えたセクタを用意し、読み出し時間の差を検出する事でチェックを行うわけだ。実はこの原理はSonyだけのものではない。
 FD時代にも、「ウェーブフォーマット」の名前で同様の物が存在していたし、StarForceも原理自体は同一だ。
 まあそれは余談だが、チェックのためにはCD/DVDドライブの回転を制御して一定に保ち(回転数が可変なら「該当セクタの読み出しに要する時間の差」を検出できない)、さらに計測の間はCPUを独占に近い状態に保たねばならない。これはATAPIならば実現はほぼ容易だ。1デバイスごとに1つの割り込みを使う、つまりデバイスごとに制御が独立しているからだ。
 しかし、SCSIやIEEE1394はそうはいかない。これらのインターフェースはディジーチェーンをサポートしており、従ってコマンドキューイングもサポートしている。OS側から見れば、ホストアダプタを制御することでデバイスを間接的に制御できるに過ぎない。このため、プロテクトがデバイスの独占をすることが難しくなっている。

 この仕様ゆえ、これらのインターフェースは高速かつ低負荷なのだが、逆に意外とも言える脆弱性をも包含する事になった。「バスロック」の問題である。何らかの理由によりあるデバイスが制御を失い、さらにフェイルセーフが上手く行かなかった場合、そのデバイスが存在するライン全てのデバイスを道連れにしてしまう。有名なのがRing Protechの誤爆症状だ。例えば、SCSIのSEライン上にあるCDドライブが誤爆により制御を失ったとする。この場合、そのSEライン上に存在する全ての機器が、デバイスマネージャより認識されなくなってしまう事がしばしば起こる。運が悪ければ、そのホストアダプタに接続されている全ての機器の制御が失われる。
 そしてSecuROMは、その原理上、デバイスへのアクセスを独占しようとする。先に「SCSIやIEEE1394では不具合の出る危険性が大きい」と述べた理由がこれだ。バスロックが起きてしまえば、ホストアダプタ自体が活線抜挿をサポートしていない限り、OSにとって本来ありえない、「デバイスの消失」が起こる。これによって内部に不整合を抱えたOSは、これまた運が悪ければブルースクリーンを出して落ちてしまう。ブルースクリーンが起きたならばOSそのものやハードウェアに損傷を与える可能性があることは既に述べた。
 これに対し、現在の主流であるATAPIの場合、デバイスの独占が容易であるために問題自体が起こりにくい上、仮に起こったとしても他のデバイスを道連れにする事はまずない。現実に使用されているインターフェースのほとんどがATAPIである以上、誤爆やブルースクリーンの問題が仮にあったとしても、被害を受ける人間は少数に留まる事になる。

 話を戻そう。SecuROMの原理と、APIの挙動を併せて考えると、このプロテクトドライバは、「CD/DVDドライブの制御を乗っ取る」という挙動を示している事になる。そして、このドライバは下級生2の終了後も解放されない、と言う事は既に実証した。そして、某blogの記述からもわかると思うが、このドライバはデバイスの独占を試みる割に、ドライブのロックを行っていない。イジェクトボタンを押せばトレイは開くし、別のアプリケーションからドライブにアクセスする事も可能である。
 つまり、ドライバが予期していないドライブへのアクセスが発生したり、逆にドライバが想定したアクセスが失敗したり、と言う場合に、OSにとっての「ストレス」が発生する。勿論、このストレスがあくまで一時的な物であって、負荷として蓄積されるので無ければ良いが、そうでない場合、ブルースクリーン発生の大きな原因になりうるのだ。そして、このドライバはストレス発生に対する防御策を取ってはいない
 なお、某blogが書いていたような、「IRQのフック」は一切行っていない。念のため。

 では、その「ストレス」が蓄積されうるか否かを見てみよう。例えば、このドライバの中ではExAllocatePoolWithTagというAPIが使われている。このAPIはドライバがファイルのアクセスをするために用いるメモリを確保するものだ、と思って頂いて(完全に正確だ、とは言わないが)おおむね間違いはない。
 しかし、これと対になる、メモリを解放するためのAPI、ExFreePoolは、ExAllocatePoolWithTagによって確保されてすぐに呼び出されるわけではない。
 このドライバの中ではこれらのAPIを呼び出している個所はそれぞれ22個所ずつある。だが、それら全てが対になって同一のサブルーチン内に存在するわけではない。様々な処理の後、別のサブルーチンでやっと解放、ということもままある。かなりのタイムラグが生ずるわけだ。
 例えば、0x011034からのサブルーチン内でExAllocatePoolWithTagを呼び出しているが、これに対応するExFreePoolはかなり後でないと出てこない。
 

* Referenced by a CALL at Address:
|:000115E0
|
:00011034 55                      push ebp
:00011035 8BEC                    mov ebp, esp
:00011037 83EC08                  sub esp, 00000008
:0001103A 6844646B20              push 206B6444
:0001103F 6A1C                    push 0000001C
:00011041 6A00                    push 00000000

* Reference To: ntoskrnl.ExAllocatePoolWithTag, Ord:003Ah
                                  |
:00011043 FF1508810100            Call dword ptr [00018108]
:00011049 8945FC                  mov dword ptr [ebp-04], eax
:0001104C 837DFC00                cmp dword ptr [ebp-04], 00000000
:00011050 7504                    jne 00011056
:00011052 33C0                    xor eax, eax
:00011054 EB3E                    jmp 00011094

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00011050(C)
|
:00011056 6A1C                    push 0000001C
:00011058 6A00                    push 00000000
:0001105A 8B45FC                  mov eax, dword ptr [ebp-04]
:0001105D 50                      push eax

* Reference To: ntoskrnl.memset, Ord:049Dh
                                  |
:0001105E E833640000              Call 00017496
:00011063 83C40C                  add esp, 0000000C
:00011066 833D0890010000          cmp dword ptr [00019008], 00000000
:0001106D 750B                    jne 0001107A
:0001106F 8B4DFC                  mov ecx, dword ptr [ebp-04]
:00011072 890D08900100            mov dword ptr [00019008], ecx
:00011078 EB17                    jmp 00011091

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0001106D(C)
|
:0001107A E881FFFFFF              call 00011000
:0001107F 8945F8                  mov dword ptr [ebp-08], eax
:00011082 837DF800                cmp dword ptr [ebp-08], 00000000
:00011086 7409                    je 00011091
:00011088 8B55F8                  mov edx, dword ptr [ebp-08]
:0001108B 8B45FC                  mov eax, dword ptr [ebp-04]
:0001108E 894218                  mov dword ptr [edx+18], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00011078(U), :00011086(C)
|
:00011091 8B45FC                  mov eax, dword ptr [ebp-04]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00011054(U)
|
:00011094 8BE5                    mov esp, ebp
:00011096 5D                      pop ebp
:00011097 C3                      ret
 

 このとおり、このサブルーチンの中では解放されていない。0x001147Dからのルーチンでは、用済みになったらさっさと解放されているのにもかかわらず、だ。
 ひょっとすると設計ミスかも、と思わなくもない。
 
 さて、これらのAPIは、メモリ確保時と解放時のIRQL情報に齟齬が生じた場合、容易にブルースクリーンとなる。エラーは大抵の場合IRQL_NOT_LESS_OR_EQUALだが、UNKNOWN_HARD_ERRORの場合や、突然のシャットダウン、ということもある。勿論、同様の問題が起こりうる事については、他のAPIにも言えるわけだが。

 ところで、elfは下級生2とパケットライトソフトの間で相性が発生する事は公式に認めている。なぜ相性が起こるかと言うと、SecuROMのドライバは、CD/DVDへのアクセスは全て「乗っ取った」つもりでいる。確かにこれは通常のWindows APIを用いてのアクセスに関しては正しい。しかし、パケットライトソフトや、一部のドライブユーティリティのように、ドライブに独自アクセスを行うものについてまで考慮してはいない。当然、これらの常駐ソフトもドライブに対して独自のアクセスを行うわけで、IRQLの操作は行ってくる。
 ここでSecuROMのドライバが、ドライブをロックしていれば、つまり他のアプリからのアクセス要求をハネ付けていれば問題なかった。しかし、実際はそうではない。また、このドライバがロードされているのが、起動時のプロテクトチェックの時のみであったなら、問題の起こる確率は極めて低かったであろう。プロテクトチェックの最中にわざわざ別アプリを起動する人間はそうはいないだろうし、仮にいたとしても極めてレアで、操作者の責任が大きいケースだ。だが、プロテクトチェックの後もドライバは解放されず、下級生2の終了後も居坐り続け、Windows再起動(ログオフでは駄目だ)までアンロードされないのでは、バッティングする機会は多くなる。
 たかがエロゲのプロテクトが、カーネルドライバを使うなどと言う分不相応な事をやらなければ、そもそも問題は起きなかった。使うにしても、ドライブをロックし、用済みになったらすぐに解放していれば、問題が起きる確率はほぼゼロに近かったはずだ。しかし、これらは守られなかった。そこに、運とタイミング次第でブルースクリーンが起こる余地を作ってしまった

 結論として、SecuROMが原因でブルースクリーンが起こりうるのか、という問いに対しては、「そうだ」と答えざるを得ない
 
 余談になるが、nyで流れているパッチを使えばどうなるのか、という話題が出た事もある。ツテを頼ってパッチを入手し、使用してみたが、この場合プロテクトドライバは作成されなかった。
 どうやら、小判鮫ルーチンを作成し、初期化を自前で行って、シナリオデコーダに制御を移しているようだ。これならば、プロテクトに起因する不具合は一切発生しない。
 ただ、これは、下手をすれば正規ユーザーよりもダウソ厨のほうが容易に「安全」を手に入れられる、と言うことでもあり、本末転倒のような気がしないでもない。


前のページへ次のページへ


<ディスクレス化研究の目次へ>
<研究室入口へ>
<掲示板へ>