制作日記

こういうの作った方が、逃げにくいじゃない

%nの説明

%nバグの説明書いてみた
L霊夢に同梱するつもりだったけど、容量大きくなるからやっぱ止め
せっかく書いたのを捨てるのはもったいないので、こっちで公開
1.標準入出力関数
 %nについて説明する前に、標準入出力関数の説明が必要になります。mugenはC言語で開発されており、C言語で初めに習うだろう標準入出力関数はprintf()です。これについてはweb上でわかりやすい説明があるので、詳細は割愛します。知って頂きたいのは、printf()の基本的な動作です。%nについてはここで説明します。mugenでprintf()に対応するのはDisplayToClipboardです。printf()と動作は大して変わりませんが、2点ほど異なります。1点目は引数の数を%の数で判断していることです。これは%nバグ自体とは関係ありませんが、覚えておいて損はしません。2点目は引数の数が5つまでに制限されていることです。こちらはそこまで気にする必要はありません。

2.%n
 %nはそれほど複雑な動きをしているわけではありません。「引数で指定した値をメモリアドレスとし、そこまでに出力した文字数(4byte)をそのアドレスに書き込む」たったこれだけです。「引数で指定した値をメモリアドレスとし」これはそのままの意味です。例えば10を指定すればメモリのアドレス10に、100を指定すればメモリのアドレス100に書き込みます。注意すべきなのは、引数は10進数で指定していますが、普通アドレスは16進数で表現されることです。アドレス0xF(16進数であることを示すため、数値の頭に0xを付けます)に書き込みたいときには、%nの引数に16を指定します。常に16進数か10進数かには気を付けてください。上の例では引数10でアドレス0xA、引数100でアドレス0x64を指定したことになります。
 次にそこまでに出力した文字数(4byte)を書き込むです。そこまでに出力したというのは、%nがあるまでに何文字あるかということです。例えば、"%nああああ"なら後ろに何文字続こうが%nで書き込む値は0です。また、書き込まれる値は"出力した"文字数ですので、"%10d%n"ならtext自体の文字数は%nまでで4文字ですが、"出力する"文字数は10文字なので、10(0xA)が書き込まれます。注意すべきなのは、(4byte)の部分です。4byteだと数値は0x00000000~0xFFFFFFFFと表されます。そして、1byteの値0x0Aを書き込みたくても、、0x0000000Aが書き込まれます。つまり0x0A以外の部分に0x00をを書き込みたくなくても、強制的に書き込まれることになります。また、DisplayToClipboardは1度に出力できる文字数が1024文字なので、0x400以上の値を書き込むことが出来ず、1byte(0x00~0xFF)ずつ書き込むことになります。よって、0x00AADDCCという値を1回の書き込みで0x00AABBCCにするのは不可能です。
 
3.リトルエンディアン
 %nバグを利用し始めるうえで%n自体より重要なのが、このリトルエンディアンです。上記では数値0xAが4byteの場合、0x0000000Aだとしていましたが、windowsのメモリ上では異なります。実際には0x0A000000と記録されています。これがリトルエンディアンというメモリに数値を記録する方法です。実際にどのように記録されているか、0x12345678という数値を例にして示します。
 メモリには1byteずつ数値が書き込まれます。しかし、0x12345678は4byteなので、1byte毎に分割します。すると、0x12,0x34,0x,56,0x78の4つに別れます。これらの値のうちどれから書き込むかですが、後ろから順番にアドレスの値を大きくしながら書き込みます。よって0x78が先頭になり、このアドレスが0x08の場合、0x56がアドレス0x09、0x34がアドレス0x0A、0x12がアドレス0x0Bに書き込まれます。よって、0x12345678はアドレス0x08に0x78563412と書き込まれたことになります。
 上で示した0x00AADDCCを0x00AACCBBににしたい場合を考えてみます。0xBB文字出力してアドレスに0xCCのアドレスを指定すると0xBB000000が書き込まれるので、0x00AABB00となります。本来は0x00AABB000000ですが、今回は説明を簡単にするため元々0x00AADDCCの次には0が続いていると思ってください。0xCCが0x00になってしまったので、0xBBと同様の方法で0xCCを0xBBを書き込んだ次のアドレスに書き込みます。すると、0x00AABBCCになります。

3.浮動小数点数
 PCが小数を2進数で表現するための方法として、浮動小数点数があります。名前の由来は小数点の位置が浮動であることです。MUGENではfvarが浮動小数点数で、他にも内部で使われています。実際に浮動小数点数を弄る機会があるかどうかは別にして、そういう表現方法があるということは覚えておいた方がよいでしょう。
 
4.文字と文字列
 PCでは文字も数値として扱います。ある値が文字なのかただの数値なのかはその値からはわかりません。このことはprintf()について勉強していれば既に知っていると思います。また、printf()ではどのような指定子の場合に文字又は数値として扱われるかも、既に知っているものとして説明を続けます。どのような値と文字が対応するかはASCIIの文字コードを参照してください。¥と\の半角はどちらも同じ文字です(同じ値に文字コードによって異なる文字が割り振られている)ので、注意してください。
 次に文字列の表現です。文字列は文字(1byteの数値)の連続ですが、文字列を一つの固まりとして記録するのではありません。あくまでも文字の連続です。つまり、文字列"ABC"('A'=0x41,'B'=0x42,'C'=0x43)は0x414243ではなく0x41,0x42,0x43です。どちらも同じように見えますが、上記のリトルエンディアンを考慮すれば、異なることがわかるはずです。前者をメモリ上に記録しようとすれば0x434241となりますが、後者は1byteの数値の連続でしかないため、順番は変わらず0x414243と記録されます。ここで問題になるのが、PCは文字列が何文字続くか知らないということです。何の工夫もせず"ABC"と"DEF"と言う2つの文字列を記録した場合、0x414243444546となってしまい文字列の切れ目が分かりません。そこで、終端文字(文字コードは0x00)を使います。この文字はその名前の通り終端を表す文字で、文字列の最後には必ずこの文字を付加します。よって先の例は0x4142430044454600となります。これでどこまでが1つの文字列なのかPCにもわかるようになりました。
 覚えることは、数値は1byte単位で逆転するが、文字列は逆転せず最後に必ず0x00があるということです。L霊夢には文字列を弄っている箇所があるので、それを参考にしてください。

4.フィールド幅、精度
 既に、メモリエディタさえ用意すれば%nを使えるようになったはずですが、ここでは%nを利用する上での小技を紹介します。printf()について勉強したなら%5dや%.5dなどの指定子も知っていると思います。前者はフィールド幅で、最低何桁表示するかを指定できます。後者は精度で、小数点以下何桁まで表示するかを指定できます。普通に数字を直接入力しても良いと思います。しかし、引数で指定した任意の値を入れることができる*を利用することで、ステコンの数を減らせたり、変数に格納した値を指定することができます。
 実際にL霊夢で使用しているもの例として示します。

text="%*d%n%d" ;59はF1、63はF5に対応
params = ifelse(gametime%2 = 1,63,59),1,4937032

 "%*d%n%d"は引数を4つ必要とします、前から順番に*,d,%n,%dです。しかしparamsには引数が3つしかありません。これは最初に説明した、DisplayToClipboardが引数の数を%の数で判断していることが原因です。*が引数を必要とすることを認識できないのです。実際に出力される際には*も含めて引数が割り当てられるため、動作に問題はありません。最後の%dに対応する引数がありませんが、printf()の仕様上引数が存在するものとして割り当てていきますので、何かしらの値は割り当てられます。%nバグの説明として名前が出た「書式文字列攻撃」ではこの仕様も攻撃方法の一つとして利用されていますが、%nバグとは一切関係ありません。本来必要な引数が足りなくても問題なく動作するとだけ認識してください。
 上の例では、gametime%2の結果を元に*に渡す引数を決めています。*を利用しなかった場合、63と59で2つステコンが必要になります。2個ならまだしも3個4個と増えていくと、記述が見辛くなってしまい可読性が落ちます。
 次に精度です。実は%nで利用するうえでフィールド幅には決定的欠点があります。それは出力文字数を0に出来ないことです。たとえ%0dで引数として0を与えても1文字出力されます。そこで精度を利用します。今度もL霊夢の記述を例に示します。

text="%.*d%n%d"
params = ifelse(IsHomeTeam,0,1),0,4942237 ;LCtrl

 *の前に.を付けており、この場合は精度を表します。フィールド幅より優れている点が、*,d共に引数が0の場合は出力文字数も0になることです。%nで使用することだけならば、フィールド幅の上位互換になります。どちらも引数は必ず1byte以下の値になるようにしてください、特に変数を引数にする場合は気を付けてください。
 これを利用したお遊びとして、デバッグ表示上で名前を左右に動かすというものをL霊夢で実装しています。
 
5.固定アドレス
 mugen.exeを実行すると、メモリ上にmugen.exeやdllが記録されます。このときmugen.exeは常に同じアドレスに展開されるため、弄るアドレスも固定されます。しかし、ステコンオーバーフロー(超即死)で弄っている領域がどのアドレスに記録されるかは毎回変わります。ステコンオーバーフローは、位置が変動する領域内のある変数のアドレスを基準として弄っているため、その領域内の他の変数を操作可能ですが、弄るアドレスを直接指定する%nでは同じ変数を弄ることは現状不可能です。そのため、%nではメモリ上に展開されたmugen.exeを弄ることになります。

6.最後に
 実際に弄ろうとするなら、プロセスメモリエディタや、バイナリエディタが必要になると思いますが、それは自身で頑張って探してください。

 L霊夢12PをKO出来るキャラが公開されたら良いな……。

コメントの投稿

非公開コメント

No title

*ってそういう意味だったんですね。参考になります
"だたの数値"→ただの数値?
"位置が変動する変動する領域内"→位置が変動する領域内?

No title

誤字報告thxです、修正しときました
あと、お役に立ったようで何よりです
プロフィール

Author:drab
霊夢改変キャラ
「12 3 ! {V} [_]」
公開中
L霊夢でもl_reimuでも好きなように読んでください

最新記事
最新コメント
月別アーカイブ
カテゴリ
検索フォーム
RSSリンクの表示
リンク