Ultimamente tenho tido certa necessidade de “brincar” com os DCU’s de alguns componentes comerciais. “Brincar” aí significa disassemblá-los mesmo, e se possível alterar diretamente o DCU.
Isto pode ser necessário, por exemplo, quando você não possui o código fonte de um componente descontinuado pelo seu desenvolvedor, ou então quando é necessário ter certo nível de “engenharia reversa”.
Inevitavelmente me vem à cabeça uma outra possibilidade: quebrar algum mecanismo de proteção contra execução de componentes shareware, o que obviamente é ilegal e não recomendável. Um mecanismo comum de desenvolvedores de componentes shareware Delphi é checar se o IDE do Delphi está sendo executado. Se estiver, o componente executa normalmente. Caso contrário, a execução é abortada ou alguma nag screen é exibida.
A forma mais infantil de criar este mecanismo de proteção é criar uma função única de checagem e/ou verificação que retorna algum tipo de resultado, geralmente booleano. Quem desenvolve componentes em versões trial nunca deveria fazer isto a não ser que queria que seus DCU’s sejam quebrados até por crackers amadores.
Recentemente disassemblei um componente e encontrei um código deste tipo:
function CheckProtection: boolean;
var
Reg: TRegistry;
begin
result := False;
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.OpenKey('My Software Key', False);
if Reg.ValueExists('RegistryKey') then
Result := True;
finally
Reg.Free;
end;
end;
procedure TMyQuery.InternalPost;
begin
if not CheckProtection then
begin
ShowMessage(‘Versão Demo. Bla Bla Bla’);
Abort;
end;
// o código normal do componente “fucional” continua
inherited InternalPost;
end;
O código não é exatamente assim, claro. É só uma reconstrução resumida do que faz o componente, deduzido a partir da dissecação do seu DCU.
O mais engraçado aqui é que não importa o código da função CheckProtection, muito menos o código que é executado depois da chamada, caso ela retorne False. Se eu modificar 3 bytes no DCU, este código NUNCA será executado.
Disassemblando o DCU gerado para o código em Pascal acima teríamos algo do tipo:
function CheckProtection: Boolean;
55 PUSH EBP
8B EC MOV EBP,ESP
83 C4 F8 ADD ESP,-8
C6 45 FF 00 MOV BYTE PTR [EBP-1],$00
B2 01 MOV DL,$01
A1 00 00 00 00 MOV EAX,DWORD PTR [_Dn_TRegistry{0x102}]
E8 00 00 00 00 CALL TRegistry.Create{0x103}
89 45 F8 MOV DWORD PTR [EBP-8],EAX
BA 02 00 00 80 MOV EDX,$80000002
8B 45 F8 MOV EAX,DWORD PTR [EBP-8]
E8 00 00 00 00 CALL TRegistry.SetRootKey{0x104}
33 C0 XOR EAX,EAX
55 PUSH EBP
68 6E 00 00 00 PUSH CheckProtection{0x10D}+$0000006E
64 FF 30 PUSH DWORD PTR FS:[EAX]
64 89 20 MOV DWORD PTR FS:[EAX],ESP
33 C9 XOR ECX,ECX
BA 84 00 00 00 MOV EDX,CheckProtection{0x10D}+$00000084
8B 45 F8 MOV EAX,DWORD PTR [EBP-8]
E8 00 00 00 00 CALL TRegistry.OpenKey{0x105}
BA 9C 00 00 00 MOV EDX,CheckProtection{0x10D}+$0000009C
8B 45 F8 MOV EAX,DWORD PTR [EBP-8]
E8 00 00 00 00 CALL TRegistry.ValueExists{0x106}
84 C0 TEST AL,AL
74 04 JE +4; (0x5 )
C6 45 FF 01 MOV BYTE PTR [EBP-1],$01
33 C0 XOR EAX,EAX
5A POP EDX
59 POP ECX
59 POP ECX
64 89 10 MOV DWORD PTR FS:[EAX],EDX
68 75 00 00 00 PUSH CheckProtection{0x10D}+$00000075
8B 45 F8 MOV EAX,DWORD PTR [EBP-8]
E8 00 00 00 00 CALL TObject.Free{0xFE}
C3 RET NEAR
E9 00 00 00 00 JMP @HandleFinally{0xFF}
EB F0 JMP -16; (0x65)
8A 45 FF MOV AL,BYTE PTR [EBP-1]
59 POP ECX
59 POP ECX
5D POP EBP
C3 RET NEAR
FF FF ? EDI
FF FF ? EDI
0F 00 00 SLDT WORD PTR [EAX]
00 4D 79 ADD BYTE PTR [EBP+121],CL
20 53 6F AND BYTE PTR [EBX+111],DL
66 74 77 JE +119; (0x103)
61 POPA
72 65 JB +101; (0xF4)
20 4B 65 AND BYTE PTR [EBX+101],CL
79 00 JNS 0; (0x94)
FF FF ? EDI
FF FF ? EDI
0B 00 OR EAX,DWORD PTR [EAX]
00 00 ADD BYTE PTR [EAX],AL
52 PUSH EDX
65 67 69 73 74 72 79 4B 65 IMUL DWORD PTR GS:[BP+DI+116],$654B7972
79 00 JNS 0; (0xA )
end;
O código fonte em Pascal não é grande, mas o código assembly correspondente mete medo em qualquer um, certo? Talvez... Mas... Não importa o tamanho ou a complexidade do assembly da função CheckProtection. Se eu pegar as 2 primeiras linhas:
function CheckProtection: Boolean;
55 PUSH EBP
8B EC MOV EBP,ESP
E transformá-las em
function CheckProtection: Boolean;
B0 A1 MOV AL,$01
C3 RET NEAR
Teríamos exatamente o código que é gerado se eu tivesse uma função do tipo:
function CheckProtection: Boolean;
begin
Result := True;
Exit;
// o código a partir daqui nunca será executado
...
End;
Ou seja, não importa o que tenha após o EXIT! Ele nunca será executado e a função SEMPRE retornará TRUE. Se o desenvolvedor que trabalhou muitas horas para criar este componente basear seu mecanismo de proteção em algo ingênuo deste tipo... sorry, but... it is cracked!
Isto é só um exemplo do que é possível fazer com um DCU, e de como não se deve programar um mecanismo de proteção de versão de componente trial ou shareware!
Pode-se fazer outras coisas interessantes com uma DCUs, editando-a diretamente, tipo mudar o timestamp permitindo burlar o erro "A unit XPTO foi compilada com versão diferente da unit ABCD".
No comments:
Post a Comment