最近博客写的少了,发现整个人学习的动力也不够了哇,还是得边写边学才好
UWP逆向初接触
其实我还是第一次听说UWP这个概念来着。。。首先先查询一下这个东西是个啥吧
在Windows集成应用商店之前的版本,我们一般是下载程序进行安装,这些程序一般指的就是后缀为“.exe”的文件,英文称之为Program,大多安装在“C:\Program Files”下。UWP是Universal Windows Platform简称,即Windows通用应用平台,可以在Windows 10 Mobile/Surface/PC/Xbox/HoloLens等平台上运行,它并不是为某一个终端而设计,而是可以在所有Windows10设备上运行一种全平台应用,一般安装在“C:\Program Files\windowsapp”下
大概意思就是说,这个玩意儿是微软的应用商城里面的东西,我们下载的东西还有会各种管理机制,比exe好。
如何安装
鉴于是小白,这里还是介绍的仔细一点好了:
首先要打开开发者模式
然后我们以管理员权限运行powershell,并且设置powershell的执行策略为RemoteSigned:
1 Set-ExecutionPolicy RemoteSigned
最后找到UWP下的
Add-AppDevPackage.ps1
进行安装即可。
最后能够在Windows的菜单里面找到这个应用。
如何逆向
对于一个uwp来说,程序的主体部分放在.appxbundle中(注意这里是bundle,是打包过的)。可以直接用压缩工具打开。里面的appx可以利用工具Telerik JustDecompile进行查看:
其中的MainPage自然就是入口啦~
经大佬提醒后知道,其实C++也可以用来写uwp。而且如果用C#写逻辑而用.net Native编译的话,那么当前的应用就只能用IDA来反编译了~
实例分析
这里以RCTF 2017的一道题为例子进行简单分析:
首先我们可以看到MainPage的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 namespace RCTF { public sealed class MainPage : Page , IComponentConnector , IComponentConnector2 { private string path = Path.Combine(Package.Current.InstalledLocation.Path, "Assets\\flag.sqlite" ); private string pfn = Package.Current.Id.FamilyName; private SQLiteConnection conn; [GeneratedCode("Microsoft.Windows.UI.Xaml.Build.Tasks" , " 14.0.0.0" ) ] private Button button; [GeneratedCode("Microsoft.Windows.UI.Xaml.Build.Tasks" , " 14.0.0.0" ) ] private TextBlock textBlock; [GeneratedCode("Microsoft.Windows.UI.Xaml.Build.Tasks" , " 14.0.0.0" ) ] private TextBox textBox; [GeneratedCode("Microsoft.Windows.UI.Xaml.Build.Tasks" , " 14.0.0.0" ) ] private bool _contentLoaded; public MainPage () { this .InitializeComponent(); this .conn = new SQLiteConnection(new SQLitePlatformWinRT(), this .path, true , null , null , null , null ); this .conn.CreateTable<flag_table>(CreateFlags.None); } public static string asdasd (string input ) { IBuffer binary = CryptographicBuffer.ConvertStringToBinary(input, BinaryStringEncoding.Utf8); string str = Package.Current.Id.FamilyName.Substring(0 , 16 ); string str1 = "0000000000000000" ; IBuffer buffer = Encoding.UTF8.GetBytes(str1).AsBuffer(); byte [] bytes = Encoding.UTF8.GetBytes(str); SymmetricKeyAlgorithmProvider symmetricKeyAlgorithmProvider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7); CryptographicKey cryptographicKey = symmetricKeyAlgorithmProvider.CreateSymmetricKey(bytes.AsBuffer()); IBuffer buffer1 = CryptographicEngine.Encrypt(cryptographicKey, binary, buffer); return CryptographicBuffer.EncodeToBase64String(buffer1); } private void button_Click (object sender, RoutedEventArgs e ) { int num; if (int .TryParse(this .textBox.Text, out num)) { IEnumerable<string > strs = from p in this .conn.Table<flag_table>().Where((flag_table p) => p.Id == num) select p.flag; foreach (string str in strs) { this .textBlock.Text = string .Concat("flag:" , MainPage.dfdfdfd(str)); } } } [DebuggerNonUserCode ] [GeneratedCode("Microsoft.Windows.UI.Xaml.Build.Tasks" , " 14.0.0.0" ) ] public void Connect (int connectionId, object target ) { switch (connectionId) { case 1 : { this .button = (Button)target; Button button = this .button; Button button1 = button; Button button2 = button; WindowsRuntimeMarshal.AddEventHandler<RoutedEventHandler>(new Func<RoutedEventHandler, EventRegistrationToken>(button1.add_Click), new Action<EventRegistrationToken>(button2.remove_Click), new RoutedEventHandler(this .button_Click)); break ; } case 2 : { this .textBlock = (TextBlock)target; break ; } case 3 : { this .textBox = (TextBox)target; break ; } } this ._contentLoaded = true ; } public static string dfdfdfd (string input ) { IBuffer buffer = Convert.FromBase64String(input).AsBuffer(); string str = Package.Current.Id.FamilyName.Substring(0 , 16 ); string str1 = "0000000000000000" ; IBuffer buffer1 = Encoding.UTF8.GetBytes(str1).AsBuffer(); byte [] bytes = Encoding.UTF8.GetBytes(str); SymmetricKeyAlgorithmProvider symmetricKeyAlgorithmProvider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7); CryptographicKey cryptographicKey = symmetricKeyAlgorithmProvider.CreateSymmetricKey(bytes.AsBuffer()); IBuffer buffer2 = CryptographicEngine.Decrypt(cryptographicKey, buffer, buffer1); return CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffer2); } [DebuggerNonUserCode ] [GeneratedCode("Microsoft.Windows.UI.Xaml.Build.Tasks" , " 14.0.0.0" ) ] public IComponentConnector GetBindingConnector (int connectionId, object target ) { return null ; } [DebuggerNonUserCode ] [GeneratedCode("Microsoft.Windows.UI.Xaml.Build.Tasks" , " 14.0.0.0" ) ] public void InitializeComponent () { if (!this ._contentLoaded) { this ._contentLoaded = true ; Application.LoadComponent(this , new Uri("ms-appx:///MainPage.xaml" ), ComponentResourceLocation.Application); } } } }
非常的清晰啊啊啊啊!这不是很容易就被人逆向了么。。。。
第一段的逻辑大概就是初始化逻辑
1 2 3 4 5 6 public MainPage () { this .InitializeComponent(); this .conn = new SQLiteConnection(new SQLitePlatformWinRT(), this .path, true , null , null , null , null ); this .conn.CreateTable<flag_table>(CreateFlags.None); }
将一些基本内容初始化,并且在指定的路径下创建一个数据并创建一个表。
这个初始化又在做什么呢,我们继续看
1 2 3 4 5 6 7 8 public void InitializeComponent () { if (!this ._contentLoaded) { this ._contentLoaded = true ; Application.LoadComponent(this , new Uri("ms-appx:///MainPage.xaml" ), ComponentResourceLocation.Application); } }
大致就是读取当前逻辑,并且载入一个元素,其中有一个令人好奇的URi:MainPage.xaml,这个是一个什么东西呢,上网查了一下,似乎就是一个xml,用来管理解决方法的。至于那个LoadComponent就是加载位于指定统一资源标识符 (URI) 处的 XAML 文件,并将其转换为由该 XAML 文件的根元素指定的对象的实例。
然后我们需要找到这个数据库的位置,发现在下列位置:
private string path = Path.Combine(Package.Current.InstalledLocation.Path, “Assets\flag.sqlite”);
这个InstalledLoaction.Path就是我们前文提到的过的C://Program Files//Windows App目录。用Everything找一找也能找到这个flag.sqlite
主要逻辑就这些,显然这个东西和Andorid一样,是由事件驱动的。因此我们关注其中和按钮相关的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private void button_Click (object sender, RoutedEventArgs e ){ int num; if (int .TryParse(this .textBox.Text, out num)) { IEnumerable<string > strs = from p in this .conn.Table<flag_table>().Where((flag_table p) => p.Id == num) select p.flag; foreach (string str in strs) { this .textBlock.Text = string .Concat("flag:" , MainPage.dfdfdfd(str)); } } }
这个内容可以看到,此时会将我们的输入的text转换成一个数字,然后和flag_table里面的数据进行比较。我们打开数据库看一下:
会发现,这个里面有很多的数据啊,总共有1024个呢。。。我们找打那个像瞎打的函数名的函数看看里面的内容:
1 2 3 4 5 6 7 8 9 10 11 12 public static string dfdfdfd (string input ){ IBuffer buffer = Convert.FromBase64String(input).AsBuffer(); string str = Package.Current.Id.FamilyName.Substring(0 , 16 ); string str1 = "0000000000000000" ; IBuffer buffer1 = Encoding.UTF8.GetBytes(str1).AsBuffer(); byte [] bytes = Encoding.UTF8.GetBytes(str); SymmetricKeyAlgorithmProvider symmetricKeyAlgorithmProvider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7); CryptographicKey cryptographicKey = symmetricKeyAlgorithmProvider.CreateSymmetricKey(bytes.AsBuffer()); IBuffer buffer2 = CryptographicEngine.Decrypt(cryptographicKey, buffer, buffer1); return CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffer2); }
会发先,首先对输入的字符串进行base64解码,然后取回str = Package.Current.Id.FamilyName.Substring(0, 16);这个ID不是普通的ID,是这个UWP的标识号。取出前16位,并且我们这里使用此符号作为加密的密钥,对其进行解密。
也就是说,我们这里在数据库里面看到的数据其实就是**【加密后的数据】**。所以我们这里只需要遍历数据库,并且取出里面所有的数据,base64解码后进行AES解密即可。至于这个Package.Current.Id.FamilyName,我们可以通过访问APPXManifest.xaml找到,其实就是:
<Identity Name=“26a6f9cc-d019-4f5d-8a1b-a352b7738f42”
这一串。
现在我们有了所有信息,就能够编写脚本解决这个问题了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from Crypto.Cipher import AESfrom pkcs7 import PKCS7Encoderimport sqlite3import base64key = b'26a6f9cc-d019-4f5d-8a1b-a352b7738f42' [0 :16 ] mode = AES.MODE_CBC iv = "0000000000000000" encryptor = AES.new(key, mode, iv) encoder = PKCS7Encoder() def decode (text ): encryptor = AES.new(key, mode, iv) plain_text = encryptor.decrypt(text) pad = plain_text[-1 ] if pad > 0 and pad <=16 : plain_text = plain_text[:len (plain_text)-pad] return plain_text if __name__ == '__main__' : conn = sqlite3.connect("flag.sqlite" ) cur = conn.cursor() table = cur.execute("SELECT * FROM flag_table" ) s = table.fetchall() ans = [] for each_id, each_con in s: ans.append(decode(base64.standard_b64decode(each_con.encode("utf-8" )))) for each in ans: if b"RCTF" in each: print (each)
吐槽一下,这个CBC模式好坑啊,会保留上一次运算结果这一点。。。只能每次new一个新的对象出来,才能够避免上次的影响。。。