Olá pessoal,
Tudo bem com vocês ?
Neste post eu gostaria de demonstrar a vocês Como recuperar o código-fonte de um objeto criptografado (WITH ENCRYPTION) no SQL Server. Quantas vezes eu já vi programadores criptografando objetos no SQL Server na falsa esperança que esse código realmente vai ficar protegido contra alterações e visualizações por parte de outros usuários.
Introdução
Para os que já trabalham na TI há algum tempo, acho que é unanimidade entre eles de que não existe proteção de código-fonte realmente efetiva.. Se você programa em .NET, tem o JetBrains dotPeck. Se você criptografa seus objetos no Oracle Database, existe o site Unwrap It! que quebra essa criptografia ONLINE. No PHP, já utilizei o Zend Guard para criptografar os arquivos fonte e existem ferramentas online que também quebram a criptografia.
No SQL Server isso também não é diferente. Existem inclusive, ferramentas (Redgate SQL Prompt, ApexSQL Complete e várias outras) que fazem isso pra você de forma automática, mostrando o código-fonte de objetos criptografados de forma tão natural e transparente que você nem percebe que o objeto era criptografado.
Embora seja possível quebrar a criptografia de objetos no SQL Server, isso não significa diretamente uma falha de segurança. Para que seja possível quebrar essa criptografia, você precisará utilizar uma conexão DAC, o qual já expliquei como habilitar e utilizar no post Habilitando e utilizando a conexão remota dedicada para administrador (DAC) no SQL Server.
A conexão DAC é uma conexão dedicada para sysadmins (o que limita bastante os usuários que podem realizar esse procedimento) que precisam se conectar em instâncias que estão enfrentando problemas que impedem novas conexões, como por exemplo, limite de usuários simultâneos da instância atingido.
Como a forma de conexão é diferente (inclusive usa uma porta dedicada), essa conexão “especial” fica sempre disponível, mesmo nessa situação. Vale ressaltar que essa conexão só permite 1 usuário conectado por vez, então se alguém já está conectado, outra pessoa não vai conseguir utilizá-la.
Todo esse requisito de utilizar a conexão DAC é simplesmente para que você consiga ler os dados da tabela sys.sysobjvalues. Mesmo que você seja sysadmin, você só conseguirá acessar os dados dessa tabela utilizando a conexão DAC.
Exemplo SEM utilizar a conexão DAC

Msg 208, Level 16, State 1, Line 16
Invalid object name ‘sys.sysobjvalues’.
Exemplo utilizando a conexão DAC

O SQL Server utiliza o algoritmo de criptografia RC4™ stream cipher para criptografar objetos e armazena esses dados na coluna imageval da tabela sys.sysobjvalues. Neste post, vou demonstrar uma algoritmo que permite recuperar essa informação e retornar o código-fonte original do objeto criptografado.
Como listar os objetos criptografados no banco de dados
Para identificar quais objetos do seu banco de dados estão criptografados, separei 4 consultas para atingir esse objetivo:
Utilizando a view sys.sql_modules:
SELECT
B.[name],
B.[type_desc]
FROM
sys.sql_modules A
JOIN sys.objects B ON A.[object_id] = B.[object_id]
WHERE
A.[definition] IS NULL
Utilizando a função OBJECTPROPERTY:
SELECT
[name],
[type_desc]
FROM
sys.objects
WHERE
OBJECTPROPERTY([object_id], 'IsEncrypted') = 1
Utilizando a view sys.syscomments:
SELECT
A.[name],
A.[type_desc]
FROM
sys.objects A
JOIN sys.syscomments B ON A.[object_id] = B.id
WHERE
B.[encrypted] = 1
Utilizando o database INFORMATION_SCHEMA:
SELECT
ROUTINE_NAME,
ROUTINE_TYPE
FROM
INFORMATION_SCHEMA.ROUTINES
WHERE
ROUTINE_DEFINITION IS NULL
Como criar uma SP criptografada
Para este post, vou criar uma SP bem simples e criptografada, e vou demonstrar como obter o código fonte dela.
USE [dirceuresende]
GO
CREATE PROCEDURE dbo.stpTeste_Decrypt
WITH ENCRYPTION
AS
BEGIN
----------------------------- Comentário da SP -----------------------------
PRINT 'dirceuresende'
SELECT 'dirceuresende'
END
Como vocês podem observar abaixo, não é possível visualizar o código-fonte de um objeto criptografado utilizando a view sys.sql_modules, mesmo que você seja membro da role sysadmin.

Nem mesmo utilizando a procedure de sistema sp_helptext é possível obter o código-fonte desse objeto, retornando a mensagem de alerta “The text for object ‘dbo.stpTeste_Decrypt’ is encrypted.”, conforme podemos visualizar abaixo:

Quebrando a criptografia de objetos pelo SSMS
Parar quebrar a criptografia de objetos pelo SQL Server Management Studio (SSMS), inicie uma conexão DAC na instância desejada. Para isso, basta adicionar o prefixo “ADMIN:” antes do nome da sua instância, ficando assim: “ADMIN:servidor\instancia”, conforme a imagem abaixo:

Agora utilize o código abaixo e você já poderá visualizar o código-fonte do seu objeto criptografado.
Código-fonte
USE [dirceuresende]
GO
SET NOCOUNT ON
-- Aqui você tem que alterar para o objeto que você quer descriptografar
DECLARE
@ObjectOwnerOrSchema NVARCHAR(128) = 'dbo',
@ObjectName NVARCHAR(128) = 'stpTeste_Decrypt'
DECLARE
@i INT,
@ObjectDataLength INT,
@ContentOfEncryptedObject NVARCHAR(MAX),
@ContentOfDecryptedObject NVARCHAR(MAX),
@ContentOfFakeObject NVARCHAR(MAX),
@ContentOfFakeEncryptedObject NVARCHAR(MAX),
@ObjectType NVARCHAR(128),
@ObjectID INT
SET @ObjectID = OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']')
IF @ObjectID IS NULL
BEGIN
RAISERROR('The object name or schema provided does not exist in the database', 16, 1)
RETURN
END
IF NOT EXISTS(SELECT TOP 1 * FROM sys.syscomments WHERE id = @ObjectID AND encrypted = 1)
BEGIN
RAISERROR('The object provided exists however it is not encrypted. Aborting.', 16, 1)
RETURN
END
IF OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']', 'PROCEDURE') IS NOT NULL
SET @ObjectType = 'PROCEDURE'
ELSE
IF OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']', 'TRIGGER') IS NOT NULL
SET @ObjectType = 'TRIGGER'
ELSE
IF OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']', 'VIEW') IS NOT NULL
SET @ObjectType = 'VIEW'
ELSE
SET @ObjectType = 'FUNCTION'
SELECT TOP 1 @ContentOfEncryptedObject = imageval
FROM sys.sysobjvalues
WHERE [objid] = OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']')
AND valclass = 1 and subobjid = 1
SET @ObjectDataLength = DATALENGTH(@ContentOfEncryptedObject)/2
SET @ContentOfFakeObject = N'ALTER ' + @ObjectType + N' [' + @ObjectOwnerOrSchema + N'].[' + @ObjectName + N'] WITH ENCRYPTION AS'
WHILE DATALENGTH(@ContentOfFakeObject)/2 < @ObjectDataLength
BEGIN
IF DATALENGTH(@ContentOfFakeObject)/2 + 4000 < @ObjectDataLength
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', 4000)
ELSE
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', @ObjectDataLength - (DATALENGTH(@ContentOfFakeObject)/2))
END
SET XACT_ABORT OFF
BEGIN TRAN
EXEC(@ContentOfFakeObject)
IF @@ERROR <> 0
ROLLBACK TRAN
SELECT TOP 1 @ContentOfFakeEncryptedObject = imageval
FROM sys.sysobjvalues
WHERE [objid] = OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']')
AND valclass = 1 and subobjid = 1
IF @@TRANCOUNT > 0
ROLLBACK TRAN
SET @ContentOfFakeObject = N'CREATE ' + @ObjectType + N' [' + @ObjectOwnerOrSchema + N'].[' + @ObjectName + N'] WITH ENCRYPTION AS'
WHILE DATALENGTH(@ContentOfFakeObject)/2 < @ObjectDataLength
BEGIN
IF DATALENGTH(@ContentOfFakeObject)/2 + 4000 < @ObjectDataLength
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', 4000)
ELSE
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', @ObjectDataLength - (DATALENGTH(@ContentOfFakeObject)/2))
END
SET @i = 1
SET @ContentOfDecryptedObject = N''
WHILE DATALENGTH(@ContentOfDecryptedObject)/2 < @ObjectDataLength
BEGIN
IF DATALENGTH(@ContentOfDecryptedObject)/2 + 4000 < @ObjectDataLength
SET @ContentOfDecryptedObject = @ContentOfDecryptedObject + REPLICATE(N'A', 4000)
ELSE
SET @ContentOfDecryptedObject = @ContentOfDecryptedObject + REPLICATE(N'A', @ObjectDataLength - (DATALENGTH(@ContentOfDecryptedObject)/2))
END
WHILE (@i <= @ObjectDataLength)
BEGIN
SET @ContentOfDecryptedObject = STUFF(@ContentOfDecryptedObject, @i, 1,
NCHAR(
UNICODE(SUBSTRING(@ContentOfEncryptedObject, @i, 1)) ^
(
UNICODE(SUBSTRING(@ContentOfFakeObject, @i, 1)) ^
UNICODE(SUBSTRING(@ContentOfFakeEncryptedObject, @i, 1))
)))
SET @i = @i + 1
END
SET @i = 0
WHILE DATALENGTH(@ContentOfDecryptedObject)/2 > (@i + 1)*2000
BEGIN
PRINT(SUBSTRING(@ContentOfDecryptedObject, 1 + 2000*@i, 2000*(@i + 1)))
SET @i = @i + 1
END
PRINT(SUBSTRING(@ContentOfDecryptedObject, 1 + 2000*@i, 2000*(@i + 1)))
Quebrando a criptografia de objetos pelo CLR (C#)
Agora que te expliquei como quebrar os objetos, vou te mostrar uma solução mais prática e funcional no dia a dia, e que você pode permitir que até mesmo usuários que não sejam sysadmin consigam visualizar o fonte de objetos criptografados, e sem precisar que você abra uma nova conexão DAC, podendo ser até mesmo de outro servidor ou instância.
Para isso, vou utilizar o CLR para realizar a conexão via DAC, utilizando um usuário sysadmin. Com isso, mesmo que a pessoa não seja sysadmin, basta que ela tenha permissão nessa função, ela poderá visualizar o fonte de objetos não criptografados. Para quebrar o código, vou utilizar o mesmo código mostrado acima, apenas executando ele pelo CLR (C#) em uma conexão DAC, transparente para o usuário que está utilizando a função.
Código-fonte
using System.Data;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using Microsoft.SqlServer.Server;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction(
DataAccess = DataAccessKind.Read,
SystemDataAccess = SystemDataAccessKind.Read
)]
public static SqlString fncDescriptografa_Objeto(SqlString Ds_Servidor, SqlString Ds_Database, SqlString Ds_Schema, SqlString Ds_Objeto)
{
var conexaoLocal => "data source=LOCALHOST;initial catalog=CLR;Application Name=SQLCLR;persist security info=False;Enlist=False;packet size=4096;user id='usuario';password='senha'";
var servidor = conexaoLocal.Replace("LOCALHOST", $"ADMIN:{Ds_Servidor.Value}");
using (var con = new SqlConnection(servidor))
{
con.Open();
using (var cmd = new SqlCommand($@"
USE [{Ds_Database.Value}];
DECLARE
@ObjectOwnerOrSchema NVARCHAR(128) = '{Ds_Schema}',
@ObjectName NVARCHAR(128) = '{Ds_Objeto}'
DECLARE
@i INT,
@ObjectDataLength INT,
@ContentOfEncryptedObject NVARCHAR(MAX),
@ContentOfDecryptedObject NVARCHAR(MAX),
@ContentOfFakeObject NVARCHAR(MAX),
@ContentOfFakeEncryptedObject NVARCHAR(MAX),
@ObjectType NVARCHAR(128),
@ObjectID INT
SET NOCOUNT ON
SET @ObjectID = OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']')
IF (@ObjectID IS NULL)
BEGIN
RAISERROR('The object name or schema provided does not exist in the database', 16, 1)
RETURN
END
IF NOT EXISTS(SELECT TOP 1 * FROM syscomments WHERE id = @ObjectID AND encrypted = 1)
BEGIN
RAISERROR('The object provided exists however it is not encrypted. Aborting.', 16, 1)
RETURN
END
IF OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']', 'PROCEDURE') IS NOT NULL
SET @ObjectType = 'PROCEDURE'
ELSE
IF OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']', 'TRIGGER') IS NOT NULL
SET @ObjectType = 'TRIGGER'
ELSE
IF OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']', 'VIEW') IS NOT NULL
SET @ObjectType = 'VIEW'
ELSE
SET @ObjectType = 'FUNCTION'
SELECT TOP 1 @ContentOfEncryptedObject = imageval
FROM sys.sysobjvalues
WHERE objid = OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']')
AND valclass = 1 and subobjid = 1
SET @ObjectDataLength = DATALENGTH(@ContentOfEncryptedObject)/2
SET @ContentOfFakeObject = N'ALTER ' + @ObjectType + N' [' + @ObjectOwnerOrSchema + N'].[' + @ObjectName + N'] WITH ENCRYPTION AS'
WHILE DATALENGTH(@ContentOfFakeObject)/2 < @ObjectDataLength
BEGIN
IF DATALENGTH(@ContentOfFakeObject)/2 + 4000 < @ObjectDataLength
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', 4000)
ELSE
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', @ObjectDataLength - (DATALENGTH(@ContentOfFakeObject)/2))
END
SET XACT_ABORT OFF
BEGIN TRAN
EXEC(@ContentOfFakeObject)
IF @@ERROR <> 0
ROLLBACK TRAN
SELECT TOP 1 @ContentOfFakeEncryptedObject = imageval
FROM sys.sysobjvalues
WHERE objid = OBJECT_ID('[' + @ObjectOwnerOrSchema + '].[' + @ObjectName + ']')
AND valclass = 1 and subobjid = 1
IF @@TRANCOUNT > 0
ROLLBACK TRAN
SET @ContentOfFakeObject = N'CREATE ' + @ObjectType + N' [' + @ObjectOwnerOrSchema + N'].[' + @ObjectName + N'] WITH ENCRYPTION AS'
WHILE DATALENGTH(@ContentOfFakeObject) / 2 < @ObjectDataLength
BEGIN
IF DATALENGTH(@ContentOfFakeObject) / 2 + 4000 < @ObjectDataLength
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', 4000)
ELSE
SET @ContentOfFakeObject = @ContentOfFakeObject + REPLICATE(N'-', @ObjectDataLength - (DATALENGTH(@ContentOfFakeObject) / 2))
END
SET @i = 1
SET @ContentOfDecryptedObject = N''
WHILE DATALENGTH(@ContentOfDecryptedObject) / 2 < @ObjectDataLength
BEGIN
IF DATALENGTH(@ContentOfDecryptedObject) / 2 + 4000 < @ObjectDataLength
SET @ContentOfDecryptedObject = @ContentOfDecryptedObject + REPLICATE(N'A', 4000)
ELSE
SET @ContentOfDecryptedObject = @ContentOfDecryptedObject + REPLICATE(N'A', @ObjectDataLength - (DATALENGTH(@ContentOfDecryptedObject) / 2))
END
WHILE (@i <= @ObjectDataLength)
BEGIN
SET @ContentOfDecryptedObject = STUFF(@ContentOfDecryptedObject, @i, 1,
NCHAR(
UNICODE(SUBSTRING(@ContentOfEncryptedObject, @i, 1)) ^
(
UNICODE(SUBSTRING(@ContentOfFakeObject, @i, 1)) ^ UNICODE(SUBSTRING(@ContentOfFakeEncryptedObject, @i, 1))
))
)
SET @i = @i + 1
END
SELECT @ContentOfDecryptedObject", con) { CommandType = CommandType.Text })
{
var resultado = (cmd.ExecuteScalar() != null) ? (string) cmd.ExecuteScalar() : "";
return resultado;
}
}
}
}
Após criar a função no seu projeto CLR e publicar a alteração, vamos utilizá-la:

Vale lembrar que nessa solução, o usuário que for utilizar a função não precisa (e nem deve) estar utilizando uma conexão DAC.
Referências:
https://sqlperformance.com/2016/05/sql-performance/the-internals-of-with-encryption
https://www.codeproject.com/Articles/5068/RC-Encryption-Algorithm-C-Version
https://sqljunkieshare.com/2012/03/07/decrypting-encrypted-stored-procedures-views-functions-in-sql-server-20052008-r2/
https://pt.linkedin.com/pulse/uma-procedure-criptografada-e-agora-f%C3%A1bio-oliveira
É isso aí, pessoal!
Espero que tenham gostado desse post e até a próxima.
SQL Server how to retrieve view get decrypt stored procedure source code encrypted objects WITH ENCRYPTION
SQL Server how to retrieve view get decrypt stored procedure source code encrypted objects WITH ENCRYPTION

Comentários (0)
Carregando comentários…