Dosbox 内から Dosbox を検出する方法について

2026/04/18 1:13

Dosbox 内から Dosbox を検出する方法について

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

DOSBox-X を特定する最も確実な方法は、容易に偽造可能な文字列ではなく、それ独自の特殊な x86 命令 opcode の取り扱い方式を利用することにあります。具体的には、エミュレータは

FE
で始まる特定の命令グループを実際に搭載されたハードウェアや他のソフトウェアとは異なる方法で解釈します。標準的な CPU はこれらの特定のバイト列を実行しようとする際にクラッシュしますが、DOSBox-X はその内部ロジックが未文書化のこれらのコマンドを管理するためにプログラムされているため、それらを正しく処理します。この振る舞いは、より古くの実装から PCem で引き継がれたレガシーエラーにより同様の命令を誤って取り扱ってきた 86Box の旧バージョンなど、バグの多い代替品との区別になります。BIOS ヘッダーやメモリ内の文字列をスキャンして検出するという方法と異なり、このアプローチは CPU エミュレーション層そのものを検査します。このロジックはエミュレータのコアにハードコーディングされているため、調整可能なデータテーブルに格納されているわけではなく、将来的なバージョンが動的リロケーションなどの機能を調査する際でも安定したままです。したがって、開発者や自動化されたスクリプトは、システムがこれらの特定の自定义 opcode を実行してもエラーをトリガーしないかどうかを確認することで、本物の DOSBox-X が実行中であることを確認することができ、これにより堅牢なテスト環境を確保できます。

本文

作成日:2025 年 12 月 15 日 19 時 04 分

ブログをよむような人なら、DOSBox のことはご存知ないはずがありません。DOSBox は MS-DOS エミュレータであり、それは必然的に x86 アーキテクチャのエミュレーターであることを意味します。しかし、86Box や QEMU といった一般的な x86 エミュレーターとは異なり、DOSBox では DOS 関連の要素がエミュレーションの一部として不可分です。BIOS の中断(インタラプト)や POST シークエンスは存在しますが、「メモリにマッピングされた ROM チップ」という意味での BIOS は存在しません。そもそも伝統的な意義における「DOS」自体も完全に再現されているわけではありません。しかし、DOSBox の内部で動作している限り、その点は自覚できないものです。期待される DOS API のほとんどが利用可能であり、Long File Names(長名)機能をサポートしていない古いや旧なバージョンであると報告された場合に当該機能を表示させないよう配慮が払われています。「察することを避けるべき存在」をいかにして検出するのでしょうか?

多くの MS-DOS 系環境は完全な MS-DOS のレプリカではなく、その特徴的な挙動や追加機能を調べることで、現在動作している環境を特定することができます[1]。DOSBox も例外ではないと思いませんか?「特徴(クイックス)」とは言うまでもなくバグの待機中である可能性が高いですが、「MOUNT」や「VER」などのコマンドは外部世界に情報をリークする能力を持っており、何か追加機能をどこかに隠していないでしょうか?

エイジーモード:正しい方法

はい、おっしゃる通りです。画面に向かって叫んでいる您可能想像できます:最も単純な方法は、FE00:0061 の文字列を取得すること——これは Award BIOS のバージョン文字列の一般的なアドレスであることが周知です[2]—そしてそれが "DOSBox" で始めているか確認します。しかし、それはあまりにも脆い(壊れやすい)、ご存知でしょう?非 DOSBox の BIOS にそのバージョン文字列を埋め込むことも可能ですし、逆に DOSBox を改変してモデル文字列を変更することも可能です。しかも DOSBox-X のソースコード中には、「将来の望ましい変更」としてそのような挙動への言及さえあるコメントが存在します:

/* TODO: *DO* allow dynamic relocation however if the dosbox-x.conf indicates that the user
 *       is not interested in IBM BIOS compatibility. Also, it would be really cool if
 *       dosbox-x.conf could override these strings and the user could enter custom BIOS
 *       version and ID strings. Heh heh heh.. :) */

したがって、このルートは取るべきではありません!別の簡単な方法があります。例えば、Z: ドライブのシリアル番号を確認するというもの(あるいはそのドライブが存在するかどうかも含めて)です。しかし、これらもすべて比较容易に偽装可能です。いや、我々はエミュレーターの本質的な一部である何かを見つける必要があります。「これが DOSBox であることを証明する」何か。

インベントゥス・インストラクションズ

DOSBox が「MOUNT.COM」のようなコマンドを通じて外部世界とどう対話し得るかを振り返りましょう。COM ファイルは単なる機械語コードであり、つまりディスアッセンブラを介して直接実行することができます。では、DOSBox の MOUNT.COM を使い试试看しましょう:

$ ndisasm MOUNT.COM
00000000  BC0004            mov sp,0x400
00000003  BB4000            mov bx,0x40
00000006  B44A              mov ah,0x4a
00000008  CD21              int byte 0x21
0000000A  FE                db 0xfe
0000000B  3805              cmp [di],al
0000000D  00B8004C          add [bx+si+0x4c00],bh
00000011  CD21              int byte 0x21
00000013  02                db 0x02

最初の 4 行は理解しやすい:

INT 21h
機能 4Ah はスタックサイズを 0x40 パラグラフ(128 バイト)に縮小します。しかし、その後の数行は……基本的にはゴミです。「db 0xfe」とは「ここには 0xfe というバイトが存在する」という意味だけであり、通常の x86 CPU はこれを拒絶して Invalid Instruction(無効な命令)例外を発生させます。

しかし、x86 CPU を設計する場合であれば、ご自身で独自の指令を発明できます!まさにその通りで、DOSBox のソースコードを見てみましょう:

/* src/cpu/core_normal/prefix_none.h からの抜粋 */
CASE_B(0xfe)               /* GRP4 Eb */
    {
        GetRM; Bitu which=(rm>>3)&7;
        switch (which) {
            case 0x00:     /* INC Eb */
                RMEb(INCB);
                break;
            case 0x01:     /* DEC Eb */
                RMEb(DECB);
                break;
            case 0x07:     /* CallBack */
                {
                    Bitu cb=Fetchw();
                    FillFlags(); SAVEIP;
                    return cb;
                }
            default:
                E_Exit("Illegal GRP4 Call %d",(rm>>3) & 7);
                break;
        }
        break;
    }

これは opcode の「FE グループ」のデコード用コードです。

0x00
INC
0x01
DEC
で、これらはどちらも x86 上の実在する正当な命令(opcode)です。しかし最後の
0x07
は DOSBox 独自のものです。この opcode に続く単語はどのカブバック関数を呼び出すべきかを示し、処理を破断して抜けます。したがって、前述のディスアッセンブル結果を補正すると、次のようになります:

00000000  BC0004            mov sp,0x400
00000003  BB4000            mov bx,0x40
00000006  B44A              mov ah,0x4a
00000008  CD21              int byte 0x21
0000000A  FE380500          CallBack 0x0005
0000000E  B8004C            mov ax,0x4c00
00000011  CD21              int byte 0x21

ふたけ:x86 命令符号化の藪に転び落ちる話

この草稿の第一版では、私はこう書きました:

x86 命令符号化の藪に転び落ちないように心がけます [...]

しかし、このカブバック opcode が機能する仕組みはまさに x86 opcodes の仕様に起因しています。x86 命令がどのように符号化されているかを知っていることを期待するのは公平ではないと感じます。すでに前半でアセンブリコードを示しましたが、なおかつ私の漫談が多少アクセスしやすくあるべきだと思っています。

すでにこの仕組みをご存知の方、あるいは気にされない方へは飛ばしても構いませんが、本当にご興味がある方のために、主な情報源は Intel 64 and IA-32 Architectures Software Developer's Manual の Volume 2 で、こちら にあります。以降のセクションでは章を括弧書きで引用します。

さて、機械語コードは複数の部分に分かれており、そのうち opcode 自体だけが「1.5 文字節」に過ぎません[2.1]。簡潔さのため、ここでは_opcode_、ModR/M バイト、そしてイミディエート(定数)バイトを無視するものとして説明します(これらが今回の対象だからです)。

前述のディスアッセンブル抜粋を見てみましょう:

0000000A  FE380500          CallBack 0x0005
0000000E  B8004C            mov ax,0x4c00

これを hex(16 進数)表記に変換すると:

FE 38 05 00    00 B8 00 4C

0F
というプレフィックスがない場合、opcode は単なる
FE
です。(2.1 セクション参照)ただしこれはグループで、「INC/DEC グループ 4」であり、実際に opcode を決定するのは次のバイトである ModR/M バイトの opcode ビットです。このバイトは以下のように分割されます:

  • バイト:
    00 111 000
    0x38
  • Mod:
    00
    0x00
  • Opcode:
    111
    0x07
  • R/M:
    000
    0x00

当方の目的においては opcode フィールドのみが重要です。したがってこれは

FE /7
と読むことができます。Opcode Extensions テーブルに従うと、(2.1 A.4.2 Table A-6 参照)実際にはこの組み合わせは存在しません。このグループでは
FE /0
FE /1
のみが存在します。しかし DOSBox が秘密の
FE /7
をサポートしていることは確かで、次に進むべきことを知るためにはソースコードへの依存せざるを得ません。そしてそれが実装しているのは:

Bitu cb=Fetchw();
FillFlags(); SAVEIP;
return cb;

特に重要なのは、

Fetchw()
が次のワード(2 バイト)を取得して戻すことです(つまり、機械に「このカブバックを呼び出すように」と指示している)。x86 はリトルエンディアンであるため、
05 00
00 05
として解釈されます。カブバックの処理が完了すると、次の命令が呼び出されます。それは
B8 00 4C
です。
B8
は「MOV AX, XXXX」を表します。この命令は 16 ビットのイミディエート(定数)を扱い、ここでは
00 4C
の値(リトルエンディアン表記では
4c00
)となります。このように次々と処理が進みます。

さて、これが「MOUNT.COM」のような仮想プログラムの生成部分をコード上どのように表現しているかを示します:

/* src/misc/programs.cpp からの抜粋 */
static Bit8u exe_block[]={
    0xbc,0x00,0x04,                 // MOV SP,0x400 スタックサイズを削減
    0xbb,0x40,0x00,                 // MOV BX,0x040 メモリ再サイズ準備
    0xb4,0x4a,                      // MOV AH,0x4A メモリブロック再サイズ指示
    0xcd,0x21,                      // INT 0x21 (DOS インタラプト)
// ポジション 12 がカブバック番号
    0xFE,0x38,0x00,0x00,            // CallBack 番号を呼び出す
    0xb8,0x00,0x4c,                 // Mov ax,4c00 (終了処理)
    0xcd,0x21,                      // INT 0x21 (DOS インタラプト)
};

便利なのは、カブバックが一般のステータスと同じ方法で返されるため、DOSBox において

FE 38 00 00
は実質的に 4 バイトの NOP(何もしない)として機能するという点です。他の x86 CPU にはそのような幸運はありません。80186 から以降では、無効な命令は「#UD」(未定義 Opcode)例外、あるいは Interrupt 06h をトリガーします。したがって我々は単に例外ハンドラを書くだけで済みます。以下のようなものです:

_catchUD:
    ; 現在の IP はスタックのトップにあるため、ax/bx をプッシュした後で +4
    push bx
    push ax
    
    mov bx, sp
    mov bx, WORD [ss:bx+4]
    mov ax, bx
    
    mov bx, WORD [cs:bx] ; リトルエンディアン(例:0x38fe)としてコピーする
    and bh, 38h
    cmp bx, 38feh
    je .notDosbox
    
    ; ここに来た場合、本当にもっとも何かがおかしい!IVT をクリーンアップし、
    ; IRET を実行して実際の #UD ハンドラを呼び出す。
    ; IP を改変しないため、無効な opcode は再実行される。
    push es
    xor ax, ax
    mov es, ax
    mov bx, [oldUDAddr] ; 以前の int 06h アドレス
    mov [es:18h], bx ; 06h*4
    mov bx, [oldUDSeg] ; 以前の int 06h セグメント
    mov [es:20h], bx ; (06h*4)+2
    pop es
    pop ax
    jmp .catchDone
    
    .notDosbox:
    ; DOSBox ではない -- IP を増殖し、AX をゼロ化する
    ; もちろんここには自由に処理できます。例えばグローバル変数を設定したり。
    add ax, 4
    mov bx, sp
    mov WORD [ss:bx+4], ax
    xor ax, ax
    add sp, 2 ; AX はもはや不要
    
    .catchDone:
    pop bx
    iret

これを設定すれば、以下のようにテストできます:

mov ax, 42
db 0xfe, 0x38, 0x00, 0x00
; 例外ハンドラがこの位置にあったか?
cmp ax, 0
jz .notDosbox ; DOSBox ではない!
; DOSBox 固有のコードはここから開始!
.notDosbox:
; DOSBox 以外の環境でのコードはここから開始!

必要であれば、中断 06h ベクターを一度リセットするための追加命令を加えれば、DOSBox の検出に十分な良質なチェック手法が到手できます!

DEBUG と x86

執筆の途中時点で、これをハードウェア上でもテストしてみるのが良いだろうと考えました。しかし、私の Pentium II システムは現在やや眠っており(=使用頻度が低く)、またスクリーンショットを撮影することも難しい状況です…そこで 86Box を使うことにしました。

これは予定通りには行かず:

[画像プレースホルダー:失敗または予期せぬ挙動を示すスクリーンショット]

重要なのは、これは DOSBox ではありません。ただし気にする必要はなく、MS-DOS の DEBUG プログラムを使って一つずつステップ実行し、何が起きているかを確認すればよいからです。DEBUG は非常に友好的とは言えませんが、この種のタスクに十分な能力を持っています。

t
というコマンドが存在し、これによってコードを一つの命令単位でステップ実行できます(まあほぼ)。そこでカブバック命令までステップ実行し、DEBUG がここでの状況について全く無知であること、しかもそれを「適度に符号化している」にもかかわらず有効な命令として処理してしまうことに気づきます!

この時点で私は完全に混乱しました。何か秘密の未公式文書化された命令でしょうか?86Box は何らかの理由で無効な命令を無視しているのでしょうか?無効な命令例外が私の想定とは異なる方法で動作するのでしょうか?DOS ドライバによって中断がどのようにマスクされるのでしょうか?

このトラブルシューティングに費やした数日を省略させていただきます:86Box は PCem から引き継いだバグを持っており、ModR/M opcode モディファイアの 0 がでない限りすべて

FE /1
として扱われてしまうのです[3]。つまり
FE /2
FE /4
、そして
FE /7
はすべて DEC 呼び出しとして振る舞います。幸いなことに修正は比較的簡単で、既にアップストリームでマージ済みです。

PR の通り、実際のハードウェア上でのテストについては linear に特別な感謝を申し上げます。これにより、これが単なるインテルのドキュメントのミスであるわけではないことを(少なくとも部分的には)確信できました。

完成品(?)

私が作成したサンプルプログラムを実行したい場合は、私の Git フォージ [リンク] から入手できます。コンパイルには NASM のインストールが必要です。DOSBox および DOSBox-X 上で問題なく動作します。

このプロジェクト自体は楽しいものがありましたが、私の意図は単に DOSBox を検出することではありませんでした。ちょうどそれが最も困難な部分だったのでした。NTVDM や Win9x の MS-DOS プロンプトを検出するのはもっと簡単であり、基本的に単一の「INT 2Fh」呼び出しで済みます。Linux 用の別の DOS エミュレーターである DOSEMU も存在し、なんと多くのカブバック API を実装しています(これらはすべて COM ファイルとして実装されており、例え UNIX.COM がホストシステム上で任意のコマンドを実行することを可能にしても隠れた機能というわけではありません)。もちろん、カスタム CPU 指令に比べて偽装するのは容易ですが、BIOS 文字列を変更するよりも副作用を招きやすい傾向があります。

同じ日のほかのニュース

一覧に戻る →

2026/04/18 0:04

クロード・デザイン

## Japanese Translation: Anthropic は今日、研究プレビュー段階で一般公開された新しい AI ツール「Claude Design」を発表しました。このツールは、進化した Claude Opus 4.7 vision モデルを基盤としており、テキストやドキュメントを瞬時に視覚デザインに変換する機能を備えています。以前では 20 つ以上のプロンプトが必要だった作業フローをわずか 2 ステップへと大幅に簡素化することで、プロトタイプ制作を容易にします。ユーザーはテキストで要件を記述するか、DOCX、PPTX、XLSX ファイルをアップロードし、Claude が最初の視覚版を作成して改善の余地を残します。オンボーディング時に既存のコードベースに直接統合され、チーム向けデザインシステムを自動的に構築するほか、デザインファイルを读取して共有標準を策定します。 本ツールにはカスタムスライダーやインラインコメントなど、精密な微調整制御に加え、組織範囲での共有機能、プライベートリンク、編集アクセス権、グループチャットといった新たなコラボレーション機能も提供されます。デザインは Canva への完全編集可能なファイルとしての直接エクスポートが可能で、あるいは PDF、PPTX、HTML、社内 URL へも代替的にエクスポートできます。バンドルは Claude Code のための直接実装用としても保存されます。これは、現実的なインタラクティブプロトタイプやワイヤーフレームからピッチデッキ、マーケティング素材、3D 要素など先端デザインに至るまでの用途をカバーします。 現在、claude.ai/design で利用可能です。本サービスは現在の研究プレビュー段階ですが、一日中急速に拡大しており、Pro、Max、Team、Enterprise サブスクリプションのすべてで利用可能になっています。特に、Enterprise ユーザーではデフォルトで機能がオフになっているものの、管理者が Organization 設定からこれを有効にすることで、アクセス管理を好みに合わせて行うことができます。この進歩により、コードを書かずに複雑な素材を作成することが可能となり、デザインサイクルが加速するとともに、ワイヤーフレームからプロダクションまでの移行プロセスにおいてクリエイティブワークフローと開発者ツールを統合することで、そのプロセスが合理化されます。

2026/04/18 6:38

FIL-C の簡略化モデル

## Japanese Translation: Fil-C は、既存の不安全な C/C++ コードベースをメモリ安全性を備えた実装に改修することを目的とした革命的ツールであり、手動による書き換えを必要としません。それは、簡略化されたモデルではソースコード、または生産環境版では LLVM IR を自動的に変換することで達成され、各関数内のポインターにメタデータレコード(`AllocationRecord*` 変数)を付与します。これらのレコードは、可視データ、境界アラインメント用の非公開バイト、および長さ情報を追跡し、参照解除やポインター算術といった標準的な操作を自動的に境界チェックを備えた操作へと書き換えることを可能にします。 このシステムは、標準ライブラリ呼び出しを Fil-C 版(例:`filc_malloc`)で置き換えにより配列を明示的に処理し、かつ廃棄された非公開メタデータオブジェクトの解放にはガバージコレータが担当するというハイブリッドアプローチによってメモリライフサイクルを管理します。これは `AllocationRecord` インスタンス自体が直接子配列を解放しないためです。スタック操作によるエラーを防ぎつつ安全性を保証するため、ローカルスコープからアドレスが流出する変数は自動的にヒープ割り当てに昇進されます。 未確認のレガシーコードベースに対する安全な橋渡しとしての位置づけを持つ Fil-C は、 unsafe ポインター交換を関数呼び出しを超えて防止するというユニークなポインター所有性の性質を持ち、積極的な最適化および並行型ガバージコレータを通じて典型的なメモリ安全性ペナルティを軽減します。最終的に、AddressSanitizer による強力なコンパイル時の安全性保証を提供すると同時に、産業界が既存の大規模コードベースを安全にし、Zig などにおける安全なコンパイル時評価を活用することを可能にします。

2026/04/18 3:17

『全ての 12 人の月面歩行者は、火薬のような匂いのする塵から「月じんかぜ」に苦しんだ(2018 年)』

## 日本語訳: 月面の塵は宇宙探査にとって二重の現実をもたらします:それはアストロノーツにとって即座に命を脅かす危険であると同時に、将来的な植民地にとっては貴重な資源でもあります。主な危険性は、その独自の物理的特性に由来します。やわらかい地球の塵とは異なり、月面の粒子は鋭く研摩性のあるケイ酸塩粒であり、太陽放射と大気による侵食の欠如のため静電的に帯電したままです。これらの小さな棘状の粒子(人間の髪の毛の約 50 倍小さいもの)は、地球重力の 1/6 の環境でも数ヶ月間浮遊し、表面より高く漂浮しながら装備に侵入し、さらに人間肺の奥深くまで到達します。史上 12 人の月面を歩いた宇宙飛行士すべて(其中包括アポロ 17 号のハリーソン・シミュット)によって記録されているように、露出は「月の枯草熱」と呼ばれる症状——喉の痛み、涙目、くしゃみ、鼻閉塞(時に数日続くものも含まれる)——および肺細胞や脳細胞に損傷をもたらす可能性のある長期的毒性を引き起こしました。また、この塵は宇宙船内部で独特な火薬のような燃えた臭いを放ちます。 これらのリスクを安全に研究するために、研究者らはドイツで粉砕された丸みを帯びた火山岩シミュラントを使用して、鋭く有毒な月面粒子への曝露なしに機器故障をモデル化しています。カリフォルニア大学の NASA アストロノート・キム・プリスクを含む 12 人の科学者による野心的なプログラムで、月面塵の毒性リスクが推定されています。生物学者のエリン・トランフィールドは、火山岩を粉砕することで鋭い縁を取り除き、より安全なシミュレーションが可能になると指摘しています。科学顧問のアイドアン・カウリーは有望な応用として、月面土壌を加熱して居住用のレンガを生産したり、レゴリットから酸素を抽出して有人ミッションを維持したりすることを挙げています。同時に、ESA アストロノート・アレクサンダー・ゲーストは減重力下での肺の健康を追跡するための「気道モニタリング」実験を行い、将来の持続可能な月面帰還を支えています。並行して、ESA はオランダのエウレカ研究所内で月面資源に関するワークショップを開催し、これらの機会への研究を推進するとともに、この独自の環境がもたらす重大な健康リスクを軽減することを目指しています。