Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 91 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,93 @@
UnwrapperPLSQL
==============
### UnwrapperPLSQL

Oracle 10g/11g PL/SQL unwrapper 0.2 by Niels Teusink.
----

From: http://blog.teusink.net/2010/04/unwrapping-oracle-plsql-with-unwrappy.html
This repository contains a python script capable of unwrapping *Oracle 10g/11g PL/SQL* procedures.
A general introduction to *PL/SQL Source Text Wrapping* can be found within the
[official oracle documentation](https://docs.oracle.com/cd/E24693_01/appdev.11203/e17126/wrap.htm).
This documentation also contains some samples for wrapped procedures and the following procedure will
be used for demonstration purposes:

```
CREATE OR REPLACE FUNCTION fibonacci wrapped
a000000
b2
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
abcd
8
14a fb
e1Yq3QQJoEoNKIeJlbgLoLdSgogwgxDcf8vWfHSKbuowFOXFKoj9MqYGqWyRxeeCUVqNVIO1
ICqJa3yPr6e7z8GZpMH3J0Cx0uQ0B1JuysymdNDlzfTvb7QWsrLU4jGs3h8Mm49/L9nyO4Xh
Ae06nawFpOJIAYpBf9wBVC+ZrjU/nuEtokBqCce6HWIoF6rYgz0V0W/47x5KpOnQ2i7X3kFe
FR8K7jT7X58k8xK9uYlZv5LhV71a7A==
```


### Usage

----

When launching the script without any arguments, it expects the wrapped procedure to be passed via stdin
and outputs the unwrapped procedure to stdout:

```console
[user@host ~]$ cat wrapped.pld | python3 unwrap.py
FUNCTION fibonacci (
N PLS_INTEGER
) RETURN PLS_INTEGER
AUTHID DEFINER
IS
FIB_1 PLS_INTEGER := 0;
FIB_2 PLS_INTEGER := 1;
BEGIN
IF N = 1 THEN
RETURN FIB_1;
ELSIF N = 2 THEN
RETURN FIB_2;
ELSE
RETURN FIBONACCI(N-2) + FIBONACCI(N-1);
END IF;
END;
```

You can also specify a file that contains a wrapped procedure as argument:

```console
[user@host ~]$ python3 unwrap.py wrapped.pld
FUNCTION fibonacci (
N PLS_INTEGER
) RETURN PLS_INTEGER
AUTHID DEFINER
IS
FIB_1 PLS_INTEGER := 0;
FIB_2 PLS_INTEGER := 1;
BEGIN
IF N = 1 THEN
RETURN FIB_1;
ELSIF N = 2 THEN
RETURN FIB_2;
ELSE
RETURN FIBONACCI(N-2) + FIBONACCI(N-1);
END IF;
END;
```

### Resources

----

* [Official Oracle Documentation](https://docs.oracle.com/cd/E24693_01/appdev.11203/e17126/wrap.htm).
* [Unwrapping Oracle PLSQL - Blog Post](http://blog.teusink.net/2010/04/unwrapping-oracle-plsql-with-unwrappy.html)
177 changes: 134 additions & 43 deletions unwrap.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,140 @@
#!/usr/bin/python
#!/usr/bin/env python3
#
# This script unwraps Oracle wrapped plb packages, does not support 9g
# Contact: niels at teusink net / blog.teusink.net
# This script unwraps Oracle wrapped procedures. The original version of the script
# was published by Niels Teusink. This script represents a refactored version, where
# the main logic is still the same. The script does not support 9g wrapped procedures.
#
# License: Public domain
# Authors:
# Niels Teusink (original)
# Tobias Neitzel (refactored)
#
import re
import base64
import zlib
import sys
import zlib
import base64
import hashlib
import argparse
import binascii


charmap = [
0x3d, 0x65, 0x85, 0xb3, 0x18, 0xdb, 0xe2, 0x87, 0xf1, 0x52, 0xab, 0x63, 0x4b, 0xb5, 0xa0, 0x5f, 0x7d, 0x68, 0x7b,
0x9b, 0x24, 0xc2, 0x28, 0x67, 0x8a, 0xde, 0xa4, 0x26, 0x1e, 0x03, 0xeb, 0x17, 0x6f, 0x34, 0x3e, 0x7a, 0x3f, 0xd2,
0xa9, 0x6a, 0x0f, 0xe9, 0x35, 0x56, 0x1f, 0xb1, 0x4d, 0x10, 0x78, 0xd9, 0x75, 0xf6, 0xbc, 0x41, 0x04, 0x81, 0x61,
0x06, 0xf9, 0xad, 0xd6, 0xd5, 0x29, 0x7e, 0x86, 0x9e, 0x79, 0xe5, 0x05, 0xba, 0x84, 0xcc, 0x6e, 0x27, 0x8e, 0xb0,
0x5d, 0xa8, 0xf3, 0x9f, 0xd0, 0xa2, 0x71, 0xb8, 0x58, 0xdd, 0x2c, 0x38, 0x99, 0x4c, 0x48, 0x07, 0x55, 0xe4, 0x53,
0x8c, 0x46, 0xb6, 0x2d, 0xa5, 0xaf, 0x32, 0x22, 0x40, 0xdc, 0x50, 0xc3, 0xa1, 0x25, 0x8b, 0x9c, 0x16, 0x60, 0x5c,
0xcf, 0xfd, 0x0c, 0x98, 0x1c, 0xd4, 0x37, 0x6d, 0x3c, 0x3a, 0x30, 0xe8, 0x6c, 0x31, 0x47, 0xf5, 0x33, 0xda, 0x43,
0xc8, 0xe3, 0x5e, 0x19, 0x94, 0xec, 0xe6, 0xa3, 0x95, 0x14, 0xe0, 0x9d, 0x64, 0xfa, 0x59, 0x15, 0xc5, 0x2f, 0xca,
0xbb, 0x0b, 0xdf, 0xf2, 0x97, 0xbf, 0x0a, 0x76, 0xb4, 0x49, 0x44, 0x5a, 0x1d, 0xf0, 0x00, 0x96, 0x21, 0x80, 0x7f,
0x1a, 0x82, 0x39, 0x4f, 0xc1, 0xa7, 0xd7, 0x0d, 0xd1, 0xd8, 0xff, 0x13, 0x93, 0x70, 0xee, 0x5b, 0xef, 0xbe, 0x09,
0xb9, 0x77, 0x72, 0xe7, 0xb2, 0x54, 0xb7, 0x2a, 0xc7, 0x73, 0x90, 0x66, 0x20, 0x0e, 0x51, 0xed, 0xf8, 0x7c, 0x8f,
0x2e, 0xf4, 0x12, 0xc6, 0x2b, 0x83, 0xcd, 0xac, 0xcb, 0x3b, 0xc4, 0x4e, 0xc0, 0x69, 0x36, 0x62, 0x02, 0xae, 0x88,
0xfc, 0xaa, 0x42, 0x08, 0xa6, 0x45, 0x57, 0xd3, 0x9a, 0xbd, 0xe1, 0x23, 0x8d, 0x92, 0x4a, 0x11, 0x89, 0x74, 0x6b,
0x91, 0xfb, 0xfe, 0xc9, 0x01, 0xea, 0x1b, 0xf7, 0xce
]


class IntegrityException(Exception):
'''
Is raised when the wrapped procedure has an invalid checksum.
'''

def __init__(self, expected: str, computed: str) -> None:
'''
Initialize the IntegrityException.

Parameters:
expected The expected hash value in hex format
computed The computed hash value in hex format

Returns:
None
'''
self.expected = expected
self.computed = computed
super().__init__()

def decode_wrapped(base64str: str) -> str:
'''
Decode the base64 encoded procedure, apply the mapping table to it, validate the integrity
of the procedure and decompress it.

Parameters:
base64str base64 encoded procedure

Returns:
decoded decoded and decompressed procedure
'''
decoded = bytearray()
base64dec = base64.b64decode(base64str)

for byte in base64dec:
decoded.append(charmap[byte])

sha1 = decoded[:20]
code = decoded[20:]
checksum = hashlib.sha1(code).digest()

if sha1 != checksum:
raise IntegrityException(binascii.hexlify(sha1).decode(), binascii.hexlify(checksum).decode())

return zlib.decompress(code).decode()


def main() -> None:
'''
Read a wrapped Oracle 10g/11g PL/SQL procedure and output its decoded form.

Parameters:
None

Returns:
None
'''
parser = argparse.ArgumentParser(description='''Oracle 10g/11g PL/SQL unwrapper v0.3 - by Niels Teusink & Tobias Neitzel''')
parser.add_argument('infile', nargs='?', default=sys.stdin, type=argparse.FileType('r'), help='filepath (default: stdin)')
args = parser.parse_args()

length = 0
wrapped = ''
discard = True
pattern = re.compile(r'^[0-9a-f]+ ([0-9a-f]+)$')

for line in args.infile.readlines():

line = line.strip()
matches = pattern.match(line)

if matches:
base64len = int(matches.groups()[0], 16)
discard = False

elif line and not discard:
length += 1
wrapped += line

length = length + len(wrapped) - 1

if discard:
print('[-] Did not find required base64 length field. Input seems to be invalid.')
return

if base64len != length:
print(f'[-] Input has unexpected b64 length: {length} (expected {base64len})')
return

print(decode_wrapped(wrapped))


try:
main()

except IntegrityException as e:
print("[-] Decoding the wrapped procedure created an invalid checksum.")
print(f"[-] Expected value: " + e.expected)
print(f"[-] Computed value: " + e.computed)

# simple substitution table
charmap = [0x3d, 0x65, 0x85, 0xb3, 0x18, 0xdb, 0xe2, 0x87, 0xf1, 0x52, 0xab, 0x63, 0x4b, 0xb5, 0xa0, 0x5f, 0x7d, 0x68, 0x7b, 0x9b, 0x24, 0xc2, 0x28, 0x67, 0x8a, 0xde, 0xa4, 0x26, 0x1e, 0x03, 0xeb, 0x17, 0x6f, 0x34, 0x3e, 0x7a, 0x3f, 0xd2, 0xa9, 0x6a, 0x0f, 0xe9, 0x35, 0x56, 0x1f, 0xb1, 0x4d, 0x10, 0x78, 0xd9, 0x75, 0xf6, 0xbc, 0x41, 0x04, 0x81, 0x61, 0x06, 0xf9, 0xad, 0xd6, 0xd5, 0x29, 0x7e, 0x86, 0x9e, 0x79, 0xe5, 0x05, 0xba, 0x84, 0xcc, 0x6e, 0x27, 0x8e, 0xb0, 0x5d, 0xa8, 0xf3, 0x9f, 0xd0, 0xa2, 0x71, 0xb8, 0x58, 0xdd, 0x2c, 0x38, 0x99, 0x4c, 0x48, 0x07, 0x55, 0xe4, 0x53, 0x8c, 0x46, 0xb6, 0x2d, 0xa5, 0xaf, 0x32, 0x22, 0x40, 0xdc, 0x50, 0xc3, 0xa1, 0x25, 0x8b, 0x9c, 0x16, 0x60, 0x5c, 0xcf, 0xfd, 0x0c, 0x98, 0x1c, 0xd4, 0x37, 0x6d, 0x3c, 0x3a, 0x30, 0xe8, 0x6c, 0x31, 0x47, 0xf5, 0x33, 0xda, 0x43, 0xc8, 0xe3, 0x5e, 0x19, 0x94, 0xec, 0xe6, 0xa3, 0x95, 0x14, 0xe0, 0x9d, 0x64, 0xfa, 0x59, 0x15, 0xc5, 0x2f, 0xca, 0xbb, 0x0b, 0xdf, 0xf2, 0x97, 0xbf, 0x0a, 0x76, 0xb4, 0x49, 0x44, 0x5a, 0x1d, 0xf0, 0x00, 0x96, 0x21, 0x80, 0x7f, 0x1a, 0x82, 0x39, 0x4f, 0xc1, 0xa7, 0xd7, 0x0d, 0xd1, 0xd8, 0xff, 0x13, 0x93, 0x70, 0xee, 0x5b, 0xef, 0xbe, 0x09, 0xb9, 0x77, 0x72, 0xe7, 0xb2, 0x54, 0xb7, 0x2a, 0xc7, 0x73, 0x90, 0x66, 0x20, 0x0e, 0x51, 0xed, 0xf8, 0x7c, 0x8f, 0x2e, 0xf4, 0x12, 0xc6, 0x2b, 0x83, 0xcd, 0xac, 0xcb, 0x3b, 0xc4, 0x4e, 0xc0, 0x69, 0x36, 0x62, 0x02, 0xae, 0x88, 0xfc, 0xaa, 0x42, 0x08, 0xa6, 0x45, 0x57, 0xd3, 0x9a, 0xbd, 0xe1, 0x23, 0x8d, 0x92, 0x4a, 0x11, 0x89, 0x74, 0x6b, 0x91, 0xfb, 0xfe, 0xc9, 0x01, 0xea, 0x1b, 0xf7, 0xce]

def decode_base64_package(base64str):
base64dec = base64.decodestring(base64str)[20:] # we strip the first 20 chars (SHA1 hash, I don't bother checking it at the moment)
decoded = ''
for byte in range(0, len(base64dec)):
decoded += chr(charmap[ord(base64dec[byte])])
return zlib.decompress(decoded)


sys.stderr.write("=== Oracle 10g/11g PL/SQL unwrapper 0.2 - by Niels Teusink - blog.teusink.net ===\n\n" )
if len(sys.argv) < 2:
sys.stderr.write("Usage: %s infile.plb [outfile]\n" % sys.argv[0])
sys.exit(1)

infile = open(sys.argv[1])
outfile = None
if len(sys.argv) == 3:
outfile = open(sys.argv[2], 'w')

lines = infile.readlines()
for i in range(0, len(lines)):
# this is really naive parsing, but works on every package I've thrown at it
matches = re.compile(r"^[0-9a-f]+ ([0-9a-f]+)$").match(lines[i])
if matches:
base64len = int(matches.groups()[0], 16)
base64str = ''
j = 0
while len(base64str) < base64len:
j+=1
base64str += lines[i+j]
base64str = base64str.replace("\n","")
if outfile:
outfile.write(decode_base64_package(base64str) + "\n")
else:
print decode_base64_package(base64str)
except Exception as e:
print('[-] Something went wrong while unwrapping the wrapped procedure.')
print(f'[-] {e}')