C/C++カテゴリとMUGENカテゴリが同じ記事につくとは1年前は思いもしなかったが、MUGENのデバッグ出力がsprintfフォーマットなんだからしかたあるまい。
で以前MUGENのsprintfフォーマットを調べたわけですが (RC6でDisplayToClipboardに使えないフォーマットがあった - chikuchikugonzalezの雑記とかDisplayToClipboardで使用可能な書式 - chikuchikugonzalezの雑記とか)、ここ最近とある業界で%n系のバグが見つかったようでにわかに活気づいております。
で、この「%n」ってなによ?ってことでちょいと調べました。
ところで%nってDisplayToClipboardの変換指定子のことであってますよね?
違ったら以下の文章はすべて駄文になりまっせ( TДT)
%n指定子って?
分かりづらく言うと、
(s)printfでフォーマット文字列内に'%n'が指定された場合、そこまで出力された文字列の文字数が%nに対応する位置のポインタ変数の指す整数型変数に格納される
ということ。わからん。
サンプルで示すと、だいたい次のC言語プログラム
#include <stdio.h> #include <stdint.h> // percentn1.c int main(int argc, char *argv[]) { int i = 0; int c = 0; for (i = 0; i < argc; i++) { printf("%s%n", argv[i], &c); // %nには変数へのポインタを渡す printf(" (%d文字)\n", c); // ポインタが指していた変数に文字数が格納される } return 0; }
を次のように実行すると
$ percentn1 "Hello, World" "Sample Code"
次のようになります
percentn1 (9文字)
Hello, World (12文字)
Sample Code (11文字)
おわかりだろうか。出力をするはずのprintfで入力系の変換指定子になっているのである。
これ一歩間違えるとこんなこともできまっせ
まぁ、int型をポインタと誤認させても行けるってことです。
#include <stdio.h> #include <stdint.h> int main(void) { int i = 0; int j = (int) &i; // ポインタをintにしてみる printf("Hello, World!%n", j); // ポインタの値を持った「int型」変数を指定 printf(" (%d文字)\n", i); // ポインタの指していた場所を表示 return 0; }
結果
$ percentn2
Hello, World! (13文字)
MUGENでやると?
こんな感じですか
; Gルガールのデバッグ部分 ; %n指定子にvar(49)を指定してみた [State -2: Debug Flags] type = AppendToClipboard trigger1 = !IsHelper text = "\nCHAIN:%d SC:%d ULT:0x%08X KILL:%d%n" params = var(10), var(11), var(51), var(57), var(49)
これってつまり
var(49)に入っている値をポインタとして使い、そこに出力文字数を格納する
という動作をします。ぶっちゃけメモリ上の任意の位置に書き込みできます。
また書きこむ値も結構自由です。%10dとすれば最低10文字出力されることになるので、%255dとかすれば255がvar(49)の指すメモリに書きこまれます。
まー普通はそんなところへアクセスすればあっさりSEGV (セグメンテーション違反、別名アクセス違反) でプログラムが死にます *1
Win版だけ?
どうやらelecbyteもこのあたりのバグを知っていた様子で、実行前にフォーマット指定子をチェックしてくるようになりました。
そのなかで使えないのは
- %x
- %o
- %c
- %n ← New!
となります。使っていると対戦画面に行く前に「ダイアログで」(゚Д゚)ゴルァ!!って怒ってきます。逆に言えば安全にそのまま終了します。
*1:昔のOSだったらOSごと死んでたんだろうなー