WITH ENCRYPTION 只是將 sys.sysobjvalues.imageval 中的原始碼以簡單 XOR 混淆。解密步驟:
imageval (真實加密碼)。WITH ENCRYPTION),取得其 imageval (假加密碼)。imageval (假明文)。真明文 = 真加密碼 XOR 假加密碼 XOR 假明文。ADMIN:InstanceName (如 ADMIN:ACCT\PAYABLE) → sysadmin 登入。sqlcmd -A -S InstanceName -o decrypted.sql 輸出到檔案。sp_configure 'remote admin connections', 1; RECONFIGURE;CREATE PROCEDURE [dbo].[sp_undecrypt]
@procedure SYSNAME = NULL,
@revfl INT = 1 -- 1=顯示警告, 0=實際執行解密
AS
BEGIN
SET NOCOUNT ON;
IF @revfl = 1
BEGIN
PRINT N'警告:此程序會刪除並重建原始加密物件。';
PRINT N'請確保已備份資料庫,建議在非生產環境執行。';
PRINT N'若確定執行,請呼叫 EXEC sp_undecrypt @procedure, 0;';
RETURN 0;
END
DECLARE @maxColID SMALLINT, @procNameLen INT, @objType CHAR(2), @parentName NVARCHAR(MAX);
SELECT @maxColID = MAX(subobjid) FROM sys.sysobjvalues WHERE objid = OBJECT_ID(@procedure);
SELECT @procNameLen = DATALENGTH(@procedure) + 29;
SELECT @objType = type, @parentName = OBJECT_NAME(parent_object_id)
FROM sys.objects WHERE object_id = OBJECT_ID(@procedure);
-- 1. 取得真實加密碼
DECLARE @real_enc NVARCHAR(MAX) = (SELECT TOP 1 imageval FROM sys.sysobjvalues
WHERE objid = OBJECT_ID(@procedure) AND valclass = 1 ORDER BY subobjid);
-- 2. 建立假明文物件 (無加密)
DECLARE @fake_plain NVARCHAR(MAX);
IF @objType = 'P' SET @fake_plain = N'CREATE PROCEDURE ' + @procedure + N' AS SELECT 1 /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/';
ELSE IF @objType = 'FN' SET @fake_plain = N'CREATE FUNCTION ' + @procedure + N'() RETURNS INT AS BEGIN RETURN 1 /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/ END';
ELSE IF @objType = 'V' SET @fake_plain = N'CREATE VIEW ' + @procedure + N' AS SELECT 1 AS col /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/';
ELSE IF @objType = 'TR' SET @fake_plain = N'CREATE TRIGGER ' + @procedure + N' ON ' + @parentName + N' AFTER INSERT AS RAISERROR(''N'',16,10) /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/';
EXEC (@fake_plain);
-- 3. 取得假明文加密後的 imageval
DECLARE @fake_enc NVARCHAR(MAX) = (SELECT TOP 1 imageval FROM sys.sysobjvalues
WHERE objid = OBJECT_ID(@procedure) AND valclass = 1 ORDER BY subobjid);
-- 4. 建立假加密物件 (同樣 WITH ENCRYPTION)
DECLARE @fake_enc_obj NVARCHAR(MAX);
IF @objType = 'P' SET @fake_enc_obj = N'CREATE PROCEDURE ' + @procedure + N' WITH ENCRYPTION AS SELECT 1 /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/';
ELSE IF @objType = 'FN' SET @fake_enc_obj = N'CREATE FUNCTION ' + @procedure + N'() RETURNS INT WITH ENCRYPTION AS BEGIN RETURN 1 /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/ END';
ELSE IF @objType = 'V' SET @fake_enc_obj = N'CREATE VIEW ' + @procedure + N' WITH ENCRYPTION AS SELECT 1 AS col /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/';
ELSE IF @objType = 'TR' SET @fake_enc_obj = N'CREATE TRIGGER ' + @procedure + N' ON ' + @parentName + N' WITH ENCRYPTION AFTER INSERT AS RAISERROR(''N'',16,10) /*' + REPLICATE(N'*', DATALENGTH(@real_enc)/2 - @procNameLen) + N'*/';
EXEC (@fake_enc_obj);
-- 5. 取得假加密物件的 imageval
DECLARE @fake_enc2 NVARCHAR(MAX) = (SELECT TOP 1 imageval FROM sys.sysobjvalues
WHERE objid = OBJECT_ID(@procedure) AND valclass = 1 ORDER BY subobjid);
-- 6. XOR 還原真明文 (逐字元)
DECLARE @real_plain NVARCHAR(MAX) = REPLICATE(N'A', DATALENGTH(@real_enc)/2);
DECLARE @i INT = 1;
WHILE @i <= DATALENGTH(@real_enc)/2
BEGIN
SET @real_plain = STUFF(@real_plain, @i, 1,
NCHAR(UNICODE(SUBSTRING(@real_enc, @i, 1)) ^
UNICODE(SUBSTRING(@fake_plain, @i, 1)) ^
UNICODE(SUBSTRING(@fake_enc2, @i, 1))));
SET @i += 1;
END
-- 7. 輸出格式化 (模擬 sp_helptext 換行邏輯)
CREATE TABLE #out(line NVARCHAR(MAX));
INSERT #out SELECT @real_plain;
-- 這裡簡化直接輸出,完整版需按 255 字元換行
SELECT @real_plain AS DecryptedCode;
-- 8. 回滾交易,還原原物件
ROLLBACK TRAN;
DROP TABLE #out;
END;
GO
-- DAC 連線下執行
EXEC sp_undecrypt 'dbo.YourEncryptedProc', 0;
-- 或 sqlcmd 輸出檔案
sqlcmd -A -S . -Q "EXEC sp_undecrypt 'dbo.YourEncryptedProc', 0" -o decrypted.sql
WITH ENCRYPTION (非 SQL 2017+ 的 ENCRYPTION 選項或 Always Encrypted)。