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.