As I’ve mentioned maybe once or twice before, I like myAutToExe a good deal. It’s great for tinkering around with AutoIt programs that have been “secured” by compiling to tokens. In some situations, being able to decompile these scripts is an absolute necessity to allow users to check for security problems in an application. In some situations, it’s just fun to see how somebody did something. (The legality of doing this is not something I’m going to even pretend to know, so don’t ask. When in doubt, DON’T DO IT.)
Obfuscation can become a major problem, however, especially with a recent technique I’ve run into. myAutToExe does deal with obfuscation to some degree, but not as deeply as I’d like. Consider the example illustrated below:
Global $000O0O0O0O0000OOO00 = False Global $OO0O0O0O00O0OO00OO0 = False Global $000O0O0O0OOOOOO000O = False Global $00OOOO0O0O00OO00O0O = False Global $0O0O0O0O00O0OOO000O = True Global $000O0O0O000O0O0O000 = True Global $000O0O0OOO0O0O0O0OO = True Global $O00O0O0O000O0O0O0OO = True Global $000000000000000000 = "Enter your username:" Global $0000000000000O0O00 = "Enter your password:" Global $0000000000000O0O0OOO = "Invalid login information. Try again." Global $00OO000000000O0O0OOO = "Sorry" Global $0000000000000O0O0O00 = "Thank you for your login. I have now stolen your account, hahaha!" Global $00OO000000000O0O0O00 = "SUCKER!!" Global $0O00000000000O0O0OOO = 0 Func S000OOO00O000OOOO00O($00OOOO0O0O00OO00O) $00OO000000000O0O0O00 = "Loser!!" $00OO000000000O0O0OOO = "Sorry" if ($00OOOO0O0O00OO00O = $OO0O0O0O00O0OO00OO0) Then MsgBox($0O00000000000O0O0OOO, $00OO000000000O0O0OOO, $0000000000000O0O0OOO) S00OOO00OOOOO0O() Else MsgBox($0O00000000000O0O0OOO, $00OO000000000O0O0O00, $0000000000000O0O0O00) S00OOO00OOOOO00() EndIf EndFunc S000OOO00O000OOOO00O($000O0O0O0OOOOOO000O) S000OOO00O000OOOO00O($O00O0O0O000O0O0O0OO)
Okay, we clearly know that somewhere this code is stealing our account data, but we also know that the code is useful, generally speaking. Assuming this code were buried in a listing that’s a few thousand lines long, just trying to figure it out by hand would get tedious really fast.
Well, the ruby script I’ve written takes care of all that. When it finds a global string with this particular variable pattern, it tries to figure out the best way to represent it. It may remove the variable completely and just present a raw string, or if the string is long and used in a lot of places, just give the string a useful name. Observe:
Global $g_false7 = False Global $g_false6 = False Global $g_false5 = False Global $g_false4 = False Global $g_true3 = True Global $g_true2 = True Global $g_true1 = True Global $g_true0 = True Global $g_000013_ = "SUCKER!!" Global $g_000014_ = 0 Func S000OOO00O000OOOO00O($v_000015_) $g_000013_ = "Loser!!" if ($v_000015_ = $g_false6) Then MsgBox($g_000014_, "Sorry", "Invalid login information. Try again.") S00OOO00OOOOO0O() Else MsgBox($g_000014_, $g_000013_, "Thank you for your login. I have now stolen your account, hahaha!") S00OOO00OOOOO00() EndIf EndFunc S000OOO00O000OOOO00O($g_false5) S000OOO00O000OOOO00O($g_true0)
We were able to name all true/false values to something very simple and in-line most of the strings. It’s now very clear which code path is dangerous, and should be examined further.
The code to do this is attached. Keep in mind that it is a work in progress and I’m very likely to forget to update it here, so email me if you’re interested in the thing. Future plans include renaming functions to somehow reflect their signature, and deal with local variables that are read-only, such as those assigned to a global and then used for comparison.
If possible, I’d like to one day build a true parser, but so far lex and yacc have proven to be far too confusing to one such as myself :(
fix_names.rb – this is the main script.
lib.rb – this is the supporting library.
Copy both ruby scripts to the same directory, and run them something like this:
ruby fix_names.rb <input_file> <output_file>
I hope this is useful!