Good health! I would not be surprised that you have not even heard about this program before. Like me, until the day when the
Python Debugger came in handy. Yes, I know, there is
pdb , but its functionality and the way it is presented, I didn’t like at all. After a brief search, I stumbled upon this wonderful product. There is everything that can be useful in debugging your
Python applications (I’ll say right away that I haven’t studied this language, so if any inaccuracies come up, please don't swear).
Caution: repeating actions from the article, you act at your own risk!
So, we start ...
The patient, I must say, is unusual. First of all: it comes with source code (!!!), even in bytecode; secondly, as sometimes happens ... well, see.
First of all, download the program (
Wing IDE Professional v 5.1.4 ). Install, inspect the folder. The main executable file is located at
./bin/wing.exe . Run it. Swears at the lack of
Python , so install it. Version
2 is
required (at the moment it is version
2.7.9 ). Run the program again. This time offers to install patches, and restart. So do.
')
Now a window pops up asking for a license (because we have a pro version). Let's introduce some nonsense:
We get the following answer:
What is funny: the program itself tells us the length of the key (20, not including the hyphens), and the characters with which it should start. In principle, it is already possible to start exploring protection from this - we will find this line in the program files.
Further - more interesting. The search result was found in the file
./bin/2.7/src.zip !
Yes Yes. Everything is really like this: the program comes with source. In them we also should dig.
Stage Two: Digging the Source
Let's include in Archive search in
Total Commander , and we will find that line again. The line is in the file:
./bin/2.7/src.zip/process/wingctl.pyo .
PYO files are binary with
optimized Python bytecode.
Fortunately for Python, there are a couple of bytecode decompilers. In order not to bother you with searches, I will give links to those that came in handy to me:
- Easy Python Decompiler ( EPD ) - a shell in which two decompilers are wired ( Uncompyle2 and Decompyle ++ );
- Fork Uncompyle2 - sometimes unpacks what others can not unpack.
So, we will unpack the entire src.zip archive into the
src folder (next to it there is the
src folder, let them unpack and everything else) and add
EPD to it:
We wait until the end of the process, and we go to inspect what happened. And it turned out decompiled files with the end of
_dis . We will rename them to
.py . Everything would be fine, but it turns out that there are also files with the end of
_dis_failed , which means that the decompiler has not mastered these files. Fortunately, there is only one file:
edit / editor.pyo_dis_failedLet's try to incite
Decompyle ++ on it ... The same trouble. No wonder I gave a link to a spare decompiler, because it was he who did what others failed. Now, remove all
pyo /
pyc files from the src folder, and rename the
.py * _dis file to
.py .
Next, repeat all of the above for the archive
opensource.zip , unpacking it in the next folder of the same name. Archive
external.zip I decided not to touch, because, having examined it, you can see that there are libraries that can be installed separately for our Python. So do:
pip install docutils
- py2pdf - put it in the external folder;
- Imaging-1.1.7 - run and install. From the external folder can be deleted;
- pygtk is the same as previous files.
The remaining libraries (
pyscintilla2 and
pysqlite ) are simply extracted from
external.zip , and decompiled as before.
Stages three and four: the source code itself. Debugging
Having rummaged through Python scripts, I came across the wing.py
file in the root of the program folder. And the first comment tells us:
In a nutshell: if the script is given the
--use-src parameter, then the source will be used from the
src ,
external ,
opensource directories of the root directory with the
Wing IDE (and not with the script).
Looking into the root folder, I found another
src folder, and
.py files in it. Throw them in our
src folder, with overwriting (here all the same originals, not decompiled files).
Now all three folders (indicated just above), copy to the root directory of the program. Let's try it ...
Run the
Wing IDE , and open the
wing.py file from the
bin directory in it. Next in the menu
Debug -> Debug Environment ... in the parameter field specify -
use-src . Now start the debugger (
F5 key). If all frauds with copying folders were successful, we will get a second copy of the running
Wing IDE . Perfectly!
Next: open the file in the parent
Wing IDE , in which we found the line on the bad
license id (
wingctl.py )
earlier , and set the bryak to this message:
In the debugging
Wing IDE, go to the menu
Help -> Enter License ... , and enter the key according to the rules (remember ?:
20 characters, with the first one from the set
['T', 'N', 'E', 'C' , '1', '3', '6'] ):
We press
Continue and we get on grandma bryaku. The first interesting function:
abstract.ValidateAndNormalizeLicenseID (id) . Go to it for
F7 . There's one more:
__ValidateAndNormalize (id) . Head over to her.
First validation check:
for c in code: if c in ('-', ' ', '\t'): pass elif c not in textutils.BASE30: code2 += c badchars.add(c) else: code2 += c
We see that we are required to have the characters of the
License ID belong to the
textutils.BASE30 set:
BASE30 = '123456789ABCDEFGHJKLMNPQRTVWXY'
There seems to be no other checks in
__ValidateAndNormalize (id) . We correct the identifier entered by us and we repeat again. We have already checked the first character:
if len(id2) > 0 and id2[0] not in kLicenseUseCodes: errs.append(_('Invalid first character: Should be one of %s') % str(kLicenseUseCodes))
And here is the second character:
if len(id2) > 1 and id2[1] != kLicenseProdCode:
kLicenseProdCodes = {config.kProd101: '1', config.kProdPersonal: 'L', config.kProdProfessional: 'N', config.kProdEnterprise: 'E'} kLicenseProdCode = kLicenseProdCodes[config.kProductCode]
Since we have the
Professional version, then the second character must be
N - correct, and come back.
abstract.ValidateAndNormalizeLicenseID (id) passed without errors. Perfectly. Oops:
if len(errs) == 0 and id[0] == 'T': errs.append(_('You cannot enter a trial license id here'))
Fix (I chose
E ), and continue. Having run through the eyes below the code, I did not find anything in addition to the previous checks, so I boldly released debugging further along
F5 . New window:
We enter a random text, we get an error message (again
20 characters, and the activation code must start with
AXX ), we find it in the files, set the breakpoint:
The first verification function is
abstract.ValidateAndNormalizeActivation (act) . It again checks on the ownership of
BASE30 . Check for the prefix, which we have already passed:
if id2[:3] != kActivationPrefix: errs.append(_("Invalid prefix: Should be '%s'") % kActivationPrefix)
The following interesting place:
err, info = self.fLicMgr._ValidateLicenseDict(lic2, None) if err == abstract.kLicenseOK:
Go to
self.fLicMgr._ValidateLicenseDict . A license hash is formed here:
lichash = CreateActivationRequest(lic) act30 = lic['activation'] if lichash[2] not in 'X34': hasher = sha.new() hasher.update(lichash) hasher.update(lic['license']) digest = hasher.hexdigest().upper() lichash = lichash[:3] + textutils.SHAToBase30(digest) errs, lichash = ValidateAndNormalizeRequest(lichash)
If you look at the contents of
lichash after executing this block, you will notice that its text is similar to the
request code displayed in the activation code entry box, although a few numbers are different. Okay, we will think that there are some random parts that do not affect activation (which, by the way, will be further confirmed!).
Then, the first three characters are cut off from the activation code, hyphens are removed, converted to
BASE16 , and padded with zeros if necessary:
act = act30.replace('-', '')[3:] hexact = textutils.BaseConvert(act, textutils.BASE30, textutils.BASE16) while len(hexact) < 20: hexact = '0' + hexact
And here it is, the most interesting:
valid = control.validate(lichash, lic['os'], lic['version'][:lic['version'].find('.')], hexact)
Some
control calls the
validate function, passing it the
lichash (
request code ), the name of the operating system for which the key is being made, the version of the program, and the converted activation code. Why did I stop attention at this place? The fact is that this
control is a
pyd file (as can be seen by adding the object name to
watch , and looking at the
__file__ field), which are ordinary
DLLs with one exported function (not
validate ), which gives Python information about what she can do. Well, let's look at it from the
Hex Rays decompiler ...
Stage Five: This Is Not Python
Drag in our
control in
IDA Pro (
ctlutil.pyd ) and look at the exported
initctlutil function:
int initctlutil() { return Py_InitModule4(aCtlutil, &off_10003094, 0, 0, 1013); }
off_10003094 is a structure in which the names and the address of the exported methods are indicated. Here is our
validate :
.data:100030A4 dd offset aValidate ; "validate" .data:100030A8 dd offset sub_10001410
Of all the code that contains the procedure
sub_10001410, the most interesting is this:
if ( sub_10001020(v6, &v9) || strcmp(&v9, v7) ) { result = PyInt_FromLong(0); }
Head over to
sub_10001020 too. It would be interesting not to give the names of variables by eye, but to give them a bad name and name them. So do. Configure the
IDA Pro debugger:
I think everything is clear from the screenshot: we have specified an application that will eventually load our
pyd file.
Now set the breakpoint at the beginning of
sub_10001020 , and start looking into the variables and input parameters. After a short debugging process, we come to the following listing function:
Convert_reqest_key function code int __usercall convert_reqest_key@<eax>(char *version@<eax>, const char *platform@<ecx>, const char *activation_key, char *out_key) { unsigned int len_1;
And the place to call this function takes the following form:
if ( convert_reqest_key(version, platform, request_key, out_key) || strcmp(out_key, act_key_hash) ) { result = PyInt_FromLong(0); }
From all this we can conclude that the
request code is converted using the function
convert_reqest_key and then compared with the converted activation code. Remember that conversion?
Then, the first three characters are cut off from the activation code, hyphens are removed, converted to BASE16 , and padded with zeros if necessary.
So, in order to get the correct activation code, we can now proceed as follows:
- Let the convert_reqest_key conversion function execute ;
- Look at the contents of out_key at the place of execution of strcmp ;
- Remove extra zeros at the beginning of out_key ;
- Convert out_key back to BASE30 ;
- Append three characters to the beginning of the resulting string ( AXX );
- Navigate hyphens every five characters if desired.
I will not philosophize slyly, but I will squeeze
print directly into
python , the program code:
print("AXX" + textutils.BaseConvert("FCBCFEFD2FF684FA6A4F", textutils.BASE16, textutils.BASE30))
At the output I received a key:
wingide - 2015/05/24 04:03:47 - AXX3Q6BQHKQ773D24P58
Entering it in the input field of the activation key, I received the cherished
RESULTS
As you can see, the hacking process is not so difficult as it turned out to be interesting! It’s fun to explore your sources in the compiled version ...
I do not know why the authors attached to its program its source code (albeit for the most part, in the form of byte-code). But I think you understand that you should not do this!
Thanks to all.