Tuesday, December 4, 2007

Como NÃO programar um componente Trial/Shareware

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: