Skip to content

Commit

Permalink
Add support for deobfuscation (#4)
Browse files Browse the repository at this point in the history
* Remove license header template in .editorconfig

* Update csharp_style_var in .editorconfig

* Add IsValidDictionaryId extension methdo

* Hard code ObfuscatorLib name instead using reflection

* Use `IsValidDictionaryId()` in dictionary .ctor

* Implement deobfuscation

* Add unit tests

* Add DaxText.ToString override

* Making VpaxObfuscator impl methods static

* Making Common .ctors public

* Bump version 0.2-beta
  • Loading branch information
albertospelta authored Feb 16, 2024
1 parent 62ab62c commit 71817f2
Show file tree
Hide file tree
Showing 19 changed files with 704 additions and 134 deletions.
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

0 comments on commit 71817f2

Please sign in to comment.