416-621-9348 cgreaves@chrisgreaves.com Visit www.ChrisGreaves.com for this image! Chris_GEDC1894_Head (Small).JPG
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.