読者です 読者をやめる 読者になる 読者になる

SIN@SAPPOROWORKSの覚書

C#を中心に、夜な夜な試行錯誤したコードの記録です。

ランダム関数を使用して動的にリテラル文字列を生成するコードを吐くプログラム

タイトルは、長く、みょーな言い回しになってますが、一応合ってます。

定数(テキスト)の露出

パスワードなどの文字列を定数としてそのままプログラムへ記述すると、テキストとしてプログラムを覗いたときに簡単に見えてしまう事は、よく知られた問題であり、これを見えにくく難読化するプログラムなども色々有るようです。

今回は、このような隠したい文字列を見えにくくする方法の一つとして、「動的にリテラル文字列を生成するコードを吐くプログラム」(ややこしい?)を作成してみました。

先日書いた、「ランダム関数による hello world」の数値列生成をそのまま応用しています。

3文字分の検索

ランダム関数が次々と返す数字列から、文字コードの並びと等しい個所を検索するのですが、文字列長が長いと、ヒットの条件が厳しくなり検索に非常に時間がかかってしまいます。そこで3文字ずつに区切るって検索することにしました。下記は、指定した文字列のoffset番目から3文字分を検索して、その開始位置を返す関数です。

まずは、対象の文字を Int32[] c に入れて、その並びが見つかるまでRandamオブジェクトのNext()を回しています。


static int Encode(int seed, String str ,int offset){
    const int len = 3; //3文字分を処理する
    var c = new Int32[len];
    for (var e = 0; e < len; e++){
        if (str.Length <= offset + e){
            c[e] = 31;
        } else{
            c[e] = str[offset + e] - 32;
        }
    }
    var ran = new Random(seed);
    for (var i = 0; ; i++) {
        for (var e = 0; e < len; e++) {
            if (c[e] != ran.Next(94))
                break;
            i++;
                if (e == len-1)
            return i - len;
        }
    }
}

文字列の数値化

次に、上記のEncodeを使用して、文字列を Int32[] pos に数値化するコードは次のようになります。
文字列が終了すると、その後は全部0になるわけですが、0が3回連続して出現するNext()は1112455番目なので、これをマジックコードとし、マジックコードが出現したら、それ以降の処理の必要は無いということでbreakしています。


const int max = 128; //扱えるリテラル文字列の最大長
const int magic = 1112455; //31が3個連続するポジション
const int seed = 200;
var pos = new Int32[max];
for (var e = 0; e < max/3; e++){
    pos[e] = Encode(seed, args[0], e*3);
    if (pos[e] == magic) {
        break;
    }
}

数値化されたデータから文字列を再現する

次は、発見した数値列を使用して文字列を再現させるコード「を生成する」コードです。


Console.Write("static string  Literal(){\n");
Console.Write("    var pos = new[]{");
for (int e = 0; e < max / 3 && pos[e] != magic; e++) {
    if(e!=0)
        Console.Write(",");
    Console.Write(string.Format("{0}", pos[e]));
}
Console.Write("};\n");
Console.Write("    var sb = new StringBuilder();\n");
Console.Write("    foreach (var res in pos.Select(Decode)){\n");
Console.Write("    for (var i = 0; i < 3; i++){\n");
Console.Write("            var c = (char) (res[i] + 32);\n");
Console.Write("            if (c == 31 + 32)\n");
Console.Write("                break;\n");
Console.Write("            sb.Append(c);\n");
Console.Write("        }\n");
Console.Write("    }\n");
Console.Write("    return sb.ToString();\n");
Console.Write("}\n");
Console.Write("\n");
Console.Write("static int[] Decode(int pos){\n");
Console.Write("    var ran = new Random("+seed+");\n");
Console.Write("    for (var i = 0; i < pos; i++)\n");
Console.Write("        ran.Next(94);\n");
Console.Write("    var res = new int[3];\n");
Console.Write("    for (var e = 0; e < 3; e++)\n");
Console.Write("        res[e] = ran.Next(94);\n");
Console.Write("    return res;\n");
Console.Write("}\n");

ちょっと、ややこしくなってきましたが、実際に使用しているようすを見て下さい。

コマンドラインから「P@ss$w0rd」という文字列を与えて、コードを生成している様子です。出力されたコードをコピーしてコンパイルするとLiteral()から「P@ss$w0rd」が返されます。

ソースコード
GitHub https://github.com/furuya02/Obfuscate

バイナリ
Obfuscate.zip

※現在、ASCIIの表示可能文字(13~94)のみ対応しています。また、変換できる文字列は128文字までです。