За администратора на SQL Server е доста лесно да възстанови текста на съхранените процедури, изгледи, функции и тригери, защитени с помощта на WITH ENCRYPTION
. За това са написани много статии и са налични няколко търговски инструмента. Основното описание на общия метод е да:
- Получете криптирания формуляр (A), като използвате специалната администраторска връзка.
- Започнете транзакция.
- Заменете дефиницията на обекта с известен текст (B) с поне същата дължина като оригинала.
- Получете криптираната форма за известния текст (C).
- Превърнете транзакцията назад, за да оставите целевия обект в първоначалното му състояние.
- Получете некриптирания оригинал, като приложите изключителен или към всеки знак:
A XOR (B XOR C)
Всичко това е доста просто, но изглежда малко като магия:не обяснява много за как и защо работи . Тази статия обхваща този аспект за онези от вас, които намират подобни подробности за интересни, и предоставя алтернативен метод за декриптиране, който е по-илюстративен за процеса.
Шифърът на потока
Основният алгоритъм за криптиране, който SQL Server използва за криптиране на модул е потоковият шифър RC4™. Очертанието на процеса на криптиране е:
- Инициализирайте шифъра RC4 с криптографски ключ.
- Генерирайте псевдослучаен поток от байтове.
- Комбинирайте обикновения текст на модула с потока от байтове, като използвате изключителни или.
Можем да видим, че този процес се случва с помощта на дебъгер и публични символи. Например, следата на стека по-долу показва SQL Server, инициализиращ ключа RC4, докато се подготвя да криптира текста на модула:
Следващият показва, че SQL Server криптира текста с помощта на псевдослучаен байтов поток RC4:
Подобно на повечето поточни шифри, процесът на декриптиране е същият като криптирането, като се използва фактът, че изключителният или е обратим (A XOR B XOR B = A
).
Използването на поточен шифър е причината изключително-или се използва в метода, описан в началото на статията. Няма нищо опасно по своята същност в използването на изключителна или, при условие че се използва защитен метод за криптиране, ключът за инициализация се пази в тайна и ключът не се използва повторно.
RC4 не е особено силен, но това не е основният проблем тук. Въпреки това си струва да се отбележи, че криптирането с помощта на RC4 постепенно се премахва от SQL Server и е отхвърлено (или деактивирано, в зависимост от версията и нивото на съвместимост на базата данни) за потребителски операции като създаване на симетричен ключ.
Ключът за инициализация RC4
SQL Server използва три части от информация, за да генерира ключа, използван за инициализиране на шифъра на потока RC4:
- GUID на семейството на базата данни.
Това може да се получи най-лесно чрез заявка за sys.database_recovery_status . Вижда се и в недокументирани команди като
DBCC DBINFO
иDBCC DBTABLE
. - Идентификаторът на обекта на целевия модул.
Това е просто познатият идентификатор на обекта. Имайте предвид, че не всички модули, които позволяват криптиране, са с обхват на схема. Ще трябва да използвате изгледи на метаданни (sys.triggers или sys.server_triggers ), за да получите идентификатора на обекта за DDL и тригери с обхват на сървъра, а не за sys.objects или
OBJECT_ID
, тъй като те работят само с обекти с обхват на схема. - Идентификаторът на подобекта на целевия модул.
Това е номерът на процедурата за номерирани съхранени процедури. Това е 1 за неномерирана съхранена процедура и нула във всички останали случаи.
Използвайки отново инструмента за отстраняване на грешки, можем да видим, че семейният GUID се извлича по време на инициализацията на ключа:
GUID на семейството на базата данни е въведен uniqueidentifier , идентификаторът на обекта е цяло число , а идентификаторът на подобект е smallint .
Всяка част от ключзата да бъдат преобразувани в определен двоичен формат. За GUID на семейството на базата данни, преобразуване на уникалния идентификатор въведете двоичен(16) произвежда правилното двоично представяне. Двата идентификатора трябва да бъдат преобразувани в двоичен в представяне с малък байт (първо най-малко значим байт).
Забележка: Бъдете много внимателни да не предоставите случайно GUID като низ! Трябва да бъде въведен uniqueidentifier .
Кодовият фрагмент по-долу показва правилни операции за преобразуване за някои примерни стойности:
DECLARE @family_guid binary(16) = CONVERT(binary(16), {guid 'B1FC892E-5824-4FD3-AC48-FBCD91D57763'}), @objid binary(4) = CONVERT(binary(4), REVERSE(CONVERT(binary(4), 800266156))), @subobjid binary(2) = CONVERT(binary(2), REVERSE(CONVERT(binary(2), 0)));
Последната стъпка за генериране на ключа за инициализация RC4 е да се свържат трите двоични стойности по-горе в един двоичен файл(22) и да се изчисли SHA-1 хеш на резултата:
DECLARE @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
За примерните данни, дадени по-горе, крайният ключ за инициализация е:
0x6C914908E041A08DD8766A0CFEDC113585D69AF8
Приносът на идентификатора на обекта на целевия модул и идентификатора на подобекта към SHA-1 хеша е трудно да се види в една екранна снимка на програмата за отстраняване на грешки, но заинтересованият читател може да се обърне към разглобяването на част от initspkey по-долу:
call sqllang!A_SHAInit lea rdx,[rsp+40h] lea rcx,[rsp+50h] mov r8d,10h call sqllang!A_SHAUpdate lea rdx,[rsp+24h] lea rcx,[rsp+50h] mov r8d,4 call sqllang!A_SHAUpdate lea rdx,[rsp+20h] lea rcx,[rsp+50h] mov r8d,2 call sqllang!A_SHAUpdate lea rdx,[rsp+0D0h] lea rcx,[rsp+50h] call sqllang!A_SHAFinal lea r8,[rsp+0D0h] mov edx,14h mov rcx,rbx call sqllang!rc4_key (00007fff`89672090)
SHAInit и SHAUupdate повиквания добавят компоненти към SHA хеша, който в крайна сметка се изчислява чрез извикване на SHAFinal .
SHAInit повикването предоставя 10h байта (16 десетични), съхранени в [rsp+40h], което е семейният GUID . Първата SHAUактуализация повикването добавя 4 байта (както е посочено в регистъра r8d), съхранени в [rsp+24h], което е обектът ДОКУМЕНТ ЗА САМОЛИЧНОСТ. Втората SHAUактуализация повикването добавя 2 байта, съхранени в [rsp+20h], което е subobjid .
Последните инструкции предават изчисления SHA-1 хеш към рутинната процедура за инициализация на RC4 ключ rc4_key . Дължината на хеша се съхранява в регистър edx:14h (20 десетични) байта, което е дефинираната дължина на хеша за SHA и SHA-1 (160 бита).
Реализацията на RC4
Основният RC4 алгоритъм е добре познат и сравнително прост. Би било по-добре да се реализира на .Net език от съображения за ефективност и производителност, но по-долу има реализация на T-SQL.
Тези две T-SQL функции имплементират алгоритъма за планиране на ключове RC4 и генератора на псевдослучайни числа и първоначално са написани от MVP на SQL Server Питър Ларсон. Направих някои незначителни модификации, за да подобря малко производителността и да позволя двоични файлове с дължина на LOB да бъдат кодирани и декодирани. Тази част от процеса може да бъде заменена от всяка стандартна реализация на RC4.
/* ** RC4 functions ** Based on http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=76258 ** by Peter Larsson (SwePeso) */ IF OBJECT_ID(N'dbo.fnEncDecRc4', N'FN') IS NOT NULL DROP FUNCTION dbo.fnEncDecRc4; GO IF OBJECT_ID(N'dbo.fnInitRc4', N'TF') IS NOT NULL DROP FUNCTION dbo.fnInitRc4; GO CREATE FUNCTION dbo.fnInitRc4 (@Pwd varbinary(256)) RETURNS @Box table ( i tinyint PRIMARY KEY, v tinyint NOT NULL ) WITH SCHEMABINDING AS BEGIN DECLARE @Key table ( i tinyint PRIMARY KEY, v tinyint NOT NULL ); DECLARE @Index smallint = 0, @PwdLen tinyint = DATALENGTH(@Pwd); WHILE @Index <= 255 BEGIN INSERT @Key (i, v) VALUES (@Index, CONVERT(tinyint, SUBSTRING(@Pwd, @Index % @PwdLen + 1, 1))); INSERT @Box (i, v) VALUES (@Index, @Index); SET @Index += 1; END; DECLARE @t tinyint = NULL, @b smallint = 0; SET @Index = 0; WHILE @Index <= 255 BEGIN SELECT @b = (@b + b.v + k.v) % 256 FROM @Box AS b JOIN @Key AS k ON k.i = b.i WHERE b.i = @Index; SELECT @t = b.v FROM @Box AS b WHERE b.i = @Index; UPDATE b1 SET b1.v = (SELECT b2.v FROM @Box AS b2 WHERE b2.i = @b) FROM @Box AS b1 WHERE b1.i = @Index; UPDATE @Box SET v = @t WHERE i = @b; SET @Index += 1; END; RETURN; END; GO CREATE FUNCTION dbo.fnEncDecRc4 ( @Pwd varbinary(256), @Text varbinary(MAX) ) RETURNS varbinary(MAX) WITH SCHEMABINDING, RETURNS NULL ON NULL INPUT AS BEGIN DECLARE @Box AS table ( i tinyint PRIMARY KEY, v tinyint NOT NULL ); INSERT @Box (i, v) SELECT FIR.i, FIR.v FROM dbo.fnInitRc4(@Pwd) AS FIR; DECLARE @Index integer = 1, @i smallint = 0, @j smallint = 0, @t tinyint = NULL, @k smallint = NULL, @CipherBy tinyint = NULL, @Cipher varbinary(MAX) = 0x; WHILE @Index <= DATALENGTH(@Text) BEGIN SET @i = (@i + 1) % 256; SELECT @j = (@j + b.v) % 256, @t = b.v FROM @Box AS b WHERE b.i = @i; UPDATE b SET b.v = (SELECT w.v FROM @Box AS w WHERE w.i = @j) FROM @Box AS b WHERE b.i = @i; UPDATE @Box SET v = @t WHERE i = @j; SELECT @k = b.v FROM @Box AS b WHERE b.i = @i; SELECT @k = (@k + b.v) % 256 FROM @Box AS b WHERE b.i = @j; SELECT @k = b.v FROM @Box AS b WHERE b.i = @k; SELECT @CipherBy = CONVERT(tinyint, SUBSTRING(@Text, @Index, 1)) ^ @k, @Cipher = @Cipher + CONVERT(binary(1), @CipherBy); SET @Index += 1; END; RETURN @Cipher; END; GO
Шифрованият текст на модула
Най-лесният начин за администратор на SQL Server да получи това е да прочете varbinary(max) стойност, съхранена в imageval колона sys.sysobjvalues , която е достъпна само чрез специалната администраторска връзка (DAC).
Това е същата идея като рутинния метод, описан във въведението, въпреки че добавяме филтър към valclass =1. Тази вътрешна таблица също е удобно място за получаване на subjid . В противен случай ще трябва да проверим sys.numbered_procedures когато целевият обект е процедура, използвайте 1 за неномерирана процедура или нула за нещо друго, както е описано по-горе.
Възможно е даизбегнете използването на DACа като прочетете imageval от sys.sysobjvalues директно, използвайки множество DBCC PAGE
обаждания. Това включва малко повече работа за намиране на страниците от метаданни, следвайте imageval LOB верига и прочетете целевите двоични данни от всяка страница. Последната стъпка е много по-лесна за изпълнение на език за програмиране, различен от T-SQL. Обърнете внимание, че DBCC PAGE
ще работи, въпреки че базовият обект обикновено не се чете от връзка, която не е DAC. Ако страницата не е в паметта, тя ще бъде прочетена от постоянното хранилище както обикновено.
Допълнителните усилия за избягване на изискването за DAC се изплащат, като позволяват на множество потребители да използват процеса на декриптиране едновременно. Ще използвам подхода на DAC в тази статия от съображения за простота и пространство.
Работен пример
Следният код създава тестова криптирана скаларна функция:
CREATE FUNCTION dbo.FS() RETURNS varchar(255) WITH ENCRYPTION, SCHEMABINDING AS BEGIN RETURN ( SELECT 'My code is so awesome is needs to be encrypted!' ); END;
Пълната реализация на декриптиране е по-долу. Единственият параметър, който трябва да се промени, за да работи за други обекти, е първоначалната стойност на @objectid
зададен в първия DECLARE
изявление.
-- *** DAC connection required! *** -- Make sure the target database is the context USE Sandpit; DECLARE -- Note: OBJECT_ID only works for schema-scoped objects @objectid integer = OBJECT_ID(N'dbo.FS', N'FN'), @family_guid binary(16), @objid binary(4), @subobjid binary(2), @imageval varbinary(MAX), @RC4key binary(20); -- Find the database family GUID SELECT @family_guid = CONVERT(binary(16), DRS.family_guid) FROM sys.database_recovery_status AS DRS WHERE DRS.database_id = DB_ID(); -- Convert object ID to little-endian binary(4) SET @objid = CONVERT(binary(4), REVERSE(CONVERT(binary(4), @objectid))); SELECT -- Read the encrypted value @imageval = SOV.imageval, -- Get the subobjid and convert to little-endian binary @subobjid = CONVERT(binary(2), REVERSE(CONVERT(binary(2), SOV.subobjid))) FROM sys.sysobjvalues AS SOV WHERE SOV.[objid] = @objectid AND SOV.valclass = 1; -- Compute the RC4 initialization key SET @RC4key = HASHBYTES('SHA1', @family_guid + @objid + @subobjid); -- Apply the standard RC4 algorithm and -- convert the result back to nvarchar PRINT CONVERT ( nvarchar(MAX), dbo.fnEncDecRc4 ( @RC4key, @imageval ) );
Обърнете внимание на окончателното преобразуване в nvarchar тъй като текстът на модула се въвежда като nvarchar(max) .
Резултатът е:
Заключение
Причините, поради които методът, описан във въведението, работи са:
- SQL сървърът използва шифъра на потока RC4 за обратимо изключителен или изходен текст.
- Ключът RC4 зависи само от ръководството на семейството на базата данни, идентификатора на обекта и subobjid.
- Временната подмяна на текста на модула означава, че е генериран същият (SHA-1 хеширан) ключ RC4.
- Със същия ключ се генерира същият RC4 поток, което позволява ексклузивно или декриптиране.
Потребителите, които нямат достъп до системни таблици, файлове на база данни или друг достъп на ниво администратор, не могат да извличат криптиран текст на модула. Тъй като самият SQL Server трябва да може да декриптира модула, няма начин да се попречи на потребители с подходящи права да правят същото.