Call: +44 (0)1904 557620 Call
Blog

Pete Finnigan's Oracle Security Weblog

This is the weblog for Pete Finnigan. Pete works in the area of Oracle security and he specialises in auditing Oracle databases for security issues. This weblog is aimed squarely at those interested in the security of their Oracle databases.

[Previous entry: "Two exploit versions of the ctxsys.drvxtabc.create_tables bug from Bunker"] [Next entry: "Dennis has released a paper describing his FPGA cracker"]

Unwrapping PL/SQL



There was an interesting questuion on my forum a couple of days ago titled " its legal to host unwrap?"; bear in mind the title means the opposite, i.e. is it illegal not its illegal. These semantics do not alter the question though! The poster wants to host a free unwrapper, its an interesting question that is already answered at one level. i.e. someone else has already done it.

There is already a Swiss site that hosts a 10g unwrapper for free - http://hz.codecheck.ch/UnwrapIt/Unwrap.jsp - I am myself unsure what the legal position would be in hosting an unwrapper as you would have no control over what people could unwrap. If you unwrap privately for clients as a paid service then as Gary suggests in his reply in the forum you can put in place contracts where the client has to show that he has legal ownership of the code he wants unwrapping but as the poster suggests he wants to host a free service. A contracted service which is of course more controlled is then a source recovery service and companies do have a genuine need for this where they have lost the original source code. Just because someone else has done it already doesnt make it legal!

I was at the UKOUG conference Monday and Tuesday and with clients yesterday and today so had littlke time to blog but one of the things I was going to talk about as it happens was unwrapping as I was cornered twice at the UKOUG conference by people asking me about unwrapping and the paper I wrote a few years ago for the How to unwrap PL/SQL that i presented at Black Hat in Las Vegas. Of course more recently Anton made available some details on how to unwrap 10g PL/SQL on his blog. David also talked about unwrapping in his Oracle Hackers Handbook - book, interestingly he had a view on the legality in that he refrained from publishing the lookup table used in the wrap process but this was actually about trade secrets and reverse engineering and not about using an unwrapper. I published a simple demo unwrapper that used the DIANA and PIDL packages to show how Oracle unwraps as part of the pstub code used for remote PL/SQL calls. This needs to work with wrapped and unwrapped code hence the need for it to work with DIANA. This is how 9i wrap works, 10g is different but both still use DIANA under the hood of course. The code is called unwrap_r.sql but it wont unwrap anything real as its simply demonstrating the use of DIANA and PIDL and those mechanisms only expose the signatures of packages and nothing else.

I also have unwrappers for 10g and 9i and lower completely written in PL/SQL of course. Here is a little demo of it running on some 9i PL/SQL code. First create a simple procedure to use for this test case. The code is just made up for this experiment and is very simple:




SQL> get sample1.sql
1 create or replace procedure test_proc (pv_num in number,
2 pv_var in varchar2, pv_var3 in out integer) is
3 l_num number:=3;
4 l_var number;
5 j number:=1;
6 procedure nested (pv_len in out number)
7 is
8 x number;
9 begin
10 x:=pv_len*5;
11 end;
12 begin
13 case l_num
14 when 1 then
15 -- IF 1
16 l_var:=3;
17 dbms_output.put_line('This is a header');
18 dbms_output.put_line('The number is '||l_var);
19 dbms_output.put_line('The case var is '||l_num);
20 when 2 then
21 -- IF 2
22 l_var:=4;
23 dbms_output.put_line('This is a header');
24 dbms_output.put_line('The number is '||l_var);
25 dbms_output.put_line('The case var is '||l_num);
26 when 3 then
27 -- IF 3
28 l_var:=6;
29 dbms_output.put_line('This is a header');
30 dbms_output.put_line('The number is '||l_var);
31 dbms_output.put_line('The case var is '||l_num);
32 else
33 dbms_output.put_line('wrong choice');
34 end case;
35 if ((j=1) and (j=3)) then
36 dbms_output.put_line('here is IF');
37 elsif ((j=2) or (j!=3)) then
38 dbms_output.put_line('The elsif clause');
39 else
40 dbms_output.put_line('else clause');
41 end if;
42 j:=4;
43 nested(j);
44 dbms_output.put_line('nested=:'||j);
45 for j in reverse 1..pv_num
46 loop
47 if mod(j,2) = 0 then
48 dbms_output.put_line('for loop with reverse');
49 end if;
50 end loop;
51* end;
SQL>




I can then wrap this with the 9i wrap utility:




C:\unwrapper>wrap iname=sample1.sql oname=sample1.plb

PL/SQL Wrapper: Release 9.2.0.1.0- Production on Mon Jun 01 14:02:34 2009

Copyright (c) Oracle Corporation 1993, 2001. All Rights Reserved.

Processing sample1.sql to sample1.plb

C:\unwrapper>head sample1.plb




Then I can show it is indeed wrapped by viewing the contents (Note the above commands are in a DOS box, the head command is on the same machine but from cygwin as the head command is available:




$ head -20 sample1.plb
create or replace procedure test_proc wrapped
0
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
3
7
9200000




Now load the wrapped file into an 11gR1 database and check its stored wrapped:




SQL> @sample1.plb

Procedure created.

SQL> select substr(text,1,60)
2 from dba_source
3 where name='TEST_PROC'
4 and rownum=1;

SUBSTR(TEXT,1,60)
------------------------------------------------------------
procedure test_proc wrapped
0
abcd
abcd
abcd
abcd
abcd
abcd


SQL>




Now we can simply unwrap it using my PL/SQL based unwrapper:




SQL> @unwrap_c

unwrap_c: Release 1.4.0.0.0 - Production on Mon Jun 01 14:07:13 2009
Copyright (c) 2008, 2009 PeteFinnigan.com Limited. All rights reserved.

NAME OF OBJECT TO CHECK [P1]: TEST_PROC
OWNER OF OBJECT TO CHECK [SYS]: SYS
TYPE OF THE OBJECT [PROCEDURE]: PROCEDURE
OUTPUT METHOD Screen/File [S]: S
FILE NAME FOR OUTPUT [priv.lst]:
OUTPUT DIRECTORY [DIRECTORY or file (/tmp)]:

create or replace procedure TEST_PROC( PV_NUM in NUMBER,
PV_VAR in VARCHAR2, PV_VAR3 in out INTEGER) is
L_NUM NUMBER:=3;
L_VAR NUMBER;
J NUMBER:=1;
procedure NESTED( PV_LEN in out NUMBER) is
X NUMBER;
begin
X:= PV_LEN * 5;
end;
begin
case L_NUM
when 1 then
L_VAR:=3;
DBMS_OUTPUT. PUT_LINE('This is a header');
DBMS_OUTPUT. PUT_LINE('The number is ' || L_VAR);
DBMS_OUTPUT. PUT_LINE('The case var is ' || L_NUM);
when 2 then
L_VAR:=4;
DBMS_OUTPUT. PUT_LINE('This is a header');
DBMS_OUTPUT. PUT_LINE('The number is ' || L_VAR);
DBMS_OUTPUT. PUT_LINE('The case var is ' || L_NUM);
when 3 then
L_VAR:=6;
DBMS_OUTPUT. PUT_LINE('This is a header');
DBMS_OUTPUT. PUT_LINE('The number is ' || L_VAR);
DBMS_OUTPUT. PUT_LINE('The case var is ' || L_NUM);
else
DBMS_OUTPUT. PUT_LINE('wrong choice');
end case;
if ( ( J = 1) and ( J = 3)) then
DBMS_OUTPUT. PUT_LINE('here is IF');
elsif ( ( J = 2) or ( J != 3)) then
DBMS_OUTPUT. PUT_LINE('The elsif clause');
else
DBMS_OUTPUT. PUT_LINE('else clause');
end if;
J:=4;
NESTED( J);
DBMS_OUTPUT. PUT_LINE('nested=:' || J);
for J in reverse 1.. PV_NUM loop
if MOD( J,2) = 0 then
DBMS_OUTPUT. PUT_LINE('for loop with reverse');
end if;
end loop;
end;
/

INFO: Elapsed time = [.1 Seconds]

PL/SQL procedure successfully completed.

For more information please visit

SQL>




This gives a 100% source code recovery for 9i and lower PL/SQL code and I have mechanisms built in that can prove this even if the original source code was lost. For 10g and 11g the algorithm is simpler and if its implemented right it will always give a complete source recovery. For 9i this (100% source recovery) is much harder to achieve as the method of wrapping is much more complex - see my Black Hat paper above for details of why its harder.