Unwrapping 10G wrapped PL/SQL

30

 

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
 
Anton
 
Share.

About Author

30 Comments

  1. Hi:
    We have a requirement to upgrade a custom application built on Oracle7 to 10g. There are some wrapped procedures which could not be migrated. Is there any toll available to unwarp the same and reqrap after migration to 10g?
    Thanks
    Murali

    • Anton Scheffer on

      @Murali, you can deploy the wrapped source to a 10G database, without unwrapping it first.

  2. What about those LIBRARied extprocs… any idea how to decode them…

    Praveen Ray

  3. Marcio Brazil on

    Hello, My name is Marcio and I from Brazil, I need to unwrap packages  9i , do you know about, any tool for 9i packages, please, if any people, help me,
    medreis@hotmail.com
    Marcio Eduardo

  4. Anton Scheffer on

    @Renuka You’re right. 9i (and before) wrapping doesn’t use Base64 encoding.

  5. Hi,
    I came across your blog and I am trying to unwrap a PL/SQL package in 9i, But it fails. If I am not mistaken, Base 64 encoding is in 10g not in 9i.  I tried unwrap utility online but it fails saying not a valid wrapped code.
     
    Thanks
    Renuka

  6. Anton Scheffer on

    @Szymon
    No I did not checked my code on the Oracle build-in packages, because that is not allowed.
    But your are right about the maximum RAW length. In my sql-queries the maximum allowed length of RAW and VARCHAR2 is 4000, which means that sql-queries can not be used for unwrapping “large” pieces of code. For those larger unwrapping tasks you have to use plsql, which has a limit of more than 32000, or java, which I use for my own unwrapper.
    And for cutting of the first 40 chars, in that example the RAW value is converted to a VARCHAR2 value. And in that conversion every RAW becomes the 2 byte hexadecimal represatation of the value.
    And for unwrapping 9i wrapped code you have to check the work of Pete Finnigan

  7. Hi,

    Did you checked that this works on various Oracle build-in packages?

    I have 10.2.0.4 database, and when trying to unwrap body of SYS.UTL_URL it works flawless.

    But when I try do unwrap body of SYS.UTL_ENCODE I get an error “ORA-24305: bad bind or define context” from call of Java inflate procedure. So something in that package, broke the decompressor.

    Another problem is maximum RAW length. For example trying to unwrap UTL_TCP fails with error:
    “ORA-06502: PL/SQL: numeric or value error: raw variable length too long
    ORA-06512: at “SYS.UTL_RAW”, line 224″
    Because of this I have tried to rewrite whole unwrap procedure in Java using NetBeans, but no success yet – I think that Base64 decoding fails, so when I cut off first 20 bytes of decoded text, it is not equal to SHA1 of the rest, and inflate function says: “java.util.zip.ZipException: unknown compression method”.
    Also tell me why, in Your example You cut off first 40 chars from Base64 decoded text, not 20 (SHA1 signature is 20bytes = 160bits long)?

    And last but not least, I have upgraded my database from 9.2.0.8 in February, so I have many wrapped packages body (from used applications) which in 20 line has text “9200000” and data below IS NOT Base64 encoded. I think that it is before 10g wrapping which 10g can use, but i don’t know (yet) how to unwrap it…

    Best regards
    Szymon

  8. Nice tool! This tool works well with built-in package from Oracle 10.2.0.1.0 and 11.1.0.7.0. It would be even more execelllent if the trojan horse problem is fixed.

  9. Anton Scheffer on

    @Lorenzo

    Lety me guess: you use a 10.2.0.1.0 database. It looks that in that version does a uppercase on the package name before wrapping it.
    My code doesn’t handle that correctly.
    So you have two options.
    Upgrade your database or change my code a bit:
    change procedure fill2 to

    procedure fill2( p_txt in varchar2 )
    is
    begin
    for i in 0 .. 10
    loop
    fill( p_txt, ascii( ‘A’ ), ascii( ‘Z’ ), to_char( i, ‘fm999′ ) );
    end loop;
    end;

    And make some more calls to fill2
    fill2( ‘PACKAGE ‘ );
    fill2( ‘PACKAGE BODY ‘ );
    fill2( ‘FUNCTION ‘ );

    Anton

  10. Hi, i created java objects and package, but tring to execute statement from “declare” i got this error.
    Error at line 1
    ORA-01403: no data found
    ORA-06512: at line 40
    ORA-06512: at line 51
    ORA-06512: at line 63
    what’s wrong?
    many thanks for yout help.

  11. Anton Scheffer on

    If you can tell more than “its not working”, maybe I can help you. I this moment the only thing I can say that it is working for me. :)

  12. Hi, i’m very interesting on that but tring to unwrap some code using your procedure seems that is not working. is there something that i missed? Can someone help me to understand?

  13. Anton Scheffer on

    @Arturo
    If you don’t what doesn’t work it is hard to give a solution, but maybe it has to do with the size of the wrapped code you are trying to unwrap. For the larger packages the source is stored in more records in dba_source. And utl_encode.base64_decode has also a limit (I think it accepts a raw of 4000 chars maximum).

  14. Hi Anton

    Really nice piece of work!
    I tried it with a user defined PL/SQL procedure and it worked well, but with most SYS objects (DBMS packages) it doesn’t work:
    it works e.g. for functions (SYS.LOGMNR_DPC,SYS.AQ$_GET_SUBSCRIBERS), but generally not for wrapped procedures/packages in the SYS/SYSTEM schema.

  15. Anton Scheffer on

    @Mark
    What your looking at is the output from the compression, not from base64 decoding. And in the compressed output the same character wil not always map to the same output character, it will depend on the place in the origin al string and the characters before it.

  16. Mark Wooldridge on

    Hope you can clear up some confusion. If you wrap ‘package a_b’ and wrap ‘package b_a’ would the a in each show to map to the same value when using the base64 decode?

    my results with your code…
    package a_b
    30 83 99 B8 F5 33 9F F5 BF 5C B8 5F BF E0 A6 A6 8E A0 E1 BB
    78 9C 2B 48 4C CE 4E 4C 4F 55 48 8C 4F 62 00 00 1D 54 04 0F

    packageb_a
    30 83 99 B8 F5 33 9F F5 BF 5C B8 18 BF 91 A6 A6 8E 61 E1 BB
    78 9C 2B 48 4C CE 4E 4C 4F 55 48 8A 4F 64 00 00 1D 56 04 0F

    is hard to see where the a and the b map.

    Thanks

  17. You can get unwrap10.exe from here http://oracle-rewrap.narod.ru/ -> Some anti-virus will say it’s a trojan horse.

    There’s a Russian forum about it http://www.sql.ru/forum/actualthread.aspx?bid=3&tid=107197&pg=7 -> check the end of the page for page for example.

    I’m able to unwrap dbms packages as well as my own packages, although, I’m not able to unwrap packages from an application we use at work.

    Tutorial:
    set echo off heading off headsep off linesize 1000 feedback off pagesize 0 trimspool on
    spool o1
    select text from dba_source
    where owner = ‘SYS’
    and name = ‘DBMS_MONITOR’
    and type = ‘PACKAGE BODY’
    order by line;
    spool off;
    host unwrap10.exe o1.lst o2.lst
    host type o2.lst

    Cheers

  18. Anton Scheffer on

    @Donald. No, I will not post the real code. Some who knows PL/SQL can easily adopt my code to get the real substitution table. And someone who doesn’t know how to do it has no purpose for unwrapping code :)

  19. Hi Anton,

    Great findings, thanks for sharing it with us. However, I have some questions
    I tried to follow the steps as per your printscreens and also insatlled the objects from your attached file.
    I am getting null value from your last printscreen step(viz. I took the last part of the code from your attached file)
    Can you also post the real code for unwrapping :-), if thats possible

    Thanks

    Kind regards
    Donald

  20. Anton Scheffer on

    @YAP I didn’t include code to actually do any unwrapping. And with Google you can find a webservice and programs to do that. So I do think that I revealed to much.

    @Indrajit I mentioned that presentatiion in my blog. It’s for 9i databases and before. My method is for 10G databases.

  21. Nice post, the 10g wrap mechanism is in my opinion much weaker than the 9i and lower mechanism. The problem with the 9i mechanism is that the symbol table is visible, with 10g to 11g that is not so BUT as you have shown the mechanism is weaker as full reversal is possible. With 9i its simply the internal state of the PL/SQL compiler, i.e. DIANA written out to disk as IDL. The unwrap process for 9i is a feature of the design of DIANA (Goos et al) as DIANA was intended for low memory older machines where code would be stored in an intermediate format and it should be possible to reconstruct the source code. writing an unwrapper for 9i and lower; a complete one is unfortunately a much bigger task than 10g. So in my opinion, we gained a hidden symbol table but a much weaker mechanism to hide our code in 10g.

    Thanks

    Pete