using System; using System.Linq; using System.Text; using System.IO; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using TwlBackupBlock; // TODO もっとアサートを入れましょう namespace FalsifyTwlBackup { partial class Program { private const int MAX_CONTENTS = 8; // ブロックセクション private const int SECTION_BANNER = 0; private const int SECTION_HEADER = 1; private const int SECTION_SIGNATURE = 2; private const int SECTION_TMD = 3; private const int SECTION_CONTENT = 4; private const int SECTION_PUBLIC_SAVE = SECTION_CONTENT + MAX_CONTENTS; private const int SECTION_SUB_BANNER = SECTION_PUBLIC_SAVE + 1; // データの挿入位置 private const int NONE = -1; private const int FRONT_BANNER = 100; // ブロック内データ本体の前 private const int FRONT_HEADER = 101; private const int FRONT_SIGNATURE = 102; private const int FRONT_TMD = 103; private const int FRONT_CONTENT = 104; private const int FRONT_PUBLIC_SAVE = FRONT_CONTENT + MAX_CONTENTS; private const int FRONT_SUB_BANNER = FRONT_PUBLIC_SAVE + 1; private const int REAR_BANNER = 200; // ブロック内データ本体の後 private const int REAR_HEADER = 201; private const int REAR_SIGNATURE = 202; private const int REAR_TMD = 203; private const int REAR_CONTENT = 204; private const int REAR_PUBLIC_SAVE = REAR_CONTENT + MAX_CONTENTS; private const int REAR_SUB_BANNER = REAR_PUBLIC_SAVE + 1; private const int REAR_DATA = 99; // データ全体の後 // 改ざんパターンの引き数 private const int ALL = 0; private const int CAT100 = 1; private const int CAT200 = 2; private const int CAT300 = 3; private const int CAT400 = 4; private const int CAT500 = 5; private const int VERIFY = 9; // TORIAEZU : 上記が Mode == Pattern なのが引っかかる・・・ // 各カテゴリでの最小番号 private const int MIN_OF_CAT100 = 100; private const int MIN_OF_CAT200 = 200; private const int MIN_OF_CAT300 = 300; private const int MIN_OF_CAT400 = 400; private const int MIN_OF_CAT500 = 500; // 各カテゴリでの最大番号 private const int MAX_OF_CAT100 = 118; private const int MAX_OF_CAT200 = 214; private const int MAX_OF_CAT300 = 303; private const int MAX_OF_CAT400 = 415; private const int MAX_OF_CAT500 = 515; private static readonly int[,] NUM_OF_PATTERN = new int[,] { {100, MAX_OF_CAT100}, {200, MAX_OF_CAT200}, {300, MAX_OF_CAT300}, {400, MAX_OF_CAT400}, {500, MAX_OF_CAT500} }; // MACとハッシュセット更新の有無 private const int NC_MAC_AND_HASHSET = 0; private const int CHG_MAC_AND_HASHSET = 1; private const int BLOCK_NUM = 14; // バックアップデータのブロックの個数 private const int IMPROPER_DATA_SIZE = 16 * 1024; // 不正なデータのサイズ(16 KB) private const int KEY_SIZE = 16; // 鍵のサイズ(16 Byte) private const int HASH_SIZE = 32; // ハッシュのサイズ(32 Byte) private const int AES_BLOCK_SIZE = 16 * 8; // 暗号化のブロックサイズ(16Byte) private const string EXTENSION = ".bin"; // TwlBackupファイルの拡張子 /// /// 使用方法を表示します。 /// static void Usage() { Console.WriteLine("Usage: "); Console.WriteLine("./FalsifyingTwlBackup.exe BACKUPFILE_NAME KEYFILE_NAME MACKEYFALE_NAME MODE"); Console.WriteLine(" BACKUPFILE_NAME : *.bin"); Console.WriteLine(" KEYFILE_NAME : *.txt"); Console.WriteLine(" MACKEYFALE_NAME : *.txt\n"); Console.WriteLine(" MODE"); Console.WriteLine(" -all (default) : output all falsifying pattern"); Console.WriteLine(" -cat CAT_NUM : output all pattern of CAT_NUM category"); Console.WriteLine(" ex) -cat 100 -> falsifying 100,101,102...."); Console.WriteLine(" -each PAT_NUM : output PAT_NUM pattern"); Console.WriteLine(" ex) -each 204 -> falsifying 204 only"); Console.WriteLine(" -verify : verify backup file"); Environment.Exit(-1); } /// /// 拡張子の判別を行います。 /// /// 確認対象となるファイル名 /// 拡張子 static void CheckExtension(string args, string extension) { Debug.Assert(args.Length > extension.Length); if (args.ToLower().EndsWith(extension) == false) { Console.WriteLine("{0} is not [\"{1}\"] file!", args, extension); Usage(); } return; } /// /// ファイルの有無を確認します。 /// /// 確認対象となるファイルのパス static void CheckFileExists(string args) { Debug.Assert(args.Length != 0); if (!File.Exists(args)) { Console.WriteLine("\"{0}\" is not exists!\n", args); Usage(); } } /// /// コマンドライン引数のオプションから処理を決定するためのフラグを立てる /// /// コマンドライン引数の格納された文字列 /// 処理判別のためのフラグ static int GetFalsifyingType(string[] args) { Debug.Assert(args.Length >= 3); if (args.Length > 3) { // -allのとき if (args[3].ToUpper().TrimStart('-') == "ALL") { return ALL; } // -catのとき else if (args[3].ToUpper().TrimStart('-') == "CAT") { if (4 >= args.Length) // CATなのに次の引数が無かったら帰れ! { Usage(); } switch (args[4]) { case "100": return CAT100; case "200": return CAT200; case "300": return CAT300; case "400": return CAT400; case "500": return CAT500; default: Usage(); break; // never reach } } // -eachのとき else if (args[3].ToUpper().TrimStart('-') == "EACH") { if (4 >= args.Length) // EACHなのに次の引数が無かったら帰れ! { Usage(); } // TODO 存在しない処理番号をはじくように else { return Convert.ToInt16(args[4]); } } // -verify else if (args[3].ToUpper().TrimStart('-') == "VERIFY") { return VERIFY; } } // それ以外 return ALL; } /// /// 指定されたbyteサイズで、0のみのbyte列を作成します。 /// /// 作成するデータのサイズ[単位:byte] /// 作成したデータのbyte列 // TODO 不正データの作成方法を選べるようにしてもいいかも static byte[] CreateImproperData(int dataSize) { byte[] data = new byte[dataSize]; for (int i = 0; i < data.Length; i++) { //data[i] = (byte)(i); data[i] = 0; } return data; } /// /// データの差し替えを行います。 /// /// 上書き対象となるデータ本体 /// 上書きする不正データ static void ReplaceImproperData(AbstractBody dataBlocks, byte[] impData) { for (int i = 0; i < impData.Length; i++) { if (i < dataBlocks.Length) // 上書き先のデータサイズを超えませんように { dataBlocks[i] = impData[i]; } } return; } /// /// ※未使用 /// 暗号化に使用するIVを作成します。 /// AES方式で、ブロックサイズはAES_BLOCK_SIZE[bit]と同じです。 /// /// 生成したivのbyte列 static byte[] GenerateNewIv() { // 暗号化用IV作成 /* * [参考] C#(.NET Framework)のAESを使ってファイルを暗号化してみる * http://hibara.sblo.jp/article/43505136.html */ System.Security.Cryptography.AesCryptoServiceProvider aes = new System.Security.Cryptography.AesCryptoServiceProvider(); aes.BlockSize = AES_BLOCK_SIZE; // AESはブロックサイズ、キー長ともに16byte aes.GenerateIV(); // IVの設定(ブロックサイズと同サイズ = 16byte) byte[] byteIV = aes.IV; // IVをバイト列に return byteIV; } /// /// 署名のハッシュセットを変更します。 /// /// 変更対象となる署名ブロックを有するBlocks /// 新しいハッシュ値 /// 変更するブロックセクション static void ChangeHashSet(Blocks dataBlocks, byte[] hash, int section) { Debug.Assert((section <= BLOCK_NUM - 1) && (section >= 0)); SignatureBody signature; signature = (SignatureBody)dataBlocks.signature.body; if (SECTION_CONTENT <= section && section < SECTION_CONTENT + MAX_CONTENTS) { hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.CONTENT + section - SECTION_CONTENT], 0); } else { switch (section) { case SECTION_BANNER: hash.CopyTo(signature.digest.banner, 0); break; case SECTION_HEADER: hash.CopyTo(signature.digest.header, 0); break; case SECTION_TMD: hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.TMD], 0); break; case SECTION_PUBLIC_SAVE: hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.PUBLIC_SAVE], 0); break; case SECTION_SUB_BANNER: hash.CopyTo(signature.digest.section[(int)Utility.SectionIndex.SUB_BANNER], 0); break; // TODO : Private Save 追加 default: Debug.Assert(false); break; } } } /// /// byte列を結合します。 /// /// 前半部分に来るbyte列 /// 後半部分に来るbyte列 /// 連結後のbyte列 static byte[] MergeByteArray(byte[] front, byte[] rear) { return front.Concat(rear).ToArray(); } /// /// byte列をbinファイルに出力します。 /// /// ファイル名となる改ざんパターンの番号 /// 出力ディレクトリのパス /// ファイルに出力するデータ static void OutputFalsifiedData(int improperNo, string outFolderPath, byte[] data) { string outFalsifiedDataPath = Convert.ToString(improperNo) + EXTENSION; // ファイル名を改ざん番号から生成 outFalsifiedDataPath = Path.Combine(outFolderPath, outFalsifiedDataPath); // 出力ディレクトリとファイル名を結合 File.WriteAllBytes(outFalsifiedDataPath, data); // 出力 } /// /// データ本体とMACとIVをひとつのブロックに結合します。 /// 必要に応じて不正データを挿入します。 /// /// 暗号化するBlockを持つBlocks /// 暗号化するBlockのセクション /// バックアップデータに関するインスタンス /// 挿入する不正データ /// 挿入する位置 /// 格納するMAC /// MACを算出するためのハッシュ値 /// データ本体、MAC、IVを順に結合したbyte列 static byte[] EncryptBlock(Blocks dataBlocks, int section, Properties prop, byte[] impData, int insertPos, byte[] byteMac, byte[] byteHash = null) { byte[] encryptedBlock = new byte[0]; // (データ本体の前に不正データ挿入) if (insertPos >= FRONT_BANNER && insertPos <= FRONT_SUB_BANNER) { if (section == insertPos - FRONT_BANNER) { encryptedBlock = MergeByteArray(encryptedBlock, impData); } } // データ本体を暗号化 encryptedBlock = MergeByteArray(encryptedBlock, dataBlocks[section].body.GetBytes()); // bodyを暗号化 // (データ本体の後に不正データ挿入) if (insertPos >= REAR_BANNER && insertPos <= REAR_SUB_BANNER) { if (section == insertPos - REAR_BANNER) { encryptedBlock = MergeByteArray(encryptedBlock, impData); } } if (byteHash != null) { byte[] buff = Utility.GetSha256(encryptedBlock); // 平文からSHA256ハッシュ算出 buff.CopyTo(byteHash, 0); byteMac = Utility.GetAesCmac(byteHash, prop.macKeyData); // SHA256ハッシュからMACを算出 } encryptedBlock = Utility.EncryptBody(encryptedBlock, prop.keyData, dataBlocks[section].iv); // データ本体を暗号化 encryptedBlock = MergeByteArray(encryptedBlock, byteMac); // MACを結合 encryptedBlock = MergeByteArray(encryptedBlock, dataBlocks[section].iv); // IVを結合 // (データブロックの後に不正データ挿入) if (section == SECTION_SUB_BANNER && insertPos == REAR_DATA) { encryptedBlock = MergeByteArray(encryptedBlock, impData); } return encryptedBlock; } /// /// 各ブロックを暗号化した後、1つの byte 列に結合します。 /// /// 暗号化するデータ /// バックアップデータに関するインスタンス /// 不正データ /// 不正データを挿入する位置 /// ハッシュセット更新の有無 /// 全てのBlockを暗号化し、連結したbyte列 static byte[] MergeEncryptBody(Blocks dataBlocks, Properties prop, byte[] impData, int insertPos, int chgMacHash) { byte[] outData = new byte[0]; byte[][] encryptedBlocks = new byte[BLOCK_NUM][]; byte[][] byteHash = new byte[BLOCK_NUM][]; byte[][] byteMac = new byte[BLOCK_NUM][]; //---------------------- // 各ブロックを暗号化 // ( MAC & HASH SET更新する場合 ) if (chgMacHash == CHG_MAC_AND_HASHSET) { for (int i = 0; i < BLOCK_NUM; i++) // 署名部以外を暗号化&byte列に結合 { byteHash[i] = new byte[HASH_SIZE]; if (i != SECTION_SIGNATURE) { if (dataBlocks[i].body != null) { encryptedBlocks[i] = EncryptBlock(dataBlocks, i, prop, impData, insertPos, byteMac[i], byteHash[i]); // ブロックを暗号化&結合 ChangeHashSet(dataBlocks, byteHash[i], i); // ハッシュセットを更新 } else { encryptedBlocks[i] = null; } } } // 最後に署名部を暗号化&byte列に結合 byteHash[SECTION_SIGNATURE] = Utility.GetSha256(dataBlocks[SECTION_SIGNATURE].body.GetBytes()); // 平文からSHA256ハッシュ算出 byteMac[SECTION_SIGNATURE] = Utility.GetAesCmac(byteHash[SECTION_SIGNATURE], prop.macKeyData); // SHA256ハッシュからMACを算出 encryptedBlocks[SECTION_SIGNATURE] = EncryptBlock(dataBlocks, SECTION_SIGNATURE, prop, impData, insertPos, byteMac[SECTION_SIGNATURE]); } // ( MAC & HASH SET更新しない場合 ) else { for (int i = 0; i < BLOCK_NUM; i++) { if (dataBlocks[i].body != null) { encryptedBlocks[i] = EncryptBlock(dataBlocks, i, prop, impData, insertPos, dataBlocks[i].mac); // ブロックを暗号化&結合 } else { encryptedBlocks[i] = null; } } } //---------------------- // 暗号化した各ブロックを結合 for (int i = 0; i < BLOCK_NUM; i++) { if (encryptedBlocks[i] != null) { outData = MergeByteArray(outData, encryptedBlocks[i]); } } return outData; } // static void FalsifyingData(int improperNo, Properties prop, byte[] improperData) //------------------------------------------------------------ // パターン番号に応じた改ざんを実行 // [in] int improperNo : 改ざんパターン番号 // [in] Properties prop : バックアップデータに関するインスタンス // [in] byte[] improperData : 改ざんに使用する不正データ //------------------------------------------------------------ /// /// パターン番号に応じた改ざんを実行 /// /// /// /// static void FalsifyingData(int improperNo, Properties prop, byte[] improperData) { if (MIN_OF_CAT100 <= improperNo && improperNo <= MAX_OF_CAT100) { FalsifyingDataCategory1xx(improperNo, prop, improperData); } else if (MIN_OF_CAT200 <= improperNo && improperNo <= MAX_OF_CAT200) { FalsifyingDataCategory2xx(improperNo, prop, improperData); } else if (MIN_OF_CAT300 <= improperNo && improperNo <= MAX_OF_CAT300) { FalsifyingDataCategory3xx(improperNo, prop, improperData); } else if (MIN_OF_CAT400 <= improperNo && improperNo <= MAX_OF_CAT400) { FalsifyingDataCategory4xx(improperNo, prop, improperData); } else if (MIN_OF_CAT500 <= improperNo && improperNo <= MAX_OF_CAT500) { FalsifyingDataCategory5xx(improperNo, prop, improperData); } else { FalsifyingDataCategory9xx(improperNo, prop, improperData); } } // static void SwitchFalsifyingPattern(int falsifyingType, Properties prop) //------------------------------------------------------------ // 作成する改ざんデータのパターンを決定。 // [in] int falsifyingType : 出力する改ざんデータのパターン // [in] Properties prop : バックアップデータに関するインスタンス //------------------------------------------------------------ /// /// /// /// /// static void SwitchFalsifyingPattern(int falsifyingType, Properties prop) { // 改ざんに使用する不正なデータ作成 byte[] improperData = CreateImproperData(IMPROPER_DATA_SIZE); switch (falsifyingType) { case ALL: // 全パターン Console.WriteLine(" Falsifying \"all pattern\"."); for (int i = 0; i < NUM_OF_PATTERN.GetLength(0); i++) { for (int j = NUM_OF_PATTERN[i, 0]; j <= NUM_OF_PATTERN[i, 1]; j++) { FalsifyingData(j, prop, improperData); Console.WriteLine(" Complete falsifying No.{0}.", j); } Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[i, 0]); } break; case CAT100: // 1xx系列全て Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[CAT100 - 1, 0]); for (int i = 100; i <= MAX_OF_CAT100; i++) { FalsifyingData(i, prop, improperData); Console.WriteLine(" Complete falsifying No.{0}.", i); } Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[CAT100 - 1, 0]); break; case CAT200: // 2xx系列全て Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[CAT200 - 1, 0]); for (int i = 200; i <= MAX_OF_CAT200; i++) { FalsifyingData(i, prop, improperData); Console.WriteLine(" Complete falsifying No.{0}.", i); } Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[CAT200 - 1, 0]); break; case CAT300: // 3xx系列全て Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[CAT300 - 1, 0]); for (int i = 300; i <= MAX_OF_CAT300; i++) { FalsifyingData(i, prop, improperData); Console.WriteLine(" Complete falsifying No.{0}.", i); } Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[CAT300 - 1, 0]); break; case CAT400: // 4xx系列全て Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[CAT400 - 1, 0]); for (int i = 400; i <= MAX_OF_CAT400; i++) { FalsifyingData(i, prop, improperData); Console.WriteLine(" Complete falsifying No.{0}.", i); } Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[CAT400 - 1, 0]); break; case CAT500: // 5xx系列全て Console.WriteLine(" Falsifying category No.{0}.", NUM_OF_PATTERN[CAT500 - 1, 0]); for (int i = 500; i <= MAX_OF_CAT500; i++) { FalsifyingData(i, prop, improperData); Console.WriteLine(" Complete falsifying No.{0}.", i); } Console.WriteLine(" Complete falsifying Cat.{0}.\n", NUM_OF_PATTERN[CAT500 - 1, 0]); break; default: // 1パターンのみを個別指定 Console.WriteLine(" Falsifying pattern No.{0}.", falsifyingType); FalsifyingData(falsifyingType, prop, improperData); Console.WriteLine(" Complete falsifying No.{0}.\n", falsifyingType); break; } } //================================================== // メイン関数 //================================================== static void Main(string[] args) { // コマンドライン引数の数チェック if (args.Length < 3) // データ、ブロック鍵、MAC鍵 { Usage(); return; } else { // 拡張子の判定 CheckExtension(args[0], ".bin"); // 入力ファイル名 CheckExtension(args[1], ".txt"); // ブロック鍵ファイル名 CheckExtension(args[2], ".txt"); // MAC鍵ファイル名 // ファイルが存在するかを確認 for (int i = 0; i < 3; i++) { CheckFileExists(args[i]); } } string twlBackupName = args[0]; string keyName = args[1]; string macKeyData = args[2]; int falsifyingType = GetFalsifyingType(args); // オプションがあれば、改ざんのパターン決定 Properties prop = new Properties(); // インスタンスの作成 byte[] twlBackupData = File.ReadAllBytes(twlBackupName); // バックアップデータの読み込み // 鍵データの読み込み string[] keyString = File.ReadAllLines(keyName); prop.keyData = new byte[KEY_SIZE]; for (int i = 0; i < prop.keyData.Length; i++) { prop.keyData[i] = Convert.ToByte(keyString[i], 16); } // MAC鍵データの読み込み string[] macKeyString = File.ReadAllLines(macKeyData); prop.macKeyData = new byte[KEY_SIZE]; for (int i = 0; i < prop.keyData.Length; i++) { prop.macKeyData[i] = Convert.ToByte(macKeyString[i], 16); } // ファイル名の出力 + 鍵データの dump Console.WriteLine("Twl Backup File : {0}", twlBackupName); Console.WriteLine("Block Key File : {0} ({1})", keyName, dumpArray(prop.keyData)); Console.WriteLine("MAC Key File : {0} ({1})", macKeyData, dumpArray(prop.macKeyData)); // TORIAEZU : ひとまず VERIFY だったら検証して終了 if (falsifyingType == VERIFY) { if (Utility.Verify(twlBackupData, prop.keyData, prop.macKeyData)) { Console.WriteLine("{0} は検証に成功しました。", twlBackupName); } Environment.Exit(0); } // バックアップデータを復号化 prop.decryptedBlocks = Utility.DecryptBackupData(twlBackupData, prop.keyData); // 出力フォルダを作成 prop.SetOutputFolderPath(twlBackupName); //========================================== // 改ざん&ファイル出力 Console.WriteLine("Start ------------"); SwitchFalsifyingPattern(falsifyingType, prop); Console.WriteLine("Complete --------"); Console.WriteLine("Output Dir: {0}", prop.outFolderPath); } // byte 配列の 16 進表示 static string dumpArray(byte[] array) { string str = ""; foreach (byte b in array) { str += String.Format("{0,0:X2} ", b); } return str.TrimEnd(); } } }