PDA

Ver la Versión Completa : Eliminar ciertos datos de la memoria RAM


dec
27-03-2007, 13:32:11
Hola,

Si uno utiliza un programa como WinHex (http://www.winhex.com/index-e.html) puede acceder a la memoria RAM que está ocupando un programa determinado. Si haces uso de este programa puedes visualizar la parte de memoria RAM ocupada por un programa, con sus identificadores, variables, valores...

Y aquí está la cuestión, en los valores. Supongamos un programa que puede ser registrado por los usuarios. Para ello el usuario ha de proporcionar al programa su "clave" de usuario y un número de serie. Dentro del programa hay al menos dos funciones "críticas"... o que a mí me lo parecen luego de investigar el asunto un poco.

A ver. Como digo son dos funciones, básicamente. Una de ellas comprueba que el número de serie proporcionado por el usuario resulta válido para la clave de usuario que también se especifique. Esta función no me parece tan crítica como la siguiente: la función que se encarga de obtener un número de serie válido realmente para el usuario, precisamente, para compararlo con el que el usuario proporciona.

Bien. El caso es que el usuario puede ejecutar el programa y simular su registro especificando una clave de usuario y un número de serie al azar. Acto seguido el programa comprueba el número de serie, y, obviamente, no es correcto (sería complicadísimo averiguarlo al azar) así que el programa cierra el cuadro de diálogo correspondiente y aquí paz y después gloria.

Pero no. Si usuario tiene preparado el programa WinEx o lo ejecuta (todavía con nuestro programa en ejecución, o sea en memoria) con WinEx el usuario puede visualizar la memoria RAM ocupada por el programa, y, sorpresa, sorpresa, en la memoria RAM se encuentra el número de serie válido para el usuario... tal como fue generado en la función correspondiente.

He intentado ya unas cuantas cosas. Al final he conseguido (creo) reducir el problema, concretarlo en la función que digo que se encarga de crear un número de serie correcto para el usuario, de tal manera que pueda compararlo con el que proporcione el usuario. Es aquí donde está el problema, y el caso es que la función digamos que es similar a esta:


function ObtenerNumeroSerie(numeroBase:
integer; claveUsuario: string): string;
var
A: Integer;
begin
result := '';
if (numeroBase <> 0) and (claveUsuario <> '') then
Result := numeroBase * Length(claveUsuario) * 77;
end;


Como puede verse (y disculpad que me enrrolle... pero intento dejar claro el asunto lo mejor que sé hacerlo), como puede verse, digo, la función se basa en cierto número base, que a su vez se combina con la "clave del usuario"... pero el caso es que la función tiene que terminar resultando en un número de serie válido para la clave de usuario, el mismo que podríamos nosotros generar en el "generador" que tengamos preparado para cuando alguien registre el programa.

Obviamente tiene que ser así. El usuario proporciona un número de serie y una clave de usuario. El generador de números de serie lo que hace es conformar un número de serie dado en base a un número base y a una clave de usuario. El programa (no el generador) tiene que hacer lo mismo: tiene que poder generar el número de serie que se corresponde con el número base y la clave de usuario que este a su vez proporcione.

Y tiene que hacerlo porque de este modo podremos luego comparar: el número de serie proporcionado por el usuario y el número de serie que tiene realmente habría de ser.

Pues bien. He ahí el problema. Al generarse en nuestro programa el número de serie dentro de la función susomentada... ¡se está dejando en la memoria RAM el número de serie! Es decir que el resultado de la siguiente función:


function ObtenerNumeroSerie(numeroBase:
integer; claveUsuario: string): string;
var
A: Integer;
begin
result := '';
result := 'pepe';
result := result + 'juan';
end;


... puede "verse" con el programa WinEx. Con este programa puede verse "pepe", por un lado, y luego "pepejuan", por otro... es decir, estamos viendo el "result" de la función...

Obviamente no todos los usuarios van a recurrir a un programa como WinEx. Pero a mí me lo ha comentado un usuario del programa que me traigo entre manos; me ha contado cómo lo ha hecho y yo he podido reproducir el sistema muy fácilmente... claro está que lo difícil era saber cómo hacerlo.

Ahora bien. Termino ya, disculpadme, por favor. ¿Qué puedo hacer? El número de serie "correcto" ha de generarse para poder compararlo con el que proporcione el usuario; ¿Cómo puedo evitar que el resultado de la función "ObtenerNumeroSerie" permanezca en memoria únicamente lo preciso? Es decir, sólo cuando se realiza la comprobación. Digamos que se realiza la comprobación y ¡zas!, el número correcto tiene que desaparecer de la memoria RAM...

Bueno. Si necesitáis más información procuraré proporcionarla. Disculpadme el pedazo de rollo que acabo de soltaros y muchas gracias a todos de antemano. :)

dec
27-03-2007, 14:07:54
Hola,

Bueno. Otra vez yo. Dándome cuenta de que lío las cosas demasiado cuando pueden simplificarse bastante. Olvídense si quieren de números de serie, generadores, registros, claves de usuario...

Simplemente inicien una nueva aplicación en Delphi. Sitúen un botón en el formulario principal. Escriban una función en la implementación del formulario principal:


function Resultado(): string;
begin
Result := 'pepe';
end;


Y en el evento "Onclick" del botón añadido al formulario codifiquen lo siguiente:


procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
begin
s := Resultado();
s := '';
end;


Compilen el programa... compílenlo... ¿Ya? Vale, vale. :)

Salgan de Delphi y ejecuten la aplicación para acto seguido hacer clic en el botón de marras. Ejecuten entonces el WinEx (http://www.winhex.com/index-e.html) (vale la versión de evaluación) y visualizen la memoria RAM (ALT + F9) de la aplicación que acabamos de compilar y ejecutar.

Para terminar busquen "pepe" en el editor hexadecimal de WinEx. ¿Verdad que está "pepe" ahí? Ahora bien, ¿qué demonios hace "pepe" ahí? ¿Cómo puede quitarse a "pepe" del medio? Y que parezca un accidente, claro. :)

Gracias de antemano a todos. :D :D

dec
27-03-2007, 14:16:29
Hola,

Una cosa más... no se les ocurra cambiar la función inmediatamente anterior por:


function Resultado(): string;
begin
Result := 'pepe' + DateToStr(Time);
end;


Porque entonces si buscan a "pepe" le encontrarán una vez (al ejecutar el programa) y otra vez más cuando pulsen en el botón... y en esta última lo encontrarán en el pasado... "pepe30/12/1899"...

No hagan esto. ¡Porque se pueden volver tan locos como yo! :eek: :eek: :p :rolleyes: :o :confused: :confused: :)

PD. El que no sabe es como el que no ve. Confucio

roman
27-03-2007, 14:20:20
Caray, pues preguntando cómo defenderte de eso, acabas de dar un manual de como hacer eso.

Yo, pues no sé, pero ¿qué no puedes escribir en la misma variable otro valor para borra a pepe?

// Saludos

seoane
27-03-2007, 14:27:31
Bien, en estos casos lo mejor es tener control sobre la memoria que se esta usando.

procedure Resultado(Str: PChar; L: Integer);
begin
// Pues yo si que uso DateToStr ¿que pasa?
StrLCopy(Str,PChar('Pepe'+DateToStr(Time)),L);
end;

function Comprobar(Str: String): Boolean;
var
Buffer: PChar;
begin
GetMem(Buffer,32);
try
Resultado(Buffer,31);
Result:= StrComp(Buffer,PChar(Str)) = 0;
finally
//Borramos la memoria
FillChar(Buffer^,32,#0);
FreeMem(Buffer);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMEssage(BoolToStr(Comprobar('Hola')));
end;

dec
27-03-2007, 14:32:32
Hola,

Gracias Román por interesarte por el tema. :)

Bueno. Antes de nada voy a corregirme a mí mismo:


DateToStr(Time)


Eso retorna al paso a "pepe" porque no se hacen las cosas bien, como puede verse. En todo caso debe ser así o así:


DateToStr(Date)
TimeToStr(Time)


Pero, bueno. Es lo de menos... me disculpo porque me pongo quizás demasiado nervioso sin ton ni son y no me paro a mirar las cosas como debe ser...

Vamos al caso. Dices Román que podría dar otro valor a la variable para eliminar el anterior valor... pero el caso es que creo haberlo intentado y no funciona. Es decir.



const
NUMERO_BASE = 123;

function Resultado(numeroBase, claveUsuario: string): string;
begin
result := numeroBase * Length(claveUsuario);
end;

function CompruebaResultado(claveUsuario, numeroSerie: string): boolean;
begin
result := ( numeroSerie = Resultado(NUMERO_BASE, claveUsuario) );
end;



A esto puede reducirse el caso. Como puede verse en la función "Resultado" se genera el valor que no debería quedarse en la memoria RAM... necesitamos ese valor para compararlo en la función "CompruebaResultado", pero, no debería permanecer en la memoria más tiempo que el preciso.

Y sin embargo aparece... pero he probado y no vale hacer algo así:


function CompruebaResultado(claveUsuario, numeroSerie: string): boolean;
var
s: string;
begin
s := Resultado(NUMERO_BASE, claveUsuario);

result := ( numeroSerie = s);

s := '';
end;


No; El número de serie sigue apareciendo. Y parece ser que es porque donde deja su "huella" en la memoria RAM es en la propia función "Resultado". Pero claro, aquí si que obviamente no podemos hacer algo como esto:


function Resultado(numeroBase, claveUsuario: string): string;
begin
result := numeroBase * Length(claveUsuario);
result := ''; // No puede ser claro está
end;


Se me ocurre al hilo de escribir aquí que tal vez si en lugar de una función fuese un procedimiento que retornara el "número de serie" en un parámetro pasado por referencia... o sea:


procedure Resultado(numeroBase, claveUsuario: string; var numeroSerie: string);
begin
numeroSerie := numeroBase * Length(claveUsuario);
end;


... Tal vez sí que podríamos luego hacer algo así:


function CompruebaResultado(claveUsuario, numeroSerie: string): boolean;
var
s: string;
begin
Resultado(NUMERO_BASE, claveUsuario, s);
result := ( numeroSerie = s);
s := '';
end;


Tengo que probar a ver... :)

dec
27-03-2007, 14:41:57
Hola,

Muchas gracias Seoane... pero, ¿estás tú seguro de que Pepe no aparece por ahí? ¡A mí no se me va de la memoria! :D :D :D

Edito: Sí que funciona como dices Seoane. Aparece Pepe cuando efectivamente uno quiere comprobar "Pepe", precisamente. Pero si uno trata con "Juan" (es decir, no "valida") entonces "Pepe" no aparece tal cual...

Tengo que tratar ahora de llevar el código al programa que digo, y veremos a ver, porque no termino de comprenderlo... Lo que ya no sé es si probar pasando como parámetro por referencia a Pepe... puesto que la solución que planteas parece más elegante, no sé. :)

dec
27-03-2007, 15:21:11
Hola,

Lamento molestar otra vez... Seoane... he intentado implementar el código (y la idea... dentro de mis posibilidades) en el programa de marras y el asunto no ha funcionado: sigue viéndose en la memoria RAM lo que no debía verse.

Y entonces me he preguntado cómo es posible si he probado tu ejemplo y yo mismo he dicho que me había funcionado... pero lo he vuelto a probar y el asunto no funciona Seoane... y ahora no sé si es que no nos ponemos de acuerdo (sin duda por culpa mía) acerca de lo que hay que quitar y lo que no...

En tu ejemplo, en todo caso (se "compruebe" o no se "compruebe") que el usuario escribió "Pepe"... este sigue apareciendo en la memoria RAM... ¿tú cómo lo ves? :)

Gracias en todo caso.

PD. Es verdad Román que acaso he proporcionado información de cierta relevancia a alguien... pero no era mi intención: no le dí importancia en ese sentido. No sé si porque doy por supuesto que al final lo explicado no va a servir... si al cabo se consigue eliminar de la memoria RAM el valor de que tratamos.

seoane
27-03-2007, 15:53:24
Si aparece no sera por culpa de la función "Comprobar" sino por culpa de la función "Resultado" que al manipular strings nos provoca el problema. Lo ideal es utilizar solo variables que podamos borrar, ya que delphi aunque libera la memoria que ocupan, no se preocupa de borrar lo que contienen.

Aquí el problema es como planteas la protección, comparar lo que entra el usuario con un valor fijo y devolver un boolean no es muy buen sistema de protección. Funciona, y para un usuario "normal" seria suficiente (1), pero si ya nos encontramos con un "listo" la cosa falla. Saltarse una protección de este tipo es tan fácil como parchear el programa para que la función "Comprobar" siempre devuelva TRUE. Así ya podemos llamar a la función mil veces en nuestro código que la protección ha quedado desactivada.

Pero supongamos que el listo no lo es tanto, y no sabe ensamblador, pero conoce el programa WinHex y prueba a "pescar" en la memoria la posible clave. Pues como la incluyamos como constante en nuestro exe ya estamos vendidos, es mas ni siquiera le haría falta leer la RAM, con solo leer el ejecutable la podría encontrar. Por eso la clave debe generarse durante la ejecución, y utilizarse durante todo el proceso (generación y comprobación) variables que luego podamos borrar. Y lo mas importante, que el listo no sepa ensamblador para que no nos desmonte todo usando lo del párrafo anterior :D

Si de verdad quieres proteger la aplicación (mas tarde o mas temprano caen todas) tendrías que irte a algo mas "profesional". Lo primero un antidebugger, para evitar que usen un debugger. Luego podemos encriptar parte del código del programa y utilizar como clave la clave del producto, de esta manera si la clave no es correcta no hay manera de obtener el código. Y por ultimo un buen rootkit para vacilarlos nosotros antes de que nos intenten vacilar ellos :D (Parece una broma, pero recordemos a Sony y alguno mas)

Como ves, si empiezas a preocuparte por lo que van a hacer los "listos" puedes terminar paranoico. Así que si verdaderamente es importante, una llave hardware y no se hable mas. ;)

(1) Para mi también seria suficiente, con tantos programas OpenSource como hay, no pienso perder el tiempo buscando claves.

basti
27-03-2007, 15:54:56
Hola, he visto este hilo y me parece interesante. Creo que tanto 'Pepe' como la fecha pueden aparecer en el volcado de memoria del programa de Seoane por dos razones, espero no equivocarme.

'Pepe' está escrito de manera estática en la función Resultado, por lo tanto no hay manera de quitarlo de la memoria. Esto no sería un problema para tu aplicación, ya que la clave del cliente no estaría escrita en el código de la aplicación.

El otro problema que veo es la pila del programa, cuando se hace la siguiente llamada:

StrLCopy(Str,PChar('Pepe'+DateToStr(Time)),L);


El valor 'Pepe'+DateToStr(Time) se copia en la pila de llamadas de la aplicación, y permanecerá ahi hasta que otras llamadas sobreescriban estos datos en la pila.

Puedes comprobarlo de la manera siguiente:
En el código de Seoane haces una implementación de StrLCopy, algo así

Procedure StrLcopy(dest, org : PChar; len : Integer);
begin
while org^ <> #0 do
begin
dest^ := org^;
Inc(dest);
Inc(org);
end;
dest^ := #0;
end;

de manera que uses esta función en vez de la original de delphi. Pones un punto de ruptura dentro de la función, vas a la ventana "Call Stack". Ahí verás el valor tanto de 'Pepe' como la fecha.

La solución, creo que podría ser una llamada a una función recursiva, justo después de la llamada a StrLCopy, para que sobreescriba la pila. La función debe ser llamada las veces que hagan falta para borrar los datos de la pila. Algo así:

procedure Resultado(Str: PChar; L: Integer);
procedure VamosAContarAlReves(n : integer);
begin
if n > 0 then
VamosAContalAlReves(n - 1);
end;
begin
// Pues yo si que uso DateToStr ¿que pasa?
StrLCopy(Str,PChar('Pepe'+DateToStr(Time)),L);
VamosAContarAlReves(100);
end;

seoane
27-03-2007, 15:59:20
Ingenioso método de borrar la pila Basti :)

Entonces ya tenemos los 3 pasos: no usar constantes, borrar las variables y borrar la pila.

dec
27-03-2007, 16:22:54
Hola,

Gracias a todos, de verdad. Estaba a punto de escribir esto porque me parecía haber encontrado una posible solución, pero, me quedo corto al lado vuestro.

Como dices Seoane el problema está en manipular variables en la función "Resultado". Si no lo hacemos, es decir, si "calculamos" el resultado ya dentro de la función "StrLCopy" entonces desaparece el problema. Es decir, que puedo decir que el problema está resuelto gracias a vosotros. Acabo de comprobarlo y volverlo a comprobar.

Dices Seoane que dejar el Serial como constante no es buena idea... pero no se hace así. Lo que se tiene como constante es un número "base". Con este número base y la clave del usuario se genera un "Serial", pero esto se hace con cierto algoritmo (luego más sobre esto) que no puede conocerse así como así.

Ahora bien. Parece (y digo parece porque he hecho pruebas pero tengo que hacer alguna más aún) que conseguimos hacer desaparecer el "Serial" correcto de la memoria RAM. Ahora lo que me deja perplejo es el tema de la pila... ¿ya no basta con la memoria RAM? ¿Acaso puede buscarse el "Serial" en la pila incluso cuando digo que ya no aparece en la memoria RAM?

Definitivamente me queda mucho más por aprender de lo que aprenderé nunca.

Para quien esté interesado, estoy utilizando una clase para todo esto que está basada en el componente "JvSerialMaker (http://jvcl.svn.sourceforge.net/viewvc/*checkout*/jvcl/branches/JVCL3_BETA2/jvcl/run/JvSerialMaker.pas?revision=8225)" de la Jedi Library (http://sourceforge.net/projects/jvcl/). Evidentemente ya sabéis que este componente "deja" en la memoria RAM el número de serie que genera y puede comprobar...

En fin voy a asegurarme con más pruebas de que el número de serie no aparece en la memoria RAM y luego acaso me ponga con el tema de la pila que apunta el compañero basti (gracias basti). Pero estoy de acuerdo con Seoane... probablemente estas cosas habría que tomarlas bastante más en serio de lo que estoy dispuesto a hacer para evitar que alguien "piratee" un programa como se empeñe en hacerlo...

Neftali [Germán.Estévez]
27-03-2007, 16:30:12
Una de ellas comprueba que el número de serie proporcionado por el usuario resulta válido para la clave de usuario que también se especifique. Esta función no me parece tan crítica como la siguiente: la función que se encarga de obtener un número de serie válido realmente para el usuario, precisamente, para compararlo con el que el usuario proporciona.

Bueno, no es mi intención "desmontar" la teoría inicial, pero aquí el fallo está en el planteamiento.
De todas formas el problema de la memoria RAM es interesante también.

Aunque cada uno programa los sistema de registro como desea, normalmente los programas no suelen utilizar el sistema mencionado, porque es muy sencillo detectar el número correcto utilizando un Debugger. No hace falta "rebuscar" en la memoria RAM, basta con ejecutarlas instrucciones del programa y revisando los registros y la pila.

Por ejemplo, "debuggando"(¡qué mal suean esto!) el programa mencionado por dec, con OllyDBG podemos llegar fácilmente (y eso que yo no soy experto en estos temas, pero con un par de minutos me ha bastado...) a este punto:
imagen (http://img462.imageshack.us/img462/1886/ollydbgxl1.png)

En la parte izquierda se ven las instrucciones y en la derecha la pila y los registros.
Normalmente en estos casos (crackendo un programa, cosa que yo nunca he hecho...:eek::eek::eek:) se busca un punto de partida (1); Si este es acertado, a partir de ahí se ejecutan las instrucciones hasta llegar a los procedimientos donde se evalua y calcula el número de serie.

(1) Para comenzar se buscan los formularios de Registro, prodecimientos con nombres sospechosos (TestID, Register, RegisterOK,...), Strings "sospechosos" como "El número de registro es incorrecto", "El número de serie no es válido",...
¿Por qué comento esto? Porque sabiéndolo, alguien que diseñe un sistema de protección debe tener en cuenta:
(a) Los procedimientos que tienen que ver con el registro y números de serie nunca deben llamarse: Registro, CalcularSN, TestRegistro,... ni cualquier otro nombre que de pistas sobre olo que están haciendo.
(b) Los mensajes de error relativos al registro deben estar codificados y nunca directamente en el código; La codificación no importa, basta un XOR, o cambiar una letra por otra, lo importante es que si revisas la memoria no aparezcan directamente.
(c) Algunas cosillas más....

Si "cuesta" encontrar en punto de entrada para comenzar a "debuggar" las cosas se hacen un poco más complicadas.

En cuanto al número de registro o números de serie, la premisa es que en ningun momento el número de serie debe estar completo en memoria. En una variable o en una cadena ni debe ser devuelto por una función.

Lo lógico es hacerlo "a partes" y una vez particionado no hacer comparaciones directas.
Un ejemplo sencillo es no hacer esto:


// 1234 calculado previamente...
if (SN = '1234') then
...


Y en su lugar hacer esto:


j := Ord('Q') - Ord('N');
// 1234 calculado previamente...
if (SN[1] = '1') and (SN[2] = (4 DIV 2)) and (SN[3] = j) and ... then
...


Comparar las partes desordenadas,....
Hay infinidad de técnicas, que van complicando el tema más... Pero algunas son básicas como las comentadas antes.
Sobre todo ¡Nunca debe estar el SN correcto almacenado de forma contigua en memoria!

Porque si no pasan cosas como esta (http://img472.imageshack.us/img472/8351/imagen13kz6.png). LLegado al punto de la comparación te puedes encontrar el nombre del registro, el SN incorrecto y el SN correcto con el que se está comparando...:(

AÑADO: En esta última imagen tampoco es muy afortunado el nombre del procedimiento ChessRk (contando que el programa se llama Chess); ¿Qué os sugiere ChessRk, sabiendo que el programa está en inglés? :D

Neftali [Germán.Estévez]
27-03-2007, 16:37:49
Veo que habéis avanzado en el tema mientras yo escribía, o mejor dicho, me enrollaba...:D:D

Ahora lo que me deja perplejo es el tema de la pila... ¿ya no basta con la memoria RAM? ¿Acaso puede buscarse el "Serial" en la pila incluso cuando digo que ya no aparece en la memoria RAM?

Como he comentado más arriba, no debería estar ni en la pila ni en los registros; Y eso significa que no se puede asignar a una variable, que no se puede pasar como parámetro y no debe ser devuelto por ninguna función (hablamos del serial correcto completo).

dec
27-03-2007, 16:48:17
Hola,

Muchas gracias Neftalí. Gracias a todos. :)

Yo me doy cuenta de lo poco que sé de todo esto... ¡me pierdo enseguida! Y como me pierda no me encuentro. ¡¿Pues no resulta ahora que tras haber comprobado con estos ojos que se han de comer la tierra, me pongo a implementar el asunto en el programa de marras y todo sigue igual?!

Igual. El número de serie correcto aparece en memoria. Pero ahora el número no está en ninguna variable como antes. Yo no sé si estoy comprendiendo lo que hablamos, porque, ahora mismo el número de serie "se calcula" dentro de la propia función "StrLCopy"...

Es decir, que pasa a la variable "destino" de la función "StrLCopy", directamente, variable que luego es destruída tal como Seoane ha explicado...

No sé. ¿Acaso me queda entonces por hacer lo que apunta el compañero basti? ¿No basta entonces con liberar dicha variable (única que llega a contener el número de serie correcto)?

No sé... voy a probar a ver... :)

Y en todo caso pelear con lo que menciona Neftalí: pero me temo que voy a dar unos cuantos palos de ciego... ¡Ay! :)

dec
27-03-2007, 16:58:44
Hola,

Yo de nuevo. :) :D

Oyes Neftalí... ¿sabes que lo de comprobar el número de serie por partes puede acaso ser una solución? Pero no se me ocurre cómo... de momento... y puede que nunca. :)

El caso es que, efectivamente, ni siquiera quitando del medio la función "Resultado", es decir, comparando el número de serie del usuario con la cadena resultante del cálculo del número de serie... es decir, comparando una variable con "un valor" (no una variable) que se conforma en el momento... aun así el número de serie, o sea el "valor" queda en la memoria...

Así que como no sea partiendo el número de serie, de modo que quede... pero irreconocible, pudiera ser una buena opción. Ahora toca averiguar cómo demonios hago esto, porque no se me ocurre... no sé... :)

dec
27-03-2007, 17:11:27
Hola,

Joroba... ahora me doy cuenta de que no he terminado de probar el asunto del modo que lo planteaba el compañero basti, es decir, lo hize como Seoane explicó, pero, no llegué a "actualizar" a lo que basti comentaba... y ahora no sé si hacerlo, puesto, que, llegando a estar el número de serie correcto en memoria incluso aunque no esté en una variable... ¿no invalida esto el método propuesto por el compañero basti?

Quiero decir, si tras intentar del mejor modo que he sabido lo que propone Seoane llego a la conclusión de que, efectivamente, el número de serie sigue permaneciendo en memoria (si estoy metiendo la pata me corregís por favor), puesto que en algún punto del programa ha de componerse para poderlo comparar con el que propone el usuario...

Parece que de todas formas la posible solución pasa (sin ir más allá...) por lo que dice Neftalí (¿o me equivoco?), es decir, no llegar nunca a formar el número de serie completo, sino hacerlo por partes... para que aunque aparezcan estas partes sea complicado reunirlas a todas y conocer así el número de serie.

Ahora bien, quedaría entonces cómo comparar el serial del usuario (por partes) con el serial correcto que hemos de generar (también por partes...)...

¡jo, jo, jo, jo! :D :D :D :D

seoane
27-03-2007, 17:16:54
Y si cambias el planteamiento. Hasta ahora estas comprobando lo que inserta el usuario, tal cual, con un numero "generado". Pues bien yo te propongo lo siguiente: tomar el valor que introduce el usuario y calcular su hash (md5 por ejemplo), entonces comparamos su hash con el que tenemos guardado, en una constante porque no nos importa que lo vean en la RAM. Y no nos importa porque aunque alguien conozca el hash nadie puede saber que numero tiene que introducir para obtener ese hash. Sencillo y efectivo, esta es la magia del Hash :D

seoane
27-03-2007, 18:18:52
Y para continuar mi mensaje anterior un poco de código:

uses Hashes;

function Comprobar(Str: String): Boolean;
begin
Result:= AnsiSameText(StrCheckSum(Str),'e885d567f57b0f87333c25f7f3a1e381');
end;

// Por ejemplo
ShowMessage(BoolToStr(Comprobar(Edit1.text),TRUE)); // Pepe es la clave

La clave correcta en este caso es "Pepe" pero como ves no aparece por ninguna parte, ni en el código, ni en memoria, ni en ningún sitio. Solo aparece su hash, pero a partir de su hash no podemos obtener la clave.

PD: La unit hashes se puede encontrar aquí
http://www.clubdelphi.com/foros/showpost.php?p=171622&postcount=4

ArdiIIa
27-03-2007, 18:37:09
Seoane, como siempre, es brillante la visión de la jugada que aportas....

He estado leyendo el hilo y no me ha dejado hacer la digestión en condiciones.

Estuve pensando sobre el tema en un planteamiento relativamente parecido.

La idea en sí, es tener un valor previo codificado... Por el sistema que fuera. En este caso mis pocas neuronas pensaron en un algoritmo de movimientos o/y complementos de bits hasta hallar una serie mas o menos complicada, y con la clave introducida, realizar el mismo movimiento y posterior comparación. En definitiva, la codificación por el método que sea. Obviamente la serie codificada e885d567f57b0f87333c25f7f3a1e381 tendría que ser previamente tratada antes de ser introducida en el propio código del programa.
Creo que mas o menos el planteamiento tuyo pero obviamente mas sencillo y versátil.

Hace años y basándome en esto mismo que digo, hice un pequeño código en ensamblador, pero realmente esta codificación afectaba al propio código del programa, de tal manera que dejaba dentro de la rutina espacios de memoria a cero (NOP), la rutina se auto-descodificaba y se movía a esa porción de memória para seguidamente dar un salto al nuevo código...

Neftali [Germán.Estévez]
27-03-2007, 19:24:25
Ahora bien, quedaría entonces cómo comparar el serial del usuario (por partes) con el serial correcto que hemos de generar (también por partes...)

Bueno, como he dicho, esta es una de las primeras cosas a hacer y que evita encontra el serial de forma trivial.

Lo siguiente es evitar comparaciones directas; Simplemente, porque no siempre se necesita encontrar el Serial, a veces basta con "Parchear".

Es decir, si yo no encuentro el Serial, pero veo tres comparaciones que dicen:

if A!='aasd' then ...
if B!='khgk' then ...
if C!='grfeu' then ...

En este caso optaría por "parchear" los tres != por tres =.

Ya se que el tema se va complicando, pero son cosas que hay que tener en cuenta; La mayoría de sistemas anticopia (serios) ya hechos tienen en cuenta estas cosas y muchas más...
En ese caso a la hora de comparar dos valores, podemos utilizar por ejemplo la resta para saber si da cero, funciones del tipo CHAR y Ord (aplicándolas a los dos) para no comparar los valores exactos, sumar y restar a ambos un valor,...
Aunque desde delphi parece una tontería hacer esto:


// en lugar de...
if ('A' = Str[1]) then
// usar...
if (char('A') = Char(Str[1])) then


Para el que está revisando el código en ensamblador supone uno o varios saltos, cosa que dificulta mucho a la hora de seguir el código (las llamadas a funciones); Pensad que si se van aplicando diferentes a cada comparación, al final la cosa se hace muy compleja para el que está revisando la pila y los registros.

ArdiIIa
27-03-2007, 19:42:05
Para el que está revisando el código en ensamblador supone uno o varios saltos, cosa que dificulta mucho a la hora de seguir el código (las llamadas a funciones); Pensad que si se van aplicando diferentes a cada comparación, al final la cosa se hace muy compleja para el que está revisando la pila y los registros.

Comenzó el hilo buscando y encontrando una cadena en memória y finalmente acabamos hablando de ingeniería inversa.
Llegados a este punto, creo que el menor de los problemas sería el como ocultar la forma y el modo de como cotejar una clave, dado que llegados a ese nivel y si resulta de interés, el invasor no se iba a preocupar de la clave, sino que se preocuparía de ver cual es el procedimiento que le hace salir del programa (por ejemplo) y que otras alternativas o saltos de programa tiene para que este funcione adecuadamente, para a continuación parchear y a disfrutar.... Creo yo...:confused:

roman
27-03-2007, 20:04:47
Una pregunta en cuanto al borrado del dato:

¿Qué no puede usarse simplemente una variable global y asignarle otro valor? No hay stack donde quede nada y la variable se ha reescrito.

// Saludos

Neftali [Germán.Estévez]
27-03-2007, 20:05:04
...creo que el menor de los problemas sería el como ocultar la forma y el modo de como cotejar una clave, dado que llegados a ese nivel y si resulta de interés, el invasor no se iba a preocupar de la clave, sino que se preocuparía de ver cual es el procedimiento que le hace salir del programa (por ejemplo) y que otras alternativas o saltos de programa tiene para que este funcione adecuadamente, para a continuación parchear

Bueno, estamos hablando de hacer las dos cosas, evitar que la comparación sea evidente y que esté un único sitio; De ahí que la comparación se haga por partes y no del número completo. Y como segunda parte que esas comparaciones que se hacen por partes no sean evidentes. A nivel de Delphi es un poco más de trabajo (tampoco mucho).

Casimiro Notevi
27-03-2007, 22:26:20
Una vez participé en un proyecto de "seguridad" para evitar que "piratearan" los programas de la empresa donde trabajaba. Tuvimos en cuenta lo que habéis comentado y un montón de cositas más, el proyecto duró 7 meses y éramos 3 personas dedicadas únicamente a eso, en su día tuvo un presupuesto de 60.000 euros. Buscamos información de todos los métodos conocidos hasta la fecha, documentos, libros, consultas a algunos expertos, etc.
Entre nosotros había uno, no era yo, que decía constantemente: "no vale la pena tanto tiempo y dinero en protegerlo, lo mejor es que lo copien y lo usen aunque sea gratis y que no use el de la competencia".
Pues bien, a la semana de sacar el programa, un chaval ruso que además nos envió un gracioso email, publicó un keygen para el programa, que además de ser más simple que nuestro propio programa, era más rápido, pequeño, cómodo y seguro.
Nos quedamos con la boca abierta, nos miramos los unos a los otros y pensamos: "¡¡¡cómo podemos ser tan malos programadores!!!".

Moraleja: sin comentarios :)

egostar
27-03-2007, 22:48:28
Una vez participé en un proyecto de "seguridad" para evitar que "piratearan" los programas de la empresa donde trabajaba. Tuvimos en cuenta lo que habéis comentado y un montón de cositas más, el proyecto duró 7 meses y éramos 3 personas dedicadas únicamente a eso, en su día tuvo un presupuesto de 60.000 euros. Buscamos información de todos los métodos conocidos hasta la fecha, documentos, libros, consultas a algunos expertos, etc.
Entre nosotros había uno, no era yo, que decía constantemente: "no vale la pena tanto tiempo y dinero en protegerlo, lo mejor es que lo copien y lo usen aunque sea gratis y que no use el de la competencia".
Pues bien, a la semana de sacar el programa, un chaval ruso que además nos envió un gracioso email, publicó un keygen para el programa, que además de ser más simple que nuestro propio programa, era más rápido, pequeño, cómodo y seguro.
Nos quedamos con la boca abierta, nos miramos los unos a los otros y pensamos: "¡¡¡cómo podemos ser tan malos programadores!!!".

Moraleja: sin comentarios :)

Yo siempre he pensado que el aliento para pasar las barreras de una protección es que sea lo mas "dificil" posible, y no creo que hayan sido "malos programadores" solo que hubo uno mejor que ustedes.

Aqui en Mexico diriamos, "para chingon, chingon y medio".:D

Salud OS.

seoane
28-03-2007, 02:17:24
:D Me voy a meter a cracker. Recordáis la protección de la que hablaba con el md5, esa tan maravillosa, pues con el OllyDbg del que hablaba Neftali me la salte en un momento, el tiempo que me llevo averiguar como guardar los cambios en ese programa endemoniado :D

La cosa fue tan sencilla como cambiar esto:

00459BF2 |. E8 4DCAFAFF CALL <JMP.&kernel32.CompareStringA>
00459BF7 |. 83E8 02 SUB EAX,2
00459BFA |. 85C0 TEST EAX,EAX


Por esto otro:

00459BF2 |. E8 4DCAFAFF CALL <JMP.&kernel32.CompareStringA>
00459BF7 B8 00000000 MOV EAX,0
00459BFC |. 0F94C0 SETE AL


Y toda mi superproteccion quedo inutilizada .... Lo que hace el aburrimiento :D

dec
28-03-2007, 02:45:34
Hola,

Muchísimas gracias a todos por vuestro interés en el asunto que planteo en este Hilo. Voy a tratar de continuar respondiendo a lo que se ha comentado en mi ausencia.

Román, dices de hacer uso de una variable global en donde se almacenaría el número de serie correcto (generado en el programa) para compararlo con el introducido por el usuario. Acto seguido habría que dar a dicha variable global otro valor. ¿Pero esto funcionaría?

Lo digo porque esta mañana comprobé que el número de serie correcto (insisto, generado en el propio programa) aparecía en la memoria incluso cuando este no era albergado en ninguna variable. O por mejor decir, el algoritmo que construye el número de serie correcto ofrecía su resultado a la propia función ("StrLCopy") encargada de copiarlo a cierta variable... que era ¿liberada? de la memoria acto seguido de utilizarla.

Es decir, que parece que sólo conque el número de serie sea generado este ya aparecerá completo en la memoria... o eso se deduce de las pruebas que he llevado a cabo, si omitimos el echo de que no llegué a hacer uso de las funciones que propuso el compañero basti más arriba. Tal vez esto último es algo que tendría que comprobar antes de nada.

Seoane, dices de utilizar un "hash", de comparar el "hash" del número de serie con el introducido por el usuario. Y dices que podríamos guardar dicho "hash" incluso en una constante, puesto que, como bien dices, del "hash" no se puede colegir el número de serie. Es cierto, pero, también lo es que no podemos guardar el número de serie correcto como una constante, sencillamente, porque este no es un valor constante: no hay un número de serie correcto para todos y con el que podamos comparar los intentos del usuario.

De hecho el número de serie ha de generarse por el propio programa, a partir de un número base y una clave de usuario. Es decir, un número de serie se "calcula" a partir de la clave de un usuario. Sin conocer esta última no es posible generar el número de serie correspondiente. Entonces, si al cabo hay que generar el número de serie (para luego obtener su "hash"), ¿no estaríamos en el mismo problema en que estamos? Es decir, a partir de que podamos calcular el "hash" de un número de serie quiere decirse que habremos generado dicho número de serie... y ya estará en la memoria... o me equivoco.

Casimiro, dices que acaso no merezca la pena trabajar en estos menesteres, y puede que lleves razón. Es algo que me he planteado también. Liberar el programa, adjuntar incluso su código fuente, y, bueno, que quien quiera pueda realizar una donación o "algo" para que uno (que trabaja en el programa) se vea compensado de algún modo. El problema es este, precisamente: que uno se quiera ver recompensado, es decir, que no se vea así ya. De hecho como digo es una opción más la de liberar el programa, hacer innecesario el uso de ningún número de serie y aquí paz y después que salga el sol por donde salga.

Sin embargo ahora mismo esto que se trata en este Hilo es más bien una especie de reto: se plantea un problema y quiere encontrarse su solución. Independientemente de que en el futuro se quite el problema del medio (y no haya que buscarle solución) lo cierto es que no quisiera evitar el problema... es decir quitarle del medio sin saber antes que al fin y al cabo es algo que tiene solución o soluciones. No digo que tú quieras deshacerte del problema, o no lo digo en ese sentido: creo que sé por dónde vas y ya digo que en cierto modo estoy bastante de acuerdo contigo.

Continúo. Veo que antes de publicar esto Seoane ha conseguido "romper" su propia protección... :D :D :D Aunque no de la misma forma en que yo he propuesto, es decir, según yo aunque comparemos "hashes" el número de serie ha de calcularse en el programa y pasará a poder verse en la memoria con un programa como WinEx. Se verán los "hashes" y no se podrá hacer nada con ellos, de acuerdo, pero también se verá el número de serie, si no estoy metiendo la pata con mi planteamiento, claro está. :)

Y esto me hace pensar que acaso logre solucionar el problema que me ocupa pero todavía pueda obtenerse un número de serie válido o a lo menos evitar el registro del programa. Probablemente esto sea así. Pero ahora me preocupa cómo quitar el número de serie de la memoria RAM. Si luego hay otros modos o no los hay de saltarse la protección del programa... eso se verá en su momento llegado el caso.

Habéis hablado también (Neftalí mayormente) de que el programa no debería dejar ver a las claras que ha sido o no registrado correctamente. De hecho así es. En el caso del programa que nos ocupa es el usuario quien se encarga de abrir el diálogo para registrarlo. En este diálogo (formulario) el usuario ha de proporcionar su clave de usuario y su número de serie. Si el registro es correcto... no pasa nada, aparentemente. Si es equivocado, tampoco damos al usuario ninguna señal de que el programa no se ha registrado correctamente.

La advertencia de que el programa no está registrado digamos que se produce cuando el usuario ejecuta ciertas acciones. Vale que alguien que sepa de estas cosas puede tratar de localizar el "salto" de dicha advertencia, y quitarla del medio antes de que aparezca. Esto seguramente puede hacerse. También es verdad que yo podría tratar de complicar un poco las cosas. Es decir, en lugar de avisar con una advertencia al usuario de que el programa no está registrado... tal vez podría además hacer que mientras el programa lleva a cabo alguna de sus acciones (o varias de ellas) se compruebe su registro y "salte" en el momento.

Esto haría las cosas más complicadas al "cracker"... porque en lugar de evitar una advertencia tendría que evitar más de una, y además no sabría, hasta que no lo pudiera comprobar por sí mismo como usuario del programa, cuántas advertencias existen, y dónde existen. Pero en fin, como he dicho, comprendo que alguien con los suficientes conocimientos y la paciencia de rigor conseguiría saltarse las advertencias que se le pusieran por delante: no soy yo mejor que los que diseñan la protección de Windows, por ejemplo, y esta siempre acaba "saltándose" de varias formas además...

Pero me preocupa ahora lo del número de serie. Cómo demonios hacer que un dato que conocemos tras un cálculo en el programa, y que luego utilizamos, sin embargo desaparezca de la memoria, no deje rastro alguno y no pueda ser averiguado, por lo tanto. Esto es lo que me preocupa ahora. Y después de escribir todo esto (aunque estanto un poco dormido aún) sigo pensando en que una posible solución puede ir por donde Neftalí apunta: tal vez generar "trozos" del número de serie, y comparar estos trozos con sus correspondientes pares.

De este modo seguirían viéndose trozos del número de serie correcto en la memoria, pero, ya complicaría el asunto bastante, puesto que a ver cómo y de qué manera se pone uno a "juntar" los trozos a poco que sean unos cuantos... se me hace que el "cracker" tendría entonces que tirar por otro camino y desde luego no podría obtener el número de serie "completo", mondo y lirondo, de la memoria ocupada por el programa.

Bueno. No sé. Digo que lo de partir el número de serie correcto en trozos podría funcionar, pero, lo cierto es que aún no sé cómo llevar a cabo esto. Se admiten ideas y sugerencias y yo por mi parte os mantendré informados en caso de avanzes o retrocesos en este sentido. :)

Otra vez muchas gracias a todos por vuestro interés. Y disculpad el rollazo. :D :D

dec
28-03-2007, 05:02:08
Hola,

Otra vez yo... acabo de probar con el método que apuntó primero Seoane, añadiéndole la función recursiva que el compañero basti propuso a su vez, pero, que si quieres arroz Catalina... sin utilizar variables, dejándolo en un "buffer" que acto seguido es liberado de la memoria, haciendo uso luego de la función "VamosAContarAlReves"... ¡que si quieres arroz Catalina!

Algún modo ha de haber, digo yo, ¿no? :D :D :D

roman
28-03-2007, 06:18:55
No entendí esto último. ¿Funcionó o no?

// Saludos

dec
28-03-2007, 06:25:03
Hola,

No Román; no me funcionó. :(

roman
28-03-2007, 06:32:44
Pues es raro. Yo hice una tímida (e ingenua) prueba así: dado que el problema era que el dato se pasaba al stack al pasar el parámetro, supuse que bastaba llamar una segunda vez al procedimiento con un valor "borrador": Resultado('XXXXXXX'),
pero el condenado pepe seguía ahí. Además de que el pepe entra en un Edit, y ése también contribuye, aún borrando el edit, me parece que pepe sigue ahí.

// Saludos

dec
28-03-2007, 06:36:55
Hola,

Pues sí. La verdad es que estoy más perdido que un pavo en un garaje. Alguna forma ha de haber de lograr hacer desaparecer de la memoria el número de serie correcto una vez utilizado por el programa. Pero a mí personalmente no se me ocurre ahora mismo ninguna, y es que todo esto de la pila, el stack, la memoria RAM, etc., etc., es nuevo para mí, lo reconozco, siempre me he movido en otro nivel... para mi mal, probablemente. :D :D :D

Casimiro Notevi
28-03-2007, 11:04:24
¿Y por qué no 'escondes' a Pepe?, un xor a nivel de bits, por decir algo, para que no se sepa qué es, quedarán caracteres 'extraños'.

Neftali [Germán.Estévez]
28-03-2007, 12:32:00
Recordáis la protección de la que hablaba con el md5, esa tan maravillosa

Bueno, estaba claro que en ese caso (ya que con el MD5 está "peliagudo") la opción era "atacar" la comparación o la constante con el MD5 a comparar si es que estuviera fija.

Neftali [Germán.Estévez]
28-03-2007, 13:40:56
Alguna forma ha de haber de lograr hacer desaparecer de la memoria el número de serie correcto una vez utilizado por el programa.

¿Y si enfocamos el tema de otra manera?
En lugar de borrarlo, que no aparezca. Tal vez me he perdido algo, pero creo que es posible...

Se trata de hacer el cálculo sin asignar el SN en ningun sitio.
Por ejemplo, este programa (http://www.megaupload.com/?d=7OVMQ8BN); He seguido los pasos (con el WinHex) para encontrar en Serial Correcto en memoria y yo no lo veo.

El procedimiento de cálculo es el siguiente:


var
b:Boolean;
i:Integer;
begin

if ((Length(Edit2.Text) - Length(Edit1.Text)) < 2) or
(Length(Edit1.Text) < 4) then begin
MessageDlg('El número de registro es incorrecto', mtWarning, [mbOK], 0);
Exit
end;

b := True;

for i := 0 to (Length(Edit1.Text) - 1) do begin
b := b and (Edit1.Text[i] = Edit2.Text[i]);
end;

b := b and (Edit2.Text[i + 1] = 'S');
b := b and (Edit2.Text[i + 2] = 'N');

if (b) then begin
MessageDlg('El registro se ha completado correctamente.', mtInformation, [mbOK], 0);
end
else begin
MessageDlg('El número de registro es incorrecto', mtWarning, [mbOK], 0);
end;


Como véis es muy "cutre", pero el número correcto no se asigna.

Neftali [Germán.Estévez]
28-03-2007, 14:12:38
Dista mucho de ser infalible (1) y (2), pero creo que la solución puede ir por aquí.

(1) Comprobación de la longitud (http://img244.imageshack.us/img244/7448/imagen15dq5.png). (pepito + palotes); Se pueden ver los registros con los valores 7 y 6 (de ambas longitudes), la resta y la comparación con 2.
(2) Comprobación de caracteres 1 a 1 (http://img261.imageshack.us/img261/6661/imagen14ae9.png). (1234 + casaca); Se puede ver cómo empieza a comparar "1" y "c" y el salto que nos lleva al mensaje de fallo.

Creo que la idea es buena.

dec
28-03-2007, 18:49:12
Hola,

No lo cojo Neftalí. En tu anterior ejemplo (que te agradezco) según se ve en el código puede registrarse el programa con el usuario "pepe" y el número de serie "pepeSN". Claro que lo sé porque tengo acceso al código fuente, en esto estamos de acuerdo.

Yo comprendo que ha de haber alguna forma de hacer lo que necesito, puesto que hay programas que me consta no caen en la "trampa" en la que cae el mío. Sin embargo por más que lo intento aún no consigo resultados.

Ten en cuenta que no se trata de comparar el valor "clave de usuario" y "número de serie". Se trata de que a una clave de usuario dada le corresponde un número de serie. El programa se encarga, a partir de la clave de usuario y un número "base", se encarga, digo, de generar el número de serie.

Yo tengo el programa y un generador de números de serie, que es otro programa. Ambos comparten la unidad "GenerarSerial" y en esta unidad hay una clase con dos métodos: "CompararSerials" y "GeneralSerial". Cuando ejecuto el generador lo que estoy haciendo es obtener un número de serie según la clave de un usuario.

El usuario me escribe: "Oyes David, este programa está molón, acabo de ingresarte en tu cuenta 1.000.000 de dólares, espero que sea suficiente para que me envíes un número de serie a nombre de "Pepe Rico".

Yo cojo el Generador e introduzco el número base del programa y la clave de usuario "Pepe Rico". El Generador me genera (toma ya) un número de serie para esa clave y ese número base y yo se lo mando a Pepe Rico: "Hola Pepe, recibido el millón ahí te va el número de serie".

El programa (ya no el Generador de "Serials"), cuando Pepe Rico introduzca su clave de usuario y su número de serie podrá comprobar si este último se corresponde con el que tiene que ser puesto que podrá calcular (como el Generador) el correcto en base al número base y a la clave de usuario introducida.

Hasta ahí bien. Todo es correcto. ¿Todo? No. Porque que el Generador deje en memoria el número de serie correcto no importa. De hecho tiene que ofrecerme ese número de serie correcto, tiene que dejarme "copiarlo" (de hecho se muestra en un "TEdit") para que yo pueda mandárselo a Pepe Rico que para eso pagó el millón de rupias.

Ahora bien, el programa, el de Pepe Rico, no el Generador, no debe (y no lo hace) mostrar el número de serie correcto al usuario... ¡pero este se queda en la memoria! Y tal vez Pepe Rico no, pero algún otro usuario conseguirá el número de serie correcto a poco que eche un vistazo...

Lo último que he probado ha sido el "CheckSum", el "hash" del número de serie, pero es que no salimos del problema: Yo puedo comparar el "CheckSum" del número de serie del usuario y del correcto, pero si puedo hacer esto último es porque he generado el número de serie correcto, y, según parece, a poco que esto se haga y se haga donde se haga ya aparecerá en memoria... irremediablemente.

Tal vez es que el planteamiento es el equivocado. Ya digo que todo mi "sistema de generación y comprobación de números de serie" se basa en el código del componente "TJvSerialMaker" de la Jedi VCL. Pero entonces es que todo programa que haga uso de este componente está comprometido en lo dicho: mostrará el número de serie correcto en la memoria.

Tal vez sea esto y no tenga remedio porque es un mal planteamiento o no está lo suficientemente pensado o simplemente es algo sencillo, de andar por casa, puesto que a Pepe Rico probablemente no mirará la memoria RAM ni sabrá siquiera que algo así existe y se puede mirar...

¡Arrggggggggggggggggggggg! :D :D ;)

seoane
28-03-2007, 19:05:50
Si no es mucho preguntar, como generas el numero de serie. Puede que el problema resida ahí. Podríamos intentar crear un algoritmo, una sola función, que permita comprobar el numero de serie dejando la memoria limpia.

En resumen, muestranos el algoritmo :D

dec
28-03-2007, 19:11:40
Hola,

Ningún problema Seoane... es de dominio público... ya digo. :) Extraído de la unidad "JvSerialMaker.pas (http://jvcl.svn.sourceforge.net/viewvc/*checkout*/jvcl/branches/JVCL3_BETA2/jvcl/run/JvSerialMaker.pas?revision=8225)" de la Jedi Library (http://sourceforge.net/projects/jvcl/). De hecho copiaré aquí los dos métodos principales... el que genera números de serie y el que compara un número de serie con el "correcto" (generado)...


function TJvSerialMaker.GiveSerial(ABase: Integer; AUserName: string): string;
var
A: Integer;
begin
if (ABase <> 0) and (AUserName <> '') then
begin
A := ABase * Length(AUserName) + Ord(AUserName[1]) * 666;
Result := IntToStr(A) + '-';
A := ABase * Ord(AUserName[1]) * 123;
Result := Result + IntToStr(A) + '-';
A := ABase + (Length(AUserName) * Ord(AUserName[1])) * 6613;
Result := Result + IntToStr(A);
end
else
Result := RsError;
end;

function TJvSerialMaker.SerialIsCorrect(ABase: Integer; AUserName: string; Serial: string): Boolean;
begin
if (AUserName <> '') and (ABase <> 0) then
Result := Serial = GiveSerial(ABase, AUserName)
else
Result := False;
end;


Como se ve se parte de un número "base" que ha de ser constante tanto en el programa generador de números de serie como en el programa a "proteger"... Ambos programas, pues, comparten el código anterior. A partir del número base y de una clave de usuario, como puede verse el primer método genera un número de serie: justamente el que corresponde a la clave de usuario + número base.

Así se consigue que tanto el programa generador de números de serie como el programa a "proteger" puedan obtener de una clave de usuario un número de serie, el mismo que ha de compararse con el que el usuario introduzca. Todo esto funciona correctamente, si no fuera porque el número de serie correcto (y justo el cuando se genera en el primer método) queda en la memoria.

Y es en el primer método donde queda el número en memoria, porque puede verse que en el método el número de serie se va generando "a trocitos", que al cabo son unidos: pues bien, en la memoria aparecen todos los trocitos... e incluso el resultado final: el número de serie correcto para cualquier clave de usuario introducida.

dec
28-03-2007, 19:32:58
Hola,

Ya me estoy volviendo loco. Resulta que utilizo el "JvSerialMaker" desde hace cierto tiempo... y me ha dado por comprobar en un antiguo programa que lo utilizaba a ver si era capaz de sacar el número de serie de la memoria... ¡pero no he podido hacerlo!

Y a dios pongo por testigo que ambos programas: tanto el antiguo como el nuevo, utilizan el mismo sistema... eso sí, el antiguo utiliza el "JvSerialMaker" tal cual, y este nuevo utiliza una "adaptación" que le he hecho por mi cuenta... ¡pero los métodos para generar el número de serie y para comprobarlo son prácticamente iguales!

No cambia nada. De hecho la adaptación que he hecho de "JvSerialMaker" se limita a desligarlo de la Jedi VCL (quitando toda referencia a esta) y a retirar las propiedades del componente para quedarme sólo con los dos métodos comentados.

¿Y sin embargo en el programa antiguo no consigo ver el número de serie en la memoria y en el programa nuevo sí? ¡Esto es de locos!

Sé que no es de locos, sino todo lo contrario. Pero yo estoy cansado. Y así me las gasto en estas circustancias... :)

Aquí puede verse la adaptación de "JvSerialMaker" que llevo a cabo; compárese con el "JvSerialMaker" original... yo creo que no hay cambio que justifique que ahora el sistema se haya vuelto "inseguro"...


unit UGeneradorSeriales;

interface

type
TGeneradorSeriales = class
public
function ObtenerNumeroSerie(numeroBase:integer;
claveUsuario: string): string;

function ComprobarNumeroSerie(numeroBase: integer;
claveUsuario: string; numeroSerie: string): boolean;
end;

implementation

uses
SysUtils;

function TGeneradorSeriales.ObtenerNumeroSerie
(numeroBase: integer; claveUsuario: string): string;
var
i: integer;
begin
result := '';
if (numeroBase <> 0) and (claveUsuario <> '') then
begin
i := numeroBase * Length(claveUsuario) + Ord(claveUsuario[1]) * 156;
result := IntToStr(i);
i := numeroBase * Ord(claveUsuario[1]) * 452;
result := Result + IntToStr(i);
i := numeroBase + (Length(claveUsuario) * Ord(claveUsuario[1])) * 3674;
result := result + IntToStr(i);
result := StringReplace(result, '-', '', [rfReplaceAll]);
end;
end;

function TGeneradorSeriales.ComprobarNumeroSerie(numeroBase: integer;
claveUsuario: string; numeroSerie: string): boolean;
begin
if (numeroBase <> 0) and (claveUsuario <> '') then
result := (numeroSerie = Self.ObtenerNumeroSerie(numeroBase, claveUsuario))
else
result := false;
end;

end.

seoane
28-03-2007, 19:54:03
Bueno dec, aquí te dejo una función para comprobar el numero de serie pero sin llegar a generarlo en ningún momento. La función para generarlo sigue siendo la misma que pones en el mensaje #40, y puedes usarla tu para generar los números de serie que le mandes a los clientes.

function GetNum(var Str: String): Integer;
var
i: Integer;
begin
Result:= 0;
while TryStrToInt(Copy(Str,1,1),i) do
begin
Result:= (Result * 10) + i;
Delete(Str,1,1);
end;
Delete(Str,1,1);
end;

function CheckSerial(ABase: Integer; AUsername, ASerial: string): Boolean;
var
A: Integer;
begin
Result:= FALSE;
if (ABase <> 0) and (AUserName <> '') then
begin
A:= ABase * Length(AUserName) + Ord(AUserName[1]) * 666;
Result:= GetNum(ASerial) = A;
A := ABase * Ord(AUserName[1]) * 123;
Result:= Result and (GetNum(ASerial)= A);
A := ABase + (Length(AUserName) * Ord(AUserName[1])) * 6613;
Result:= Result and (GetNum(ASerial) = A);
end;
end;

Bueno, ahora ya no aparece el numero de serie en la memoria. Ya solo nos tiene que preocupar que nos parchee el programa algún ruso con mucho tiempo libre :D .

Neftali [Germán.Estévez]
28-03-2007, 20:06:00
Segun lo que hemos comentado, cambiando un poco la función y añadiendo un parámetro, puedes obtener el número "a trozos".
Revisa este ejemplo (http://www.megaupload.com/?d=9ADUH8QO).

La de obtener el serial quedaría así:


function GiveSerial(ABase: Integer; AUserName: string; APart:Integer): string;
const
RsError = 'Error en los parámetros.';
var
A: Integer;
begin
if (ABase <> 0) and (AUserName <> '') then
begin
if (APart = 1) then begin
A := ABase * Length(AUserName) + Ord(AUserName[1]) * 666;
Result := IntToStr(A);
Exit;
end;

if (APart = 2) then begin
A := ABase * Ord(AUserName[1]) * 123;
Result := IntToStr(A);
Exit;
end;

if (APart = 3) then begin
A := ABase + (Length(AUserName) * Ord(AUserName[1])) * 6613;
Result := IntToStr(A);
Exit;
end;
end
else
Result := RsError;
end;


Y en este caso cambiaríamos la de comprobación por algo así:


var
Str, Str2: String;
i, l1, l2:Integer;
j1, j2, j3, j4:Integer;
begin

Str := GiveSerial(123456, Edit1.Text, 1);
Str2 := Copy(Edit2.Text, 1, Length(Str));

if (Str <> Str2) then begin
MessageDlg('SN incorrecto.', mtError, [mbOK], 0);
Exit;
end;

l1 := Length(Str);
Str := GiveSerial(123456, Edit1.Text, 2);
Str2 := Copy(Edit2.Text, l1 + 2, Length(Str));

if ((StrToIntDef(Str, 0) - StrToIntDef(Str2, -99)) <> 0) then begin
MessageDlg('SN incorrecto.', mtError, [mbOK], 0);
Exit;
end;

l2 := Length(Str);
Str := GiveSerial(123456, Edit1.Text, 3);
Str2 := Copy(Edit2.Text, l1 + l2 + 3, Length(Str));

j1 := StrToIntDef(Str, 0);
j2 := StrToIntDef(Str2, -99);
j3 := (j1 DIV j2);
j4 := (j1 MOD j2);
if (j3 <> 1) or (j4 <> 0)then begin
MessageDlg('SN incorrecto.', mtError, [mbOK], 0);
Exit;
end;


MessageDlg('El programa se ha registrado correctamente.', mtInformation, [mbOK], 0);

end;


Es un ejemplo, pero fácilmente puedes generarlizarla un poco más... (lo he escrito sobre la macha y tampoco lo he pensado mucho...).
El número de serie no aparece completo, es más ni siquiera aparecen todos los trozos. Sinceramente creo que es casi imposible que alguien mirando la memoria del programa se le ocurre que 6 números que hay por ahí son parte del número de serie. Si la comprobación la cambias de orden pues mucho menos.

Otro tema es el del Debug; Eso si quieres lo dejamos para otro momento....

dec
28-03-2007, 20:20:32
Hola,

Os agradezco a todos vuestro esfuerzo, de verdad. No doy pie con bola y no es culpa vuestra, ni mucho menos. No me encuentro muy bien, a lo que se ve. Supongo que será algo pasajero, puesto que no es la primera vez que me ocurre.

Probando la última solución propuesta por Seoane (no me gusta copiar y pegar, pero, he tenido que hacerlo sin remedio) encuentro el siguiente problema:

(Nótese que el nombre de la función "CheckSerial" es ahora "ComprobarNumeroSerie" pero que no cambia más que esto)


function TGeneradorSeriales.ComprobarNumeroSerie(numeroBase: integer;
claveUsuario: string; numeroSerie: string): boolean;
var
A: Integer;
begin
Result:= FALSE;
if (numeroBase <> 0) and (claveUsuario <> '') then
begin
A:= numeroBase * Length(claveUsuario) + Ord(claveUsuario[1]) * 666;
Result:= GetNum(numeroSerie) = A;
A := numeroBase * Ord(claveUsuario[1]) * 123;
Result:= Result and (GetNum(numeroSerie) = A);
A := numeroBase + (Length(claveUsuario) * Ord(claveUsuario[1])) * 6613;
Result:= Result and (GetNum(numeroSerie) = A);
end;

if not Result then
begin
ShowMessage(Format('¿Número de serie incorrecto? %s', [numeroSerie]));
end;

end;


El caso es que incluso proporcionando un código de serie válido este resulta ser "no válido". Por tanto entramos en el "If not Result then...".

He podido comprobar que si no proporcino clave de usuario y sí únicamente el número de serie, entro en el "if not Result then..." y el mensaje muestra efectivamente el número de serie introducido.

Pero si añado la clave de usuario (como tiene que ser) junto con el número de serie, entonces tampoco valida el número (aunque sea correcto) y además el mensaje muestra el número "partido"... no completo...

Esto me supera, probablemente. No he comprobado esto último que comenta Neftalí, puesto que lo acabo de leer. :) Y ya lo que me trae de cabeza es que intente buscar el número de serie en la memoria en antiguos programas en donde hacía uso del mismo sistema... y no aparezca.

O sea, que algo he tocado... y ya se sabe que lo que funciona no debe tocarse... ¡qué sé yo!

Bueno. Os agradezco a todos de veras el esfuerzo que ponéis en tratar de echarme una mano, pero, empiezo a pensar que debo tomarme esto mucho más seriamente de lo que soy capaz ahora mismo.

Tampoco corre prisa, supongo. Voy a tratar de tomármelo con más calma a ver qué tal...

Gracias a todos monstruos. :) :D

seoane
28-03-2007, 20:32:49
:confused: No entiendo:

Si yo genero un numero de serie con la función que aparece en el mensaje #40. Obtengo esto:

Base = 1234
Usuario = Domingo
Resultado = 53926-10321176-3149022


Y usando la función que te pase si, yo hago lo siguiente, obtengo TRUE:

CheckSerial(1234,'Domingo','53926-10321176-3149022');

dec
28-03-2007, 20:42:51
Hola,

Vale Seoane.... :D

Sí; acabo de comprobar que funciona el asunto, y que el número de serie correcto no aparece en la memoria... ¡y esto quiere decir que lo he... que lo habéis conseguido! Toca ahora (para interesados) estudiar el código propuesto y saber porqué funciona y porqué antes no funcionaba...

En todo caso parece que el asunto está arreglado... en otro caso volveré a acosaros de nuevo. Gracias por todo. Si no fuera por vosotros probablemente esto me hubiera costado una úlcera (siempre quise decir algo así).

¡Muchas gracias! :) :)

PD. Cuando me asegure de que todo está en orden lo referiré de nuevo aquí, no podré evitarlo. Hasta pronto.

Por cierto... el código quedará más o menos de la siguiente forma:


unit UGeneradorSeriales;

interface

type
TGeneradorSeriales = class
public
function ObtenerNumeroSerie(numeroBase:integer;
claveUsuario: string): string;

function ComprobarNumeroSerie(numeroBase: integer;
claveUsuario: string; numeroSerie: string): boolean;
end;

implementation

uses
SysUtils;

// Made in Seoane :P (ClubDelphi.com)
function GetNum(var Str: String): Integer;
var
i: integer;
begin
result := 0;
while TryStrToInt(Copy(Str,1,1),i) do
begin
result := (result * 10) + i;
Delete(Str,1,1);
end;
Delete(Str,1,1);
end;

function TGeneradorSeriales.ObtenerNumeroSerie
(numeroBase: integer; claveUsuario: string): string;
var
A: integer;
begin
if (numeroBase <> 0) and (claveUsuario <> '') then
begin
A := numeroBase * Length(claveUsuario) + Ord(claveUsuario[1]) * 666;
result := IntToStr(A) + '-';
A := numeroBase * Ord(claveUsuario[1]) * 123;
result := result + IntToStr(A) + '-';
A := numeroBase + (Length(claveUsuario) * Ord(claveUsuario[1])) * 6613;
result := result + IntToStr(A);
end;
end;

function TGeneradorSeriales.ComprobarNumeroSerie(numeroBase: integer;
claveUsuario: string; numeroSerie: string): boolean;
var
A: integer;
begin
result := false;
if (numeroBase <> 0) and (claveUsuario <> '') then
begin
A := numeroBase * Length(claveUsuario) + Ord(claveUsuario[1]) * 666;
result := GetNum(numeroSerie) = A;
A := numeroBase * Ord(claveUsuario[1]) * 123;
result := result and (GetNum(numeroSerie) = A);
A := numeroBase + (Length(claveUsuario) * Ord(claveUsuario[1])) * 6613;
result := result and (GetNum(numeroSerie) = A);
end;
end;

end.

mamcx
28-03-2007, 20:52:38
A proposito de esto, la mejor guia (en cuanto a ser de facil digestion) de hacer programas anti-crack con Delphi:

http://www.inner-smile.com/nocrack.phtml

;)

dec
28-03-2007, 20:56:56
Hola,

Gracias Mario. Echaremos un vistazo. :)

Por cierto Seoane... te vas a partir de la risa... ¡todo va bien, pero mis clientes no podrán llamarse "raro", ni "ramon", ni por supuesto "pepe"!

No te preocupes. Ya trataré de averiguar qué ocurre para que usuarios como "domi", "seoane", "domingo seoane", generen números de serie que luego pueden validarse sin problemas (ni aparecer en memoria), pero, sin embargo, nombres de usuarios como "raro", "ramon", "pepe"... no validen...

Por eso más arriba dije que no me había funcionado... había copiado y pegado bien, pero no me funcionó con el nombre de usuario conque probé... :D :D

Me estoy volviendo loco, me estoy volviendo loco, me estoy volviendo loco, poco a poco, poco a poco... (bis) :D

Edito: Y no es para menos. Ahora resulta que el nombre de usuario "pepe" no valida, pero el número de serie sigue apareciendo en memoria... que da igual, porque no valida, pero aparece... decidme si esto es normal o qué... :D

En todo caso parece que hemos avanzado mucho (gracias Seoane) porque, efectivamente, el usuario "juan" sí valida, pero, si no se introduce un número de serie válido, valga la redundancia, el correcto (generado en el programa) no aparece en memoria.

Bueno. ¡Pues con no vender el programa a nadie que se llame "pepe" o "ramon"! :D :D :D :D

seoane
28-03-2007, 21:05:12
Por cierto Seoane... te vas a partir de la risa... ¡todo va bien, pero mis clientes no podrán llamarse "raro", ni "ramon", ni por supuesto "pepe"!


:D Por que tu lo dices, porque yo hago esto:

var
Str: string;
begin
Str:= GiveSerial(1234,'pepe');
Writeln(Str);
Writeln(CheckSerial(1234,'pepe',Str));
Readln;
end.

Y me valida de maravilla

Al igual que estos otros: raro ,ramon, ...

No sera problema del numero base que estas utilizando ??? :confused:

dec
28-03-2007, 21:17:41
Hola,

Estaba leyendo lo que ha enlazado Mario antes... ciertamente interesante.

Seoane... ¡otra vez tienes razón! (yo de ti comenzaría a mirármelo) :)

Era cuestión del número base (de cinco cifras). Cambiando el número base problema resuelto. Aunque no se trata de un programa, sino de varios programas, y por tanto hay implicados varios números base, pero cambiaré todos a cuatro cifras (todos tienen ahora cinco) y probaré a ver...

Otra vez gracias. Interesante lo que enlaza Mario, echadle un vistazo si podéis y os sentís interesados en el tema. :)

Gracias otra vez a todos y gracias otra vez Domingo. :)

dec
28-03-2007, 22:36:11
Hola,

Bueno. Otra vez yo. Quería que supiérais que ya estoy más tranquilo. Aunque no hago caso ni de la mitad de las cosas que se mencionan en el sitio Web que enlazó antes Mario, al menos el "bug" que tenía el programa que estoy llevando a cabo ya no existe, gracias a todos vosotros.

Es cierto que no tengo ahora mismo ganas de estudiar el código de Domingo Seoane, pero, supongo que cogeré esas ganas de un momento a otro... Por mi parte actualizé ya el programa, me puse en contacto con el usuario que me informó del "bug", le envié los datos de registro del programa (que se los ha ganado) y le agradecí de su útil colaboración.

Bueno. Pues eso. Que ya estoy más tranquilo. ¡Me entran ganas de ponerme a pegar tiros en el Counter Strike!... Por cierto soy "deckiller" y luego estar en "cs.rin.ru". :D

PD. Gracias otra vez a todos. :)

ArdiIIa
29-03-2007, 04:10:00
Alguien conoce o ha utilizado Ionwors (http://www.ionworx.com/solutions.html)

Echando un vistazo por Code Projec al final llegué a ese sitio...

dec
29-03-2007, 08:52:05
Hola,

Gracias ArdiIIa. Si te interesa el tema no dejes de leer el sitio Web que enlazó Mario más arriba: Anti Cracking FAQ (http://www.inner-smile.com/nocrack.phtml). Creo que ofrece ideas muy curiosas y a tener en cuenta.

También están disponibles libremente los componentes Turbo Power OnGuard (http://sourceforge.net/projects/tponguard/). No hize en su momento sino probarlos muy por encima, pero, en fin, la descripción de los componentes es bastante clara:


OnGuard is a library to create demo versions of your Borland Delphi & C++Builder applications. Create demo versions that are time-limited, feature-limited, limited to a certain number of uses, or limited to a certain # of concurrent network users.

Casimiro Notevi
29-03-2007, 11:05:58
Qué bien vendría una buena traducción de esa página 'Anti Cracking FAQ'

ArdiIIa
29-03-2007, 18:53:08
Qué bien vendría una buena traducción de esa página 'Anti Cracking FAQ'
Venga Casmimiro, no me digas que no se entiende.... (http://translate.google.com/translate?u=http%3A//www.inner-smile.com/nocrack.phtml&hl=en&langpair=en|es&tbb=1&ie=ISO-8859-1)

Casimiro Notevi
29-03-2007, 21:15:16
Venga Casmimiro, no me digas que no se entiende.... (http://translate.google.com/translate?u=http%3A//www.inner-smile.com/nocrack.phtml&hl=en&langpair=en%7Ces&tbb=1&ie=ISO-8859-1)

en fin... dije: "una buena traducción" :D

Más o menos se entiende en inglés y en la traducción de google, pero hay "detalles" que muchas veces son los más importantes y que se pierden por culpa de no conocer bien un idioma o por las traducciones automáticas.

Este año no puedo dejarlo pasar sin que me apunte a la escuela de idiomas :rolleyes:

dec
30-03-2007, 00:56:50
Hola,

¡Esa es la comunidad! ¡Doscientos traductores peleándose por complacer al compañero Casimiro -entre otros! ¡Yo, yo, yo lo traduzco! ¡No, lo traduzco yo! ¡¡Pa'qué, si ya está traducido!! Anoche que me lo propuse. ¡Jo, que tío! ¡Gracias! ¡Muchas gracias! ¡Gracias amigo! ¡Muchas gracias! ¡Qué onda guey! ¡Sos un genio! ¡Viva, viva el traductor innombrable! ¡Viva! ¡Viva! ¡Viva!

PD. También Casimiro podías apuntarte ya a la escuela de idiomas... :D :D :D

roman
30-03-2007, 01:03:13
Nah, está muy largo ese artículo. A ver Casimiro, repite conmigo:

John is a student
My name is Casimiro

:D

// Saludos

dec
30-03-2007, 01:13:02
My tailor is rich

:D :D :D :D :D

roman
30-03-2007, 01:16:58
Sigamos con la clase:

Linux sucks
Windows rocks
Bill is my friend

:D

seoane
30-03-2007, 01:28:00
Sigamos con la clase:

Linux sucks
Windows rocks
Bill is my friend

:D

:eek: :eek: NO LO HAGAS CASIMIRO ES UNA TRAMPA !!! :D

roman
30-03-2007, 01:35:03
Please, follow me, Casimiro,

I love Vista

Casimiro Notevi
30-03-2007, 01:41:21
No sé, no sé... para mí que hay gato encerrado en ese curso de roman :D




P.d.: sí, definitivamente, me hace falta un buen y rápido curso de inglés :o

mamcx
30-03-2007, 04:42:29
Lo bueno del curso de Roman es que no solo se limita a estudiar el Idioma, sino a pensar como un gringo:

Modulo 1:
Adjetivos y sustantivos

Linux sucks
Windows rocks
Bill is my friend


Modulo 2:
Uso de signos de puntuacion

Linux, ¿sucks?.
Windows, ¿rocks?.
¿Bill is my friend?.


Modulo 3:
Frases complejas con condicionales enmarcadas en contexto

Today, in Incongruent news magazine, in interview with Chocolate Jhones, Bill declare:
...........
...........
...........
Bill:

"You know the sad truth: Linux really sucks. Is not user friendly... instead
Windows rocks because is trully user friendly.

I'm the principal man behind Windows... and windows IS friendly so, you know, I'm friendly and a very cool man.

Chocolate Jhones:
"So, you are saying that Bill is my friend".

Bill:
"Of Course!"

Chocolate Jhones:
"And why is your friend-likely similar to Mac OS X?"

Bill:
"Stay in the Linux issue...please."

Chocolate Jhones:
"Doh!"


Adivinen como es el modulo 5....;)

P.D. Chanfle,le acabo de publicar a Roman el contenido del curso que hice con él hace un par de decadas... Pero paguen igual, el módulo 5 y 6 lo valen ;)