Shrouding CSharp and Java Source Code with AWK
Originally published on: Sun, 25 Oct 2009 22:32:11 +0000
I enjoy tinkering with both Java and C# and publish a lot of my source on this blog under a very liberal open-source license.
I have wondered, however, what I would do if I had to protect a commercial program written in either of the two. The obvious choice would be to purchase a professional obfuscation program that defeats most decompilers. Fair enough. Most of these work on the compiled intermediate code.
What, however, if I just wanted to put some mild protection into the compiled product? The mild protection I refer to would still allow the code to be decompiled back to source form, but the person decompiling would have to work hard to make use of the bulk of the code.
My solution was to try a simple shrouding utility.
Shrouding ( also referred to as fogging ) is a kind of source-level obfuscation that was somewhat popular years ago for C programs sold in source form. The programs would generally compile but would be unreadable enough that they would discourage someone from using portions of unlicensed code in other programs.
A lot of these source-level shroud utilities are language-aware and determine what variables, functions, methods, and classes are locally-scoped. The shroud utility then replaces these with generated identifiers.
Most shrouding utilities go a step further and obfuscate literal strings as well as rewriting flow-control constructs.
All I had intended to do was to change the locally-scoped identifier-names to something that my script generates.
My proof-of-concept is an AWK script called foggy.awk. foggy.awk replaces any identifier it finds beginning with two underscore characters with a new identifier containing the prefix "i_" and a sequentially generated number.
This means that in my code, I have to observe a coding convention. I will have to prefix the name of any identifier that should be shrouded with two underscores.
First, here's the shrouding script:
foggy.awk
# Foggy.awk
# A source-code shrouding utility.
# Replace any "identifier" beginning with __ with a sequentially-
# numbered new identifier
#
# License: MIT / X11
# Copyright (c) 2009 by James K. Lawless
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
BEGIN {
legal="abcdefghijklmnopqrstuvwxyz";
legal=legal "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
legal=legal "_0123456789" ;
id_counter=0;
id_list["__"]="__";
}
{
state=0;
s="";
for(i=1;i<=length($0);i++) {
c=substr($0,i,1);
if(state==0) {
if(c=="_") {
state=1;
}
else {
s=s c;
}
}
else {
if(state==1) {
if(c=="_") {
state=2;
id="__";
}
else {
s=s "_" c;
state=0;
}
}
else {
# state == 2
if(index(legal,c)<1) {
if(id_list[id]=="") {
tmp_id="i_" id_counter;
id_counter++;
id_list[id]=tmp_id;
}
s=s id_list[id];
# decrement the index to forget the current
# character we're looking at
i--;
state=0;
}
else {
id=id c;
}
}
}
}
if(state==0) {
printf("%s\n",s);
}
else {
if(state==1) {
printf("%s_\n",s);
}
else {
if(state==2) {
if(id_list[id]=="") {
tmp_id="i_" id_counter;
id_counter++;
id_list[id]=tmp_id;
}
printf("%s%s\n",s,id_list[id]);
}
}
}
}
As a side note, I have compiled the above script with the Thompson Automation AWK (TAWK) compiler for Windows into the exe foggy.exe available in the foggy.zip file referenced at the end of this post.
Unfortunately, TAWK is no longer commercially available. I reviewed this compiler years ago in Dr. Dobbs Journal. ( See Examining the TAWK Compiler, DDJ May '97 here: http://www.ddj.com/architect/184410193 ...)
I also tested with GNU GAWK for Windows.
I would have preferred to use a regular expression with captured groups, but the AWK tools I have do not support those sorts of regex features. This likely would have been a smaller Perl script, but I wanted to try it in AWK. The necessary parser state transitions didn't seem to be too deep, so I gave it a go.
To use the above script either type:
gawk -f foggy.awk < inputfle > outputfile
...or, if you're using foggy.exe
foggy.awk < inputfle > outputfile
Let's first take a look at a Java program that now contains shroud-ready identifiers. I took the source from MicroHttp1.java from my post here: http://www.mailsend-online.com/blog?p=35
// MicroHttp1
// A small , specialized web server in Java
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
import java.io.*;
import java.net.*;
import java.util.Date;
public class MicroHttp1Obf {
public static void main(String[] __args)
throws IOException {
ServerSocket __serv;
Socket __s;
String __str;
PrintWriter __pw;
BufferedReader __br;
__serv=new ServerSocket(80);
System.out.println("Micro HTTP Server v 0.1");
System.out.println();
for(;;){
__s=__serv.accept();
__br=new BufferedReader(
new InputStreamReader(
__s.getInputStream()));
for(;;) {
__str=__br.readLine();
System.out.println(__str);
if( ! __br.ready())
break;
}
System.out.println();
__pw=new PrintWriter(
__s.getOutputStream(), true);
__pw.print("HTTP 200 OK\r\n");
__pw.print("Content-type: text/html\r\n\r\n");
__pw.print("<html><head /><body>");
__pw.print("Current date/time " + new Date());
__pw.print("</body></html>");
__pw.close();
}
}
}
Note that I had to change the class definition to what I would be targetting for my output filename, since the main Java class and source filename prefix must match.
There are lots of underscores now in this short script. Here's the shrouded output:
// MicroHttp1
// A small , specialized web server in Java
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
import java.io.*;
import java.net.*;
import java.util.Date;
public class MicroHttp1Obf {
public static void main(String[] i_0)
throws IOException {
ServerSocket i_1;
Socket i_2;
String i_3;
PrintWriter i_4;
BufferedReader i_5;
i_1=new ServerSocket(80);
System.out.println("Micro HTTP Server v 0.1");
System.out.println();
for(;;){
i_2=i_1.accept();
i_5=new BufferedReader(
new InputStreamReader(
i_2.getInputStream()));
for(;;) {
i_3=i_5.readLine();
System.out.println(i_3);
if( ! i_5.ready())
break;
}
System.out.println();
i_4=new PrintWriter(
i_2.getOutputStream(), true);
i_4.print("HTTP 200 OK\r\n");
i_4.print("Content-type: text/html\r\n\r\n");
i_4.print("<html><head /><body>");
i_4.print("Current date/time " + new Date());
i_4.print("</body></html>");
i_4.close();
}
}
}
There are really only about five shrouded identifiers in this program. I suppose someone who really wanted to work at it could perform global search-and-replace operations to change them into something meaningful as they analyze the code.
Let's try something a little larger that has more local identifiers. For this next test, I took the C# source code from my command-line Twitter client Twimmando. See: http://www.mailsend-online.com/blog?p=15
Here's the shroud-ready source:
// Twimmando - a command-line Twitter client
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Net;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;
namespace Twimmando
{
public enum __RequestMethod
{
__Get,
__Post,
__Head
}
class __Config
{
public __RequestMethod __Method;
public string __Uri;
public string __UserName;
public string __Password;
public string __PostData;
}
class __Twimmando
{
public static string __Version = "Twimmando v1.01 (obfuscated)";
public static void Main(string[] __args)
{
__Config __config;
try
{
__config=__processCommandLine(__args);
if(__config.__Uri==null)
Environment.Exit(1);
__doHttpRequest(__config);
}
catch(Exception e)
{
Console.Error.WriteLine(e.ToString());
Environment.Exit(1);
}
Environment.Exit(0);
}
public static __Config __processCommandLine(string[] __args)
{
int __i;
__Config __config=new __Config();
__config.__PostData="";
__config.__Method=__RequestMethod.__Get;
for(__i=0;__i<__args.Length;__i++) {
if(string.Compare(__args[__i],"-head",true)==0) {
__config.__Method=__RequestMethod.__Head;
}
else
if(string.Compare(__args[__i],"-post",true)==0) {
__config.__Method=__RequestMethod.__Post;
}
else
if(string.Compare(__args[__i],"-uri",true)==0) {
__config.__Uri=__args[++__i];
}
else
if(string.Compare(__args[__i],"-u",true)==0) {
__config.__UserName=__args[++__i];
}
else
if(string.Compare(__args[__i],"-p",true)==0) {
__config.__Password=__args[++__i];
}
else
if(string.Compare(__args[__i],"-f",true)==0) {
if(__config.__PostData.Length>0) {
__config.__PostData+="&";
}
__config.__PostData+=__args[__i+1]+"="+HttpUtility.UrlEncode(__args[__i+2]);
__i+=2;
}
}
if(__config.__Uri==null) {
Console.WriteLine(__Twimmando.__Version + "\nby Jim Lawless (@lawlessGuy)");
Console.WriteLine("Usage:\n\ttwimmando [options]\nWhere options are:");
Console.WriteLine(" -uri resourceURI ; such as /statuses/public_timeline.xml");
Console.WriteLine(" -u username ; Twitter password");
Console.WriteLine(" -p password ; Twitter user name");
Console.WriteLine(" -head ; send HTTP HEAD request, default is GET");
Console.WriteLine(" -post ; send HTTP POST request, default is GET");
Console.WriteLine(" -f name value ; add POST data name/value pair to request");
}
return __config;
}
public static void __doHttpRequest(__Config __config)
{
HttpWebRequest __webRequest;
HttpWebResponse __webResponse;
Uri __uri=new Uri("http://twitter.com" + __config.__Uri);
__webRequest = (HttpWebRequest)HttpWebRequest.Create(__uri);
if(__config.__Password!=null) {
__webRequest.Credentials = new NetworkCredential(__config.__UserName, __config.__Password);
}
__webRequest.UserAgent = __Twimmando.__Version;
if(__config.__Method==__RequestMethod.__Post) {
__webRequest.Method = "POST";
// remove Expect header
__webRequest.ServicePoint.Expect100Continue = false;
__webRequest.ContentType="application/x-www-form-urlencoded";
__webRequest.ContentLength = __config.__PostData.Length;
StreamWriter __sOut=new StreamWriter(__webRequest.GetRequestStream(),System.Text.Encoding.ASCII);
__sOut.Write(__config.__PostData);
__sOut.Close();
}
else
if(__config.__Method==__RequestMethod.__Head) {
__webRequest.Method="HEAD";
}
__webResponse = (HttpWebResponse)__webRequest.GetResponse();
if(__config.__Method==__RequestMethod.__Head) {
Console.WriteLine(__webResponse.Headers.ToString());
}
else {
Stream __stream = __webResponse.GetResponseStream();
StreamReader __streamReader =
new StreamReader(__stream, Encoding.ASCII);
Console.WriteLine(__streamReader.ReadToEnd());
}
__webResponse.Close();
}
}
}
Here's the encoded result:
// Twimmando - a command-line Twitter client
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Net;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;
namespace Twimmando
{
public enum i_0
{
i_1,
i_2,
i_3
}
class i_4
{
public i_0 i_5;
public string i_6;
public string i_7;
public string i_8;
public string i_9;
}
class i_10
{
public static string i_11 = "Twimmando v1.01 (obfuscated)";
public static void Main(string[] i_12)
{
i_4 i_13;
try
{
i_13=i_14(i_12);
if(i_13.i_6==null)
Environment.Exit(1);
i_15(i_13);
}
catch(Exception e)
{
Console.Error.WriteLine(e.ToString());
Environment.Exit(1);
}
Environment.Exit(0);
}
public static i_4 i_14(string[] i_12)
{
int i_16;
i_4 i_13=new i_4();
i_13.i_9="";
i_13.i_5=i_0.i_1;
for(i_16=0;i_16<i_12.Length;i_16++) {
if(string.Compare(i_12[i_16],"-head",true)==0) {
i_13.i_5=i_0.i_3;
}
else
if(string.Compare(i_12[i_16],"-post",true)==0) {
i_13.i_5=i_0.i_2;
}
else
if(string.Compare(i_12[i_16],"-uri",true)==0) {
i_13.i_6=i_12[++i_16];
}
else
if(string.Compare(i_12[i_16],"-u",true)==0) {
i_13.i_7=i_12[++i_16];
}
else
if(string.Compare(i_12[i_16],"-p",true)==0) {
i_13.i_8=i_12[++i_16];
}
else
if(string.Compare(i_12[i_16],"-f",true)==0) {
if(i_13.i_9.Length>0) {
i_13.i_9+="&";
}
i_13.i_9+=i_12[i_16+1]+"="+HttpUtility.UrlEncode(i_12[i_16+2]);
i_16+=2;
}
}
if(i_13.i_6==null) {
Console.WriteLine(i_10.i_11 + "\nby Jim Lawless (@lawlessGuy)");
Console.WriteLine("Usage:\n\ttwimmando [options]\nWhere options are:");
Console.WriteLine(" -uri resourceURI ; such as /statuses/public_timeline.xml");
Console.WriteLine(" -u username ; Twitter password");
Console.WriteLine(" -p password ; Twitter user name");
Console.WriteLine(" -head ; send HTTP HEAD request, default is GET");
Console.WriteLine(" -post ; send HTTP POST request, default is GET");
Console.WriteLine(" -f name value ; add POST data name/value pair to request");
}
return i_13;
}
public static void i_15(i_4 i_13)
{
HttpWebRequest i_17;
HttpWebResponse i_18;
Uri i_19=new Uri("http://twitter.com" + i_13.i_6);
i_17 = (HttpWebRequest)HttpWebRequest.Create(i_19);
if(i_13.i_8!=null) {
i_17.Credentials = new NetworkCredential(i_13.i_7, i_13.i_8);
}
i_17.UserAgent = i_10.i_11;
if(i_13.i_5==i_0.i_2) {
i_17.Method = "POST";
// remove Expect header
i_17.ServicePoint.Expect100Continue = false;
i_17.ContentType="application/x-www-form-urlencoded";
i_17.ContentLength = i_13.i_9.Length;
StreamWriter i_20=new StreamWriter(i_17.GetRequestStream(),System.Text.Encoding.ASCII);
i_20.Write(i_13.i_9);
i_20.Close();
}
else
if(i_13.i_5==i_0.i_3) {
i_17.Method="HEAD";
}
i_18 = (HttpWebResponse)i_17.GetResponse();
if(i_13.i_5==i_0.i_3) {
Console.WriteLine(i_18.Headers.ToString());
}
else {
Stream i_21 = i_18.GetResponseStream();
StreamReader i_22 =
new StreamReader(i_21, Encoding.ASCII);
Console.WriteLine(i_22.ReadToEnd());
}
i_18.Close();
}
}
}
This shrouded program was much more difficult for me to read, as I used some local class definitions and used more local variables and methods than I had used in the sample Java program.
My conclusion?
Well, if I had to employ this method, I think I could grow into learning to deal with the added noise of the extra underscores in the source.
However, I still think that language-aware shrouding utilities would be much less painful to use.
The source and Windows executable file for foggy.awk can be downloaded in a single archive at:
http://www.mailsend-online.com/wp/foggy.zip
Unless otherwise noted, all code and text entries are Copyright ©2009 by James K. Lawless
Save to del.icio.us
Save to StumbleUpon
Digg it
Save to Reddit
Share on Facebook
Share on Twitter
More bookmarks