| 416-621-9348 cgreaves@chrisgreaves.com | |
|---|---|
| Home Site About Services Products |
Obfuscation of Source Code
One can lock or password-protect projects in languages such as VBA, but one cannot thwart the criminal element who think it fun to break into the source code.
You can compile your code into a DLL if you have a copy of Visual Basic or similar, and if you are prepared to cope with the problem of installing updates on your client’s machines.
Strip Comments and White Space
You already use Rob Bovey’s Code Cleaner to strip out comments and reduce the size of the installed template; that’s a form of obfuscation.
Let’s face it, we use indentation (project’s NESTR does the job for you) and comments to make code readable; it follows that we avoid comments and white space to make code Unreadable)
There are several other techniques that can be implemented at the source code level, amongst them identifier-replacement and line-rearrangement.
Identifier Scrambling
Below is an example of identifier scrambling:
Public Function wxomrbwrvixfvyohprnayrxbjwdid(tlocoyweuffu As String) As String Dim ujjquljlkubf As String ujjquljlkubf = "" If blnfileexists(tlocoyweuffu) Then ujjquljlkubf = modImages.strAsciiFromImageFile(tlocoyweuffu, " ") Dim twjbe() As String Call BuildAssignmentFromString(twjbe, "yw", ujjquljlkubf, 70) Else ReDim twjbe(0) End If Dim vcvaohbex As String vcvaohbex = vcvaohbex & vbCrLf & "sub " & pnmmwvjozbizwezkslydsa & UW.modDPNE.strGetName(tlocoyweuffu) & evdlwcntubycgllrsxxcfy vcvaohbex = vcvaohbex & vbCrLf & "dim yw as string" Dim shnekxw As Long For shnekxw = LBound(twjbe) To UBound(twjbe) - 1 If Len(twjbe(shnekxw)) > 0 Then vcvaohbex = vcvaohbex & vbCrLf & twjbe(shnekxw) Else End If Next shnekxw vcvaohbex = vcvaohbex & vbCrLf & "call vabfetvflgi(yw," & """" & """)" wxomrbwrvixfvyohprnayrxbjwdid = vcvaohbex & vbCrLf & "end sub" End Function
In this example, procedure identifiers, procedure arguments, and data declaration statements have been identified, the identifiers extracted and scrambled with a randomized string. The randomizing is time-dependent; a later run through the obfuscator will produce a different set of randomized strings.
The comment lines have been eliminated with Rob Bovey’s code-cleaner.
Since the procedure name is scrambled, it will in general not be available to external routines. So identifier obfuscation is a bad idea for a utility library or any application that offers an interface to developers.
The procedure names and procedure argument names are obtained through PROJE’s analysis table, so switches are available to exclude specific types of procedures; we could scramble all identifiers (procedures and declarations) marked as PRIVATE and leave intact identifiers marked specifically as PUBLIC”
For example, we could obfuscate all but the macro names, leaving the end—user interface intact but denying access for the developer.
The identifiers “blnfileexists” and “BuildAssignmentFromString” were not scrambled because they were not defined in the project; they are procedures in a referenced/linked project.
The identifier “modImages.strAsciiFromImageFile” likewise gives away the name of an external module and procedure name, while “UW.modDPNE.strGetName” also gives away the project name.
Line-Rearrangement
Public Function ProjectObfuscator(os As OptionsStructure, vbp As VBProject) ‘ (1) Call LoadOptions(os) ‘ (2) Dim strAr() As String ‘ (3) Call LoadProjectToArray(os, vbp, strAr) ‘ (4) Dim strObf() As String ‘ (5) ReDim strObf(1, 0) ‘ (6) Call ObtainObfuscation(strAr, strObf, vbp) ‘ (7) Call GenerateObfuscation(strObf) ‘ (8) Call ImplementObfuscation(strObf, vbp) ‘ (9) End Function ‘ (10)
The procedure above can be rewritten with its lines re-arranged:
Public Function ProjectObfuscator(os As OptionsStructure, vbp As VBProject) ‘ (1) GoTo 1 2: Dim strAr() As String ‘ (3) GoTo 3 4: Dim strObf() As String ‘ (5) GoTo 5 6: Call ObtainObfuscation(strAr, strObf, vbp) ‘ (7) GoTo 7 8: Call ImplementObfuscation(strObf, vbp) ‘ (9) Exit Function 1: Call LoadOptions(os) ‘ (2) GoTo 2 3: Call LoadProjectToArray(os, vbp, strAr) ‘ (4) GoTo 4 5: ReDim strObf(1, 0) ‘ (6) GoTo 6 7: Call GenerateObfuscation(strObf) ‘ (8) GoTo 8 End Function ‘ (10)
Complexity
A seemingly simple set of nested statements can be made complex providing that the overhead of procedure calls is not found to be too great:
Function GenerateObfuscation(strObf() As String) Dim lng As Long For lng = LBound(strObf, 2) To UBound(strObf, 2) If Len(strObf(0, lng)) = 0 Then Else strObf(1, lng) = UW.strRandString(strObf(0, lng)) strObf(1, lng) = UW.strRandString(LCase(strObf(1, lng))) End If Next lng End Function
Becomes
Function GenerateObfuscation(strObf() As String) Dim lng As Long Call c End Function Function a strObf(1, lng) = UW.strRandString(strObf(0, lng)) strObf(1, lng) = UW.strRandString(LCase(strObf(1, lng))) End Function Function b If Len(strObf(0, lng)) = 0 Then Else Call A End If End Function Function c For lng = LBound(strObf, 2) To UBound(strObf, 2) Call B Next lng
End Function
Couple this with local identifier obfuscation and the code approaches unreadability.
The quality of code kicks in, however, since the procedures would require parameter declarations.
Consider as an example Function “b”
Function b If Len(strObf(0, lng)) = 0 Then Else Call A End If End Function
Where is strOBF defined, and how can we locate this definition and institute it as a parameter to the code?
The solution would seem to be a parser over and beyond the current capabilities of Proje; we detect all declarations, and whether they are local to a procedure, a parameter of a procedure, or not, but the task of idenifying the scope within sub-blocks of code seems overly ambitious.
However, consider the following:
Any word which is not a reserved word or a literal constant is a user-defined identifier.
If the word belongs to the set of accumulated procedure arguments, it is local to the procedure.
If the word belongs to the set of accumulated procedure declarations, it is local to the procedure.
Otherwise the word can be assumed to belong to the set of identifiers defined globally to the procedure.
Therefore we can take the set of identifiers recognized as defined locally to the procedure and match them against the block of code to be extracted and established as a slave procedure.
For each identifier found within the code block, that identifier becomes a parameter of the slave procedure.
The call to the slave procedure will be the procedure header without the type phrases.
Any declaration found within the code block must be left outside the slave procedure.
It follows from the immediately preceding statement that all declarations can be floated to the head of the host procedure, further increasing obfuscation.
Thus the block of code:
Dim strOneArg As String strOneArg = Trim(strsplitat(strArgs, ",")) strOneArg = strSplitReserved(strOneArg) strObf(0, UBound(strObf, 2)) = strOneArg strObf(1, UBound(strObf, 2)) = "" ReDim Preserve strObf(UBound(strObf, 1), UBound(strObf, 2) + 1)
Becomes
Function a(ByRef strOneArg As String, ByRef strArgs As String, ByRef strObf() As String) strOneArg = Trim(strsplitat(strArgs, ",")) strOneArg = strSplitReserved(strOneArg) strObf(0, UBound(strObf, 2)) = strOneArg strObf(1, UBound(strObf, 2)) = "" ReDim Preserve strObf(UBound(strObf, 1), UBound(strObf, 2) + 1) End Function
And
Dim strOneArg As String Call a(strOneArg, strArgs, strObf)
Locked vs. Unlocked code
The traditional way of hiding VBA code is to lock the template.
Unlocker software come sin a variety of cheap forms, and means that locking code does not prevent someone from inspecting or changing it.
Of course, trying to reproduce any given bug using the original locked code is the ultimate test.
That said, obfuscated code allows the developer to allow an authorised client to unlock the code for debugging purposes.
The code being obfuscated can be used to trap a specific event or error, at which time, the public procedure having been identified, work can continue.
Reversibility
How can we devise a means of reversing obfuscation?
The most obvious method is to render obfuscated code disabled by commenting, save that source, and then de-comment the code to produce a release version.
When a debugging session reveals an obfuscated line of code such as “If Len(twjbe(shnekxw)) > 0 Then”, the code can be located in the saved source version, and the readable statements viewed in the disabled (commented) format.
Loading
Toronto and Mississauga, Friday, March 19, 2010 12:10 PM
Copyright © 1996-2010 Chris Greaves. All Rights Reserved.