In my last blog post I explained a XOR alghorithm that is used to count Oracle database block checksum.
I also wrote, that sometimes you are facing problems, that are unresolvable without a low-level knowledge. This is the story of this kind of situation. The story of misread documentation. The story of haste and hex.
About a year ago, I got a call from one company that did an APEX upgrade for one of their customers. Very big customer. Quite a big database and a very important one, working 24/7.
They told me that they upgraded APEX on a database with one PDB and a week later they tried to apply some patches and restarted the database. After the restart they got the following error:
1 | SQL> alter pluggable database kowalsky open ; |
2 | alter pluggable database kowalsky open |
5 | ORA-00604: error occurred at recursive SQL level 1 |
6 | ORA-01405: fetched column value is NULL |
Oracle Support told them that they messed up APEX upgrade and that they should restore a database from before the upgrade and perform it correctly. They were right. But the customer was not happy to find out that they are about to lose a week of production data.
And this is where a true fun begins!!!
To investigate which query is the problem, you can use the following syntax:
1 | SQL> alter session set events '1405 trace name errorstack level 1'; |
The trace file showed that this query was a problematic one:
1 | ----- Error Stack Dump ----- |
2 | ORA-01405: fetched column value is NULL |
3 | ----- Current SQL Statement for this session (sql_id=) ----- |
4 | select version from registry$ where status not in (9, 99) and namespace='SERVER' and cid='APEX' |
After tracing the session with event 10046 on level 12 I found the execution plan for this query:
1 | STAT #140575638426528 id=1 cnt=1 pid=0 pos=1 obj=1508 op='TABLE ACCESS BY INDEX ROWID REGISTRY$ (cr=2 pr=0 pw=0 time=10 us cost=1 size=25 card=1)' |
2 | STAT #140575638426528 id=2 cnt=1 pid=1 pos=1 obj=1509 op='INDEX UNIQUE SCAN REGISTRY_PK (cr=1 pr=0 pw=0 time=6 us)' |
Here we can see, that column "version" should contain some value for ‘APEX’ but it is null. We could try to put this value in a datafile but it is not easy to extend a row manually. The other thing we could do is to locate blocks of index REGISTRY_PK and make sure that the query will not return any rows (like change value APEX to APEK or something like that).
Or just try to change the status of this row to value 9 or 99.
Back then I used BBED to do it, but there were other cases when I had to do it with a bunch of scripts and a hex editor because I couldn’t compile BBED. So I decided to write my own copy of BBED to have something useful when needed 😉
I called the project RICO2 – based on my first tool to extract data from database files: RICO.
How does it work? Well, it is very similar to BBED but the whole functionality is not covered properly yet. I will make it better with each case I will work on.
First of all, let’s prepare the listfile for rico2. It is simple – just put a list of files you will want to edit. The file can look like this:
1 | [oracle@rico ~]$ cat listfile.txt |
2 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_system_f88k6mg3_.dbf |
3 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_sysaux_f88k56b9_.dbf |
4 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_undotbs1_f88k8cph_.dbf |
5 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_system_f88k9mvk_.dbf |
6 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_users_f88k8bmv_.dbf |
7 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_sysaux_f88k9mvf_.dbf |
8 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_system_f88kr04k_.dbf |
9 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_sysaux_f88kr04f_.dbf |
10 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_users_f88kr04n_.dbf |
11 | /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_example_f88kr049_.dbf |
12 | /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_system_f88l7sop_.dbf |
13 | /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_sysaux_f88l7sov_.dbf |
14 | /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_users_f88l7sow_.dbf |
15 | /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_example_f88l7sox_.dbf |
In a trace file I located appropriate wait events that will show me which blocks and files are interesting for me:
1 | WAIT #140575638426528: nam='db file sequential read' ela= 8 file#=12 block#=2817 blocks=1 obj#=1509 tim=21570820295 |
2 | WAIT #140575638426528: nam='db file sequential read' ela= 6 file#=12 block#=2809 blocks=1 obj#=1508 tim=21570820339 |
1509 is REGISTRY_PK and 1508 is REGISTRY$. File 12 is: /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_system_f88l7sop_.dbf
So now it is time to start our rico2.py:
1 | [oracle@rico ~]$ python rico2.py listfile.txt |
2 | RICO v2 by Kamil Stawiarski (@ora600pl | www.ora-600.pl) |
3 | This is open source project to map BBED functionality. |
4 | If you know how to use BBED, you will know how to use this one. |
5 | Not everything is documented but in most cases the code is trivial to interpret it. |
6 | So if you don 't know how to use this tool - then maybe you shouldn' t ;) |
8 | Usage: python2.7 rico2.py listfile.txt |
9 | The listfile.txt should contain the list of the DBF files you want to read |
13 | This tool should be used only to learn or in critical situations! |
14 | The usage is not supported! |
15 | If found on production system, this software should be considered as malware and deleted immediately! |
17 | 1 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_system_f88k6mg3_.dbf |
18 | 2 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_sysaux_f88k56b9_.dbf |
19 | 3 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_undotbs1_f88k8cph_.dbf |
20 | 4 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_system_f88k9mvk_.dbf |
21 | 5 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_users_f88k8bmv_.dbf |
22 | 6 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_sysaux_f88k9mvf_.dbf |
23 | 7 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_system_f88kr04k_.dbf |
24 | 8 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_sysaux_f88kr04f_.dbf |
25 | 9 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_users_f88kr04n_.dbf |
26 | 10 /u01/app/oracle/oradata/SKIPER/datafile/o1_mf_example_f88kr049_.dbf |
27 | 11 /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_system_f88l7sop_.dbf |
28 | 12 /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_sysaux_f88l7sov_.dbf |
29 | 13 /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_users_f88l7sow_.dbf |
30 | 14 /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_example_f88l7sox_.dbf |
Now we have to set pointer to appropriate block. Let’s start with a table block:
2 | DBA 0x2c00af9 (46140153 11,2809) |
4 | File: /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_system_f88l7sop_.dbf(11) |
5 | Block: 2809 Dba: 0x2c00af9 |
6 | ------------------------------------------------------------ |
9 | struct kcbh, 20 bytes @0 |
11 | struct ktbbh, 72 bytes @20 |
15 | ub1 rowdata[7893] @295 |
In this block we can see 16 records. We can confirm that this is the correct block by checking the ktbbh structure (@24).
2 | struct ktbbh, 72 bytes @20 |
4 | union ktbbhsid, 4 bytes @24 |
5 | ub4 ktbbhsg1 @24 0x5e4 [raw hex: e4050000 OBJD: 1508] |
7 | struct ktbbhcsc, 8 bytes @28 |
8 | ub4 kscnbas @28 0x1d3898 [raw hex: 98381d00] |
13 | ub4 ktbbhfnx @40 0x0 [raw hex: 00000000] |
14 | struct ktbbhitl[0], 24 bytes @44 |
15 | struct ktbitxid, 8 bytes @44 |
16 | ub2 kxidusn @44 0x3 [raw hex: 0300] |
17 | ub2 kxidslt @46 0xb [raw hex: 0b00] |
18 | ub4 kxidsqn @48 0x61a [raw hex: 1a060000] |
19 | struct ktbituba, 8 bytes @52 |
20 | ub4 kubadba @52 0x100aacf [raw hex: cfaa0001] |
21 | ub2 kubaseq @56 0x18b [raw hex: 8b01] |
23 | ub2 ktbitflg @60 0x8000 [raw hex: 0080] |
24 | union _ktbitun, 2 bytes @62 |
25 | sb2 _ktbitfsc @62 0x0 [raw hex: 0000] |
26 | ub2 _ktbitwrp @62 0x0 [raw hex: 0000] |
27 | ub4 ktbitbas @64 0x1badd2 [raw hex: d2ad1b00] |
28 | struct ktbbhitl[1], 24 bytes @68 |
29 | struct ktbitxid, 8 bytes @68 |
30 | ub2 kxidusn @68 0x0 [raw hex: 0000] |
31 | ub2 kxidslt @70 0x0 [raw hex: 0000] |
32 | ub4 kxidsqn @72 0x0 [raw hex: 00000000] |
33 | struct ktbituba, 8 bytes @76 |
34 | ub4 kubadba @76 0x0 [raw hex: 00000000] |
35 | ub2 kubaseq @80 0x0 [raw hex: 0000] |
37 | ub2 ktbitflg @84 0x0 [raw hex: 0000] |
38 | union _ktbitun, 2 bytes @86 |
39 | sb2 _ktbitfsc @86 0x0 [raw hex: 0000] |
40 | ub2 _ktbitwrp @86 0x0 [raw hex: 0000] |
41 | ub4 ktbitbas @88 0x0 [raw hex: 00000000] |
Now we have to find the appropriate row. In addition to regular BBED I added a possibility to search rows by a column:
1 | rico2 > select col0=c:APEX |
Great! My row is 14th row in a data set (remember that numbers start with 0).
Let’s display the row and format it accordingly to columns in REGISTRY$
10 | col 1[26] @303: 4f7261636c65204170706c69636174696f6e2045787072657373 |
17 | col 8[7] @342: 7876020e102118 |
19 | col 10[59] @351: 4f7261636c65204170706c69636174696f6e20457870726573732052656c6561736520342e322e352e30302e3038202d2050726f64756374696f6e |
20 | col 11[13] @411: 56414c49444154455f41504558 |
22 | col 13[7] @426: 7876020e102119 |
23 | col 14[7] @434: 78720707072a03 |
24 | col 15[7] @442: 78720707073413 |
32 | col 23[6] @457: 534552564552 |
43 | col 0[4] @298: 41504558 APEX |
44 | col 1[26] @303: 4f7261636c65204170706c69636174696f6e2045787072657373 Oracle Application Express |
51 | col 8[7] @342: 7876020e102118 |
53 | col 10[59] @351: 4f7261636c65204170706c69636174696f6e20457870726573732052656c6561736520342e322e352e30302e3038202d2050726f64756374696f6e |
54 | col 11[13] @411: 56414c49444154455f41504558 |
56 | col 13[7] @426: 7876020e102119 |
57 | col 14[7] @434: 78720707072a03 |
58 | col 15[7] @442: 78720707073413 |
66 | col 23[6] @457: 534552564552 |
As you can see, col 6 (7th column in a table, so "STATUS") has value "1″. The problematic query is skipping values 9 and 99. 9 is c10a in hex representation for NUMBER and 99 is c164 – both are ok, because I only have to be sure not to change the length of the row.
So let’s try to change it to c10a. First of all I have to set offset to the appropriate value.
3 | File: /u01/app/oracle/oradata/SKIPER/652E0234EC88260AE0530100007FFD8B/datafile/o1_mf_system_f88l7sop_.dbf(11) |
4 | Block: 2809 Offsets: 338 to 850 Dba: 0x2c00af9 |
5 | --------------------------------------------------------------- |
6 | c1020180 07787602 0e102118 ff3b4f72 | .....xv...!..;Or |
7 | 61636c65 20417070 6c696361 74696f6e | acle Application |
8 | 20457870 72657373 2052656c 65617365 | Express Release |
9 | 20342e32 2e352e30 302e3038 202d2050 | 4.2.5.00.08 - P |
Now I can put the new hex values:
2 | Usage: First - set offset to a place that you want to modify. |
3 | Then: modify [-s bytestring | -h hex] |
5 | You want to modify block: 2809 at offset: 338 |
8 | Block data changed. To save changes set edit mode and type: save |
After this step, we have to recalculate database block checksum:
4 | Block data changed. To save changes set edit mode and type: save |
The last thing to do is to save changes to disk:
2 | You have to be in edit mode to save block to disk. Type: set mode edit |
5 | Current block data successfully saved to disk. To revert changes, type: dupa |
And check the database:
1 | SQL> alter system flush shared_pool; |
5 | SQL> alter system flush buffer_cache; |
9 | SQL> alter pluggable database kowalsky open; |
11 | Pluggable database altered. |
It WORKS!!! 😀
OK, now let’s just revert the value of the status column to "1″ with normal SQL:
1 | SQL> alter session set container=kowalsky; |
5 | SQL> select version, status from registry$ where cid= 'APEX' ; |
11 | SQL> update registry$ set version= '4.2.5.00.08' , status=1 where cid= 'APEX' ; |
After the final restart (just to be sure) you can open another beer and be happy 😉
You have to be aware of one thing – this is an unsupported and highly risky method of fixing things.
It should be the last resort situation. Additionally, this tool should be considered a malware on a productional system and although it simulates some original BBED behaviors, it is NOT based on reverse engineering – so RICO2 is just a set of my ideas of how the database block works, based on official materials. If you want to use this tool – use it at your own risk.
Is that clear!? Good! Have fun 😉
https://github.com/ora600pl/rico2
P.S.
Soon we will start building agenda for POUG18! http://poug.org/ You won’t believe the value of the content! Soon I will start a contest in which you will be able to win discount codes for the tickets, so observe the blog 😉