おもちゃバコ

句読点がキショい

「Winnyの技術」を読んだ

こんにちは

Winnyの技術を読んだ感想,レビュー,備忘録的なのです。


書籍

達人出版会で購入しましたが,PDFで読めるのがいいですね。
tatsu-zine.com

Amazonは実本しかないようで,プレミア価格っぽいです。

想定読者

  • P2Pの技術に興味があるけど知識がない人
  • ネットワーク技術を学習する取っ掛かりがほしい人
  • Winnyの技術に興味がある人

内容はWinnyに使用されている技術の解説ですが,P2P型のシステムなのでP2Pの解説が大部分を占めています。
P2Pの入門書としては読みやすく,技術解説も詳細に記述されているので,NFTなどでP2Pに興味を持った人が初めに読む1冊としても良いと思います。
ただし,執筆日が2005年なため情報が古い場合がある点には注意。

図を多用して解説されているので読みやすいネットワークアプリ開発の入門書を探している人にもおススメですが,既に何冊か大学講義で使用されているレベルのネットワーク関係の本を読んだことがある人は既知の内容かもしれません。
ただ,Winny開発者でもある著者の思想やP2P型のファイル共有ソフトへの考え・歴史,Winnyを運用して得られた知見なども記述されているので,その点では面白く読めると思います。

(関係ないけど)本文中のいくつかの単語は3音ルール(JIS Z 8301)で書かれており,個人的に読みやすかったです。
サーバーとサーバだったら後者が好き。


メモ

ウルトラ雑

1章 P2Pの基礎知識

・Peer to Peer(P2P)
 主従関係のないシステムモデル
 -> P2Pソフトの中で(当時)最も普及したものがファイル共有ソフト
 サーバは存在せず,各ピア(ノード)が状況に応じて変化する。
 -> P2Pのピアはサーバント(サーバ兼クライアント)とも呼ぶらしい

・クライアント・サーバ方式
 サーバ: クライアントからの接続を受けてサービス提供
 クライアント: サーバに接続して一方的にサービスを利用
 -> FTP, TELNETなど (関連技術の説明に時代を感じますね)

P2P型システムの特徴
 1. システム拡大に強い
  C/S方式ではサーバへのリクエスト増加でネットワーク負荷が高まる
  -> P2P型は各ノードがサーバ機能を持つため負荷が集中することがない
 2. 障害に強い
  C/S方式ではサーバが停止するとシステム全体が利用不可
  -> P2P型はノードの一部が停止しても他ノードを利用すればよい
 3. データの一元管理・短時間同期は困難
  P2P型はデータがネットワークに分散している都合上,管理が困難
  -> C/S方式はサーバが管理できる
  P2P型は一度稼働するとシステム停止が実質的に不可能
  -> 全ノードを管理することが出来ないため

P2PとC/Sのどっちが良いか
 ただの手段に過ぎないので,利用用途に合わせて選ぼうね
 P2P: 利用者の資源を有効活用可能 (NFTとか見るとそうだよね)
 C/S: データの一元管理が可能

ファイル共有ソフトの世代
 第一と第二の違いは分かるが,第三は諸説ありそう
 1. 第一世代: Napster
  サーバの集中管理
 2. 第二世代: Gnutella
  サーバ不要のノード間やり取り
 3. 第三世代: Winny
  キャッシュ機構,匿名性を持つ

・レジューム機能
 ファイル転送失敗時に途中から再開する機能

・キャッシュ機構と匿名性
 多数のノードを経由すればキャッシュか発信元なのか区別できない
 -> キャッシュファイルの量が匿名性に関与

WinnyFreenet
 WinnyFreenetを参考にして誕生した
 -> 匿名性はFreenet
 -> 匿名と効率のトレードオフを取ったのがWinny

2章 Winny紹介

技術的な詳細はコピペになるので省略します。本書を見てね。

Winnyのコンセプト
 -> コンセプトから分かる通り,利用者のことも考えている
 -> 著者はインタプリタVM系の言語は好きではないっぽい(効率重視)
 1. 匿名性
 2. 共有効率
 3. Windowsネイティブ

Freenetの課題点
 ネットワーク規模に比例して検索依頼の効率が低下する
 -> 資源は有限なため,理論上は可能であっても効率がかなり悪い
 -> WinnyFreenetの「情報の間接的なやり取り」に注目した

・プロクシーサーバ(プロキシサーバ)
 FWでさえぎられているLAN内のノードと外部ノードとの通信に使用
 -> 外部ノードからはプロキシサーバが通信相手であり,LAN内の通信相手は不明
 -> 通信の仕組み上,IPアドレスは開示されるが,中継ノードを挟めば有耶無耶に…
 プロキシサーバは中継時にデータをキャッシュする
 -> 同データを要求された場合はキャッシュを利用

Winnyはプロキシサーバから着想を得た
 プロキシサーバはC/S方式を前提とし,P2Pに応用するには...
 -> プロキシサーバを内包したファイル共有ソフト

Winnyの開発目的は技術的な検証
 P2P型のファイル共有システムという性質上,使用してもらわないと評価出来ない
 -> ユーザ資源(環境)は様々で,先行事例もなかったから
 -> 検証が目的だったので,Winny1は正式版をリリースして開発を終了

・Winny2
 Winny1上に大規模BBSを実現することが目的
 -> 開発環境をVisual C++ から C++ Builderへ(GUI関連が目的)
 -> あくまで分散BBSの開発が目的

3章 Winnyの仕組み

Winny特有の仕組みについての技術解説。
かなり踏み込んでいるので詳細は云々。

Winnyネットワーク
 インターネット状にWinnyノードが相互に構築したネットワーク
 -> ノード参加・離脱が頻繁な動的ネットワーク
 -> ネットワーク全体の機能を保つ必要がある
 -> 上流・下流の概念があるので転送ノードを限定できる

Winnyノード
 FTTH, ADSL, ISDNなどの順に重みづけ

・上流
 処理能力の高いノード
 -> キーとキャッシュファイルを多数集め,効率を高める
 -> 木構造の根
 -> 構造的にキーを多数保持するので検索がヒットしやすい

下流
 処理能力の低いノード
 -> 木構造の葉
 -> キャッシュ済みか検索結果を受信したキーが多数
 -> 上流に向かって検索クエリを飛ばす

クラスタリング
 集めているファイルの傾向が似たユーザ同士をまとめて効率を高める
 -> 自動ダウンロード機能に利用するキーワードでクラスタリング
 -> 上流・下流クラスタリングの概念を取り入れたことがWinnyの売り
 -> クラスタが異なれば同一LAN内でもWinnyネットワークでは距離が遠い

・効率の良いネットワーク形態
 ノード間のホップ数を短くする
 -> 転送時間が短くなり,経由ノードへの負担が小さくなる
 -> 理論上はフルメッシュが最強だけど,ネットワーク規模的に不可能
 -> 1960年,ミルグラムの手紙やり取りの実験

4章 実装

Winnyの実装がガチガチに解説されている。
利用者を意識した設計についても言及されているので,P2Pに限らずユーザ目線のアプリ開発にも参考になるかも。

・バージョン
 新バージョンのノードを見つけると警告メッセージを表示
 -> プロトコルの互換性によって通信できなくなるのを防ぐため
 -> 利用者に寄り添っていたことが伺えるね。

クラスタリングの評価
 「好みが近い」という主観的な情報から相関性をどう求めるか
 -> 3つのキーワードを設定し,近似度から論理距離を求める
 -> 一致文字数の合計で相関度を評価する都合上,短いキーワードが有利
 -> 仕組みを理解してなくても使用でき,理解していれば自由に渡り歩ける
 この辺は考察しがいのある面白い箇所だと思う。
 -> キーワードを意図的に長くし,ノード間の相関係数を高めるなど

・ハッシュはMD5
 脆弱性の観点からSHA-1に置き換えることを検討していた
 -> MD5SHA-1ってとこに時代を感じる

・ノードの処理能力を加味したルール
 当時はADSLが主流だが,光回線より効率が悪い
 -> ADSLを制限すればダウンロード総量を減らせるが,Winnyネットとしては遅い
 -> 上流と下流の性能を考慮したチューニングが必要

・自前タスク管理
 OSのマルチスレッド機能に頼ると面倒なバグに直面しそうだがら自前したらしい
 -> デッドロックデバッグなどがやっかいだと思っていた
 -> これまでの経験則なんだろうね
 -> ノンプリエンプティブマルチタスク

5章 P2Pソフトの開発手法

Winnyの運用を通して得られた知見について。
運営型のプロジェクトと見ると技術者は参考になる部分が多いと思う。

P2Pアプリケーションのテスト
 数台のPCだけでは限界がある。
 -> 大多数のネットワークアプリに当てはまるはず
 -> シミュレーションするのが良いが,最終的にはテスターを募る
 -> 問題点の解消はテスター離れを防ぐためにも迅速に!

・アプリケーション開発
 プログラムは気軽に作り変えるのはほぼ不可
 -> 互換性,ユーザ離れ
 開発が終了しない
 -> 公開されない神アプリ現象って勝手に思っている
 注目を集めると攻撃される
 -> 攻撃に対するテストもやろうね
 -> 偽りのバージョン警告を出されたりしたらしい

・シミュレーション
 最終的には動作しないとわからないけど...
 -> やってみないと分からない箇所もなんらかの検証は必要
 Winnyの開発前に取り組んだのはシミュレータ開発
 -> シミュレーション先行は基本だよね
 βテストで分かることも多い
 -> ユーザがポートを開いていない場合の対処など
 -> 基本的にユーザの振る舞いを予測することは困難

Winnyネットワークの状況把握は不可能か
 モニター機構を備えれば可能だが,P2Pシステムと相性が悪い
 -> 特定ノードへの接続がバレるとDDoSされるなど

・匿名性と転送効率はトレードオフの関係
 適切な中継発生率を求めて,効率を高める

Winnyと暗号技術
 暗号化は共通鍵,Winny2のBBS認証にはRSA,その他もろもろ(RC4とか)
 -> 執筆時点では通信内容がのぞき見されることはなかったっぽいね
 -> Winnyプロトコルをマネされたら面倒
 -> 基本的にはユーザ保存のものは公開データと考える
 基本的には暗号は破られるものと考える
 -> 破られても致命的な問題を起こさない設計
 IPアドレスなどの情報は暗号化してもすぐバレるけど,心理的な効果は期待できる
 -> クロールプログラム対策とか
 -> 平文よりは遥かにマシだしね
 パッカーを利用
 -> マルウェアとかで主流の手法だね

Winnyネットワーク/プログラムへの攻撃
 ・ゴミファイルのばら撒き
  -> クラスタリングの機能を逆手に取った攻撃
  -> ノード負荷の観点からもイヤッ(ちいかわ)
 ・ファイル捏造
 ・ウィルス問題
  -> Winnyが注目された最大要因だと思う問題
 ・プログラム改ざん
  -> ハッシュ値を確認して動作変更などの対策
  -> 他プログラムと同様,システム解析への時間稼ぎにすぎない

オープンソース
 フリーライドの増加などを考慮して行わなかった

6章 残された課題と可能性

将来の展望的なお話
論文みたいだね

・デジタル認証とアクセスコントロール
 特定ファイルの削除などは検討していたっぽい
 -> 情報流出やマルウェア騒動の対策としても必要だったと思う

ファイル共有ソフトの応用
 著作物の不正利用が問題になった
 -> 社会システムが技術の進歩に伴わなければならない事例
 -> インフラ利用も出来たのではないか

付録

キャッシュ構造やシミュレーション結果,Winnyプロトコル
結構面白い


感想

読みやすいネットワーク関係の本を探しているときに出会いました。

本書の大部分はWinnyの技術説明ですが,簡単なP2Pソフトを作成できる位の知識は身に付けられる内容だと思います。
また,内容に興味があったのでネットワーク技術の書籍としては個人的に読みやすく,飽きずに最後まで読み切れたので良かったです。

Winnyは社会に負の影響を与えた事は否定できませんが,開発に使用された技術や考え方は悪いものではないので, 頭ごなしに全てを否定するのではなく参考に出来る部分は参考にしていきたいですね。

ただ,Winnyは利用者に寄り添った実装を行っている部分が多々見受けられたので,開発者は利用者の使用用途をある程度は予測していたのではないかと感じました。
(まあ,この辺は終わった話なので深くは言及しませんが...)

P2P技術とソフトウェアの在り方について考えさせられる本でした。

「Winny 天才プログラマー金子勇との7年半」を読んだ

こんちは

Winny 天才プログラマー金子勇との7年半を読んだ感想です。

ほぼ読書感想文です。


Amazon

Amazon Primeの特典で読みました。

自分は世代ではないので詳しくないのですが,Winnyマルウェアが拡散されていたことを受けて当時の官房長官Winnyの利用を停止するように国民に呼びかけるほど注目されていたそうです。

Winnyが注目された時代を覚えている人は本書籍を楽しく読めると思います。


感想

技術的な話がメインではなく,Winnyの開発者である金子勇さんが無罪になるまでの過程を弁護士の視点から書かれています。
内容の大部分は検察と弁護側のやり取りが中心で,担当弁護士(著者)が無罪を勝ち取るまでの軌跡です。

Winny事件の怖いところは,Winnyを利用して違法ダウンロードを行っていた者だけではなく,Winnyの開発者も著作権侵害行為を「幇助」したとして逮捕されてしまったことです(「幇助」はWinny事件のキーワード)
もしWinny事件で無罪が勝ち取れなかった場合,日本での技術の開発や利用に大きな影響を与えていたかもしれない…

検察とのやり取り(というか戦い)も詳細に記述されていますが,検察はなかなかあくどいことをしていたようで,弁護側の視点から見た検察の姿なので悪者に見えてしまいますが,尋問の様子をまとめた当時のブログを読むと事実っぽいです。
公判を傍聴した人が内容をまとめて公開している当時のブログが存在する当たりWinnyの注目度がわかりますね。
(現在(20221124)でも「カプセルマン winny」とかで検索すると出てきます。カプセルマンの詳細は本書を読んでね。)

また,本文中に某放送局が弁護団に送った手紙の要約とその手紙の写真が掲載されているのですが,ほぼ晒し同然の扱いで笑いました。
正直誰でも怒る内容だったので同情の余地はない気がします。

最終的には無罪となりましたが,Winnyについた悪いイメージと無罪になるまでに経過した時間を考えると何とも言えませんね…
ただ,NFTの登場でP2Pの技術が再度注目されたことで,Winnyに実装されている技術は将来性のあるものだったとも考えると,無罪を勝ち取った事で開発者の名誉は守れたのではないかと個人的には思います。


まとめ

P2Pについて調べていた時にWinnyの記事を見かけ,仕事のお昼休みに読み始めたのですが担当弁護士の狂言回しが面白くてスラスラ読めました。

趣味でプログラムを書く人やソフトウェアを開発して公開している人などは,本書を読んでみるといろいろ思うことがあるはずです。
特に「どのような目的」で「どのようにして公開」するかは技術者であれば明確にしておきたい項目だと思いました。

また,悪意の有無に限らず,ソフトウェアなどを公開することで社会に与える影響を考慮することは,技術者として必要な能力だなと改めて感じました。

おまけ

この記事を書いているときにwikipediaを見て知ったのですが,来年(2023年)に映画化されるそうですね。
ja.wikipedia.org

「デバッガによるx86プログラム解析入門 x64対応版」を読んだ

ちは~

デバッガによるx86プログラム解析入門 x64対応版 を読んだ感想,備忘録です。


Amazon

Kindleで購入しました。

2014年11月1日発行の第1版第1刷なので,現在(20221105)では内容が少し古いと感じる部分がありました。
ただ,プログラム解析の本質には関係ないので大体の人は気にならないと思います。


対象読者

・プログラム解析に興味がある人
・システム層のプログラムに興味がある人
アセンブリの取っ掛かりが欲しい人
コンピュータサイエンス関係の学生

解析対策と個人的な興味を兼ねて本書を購入しました。
ブレークポイントの仕組みなどについても解説されており,エンジニアなら読んで損はないはず。

何のとは明言しませんが,不正対策を行うエンジニアに特におススメです。

C言語C++を学んでいれば読める内容だと思います。
(この書籍を手に取る人は基本的に問題ないはず)

対象はWindowsなことに注意。


メモ

ちょうざつ~

Chapter1 デバッガとは

・デバッガの用途
 作成したプログラムのデバッグ以外にも,第三者のプログラム解析に利用される。
 -> 開発終了したソフトウェアのバグ修正とかね。

・対象による分類
 ・ユーザモードデバッガ
  OS上でユーザモードで動作するアプリのデバッガ。
  -> 他で実行されているプログラムを制御下に置く権限を持つ。
 ・カーネルモードデバッガ
  OSの中核で動作する特殊なドライバやアプリを対象としたデバッガ。

ソースコードによる分類
 ・シンボリックデバッガ
  あらかじめ出力したデバッグ用情報などをもとにデバッグする。
  -> Visual Studioのデバッガとかかな。
 ・アブソリュートデバッガ
  シンボリックデバッガ以外のデバッガ。

・その他の分類
 ・Just-In-Timeデバッガ
  実行中のプログラムが死んだときに自動的に起動させてデバッグする。
  -> Windowsに備わってるJITデバッグ機能に対応しているデバッガ。
  -> Visual Studioにもあるはず。

・代表的なデバッガ
 1. OllyDbg
  フリーウェア。多分世界で一番人気。
 2. SoftICE
  カーネルモードデバッガ。MSがフリーのカーネルモードデバッガを公開したことで姿を消した。
  個人でカーネルモードデバッガを開発するのは厳しいかもね。
 3. Debugging Tools For Windows
  Microsoftが公開しているデバッグツール群。
  64ビット対応の主要なデバッガ。
 4. IDA
  みんな大好き有能でデバッグツール。
  高機能だけど高価。
  -> マルウェア解析でよく見るよね。

WindowsXPまではデバッガ終了時にプログラムも終了してた。
 1度アタッチされたらデバッガと運命を共にする。
 -> タスマネで終了できないアプリにアタッチしてデバッガごと終了する裏技。

・ステップ実行
 ステップオーバ,ステップイン,ステップアウトを説明できるように。

.NET Framework対応アプリ
 C/C++と違い,プログラム自体にネイティブコード元が格納されている。
 -> .NET Framework側でネイティブコードを実行。

リバースエンジニアリング
 使用許諾で禁止されているが,独禁法著作権的には云々らしい。
 -> エミュレータや互換機開発で行われてるぐらい?
 -> 近年では,マルウェア解析でお盛んになっている。

Chapter2 デバッガと関連ツールを使用するために必要な基礎事項

・基礎は大事
 プログラム解析の前にコンピュータの基礎知識は身に着けよう。
 -> パタヘネとヘネパタを読んでおけば大丈夫だと思う。
 -> よくわからないときは時間を置こう(個人的な感想)

・ニブルとバイト
 コンビ名みたい。
 オクテットも忘れないでね。

・リトルエンディアン
 大体のWindowsが利用しているCPUはこっち。
 -> 家庭用ゲーム機とかだとビッグエンディアンもある。

・データ型
 Byte, Word ,DWord, QWordは頻出。

・メモリアドレス0x400000
 基本的にEXEが読み込まれるプロセスメモリ上のアドレスは0x400000。
 -> ソフト付属のDLLは基本的に0x10000000に読まれる。
 -> 複数DLLを使用する場合は別アドレスにも読まれる(リロケーション)

x86系32ビットCPUの汎用レジスタ
 EAX, EBX, ECX, EDX, ESI, EDI
 特殊用途のESP, EBP, EIP, EFLAGS
 -> 覚えるために書いた。
 EAXとかはAHとALに分けられる。
 -> AHが上位1バイト,ALは~。
 本来の用途はあるが,別にその用途じゃなくても良い。
 -> EAXはアキュームレータとか。

・Address Space Layout Randomization(ASLR)
 脆弱性緩和機能のことで,Vista以降に実装されている。
 -> 0x400000固定ではなくランダムなプロセスメモリ上のアドレスに読まれる。
 -> ASLR対応設定は無効化されやすいかも。

・NOP命令
 既存コードを上書きしたいときによく使用されるよね。

・ネイティブAPI
 Windows NT系OSが内部で使用するAPIで,ユーザモードとカーネルモードの仲介役が多い。
 システム詳細部に入り込めてしまうため,資料は基本的に非公開。
 -> リバースエンジニアリングによって詳細が明らかにされたAPIマルウェアに利用されぎみ。
 -> ntdll.dll格納されているのが多い(NtReadFileとか)

・PEファイル
 大体のWindows用実行ファイルをPortable Executable(PE)ファイルと呼ぶ。
 ・MZスタブ
  実行可能ファイルの先頭ブロック。オフセット。
 ・COFFファイルヘッダ
 ・オプションヘッダ
  Windows上で実行される際の基本情報部分。
  PEファイルヘッダ中で注目の的。
  -> AddressOfEntryPointとか。
  -> ImageBaseを基準とした相対アドレスはRVA(相対仮想アドレス)
 ・セクションテーブル
  プログラムコード,リソースなどを格納する各セクション情報。
  -> .text, .dataなど。

・Import Address Table(IAT)
 プログラムで使用するAPI関数の開始位置アドレスを格納したテーブル。
 -> DLLファイル中のAPI関数がメモリ上のどこにあるかを表現。
 -> プログラム実行時にIATが意味をもつ。

・ntdll.dll/Kernel32.dll/USER32.dll
 Windows上で実行されるプロセスでは,これらDLLのメモリ上アドレスは同一。
 -> 特定DLLのエクスポート関数を実行するにはrundll32.exe

rootkit
 OS深層部にあるデータを変更し,特定プロセスを隠ぺいする機能を持つ。

カーネルドライバのデジタル署名
 Microsoftの認定組織から承認が必要で,プログラム解析用途のツールはデジタル署名が難しい。
 -> Vista以降はカーネルドライバにデジタル署名がほぼ必要。

・プロセスメモリ
 32bit環境では,各プロセスに約4GBののプロセスメモリ空間が割り当てられる。
 -> プロセスメモリを「ページ」に分け,必要な分だけメモリに読み込み,
   後はHDDなどに保存する「ページング方式」が使用される。
 -> プロセスメモリ上のアドレスと記憶装置としてのメモリ上のデータ位置は異なる(仮想アドレス空間)
 -> Windowsのページサイズは0x10000バイト単位らしい。
 -> 0x80000000以降はカーネルモード用。

・プロセスメモリ上の各ページのアクセス種類
 実行,読み込み,書き込み,アクセス不可などの属性がある。
 -> デバッガやプロセスメモリエディタなどは属性変更の機能を持っていることが多い。

ブレークポイント
 ・INT 3命令(0xCC)
 ・メモリアクセス違反
  プロセスメモリ上の任意アドレスを含むページをアクセス不可にして,そこにアクセスしたときにOSに落としてもらう。
  OS上の不正な処理を故意に引き起こしているのであまり良くない。
  -> 独自ASSERT的な奴?
 ・ハードウェアブレークポイント
  ブレーク条件を色々設定でき,プロセスメモリのアクセス属性を書き換えるわけではないのでいい感じ。

・ステップ実行などの仕組み
 デバッギーの処理を停止状態でEFLAGSのトラップフラグをセットする。
 -> 実行後にデバッガ用の例外処理を発生させ,OS経由でデバッガに通知している。

・一般的な解析
 1. 動的解析
 2. 静的解析
 3. ブラックボックス手法
 4. ホワイトボックス手法

Chapter3 デバッガと関連ツール

ツール紹介がメイン。
小話が面白い。

・OllyDbg
 USB等から利用されることを考慮して,設定ファイルは.ini保存らしい。

・ファイルマッピング
 ファイルの編集対象部分のみを読み込む。
 -> メモリ節約。

・プロセスメモリエディタ
 改造コードとかのアレ。
 -> うさみみハリケーンとか

・リソースエディタ
 実行ファイルのリソースセクション解析ソフト。
 -> デバッグ用に封印されている機能を探すときなど。

仮想マシンの利用
 解析ツール対策しているソフトをゲストOSで立ち上げ,ホストOSでは仮想マシン構築ソフトをデバッガに掛ける。
 -> 通常より解析が困難になるがアプローチとしては有効。

Chapter4 デバッガでプログラムの実行処理を追いかける

OllyDbgの説明が主。
著者作成のプログラムを対象に演習を行う。

・どこに注目するか
 1. プログラム解析前
  コンパイラやパッカー,リソースエディタでの項目など。
 2. 解析時
  レジスタ,プロセスメモリの内容。
  条件分岐,呼び出しAPI関数など。
 3. 関連要素
  ソフト同梱のヘルプなどの文書。
  作者の開発日誌や書き込み内容,バグ修正内容など。
  -> ソーシャルエンジニアリング的な要素も重要。

・メッセージボックスセオリー
 CDチェック警告などをわざと表示し,その周辺をバイナリエディタでチョメる。
 -> MessageBox関数が呼び出されていることが多かったのが由来。

Chapter5 実践的な処理の変更

サンプルプログラムのCD/DVDチェック回避から学ぶ。
現代では通じないと思うが,プログラム解析の観点ではわかりやすい。

・パラサイトルーチン
 コードインジェクション

・2重起動チェック
 CreateMutex関数がよく使われる。
 -> ミューテックスオブジェクトが重複しないように。
 -> 用途が限定されやすいので無効化して回避する手段もあり。
 EnumWindows関数で検索も考えられる。

・画面切り替えチェック
 ウィンドウのアクティブを監視する機能。

・デバッガ起動チェック
 プログラム解析対策の基本要素だね。
 -> EnumWindowsやGetClassNameは使われやすいAPI
 IsDebuggerPresent対策
 -> GetProcAddress関数呼び出しを監視する。

・差分パッチコード
 該当する文字列を別文字列に置換する。
 -> なるほどねってなった。

Chapter6 ゼロからはじめるデバッガ自作

Windows7/8用のデバッガを製作方法が解説されている。
Visual Studioの設定やデバッガ作成に必要な各種APIの説明付き。

・デバッガの基本処理
 デバッギーの起動,起動済みプロセスへのアタッチ。
 プロセス作成,例外発生などの通知を待つ。

・SeDebugPrivilege
 デバッガがよく使用する特権
 -> Windowsの制約回避のため使用することを推奨
 -> CreateProcess, OpenProcessなどのAPIを解説

・プロセスメモリのアクセス属性
 アクセス属性の変更はVirtualProtectEx関数を併用する。
 -> PAGE_EXECUTE_READWRITEが多い。
 -> 0x1000バイトのページ単位

・逆アセンブラ
 OllyDbg公式サイトで配布されているソースコードがわかりやすい。

APIフック
 解析対象プロセスのモジュール内のIATを解析し,
 任意のAPI関数アドレスを書き換えるのが主流。
 -> CreateRemoteThread関数など

Chapter7 デバッガ対策

解析ツール検出によく使われるAPI関数が解説されている。

マルウェア解析技術についても解説されている。
-> 別書籍で知っている内容だったので流し見

・基本的に考えは「解析を妨害する」こと
 解析を完全に防ぐことは不可能
 -> 解析行為を諦めさせることを念頭に置く
 -> 耐タンパー化技術,難読化

ロジックボム
 解析を検出したときにシステムファイルを削除するなど
 -> マルウェアがよく使う過剰な対抗手段

・検出技術と対策技術はいたちごっこ

・パッカーとクリプター

Chapter8 デバッガに関する問題

プログラム解析の発展などについて多角的に解説されている。

デジタルミレニアム著作権法(DMCA)
 プログラム解析とPCゲームの著作権について
 -> 解析結果の公開や法律などの問題点が指摘されている。
 -> 解析結果の販売などの判例

・プログラム解析におけるrootkit
 カーネルモードで動作してAPIフック
 -> カーネルレベルのプロセス隠蔽はデバッガでも解析が面倒

・ネットゲーム
 modやチート,RMTなどについて

マルウェア対策としてのrootkit
 ユーザが気づかないままrootkit技術を使用
 -> rootkit技術の利用は賛否が分かれる

ロジックボムの利用
 誤作動や実装時間,存在が知れ渡った際の風評を考慮すべき

Winny
 開発者とソフトウェアのもたらす影響について
 -> ソフトウェアが違法行為を助長する可能性の把握は困難
 -> 関連書籍を購入したのでそっちで詳しく見てみる予定

・プログラム解析技術の悪用
 技術の悪用防止は現実的に不可能
 -> 用途は利用者の良識次第
 -> Winnyがその例

Chapter9 アセンブリ言語によるプログラミング基礎

アセンブリ言語の基礎を解説
-> MASM32やWinAsm Studioなどについても言及

アンセブリを学びたいときはこの章を読み込む
-> プロセスメモリパッチャーの作成を説明している

アセンブリ言語とプログラム解析
 プログラム解析技術を身に着ける方法として優秀
 -> スキルアップとしては優秀だが時間と労力は凄い

Chapter10 64ビットアプリケーションの解析

32ビットと64ビットの解析方法の相違点について解説されている

・32ビットアプリケーション解析の応用
 -> まずは32ビットの解析を学ぼう

Chapter11 情報セキュリティとプログラム解析

・日本における同人ゲームなどの個人製作アプリ
 大手企業のソフトと違い脆弱性対応はほとんどされない
 -> 海外のリバースエンジニアリング界隈で注目されていないだけ
 -> 脆弱性修正パッチと銘打ってマルウェアを仕込む輩がいる


感想

プログラム解析に興味を持った時に出会った書籍でしたが, 解析手法について素人でもわかりやすく解説されていてとても読みやすかったです。

特にプログラム解析手法だけでなく,それら解析ソフトの利用用途や開発して公開することのリスクなど, 技術者の倫理的な観点も絡めて解説されていたので新鮮でした。

また,おまけのページにアセンブリや解析によく利用されるWindowsAPIについても説明されており, 個人的にとりあえず手元に置いておきたい本です。

学生の時に読んでおきたい1冊でした。

ファイルを簡単に暗号化したかった

こんばんわ

ファイルを簡単に暗号化する方法を模索した時のメモです。
ふーんって程度に。

動機

平文はちょっとな~ってレベルのファイルを簡単に暗号化する方法を知りたかった。

見られるのは嫌だけどガチガチに暗号化するレベルのモノではないときに使いたい。

環境

Windows 10/11 (バージョン省略)

OpenSSL1.0.2
OpenSSL1.1.1

Git Bash
MSYS2

gcc or g++ or clang++
(Pythonとかなんでもいいです)

Linuxコマンド使えれば何でも良いです(多分)


サンプルファイル

今回はこれをなんちゃって暗号化とOpenSSLで暗号化してみる。

ファイル名はfile.txt

abcdefg
fdadsafdsa
abcdefg
fdasdfghthtjhh
tretafdsgdghs
dssaweqt231551
gdhbdfst 351242
あいうえよんさ

最終的に3パターン考えましたが,
やっぱりOpenSSLが安定です。


1. xxdコマンドを駆使する

16進数や2進数でダンプするコマンド。
オプションr(-r, -revert)が16進ダンプをバイナリに変換するのでこれを利用する。

xxd file.txt > dump.txt
で16進数でダンプして
xxd -r dump.txt > origin.txt
で元に戻す。

1. xxd file.txt > dump.txt

dump.txtの中身はこれ

00000000: 6162 6364 6566 670a 6664 6164 7361 6664  abcdefg.fdadsafd
00000010: 7361 0a61 6263 6465 6667 0a66 6461 7364  sa.abcdefg.fdasd
00000020: 6667 6874 6874 6a68 680a 7472 6574 6166  fghthtjhh.tretaf
00000030: 6473 6764 6768 730a 6473 7361 7765 7174  dsgdghs.dssaweqt
00000040: 3233 3135 3531 0a67 6468 6264 6673 7420  231551.gdhbdfst
00000050: 3335 3132 3432 200a                      351242 .

丸見えだけど,何回かxxdすればわかんないんじゃね?

2. xxd file.txt | xxd > dump.txt

3回やったら長かったので,ここでは2回の結果で。

00000000: 3030 3030 3030 3030 3a20 3631 3632 2036  00000000: 6162 6
00000010: 3336 3420 3635 3636 2036 3730 6120 3636  364 6566 670a 66
00000020: 3634 2036 3136 3420 3733 3631 2036 3636  64 6164 7361 666
00000030: 3420 2061 6263 6465 6667 2e66 6461 6473  4  abcdefg.fdads
00000040: 6166 640a 3030 3030 3030 3130 3a20 3733  afd.00000010: 73
00000050: 3631 2030 6136 3120 3632 3633 2036 3436  61 0a61 6263 646
00000060: 3520 3636 3637 2030 6136 3620 3634 3631  5 6667 0a66 6461
00000070: 2037 3336 3420 2073 612e 6162 6364 6566   7364  sa.abcdef
00000080: 672e 6664 6173 640a 3030 3030 3030 3230  g.fdasd.00000020
00000090: 3a20 3636 3637 2036 3837 3420 3638 3734  : 6667 6874 6874
000000a0: 2036 6136 3820 3638 3061 2037 3437 3220   6a68 680a 7472
000000b0: 3635 3734 2036 3136 3620 2066 6768 7468  6574 6166  fghth
000000c0: 746a 6868 2e74 7265 7461 660a 3030 3030  tjhh.tretaf.0000
000000d0: 3030 3330 3a20 3634 3733 2036 3736 3420  0030: 6473 6764
000000e0: 3637 3638 2037 3330 6120 3634 3733 2037  6768 730a 6473 7
000000f0: 3336 3120 3737 3635 2037 3137 3420 2064  361 7765 7174  d
00000100: 7367 6467 6873 2e64 7373 6177 6571 740a  sgdghs.dssaweqt.
00000110: 3030 3030 3030 3430 3a20 3332 3333 2033  00000040: 3233 3
00000120: 3133 3520 3335 3331 2030 6136 3720 3634  135 3531 0a67 64
00000130: 3638 2036 3236 3420 3636 3733 2037 3432  68 6264 6673 742
00000140: 3020 2032 3331 3535 312e 6764 6862 6466  0  231551.gdhbdf
00000150: 7374 200a 3030 3030 3030 3530 3a20 3333  st .00000050: 33
00000160: 3335 2033 3133 3220 3334 3332 2032 3030  35 3132 3432 200
00000170: 6120 6533 3831 2038 3265 3320 3831 3834  a e381 82e3 8184
00000180: 2065 3338 3120 2033 3531 3234 3220 2e2e   e381  351242 ..
00000190: 2e2e 2e2e 2e2e 2e0a 3030 3030 3030 3630  ........00000060
000001a0: 3a20 3836 6533 2038 3138 3820 6533 3832  : 86e3 8188 e382
000001b0: 2038 3865 3320 3832 3933 2065 3338 3120   88e3 8293 e381
000001c0: 3935 3061 2020 2020 2020 202e 2e2e 2e2e  950a       .....
000001d0: 2e2e 2e2e 2e2e 2e2e 2e0a                 ..........

英数字はそのまんまだけど,1回だけよりはマシかな…

3. xxd -r dump.txt | xxd -r
問題なく元に戻る。

xxdを行った回数xxd -rすればよい。

わかりにくくなったし,日本語文章だけならxxdを数回すれば自分的には十分かな。

ただxxdの度にファイルサイズが増大すること,英数字にはほぼ無力。
あとxxdコマンドを知っている人には直ぐバレるのが欠点。

オプションseek(-seek)とか,オプションを駆使すればわかりにくくはできると思う。

2. xxdコマンドからC/C++で読み込む

オプションi(-i, -include)でC言語形式のファイルを出力するので,これを利用する。

正直,プログラム書くならxor暗号で暗号化するプログラムを書いた方がマシ。

1. xxd -i file.txt > aiueo.data
こんな感じのが出力される。

unsigned char aiueo_txt[] = {
  0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x0a, 0x66, 0x64, 0x61, 0x64,
  0x73, 0x61, 0x66, 0x64, 0x73, 0x61, 0x0a, 0x61, 0x62, 0x63, 0x64, 0x65,
  0x66, 0x67, 0x0a, 0x66, 0x64, 0x61, 0x73, 0x64, 0x66, 0x67, 0x68, 0x74,
  0x68, 0x74, 0x6a, 0x68, 0x68, 0x0a, 0x74, 0x72, 0x65, 0x74, 0x61, 0x66,
  0x64, 0x73, 0x67, 0x64, 0x67, 0x68, 0x73, 0x0a, 0x64, 0x73, 0x73, 0x61,
  0x77, 0x65, 0x71, 0x74, 0x32, 0x33, 0x31, 0x35, 0x35, 0x31, 0x0a, 0x67,
  0x64, 0x68, 0x62, 0x64, 0x66, 0x73, 0x74, 0x20, 0x33, 0x35, 0x31, 0x32,
  0x34, 0x32, 0x20, 0x0a, 0xe3, 0x81, 0x82, 0xe3, 0x81, 0x84, 0xe3, 0x81,
  0x86, 0xe3, 0x81, 0x88, 0xe3, 0x82, 0x88, 0xe3, 0x82, 0x93, 0xe3, 0x81,
  0x95, 0x0a
};
unsigned int aiueo_txt_len = 110;

雑に読むなら下のプログラムで十分なはず。

#include <iostream>
#include "aiueo.data"

int main()
{
    for (int i = 0u; i < aiueo_txt_len; ++i)
    {
        std::cout << aiueo_txt[i];
    }
    return 0;
}

3. OpenSSLで暗号化

opensslコマンドで暗号化できるらしいことを知りました。

この方法が最強だと思います。

openssl enc -e -aes128 -in file.txt -out dump.txt -pass pass:aiueo
openssl enc -e -aes128 -in file.txt -pass pass:aiueo > dump.txt

暗号化オプションを選択して入出力ファイルを指定し,パスワードを設定。
複合はオプションe(-e)をオプションd(-d)にすればよい。

openssl enc -d -aes128 -in file.txt -out dump.txt -pass pass:aiueo

多分どんなファイルでも大丈夫。

と思ったのですが,実際に使ってみたら上手くいきませんでした。
OpenSSL1.0.2で暗号化したものをOpenSSL1.1.1で複合したら失敗しました。

どうやらOpenSSLのバージョンが関係しているそうです
(運良く暗号側がOpenSSL1.0.2を使用してた)
OpenSSL1.1.0と以前のバージョンとの互換性

ArchLinuxのOpenSSLのページでも言及されてますね。

ダイジェストアルゴリズムを合わせれば問題なく読めました。
暗号と複合が逆なら合わせればおけー。

暗号側 OpenSSL1.0.2: MD5
openssl enc -e -aes128 -in file.txt -pass pass:aiueo > dump.txt

複合側 OpenSSL1.1.1: SHA256 -> MD5
openssl enc -d -aes128 -md md5 -in file.txt -out dump.txt -pass pass:aiueo

MD5の安全性が気になる人はSHA-256とか使うと良さそうです。
自分はそこまで気にしてないのでMD5でいいや。


まとめ

OpenSSLがさいつよだけど,バージョン互換には注意。
(OpenSSLは公式ではソースコードしか配布していないっぽい(?)ので,Msys2やWSL2の力を借りるのが早そう)

xxdで暗号化を考えるより「暗号化 コマンド」でググったらOpenSSLが出てきたのでGoogle検索は偉大。

初めからきちんと調べてから取り組みましょうって話だよね

「初めてのマルウェア解析 Windowsマルウェアを解析するための概念、ツール、テクニックを探る」を読んだ

こんにちは

初めてのマルウェア解析を読んだ感想,レビュー,備忘録的なものです。


書籍

今回はオライリーの公式サイトで電子書籍版を購入しました。
pdfやepub形式でダウンロード出来るので,環境を選ばない点で便利ですね。

www.oreilly.co.jp

想定読者

  • マルウェア解析に興味がある人。
  • セキュリティの基礎を学びたい人。
  • システムよりの知識を深めたい人。
  • 解析ツールの使い方を知りたい人。

プログラム知識はC言語とPython2.7を知っていれば大丈夫です。
アセンブリも出てきますが本書に興味を持っている人は大丈夫だと思います。
対象PCはWindowsですが,解析環境はLinuxです。

何とは明言しませんが,不正対策に興味がある人にもおススメです。
マルチプラットフォーム(特にWindows)を扱う人は楽しく読めると思います。
DLLの理解を深めたい人にもおススメ。

手を動かして学びたい派は満足できるはず。


メモ

マルウェアはMalicious Softwareが正式名称。

自分はLinux環境の物理マシンがないので,マルウェア実行はしない。
サンプルコードを書いて疑似的に試すだけ。

メモは雑。

1章 ようこそ,マルウェア解析の世界へ

マルウェアは「悪意ある行為を実行するコードの総称」
 -> でバイナリ,スクリプトなど形式は色々。

マルウェア解析の種類
  1. 表層解析
    プログラムを実行せずに解析する手法
    分析対象に関連するメタデータの抽出などを行う。
  2. 動的解析
    分析対象を隔離環境で実行し,動作を解析する手法
    実行が簡単で,挙動が確認できるので有益な情報が得やすい。
  3. コード解析(静的解析)
    プログラムの内部動作を理解するために行う高度な解析手法
    コード解析は,静的コード解析・動的コード解析に分類される。
    逆アセンブルなどを行うため,OSなどのシステム概念を理解する必要あり。
  4. メモリ解析
    フォレンジック調査で必要な痕跡を見つけるためにRAMを分析する手法
    マルウェアのステルス機能を検出する上でも役立つ。

マルウェア解析はVMで行おうね。
 -> マルウェアによっては仮想環境を回避してホストに感染してくるので注意
 -> 仮想ソフト最新,ネット接続ダメ,仮想環境は空にすること。
 -> ネット接続時はパケットキャプチャやwgetなどで偽装することを考える。

・本書はWindowsマルウェア分析なので,ホストはLinuxか🍎がおススメ
 -> 脱走されてもワンチャン感染できないため。
 -> 書籍はLinux上にWindowsVMとLinuxVMを立てて,LinuxVMがルーティング。
 -> 白紙のスナップショットを作成しておくといろいろ楽

・INetSim
 -> マルウェアが使用するネットワークプロトコルのサーバをシミュレーション
 -> 後でがっつり説明するよだって。

・検体は自分で探して。

関係ないけど「ようこそ,~」の構文ってよく使われてるよね。

2章 表層解析

・ファイル形式の判別で対象OSが判別
 -> Portable Executable(PE)ならWindowsとか。
 -> Windowsベースは大体exe, dll, sysのどれか。

・ファイル形式ではなくファイルシグニチャが重要
 -> ファイルヘッダに書き込まれてる識別子。MZ(4D 5A)とか。
 -> バイナリエディタで開けばわかる(自分はvim+xxd)
 -> fileコマンドやpython-magicでもいける。
 -> WindowsはCFF Explorerが良き。

・ファイル内容に基づいて生成されるフィンガープリントは重要
 -> ファイル内容に基づくハッシュ値があればファイル名の偽装は見破れる。
 -> マルウェアがコピーしたマルウェアがオリジナルなのかが判別できる。
 -> データベースで検索すれば手元の検体が新種か判別できる。

・md5sum, sha256sum, sha1sumでファイルハッシュを生成できる(Linux)
 -> WindowsはHashMyFilesなど。
 -> Pythonならhaslib。
 -> Git Bashでmd5sumやfileなどが使えたのでWindowsの方はうんぬんかんぬん。

マルウェアスキャンサービスはVirusTotalがおススメ
 -> PythonAPIが公開されてる。
 -> 標的型攻撃の場合はハッシュだけ送信するのが良さそう。
 -> 機密情報が記載されてる場合もあるので

・文字列抽出で手がかりを探す
 -> Linuxはstringsコマンドでいける(-a, -el)
 -> Windowsはpestudioが強い
 -> Stringsコマンドを公式からダウンロードでもいける。

・検出回避のために難読化されてるときはFLOSSがよい。
 -> FireEyeLabs Obfuscated String Solver

・パッカー(Packer)
 プログラムを圧縮して,実行ファイルを難読化するプログラム。
 -> もともと実行ファイルを圧縮するものだった
 -> 今ではマルウェア圧縮に使われているらしい(ESET)
 -> UPXが有名(-dオプションで解凍)
 -> 圧縮ファイルとして暗号化させることでシグネチャ参照を回避するのが目的?

・クリプター(Cryptor)
 圧縮ではなく暗号化を行う。

・パッカー検出はExeinfo PEなどがある。

・PEヘッダはプログラム機能に関する情報がめっちゃ得られる。

マルウェアはOS標準機能を利用する。
 -> Windows上にファイルを作成する場合はkernel32.dllのAPIを呼び出すなど。
 -> マルウェアが依存するDLLを調べれば目的や挙動が示唆できる。
 -> ファイル依存関係はPEファイル構造のインポートテーブルに格納される。
 -> 実行時にDLLを明示的に読み込み,関数アドレスを解決することで
  インポートテーブルに含まれないようにしている場合もある。

・インポート関数の数で難読化されているか判別できる。
 -> 少なければ難読化されている可能性が高い。

・エクスポート関数名は解析対策に偽装されている場合があるので注意

コンパイル時刻の検体特定に役立つ。
 -> 改ざんされていても改ざんされている事実が残るため。
 -> Delphiは1992/06/19になるのでわかりやすい。

・アイコン,メニュー,ダイアログなどのリソースは.rsrcに格納されている。
 -> Resource Hackerで解析可能。
 -> よくあるアイコンをExcelに偽装しているマルウェアもここを見ればわかる。

・ファジーハッシュ(Fuzzy Hashing)
 類似度を検証するためのハッシュ(雑)
 MD5とかは同一判別しかできないが,
 類似度解析を行えばマルウェアファミリーを識別できる。
 -> ssdeepなど。

・インポートハッシュ(import Hash)
 同じ攻撃者によって作成された別のマルウェアを識別する手法(参考)
 -> ライブラリ・インポート関数の命名や順序からハッシュ値を計算する。

・YARA
 マルウェアに含まれる固有の文字列・データを識別するソフト。
 -> 詳細は本書見て。

3章 動的解析

・Process Hacker
 システムリソース監視に役立つオープンソースツール。

・Process Monitor
 ファイルシステムレジストリ,プロセスなどをリアルタイム監視できるツール。
 -> Noribenと連携するとマルウェア解析に役立つ。

Wireshark
 みんな大好きパケットスニファ。

・INetSim
 DNS, HTTP/HTTPSなどの標準インターネットサービスを模倣するソフト

・動的解析のステップ
 1. クリーンスナップショットへ戻す
 2. 監視・動的解析ツールの実行
 3. マルウェアの実行
 4. 監視ツールの停止
 5. 結果の分析

Dynamic Link Library
 他プログラムで利用できるエクスポート関数を含むモジュール。
 API関数を呼び出したい場合は,
 APIをエクスポートするDLLをメモリ空間にロードする必要がある。
 -> 詳しくはここ

・DLLが悪用される理由
 ・任意のプロセスに読み込ませることで,マルウェアの挙動を隠す。
 ・実行中のプロセスにDLLインジェクションを仕掛け,
  システム持続性を確保する。
 ・DLLをメモリ空間に読み込ませ,プロセス機能を操作する。
 ・実行ファイルより解析が難しい場合がある。

・rundll32.exe
 DLLを起動,エクスポートされた関数を呼び出す。
 1. DLL Entry Point関数を呼び出す(DLLMain関数のこと)
  -> マルウェアはDLLMain関数に実装されていることが多い。
 2. DLL読み込み完了後,エクスポート関数のアドレスを取得して関数を呼び出す。

・エクスポート関数のないDLL
 DLL Entry Point関数が呼び出されることを利用して,
 DLLMainにロガーなどの機能を実装する。

・Remote DLL
 システム上で実行中のプロセスにDLLインジェクションを仕掛けるツール。
 explorer.exeなどの特定プロセス以外で実行された場合,
 挙動を変えたりするプロセス対策。

・サービスDLL
 サービスとして読み込まれる時だけ実行されるDLLのこと。

4章 アセンブリ言語と逆アセンブル入門

みんな大好きリバースエンジニアリングの基礎。
詳細に説明されているのでアセンブリ入門者にもおススメ。

・データのメモリ常駐
 メモリ内では,データの下位バイトは下位アドレスに,
 上位バイトは上位アドレスに格納されるリトルエンディアン形式。

・命令ポインタ
 EIPはメモリ内の次の命令アドレスを指す。
 (多分)プログラムカウンタのこと(参考)

・オペコードとオペランド
 混乱しないで!

・各括弧はメモリアドレス指定
 -> ものによるのを忘れずに。

・逆アセンブル結果から型情報の推測は重要。

・CMP命令
 引き算してゼロフラグを設定するか判別。
 -> cmp eax, 45
・TEST命令
 ANDしてゼロフラグを設定するか判別。
 -> test eax, eax

・スタックは上位アドレスから下位アドレスの方向へ積み上げる。
 -> popされた値は論理的に削除されるが,物理的にはメモリ上に残っている。

・関数プロローグ
 関数実行前の前処理のこと。
 関数終了後に復元する処理は関数エピローグ。
 -> 大体フレームポインタを弄ってる。

Windows 32-bit on Windows 64-bit(WOW64)
 64bit Windowsで32bitの実行ファイルを実行するためのサブシステム。
 64ビットDLLは/Windows/System32
 32ビットDLLは/Windows/SysWOW64
 -> 32ビットマルウェアがSystem32にアクセスする場合はSysWOW64
 -> OSが自動でリダイレクトしてくれる。
 -> 混乱するかもしれないので32ビットWindows環境も視野に。

5章 IDAによる逆アセンブル

IDAの機能説明がモリモリ書いてあるので,読み返すときはこの章。

・Ghidra
 NSAが2019年に無償公開したリバースエンジニアリングフレームワーク
 -> 最近(20221104頃)よく見るよね。
 -> 本書では使わないよ。

マルウェアはWindowsAPI関数をよく利用する。
 -> ファイルシステム,プロセス,メモリ,ネットワーク通信など。
 -> 基礎は大事ということ。

・代表的なDLL
 1. Kernel32.dll
  プロセス,メモリなどのシステム操作関係。
 2. Advapi32.dll
  サービスとレジストリに関連。
 3. Gdi32.dll
  グラフィック関連。
 4. User32.dll
  WindowsUIコンポーネントの作成・操作関係。
  キーロガーとかに使われやすい。
 5. MSVCRT.dll
  C言語標準ライブラリ。
 6. WS2_32.dll/WSock32.dll
  ネットワーク通信関連。
 7. WinInet.dll
  HTTP/FTPプロトコルとのやり取り関係。
 8. Urlmon.dll
  MIME-Type処理とWebコンテンツのダウンロードなど。
  WinInetのラッパー。
 9. NTDLL.dll
  WindowsネイティブAPI関数。
  ユーザモードやカーネルモードのプログラムインタフェースとして機能する。
  文章化がされていないが,マルウェア開発者はよく使うらしい。

ハンガリアン記法
 変数前にデータ型の略語を付ける記法。
 -> DWORD dwTest;

・WindowsAPIに付いてるAとかWとか
 AはANSI文字列,WはUnicode文字列。
 -> CreateFileA関数はANSI文字列を入力として受け取る。

・接尾辞のEX
 互換性のない関数を更新した時に付く。
 RegCreateKeyEx(RegCreateKeyの拡張版)

・命令の書き換えは同じバイトに調整する。
 -> 少ない場合はNOPでパディング。
 -> 多い場合は後ろの命令を上書きするしかない。

6章 マルウェアデバッグ

x64dbgの使い方も説明している。

・ステップインとステップオーバ
 ステップインは関数内部まで行く。
 ステップオーバは関数全体をスキップする。
-> 違いを答えられるようにしよう。

・ソフトウェアブレークポイント
 指定アドレスをINT3命令などのソフトウェアブレークポイント命令に置換する。
 -> 無制限に設定できる反面,ブレークポイント命令探知されやすい。
 -> デバッガの挙動を変更(妨害に近い?)できる(INT Scanning)

・ハードウェアブレークポイント
 CPUデバッグレジスタを利用する。
 -> x86ならDR0~DR7
 -> 命令置換はないが,デバッグレジスタ内の値に基づいて中断を判別。

・メモリブレークポイント
 命令がメモリへアクセスしたときに停止する。
 -> メモリ内に存在するデータへどの命令がアクセスするのか特定したり。

・条件付きブレークポイント

・dnSpy
 .NETアプリケーション用解析ツール。

7章 マルウェアの機能と持続性

ダウンローダ
 インターネットから部品をダウンロードして実行するプログラム。
 -> UrlDownloadToFile関数をShellExecute関数などと組み合わせる。

・ドロッパー

キーロガー
 -> GetAsyncKeyState関数やSetWindowHookEX関数によるHookが主流。

・リムーバルメディアによる複製
 Autorunの利用(デフォルトでは禁止されているはず)
 -> GetLogicalDriveStringsAとCopyFileAを利用するなど。

・APT1 WEBC2-DIVバックドア

・攻撃者はSNSクラウドストレージサービスなどをC2通信に利用する。
 -> 悪意があるのか判別が難しい

PowerShellは悪用されやすい(cmdletなどが...)
 Post-Exploitationフェーズなどで間接的に呼び出すなど。
 -> メモリから直でコードを動作させたりする。
 -> 標準では安全機構として.ps1は実行できないようになっている(はず)

・持続性メカニズム(Persistence)
 再起動しても再感染なしに攻撃コードを実行する的な概念(?)
 01. RUNレジストリキー
 02. スケジュールタスク
 03. スタートアップフォルダ
 04. Winlogonレジストリ
 05. Image File Execution Options
 06. アクセシビリティプログラム
   -> ログインしてなくても使える(Shift5回のアイツとか)
 07. AppInit_DLLs
   -> セキュアブート化で無効になっているはず。
 08. DLL Search Order Hijacking
 09. COM hijacking
 10. サービス

8章 コードインジェクションとフッキング

仮想メモリ
 OSのメモリマネージャが提供する仮想的なメモリ。
 -> プロセスは独自のメモリ空間を持っているように見える。
 -> プロセスメモリとカーネルメモリに分離される。

・プロセスメモリ
 1. プロセス実行ファイル
 2. DLL
 3. プロセス環境変数
 4. プロセスヒープ
 5. スレッドスタック
 6. プロセス環境ブロック(PEB)
カーネルメモリ
 1. hal.dll(The Hardware Abstraction Layer)
 2. ntoskrnl.exe
 3. win32k.sys

・CreateToolhelp32Snapshot関数
 実行中のプロセスのスナップショットを取得する。
 -> Process32First/Process32Nextと大体セット。

・リモートDLLインジェクション
 LoadLibrary関数経由でプロセスメモリ空間へDLLをロードさせる。
 1. PID取得(ハンドルを開く)
 2. 対象にメモリ割り当て
  -> VirtualAllocEx関数
 3. 割り当てたメモリにDLLパス名をコピー
  -> リモートスレッドを作成し,LoadLibraryを呼び出す。
  -> GetModuleHandleA関数などでkernel32.dllのベースアドレスを取得したり。
 4. CreateRemoteThread関数を呼び出し,対象にスレッドを作成
  -> LoadLibrary関数を実行するため。
  -> NTCreateThreadEx関数もある(No文書)
 5. VirtualFreeとCloseHandleで後片付け

・同程度の整合性レベルでプロセスにコードを挿入可能
 -> 特権権限ならそれ相当が必要。

APCインジェクション
 非同期プロシージャコール(Asynchronous Procedure Calls,APC)で
 対象プロセスにDLLを読み込ませる。
 1. OpenThreadで対象のスレッドへのハンドルを開く
 2. QueueUserAPCでスレッドのAPCキューへAPC関数を入れる

・SetWindowsHookExによるDLLインジェクション

・Shimを悪用したDLLインジェクション
 DLL挿入目的でShimを作成する。

・リモート実行ファイル/シェルコードインジェクション
 直にプロセスメモリへコードを挿入する。
 -> DLLの配置が不要。
 1. OpenProcessでプロセスへのハンドルを開く
 2. VirtualAllocExで特定アドレスへメモリ割り当て
 3. WriteProcessMemoryで割り当てメモリへ実行ファイルを書き込む
 4. CreateRemoteThreadしてエントリポイントのアドレス指定

・プロセスハロウイン
 正当なプロセスの実行セクションを別の実行ファイルへ置き換える。
 -> FWやIPS検知を回避する目的。
 -> VirtualAllocEXなどを避けてNtMapViewOfSectionなどを利用する場合もある。

・IATフッキング
 関数ポインタの交換に依存。

・インラインパッチ
 トランポリン

9章 難読化手法

・難読化理由
 C2通信の秘匿,IPS検知回避,表層解析対策など。

・単純エンコード
 BASE64エンコード,XOR暗号化など。
 -> 実装が簡単かつリソースが少なくて性能も十分なためよく使われる。

・シーザ暗号
 暗号: (i + key) % (length of CHARA)
 複合: (j - key) & (length of CHARA)

BASE64エンコーディング
 暗号対象を3バイト(24ビット)ずつ分解し,
 そのビット列を連結したものを6ビットで構成してテーブルに当てはめる。
 -> 3バイト入力を4文字に変換する。
 -> 性質上,エンコード文字列の長さは4の倍数。

・XORエンコーディング
 ・シングルバイトXOR
  各文字に対してKeyを固定してXOR。
  キー長は1バイト(実質,暗号化出来てない)
  -> ブルートフォースで突破されやすい。
  -> NULL(0x00)バイトでキーが割り出される(何も考えてないと直ぐばれる)
 ・マルチバイトXORエンコード
  32ビットのキーとかでエンコードする。

マルウェアの暗号化
 暗号インジケータ
  1. 暗号化関数へ参照する文字列やインポート
  2. 暗号化定数
  3. 暗号化モジュールに利用するユニーク命令

・Signsrch
 暗号シグネチャ検索などの便利ツール。

・FindCrypt2

10章 メモリフォレンジックを利用したマルウェアハンティング

ツールを利用した解析方法について解説されている。

・メモリフォレンジックの手順
 1. メモリイメージ取得 => メモリダンプ
 2. メモリ解析

11章 メモリフォレンジックを利用した高度なマルウェア検出

・PAGE_EXECUTE_READWRITE
 他プロセスから介入されないようにする保護属性

・仮想アドレス記述子(VSD)

・コードインジェクション検出
 PAGE_EXECUTE_READWRITE保護を持つメモリ領域を探索して,
 内容を検証すればよい。

カーネルモードコード署名
 カーネルモードドライバをメモリに読むためにはデジタル署名を必要としている。
 -> ルートキットによるカーネルドライバのインストール対策。
 -> bootkitで回避しようとしてくる。

・bootkit
 OSが読まれる前(システム起動プロセス初期段階など)で感染させる。
 -> カーネルサードパーティ製ドライバの脆弱性をつく。

カーネルモジュール


感想

エンジニアなのにマルウェアは「得体のしれない怖いモノ」としか認識していなかったのですが,本書を読んだことでマルウェアも「プログラム」って認識にレベルアップしました。

マルウェアの挙動を知ることで間接的にシステム層寄りについても理解を深めることが出来たので良かったです。

読み物としても面白いので何度か読み直したいな~

「C言語によるオブジェクト指向プログラミング入門」を読んだ

こんにちは

C言語によるオブジェクト指向プログラミング入門」を読んだレビュー,感想,備忘録的なのです。

C言語しか知らない7年前(2015年)に購入して,本棚で冬眠させていました。
(授業がC言語だったのに,この本を買った翌週にPythonやらされて萎えた思い出。)

積み本消化も兼ねて...

Amazon

初版の物理本で読みました。

対象読者

本書の「はじめに」にも詳しく書かれている。
・昔からC言語を使用していて,C++などの知識がない人。
・学生時代にオブジェクト指向型の言語を学んだが,実務でC言語しか使えなくて八方塞がりの人。

個人的に下記をプラス。
C++Javaなどのオブジェクト指向型言語で書けばオブジェクト指向になると思っている人。
C言語オブジェクト指向が出来ないと思っている人。
・ライブラリ開発に関して知りたい人。
・プログラミングの基礎は学んでいるので入門書以外でC言語を学びたい人。

学生時代の課題は「C言語でよろしく」な環境の人にもオススメ。
あと,組み込みメインの人(むしろこの人たちが対象な気がする)。

あくまでC言語オブジェクト指向を学ぶのが題材なので, オブジェクト指向そのものを学びたい人は別の書籍が良いかもしれません。

ライブラリ開発についても触れているので,ライブラリ開発に興味がある人にもおススメ。
-> 題材はC言語ですが,基本的な考え方はどの言語でも同じはず。
-> gccコンパイルオプションやMakefileの詳細についても書かれてる。
-> 静的リンク,動的リンクもサンプルを通して理解できる。

C言語のキャストやポインタをあまり理解していない人にもおススメかも。
-> リファレンスカウンタの話題などがあるので勉強になるはず。

MacWindowsでなくLinuxベースで解説されている。
-> シグナル処理にも触れてる。


メモ

ざっくり。
のはずが。。。

はじめに

C++Javaなどのオブジェクト指向プログラミング言語を説明して, C言語オブジェクト指向プログラミングをする理由を説明してる。
-> 組み込み系は資産やコンパイラなどの関係からC言語をよく使ってるよね的な話。
-> C++に移行することなしに,オブジェクト指向を実現したい需要がままある。

1章でオブジェクト指向の簡単な説明。
2章で実用的なライブラリ作成の説明。
3章以降でC言語によるオブジェクト指向の説明。

より良いプログラムを書くためには,オブジェクト指向のほかに構造化プログラミングやライブラリ作成についても知る必要があるね。

第1章 オブジェクト指向とは

規模の小さいプログラムなら雑に設計しても全体を把握できるけど,規模が大きくなると修正や機能拡張が指数的に困難になるよね。
このとき,ソースコードを管理する(gitとかではない)手法の1つに「オブジェクト指向プログラミング」がある。

オブジェクト指向前夜 - 構造化プログラミング
オブジェクト指向が普及する前に主流だった開発手法で「はじめにプログラムの全体構造を考え,段階を追って細分化していく」考え方。wikipedia
プログラマのメイン作業であるプログラミングの作業分割は,大きく2つに分けて「処理別」と「機能別」が考えられる。
・ゲームの初期化を考えると,画面やサウンドの初期化など,機能別に細分化していくことが「段階的詳細化」と呼ばれる。
・構造化プログラミングの3要素は「連接」・「選択」・「反復」で,合わせて「基本制御構造」と呼ばれる。
 -> 全プログラム(アルゴリズム)は3つの基本制御構造に分解でき,これを「構造化定理」と呼ぶ。

つまり構造化プログラムとは,目的の処理に対して「段階的詳細化」を行い,「基本制御構造」のみで記述できるようになるまで分解していくことになる。
-> 基本構造が順番に並んでいるため,どの部分がどの構造にあたるか明確で理解しやすい。

ごっこゲームのソースコードを題材に,ディレクトリやファイル分割についての考え方が書かれている。
-> 「ネットワーク部分担当ね」と「全機能の初期化部分を担当ね」と言われたら後者は混乱しやすいよね。

オブジェクト指向プログラミング
オブジェクト指向の書籍には「オブジェクト指向の概念を抽象的に説明している内容」と「C++Javaなどを利用してオブジェクト指向で書く内容」が多いように思える。
-> 前者は「プロジェクトマネージャ」寄り。「委譲」とか「動的束縛」とか説明してるよね。
-> 後者は「プログラマ」向けの本だが,「オブジェクト指向の本」というよりは「プログラミング言語の解説書」に近いものが多い気がする。

オブジェクト指向という考え方は「プログラミングパラダイム」の話題なので,本来はプログラミング言語に依存しない。
-> C言語アセンブリでも理屈的には実現可能なはず。
-> classなどの「オブジェクト指向」をサポートする言語でないとオブジェクト指向が出来ない訳ではない。
-> 本来は言語に対して考えるのでなく「このプログラムはオブジェクト指向で書かれている」というように「プログラムに対して」言及する。

オブジェクト指向対応言語は下記機能でオブジェクト指向プログラミングを支援する。
(あくまで「支援」で,どうプログラムを書くかは本人次第。)
1. オブジェクト指向的な書き方を支援する文法機能。
 -> classとか。
2. 文法がオブジェクト指向に寄っているので,自然とオブジェクト指向に近づきやすい。
 -> オブジェクト指向非対応言語だと好きに書けるのでオブジェクト指向になりにくい傾向がある。

JavaC言語の比較。
Javaの場合
-> 言語仕様に強制力があり,意識しなくてもある程度は奇麗に見える。
・ファイル名はクラス名と(基本的には)一致。
・1ファイルには(基本的には)1つのpublicなクラスのみ。
C言語の場合
-> 統一的な書き方がないのでやりたい放題できる。
・書き方が自由なので,複数人開発の場合はポリシーを設定しとくのが良い。
・大規模開発の場合は,コーディング規約などを決めるコストがかかる(強制ではないが…)。

オブジェクト指向対応の言語との違い。
1. オブジェクト指向で書こうとしたときに,冗長な部分をカバーしてくれる。
 -> 継承とか。
2. 文法が自然とオブジェクト指向になりやすいようになっている。

C言語オブジェクト指向をするときに最も困難なことは「開発メンバー全員がきちんと趣旨を理解している」こと,つまり「教育」。
カプセル化してあるのにメンバ変数を無暗に書き換えるなどされては意味がない。
・きちんと理解していないと「この部分はオブジェクト指向だが,あの部分は違う」などのキメラ化が進む。

オブジェクト指向を1言で言うと「分類の仕方」となる。
(以下,本書の鬼ごっこプログラムのファイル分割の例。)
構造化プログラミングの場合
・init.c: 各種初期化ルーチン
 display, sound, keyboard, playerなど。
・game_main.c: ゲームメインルーチン
 player_move, rabit_move, catch_monsterなど。
・done.c: 各種終了処理のルーチン
 display_done, sound_done, player_doneなど。
オブジェクト指向の場合
・display.c: ディスプレイ関連
 display_init, display_doneなど。
・sound.c: サウンド関連
 sound_init, sound_doneなど。
・player.c: プレイヤー関連
 player_init, player_move, player_doneなど。

オブジェクト指向の最大の特徴は下記の2つ。
1. オブジェクト単位に分類するので,コード保守がオブジェクト単位。
2. オブジェクト単位に分類するので,機能(オブジェクト)を動的に切り離しやすい。

オブジェクト指向の大きな効果は「プログラム中のデータをオブジェクト単位であつけるので,直感的に理解しやすいプログラムが書ける」ようになること。

第2章 ライブラリの作成

プログラム開発の大部分はライブラリ開発で,オブジェクト指向においては「クラスライブラリ開発が開発工数の大部分を占める」ことになる。
この章ではオブジェクト指向の準備としてライブラリ作成について触れる。
-> ライブラリの速度性や誤差,チューニング,共有ライブラリについても触れているので結構本格的。
-> マクローリン展開によるsin, cosの実装方法についても解説されているので興味があれば読んでみて。

高速三角関数ライブラリ
sin関数, cos関数を題材としてライブラリ作成について触れる。
-> 数学ライブラリに含まれるsin, cosは高い精度まで計算している。 反面,ゲームなどの精度より速度を重視する場合は独自高速化されていることが多い。
-> 配列に予め計算したものを詰め込む(LUTかな)版と線形補完版を作成する。

条件的な奴
・与える角度の有効数字が3桁程度なので,戻り値も3桁程度にする。
・必要メモリサイズは32ビットマシンでsizeof(int)*360=720バイトの想定。

sin関数
・sinX = sin(180-X)
・sinX = -sin(180+X)
の定理を使うので,第1象限の0から90度の領域分の配列で第2~第4象限までカバーでき, メモリも約1/4に節約できるが速度が犠牲になるので360の配列を1周分として,コンパイル設定で1/4周分を切り替えられるようにする。

cos関数
・cosX = sin(90+X)
の定理を利用する。

角度intで360個のsinを値を前もって計算して配列に押し込む版
-> 数学ライブラリを利用して計算するのとマクローリン展開で独自計算があるが,独自計算の方を利用する。
・初期化処理について。
 -> sin関数, cos関数の呼び出し時に初期化済みかのチェックを行う。
 -> 初期化を行うsin関数と初期化を行わないsin関数を作成し,関数ポインタで動的に切り替えると初期化判定コストが安くなる。

typedef double (*sin_func_t)(const int);
static sin_func_t fastSindFunc = FastSindInit;

static double FastSindNoInit(const int degree)
{
    return (sin(degree * (M_PI / 180.0)));
}

static double FastSindInit(const int degree)
{
    // ここで初期化処理。

    fastSindFunc = FastSindNoInit;
    return FastSindNoInit(degree);
}

static fastSind(const int degree)
{
    return fastSindFunc((degree));
}

ライブラリの構成
fastsin - include - fastsin.h
   - configure.h
   - init.h
   - init.c
   - fastsin.c
   - Makefile

・パブリックヘッダファイル
ユーザがライブラリ利用時にインクルードするための外部に公開するヘッダファイルのこと。
-> fastsin.h が該当。
-> ライブラリ仕様書の役割もあるため,ライブラリ関数のプロトタイプ宣言やマクロ,構造体の定義などを行う必要がある。
-> ライブラリ中で内部的に利用している関数などは含めてはいけない(fastsin.hに書かれているもののみユーザは利用できる)。

一度公開したものは安易に仕様変更してはいけない。
-> パブリックヘッダファイルの変更はライブラリ仕様変更とほぼ同義。

・設定用ヘッダファイル
ライブラリのビルド時設定など,ライブラリ中のソースコード全体から参照されるような定義を行う。
-> configure.h が該当。
-> 設定可能なパラメータ類などはこのヘッダにまとめておくとよい。

・プライベートヘッダファイル
ライブラリ内部の閉じた環境で利用されるもので,ライブラリファイルの分割時に出来たヘッダファイルのこと。 -> init.h が該当(init.cはユーザに対して公開するものではないため)。
-> configure.h も該当する。

・ライブラリ本体
ライブラリの実装が書かれているファイル。
-> fastsin.cとinit.c が該当。

Makefile
ライブラリのビルドとインストールに使用されるmakeコマンドに必要なファイル。
-> ライブラリのビルド方法とインストール方法のマニュアル的な役割も持つ。

ライブラリのリスト
・fastsin.h
パブリックヘッダファイルでユーザに公開されるので,コメントを丁寧に入れる。
-> 多重インクルード対策を忘れずに。

・configure.h
マクロ定数などの定義はこのファイル。
-> ユーザも設定できるため,コメントはなるべく入れる。
-> make時に設定できるようにしておく。
-> #error でパラメータ設定ミスを通知してあげられるといいね。

#ifndef SHORT_INT_ARRAY  
// #define SHORT_INT_ARRAY
#endif

#ifndef FINE_DEGREE
#define FINE_DEGREE(360*1)
#endif

とあるとき,

$ make CFLAGS="-DSHORT_INT_ARRAY -DFINE_DEGREE=3600"

$ gcc -c fastsin.c -DSHORT_INT_ARRAY -DFINE_DEGREE=3600

などでコンパイル時に定義できる。

・init.h/init.c
ライブラリ実装者が利用するものなので裁量はおまかせ。
-> #includeの<>でくくったファイル名は,/usr/includeなどから検索される。
-> ダブルクォーテーションの場合は,カレントディレクトリから検索され,無ければ標準ヘッダの格納場所を検索。

sin(0)とsin(360)は同じ結果だが,剰余計算は一般的には時間がかかるのでメモリを1つ余分に確保する方を選択していた。
-> 高速化とメモリはトレードオフ関係になりやすい気がする。

・fastsin.c
マクロであっても公開する必要がないものは.cファイルの先頭部分に定義している。

インライン関数にすることで,関数内処理が関数呼び出し箇所に直接埋め込まれるので高速。
-> 関数コール時のスタック操作コストが無くなり高速化する。
-> 複数箇所から呼び出されている場合は,同じ処理が該当箇所に埋め込まれるためオブジェクトサイズが増大する。 また,キャッシュの利用効率が悪化するため,逆に低速になる場合もある。
-> インライン関数内で定義しているローカル変数用にスタックは消費されることもある。
-> 長期運用してく過程でいろいろな場所で呼び出されるようになるとボトルネックになる可能性があるので,濫用は注意。

Makefile
Makefile上で定義したパラメータは,そのMakefileが利用される場合のみ有効。
-> Makefileのあるディレクトリ内のみで有効なので,ディレクトリ外からのインクルードなどによりヘッダファイル内の定義の整合性が取れなくなる可能性がある。
また,構造体のサイズが変わったことによるメモリ破壊など,わかりにくいバグの原因になることがあるので注意。

がっつり紹介されているので気になったら読んでね。

ライブラリアーカイブ
ライブラリはコンパイル済みのオブジェクトファイル形式で提供することが可能。
-> ライブラリが巨大化するにつれ,オブジェクトファイルの数が増大するため,コンパイル済みのオブジェクトファイルを全て提供するのは現実的でない。

通常,いくつかのオブジェクトファイルを1つにまとめたアーカイブ形式で提供する。
-> Unixでは/usr/lib や /usr/local/lib をみると libc.aやlibcurse.aなどのファイルが大量に存在している。

アーカイブ形式でまとめられたライブラリを「ライブラリアーカイブ」と呼ぶ。

ライブラリのインストール

install:    all
    mkdir -p $(INCLUDE_DIR)
    cp $(INCS) $(INCLUDE_DIR)
    cp $(TARGET) $(LIBRARY_DIR)

uninstall:
    rm $(LIBRARY_DIR)/$(TARGET)
    rm -fR $(INCLUDE_DIR)

$ make installを実行することで,ヘッダファイルをINSTALL_DIR,INCLUDE_DIRで指定したディレクトリにインストールする。
また,libfastsin.aをINSTALL_DIR, LIBRARY_DIRで指定したディレクトリにインストールする。
-> /usr/local 以下の適切なディレクトリにヘッダファイルfastsin.hとライブラリlibfastsin.aがインストールされる。

#include <fastsin/fastsin.h> 

を行い,リンク時に-lfastsinオプションを使用することでfastsinライブラリを使用できるようになる。

コンパイラによっては/usr/local 以下がヘッダファイルやライブラリの検索対象に含まれていない場合もある。

$ gcc -I/usr/local/include ...

のように-Iオプションで検索対象に含めることができる。

$ gcc -L/usr/local/include ...

のように-Lオプションでライブラリの検索対象を追加できる。

Unixは一度インストールしたものは削除しないという考え方があるため,ソフトウェアによってはアンインストール用のターゲットが存在しない場合もある。
-> ユーザの立場で考えるとアンインストール用のターゲットは用意しておくのが良い。

init.h などのプライベートヘッダファイルは$ make install でインストールされるべきではない。
-> ユーザが必要とするもののみインストール(公開)する」という考え方。

評価
sin関数,cos関数の性能評価。
Makefileの活用法なども解説されているが,基本的には数学よりの話なので軽く流す。
-> コンパイルオプションに最適化設定についても言及されている(結果込み)。

性能評価時はキャッシングの効果や最適化,ページング,ホットスポットなどを考慮する必要がある。
-> キャッシング効果は絶大。
-> ページングは,仮想メモリ機能により特定サイズ単位でメモリ上のデータをハードディスク上に退避・復旧する動作。
-> ホットスポットは,処理時間の大部分を占めている箇所。

雑に結果だけ抜粋。
・fastsind: 1.40秒
・fastsinf: 4.76秒
・数学ライブラリsin: 12.70秒
-> 本書では誤差についても書かれている。

高速化のヒント
1. 配列サイズを360->512にして,剰余演算子をマスク計算に置き換えてアクセス。
2. 初期化用関数を作成し,明示的に初期化して初期化済みフラグ参照を無くす。
3. 「角度は負の値にならない」を前提とすることでfast_sinf()のifを減らす。
4. 条件分岐を減らす。

結構詳細に書かれているので興味があれば。
-> 沼に片足突っ込むレベルで書かれてた。

共有ライブラリ
作成したライブラリは一般的に「静的ライブラリ」と呼ばれる。
-> 共有ライブラリで配布されるのが一般的。
-> 共有ライブラリはハードディスク,メモリなどの資源に対して高効率。

静的ライブラリは,実行形式の作成時に本体がリンクされ,実行時に結合される。
-> 静的リンク。
-> 実行形式に埋め込まれるためファイルサイズが大きくなる。

共有ライブラリは,実行形式のリンク時にどの共有ライブラリがリンクされるかという情報が埋め込まれる。
-> 実行時に,実行形式とは別に共有ライブラリがロードされる。
-> ハードディスク的には共有ライブラリ本体が1で十分なので,ディスクページ節約となる。
-> 実行時に仮想メモリシステムによって,テキスト領域などの共有できる部分は複数プロセス間で共有されるため,実行時メモリサイズも節約できる。
-> 実行時に動的リンクされるため動的ライブラリとも呼ばれる。

動的ライブラリと共有ライブラリは厳密には意味が異なる。
-> 動的ライブラリは,ハードディスク上のサイズを節約する。
-> 共有ライブラリは,使用メモリを節約する。
-> 共有ライブラリの実現には動的リンクが不可欠で,実際には似た意味で使われる。

共有ライブラリはOSに依存する部分が多いため注意。
-> .dllとか.soってコト!?

Cファイルのコンパイル時には「位置独立コード」を作成する必要がある。
-> $ gcc -c fastsin.c -fpic -O -Wall の-fpicのこと。

共有ライブラリのコンパイル方法,Makefile,インストールについて詳細が書かれているが長いので省略。

まとめ
・ランダムアクセスして性能評価したけど,値が順次参照する用途だと速いかもね。
 -> グラフィックスの円描画時の座表計算など。
・メモリに値を確保しているから,ページングが発生すると遅くなるかも。
・精度,メモリ量,速度のトレードオフがあるよ。
 -> ユーザが調整できることも大切。
 -> ハッシュを利用すると速度と使用メモリ量のトレードオフを調整できるよ。

第3章 オブジェクト指向に必要な概念とC言語による実装

「抽象化」,「カプセル化」,「継承」,「多様性」の4つに加え,「動的なオブジェクトの生成」についても説明される。
-> C言語オブジェクト指向のサポートがないので,実装者がオブジェクト指向を意識して書く必要がある。
-> プログラムの題材はシューティングゲーム(CUIベースなので面倒なことはない)。

その1. 抽象化
プレイヤーや弾丸などのあるひとまとりのデータのことを「オブジェクト」と呼ぶ。
-> 利用者が実際の処理を気にすることなくオブジェクトを扱えるようにすることを「抽象化」と呼ぶ。

新しく作成した抽象型のことを「クラス」と呼ぶ。
-> 本書では構造体をクラスみたいに扱っている。
-> 変数に相当するクラス実体を「オブジェクト」と呼ぶ。
-> 実体は「インスタンス」と呼ぶ。
-> メモが雑で訳わかんなくなりそうなので,C++のクラスをイメージしておけばよい。

その2. カプセル化
オブジェクト指向プログラミングでは,クラスはソースコードを構成する1単位として扱う。
-> 他クラスとはっきり独立させる必要がある。
-> あるオブジェクトを操作したい場合は,勝手にメンバを弄るのでなく,オブジェクトに「命令」を送信する。
-> オブジェクトに送信する命令を「メッセージ」と呼ぶ。

オブジェクトの持つメンバ変数は隠ぺいする。
-> 直接弄るのでなく,メッセージを介して操作する。
-> オブジェクトの独立性を高めることを「カプセル化」と呼ぶ。
-> C言語でいうところの「構造体」によってオブジェクトを抽象化して,構造体操作用の関数(メソッド)を追加したもの。

オブジェクトの独立性を高めることが「カプセル化」の目的である。
-> 内部メンバを隠蔽することが目的ではない。

クラス定義をライブラリ化したものは「クラスライブラリ」と呼ばれる。
-> オブジェクト指向プログラミングは,ひたすらクラスライブラリを作成していくこととも考えられる。

その3. 動的オブジェクトの生成
C言語でクラスを作成するときのイメージ↓
-> classがないのでこんな感じになるはず。

#ifndef PLAYER_H_INCLUDE
#define PLAYER_H_INCLUDE

typedef struct Player_ * Player;

int PlayerGetX(Player* const player);
int PlayerGetY(Player* const player);
int PlayerGetVX(Player* const player);
int PlayerGetVY(Player* const player);

void PlayerInit(Player* const player, const int x, const int y, const int vx, const int vy);
void PlayerMove(Player* const player);
void PlayerDone(Player* const player);

#endif // PLAYER_H_INCLUDE

相互インクルードは意識しよう。
-> 後に解説されるみたい。

Player.cで構造体の定義を行い,Player.hでは構造体へのポインタを定義してクラスの使用時にポインタを利用する。
-> C言語では未定義の構造体へのポインタを定義できることを利用する。
-> C言語での隠蔽実装的な奴だと思う。

未定義の構造体へのポインタ代わりにvoid*を利用できるが,コンパイラの型チェックを捨てることになるので非推奨。
-> 将来的にポインタがらみのバグに悩まされるかもよ...

その4. 継承
シューティングゲームに弾丸を継承した誘導ミサイルを実装することを考える。
-> C言語には継承機能はないので,リストを利用して継承を再現している。

構造体の先頭に別の構造体を配置して,ある構造体の仕組みを継承させる。
-> リストを継承した場合,リンクリストを操作する関数の追加は,struct listに関するものだけ作成すればよい。
-> PersonやCarはstruct list構造体を継承していると考えられる。
-> 構造体の先頭に別の構造体を配置する手法は,C言語ではよく使われる。

typedef struct Person_
{
    struct list list;

    const char* m_name;
    int m_bloodType;
} Person;

typedef struct Car_
{
    struct list list;

    const char* m_name;
    int m_type;
} Car;
Person* p;
Person person;

insertList((struct list*)p, (struct list*)(&person));

外側の構造体へのポインタで,内側の構造体へアクセスすることで,struct TypeBをstruct TypeAに継承させている可能性があることに注意。

struct Type A
{
    struct Type B
    {
        int value;
    }
};

struct TypeA* type_a =;
((struct TypeB*)type_a)->value = 0;

実体ごとにサイズを変更できるような場合は注意。
-> コメントで構造を明記しておくのが良い。

struct String
{
    int length;
    char string[1];
};

void Create(char *string)
{
    struct string* stringObject;
    ...
    stringObject = (struct string*)malloc(size);
    ...
}

誘導ミサイルの場合はこんな感じ。

typedef struct Missile_
{
    Shot shot; // 継承

    int m_quick;
    Player m_target;
} Missile;

プライベートヘッダを作成しても文法的にアクセスを抑止できない点に注意。
-> ShotP.h(PrivateのP)とShot.h(公開用)を用意しても,ShotP.hをインクルードされてしまうことは防げない。
-> コーディング規約は大事だね。

コンストラクタとデストラクタに相当する関数は意識して作成する必要がある。
-> 文法的に機能がないため。

has-a関係とis-a関係
-> 動作的には同じだが,考え方に大きな違いがあるので注意。

ShotGetStruct(&(missile[i]->Shot)); // has-A
ShotGetStruct((Shot)(missile[i]));  // is-a

その5. 多態性
キャストでShotクラスを継承したMissileクラスを実現し,Shotクラスのメソッドを利用できた。
-> ShotクラスからMissileクラス操作(逆)はできるだろうか。

Shot shot;
MissileMove((Missile)shot);

C言語の立場
shot変数のメモリ領域はShot構造体の分しか確保されていない。
-> コンパイルは通るが,不正メモリアクセスとなる。
-> Segmentation faultは起こらず,関係ないメモリ領域を参照するはず。
オブジェクト指向の立場
MissileはShotの1種だが,ShotはMissileの1種ではない。

C言語的にもオブジェクト指向的にも下記コードは正しい。

Shot p;
if (key == SHOT) p = CreateShot();
else p = (Shot)CreateMissile();

リストなどでオブジェクトを一般化して扱おうとするとダウンキャストのような問題が発生する。
-> C++では仮想関数があるので問題ない。
-> C言語では仮想関数の仕組みを実装する必要がある。
-> 関数ポインタをうまく利用する。

typedef struct Shot_
{
    int x, y;
    void (*moveMethod)(struct Shot_* shot);
    void (*destroyMethod)(struct Shot_* shot);
} Shot;

void ShotMove(Shot shot)
{
    (*(shot->moveMethod))(shot);
}

int main()
{
    ...
    // 弾丸だろうがミサイルだろうが同じ関数で処理できる。
    for (p = listHead; p != NULL; P = p->next)
    {
        ShotMove(p->shot);
    }
    ...
}

関数ポインタは使い方によってはプログラムがきれいになるが,関数呼び出し部分を見ただけでは実装が見えない。
-> デバッグが面倒(動作させないとわかりにくい)。
-> 適切な動作を行ってくれるのでクラス間の違いを意識する必要がない(ShotMove)。

まとめ
オブジェクト指向考え方の基礎は「モノ」を「モノ」として扱うこと。

その他の話題
その他の話題にしては濃い内容。

・バックポインタ
ほかのオブジェクトから参照されているにも関わらずオブジェクトを消去すると。存在しないオブジェクトへの参照が発生する。
-> 対処法1: オブジェクトにバックポインタを持たせる。
-> 対処法2: オブジェクトにリファレンスカウンタを持たせる。

参照先から参照元を逆引きするようなポインタを「バックポインタ」と呼ぶ。
-> 削除するオブジェクトへの参照を潰しておく機能を削除オブジェクトのメソッドに組み込んでおく。

・リファレンスカウンタ
バックポインタによる対処だとオブジェクト間の参照構造が複雑になった場合にデッドロックの原因になりやすい。
-> バグ原因を発見してもオブジェクト消去のタイミングを調整する必要があり,修正が困難になりやすい。

オブジェクトにカウンタを持たせ,カウンタを増減させる方式を「リファレンスカウンタ」と呼ぶ。
-> 参照先のオブジェクトのカウンタを増やし,参照関係が無くなったときにカウンタを減らす。
-> オブジェクトを削除する際はカウンタが0になっているか確認する。
-> 削除時にカウンタが残っている場合は未動作フラグなどを立てて動作しないようにしておく。

実装が簡単で動作が確実なため,オブジェクト指向以外でもよく使用される。

・どこまでをクラスにするのか
オブジェクトが相互にメッセージをやり取りするように実装する。
突き詰めるとmain.cの記述内容は下記の通りかな。
コマンドライン引数の処理
・シグナルの設定
・すべてのオブジェクトの親となるオブジェクトの生成と消去

どこまでをオブジェクト指向で書くかは設計者の自由。
-> 闇雲に全てをクラス化しようとしても複雑になるだけ。
(一生完成しない神システムになるかもしれないしね)
-> 複数の書き方を組み合わせて,適所で応用する力のほうがずっと重要。

・シグナル処理について
Unix環境で動作するアプリケーションは,大体シグナル処理を入れる。
-> Ctr-cやkill -INT [pid]などでプロセスにシグナル送信して強制終了。
-> 何も考えずにexit()しても,メモリ解放はOSが適切に行ってくれると思うし。

組み込み機器用のOSは貧弱な場合が多いため,アプリ側で責任をもってメモリ解放する必要が多い。
また,きちんとした終了処理を行いたい場合も多々あるはず。
・終了処理を必要とするライブラリを使用しているため(cursesライブラリなど)。
・OSが貧弱でメモリ解放処理はアプリ側に一任されている。

シグナル処理には2つ方法がある。
1. シグナルハンドラ内部で全親のオブジェクトを消去。
2. シグナル受信時にフラグを立て,プログラム中ではフラグを見てbreakする。

ハンドラ内部でメモリ解放する。

void signalHandler(int value)
{
    if (objectがNULLでない) DestroyAllObject();
    exit(1);
}

int main(void)
{
    signal(SIGINT, signalHandler);
    signal(SIGTERM, signalHandler);

    CreateObject();
    gameMainLoop();

    exit(0);
}

オブジェクト生成中にシグナルハンドラが呼ばれた場合は?
-> メモリ確保中なのでオブジェクト消去が行われず,メモリ解放されない。
-> malloc()内部でシグナルが発生するかも。
-> シグナル処理の根源的な問題。

シグナル処理は「どこで呼ばれるかわからない」を意識する必要がある。
-> フラグを利用した方法が一般的に利用されている。

フラグを利用したシグナル処理。
-> グローバル変数のフラグをシグナルハンドラ内で立て,フラグが立っている場合は終了させる。
-> いつシグナルが発行されるか意識する必要がない。
-> 対処漏れが発見しやすい。

int terminated = 0;
void signalHandler(int value)
{
    terminated = 1;
}

int main(void)
{
    signal(SIGINT, signalHandler);
    signal(SIGTERM, signalHandler);

    while(true)
    {
        if (terminated) break;
    }

    exit(0);
}

シグナル処理の難しさはタイミング依存のバグが多いこと。
-> 大体再現できない。

・構造体の先頭メンバは本当に先頭にあるのか?
Shotクラスを継承したMissileクラスは,Missile構造体の先頭にShot構造体を置いていた。
-> 構造体の先頭メンバは先頭に配置されていることを前提としている。
-> 構造体の先頭に空白領域を作るコンパイラだとヤバいが,大体大丈夫なはず。

構造体の定義時のメモリ配置方法は,Application Binary Interface(ABI)という仕様で決められている。
-> 異なるコンパイラで作成したオブジェクトファイル同士でも相互リンクできるようにするため。

CPU毎に仕様は変わるが,構造体の先頭メンバはメモリ上で先頭に置かれること,構造体のメンバのメモリ配置はメンバ定義順に一致することは多くのABIで保証されているよう。

・is-aとhas-aの関係
C言語では完全な隠ぺいが出来ない。

場合によってはis-aよりhas-aの方がよい場合もある。
-> プライベートヘッダの意味はなくなるが,実装は楽。

時と場合に応じて臨機応変に対応すべし。

・相互インクルードの問題
相互インクルードの対策は下記が主流。
1. 各種ファイルのインクルードやポインタ定義の順番を調整。
2. typedefs.hのようなファイルを作成し,ポインタ定義など,よく使われそうな型を定義する。
3. 相互インクルードする際に最初に定義が必要なものはインクルードガードの外側に出しておく(インクルードとインクルードで挟むやり方)。

だいたい1か3のやり方が採用されやすい。
-> 個人的には1がいいかな。

第4章 オブジェクト指向プログラミングの実装例(設計編)

実際のプログラミング時に発生しやすい相互インクルード,隠蔽性,メモリ開放漏れ,エラー処理などについても解説されてる。
クラス作成の際のオブジェクト間関係についての考え方も載せてあった。

大体知っているので流し見。

デバッグの考慮
#defineマクロでデバッグレベル別に機能を定義する。
デバッグレベル低ではマクロの中身が空など。

第5章 オブジェクト指向プログラミングの実装例(開発編)

ごっこゲームを題材として,実際にどんな感じでプログラミングするかが解説されている。
基本的には今までの総まとめなのでメモは割愛。


まとめ

予想していたよりかなり面白い内容でした。

C言語を扱うことは中々無いですが,C++でも通ずる内容だったので良かった。

C言語メインの人は読んでおくといいかも。

「DirectX12の魔導書」を読んだ

こんにちは

「DirectX12の魔導書」を読んだ備忘録,感想,レビュー的なのです.

2年前に買って何回か読んで放置していたのですが,今なら理解できると思ってリベンジしました.

あと,積み本消化も兼ねて。

Amazon



類似書籍

DirectX12を勉強する際に以下の書籍も読みました.

技術同人誌です.
初心者でも分かりやすく解説されており,お値段も安いのでおススメです.
slash-labo.booth.pm

DirectX12というよりシェーダに特化した内容の書籍です.
エンジンコードにDirectX12が使用されているので参考になるかも.

洋書です.
英語が読めるならば一番良い書籍です.
(自分は何となくしかわかりませんでした.)

対象

  • DirectX12を触りたいけど何すればいいかわからない人。
  • DirectXについて知りたい人。
  • グラフィックエンジンを開発したい人。

忖度なく書きますが,本書は間違いが多いので丸写しでは動作しないと思った方が良いです。

お値段も高いので類似書籍が出ている現在(20220718)では少し微妙かも。。。
ただ,エフェクトやモデル読み込み回りも解説されているので,DirectX12を学ぶついでにその辺も勉強したい人はオススメ。

人を選ぶ内容だと感じたので,気になった人は書店で雰囲気を見てから購入するのを進めます。


メモ

メモは読みながら記述したものなので,清書はされておらず勘違いが含まれるかもしれないので注意.
実行結果はサンプルをちょっと変えたもの。

スペック

・Windows10 21H1
Intel Core i7-10700 2.90GHz
・RAM 16.0GB
NVIDIA GeForce GTX 1660 SUPER
・Direct3D12 12_1
Visual Studio 2019

Chapter 1 前提となる知識とDirectX12の概略

数学やレンダリングの基礎解説が主で,DirectX12の実装は次の章から.
DirectXの歴史が筆者の感想を交えて解説されていて面白い.

DirectX12の要素簡易まとめ
ディスクリプタ
 GPUに送信したデータをシェーダで使用するための仕様書.
ディスクリプタヒープ
 ディスクリプタ置き場.
ディスクリプタテーブル
 ディスクリプタヒープとシェーダのレジスタを関連付けるテーブル.
・ルートシグネチャ
 ディスクリプタテーブルをまとめる.
・パイプラインステート
 表示までの流れ.
・コマンドリスト/コマンドキュー
 字面通り.
・フェンス
 画面フリップを描画コマンドが完了するまで待つ仕組み.
・バリア
 コマンドを投げた時,リソースが何用途で使用されるか設定するもの.

Chapter 2 グラフィックスパイプラインとさまざまなシェーダー

グラフィックスパイプラインやHLSLについて。

・グラフィックスパイプラインの各ステージ
1. IA -> VS -> RS -> PS -> 出力
2. IA -> VS -> GS -> RS -> PS -> 出力
3. IA -> VS -> HS -> TS -> DS -> RS -> PS -> 出力
4. IA -> VS -> HS -> TS -> DS 0> GS -> RS -> PS -> 出力
の大体4パターン。

・セマンティクス
パイプラインに流すデータの使用目的をシステムに通知するもの。
しぇーだからは型や定数に見える(POSITIONやTEXCOORDとか)。

・レンダーターゲット
描画先。

Chapter 3 初期化から画面クリアまで

いよいよDirectX12のプログラムを書き始める。
git clone https://github.com/boxerprogrammer/directx12_samples.git
でサンプルコードは取得できるはず。

サンプルを動かしたいだけの人はDx12BookSamples.slnを開いて,Chapterに合わせてターゲットを右クリックで変更すれば良い。
実践派なのでメモはほどほどに,ここからは結果だけ。

出力は出来たけど,プログラム終了時にコンソール出力にWARNINGが出てた。

D3D12 WARNING: Process is terminating. Using simple reporting. Please call ReportLiveObjects() at runtime for standard reporting. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live Producer at 0x000001D2E97825A8, Refcount: 15. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB9594F0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB978C60, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB97E5B0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB75B480, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB88AC60, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB754190, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB7543E0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F220D670, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F223F930, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F2241E50, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F22AD310, Refcount: 7. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F2402D00, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F245B910, Refcount: 2. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F2352330, Refcount: 2. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F245E210, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F245E460, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F245E970, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24ADDF0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24B4770, Refcount: 2. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24B44C0, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24B5720, Refcount: 2. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24B5470, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24BC440, Refcount: 2. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F227C970, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24C06A0, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F2548FB0, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2F24E50B0, Refcount: 1. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING:  Live Object at 0x000001D2EB680100, Refcount: 0. [ STATE_CREATION WARNING #0: UNKNOWN]
D3D12 WARNING: Live                         Object :     28 [ STATE_CREATION WARNING #0: UNKNOWN]
DXGI WARNING: Live Producer at 0x000001D2E971AD68, Refcount: 8. [ STATE_CREATION WARNING #0: ]
DXGI WARNING:   Live Object at 0x000001D2E971D400, Refcount: 1. [ STATE_CREATION WARNING #0: ]
DXGI WARNING:   Live Object at 0x000001D2E97209C0, Refcount: 1. [ STATE_CREATION WARNING #0: ]
DXGI WARNING:   Live Object at 0x000001D2F245C800, Refcount: 1. [ STATE_CREATION WARNING #0: ]
DXGI WARNING:   Live Object at 0x000001D2F24B0DE0, Refcount: 1. [ STATE_CREATION WARNING #0: ]
DXGI WARNING:   Live Object at 0x000001D2F24B1270, Refcount: 1. [ STATE_CREATION WARNING #0: ]
DXGI WARNING: Live                         Object :      5 [ STATE_CREATION WARNING #0: ]

おそらくメモリ解放忘れ。
自分はComPtrでメモリ解放をシステムに任せたら出なくなった。

気になる人はエラーログの通りReportLiveObjects()を呼べば詳細が見れると思う。↓
多分1つ1つRelease()してけば直ると思う。

D3D12 WARNING: Live ID3D12Device at 0x00000288EF0E6C58, Refcount: 15 [ STATE_CREATION WARNING #274: LIVE_DEVICE]
D3D12 WARNING:  Live ID3D12RootSignature at 0x00000288EF11E890, Refcount: 0, IntRef: 2 [ STATE_CREATION WARNING #577: LIVE_ROOTSIGNATURE]
D3D12 WARNING:  Live ID3D12PipelineState at 0x00000288F11A8FD0, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #572: LIVE_PIPELINESTATE]
D3D12 WARNING:  Live ID3D12PipelineState at 0x00000288F13FE130, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #572: LIVE_PIPELINESTATE]
D3D12 WARNING:  Live ID3D12Resource at 0x00000288F13092D0, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #575: LIVE_RESOURCE]
D3D12 WARNING:  Live ID3D12Heap at 0x00000288F1309020, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #579: LIVE_HEAP]
D3D12 WARNING:  Live ID3D12Fence at 0x00000288F11C75A0, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #580: LIVE_MONITOREDFENCE]
D3D12 WARNING:  Live ID3D12CommandQueue at 0x00000288F11C77F0, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #570: LIVE_COMMANDQUEUE]
D3D12 WARNING:  Live ID3D12Fence at 0x00000288F7C9BF50, Name: Internal D3D12 Debug Fence, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #580: LIVE_MONITOREDFENCE]
D3D12 WARNING:  Live ID3D12CommandAllocator at 0x00000288F7CD0220, Refcount: 1, IntRef: 2 [ STATE_CREATION WARNING #571: LIVE_COMMANDALLOCATOR]
D3D12 WARNING:  Live ID3D12GraphicsCommandList at 0x00000288F7CD2740, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #573: LIVE_COMMANDLIST12]
D3D12 WARNING:  Live ID3D12CommandQueue at 0x00000288F7D33F60, Refcount: 7, IntRef: 0 [ STATE_CREATION WARNING #570: LIVE_COMMANDQUEUE]
D3D12 WARNING:  Live ID3D12Fence at 0x00000288F7EB5700, Name: Internal D3D12 Debug Fence, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #580: LIVE_MONITOREDFENCE]
D3D12 WARNING:  Live IDXGISwapChain at 0x00000288F7EE68F0, Refcount: 2 [ STATE_CREATION WARNING #275: LIVE_SWAPCHAIN]
D3D12 WARNING:  Live ID3D12LifetimeTracker at 0x00000288F7DDCCD0, Refcount: 2, IntRef: 0 [ STATE_CREATION WARNING #1164: LIVE_LIFETIMETRACKER]
D3D12 WARNING:  Live ID3D12Fence at 0x00000288F7EE95C0, Name: Internal DXGI Fence, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #580: LIVE_MONITOREDFENCE]
D3D12 WARNING:  Live ID3D12CommandAllocator at 0x00000288F7EE9810, Name: Internal DXGI CommandAllocator, Refcount: 1, IntRef: 2 [ STATE_CREATION WARNING #571: LIVE_COMMANDALLOCATOR]
D3D12 WARNING:  Live ID3D12GraphicsCommandList at 0x00000288F7EE9D20, Name: Internal DXGI CommandList, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #573: LIVE_COMMANDLIST12]
D3D12 WARNING:  Live ID3D12GraphicsCommandList at 0x00000288F7F37350, Name: Internal DXGI CommandList (debug layer indirect), Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #573: LIVE_COMMANDLIST12]
D3D12 WARNING:  Live ID3D12Resource at 0x00000288F7F3EC20, Refcount: 2, IntRef: 0 [ STATE_CREATION WARNING #575: LIVE_RESOURCE]
D3D12 WARNING:  Live ID3D12Heap at 0x00000288F7F3E970, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #579: LIVE_HEAP]
D3D12 WARNING:  Live ID3D12Resource at 0x00000288F7F3FBD0, Refcount: 2, IntRef: 0 [ STATE_CREATION WARNING #575: LIVE_RESOURCE]
D3D12 WARNING:  Live ID3D12Heap at 0x00000288F7F3F920, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #579: LIVE_HEAP]
D3D12 WARNING:  Live ID3D12Fence at 0x00000288F7F468F0, Refcount: 2, IntRef: 0 [ STATE_CREATION WARNING #580: LIVE_MONITOREDFENCE]
D3D12 WARNING:  Live ID3D12DescriptorHeap at 0x00000288F7D07310, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #576: LIVE_DESCRIPTORHEAP]
D3D12 WARNING:  Live ID3D12Fence at 0x00000288F7F46C80, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #580: LIVE_MONITOREDFENCE]
D3D12 WARNING:  Live ID3D12CommandAllocator at 0x00000288F7FCB650, Name: Internal DXGI CommandAllocator, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #571: LIVE_COMMANDALLOCATOR]
D3D12 WARNING:  Live ID3D12CommandAllocator at 0x00000288F7F66EF0, Name: Internal DXGI CommandAllocator, Refcount: 1, IntRef: 0 [ STATE_CREATION WARNING #571: LIVE_COMMANDALLOCATOR]
D3D12 WARNING:  Live ID3D12GraphicsCommandList at 0x00000288F8AA0850, Refcount: 0, IntRef: 1 [ STATE_CREATION WARNING #573: LIVE_COMMANDLIST12]

プログラム終了の前に下記コードを埋め込むと見れるよ。

    // 変数名はサンプルコードに合わせた。
    ID3D12DebugDevice* pDebugDevice = NULL;
    dev_->QueryInterface(&pDebugDevice);
    dev_->Release();
    pDebugDevice->ReportLiveDeviceObjects(D3D12_RLDO_DETAIL);
    pDebugDevice->Release();

Chapter 4 ポリゴンの表示

特に躓くとこは無いはず。

提示されているソースコードが微妙に間違っているぐらい?
(Githubのサンプルを見たほうが良い)

Chapter 5 ポリゴンにテクスチャを貼り付ける

シェーダをこねくりまわす回。
シェーダについて詳しく知りたい人はここを読み込むと良さそう。

DirectX Texが必要。
git clone https://github.com/microsoft/DirectXTex.git
本書では環境変数の書き換えで対応しているが,環境変数は汚したくないのでゴリ押した。

  1. git cloneかzipでDirectXTexを落として,DirectX12プロジェクト直下に配置。
  2. DirectXTex_Desktop_2019.slnを開いてビルド(DirectXTex.libを作るため)。
  3. DirectXTex/Bin/Desktop_2019/x64/DirectXTex.libをコピー。
    -> 人による(サンプルだと/directx12_samples/Chapter5/に配置でいけた)。
  4. 相対パスでincludeする。
    -> #include "../DirectXTex/DirectXTex/DirectXTex.h" な感じ。
    -> #pragma comment(lib, "DirectXTex.lib")も忘れずに。

Chapter 6 行列による座標変換

3DCGの基礎。
数学は軽く流して,データ設定のやり方解説がメイン。

d3dx12.hもここから使い始める。

実行結果は割愛。

Chapter 7 PMDの読み込みとモデル表示

3Dモデル表示(MMD)がメイン。
(MMDMikuMikuDanceの略らしい。)

.objなどの単純フォーマットを読み込みたい人はここを読み込むと良さそう。
法線ベクトルなどにも触れ始めてる。

Chapter 8 マテリアル(材質)

トゥーンシェーダにも触れ始める。
絵面的にも面白くなってくるころ。

Debug版でビルドしたらRuntimeLibraryが云々と言われた。

1>DirectXTex.lib(DirectXTexImage.obj) : error LNK2038: 'RuntimeLibrary' の不一致が検出されました。値 'MDd_DynamicDebug' が MTd_StaticDebug の値 'main.obj' と一致しません。

Visual Studio2019ならデバッグ>プロパティ>C/C++>コード生成>ランタイムライブラリを
マルチスレッド デバッグ DLL (/MDd)
に設定すればいけた。

Debug版なので構成タブはDebugにした。

(なんか違う気がする…)

Chapter 9 リファクタリング

ComPtrを使ってクラス化してたので特にすることは無かった。

本書のまま進んでた人はこのタイミングでライブラリを作ろう。

Chapter 10 スキニングとアニメーション

ここからDirectX12とゲーム開発の組み合わせが本格化する。
ボーン処理などの解説も少しある。

DirectX12よりはスキニングがメイン。

Chapter 11 インバースキネマティクス(IK)

ほぼIKの解説。
結構詳しく書いてあるのでこれのために本書を買うのもアリだと思う。

スクワットしてる。

Chapter 12 マルチパスレンダリング

ここからDirectX12の話に戻りつつある。

簡単なポストエフェクトを実装する。
絵面が変化するので面白い。

Chapter 13 影行列とシャドウマップ

ついに影を描写する。

先にレイトレを学んどくと理解が楽かも。

Chapter 14 マルチレンダーターゲットとその応用

だんだん実装が辛くなってくるころ(バグりまくる)。
被写界深度とかを実装する。

実装とは関係ないけど,HRESULTの結果も表示してくれるデバッガ凄い。
調べる必要ないじゃん。

Chapter 15 スクリーンスペースアンビエントオクルージョン(SSAO)

環境遮蔽の原理と実装。
DirectX12よりも数学が辛くなった。

ここはサンプルを実行だけしてスルーした。

Chapter 16 imguiの利用

昔OpenGL3.Xとimguiを使って簡単なモデルビュアー作ってたので馴染み深い。
(自分語り。↓が実行画面。)

簡単に使える+UE4などのゲームエンジンにも導入できるのでオススメのライブラリ。
github.com

FFVIIRemakeやFallout76などでも使われてるらしい。
github.com

Chapter 17 Effekseerライブラリの利用

3Dエフェクトを作成するためのツール。
結構使い方をプログラム目線から解説しているのは貴重な気がする。

Chapter 18 DirectXTexの利用(文字列表示)

日本語フォント表示などの解説。
フォントデータの読み込みなど。


感想

プログラム書くのが結構難しかったので,メモはかなり大雑把になったな~って感じ。

本文中のプログラムは結構間違えていますが, IKやimguiなどのゲーム開発に必要な脇道的な内容も載っているのでこれだけでも読み価値はあると思います。

グラフィックスプログラマを目指す人にはおススメの1冊だと思いました。
(小学生並の感想)