UWP逆向初接触

最近博客写的少了,发现整个人学习的动力也不够了哇,还是得边写边学才好


UWP逆向初接触

其实我还是第一次听说UWP这个概念来着。。。首先先查询一下这个东西是个啥吧

UWP – Universal Windows Platform

在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 AES
from pkcs7 import PKCS7Encoder
import sqlite3
import base64


#declared outside of all functions
key = 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 = []
# print(s)
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)

# RCTF{Micr0s0ft_1aji}

吐槽一下,这个CBC模式好坑啊,会保留上一次运算结果这一点。。。只能每次new一个新的对象出来,才能够避免上次的影响。。。