NeMiNeM
28.10.2007, 18:15
Автор: David Litchfield [davidl@ngssoftware.com]
Дата: 21 марта 2007
Перевод: NeMiNeM
www.antichat.ru
Это перевод первой статьи из цикла «Анатомия Oracle» Дэвида Литчфильда.
Исследование проводилось на сервере с установленным на Windows Oracle 10g Release 2.
СУРБД Oracle была создана с мыслю о гибкости и часть этого возможна благодаря redo-логам (или журналам операций). Целью использования журнала операций является экстренное восстановление БД в некоторых случаях сбоев системы и потери файлов данных. Восстановив файлы данных из ранее сделанных резервных копий, файлы журнала операций (включая архивные файлы журнала) могут повторить все последние транзакции. Таким образом, файлы данных будут полностью восстановлены. Это также имеет ценность для исследователя, ответственного за анализ вторжения в БД. Все действия взломщика могут остаться в логах. "Могут", потому что это зависит от конфигурации БД и нагрузки. Доказательств может и не быть вообще. В этой статье мы рассмотрим redo-логи и научимся находить доказательства взломов и понимать, как действуют взломщики. В этой статье не используется программа Logminer или дамп-файлы на базе ASCII, которые можно сгенерировать командой ALTER SYSTEM DUMP LOGFILE.
[Обзор работы redo-логов]
События, которые привели к изменению данных в базе, записуются в буфер БД в память SGA (System Global Area – Глобальная Область Системы). Каждые три секунды или когда делается COMMIT, фоновый процесс LGWR переписывает эти данные с буфера в файлы на диске. Эти файлы (два или больше) известны как Online Redo Log. Когда один файл заполняется - сервер переключается на другой файл группы и т.д. Когда все файлы заполнены, сервер возвращается к первому и начинает перезаписывать предыдущие вводы. Чтоб избежать потери нужной информации, в Oracle есть фоновой процесс ARCn, который архивирует логи. Хотя нужно заметить, что не на всех БД Oracle архивация включена. Сервер с включенной опцией работает в режиме ARCHIVELOG и в режиме NONARCHIVELOG если наоборот. Это существенно, поскольку, когда сервер не архивирует данные, следы атаки можно быстро перезаписать. Очевидно, что количество запросов, которые изменяют статус БД прямо зависит от быстроты изменений. Несомненно, взломщик может использоваться этим и мы увидим как чуть позже.
Проверка режима сервера:
1)Проверить значение параметра LOG_ARCHIVE_START в файле параметров запуска сервера
2) Сделать спец. запрос. True - режим архивации, False - наоборот.
SQL> SELECT VALUE FROM V$PARAMETER WHERE NAME = ‘log_archive_start’;
VALUE
--------
TRUE
Исходя из бинарного формата, нет разницы между архивным redo-логом и онлайн файлом redo-логов.
А почему бы просто не использовать ALTER SYSTEM DUMP LOGFILE???
Бинарный redo-лог файл можно сдампить в ASCII, который можно будет просмотреть в любом текст.-редакторе используя команду ALTER SYSTEM DUMP LOGFILE. Можем предположить, что анализ можно сделать только когда будет возможность интерпретировать бинарную версию, но это так. Во-первых, исследователь может не иметь доступ к БД сервера Oraclе, который может использовать для дампа лог-файла; он не может использовать компрометированный сервер, поскольку исполнение команды приведет к дампу ASCII версии в файл отслеживания в директории USER_DUMP_DEST, которая зазначена в файле загрузки или с точки зрения V$PARAMETER. Дампинг таким образом перезапишет большие данные на диске в системе и потенциально удалит доказательства атаки. Таким образом, этого стоит избегать. Плюс, дампинг лог-файла не показывает всю информацию, которая есть в файле. Например, после переключения между лог-файлами, когда файл группы заполнен и процесс LGWR переходит к другому файлу, порядковый номер логов изменяется. Только записи, которые используют данную последовательность логов будут сдампены. Таким образом, более старые номера пропускаются.
Наконец, начиная с Oracle 9.0.1 названия любых DDL команд таких, как CREATE, ALTER, DROP, GRANT
и REVOKE, которые были исполнены, можно найти только в бинарном варианте. "DUMP LOGFILE" не покажет их. Это значит, что необходимые доказательства будут пропущены. К тому же, не достаточно просто посмотреть на ASCII дамп redo-лога; исследователь также должен уметь интерпретировать бинарник. Таким образом, он должен исползовать ALTER SYSTEM на своем собственном сервере, чтоб проанализировать файлы и записи. Это и делает утилита Logminer.
(исследование Пола Врайта - Oracle Database Forensics using LogMiner, Paul Wrigh thttp://www.giac.org/certified_professionals/practicals/gcfa/0159.php)
[Анализ Redo-лога в двоичном формате]
Хэдер лога
Анализируем заголовок онлайн redo-лог файла, который состоит из двоих частей. Первая - тип, размер и количество блоков. Вторая - относится к самой БД. Назовем эти две части - хэдер файла и хэдер redo-лога соответственно.
Хэдер файла
Большинство двоичных данных и лог-файлов в Oracle имеют подобный формат. Онлайн redo-лог не исключение. На диаграмме ниже показаны первые 80 байт файла онлайн redo-логов.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/1.jpg
Тип файла
Чтоб различать разные типы файлов в Oracle, второй байт используется как указатель типа файла. Например, в 2 релизе 10g - файлы данных имеют тип 0xA2, файлы управления - тип 0xC2 и мы можем увидеть с дампа hex, что наш файл есть типа 0x22.
Размер блока
Каждый файл логов, как и другие файлы в Oracle, разбит на блоки. Размер блока может быть найден в 21 байте файла. DBA может установить размер блока для дата-файлов. Размер блока для файлов redo-лога зависит от ОСи. В Windows, Linux и Solaris - 512 байт - 0x0200, в то время как в HP-UX размер блока - 1024. Хэдер файла занимает целый блок как и хэдер redo-лог файла - т.е. два блоки предназначены для информации о логе redo.
Количество блоков
25 байт в хэдере - это количество блоков в файле, не считая блоки, которые использует сам хэдер файла. В логе это число 0x00019000 - или 102400 в десятичной системе. Если прибавить к этому числу 1 (как блок используемый хэдером файла) и потом умножить на размер блока - мы получим целый размер файла:
(102400 + 1) * 512 = 52429312
Проверим:
C:\oracle\product\10.2.0\oradata\orcl>dir REDO01.LOG
Volume in drive C has no label.
Volume Serial Number is 0B3A-E891
Directory of C:\oracle\product\10.2.0\oradata\orcl
13/03/2007 19:08 52,429,312 REDO01.LOG
1 File(s) 52,429,312 bytes
0 Dir(s) 14,513,668,096 bytes free
Magic
-это файл-маркер используемый Oracle'ом для быстрой проверки является ли файл на самом деле файлом Oracle.
Хэдер Блока
Каждый блок имеет свой 16-байтный хэдер, даже когда запись redo переходит границу блока.
Это важно, когда информация извлекается в redo-лог как символьная строка, или любые другие данные для потребности, могут быть разделены хэдером.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/2.jpg
Здесь мы видим пример блок хэдера (желтый цвет), который разделил символьную строку - в этом случаи команду CREATE USER DDL. Каждый хэдер блока начинается с сигнатуры 0x0122, после которой идет номер блока, и потом порядковый номер и офсет (смещение). Последние три бита информации формируют Relative Block Address или RBA записи redo со смещением на начало записи внутри блока. Таким образом, если офсет 0x38, как в нашем случаи, запись redo начнется с 0x01679A00 + 0x38 = 0x01679A38.В конце концов, у нас есть контрольная сумма. И так нам нужно разобраться - как получается контрольная сумма.
Контрольная сумма ]
Для обеспечения целостности данных, каждый блок имеет контрольную сумму. При проверке контр. суммы, по сути каждый блок делится на 64-байтные под-блоки. Первые 16 байтов каждого под-блока XORуются с вторыми 16 байтами, третье с четвертыми. <...> Из этих 16
байт, первые четыре байта XORуются с другими четырьмя байтами, затем третье, затем четвертые. В итоге получается результат в 4 байта - или DWORD. 16 бит высшего порядке должны совпадать с нижними 16битами если контрол. сумма верна. Следующие две функции показывают как это реализировано на С.
int do_checksum(int block_size, unsigned char *buffer)
{
unsigned char block1[16]="";
unsigned char block2[16]="";
unsigned char block3[16]="";
unsigned char block4[16]="";
unsigned char out1[16]="";
unsigned char out2[16]="";
unsigned char res[16]="";
unsigned char nul[16]="";
int count = 0;
unsigned int r0=0,r1=0,r2=0,r3=0,r4=0;
}
while(count < block_size)
{
memmove(block1,&buffer[count],16);
memmove(block2,&buffer[count+16],16);
memmove(block3,&buffer[count+32],16);
memmove(block4,&buffer[count+48],16);
do_16_byte_xor(block1,block2,out1);
do_16_byte_xor(block3,block4,out2);
do_16_byte_xor(nul,out1,res);
memmove(nul,res,16);
do_16_byte_xor(nul,out2,res);
memmove(nul,res,16);
count = count + 64;
}
memmove(&r1,&res[0],4);
memmove(&r2,&res[4],4);
memmove(&r3,&res[8],4);
memmove(&r4,&res[12],4);
r0 = r0 ^ r1;
r0 = r0 ^ r2;
r0 = r0 ^ r3;
r0 = r0 ^ r4;
r1 = r0;
r0 = r0 >> 16;
r0 = r0 ^ r1;
r0 = r0 & 0xFFFF;
return r0;
int do_16_byte_xor(unsigned char *block1, unsigned char *block2, unsigned char *out)
{
int c = 0;
while (c<16)
{
out[c] = block1[c] ^ block2[c];
c ++;
}
return 0;
}
Если 16 бит высшего порядка не равняются 16 битам нижнего значит есть проблема с блоком. Контрол. сумму можно "исправить" используя XOR результата с текущей контрольной суммой.
Например, припустим, что контрольная сумма в блоке 0x4E50, а результат DWORD после исполнения операций ХОR 0x59B109B1. Это значит 0x59B1 для верхнего ряда 16 бит и 0x09B1 для нижнего. XOR даст всумме 0x5000 (Если бы контр. сумма была правильная - выдал бы 0x0000. Итак, чтоб это исправить, мы просто используем XOR таким образом:
0x4E50 ^ 0x5000 = 0x1E50
Если б нам тогда нужно было изменить контр. сумму на эту (0x1E50) - она бы теперь подошла.
Хэдер redo-лога
Хэдер redo-лога намного интереснее хэдера файла и вмещает очень много информации: SID БД, версию БД и время, когда начался лог.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/3.jpg
На картинке мы видим 4 разных Номера Системного Изменения (System Change Numbers - SCN): Low, Next, Enabled и Thread Closed SCNs. Вместе с каждым есть дата. Перед тем, как разобраться, как исчисляется время в логах, давайте быстренько пройдемся по этих SCN. SNC или номер системного изменения - это как маркер, который использует Oracle для индикации состояния системы. Другими словами, если кто-то изменяет состояние БД, например, используя INSERT после COMMIT, Oracle за всем этим следит, используя SCN. Если состояние нужно восстановить - в будущем можно использовать SNC для индикации "версии" состояния БД, которую вы хотите восстановить.
[Как записывается время в Redo-логах]
Каждая запись в логах имеет отметку времени, в секундах, которую исследователь может использовать для понимания последовательности событий. Обратите внимание, что время показывает, когда запись была создана в логе, а не когда произошло изменение в БД, т.е. может быть промежуток между реальным событием и фактической записью на диск. Хотя, операции DDL исполняются сразу и дату в логах можно считать точной. Что касается точности времени записей, назовем момент 1 января 1988 00:00:00 - T-день
. Во-первых, время хранится как 32-ох битное значение - это перекодированная версия времени, начиная с T-дня. Исчисляется так: возьмите текущий год и отнимите 1988. Умножьте на 12. Возьмите текущий месяц, отнимите и добавьте это к результату. Умножьте на 31 и добавьте текущий день минус один. Умножьте на 24 и добавьте текущий час. Умножьте это на 60 и добавьте текущие минуты, умножьте на 60 и добавьте секунды. Итак, если сейчас 2007-03-15 20:05:10 то метка времени будет:
2007 - 1988 = 19
19 * 12 = 228
228 + 3 - 1 = 230
230 * 31 = 7130
7130 + 15 - 1 = 7144
7144 * 24 = 171456
171456 + 20 = 171476
171476 * 60 = 10288560
10288560 + 5 = 10288565
10288565 * 60 = 617313900
617313900 + 10 = 617313910
Метка времени:
Timestamp = 617313910 (0x24CB7676)
Теперь, когда мы знаем, как время исчисляется в логах, мы можем его "декодировать" с помощью этого кода на С
/* Oracle timestamp "decoder" */
#include <stdio.h>
int main(int argc, char *argv[])
{
unsigned int t = 0;
unsigned int seconds = 0;
unsigned int minutes = 0;
unsigned int hours = 0;
unsigned int day = 0;
unsigned int month = 0;
unsigned int year = 0;
if(argc == 1)
return printf("%s timestamp\n",argv[0]);
t = atoi(argv[1]);
seconds = (t % 60);
t = t - seconds;
t = t / 60;
minutes = t % 60;
t = t - minutes;
t = t / 60;
hours = t % 24;
t = t - hours;
t = t / 24;
day = t % 31;
t = t - day;
t = t / 31;
day ++;
month = t % 12;
t = t - month;
month ++;
t = t / 12;
year = t + 1988;
printf("%.2d/%.2d/%.4d %.2d:%.2d:%.2d\n",day,month,year,hours,minutes,seconds);
return 0;
}
Бесполезный факт: если установить системное время на день раньше 1/1/1998 то Oracle 10g не будет правильно работать.
[Содержимое Redo-record]
Запись изменений (redo record) включает в себя все изменения для данного SCN. Запись имеет хэдер и один, или больше векторов изменений (change vectors). Их может быть один или больше для одного события. Например, если пользователь исполняет INSERT в таблицу с индексом, создается несколько векторов изменений. Будет вектор redo и undo для INSERT и потом insert leaf row для index и commit. Каждый вектор имеют свой код операции для легкой идентификации между собой. Ниже представлена таблица показывает наиболее популярные:
5.1 Undo Record
5.4 Commit
11.2 INSERT on single row
11.3 DELETE
11.5 UPDATE single row
11.11 INSERT multiple rows
11.19 UPDATE multiple rows
10.2 INSERT LEAF ROW
10.4 DELETE LEAF ROW
13.1 Allocate space [e.g. after CREATE TABLE]
24.1 DDL
Исследователь должен изучить каждую запись и отличить обычную от записи атаки.
[Операции с DML (Data Manipulation Language, язык обработки данных)]
DML включает операции INSERT, UPDATE и DELETE. Исполнение любой из них приводит к изменению состояния БД и тогда создается запись redo. Если взломщик исполнит любую с этих команд, вы найдете запись об этом в файле лога.
Расследование записи об INSERT
Этот hex-дамп показывает, как будет выглядит запись в логе после исполнения INSERT и сommit сразу после него - ну мы припустим, что не знаем об этом. Посмотрим, как мы разберемся.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/4.jpg
Сначала мы видим запись - размером 0x01A8 байт и VLD - 0x0D. Это говорит нам, что код операции (opcode) для первого вектора изменения можно найти в 0x44 байте в записи - – 0x0B02 – 11.2 - INSERT. 4 байта перед этим - это отметка времени для записи - 0x24CC67F9 или 03/16/2007 13:15:37. Зная, что мы имеем дело с INSERT, с 0x0B02, можем найти ID объекта для объекта, над которым проводился INSERT - это 22 байта перед кодом операции. В дампе на картинке он выделен серым цветом - 0x0057 – или 87. Исследователь должен узнать номера объектов, типы и хозяинов этих объектов в БД. Таким образом, он заметит что объект 87 это таблица SYS под именем SYSAUTH$. Потом, исследователь может найти количество столбцов, проверяя размер массива 24 байта от кода операции, 2 байта от ID обьекта. В нашем случаи 0x0C – 12. Это указывает на то, что INSERT был произведен в три столбца. Почему это так? Во-первых, каждая запись в массиве занимает 2 байта - значит 6 записей - но одна с 6 - это запись самого размера и ещё две 0x0014 и 0x0031. Последние две - напол-изменены, но могут быть 0x44. В дампе мы видим записи в строке 0x001d2870 и голубых прямоугольниках: 0x0002, 0x0002 и 0x0003. Значит, количество байтов данных INSERTованых в первый столбец - два, во второй - тоже два, и в третий - три. Сразу после массива у нас есть "ор" и "ver". В зависимости от того, имеем ли мы чётное или нечётное число записей в массиве, это будет влиять на размещение "op" и "ver":
если чётное - сразу после, а если нечётное - через два байта. В примере, мы видим, что и "ор" и "ver" - одиница. "ор" может быть 1 с 1,2 или 17. В зависимости от значения "ор" - фактическое размещение значений, INSERTованых в бд, будет различаться. В случаи 1 - их можно найти на 72 байте от позиции “op”. Если “op” - 2 - на 64 байте. Если 17 - на 120 байте от “op”. В хекс-дампе, показанном выше, мы видим данные красным цветом - 72 байта после “op” . И снова, с "живых" стадий ответа сервера мы знаем что столбцы 1,2 и 3 с таблицы SYS.SYSAUTH$ - числовые и мы понимаем, что ищем цифровые данные. Нужно объяснить, как Oracle хранит числа. Числа 1-99 считаются как "юниты" и хранятся как 2 байта - первый - индикатор (0xC1), а второй байт это сам номер плюс один. Если номер 1 будет закодирован как 0xC102, то второй будет 0xC103 и т.д. Числа между 100 и 9999 хранятся как 2 или 3 байта. Байт-индикатор повышается к 0xC2, чтоб показать увеличение значения. Таким образом, 100 будет 0xC202, но 101 будет 0xC20202. Здесь мы имеем 1 из "100-ней" и 1 с "юнитов". 201 будет 0xC20302, 301 - 0xc20402 и т.д. Если мы увеличиваем число между 10000 и 999999 - наш индикатор увеличится к 0xC3. 10000 будет хранится как 0xC302, 10001 как 0xC2020102. Здесь 1 с "10000", нет соток и 1 с "юнитов". Для каждых дополнительных двоих нулей в конце- номер индикатора увеличивается на 1. Таким образом, для 1000000-99999999, индикатор 0xC4, для 100000000-9999999999 - индикатор 0xC5 и т.д. Возвращаясь к нашему дампу, мы видим, что данные записанные как ,
0xC105 и 0xC20931. Декодируем эти числа. Получаем 1 для первого, 4 для второго и 848 для третьего [(9-1) * 100 + (49 -1)].
Реконструируя, что случилось мы увидим что было произведено:
SQL> INSERT INTO SYS.SYSAUTH$ (GRANTEE#,PRIVILEGE#, SEQUENCE#) VALUES (1,4,848);
Таблица SYS.SYSAUTH$ следит за тем, кто был назначен мемберством какого типа и с "живого" анализа, исследователь увидит, что “user” 1 (т.е. с столбца GRANTEE#) - PUBLIC и что DBA - 4. SQL была сделана паблик, другими словами, каждый пользователь - DBA.
Дата: 21 марта 2007
Перевод: NeMiNeM
www.antichat.ru
Это перевод первой статьи из цикла «Анатомия Oracle» Дэвида Литчфильда.
Исследование проводилось на сервере с установленным на Windows Oracle 10g Release 2.
СУРБД Oracle была создана с мыслю о гибкости и часть этого возможна благодаря redo-логам (или журналам операций). Целью использования журнала операций является экстренное восстановление БД в некоторых случаях сбоев системы и потери файлов данных. Восстановив файлы данных из ранее сделанных резервных копий, файлы журнала операций (включая архивные файлы журнала) могут повторить все последние транзакции. Таким образом, файлы данных будут полностью восстановлены. Это также имеет ценность для исследователя, ответственного за анализ вторжения в БД. Все действия взломщика могут остаться в логах. "Могут", потому что это зависит от конфигурации БД и нагрузки. Доказательств может и не быть вообще. В этой статье мы рассмотрим redo-логи и научимся находить доказательства взломов и понимать, как действуют взломщики. В этой статье не используется программа Logminer или дамп-файлы на базе ASCII, которые можно сгенерировать командой ALTER SYSTEM DUMP LOGFILE.
[Обзор работы redo-логов]
События, которые привели к изменению данных в базе, записуются в буфер БД в память SGA (System Global Area – Глобальная Область Системы). Каждые три секунды или когда делается COMMIT, фоновый процесс LGWR переписывает эти данные с буфера в файлы на диске. Эти файлы (два или больше) известны как Online Redo Log. Когда один файл заполняется - сервер переключается на другой файл группы и т.д. Когда все файлы заполнены, сервер возвращается к первому и начинает перезаписывать предыдущие вводы. Чтоб избежать потери нужной информации, в Oracle есть фоновой процесс ARCn, который архивирует логи. Хотя нужно заметить, что не на всех БД Oracle архивация включена. Сервер с включенной опцией работает в режиме ARCHIVELOG и в режиме NONARCHIVELOG если наоборот. Это существенно, поскольку, когда сервер не архивирует данные, следы атаки можно быстро перезаписать. Очевидно, что количество запросов, которые изменяют статус БД прямо зависит от быстроты изменений. Несомненно, взломщик может использоваться этим и мы увидим как чуть позже.
Проверка режима сервера:
1)Проверить значение параметра LOG_ARCHIVE_START в файле параметров запуска сервера
2) Сделать спец. запрос. True - режим архивации, False - наоборот.
SQL> SELECT VALUE FROM V$PARAMETER WHERE NAME = ‘log_archive_start’;
VALUE
--------
TRUE
Исходя из бинарного формата, нет разницы между архивным redo-логом и онлайн файлом redo-логов.
А почему бы просто не использовать ALTER SYSTEM DUMP LOGFILE???
Бинарный redo-лог файл можно сдампить в ASCII, который можно будет просмотреть в любом текст.-редакторе используя команду ALTER SYSTEM DUMP LOGFILE. Можем предположить, что анализ можно сделать только когда будет возможность интерпретировать бинарную версию, но это так. Во-первых, исследователь может не иметь доступ к БД сервера Oraclе, который может использовать для дампа лог-файла; он не может использовать компрометированный сервер, поскольку исполнение команды приведет к дампу ASCII версии в файл отслеживания в директории USER_DUMP_DEST, которая зазначена в файле загрузки или с точки зрения V$PARAMETER. Дампинг таким образом перезапишет большие данные на диске в системе и потенциально удалит доказательства атаки. Таким образом, этого стоит избегать. Плюс, дампинг лог-файла не показывает всю информацию, которая есть в файле. Например, после переключения между лог-файлами, когда файл группы заполнен и процесс LGWR переходит к другому файлу, порядковый номер логов изменяется. Только записи, которые используют данную последовательность логов будут сдампены. Таким образом, более старые номера пропускаются.
Наконец, начиная с Oracle 9.0.1 названия любых DDL команд таких, как CREATE, ALTER, DROP, GRANT
и REVOKE, которые были исполнены, можно найти только в бинарном варианте. "DUMP LOGFILE" не покажет их. Это значит, что необходимые доказательства будут пропущены. К тому же, не достаточно просто посмотреть на ASCII дамп redo-лога; исследователь также должен уметь интерпретировать бинарник. Таким образом, он должен исползовать ALTER SYSTEM на своем собственном сервере, чтоб проанализировать файлы и записи. Это и делает утилита Logminer.
(исследование Пола Врайта - Oracle Database Forensics using LogMiner, Paul Wrigh thttp://www.giac.org/certified_professionals/practicals/gcfa/0159.php)
[Анализ Redo-лога в двоичном формате]
Хэдер лога
Анализируем заголовок онлайн redo-лог файла, который состоит из двоих частей. Первая - тип, размер и количество блоков. Вторая - относится к самой БД. Назовем эти две части - хэдер файла и хэдер redo-лога соответственно.
Хэдер файла
Большинство двоичных данных и лог-файлов в Oracle имеют подобный формат. Онлайн redo-лог не исключение. На диаграмме ниже показаны первые 80 байт файла онлайн redo-логов.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/1.jpg
Тип файла
Чтоб различать разные типы файлов в Oracle, второй байт используется как указатель типа файла. Например, в 2 релизе 10g - файлы данных имеют тип 0xA2, файлы управления - тип 0xC2 и мы можем увидеть с дампа hex, что наш файл есть типа 0x22.
Размер блока
Каждый файл логов, как и другие файлы в Oracle, разбит на блоки. Размер блока может быть найден в 21 байте файла. DBA может установить размер блока для дата-файлов. Размер блока для файлов redo-лога зависит от ОСи. В Windows, Linux и Solaris - 512 байт - 0x0200, в то время как в HP-UX размер блока - 1024. Хэдер файла занимает целый блок как и хэдер redo-лог файла - т.е. два блоки предназначены для информации о логе redo.
Количество блоков
25 байт в хэдере - это количество блоков в файле, не считая блоки, которые использует сам хэдер файла. В логе это число 0x00019000 - или 102400 в десятичной системе. Если прибавить к этому числу 1 (как блок используемый хэдером файла) и потом умножить на размер блока - мы получим целый размер файла:
(102400 + 1) * 512 = 52429312
Проверим:
C:\oracle\product\10.2.0\oradata\orcl>dir REDO01.LOG
Volume in drive C has no label.
Volume Serial Number is 0B3A-E891
Directory of C:\oracle\product\10.2.0\oradata\orcl
13/03/2007 19:08 52,429,312 REDO01.LOG
1 File(s) 52,429,312 bytes
0 Dir(s) 14,513,668,096 bytes free
Magic
-это файл-маркер используемый Oracle'ом для быстрой проверки является ли файл на самом деле файлом Oracle.
Хэдер Блока
Каждый блок имеет свой 16-байтный хэдер, даже когда запись redo переходит границу блока.
Это важно, когда информация извлекается в redo-лог как символьная строка, или любые другие данные для потребности, могут быть разделены хэдером.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/2.jpg
Здесь мы видим пример блок хэдера (желтый цвет), который разделил символьную строку - в этом случаи команду CREATE USER DDL. Каждый хэдер блока начинается с сигнатуры 0x0122, после которой идет номер блока, и потом порядковый номер и офсет (смещение). Последние три бита информации формируют Relative Block Address или RBA записи redo со смещением на начало записи внутри блока. Таким образом, если офсет 0x38, как в нашем случаи, запись redo начнется с 0x01679A00 + 0x38 = 0x01679A38.В конце концов, у нас есть контрольная сумма. И так нам нужно разобраться - как получается контрольная сумма.
Контрольная сумма ]
Для обеспечения целостности данных, каждый блок имеет контрольную сумму. При проверке контр. суммы, по сути каждый блок делится на 64-байтные под-блоки. Первые 16 байтов каждого под-блока XORуются с вторыми 16 байтами, третье с четвертыми. <...> Из этих 16
байт, первые четыре байта XORуются с другими четырьмя байтами, затем третье, затем четвертые. В итоге получается результат в 4 байта - или DWORD. 16 бит высшего порядке должны совпадать с нижними 16битами если контрол. сумма верна. Следующие две функции показывают как это реализировано на С.
int do_checksum(int block_size, unsigned char *buffer)
{
unsigned char block1[16]="";
unsigned char block2[16]="";
unsigned char block3[16]="";
unsigned char block4[16]="";
unsigned char out1[16]="";
unsigned char out2[16]="";
unsigned char res[16]="";
unsigned char nul[16]="";
int count = 0;
unsigned int r0=0,r1=0,r2=0,r3=0,r4=0;
}
while(count < block_size)
{
memmove(block1,&buffer[count],16);
memmove(block2,&buffer[count+16],16);
memmove(block3,&buffer[count+32],16);
memmove(block4,&buffer[count+48],16);
do_16_byte_xor(block1,block2,out1);
do_16_byte_xor(block3,block4,out2);
do_16_byte_xor(nul,out1,res);
memmove(nul,res,16);
do_16_byte_xor(nul,out2,res);
memmove(nul,res,16);
count = count + 64;
}
memmove(&r1,&res[0],4);
memmove(&r2,&res[4],4);
memmove(&r3,&res[8],4);
memmove(&r4,&res[12],4);
r0 = r0 ^ r1;
r0 = r0 ^ r2;
r0 = r0 ^ r3;
r0 = r0 ^ r4;
r1 = r0;
r0 = r0 >> 16;
r0 = r0 ^ r1;
r0 = r0 & 0xFFFF;
return r0;
int do_16_byte_xor(unsigned char *block1, unsigned char *block2, unsigned char *out)
{
int c = 0;
while (c<16)
{
out[c] = block1[c] ^ block2[c];
c ++;
}
return 0;
}
Если 16 бит высшего порядка не равняются 16 битам нижнего значит есть проблема с блоком. Контрол. сумму можно "исправить" используя XOR результата с текущей контрольной суммой.
Например, припустим, что контрольная сумма в блоке 0x4E50, а результат DWORD после исполнения операций ХОR 0x59B109B1. Это значит 0x59B1 для верхнего ряда 16 бит и 0x09B1 для нижнего. XOR даст всумме 0x5000 (Если бы контр. сумма была правильная - выдал бы 0x0000. Итак, чтоб это исправить, мы просто используем XOR таким образом:
0x4E50 ^ 0x5000 = 0x1E50
Если б нам тогда нужно было изменить контр. сумму на эту (0x1E50) - она бы теперь подошла.
Хэдер redo-лога
Хэдер redo-лога намного интереснее хэдера файла и вмещает очень много информации: SID БД, версию БД и время, когда начался лог.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/3.jpg
На картинке мы видим 4 разных Номера Системного Изменения (System Change Numbers - SCN): Low, Next, Enabled и Thread Closed SCNs. Вместе с каждым есть дата. Перед тем, как разобраться, как исчисляется время в логах, давайте быстренько пройдемся по этих SCN. SNC или номер системного изменения - это как маркер, который использует Oracle для индикации состояния системы. Другими словами, если кто-то изменяет состояние БД, например, используя INSERT после COMMIT, Oracle за всем этим следит, используя SCN. Если состояние нужно восстановить - в будущем можно использовать SNC для индикации "версии" состояния БД, которую вы хотите восстановить.
[Как записывается время в Redo-логах]
Каждая запись в логах имеет отметку времени, в секундах, которую исследователь может использовать для понимания последовательности событий. Обратите внимание, что время показывает, когда запись была создана в логе, а не когда произошло изменение в БД, т.е. может быть промежуток между реальным событием и фактической записью на диск. Хотя, операции DDL исполняются сразу и дату в логах можно считать точной. Что касается точности времени записей, назовем момент 1 января 1988 00:00:00 - T-день
. Во-первых, время хранится как 32-ох битное значение - это перекодированная версия времени, начиная с T-дня. Исчисляется так: возьмите текущий год и отнимите 1988. Умножьте на 12. Возьмите текущий месяц, отнимите и добавьте это к результату. Умножьте на 31 и добавьте текущий день минус один. Умножьте на 24 и добавьте текущий час. Умножьте это на 60 и добавьте текущие минуты, умножьте на 60 и добавьте секунды. Итак, если сейчас 2007-03-15 20:05:10 то метка времени будет:
2007 - 1988 = 19
19 * 12 = 228
228 + 3 - 1 = 230
230 * 31 = 7130
7130 + 15 - 1 = 7144
7144 * 24 = 171456
171456 + 20 = 171476
171476 * 60 = 10288560
10288560 + 5 = 10288565
10288565 * 60 = 617313900
617313900 + 10 = 617313910
Метка времени:
Timestamp = 617313910 (0x24CB7676)
Теперь, когда мы знаем, как время исчисляется в логах, мы можем его "декодировать" с помощью этого кода на С
/* Oracle timestamp "decoder" */
#include <stdio.h>
int main(int argc, char *argv[])
{
unsigned int t = 0;
unsigned int seconds = 0;
unsigned int minutes = 0;
unsigned int hours = 0;
unsigned int day = 0;
unsigned int month = 0;
unsigned int year = 0;
if(argc == 1)
return printf("%s timestamp\n",argv[0]);
t = atoi(argv[1]);
seconds = (t % 60);
t = t - seconds;
t = t / 60;
minutes = t % 60;
t = t - minutes;
t = t / 60;
hours = t % 24;
t = t - hours;
t = t / 24;
day = t % 31;
t = t - day;
t = t / 31;
day ++;
month = t % 12;
t = t - month;
month ++;
t = t / 12;
year = t + 1988;
printf("%.2d/%.2d/%.4d %.2d:%.2d:%.2d\n",day,month,year,hours,minutes,seconds);
return 0;
}
Бесполезный факт: если установить системное время на день раньше 1/1/1998 то Oracle 10g не будет правильно работать.
[Содержимое Redo-record]
Запись изменений (redo record) включает в себя все изменения для данного SCN. Запись имеет хэдер и один, или больше векторов изменений (change vectors). Их может быть один или больше для одного события. Например, если пользователь исполняет INSERT в таблицу с индексом, создается несколько векторов изменений. Будет вектор redo и undo для INSERT и потом insert leaf row для index и commit. Каждый вектор имеют свой код операции для легкой идентификации между собой. Ниже представлена таблица показывает наиболее популярные:
5.1 Undo Record
5.4 Commit
11.2 INSERT on single row
11.3 DELETE
11.5 UPDATE single row
11.11 INSERT multiple rows
11.19 UPDATE multiple rows
10.2 INSERT LEAF ROW
10.4 DELETE LEAF ROW
13.1 Allocate space [e.g. after CREATE TABLE]
24.1 DDL
Исследователь должен изучить каждую запись и отличить обычную от записи атаки.
[Операции с DML (Data Manipulation Language, язык обработки данных)]
DML включает операции INSERT, UPDATE и DELETE. Исполнение любой из них приводит к изменению состояния БД и тогда создается запись redo. Если взломщик исполнит любую с этих команд, вы найдете запись об этом в файле лога.
Расследование записи об INSERT
Этот hex-дамп показывает, как будет выглядит запись в логе после исполнения INSERT и сommit сразу после него - ну мы припустим, что не знаем об этом. Посмотрим, как мы разберемся.
http://i242.photobucket.com/albums/ff206/articlez/Oracle%20Forensics%201/4.jpg
Сначала мы видим запись - размером 0x01A8 байт и VLD - 0x0D. Это говорит нам, что код операции (opcode) для первого вектора изменения можно найти в 0x44 байте в записи - – 0x0B02 – 11.2 - INSERT. 4 байта перед этим - это отметка времени для записи - 0x24CC67F9 или 03/16/2007 13:15:37. Зная, что мы имеем дело с INSERT, с 0x0B02, можем найти ID объекта для объекта, над которым проводился INSERT - это 22 байта перед кодом операции. В дампе на картинке он выделен серым цветом - 0x0057 – или 87. Исследователь должен узнать номера объектов, типы и хозяинов этих объектов в БД. Таким образом, он заметит что объект 87 это таблица SYS под именем SYSAUTH$. Потом, исследователь может найти количество столбцов, проверяя размер массива 24 байта от кода операции, 2 байта от ID обьекта. В нашем случаи 0x0C – 12. Это указывает на то, что INSERT был произведен в три столбца. Почему это так? Во-первых, каждая запись в массиве занимает 2 байта - значит 6 записей - но одна с 6 - это запись самого размера и ещё две 0x0014 и 0x0031. Последние две - напол-изменены, но могут быть 0x44. В дампе мы видим записи в строке 0x001d2870 и голубых прямоугольниках: 0x0002, 0x0002 и 0x0003. Значит, количество байтов данных INSERTованых в первый столбец - два, во второй - тоже два, и в третий - три. Сразу после массива у нас есть "ор" и "ver". В зависимости от того, имеем ли мы чётное или нечётное число записей в массиве, это будет влиять на размещение "op" и "ver":
если чётное - сразу после, а если нечётное - через два байта. В примере, мы видим, что и "ор" и "ver" - одиница. "ор" может быть 1 с 1,2 или 17. В зависимости от значения "ор" - фактическое размещение значений, INSERTованых в бд, будет различаться. В случаи 1 - их можно найти на 72 байте от позиции “op”. Если “op” - 2 - на 64 байте. Если 17 - на 120 байте от “op”. В хекс-дампе, показанном выше, мы видим данные красным цветом - 72 байта после “op” . И снова, с "живых" стадий ответа сервера мы знаем что столбцы 1,2 и 3 с таблицы SYS.SYSAUTH$ - числовые и мы понимаем, что ищем цифровые данные. Нужно объяснить, как Oracle хранит числа. Числа 1-99 считаются как "юниты" и хранятся как 2 байта - первый - индикатор (0xC1), а второй байт это сам номер плюс один. Если номер 1 будет закодирован как 0xC102, то второй будет 0xC103 и т.д. Числа между 100 и 9999 хранятся как 2 или 3 байта. Байт-индикатор повышается к 0xC2, чтоб показать увеличение значения. Таким образом, 100 будет 0xC202, но 101 будет 0xC20202. Здесь мы имеем 1 из "100-ней" и 1 с "юнитов". 201 будет 0xC20302, 301 - 0xc20402 и т.д. Если мы увеличиваем число между 10000 и 999999 - наш индикатор увеличится к 0xC3. 10000 будет хранится как 0xC302, 10001 как 0xC2020102. Здесь 1 с "10000", нет соток и 1 с "юнитов". Для каждых дополнительных двоих нулей в конце- номер индикатора увеличивается на 1. Таким образом, для 1000000-99999999, индикатор 0xC4, для 100000000-9999999999 - индикатор 0xC5 и т.д. Возвращаясь к нашему дампу, мы видим, что данные записанные как ,
0xC105 и 0xC20931. Декодируем эти числа. Получаем 1 для первого, 4 для второго и 848 для третьего [(9-1) * 100 + (49 -1)].
Реконструируя, что случилось мы увидим что было произведено:
SQL> INSERT INTO SYS.SYSAUTH$ (GRANTEE#,PRIVILEGE#, SEQUENCE#) VALUES (1,4,848);
Таблица SYS.SYSAUTH$ следит за тем, кто был назначен мемберством какого типа и с "живого" анализа, исследователь увидит, что “user” 1 (т.е. с столбца GRANTEE#) - PUBLIC и что DBA - 4. SQL была сделана паблик, другими словами, каждый пользователь - DBA.