Many people have tried to unwrap wrapped PL/SQL. Most people haven’t succeeded in doing it, but since Pete Finnegan’s presentation on the 2006 Black Hat conference, http://www.blackhat.com/presentations/bh-usa-06/BH-US-06-Finnigan.pdf, unwrapping PL/SQL on the older database versions, pre 10G, is possible. David Litchfield, in his book “The Oracle Hacker’s Handbook”, describes a method to unwrap code on a 10G database. According to the description it doesn’t seem too difficult to do:
“First, it is base64 decoded. Then, each byte is
resubstituted with a second corresponding substitution table. Last, the text is
decompressed, leaving the clear text of the PL/SQL.”
A year ago, after reading the book, I first tried to unwrap some code. Without success! But this year I was looking for a nice project to kill some time and I decided to give it another go. From Litchfield’s description it is clear that you need a substitution table. Litchfield didn’t reveal the table. I couldn’t find it with Google. Looking for it in the wrap utility it not allowed (and difficult too, see for instance this work from some Israeli researchers http://webcourse.cs.technion.ac.il/236349/Spring2009/ho/WCFiles/final_report.pdf ) So I had to find another way to get the table. I started with just looking at the output of some “simple” wrapped procedures. You can use wrap.exe to wrap the PL/SQL code in a file, but it is much easier to use dbms_ddl.wrap.
In this output we see a line with a000000 en 15 lines with abcd. These lines I have found in every wrapped code. The third line, in this example 367, has probably something to do with database version. On my 10.2.0.4 it’s always 367, on a10.2.0.1 it’s always 2e and with my 10.1.02.4 wrap.exe it’s always b2. The 19th line, indicates the type of the wrapped object . A procedure gives 7, a function 8 and a package body b.
The next line contains 2 hex numbers. The first is the length from the unwrapped text without the create + 1, the second is the length of the base64-encoded text. So looking at the output from dbms_ddl.wrap reveals some information, but even the small procedure from this example produces too much wrapped text for me to get a clue on a substitution table. But luckily it is possible to wrap even smaller pieces of code.
If we base64-decode this, skip the first 20 bytes (the size of a SHA1-hash, see Litchfields book) we have only 18 bytes left to decipher. For the base64-decoding we can use the package utl_encode. Oracle has also a package utl_compress, which can be used for Lempel-Ziv compression/decompression, but his time I used some Java in the database for compression/decompression (see attached file for the code). Now we have 2 options:
- Find a substitution table, apply it, and decompress the result to get unwrapped PL/SQL code.
- Compress PL/SQL code, and compare this to the base64-decoded output to FIND (a part of) the substitution table.
So let’s try the second option. The only problem is what to compress. I started with the same input I use for dbms_ddl.wrap.
Oops, the zipped code is one byte short. But remember the two hex numbers in the wrapped output. The first hex number was the length of the unwrapped code + 1. So I tried adding a newline character to the input of the compression. That gives 1 more byte for the zipped code, but it has another problem. In the output of the wrapped column we have 2 lines with 166. These lines should both have the same value in the zipped column. And that is not so. So I tried adding a space. With more success.
And this gives us 16 entries, from the 256, for the substation table. How easy can it be! Doing the same for “PACKAGE b” gives some more values, but “PACKAGE c” results again in 2 different “zipped values” for the same “wrapped values”. Adding a space to the input text wasn’t correct choice . After a few more trials I found out it has to the byte 0. So now we have a way to generate a part of the substitution table. Is it enough to create the complete substitution table? Calling this query in a PL/SQL loop with a lot of different names results again in errors. But this error has to do with the second byte. And for the people who don’t know the Zip-format by heart (http://www.gzip.org/zlib/rfc-zlib.html), that’s where the compression level is stored. So I changed the compression level to 9, and then I was able to generate a complete substitution table. And with this table unwrapping not that difficult at all. You can use it in a SQL-statement.
Or you can use it in a Java-program to unwrap the plb-files of all your lost sources.
The used code can be downloaded here used code