最近碰到一個問題,需要解密幾個使用oracle wrap加密過的程序包,查了下,已經有很多可用的程序,支持10g,11g,連12c都支持。查找過程中,花了點時間研究了下解密的過程,簡單記錄一下。
1. 解密算法解析
要解密,首先要了解加密的原理,在這幾個版本的oracle中,解密的理論依據都來源于 "The oracle hacker's handbook" by David Litchfield 這本書,書中介紹了wrap的過程:
- 將源碼使用lz壓縮算法壓縮,得到壓縮串lzstr
- 對壓縮串做sha-1運算,得到40位的哈希串shstr
- 將哈希串與壓縮串拼在一起,shstr+lzstr
- 將得到的字符串再按字節執行字典表替換
- 再執行base64編碼,就得到了加密的字符串
如果要解密加密以后的字符串,按照以上加密的過程反向推算,可以發現最關鍵的就是第4步中的字典表替換過程,要解決這個問題,我們就要得到這個替換的字典表,幸運的是,經過測試發現,oracle的這個字典表是固定的按字節映射,所以:
我們可以通過wrap加密一個簡單字符串,比如create PACKAGE a0
,得到加密后的密文,然后執行一次base64解碼,就能得到替換后的字符串,代碼如下
select substr(utl_encode.base64_decode(utl_raw.cast_to_raw(
rtrim(substr(wrap_text,
instr(wrap.wrap,chr(10),1,20) + 1),chr(10)))),
41) from dual
這里說明一下,wrap以后的密文里,前面20行是沒有用的,密文是從第21行開始的,第21行的前40個字符,是拼接的哈希串,從第41個字符開始才是字典表轉換后的密文
本例中,得到的結果為
78DA0B7074F67674775548346000001185029E
對字符串create PACKAGE a0
執行lz壓縮,得到字典表轉換前的字符串,本例中的結果為
308399B8F5339FF5BF5C5A2170A6A6F302E141
這樣我們得到了轉換前和轉換后的字符串,就可以按字節建立起我們需要的字典表了,比如本例中:
轉換前-轉換后
30 78
83 DA
依次類推,我們就可以建立起這個字典表,因為一個字節從00-FF有256個字符,所以相應的這個典表應該有256條記錄,這里我們只得到了其中的一部分,還需要選用不同的字符串,多次窮舉運算,最終得到全部的記錄。事實上,因為這個字典表是固定的,所以得到的結果可以重復使用。當然也可以直接使用別人提供的。
有了字典表,接下來的工作就比較簡單了,按照加密過程反向運算即可:
- 從數據庫中得到package的密文
- base64解碼
- 字典表反向映射
- 去掉前40位哈希串
- lz解壓縮
2. 解密程序源碼
這里貼上部分源碼,供參考
LZ解壓和壓縮的代碼,使用java source過程,為了支持比較大的package,使用CLOB作為參數類型,經測試,可以支持較大的程序包
create or replace java source named CUX_UNWRAPPER
as
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.zip.Deflater;
import java.util.zip.InflaterInputStream;
import oracle.jdbc.OracleDriver;
import oracle.sql.CLOB;
public class UNWRAPPER {
//解壓縮
public static CLOB Inflate(CLOB src) throws IOException, SQLException {
StringBuffer sb = new StringBuffer();
String s = src.stringValue();
try{
ByteArrayInputStream bis =
new ByteArrayInputStream(decodeHex(s.toCharArray()));
//ByteArrayInputStream bis = new ByteArrayInputStream(src);
InflaterInputStream iis = new InflaterInputStream(bis);
for (int c = iis.read(); c != -1; c = iis.read()) {
sb.append((char)c);
}
}catch (Exception e)
{
e.printStackTrace();
}
Connection conn = DriverManager.getConnection("jdbc:default:connection:");
CLOB clob = CLOB.createTemporary(conn, false, CLOB.DURATION_SESSION);
clob.setString(1, sb.toString());
return clob;
}
public static byte[] Deflate(String src, int quality) {
try {
byte[] tmp = new byte[src.length() + 100];
Deflater defl = new Deflater(quality);
defl.setInput(src.getBytes("UTF-8"));
defl.finish();
int cnt = defl.deflate(tmp);
byte[] res = new byte[cnt];
for (int i = 0; i < cnt; i++)
res = tmp;
return res;
} catch (Exception e) {
}
return null;
}
public static int toDigit(char ch, int index) {
int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new RuntimeException("illegal hexadecimal character " + ch +
" at index " + index);
}
return digit;
}
//16進制字符串轉字節數組
public static byte[] decodeHex(char[] data) {
int len = data.length;
if ((len & 0x01) != 0) {
throw new RuntimeException("odd number of characters ");
}
byte[] out = new byte[len >> 1];
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f = f | toDigit(data[j], j);
j++;
out[i] = (byte)(f & 0xFF);
}
return out;
}
}
/
--編譯java source
alter java source CUX_UNWRAPPER compile
/
解密plsql主程序
--程序包體
create or replace package body cux_unwrapper is
function deflate(src in varchar2) return raw is
begin
return deflate(src, 6);
end;
--壓縮
function deflate(src in varchar2, quality in number) return raw as
language java name 'UNWRAPPER.Deflate( java.lang.String, int ) return byte[]';
--解壓縮
function inflate(src in clob) return clob as
language java name 'UNWRAPPER.Inflate( oracle.sql.CLOB ) return oracle.sql.CLOB';
--輸出clob
procedure out_put(p_clob clob) is
l_len integer;
l_pos integer := 1;
l_chunk integer := 2000;
l_buffer varchar2(4000);
l_amount integer;
l_ins_pos integer := 1;
begin
l_len := dbms_lob.getlength(p_clob);
while l_pos < l_len loop
if l_len - l_pos < l_chunk then
l_ins_pos := l_len;
else
l_ins_pos := dbms_lob.instr(p_clob, chr(10), l_pos + l_chunk, 1);
end if;
l_amount := l_ins_pos - l_pos + 1;
dbms_lob.read(p_clob, l_amount, l_pos, l_buffer);
dbms_output.put_line(l_buffer);
l_pos := l_ins_pos + 1;
end loop;
end out_put;
--解密主程序
PROCEDURE unwrap(p_owner IN VARCHAR,
p_name IN VARCHAR,
p_type IN VARCHAR) AS
l_wrap varchar2(32767);
l_inf clob;
l_res clob;
l_src clob;
l_inflate varchar2(32767);
l_temp VARCHAR2(32767);
l_bt varchar2(32767);
l_text varchar2(32767);
l_char varchar2(2);
l_len number;
l_slen integer;
l_offset integer := 1;
l_chunk_len integer := 10080;
--解密字節對照字典表
l_dict varchar2(512) := '3D6585B318DBE287F152AB634BB5A05F' ||
'7D687B9B24C228678ADEA4261E03EB17' ||
'6F343E7A3FD2A96A0FE935561FB14D10' ||
'78D975F6BC4104816106F9ADD6D5297E' ||
'869E79E505BA84CC6E278EB05DA8F39F' ||
'D0A271B858DD2C38994C480755E4538C' ||
'46B62DA5AF322240DC50C3A1258B9C16' ||
'605CCFFD0C981CD4376D3C3A30E86C31' ||
'47F533DA43C8E35E1994ECE6A39514E0' ||
'9D64FA5915C52FCABB0BDFF297BF0A76' ||
'B449445A1DF0009621807F1A82394FC1' ||
'A7D70DD1D8FF139370EE5BEFBE09B977' ||
'72E7B254B72AC7739066200E51EDF87C' ||
'8F2EF412C62B83CDACCB3BC44EC06936' ||
'6202AE88FCAA4208A64557D39ABDE123' ||
'8D924A1189746B91FBFEC901EA1BF7CE';
l_sl varchar2(512) := '000102030405060708090A0B0C0D0E0F' ||
'101112131415161718191A1B1C1D1E1F' ||
'202122232425262728292A2B2C2D2E2F' ||
'303132333435363738393A3B3C3D3E3F' ||
'404142434445464748494A4B4C4D4E4F' ||
'505152535455565758595A5B5C5D5E5F' ||
'606162636465666768696A6B6C6D6E6F' ||
'707172737475767778797A7B7C7D7E7F' ||
'808182838485868788898A8B8C8D8E8F' ||
'909192939495969798999A9B9C9D9E9F' ||
'A0A1A2A3A4A5A6A7A8A9AAABACADAEAF' ||
'B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF' ||
'C0C1C2C3C4C5C6C7C8C9CACBCCCDCECF' ||
'D0D1D2D3D4D5D6D7D8D9DADBDCDDDEDF' ||
'E0E1E2E3E4E5E6E7E8E9EAEBECEDEEEF' ||
'F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF';
cursor c_src is
SELECT src.line, src.text
FROM DBA_SOURCE src
WHERE owner = p_owner
AND Name = p_name
AND TYPE = p_type
order by line;
BEGIN
dbms_lob.createtemporary(lob_loc => l_inf,
cache => TRUE,
dur => dbms_lob.session);
--取得package密文
for rec in c_src loop
l_src := l_src || rtrim(rec.text);
end loop;
--out_put('source:<'||l_src||'>');
l_src := rtrim(substr(l_src, instr(l_src, chr(10), 1, 20) + 1), chr(10));
l_src := replace(l_src, chr(10), '');
-- dbms_output.put_line('source:<'||l_src||'>');
--l_src:=substr(l_src,41);
--base64解碼
l_len := dbms_lob.getlength(l_src);
while l_offset < l_len loop
if (l_len - l_offset) < 10080 then
l_chunk_len := (l_len - l_offset);
end if;
l_temp := dbms_lob.substr(l_src, l_chunk_len, l_offset);
l_bt := utl_encode.base64_decode(utl_raw.cast_to_raw(l_temp));
if l_bt is not null then
if l_offset = 1 then
--去掉前40位哈希串
l_bt := substr(l_bt, 41);
end if;
-- l_wrap := l_wrap || l_bt;
--字典表轉換
l_bt := utl_raw.translate(l_bt, l_sl, l_dict);
l_offset := l_offset + l_chunk_len;
-- l_inflate := l_inflate || l_bt;
dbms_lob.writeappend(l_inf, length(l_bt), l_bt);
end if;
end loop;
/* dbms_output.put_line('base:' || l_wrap);
dbms_output.put_line('<' || l_inflate || '>');*/
--解壓縮
l_res := inflate(l_inf);
out_put(l_res);
END unwrap;
END;
/