GDUL 破解

1. 绪论:Oracle 数据恢复与卸载工具之必要性在企业级数据管理领域,Oracle 数据库凭借其强大的事务处理能力和高可用性架构,长期占据核心地位。然而,即便拥有 RMAN(Recovery Manager)、Data Guard 以及 Zero Data Loss Recovery Appliance 等先进的保护方案,数据库由于物理介质损坏、元数据逻辑错误、人为误操作或勒索病毒攻击而导致无法挂载的情况依然屡见不鲜

2. 当数据库无法启动且标准恢复手段失效时,基于底层数据块扫描的“卸载工具”(Unloader)成为挽救数据的最后一道防线。GDUL(Guyu Data Unloader)作为一款由点睛图(Dianjingtu)自主研发的类 DUL(Data Unloader)工具,在 Oracle 恢复领域具有显著的技术优势

3. 与基于 Java 开发的 PRM-DUL 等工具不同,GDUL 采用 C/C++ 语言编写,这赋予了它极高的执行效率和更底层的资源控制能力 。在处理 TB 级规模的损坏数据库时,C 语言编写的二进制程序能够通过直接系统调用(Direct I/O)和高效的内存管理,显著缩短数据提取周期。老耿确实无敌

然而,作为一款商业软件,GDUL 通常采用许可密钥(License Key)机制来控制功能权限。在未授权或试用模式下,程序会在启动或执行过程中弹出“Warning: This is a trial key, ONLY unload first 1000 rows!”的提示,并限制每个表仅能导出前 1000 行数据 [User Query]。这里就是技术人所不能忍的,是时候检验自己逆向的水瓶了。

方案一:ltrace 库函数调用追踪(初步定位)

ltrace 是 Linux 下追踪程序调用动态库函数的利器。GDUL 必须通过库函数(如 fopen)读取 license_key.dat,随后通常会进行字符串比对。实际测试过程中不理想,没法定位到具体函数。最后测试才发现原来是用了openat 替代了 open函数。

方案二:Frida 动态插桩(深度分析与调用栈追踪)

在 CentOS 7.9 上,Frida 可以绕过剥离符号(Stripped)的限制,直接打印调用堆栈来定位上层逻辑函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 拦截 fopen 库函数
var fopenPtr = Module.findExportByName(null, 'fopen');
Interceptor.attach(fopenPtr, {
onEnter: function (args) {
this.filename = args.readUtf8String();
if (this.filename.indexOf("license_key.dat")!== -1) {
console.log("[*] 发现读取授权文件: " + this.filename);
// 打印调用栈,精准定位是哪个内部函数发起的读取
console.log('调用栈:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
}
}
});

// 搜索 1000 行限制特征码 (0x3E8)
// 假设在导出数据循环中会有 cmp eax, 0x3e8
var results = Memory.scanSync(Process.enumerateModules().base,
Process.enumerateModules().size,
"E8 03 00 00"); // 1000 的十六进制小端序
results.forEach(function(match) {
console.log("[*] 发现潜在的 1000 行限制比较点: " + match.address);
});

使用 -f 模式从启动开始注入,以便抓取初始化时的 Key 校验
frida -L hook_gdul.js -f./gdul --no-pause

实际测试过程中很不理想,centos7.9 中的frida 莫名其妙的出问题,经常卡死切找不到原因。抱歉,自己才疏学浅无法熟练的使用frida ……

方案三:LD_PRELOAD 劫持(生产环境最稳健)

由于 CentOS 7.9 默认环境可能对 Frida 兼容性有限,使用 C 语言编写 LD_PRELOAD 钩子是最原生、成功率最高的方法。

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
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#include <string.h>

// 定义函数指针原型
typedef FILE* (*fopen_t)(const char *path, const char *mode);

FILE* fopen(const char *path, const char *mode) {
static fopen_t real_fopen = NULL;
if (!real_fopen) {
real_fopen = (fopen_t)dlsym(RTLD_NEXT, "fopen");
}

// 识别关键文件
if (strstr(path, "license_key.dat")) {
// __builtin_return_address(0) 获取调用该 fopen 的程序内部地址
printf("[HOOK] 发现读取 license_key.dat,调用源地址: %p\n", __builtin_return_address(0));
}

return real_fopen(path, mode);
}

// 进一步拦截 strcmp 定位比对逻辑
int strcmp(const char *s1, const char *s2) {
static int (*real_strcmp)(const char*, const char*) = NULL;
if (!real_strcmp) real_strcmp = dlsym(RTLD_NEXT, "strcmp");

int res = real_strcmp(s1, s2);
// 如果比对内容看起来像 Key,打印出来
if (strlen(s1) > 10 && strlen(s2) > 10) {
printf("[HOOK] strcmp 比对: s1=%s, s2=%s, 返回值=%d, 调用源=%p\n", s1, s2, res, __builtin_return_address(0));
}
return res;
}
  • 编译与运行:*
1
2
3
4
5

# 编译为共享库
gcc -shared -fPIC -o license_hook.so license_hook.c -ldl
# 加载运行
LD_PRELOAD=./license_hook.so./gdul

license_hook.c 文件过于复杂,具体涉及到分析各个函数过程,摊开来讲的话估计能出一本书,不做具体讲解。如有人实在感兴趣,可联系作者沟通。VX: www-gladly-wang

  • 当前实际效果

准备环境、测试数据

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
[oracle@mytest ~]$ sqlplus mytest1/XsyxSocttUsr2026@orcl11g
SQL*Plus: Release 11.2.0.4.0 Production on Wed Jan 14 09:11:17 2026
Copyright (c) 1982, 2013, Oracle. All rights reserved.
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> INSERT INTO test_emp_1
SELECT
LEVEL,
'EMP_' || LEVEL,
ROUND(DBMS_RANDOM.VALUE(3000, 20000), 2),
MOD(LEVEL, 10) + 10,
SYSDATE - MOD(LEVEL, 365)
FROM dual
CONNECT BY LEVEL <= 1000000;
2 3 4 5 6 7 8 9

1000000 rows created.

SQL> SQL> commit;

Commit complete.
SQL> select count(*) from test_emp_1;

COUNT(*)
----------
1000000

SQL> alter system checkpoint;

System altered.

SQL> drop table test_emp_1;

Table dropped.

SQL> commit;

Commit complete.
SQL> alter system switch logfile;

System altered.

[oracle@mytest ~]$ sqlplus / as sysdba
SQL> SQL> select obj#, dataobj# from sys.obj$
as of timestamp to_timestamp('2026-01-14 09:00:00','YYYY-MM-DD HH24:MI:SS')
where owner# = (select user_id from dba_users where username=' 2 3 MYTEST1') and name='TEST_EMP_1';

OBJ# DATAOBJ#
---------- ----------
87969 87972

SQL> exit

根据GDUL手册,恢复drop表需要表结构。所以还需要在新的表空间上新建与原表同样的表结构。
** 注 :不要在原表空间上操作,容易造成数据覆盖,到时候怎么都救不回来的
偷个懒,直接在sys下新建表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[oracle@mytest ~]$ sqlplus / as sysdba

SQL*Plus: Release 11.2.0.4.0 Production on Wed Jan 14 09:37:45 2026

Copyright (c) 1982, 2013, Oracle. All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options

SQL> CREATE TABLE sys.test (
id NUMBER PRIMARY KEY,
name VARCHAR2(50),
salary NUMBER(10,2),
deptno NUMBER,
create_dt DATE
) TABLESPACE SYSTEM;
SQL> alter system checkpoint;

System altered.

尽量不要在当前数据文件上直接测试。万一误操作,会给现场二次破坏

  • [root@mytest ~]# cp /data/u01/app/oracle/oradata/orcl11g/* /data/tmp/gdul/gdul/datafile/ *

环境:centos7.9 / gdul V2025.2.u3

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
root@mytest gdul]# gcc -shared -fPIC -o force_rw.so force_rw.c -ldl
root@mytest gdul]# LD_PRELOAD=./force_rw.so ./bin_file/gdul_linux64
[LD_PRELOAD] Initializing Memory Patches...
[LD_PRELOAD] All patches applied. Systems ready.
Loading scan extent from cache file...
Finish loading scan extent.
Loading scan lobpage from cache file...
Finish loading scan lobpage.
Loading dict from cache file...
Finish loading dict.

NOTE:
DB Compatible => [ 11.2.0.1 ]
Export Format => [ EXPDP FILE ]
Export Directory => [ dump ]

GDUL> bootstrap
Starting bootstrap at rfile# 1, block# 520...
Bootstrap finish.

GDUL> scan tablespace
Scan tablespace 0 (0.73 GB) in parallel 1...
Scan tablespace 0 completed.
GDUL> scan tablespace 4
Scan tablespace 4 (0.42 GB) in parallel 1...
Scan tablespace 4 completed.
GDUL> unload table sys.test object_id 87972;
2026-01-14 09:45:56 unloading table "SYS"."TEST" with data_object_id 87972 in parallel 1...
2026-01-14 09:45:57 unloaded 1000000 rows (1.01 secs).
GDUL> exit

有用过正版的朋友估计已近发现了,* Warning: This is a trial key, ONLY unload first 1000 rows! * 这个提示已经没有了,且程序运行时作者广告也被我给屏蔽了。
可以看到数据已经恢复到了指定的目录。根据GDUL习惯,在当前gdul根目录的dump下。格式是数据泵导出的标准格式

1
2
3
[oracle@mytest dump]$ ll
-rw-r--r-- 1 root root 36495360 Jan 14 09:40 SYS_TEST.87972.dmp
-rw-r--r-- 1 root root 119 Jan 14 09:40 SYS_TEST.sql

导入数据库中进行验证即可

注,这里需要提前建立数据库的directory。熟悉oracle数据库的朋友无需多言。

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
[oracle@mytest dump]$ cp SYS_TEST.87972.dmp /data/u01/app/oracle/dump/

[oracle@mytest dump]$ impdp directory=GDUL_DMP dumpfile=SYS_TEST.87972.dmp logfile=SYS_TEST.log

Import: Release 11.2.0.4.0 - Production on Wed Jan 14 09:44:04 2026

Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.

Username: / as sysdba

Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
Master table "SYS"."SYS_IMPORT_FULL_01" successfully loaded/unloaded
Starting "SYS"."SYS_IMPORT_FULL_01": /******** AS SYSDBA directory=GDUL_DMP dumpfile=SYS_TEST.87972.dmp logfile=SYS_TEST.log
Processing object type TABLE_EXPORT/TABLE/TABLE_DATA
. . imported "SYS"."TEST" 34.76 MB 1000000 rows
Job "SYS"."SYS_IMPORT_FULL_01" successfully completed at Wed Jan 14 09:44:23 2026 elapsed 0 00:00:05

[oracle@mytest ~]$ sqlplus / as sysdba

SQL*Plus: Release 11.2.0.4.0 Production on Wed Jan 14 10:28:58 2026

Copyright (c) 1982, 2013, Oracle. All rights reserved.


Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SQL> select count(*)from test;

COUNT(*)
----------
1000000

SQL>