Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for deobfuscation #4

Merged
merged 11 commits into from
Feb 16, 2024
10 changes: 5 additions & 5 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion

# Types: use keywords instead of BCL types, and permit var only when the type is clear
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
# Types: use keywords instead of BCL types, and always suggest var
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion

Expand Down Expand Up @@ -152,7 +152,7 @@ csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

# License header
file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
# file_header_template =

# C++ Files
[*.{cpp,h,in}]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,4 @@
<PackageReference Include="Newtonsoft.Json" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Dax.Vpax.Obfuscator" />
<InternalsVisibleTo Include="Dax.Vpax.Obfuscator.Common.Tests" />
</ItemGroup>

</Project>
40 changes: 25 additions & 15 deletions src/Dax.Vpax.Obfuscator.Common/ObfuscationDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ public sealed class ObfuscationDictionary
private readonly Dictionary<string, ObfuscationText> _obfuscated;

[JsonConstructor]
internal ObfuscationDictionary(string id, ObfuscationText[] texts)
public ObfuscationDictionary(string id, ObfuscationText[] texts)
{
if (id == null) throw new ArgumentNullException("The dictionary identifier cannot be null.", nameof(id));
if (!IsValidId(id)) throw new ArgumentException("The dictionary identifier is not valid.", nameof(id));
if (id == null || !Guid.TryParseExact(id, "D", out var guid) || guid == Guid.Empty)
throw new ArgumentException("The dictionary identifier is not valid.", nameof(id));

Id = id;
Texts = texts.OrderBy((t) => t.Value).ToArray();
Expand All @@ -26,9 +26,26 @@ internal ObfuscationDictionary(string id, ObfuscationText[] texts)
public string Id { get; }
public IReadOnlyList<ObfuscationText> Texts { get; }

public string GetValue(string obfuscated)
{
if (_obfuscated.TryGetValue(obfuscated, out var text))
return text.Value;

throw new KeyNotFoundException($"The obfuscated value was not found in the dictionary [{obfuscated}].");
}

public string GetObfuscated(string value)
{
if (_plaintexts.TryGetValue(value, out var text))
return text.Obfuscated;

throw new KeyNotFoundException($"The value was not found in the dictionary [{value}].");
}

public bool TryGetValue(string obfuscated, [NotNullWhen(true)] out string? value)
{
if (_obfuscated.TryGetValue(obfuscated, out var text)) {
if (_obfuscated.TryGetValue(obfuscated, out var text))
{
value = text.Value;
return true;
}
Expand All @@ -39,7 +56,8 @@ public bool TryGetValue(string obfuscated, [NotNullWhen(true)] out string? value

public bool TryGetObfuscated(string value, [NotNullWhen(true)] out string? obfuscated)
{
if (_plaintexts.TryGetValue(value, out var text)) {
if (_plaintexts.TryGetValue(value, out var text))
{
obfuscated = text.Obfuscated;
return true;
}
Expand Down Expand Up @@ -79,18 +97,10 @@ public static ObfuscationDictionary ReadFrom(string path)
public static ObfuscationDictionary ReadFrom(Stream stream, bool leaveOpen = false)
{
using (var streamReader = new StreamReader(stream, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen))
using (var reader = new JsonTextReader(streamReader)) {
using (var reader = new JsonTextReader(streamReader))
{
var serializer = JsonSerializer.Create();
return serializer.Deserialize<ObfuscationDictionary>(reader) ?? throw new InvalidOperationException("The deserialized dictionary is null.");
}
}

internal static bool IsValidId(string value)
{
if (value == null) return false;
if (!Guid.TryParseExact(value, "D", out var guid)) return false;
if (guid == Guid.Empty) return false;

return true;
}
}
6 changes: 3 additions & 3 deletions src/Dax.Vpax.Obfuscator.Common/ObfuscationText.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ namespace Dax.Vpax.Obfuscator.Common;
public sealed class ObfuscationText
{
[JsonConstructor]
internal ObfuscationText(string value, string obfuscated)
public ObfuscationText(string value, string obfuscated)
{
Value = value;
Obfuscated = obfuscated;
Value = value ?? throw new ArgumentNullException(nameof(value));
Obfuscated = obfuscated ?? throw new ArgumentNullException(nameof(obfuscated));
}

public string Value { get; }
Expand Down
15 changes: 14 additions & 1 deletion src/Dax.Vpax.Obfuscator.TestApp/Form1.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 49 additions & 6 deletions src/Dax.Vpax.Obfuscator.TestApp/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@ private void buttonObfuscate_Click(object sender, EventArgs e)
if (folderBrowserDialog1.ShowDialog() != DialogResult.OK) return;

buttonObfuscate.Enabled = false;
try {
try
{
var dictionary = checkBoxIncrementalFileObfuscation.Checked ? new FileInfo(openDictionaryFileDialog.FileName) : null;
var vpax = new FileInfo(openVpaxFileDialog.FileName);

Obfuscate(vpax, dictionary, outputPath: folderBrowserDialog1.SelectedPath);
MessageBox.Show("Obfuscation completed.", "Obfuscation", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex) {
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Obfuscation", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally {
finally
{
buttonObfuscate.Enabled = true;
}
}
Expand All @@ -46,16 +49,19 @@ private void buttonObfuscateDirectory_Click(object sender, EventArgs e)
var outputPath = folderBrowserDialog1.SelectedPath;

buttonObfuscateDirectory.Enabled = false;
try {
try
{
foreach (var vpax in new DirectoryInfo(inputPath).GetFiles("*.vpax"))
Obfuscate(vpax, dictionaryFile: null, outputPath);

MessageBox.Show("Obfuscation completed.", "Obfuscation", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex) {
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Obfuscation", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally {
finally
{
buttonObfuscateDirectory.Enabled = true;
}
}
Expand All @@ -77,5 +83,42 @@ private void Obfuscate(FileInfo vpaxFile, FileInfo? dictionaryFile, string outpu
dictionary.WriteTo(dictionaryPath, overwrite: checkBoxOverwriteDictionary.Checked, indented: true);
File.WriteAllBytes(vpaxPath, stream.ToArray());
}

private void buttonDeobfuscate_Click(object sender, EventArgs e)
{
if (openVpaxFileDialog.ShowDialog() != DialogResult.OK) return;
if (openDictionaryFileDialog.ShowDialog() != DialogResult.OK) return;

buttonDeobfuscate.Enabled = false;
try
{
var dictionary = new FileInfo(openDictionaryFileDialog.FileName);
var vpax = new FileInfo(openVpaxFileDialog.FileName);

Deobfuscate(vpax, dictionary);
MessageBox.Show("Deobfuscation completed.", "Deobfuscation", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Deobfuscation", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
buttonDeobfuscate.Enabled = true;
}
}

private void Deobfuscate(FileInfo vpaxFile, FileInfo dictionaryFile)
{
var data = File.ReadAllBytes(vpaxFile.FullName);
using var stream = new MemoryStream();
stream.Write(data, 0, data.Length);

var obfuscator = new VpaxObfuscator();
obfuscator.Deobfuscate(stream, ObfuscationDictionary.ReadFrom(dictionaryFile.FullName));

var vpaxPath = Path.Combine(vpaxFile.DirectoryName!, Path.ChangeExtension(vpaxFile.Name, ".deobfuscated.vpax"));
File.WriteAllBytes(vpaxPath, stream.ToArray());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Dax.Vpax.Obfuscator.Extensions;
using System.Text;
using TabularEditor.Dax.Tokenizer;

namespace Dax.Vpax.Obfuscator;

internal sealed partial class DaxModelDeobfuscator
{
internal string DeobfuscateExpression(string expression)
{
var tokens = DaxTokenizer.Tokenize(expression, DaxLocale.US, includeHidden: true);
var builder = new StringBuilder(expression.Length);

foreach (var token in tokens)
{
var tokenText = token.Text;

if (tokenText.Length == 0)
{
switch (token.Type)
{
case DaxToken.STRING_LITERAL:
builder.Append("\"\"");
break;
case DaxToken.TABLE:
builder.Append("''");
break;
}
continue;
}

switch (token.Type)
{
case DaxToken.SINGLE_LINE_COMMENT:
case DaxToken.DELIMITED_COMMENT:
tokenText = _dictionary.GetValue(tokenText);
break;
case DaxToken.COLUMN_OR_MEASURE when token.IsReservedExtensionColumn():
tokenText = token.Replace(expression, tokenText);
break;
case DaxToken.STRING_LITERAL when token.IsExtensionColumnName():
tokenText = ReplaceExtensionColumnName(token);
break;
case DaxToken.TABLE_OR_VARIABLE when token.IsVariable():
case DaxToken.TABLE:
case DaxToken.COLUMN_OR_MEASURE:
case DaxToken.STRING_LITERAL:
case DaxToken.UNTERMINATED_COLREF:
case DaxToken.UNTERMINATED_TABLEREF:
case DaxToken.UNTERMINATED_STRING:
tokenText = token.Replace(expression, _dictionary.GetValue(tokenText));
break;
}

builder.Append(tokenText);
}

return builder.ToString();

string ReplaceExtensionColumnName(DaxToken token)
{
var (tableName, columnName) = token.GetExtensionColumnNameParts();
tableName = _dictionary.GetValue(tableName);
columnName = _dictionary.GetValue(columnName);

var value = $"{tableName.DaxEscape()}[{columnName.DaxEscape()}]";
return token.Replace(expression, value, escape: true);
}
}
}
Loading
Loading