ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする

ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする

背景

たまたまサーバーがランサムウェア ウイルスによって攻撃され、「幸運な偶然」により、.ibd ファイル (innodb テーブルの生データ ファイル) の一部がそのまま残されましたが、同時に .fpm ファイルが完全に暗号化されました (構造ファイル)。 この場合、.idb は次のように分割できます。

  • 標準のツールとガイドを使用して修復することができます。 このような場合には、優れたものがあります なる;
  • 部分的に暗号化されたテーブル。 ほとんどの場合、これらは大きなテーブルであり、(私が理解しているように) 攻撃者には完全な暗号化に十分な RAM がありませんでした。
  • 完全に暗号化されたテーブルは復元できません。

テーブルがどのオプションに属しているかを判断するには、目的のエンコード (私の場合は UTF8) で任意のテキスト エディターで開き、ファイルにテキスト フィールドが存在するかどうかを確認するだけです。次に例を示します。

ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする

また、ファイルの先頭には多数の 0 バイトが見られ、ブロック暗号化アルゴリズム (最も一般的) を使用するウイルスも通常は影響を及ぼします。
ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする

私の場合、攻撃者は暗号化された各ファイルの末尾に 4 バイトの文字列 (1、0、0、0) を残したので、作業が簡素化されました。 感染していないファイルを検索するには、次のスクリプトで十分でした。

def opened(path):
    files = os.listdir(path)
    for f in files:
        if os.path.isfile(path + f):
            yield path + f

for full_path in opened("C:somepath"):
    file = open(full_path, "rb")
    last_string = ""
    for line in file:
        last_string = line
        file.close()
    if (last_string[len(last_string) -4:len(last_string)]) != (1, 0, 0, 0):
        print(full_path)

したがって、最初のタイプに属するファイルが見つかることがわかりました。 XNUMX 番目の作業には多くの手作業が含まれますが、見つかったものですでに十分でした。 すべてうまくいきますが、知っておく必要があります 極めて精密な構造 そして (もちろん) 頻繁に変更されるテーブルを操作しなければならないケースが発生しました。 フィールドタイプが変更されたのか、新しい列が追加されたのかを誰も覚えていませんでした。

残念ながら、Wilds City はそのようなケースに協力することができなかったため、この記事が書かれています。

要点をつかむ

3 か月前のテーブルの構造が現在のテーブルと一致しません (おそらく XNUMX つのフィールド、場合によってはそれ以上のフィールド)。 テーブル構造:

CREATE TABLE `table_1` (
    `id` INT (11),
    `date` DATETIME ,
    `description` TEXT ,
    `id_point` INT (11),
    `id_user` INT (11),
    `date_start` DATETIME ,
    `date_finish` DATETIME ,
    `photo` INT (1),
    `id_client` INT (11),
    `status` INT (1),
    `lead__time` TIME ,
    `sendstatus` TINYINT (4)
); 

この場合、以下を抽出する必要があります。

  • id_point int(11);
  • id_user int(11);
  • date_start 日付時刻;
  • date_finish 日付時刻。

回復には、.ibd ファイルのバイト単位の分析が使用され、その後、ファイルがより読みやすい形式に変換されます。 必要なものを見つけるには、int や datatime などのデータ型を分析するだけでよいため、この記事ではそれらについてのみ説明しますが、他の同様のインシデントで役立つ可能性がある他のデータ型も参照する場合があります。

1の問題: DATETIME 型と TEXT 型のフィールドには NULL 値があり、ファイル内で単純にスキップされます。このため、私の場合は復元する構造を決定できませんでした。 新しい列では、デフォルト値が null であり、innodb_flush_log_at_trx_commit = 0 の設定によりトランザクションの一部が失われる可能性があるため、構造を決定するために追加の時間を費やす必要があります。

2の問題: DELETE で削除された行はすべて ibd ファイル内にありますが、ALTER TABLE ではその構造が更新されないことを考慮する必要があります。 その結果、データ構造はファイルの先頭から最後まで変化する可能性があります。 OPTIMIZE TABLE を頻繁に使用する場合は、このような問題が発生する可能性は低くなります。

注意、DBMS のバージョンはデータの保存方法に影響するため、この例は他のメジャー バージョンでは機能しない可能性があります。 私の場合は、Windows バージョンの mariadb 10.1.24 を使用しました。 また、mariadb では InnoDB テーブルを操作しますが、実際には、 XtraDBこれにより、InnoDB mysql でのこのメソッドの適用性が除外されます。

ファイル分析

Pythonではデータ型 バイト() 通常の数値セットの代わりに Unicode データを表示します。 この形式でファイルを表示できますが、便宜上、バイト配列を通常の配列 (list(example_byte_array)) に変換することで、バイトを数値形式に変換できます。 いずれの場合も、どちらの方法も分析に適しています。

いくつかの ibd ファイルを確認すると、次のものが見つかります。

ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする

さらに、これらのキーワードでファイルを分割すると、ほぼ均等なデータ ブロックが得られます。 infimum を除数として使用します。

table = table.split("infimum".encode())

興味深い観察です。データ量が少ないテーブルの場合、下限と上限の間には、ブロック内の行数へのポインタが存在します。

ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする — 1行目のテストテーブル

ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする - 2行のテストテーブル

行配列 table[0] はスキップできます。 調べてみても、生のテーブルデータを見つけることができませんでした。 おそらく、このブロックはインデックスとキーを格納するために使用されます。
table[1] から始めてそれを数値配列に変換すると、すでにいくつかのパターンに気づくことができます。

ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする

これらは文字列に格納された int 値です。 最初のバイトは、数値が正か負かを示します。 私の場合、すべての数値は正です。 残りの 3 バイトから、次の関数を使用して数値を決定できます。 脚本:

def find_int(val: str):  # example '128, 1, 2, 3'
    val = [int(v) for v in  val.split(", ")]
    result_int = val[1]*256**2 + val[2]*256*1 + val[3]
    return result_int

たとえば、 128、0、0、1 = 1または 128、0、75、108 = 19308.
テーブルには自動インクリメントを備えた主キーがあり、ここでも見つけることができます。

ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする

テスト テーブルのデータを比較した結果、DATETIME オブジェクトは 5 バイトで構成され、153 で始まることが明らかになりました (おそらく年間隔を示しています)。 DATTIMEの範囲が「1000-01-01」から「9999-12-31」なのでバイト数は変わると思いますが、私の場合は2016年から2019年のデータなので、 5バイトあれば十分です。

秒を除いた時間を決定するために、次の関数が作成されました。 脚本:

day_ = lambda x: x % 64 // 2  # {x,x,X,x,x }

def hour_(x1, x2):  # {x,x,X1,X2,x}
    if x1 % 2 == 0:
        return x2 // 16
    elif x1 % 2 == 1:
        return x2 // 16 + 16
    else:
        raise ValueError

min_ = lambda x1, x2: (x1 % 16) * 4 + (x2 // 64)  # {x,x,x,X1,X2}

年と月の関数を書くことができなかったので、ハックする必要がありました。 脚本:

ym_list = {'2016, 1': '153, 152, 64', '2016, 2': '153, 152, 128', 
           '2016, 3': '153, 152, 192', '2016, 4': '153, 153, 0',
           '2016, 5': '153, 153, 64', '2016, 6': '153, 153, 128', 
           '2016, 7': '153, 153, 192', '2016, 8': '153, 154, 0', 
           '2016, 9': '153, 154, 64', '2016, 10': '153, 154, 128', 
           '2016, 11': '153, 154, 192', '2016, 12': '153, 155, 0',
           '2017, 1': '153, 155, 128', '2017, 2': '153, 155, 192', 
           '2017, 3': '153, 156, 0', '2017, 4': '153, 156, 64',
           '2017, 5': '153, 156, 128', '2017, 6': '153, 156, 192',
           '2017, 7': '153, 157, 0', '2017, 8': '153, 157, 64',
           '2017, 9': '153, 157, 128', '2017, 10': '153, 157, 192', 
           '2017, 11': '153, 158, 0', '2017, 12': '153, 158, 64', 
           '2018, 1': '153, 158, 192', '2018, 2': '153, 159, 0',
           '2018, 3': '153, 159, 64', '2018, 4': '153, 159, 128', 
           '2018, 5': '153, 159, 192', '2018, 6': '153, 160, 0',
           '2018, 7': '153, 160, 64', '2018, 8': '153, 160, 128',
           '2018, 9': '153, 160, 192', '2018, 10': '153, 161, 0', 
           '2018, 11': '153, 161, 64', '2018, 12': '153, 161, 128',
           '2019, 1': '153, 162, 0', '2019, 2': '153, 162, 64', 
           '2019, 3': '153, 162, 128', '2019, 4': '153, 162, 192', 
           '2019, 5': '153, 163, 0', '2019, 6': '153, 163, 64',
           '2019, 7': '153, 163, 128', '2019, 8': '153, 163, 192',
           '2019, 9': '153, 164, 0', '2019, 10': '153, 164, 64', 
           '2019, 11': '153, 164, 128', '2019, 12': '153, 164, 192',
           '2020, 1': '153, 165, 64', '2020, 2': '153, 165, 128',
           '2020, 3': '153, 165, 192','2020, 4': '153, 166, 0', 
           '2020, 5': '153, 166, 64', '2020, 6': '153, 1, 128',
           '2020, 7': '153, 166, 192', '2020, 8': '153, 167, 0', 
           '2020, 9': '153, 167, 64','2020, 10': '153, 167, 128',
           '2020, 11': '153, 167, 192', '2020, 12': '153, 168, 0'}

def year_month(x1, x2):  # {x,X,X,x,x }

    for key, value in ym_list.items():
        key = [int(k) for k in key.replace("'", "").split(", ")]
        value = [int(v) for v in value.split(", ")]
        if x1 == value[1] and x2 // 64 == value[2] // 64:
            return key
    return 0, 0

n 時間をかければ、この誤解はきっと解けると思います。
次に、文字列から日時オブジェクトを返す関数です。 脚本:

def find_data_time(val:str):
    val = [int(v) for v in val.split(", ")]
    day = day_(val[2])
    hour = hour_(val[2], val[3])
    minutes = min_(val[3], val[4])
    year, month = year_month(val[1], val[2])
    return datetime(year, month, day, hour, minutes)

int、int、datetime、datetimeから頻繁に繰り返される値を検出することができました ibd ファイルのバイト単位の分析を使用して、構造ファイルを使用せずに XtraDB テーブルからデータをリカバリする、これが必要なもののようです。 さらに、このようなシーケンスは XNUMX 行につき XNUMX 回繰り返されません。

正規表現を使用して、必要なデータを見つけます。

fined = re.findall(r'128, d*, d*, d*, 128, d*, d*, d*, 153, 1[6,5,4,3]d, d*, d*, d*, 153, 1[6,5,4,3]d, d*, d*, d*', int_array)

この式を使用して検索する場合、必須フィールドの NULL 値を決定することはできないことに注意してください。ただし、私の場合、これは重要ではありません。 次に、ループ内で見つかった内容を調べます。 脚本:

result = []
for val in fined:
    pre_result = []
    bd_int  = re.findall(r"128, d*, d*, d*", val)
    bd_date= re.findall(r"(153, 1[6,5,4,3]d, d*, d*, d*)", val)
    for it in bd_int:
        pre_result.append(find_int(bd_int[it]))
    for bd in bd_date:
        pre_result.append(find_data_time(bd))
    result.append(pre_result)

実際には、結果配列のデータが必要なデータです。 ###追記.###
この方法がすべての人に適しているわけではないことは理解していますが、この記事の主な目的は、すべての問題を解決することではなく、行動を促すことです。 最も正しい解決策は、自分でソースコードを勉強し始めることだと思います マリアブ, しかし、時間が限られているため、現在の方法が最も早いようです。

場合によっては、ファイルを分析した後、おおよその構造を特定し、上記のリンクからの標準的な方法のいずれかを使用してファイルを復元できることがあります。 これははるかに正確であり、問​​題の発生も少なくなります。

出所: habr.com

コメントを追加します