Conseils sur la décompilation et la désobfuscation

Conseils sur la décompilation et la désobfuscation

Il y a quelques semaines, je me suis fait volé mon pc portable avec quelques codes sources de projet que je n’avais pas encore sauvegardé car sans internet (quasiment 10 jours de travail de perdu).

Toutefois j’avais encore les xap à ma disposition ce qui m’a aidé a récupérer le code sources (offusqué) de l’app. Pour être honnête, même offusqué, en une dizaine d’heures et avec quelques aspirines, vous pouvez facilement revenir à l’original.

Voilà pour information à quoi ressemble un code offusqué une fois décompilé :

public class YoutubeHelper
{
[CompilerGenerated]
private sealed class a
{
private sealed class a
{
public YoutubeHelper.a a;
public List b;
public void c()
{
if (this.a.c)
{
this.a.b.Videos.Clear();
}
using (List.Enumerator enumerator = this.b.GetEnumerator())
{
while (enumerator.MoveNext())
{
YoutubeVideo current = enumerator.get_Current();
this.a.b.Videos.Add(current);
}
}

}
}
public HttpWebRequest a;
public YoutubeHelper b;
public bool c;
public void d(IAsyncResult A_0)
{
YoutubeHelper.a.a a = new YoutubeHelper.a.a();
a.a = this;
Stream responseStream = this.a.EndGetResponse(A_0).GetResponseStream();
a.b = this.b.ParseYoutubeData(responseStream);
if (a.b != null)
{
Deployment.get_Current().get_Dispatcher().BeginInvoke(new Action(a.c));
this.b._youtubeCurrentPage = 0;
}
}
}
[CompilerGenerated]
private bool b;
[CompilerGenerated]
private string c;
[CompilerGenerated]
private ObservableCollection d;

public void LoadYoutube(bool clear)
{
AsyncCallback asyncCallback = null;
YoutubeHelper.a a = new YoutubeHelper.a();
a.c = clear;
a.b = this;
this.Loading = true;
this.RaisePropertyChanged("Loading");
string text = string.Format("http://gdata.youtube.com/feeds/api/videos?q={0}&start-index={1}&max-results=20&v=2", this.Search, this._youtubeCurrentPage);
a.a = (HttpWebRequest)WebRequest.Create(new Uri(text, 1));
try
{
WebRequest arg_73_0 = a.a;
if (asyncCallback == null)
{
asyncCallback = new AsyncCallback(a.d);
}
arg_73_0.BeginGetResponse(asyncCallback, null);
}
catch (Exception)
{
}
}

}

En gros : ca pique les yeux, c’est difficilement lisible mais avec un peu de courage, ça se simplifie et surtout, ca ne compile pas.

Voilà donc un petit guide pour vous aider à la tâche !

Reconstruire vos pages et vos usercontrols

Imaginons que vous avez une page nommée MainPage.xaml (qui ne l’a pas ???)

Lorsque vous allez décompiler votre projet, vous allez avoir deux fichiers : mainpage.xaml et MainPage.cs. Commençons par les renommer correctement : MainPage.xaml et MainPage.xaml.cs et mettez Page en tant que Build Action pour votre page xaml.

Allons maintenant dans le code behind et indiquons notre classe comme partial. Supprimons le code qui a été auto-généré lors de la compilation initiale:

  • Supprimez la fonction InitializeComponent
  • Repérez les variables correspondants à votre élément XAML, ceci est plutôt simple, il précède la variable “private bool _contentLoaded” et sont indiqué comme internal :
  • Supprimez la variable _contentLoaded

Vos deux fichiers devraient maintenant être liés.

Toutefois vous remarquerez que visual ne fusionne pas les deux fichiers comme d’habitude :

Pour cela, pas besoin de s’embêter à éditer le fichier vcproj, installez (si ce n’est pas déjà fait) VSCommands (ici : http://visualstudiogallery.msdn.microsoft.com/d491911d-97f3-4cf6-87b0-6a2882120acf/)

Sélectionnez les deux éléments puis faites un clic droit et sélectionnez “Group Items”

Vous voila maintenant avec les deux fichiers regroupés comme d’origine.

Gérer les lambdas

Pour résumer le travail, toutes les lamba expressions ont été converti par le compilateur en classe interne, il faut donc les retransformer tout en faisant attention à un point : les classes sont nommés comme les variables et les fonctions soient : a, b, c … un même nom peut donc représenter à la fois une classe interne, une fonction et une variable, autant vous dire que la fonction rename de visual s’y perd.

Les tableaux

Voilà à quoi ressemble un tableau une fois décompilé :

accesseur :

int i = a[20];

=> int i = a.get_Item(0);

affectation :

a[20]=3;

=> a.set_Item(20,3);

Voici donc quelques expressions régulières pour vous aider :

replace .get_Item({[^)(]+}) par [1]

et

replace .set_Item({[^),(]+},{[^),(]+}) par [1] = 2

Attention, ces expressions régulières ne sont pas parfaites, elles ne prennent pas les expressions contenant des parenthèses à l’intérieur comme : a[toto(12)]. Faites donc une recherche de set_Item et de get_item après coup pour gérer les cas restants.

Les propriétés

Un des points positifs lorsque l’on doit décompiler un programme est que les propriétés même après offuscation, garde leurs noms (sinon il serait impossible de gérer les RaisePropertyChanged par exemple), toutefois, les décompilateurs du marché ont un peu de problèmes avec, par exemple :

MyGrid.Width=200;

deviendra :

MyGrid.set_Width(200);

ou encore :

int i=MyGrid.Width;

deviendra

int a=MyGrid.get_Width();

il faudra donc passer un peu de temps pour réécrire l’ensemble des affections ou utilisations des propriétés. Voici quelques expression régulière pour vous aider dans cette tâche (à n’utiliser que si vous n’avez pas de fonction nommées set_XXX ou get_XXX)

Modifier les accesseurs :

.get_{[^(]+}()   en .1

De même pour modifier les affectateurs

.set_{[^(]+} en .1=

Enregistrement et dés-enregistrement d’event handler

Après décompilations, vos events vont ressembler à cela :


contacts.add_SearchCompleted(delegate(object senderr, ContactsSearchEventArgs er)

au lieu de :


contacts.SearchCompleted+=(delegate(object senderr, ContactsSearchEventArgs er)

de même pour le dés-enregistrement.

Voici donc deux expressions régulières pour corriger cela.

enregistrement :

replacez .add_{[^(]+} par .1+=

dés-enregistrement :

replacez .remove_{[^(]+} par .1-=

 

Les affectations multiples

Si vous écrivez :


Toto.ScaleY=Toto.ScaleX=0;

une fois compilé, offusqué et redécompilé, le code deviendra :


ScaleTransform arg_108_0 = this.Toto;
double num3;
this.Toto.set_ScaleY(num3 = 0);
double initialScale;
arg_108_0.set_ScaleX(num3);

A vous donc de supprimer l’ensemble des variables intermédiaire pour simplifier la relecture de votre code.

Les énumérations

Toutes les énumérations sont interprété en tant que int dans le code décompilé, par exemple :


new Uri("/toto.xaml,Urikind.Relative);

sera décompilé comme :


new Uri("/toto.xaml,2);

Ce qui est tout de suite moins parlant. Pour vous faciliter le travail, je conseille vivement Refactor.

Pour cela, castez votre int avec la bonne énumération :


new Uri("/toto.xaml,(UriKind)2);

Puis sélectionnez dans la marge, l’outil “Compute constant value”.

Refactor se chargera alors automatiquement de trouver la bonne valeur pour l’énumération. Cela est encore plus intéressant quand on sait qu’il gère aussi automatiquement les OR binaire :

(RegexOptions) 5 

Deviendra alors :

RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.None

 Override & virtual

Attention, parfois les méthodes override seront vu comme des méthodes virtuelles par le décompilateur, ce qui signifie qu’elles ne surchargeront pas parfois les méthodes initiales, par exemple :

protected virtual void OnNavigatedTo(NavigationEventArgs e)

ne sera jamais appelé contrairement à

protected override” void OnNavigatedTo(NavigationEventArgs e)

Gestion de la localisation

Une des difficultés que j’ai rencontré est de récupérer les localisations de mes apps.

Lorsque vous allez décompiler votre dll, vous aller trouver un fichier XXX.resources. L’extension resources correspond à la version binaire des fichiers resx de votre projet. Il est assez facile de le reconvertir en resx, grâce à l’outils resgen.exe inclut dans l’installation de visual studio.

Ouvrez “Visual Studio Command Prompt” et tapez la commande suivante :

resgen AppResources.resources AppResources.resx

(où AppResources correspond à votre fichier ressources)

Lorsque l’on ouvre le fichier resx généré, on se rend compte qu’il ne contient que la localisation par défaut de votre application.

Où trouver les autres localisations ?

Ouvrez de nouveau votre xap, vous remarquerez des dossiers fr, es, de…

Dans ces dossiers vous allez trouver des fichiers resources.dll

Si on décompile ce fichier, on peut voir :

que celle-ci contient le fichier “.resources” propre à une langue. Il nous reste donc à le sauvegarder, à le convertir en resx comme auparavant et de faire ceci pour chaque langue.

 

Comments are closed.