逆コンパイルとは
逆コンパイルとは完成しているアプリケーションからソースコードを取り出すことです。
.NETにおける逆コンパイル問題
.NETで開発されたアプリケーションは簡単に逆コンパイルが可能です。そのためソフトウェアの詳細を隅々まで調べることができ、セキュリティ上の欠陥を探して利用したり、開発者が必死になって考えた斬新なアイデアを盗んだり、プログラムを不正利用することなども可能です。
上記の記事は2008年の物のため現在のマイクロソフトの見解と異なっている可能性があります。
難読化とは
例えば変数名やメソッド名を関連性のない名前に変更したり、メソッドの内部に無駄なソースコードを追加したり、固定文字列を暗号化することによって逆コンパイルされたとしても可読性が著しく低いソースコードを出すことによって解析を辛くすることです。
難読化の必要性
個人的な見解にはなりますが下記の2パターンに該当する場合は難読化を視野に入れるべきかと考えます。
- アプリケーションを製品として有償販売する場合
- 内部にDBコネクション文字列などセキュリティに関する文字列を直書きしている場合
逆に言うと上記の場合以外は不要と考えています。
また、2007年の記事にはなりますがこちらのサイトのアンケート結果では下記にようになっています。
作成したアプリケーションを難読化していますか?
選択肢 | 投票数 | 投票率 |
---|---|---|
必ずするようにしている | 2票 | 1.53% |
高価なものだけしている | 1票 | 0.76% |
全くしていない | 128票 | 97.71% |
上記の記事は2008年の物のため現在のマイクロソフトの見解と異なっている可能性があります。
逆コンパイルの方法
逆コンパイルの方法は複数ありますが、私が使ったことがあるという理由でここではILSpyを紹介します。
ILSpyをダウンロードする
GitHubに公開されているILSpyのページからアプリケーションをダウンロードします。
1.こちらのGitHubページにアクセスする。
2.下の方にスクロールしてAssetsの[ILSpy_binaries_7.2.0.6844.zip]をダウンロードします。
※現在の最新版は7.2です。7.2のダウンロードはこちらになります。
3.ダウンロードしたフォルダを解凍してフォルダ内のILSpy.exeを起動する。
4.左上の[File]→[Open...]から逆コンパイルしたい.exeまたは.dllを選択する。
※上記画像はアプリケーションを逆コンパイルした際の例
このように無償で誰でも簡単にアプリケーションを逆コンパイルしてソースコードを解析することができます。
難読化の方法
難読化にも方法は複数ありますが、私が使ったことがあるという理由でここではConfuserExを紹介します。
ConfuserExをダウンロードする
GitHubに公開されているConfuserExのページからアプリケーションをダウンロードします。
1.こちらのGitHubページにアクセスする。
2.Assetsの[ConfuserEx_bin.zip]をダウンロードします。
※現在の最新版は1.0です。1.0のダウンロードはこちらになります。
3.ダウンロードしたフォルダを解凍してフォルダ内のConfuserEx.exeを起動する。
4.[Project]の[+]ボタンから難読化したい.exeまたは.dllを選択する。
5.[Setting]の[Grobal settings]に[+]ボタンを押してルールを追加する。
6.[編集]ボタンを押して[Preset]に難読化レベル(今回はNomalを選択)を設定する。
難読化レベルは上げるほど解析しずらくなりますが、同時にソフトが正常に動作しなくなる可能性も高くなります。
7.[Protect!]で[Protect!]ボタンを押して難読化する。
このように難読化も簡単に実施することが可能です。
難読化したアプリケーションをILSpyで確認してみる
難読化したアプリケーションを確認した結果、下記のように変化しました。
public class ClassReadCSV
{
public List<string> GetCSVData(string strLogPass)
{
string strFilePath = null;
List<string> listRet = new List<string>();
try
{
strFilePath = strLogPass;
using TextFieldParser objIoTFP = new TextFieldParser(strFilePath, Encoding.GetEncoding("Shift_JIS"));
TextFieldParser textFieldParser = objIoTFP;
textFieldParser.TextFieldType = FieldType.Delimited;
textFieldParser.SetDelimiters(",");
textFieldParser.TrimWhiteSpace = false;
textFieldParser = null;
while (!objIoTFP.EndOfData)
{
string[] arrayRow = objIoTFP.ReadFields();
string strVeiwResult = null;
string[] array = arrayRow;
foreach (string objField in array)
{
strVeiwResult = strVeiwResult + objField + ",";
}
strVeiwResult = strVeiwResult.TrimEnd(',');
listRet.Add(strVeiwResult);
}
}
catch (Exception ex2)
{
ProjectData.SetProjectError(ex2);
Exception ex = ex2;
MessageBox.Show(ex.Source.ToString());
ProjectData.ClearProjectError();
}
return listRet;
}
}
難読化後(すごく長いので折りたたんでいます。)
public class \u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e
{
public List<string> \u202a\u200c\u200f\u206b\u206a\u202a\u200f\u200b\u206c\u202a\u202e\u202a\u206e\u200b\u202b\u206b\u206f\u202e\u206e\u200d\u200f\u206a\u200f\u200c\u206f\u202a\u202a\u202e\u206d\u202b\u206e\u206a\u202b\u200e\u202c\u206e\u206c\u200f\u206b\u206a\u202e(string P_0)
{
string text = default(string);
string[] array = default(string[]);
int num7 = default(int);
string text3 = default(string);
bool flag2 = default(bool);
bool flag = default(bool);
List<string> result = default(List<string>);
while (true)
{
int num = 57029038;
while (true)
{
uint num2;
switch ((num2 = (uint)num ^ 0x3545C508u) % 3u)
{
case 0u:
break;
case 1u:
goto IL_0024;
default:
{
List<string> list = new List<string>();
try
{
while (true)
{
IL_003d:
int num3 = 371558307;
while (true)
{
switch ((num2 = (uint)num3 ^ 0x3545C508u) % 3u)
{
case 0u:
break;
case 2u:
goto IL_0060;
default:
{
TextFieldParser textFieldParser = \u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u200c\u202e\u206d\u202b\u202b\u200e\u206a\u206f\u200b\u200c\u206b\u200d\u202a\u200b\u206a\u200d\u200d\u200b\u206b\u200b\u200f\u200c\u206e\u206e\u206e\u202d\u202c\u202d\u202c\u202a\u202b\u202d\u202e\u200b\u206f\u202d\u200e\u202a\u202c\u200c\u202e(text, \u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u206c\u206d\u200f\u200f\u200b\u206b\u200b\u200f\u206c\u200c\u200d\u202c\u202d\u202d\u206f\u206f\u206a\u206a\u206d\u202a\u206a\u206f\u206c\u206f\u202c\u202b\u202e\u200f\u200c\u200d\u206a\u206f\u200b\u200e\u200d\u200e\u202b\u202c\u200c\u200d\u202e(<Module>.\u206a\u206e\u202e\u202e\u202a\u206f\u200b\u200e\u202c\u200f\u206e\u202a\u202e\u202b\u202a\u202a\u202c\u206a\u200f\u202e\u206c\u202a\u200d\u202a\u200c\u202b\u206d\u206d\u202e\u206f\u200d\u202e\u202e\u200c\u206f\u202e\u202d\u200e\u206a\u202a\u202e<string>(4110665164u)));
try
{
TextFieldParser textFieldParser2 = textFieldParser;
\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u202b\u206e\u206b\u202b\u206a\u200f\u200d\u202d\u202a\u202b\u206e\u202c\u202e\u202e\u206e\u202a\u200c\u200d\u202a\u202d\u200f\u202e\u202d\u202e\u200d\u200e\u206e\u206a\u202a\u202e\u202d\u202d\u200d\u206d\u206a\u200f\u200d\u202d\u206b\u206b\u202e(textFieldParser2, FieldType.Delimited);
\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u202c\u200c\u206e\u200e\u202e\u200c\u200c\u200d\u202c\u200d\u206a\u200d\u202b\u200f\u206b\u206d\u206b\u206e\u200e\u206c\u206b\u206f\u202c\u206c\u206d\u202c\u206b\u202c\u200b\u206d\u206f\u202c\u206c\u202a\u206b\u200e\u202e\u202d\u200b\u206a\u202e(textFieldParser2, new string[1] { <Module>.\u200f\u202e\u200f\u202d\u200c\u202a\u200f\u206d\u202b\u200f\u206a\u202d\u202b\u200f\u206b\u202e\u206c\u200c\u200b\u202e\u202e\u202e\u200c\u206d\u200e\u206a\u200f\u206c\u206d\u206a\u206e\u202a\u200e\u202e\u200c\u200e\u202b\u200b\u206f\u202e\u202e<string>(2250328912u) });
while (true)
{
IL_00b0:
int num4 = 1468968514;
while (true)
{
switch ((num2 = (uint)num4 ^ 0x3545C508u) % 19u)
{
case 12u:
break;
default:
goto end_IL_00b5;
case 14u:
{
string text2 = array[num7];
text3 = \u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u202d\u202b\u206b\u200f\u200b\u200d\u200e\u200f\u200c\u202e\u206e\u206a\u200f\u200e\u202a\u206e\u206c\u202c\u206d\u200b\u206d\u202a\u200c\u200b\u206e\u206a\u202c\u206a\u202e\u200f\u202a\u202a\u202e\u200b\u202a\u200c\u206a\u200c\u206d\u206b\u202e(text3, text2, <Module>.\u202c\u202e\u202c\u200d\u200c\u202e\u206b\u206f\u202a\u200f\u202a\u202b\u202a\u200c\u200d\u206e\u202d\u200c\u200e\u202b\u202d\u206f\u206e\u202c\u202d\u202a\u206d\u206b\u206e\u206d\u200c\u200f\u206c\u206f\u206b\u200c\u206c\u202b\u206e\u202a\u202e<string>(3122368461u));
num4 = 1724083459;
continue;
}
case 16u:
\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u206f\u206f\u206c\u202c\u200c\u202e\u202e\u200c\u202a\u202d\u200d\u200e\u200b\u202d\u200d\u200e\u202a\u202d\u200c\u202d\u206e\u200c\u202b\u200c\u202b\u202e\u202b\u206a\u202e\u206b\u202c\u206a\u206c\u200c\u200b\u206f\u206a\u206a\u200b\u206b\u202e(textFieldParser2, false);
num4 = ((int)num2 * -1163212801) ^ 0x5828DC46;
continue;
case 4u:
{
int num8;
int num9;
if (!flag2)
{
num8 = -1830664316;
num9 = num8;
}
else
{
num8 = -799792013;
num9 = num8;
}
num4 = num8 ^ ((int)num2 * -52529806);
continue;
}
case 15u:
flag2 = !\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u206c\u202d\u202e\u200b\u200c\u206e\u206f\u206f\u202c\u200d\u206b\u202a\u206f\u202c\u206a\u202e\u206e\u206d\u200d\u200c\u206a\u200e\u202a\u202b\u200e\u206e\u206c\u202b\u206f\u206c\u200c\u206f\u202a\u200e\u206b\u206f\u206a\u202d\u206f\u200c\u202e(textFieldParser);
num4 = 1624345918;
continue;
case 13u:
num4 = (int)(num2 * 1048388339) ^ -1054683859;
continue;
case 10u:
flag = num7 < array.Length;
num4 = 163164134;
continue;
case 1u:
text3 = \u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u202b\u206c\u202e\u206f\u200e\u206c\u206a\u206f\u202a\u206f\u206d\u200c\u202e\u202e\u206d\u206c\u206b\u202e\u206c\u206a\u200b\u206e\u206a\u202a\u200f\u202d\u200f\u206e\u206e\u206e\u202e\u206c\u202c\u206f\u206c\u202d\u202e\u206b\u206b\u206c\u202e(text3, new char[1] { ',' });
list.Add(text3);
num4 = ((int)num2 * -1705654412) ^ 0x76CE61FC;
continue;
case 9u:
num7 = 0;
num4 = (int)((num2 * 884672622) ^ 0x10031C2);
continue;
case 11u:
{
string[] array2 = \u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u202a\u206e\u206f\u202a\u206d\u202c\u206d\u200d\u206b\u202e\u206f\u206b\u200c\u202d\u202d\u200b\u200f\u206f\u202e\u206b\u202b\u202d\u202e\u206b\u202b\u200d\u206e\u202e\u206c\u202a\u206f\u206e\u200b\u206d\u200e\u206c\u202d\u206d\u206b\u202b\u202e(textFieldParser);
text3 = null;
array = array2;
num4 = 1568070890;
continue;
}
case 0u:
num7 = checked(num7 + 1);
num4 = (int)(num2 * 1978488914) ^ -137648958;
continue;
case 5u:
textFieldParser2 = null;
num4 = ((int)num2 * -1309534186) ^ -247549455;
continue;
case 18u:
num4 = ((int)num2 * -294384958) ^ 0x8D28331;
continue;
case 3u:
{
int num5;
int num6;
if (flag)
{
num5 = -1250506192;
num6 = num5;
}
else
{
num5 = -1311254160;
num6 = num5;
}
num4 = num5 ^ (int)(num2 * 1545074150);
continue;
}
case 7u:
num4 = (int)((num2 * 1343806703) ^ 0x1B8735C9);
continue;
case 8u:
num4 = (int)(num2 * 1015212331) ^ -1857778250;
continue;
case 6u:
num4 = ((int)num2 * -721222060) ^ -842287069;
continue;
case 2u:
num4 = (int)((num2 * 271892887) ^ 0xD60A80F);
continue;
case 17u:
goto end_IL_00b5;
}
goto IL_00b0;
continue;
end_IL_00b5:
break;
}
break;
}
}
finally
{
if (textFieldParser != null)
{
while (true)
{
IL_02ce:
int num10 = 639651897;
while (true)
{
switch ((num2 = (uint)num10 ^ 0x3545C508u) % 4u)
{
case 0u:
break;
default:
goto end_IL_02d3;
case 1u:
\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u206f\u206c\u206f\u206b\u202a\u206a\u200d\u202a\u206e\u206c\u202a\u206b\u206e\u200f\u200f\u200e\u200d\u206e\u202c\u200b\u206f\u200e\u202e\u206a\u200e\u202d\u200c\u200c\u200e\u200b\u200f\u200b\u202e\u200f\u202a\u202a\u200b\u206b\u200f\u200e\u202e((IDisposable)textFieldParser);
num10 = ((int)num2 * -1531068862) ^ -1212105411;
continue;
case 3u:
num10 = ((int)num2 * -724610820) ^ 0x5B81D22A;
continue;
case 2u:
goto end_IL_02d3;
}
goto IL_02ce;
continue;
end_IL_02d3:
break;
}
break;
}
}
}
goto end_IL_0042;
}
}
goto IL_003d;
IL_0060:
text = P_0;
num3 = ((int)num2 * -1154434067) ^ 0x4AE42BB3;
continue;
end_IL_0042:
break;
}
break;
}
}
catch (Exception ex)
{
\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u202c\u202a\u200e\u200f\u202e\u200e\u202d\u200d\u200f\u200f\u202e\u206c\u200e\u206e\u206f\u200c\u200e\u206d\u202b\u200c\u202a\u200d\u206f\u200b\u200c\u206b\u200e\u202c\u206e\u200e\u200e\u200b\u206b\u200f\u206a\u206f\u200c\u206b\u202c\u202e(ex);
Exception ex2 = ex;
while (true)
{
IL_0328:
int num11 = 715917943;
while (true)
{
switch ((num2 = (uint)num11 ^ 0x3545C508u) % 3u)
{
case 2u:
break;
case 1u:
goto IL_034b;
default:
\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u200c\u200e\u206f\u206d\u202a\u200b\u200c\u206e\u200d\u202c\u206e\u206f\u202a\u200c\u206d\u206f\u200d\u206e\u200c\u200b\u200e\u206b\u202d\u206e\u200d\u206f\u206b\u206d\u200d\u202e\u206a\u206b\u200b\u206f\u200d\u202a\u206a\u202e\u206c\u202d\u202e();
goto end_IL_032d;
}
goto IL_0328;
IL_034b:
\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u202e\u202a\u202c\u206c\u206b\u206e\u200f\u202c\u200c\u202c\u200f\u200f\u202a\u202d\u202b\u202b\u200c\u202b\u206f\u202d\u202b\u200b\u206d\u206f\u202e\u206b\u202a\u206b\u206d\u206a\u202e\u200f\u206a\u202a\u202e\u200f\u206d\u206c\u206c\u202e\u202e(\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u206e\u202a\u200b\u200d\u206f\u202c\u206e\u200c\u206d\u206a\u202a\u202c\u202c\u206f\u206d\u202b\u206e\u202b\u202b\u206d\u206e\u206b\u206f\u206f\u206a\u206e\u206d\u200d\u200e\u202e\u206d\u206b\u202e\u206d\u200b\u202d\u200c\u200d\u200b\u206c\u202e(\u202b\u202a\u206d\u202e\u200d\u200b\u202e\u200b\u206f\u202d\u200d\u206a\u200e\u200c\u202c\u200e\u202e\u200b\u206d\u202a\u202a\u200f\u206b\u202b\u200b\u200b\u202c\u206e\u206d\u200c\u206c\u202e\u200d\u206d\u200d\u206e\u200b\u202b\u202b\u206a\u202e.\u206b\u200f\u200b\u206f\u202e\u200e\u206b\u200c\u206d\u202d\u206b\u202a\u206c\u200e\u206e\u206b\u206a\u200c\u206b\u202c\u206f\u206d\u202a\u200d\u200e\u206a\u206c\u202c\u200f\u202c\u200c\u206a\u200b\u202c\u202a\u200e\u200b\u202b\u200e\u200f\u202e(ex2)));
num11 = (int)(num2 * 133330755) ^ -1215901149;
continue;
end_IL_032d:
break;
}
break;
}
}
while (true)
{
int num12 = 501616785;
while (true)
{
switch ((num2 = (uint)num12 ^ 0x3545C508u) % 3u)
{
case 0u:
break;
case 2u:
goto IL_0398;
default:
return result;
}
break;
IL_0398:
result = list;
num12 = ((int)num2 * -305028259) ^ 0x7ADC67C3;
}
}
}
}
break;
IL_0024:
text = null;
num = ((int)num2 * -952149003) ^ -1889253393;
}
}
}
このようにとても読めたものじゃない著しく可読性が低いソースコードに変化しています。
まとめ
- .NETで開発したアプリケーションは簡単に逆コンパイルされる。
- 製品として販売するアプリケーションの場合は難読化することを検討する。
- ソースコード内にセキュリティ的にヤバい固定文字列を埋め込む場合は難読化することを検討する。
- そもそもセキュリティ的にヤバい固定文字列をソースコードに埋め込まないように実装する。(DBから取得する等)
以上です。