最近碰到一个问题,需要解密几个使用oracle wrap加密过的程序包,查了下,已经有很多可用的程序,支持10g,11g,连12c都支持。查找过程中,花了点时间研究了下解密的过程,简单记录一下。
解密算法解析
加密过程
要解密,首先要了解加密的原理,在这几个版本的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解码,就能得到替换后的字符串,代码如下
1
2
3
4
|
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
这样我们得到了转换前和转换后的字符串,就可以按字节建立起我们需要的字典表了,比如本例中:
1
2
3
|
转换前-转换后
30 78
83 DA
|
依次类推,我们就可以建立起这个字典表,因为一个字节从00-FF有256个字符,所以相应的这个典表应该有256条记录,这里我们只得到了其中的一部分,还需要选用不同的字符串,多次穷举运算,最终得到全部的记录。事实上,因为这个字典表是固定的,所以得到的结果可以重复使用。当然也可以直接使用别人提供的。
有了字典表,接下来的工作就比较简单了,按照加密过程反向运算即可:
- 从数据库中得到package的密文
- base64解码
- 字典表反向映射
- 去掉前40位哈希串
- lz解压缩
解密程序源码
这里贴上部分源码,供参考
LZ解压和压缩的代码,使用java source过程,为了支持比较大的package,使用CLOB作为参数类型,经测试,可以支持较大的程序包
java 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
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主程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
--程序包体
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;
/
|
文章作者
Imhun
上次更新
2022-07-15
(38a6c3e)