Oracleデータベースの深層(3)−BBEDでデータファイルをハッキング

今回利用するサンプルデータは最初からサンプルスキーマとして
インストールしたHRスキーマのJOBSテーブルです。
ご覧の通り19件データが存在する。

sys@O11G2> select * from hr.jobs;

JOB_ID     JOB_TITLE                           MIN_SALARY MAX_SALARY
---------- ----------------------------------- ---------- ----------
AD_PRES    President                                20080      40000
AD_VP      Administration Vice President            15000      30000
AD_ASST    Administration Assistant                  3000       6000
FI_MGR     Finance Manager                           8200      16000
FI_ACCOUNT Accountant                                4200       9000
AC_MGR     Accounting Manager                        8200      16000
AC_ACCOUNT Public Accountant                         4200       9000
SA_MAN     Sales Manager                            10000      20080
SA_REP     Sales Representative                      6000      12008
PU_MAN     Purchasing Manager                        8000      15000
PU_CLERK   Purchasing Clerk                          2500       5500
ST_MAN     Stock Manager                             5500       8500
ST_CLERK   Stock Clerk                               2008       5000
SH_CLERK   Shipping Clerk                            2500       5500
IT_PROG    Programmer                                4000      10000
MK_MAN     Marketing Manager                         9000      15000
MK_REP     Marketing Representative                  4000       9000
HR_REP     Human Resources Representative            4000       9000
PR_REP     Public Relations Representative           4500      10500

19 rows selected.
例としてVice PresidentのROWIDを調べてみます

sys@O11G2> select rowid from hr.jobs where JOB_ID='AD_VP';

ROWID
------------------
AAAR5TAAFAAAAC/AAB
ROWIDを分析するため、便利なプロシージャを実装します。

sys@O11G2> create or replace procedure get_rowinfo(rid in rowid) as
  sm      varchar2(9) := 'SMALLFILE';
  rid_t   number;
  obj_n   number;
  file_n  number;
  block_n number;
  row_n   number;
begin
  DBMS_ROWID.ROWID_INFO(rid, rid_t, obj_n, file_n, block_n, row_n, sm);
  DBMS_OUTPUT.PUT_LINE('Type:            ' || to_char(rid_t));
  DBMS_OUTPUT.PUT_LINE('Data obj number: ' || to_char(obj_n));
  DBMS_OUTPUT.PUT_LINE('Relative fno:    ' || to_char(file_n));
  DBMS_OUTPUT.PUT_LINE('Block number:    ' || to_char(block_n));
  DBMS_OUTPUT.PUT_LINE('Row number:      ' || to_char(row_n));
end;
/
先ほどのROWIDを分析します。

sys@O11G2> exec get_rowinfo('AAAR5TAAFAAAAC/AAB');
Type:            1
Data obj number: 73299
Relative fno:    5
Block number:    191
Row number:      1

PL/SQL procedure successfully completed.

解説:
TYPEは1:DATA、2:INDEXです。
Data obj numberはあまり重要ではありませんので、説明を省略します。
Relative fnoとBlock numberが分かればDBA(Data Block Address)も分かります。
DBAはこれからのBBED操作上とても重要です。
*ここのDBAはデータベース管理者のことではありません。
おまけ:
ORACLE ROWIDの仕組みを少々触れます。
ROWIDはBASE64でencodeされています。
encodeの方法としては
A-Zを十進の0-25に、
a-zを十進の26-51に、
0-9を十進の52-61に、
+と/を十進の6263に変換されます
(http://www.ietf.org/rfc/rfc2045.txt)
例:AAAR5Tの場合、
A:000000
R:010001
5:111001
T:010011

000000 000000 000000 010001 111001 01001173299
となります。

ROWIDに下記の情報が含まれています。
1)データオブジェクト番号
2)データファイル番号
3)データブロック番号
4)行(ROW)位置
フォーマットは:OOOOOO.FFF.BBBBBB.RRR となっています
   OOOOOO はオブジェクトID
      FFF はデータファイル番号
      BBBBBB はブロック番号
      RRR は行番号
上記のROWIDをDECODEしてみると、
sys@O11G2> select 'AAAR5TAAFAAAAC/AAB' as therowid,
  to_number(utl_encode.base64_decode(utl_raw.cast_to_raw(
lpad(substr('AAAR5TAAFAAAAC/AAB',1, 6), 8, 'A'))), 'XXXXXXXXXXXX') as objid,
  to_number(utl_encode.base64_decode(utl_raw.cast_to_raw(
lpad(substr('AAAR5TAAFAAAAC/AAB', 7, 3), 4, 'A'))), 'XXXXXX') as filenum,
  to_number(utl_encode.base64_decode(utl_raw.cast_to_raw(
lpad(substr('AAAR5TAAFAAAAC/AAB', 10, 6), 8, 'A'))), 'XXXXXXXXXXXX') as blocknum,
  to_number(utl_encode.base64_decode(utl_raw.cast_to_raw(
lpad(substr('AAAR5TAAFAAAAC/AAB', 16, 3), 4, 'A'))), 'XXXXXX') as rowslot
  from dual
  ;

THEROWID                OBJID    FILENUM   BLOCKNUM    ROWSLOT
------------------ ---------- ---------- ---------- ----------
AAAR5TAAFAAAAC/AAB      73299          5        191          1

一目瞭然でしょう。
DBをシャットダウンし、
bbedの設定ファイルを作成する。

sys@O11G2> select file#||' '||name||' '||bytes from v$datafile;

FILE#||''||NAME||''||BYTES
--------------------------------------------------------------------------------
1 /u01/oracle/app/oradata/o11g2/system01.dbf 713031680
2 /u01/oracle/app/oradata/o11g2/sysaux01.dbf 555745280
3 /u01/oracle/app/oradata/o11g2/undotbs01.dbf 104857600
4 /u01/oracle/app/oradata/o11g2/users01.dbf 5242880
5 /u01/oracle/app/oradata/o11g2/example01.dbf 104857600

sys@O11G2> show parameter db_block_size;

NAME             TYPE        VALUE
---------------- ----------- ------
db_block_size    integer     8192

sys@O11G2> shutdown immediate
Database closed.
Database dismounted.
ORACLE instance shut down.

[oracle@CentOS53 bbed]$ more filelist.txt
1 /u01/oracle/app/oradata/o11g2/system01.dbf 713031680
2 /u01/oracle/app/oradata/o11g2/sysaux01.dbf 555745280
3 /u01/oracle/app/oradata/o11g2/undotbs01.dbf 104857600
4 /u01/oracle/app/oradata/o11g2/users01.dbf 5242880
5 /u01/oracle/app/oradata/o11g2/example01.dbf 104857600

[oracle@CentOS53 bbed]$ more par.bbd
blocksize=8192
listfile=filelist.txt
mode=edit
究極のツールを実行します。

[oracle@CentOS53 bbed]$ bbed parfile=par.bbd
Password:

BBED: Release 2.0.0.0.0 - Limited Production on Tue Oct 27 11:07:48 2009

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

************* !!! For Oracle Internal Use only !!! ***************
今回のデータベースでは8Kのブロックサイズで起動しましたが、
set filenameとset blockを使えば指定し直すことも可能です。
基本的に8Kのブロックサイズの場合特殊な型を使っていなければ
同じテーブルを一個のブロックに集中格納する可能性が高いです。
先ほどの調査でVice Presidentが存在するブロック情報を得ましたね。
まずそれを探すために、data block addressを指定し、最初から探すため
OFFSETも0に指定します。
BBED> set dba 5,191
        DBA             0x014000bf (20971711 5,191)
BBED> set offset 0
        OFFSET          0
検索する場合、対応するデータ型は下記の通りです。(help find)
x:16進
d:10進
u:符号なし10進
o:8進
c:キャラクタ

BBED> find /c Vice President
 File: /u01/oracle/app/oradata/o11g2/example01.dbf (5)
 Block: 191              Offsets: 8143 to 8191           Dba:0x014000bf
------------------------------------------------------------------------
 50726573 6964656e 7403c302 3302c304 2c010407 41445f50 52455309 50726573
 6964656e 7404c303 015102c3 05010606 88

 <32 bytes per line>
極端な例として、ハッカーが秘密情報を探るため、
関連ブロックの情報をダンプします。

BBED> dump /v dba 5,191 offset 8143 count 128
 File: /u01/oracle/app/oradata/o11g2/example01.dbf (5)
 Block: 191     Offsets: 8143 to 8191  Dba:0x014000bf
-------------------------------------------------------
 50726573 6964656e 7403c302 3302c304 l President.テ.3.テ.
 2c010407 41445f50 52455309 50726573 l ,...AD_PRES.Pres
 6964656e 7404c303 015102c3 05010606 l ident.テ..Q.テ....
 88                                  l .

 <16 bytes per line>
では、このテーブルのレコード数はいくつでしょうか?
ここでデータブロックの知識が必要ですが、
kdbhの構造体をみてみます。
BBED> print kdbh
struct kdbh, 14 bytes                       @100
   ub1 kdbhflag                             @100      0x00 (NONE)
   sb1 kdbhntab                             @101      1
   sb2 kdbhnrow                             @102      19
   sb2 kdbhfrre                             @104     -1
   sb2 kdbhfsbo                             @106      56
   sb2 kdbhfseo                             @108      7397
   sb2 kdbhavsp                             @110      7341
   sb2 kdbhtosp                             @112      7341
注目するのはkdbhnrowです。
この場合は19レコードが存在しますね。

なるほど、じゃ4行目のデータがほしいなら、
eXamineコマンドでポインタを移動しながらチェックします。

BBED> print *kdbr[3]
rowdata[541]
------------
ub1 rowdata[541]                            @8038     0x2c

今OFFSET=8038のところで止まっています。
eXamineコマンドで/rフォーマットを指定します。(文字列:c、数値:n)
BBED> x /rccnn
rowdata[541]                                @8038
------------
flag@8038: 0x2c (KDRHFL, KDRHFF, KDRHFH)
lock@8039: 0x01
cols@8040:    4

col    0[6] @8041: FI_MGR
col   1[15] @8048: Finance Manager
col    2[2] @8064: 8200
col    3[3] @8067: 16000

ハッキングの視点からFinance Managerの給料がわかることになります。
ここで質問が出ると思いますが、
そのテーブルのカラム名は何?
まぁ、ここに保存していませんね。
同じ方法でDBA_TAB_COULMNSとDBA_OBJECTSを調べればわからないこともないでしょう
オブジェクトIDが分かるから、
オブジェクト名称(テーブル名)も分かることになりますね。
DBA_TAB_COULMNSからカラム名称を抽出可能です。
ちょっと難しい部分はBlock番号とファイル番号を調べるところですね、
そしてSYSTEM表領域に属すことはもう分かるので・・・