elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Introducción a Git (Primera Parte)


  Mostrar Temas
Páginas: [1]
1  Programación / Programación C/C++ / Comportamiento extraño de g++ en: 10 Agosto 2013, 01:10 am
Hola a todos,

El otro día estaba programando un cierto código con árboles y cuando compilaba el programa se comportaba de forma que no conseguía entender. Para no poner el código original, he simplificado el código lo máximo posible para que se vea el comportamiento con el código tan corto y simple como sea posible.

Ante todo, para no ir repitiéndome, comento que la entrada (vía stdin) que utilizo en los códigos que voy a poner es la string 110.

El primer código es el siguiente, que se comporta como yo esperaría:
Código
  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4.  
  5. typedef long long ll;
  6. typedef vector<int> VI;
  7.  
  8. VI fe;
  9.  
  10. int llegeix() {
  11.  char c;
  12.  cin >> c;
  13.  if (c == '0') return -1;
  14.  
  15.  int u = fe.size();
  16.  fe.push_back(42);
  17.  
  18.  int aux = llegeix();
  19.  fe[u] = aux;
  20.  
  21.  return u;
  22. }
  23.  
  24. int main() {
  25.  
  26.  llegeix();
  27.  
  28.  for (int i = 0; i < fe.size(); ++i) cerr << fe[i] << endl;
  29. }

La función llegeix (que para que tenga sentido, significa lee en catalán), simplemente guarda en el vector fe la posición del siguiente 1 leído o -1 si se lee un cero (caso en el que para).

Naturalmente, el programa imprime lo siguiente, que es correcto.
Código:
1
-1

Y aquí viene el juego de manos que convierte este código en la versión que manifiesta el comportamiento que no entiendo. Simplemente comento la línea 18 y asigno directamente al vector lo que devuelve la función:
Código
  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4.  
  5. typedef long long ll;
  6. typedef vector<int> VI;
  7.  
  8. VI fe;
  9.  
  10. int llegeix() {
  11.  char c;
  12.  cin >> c;
  13.  if (c == '0') return -1;
  14.  
  15.  int u = fe.size();
  16.  fe.push_back(42);
  17.  
  18.  //int aux = llegeix();
  19.  fe[u] = llegeix();
  20.  
  21.  return u;
  22. }
  23.  
  24. int main() {
  25.  
  26.  llegeix();
  27.  
  28.  for (int i = 0; i < fe.size(); ++i) cerr << fe[i] << endl;
  29. }

En mi ordenador, con esta versión de g++:
Código:
alex@portatil:~$ g++ --version
g++ (Debian 4.7.2-5) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Da la siguiente salida, diferente del código anterior, por motivos que no entiendo:
Código:
42
-1

He comprobado el código en IdeOne con la versión 4.8.1 que tienen actualmente y ambos códigos me dan los mismos resultados que en mi ordenador. A mi entender, dichos códigos deberían comportarse de la misma forma, ya que si bien la función recursiva modifica el vector (añade un elemento), la variable u no se ve modificada y es diferente en cada llamada de la función.

Para colmo, un amigo con Windows y Dev-C++, que creo que lleva una versión bastante antigua de g++, me dijo que al compilarlos ambos códigos daban la salida que me da a mí el primer código, es decir, la correcta.

Sinceramente no entiendo por qué ambos códigos dan resultados distintos y lo más curioso es que si el 42 que he puesto por poner algo se cambia por cualquier otro número en el segundo código, la salida del primer elemento del vector será dicho número, como si la asignación del primer elemento no se hiciera nunca.

¿Alguien puede decirme el motivo de este comportamiento?

Muchas gracias
2  Programación / Programación C/C++ / Ejemplo de Minimax: 3 en raya en: 15 Enero 2012, 23:32 pm
Esta tarde con un amigo ha salido el tema por cosas de la conversación y le he desafiado a ganar al 3 en raya (Tic-tac-toe) a una IA que hiciera para el ordenador. Tras unos 15 minutos, os dejo el código que he hecho, puesto que considero que puede ser interesante la parte del minimax para aquellos que no hayan hecho nunca uno. Los gráficos son cutres y por consola, puesto que no era el objetivo del programa hacer una interfaz bonita.
Código
  1. #include <iostream>
  2. #include <vector>
  3. #include <string.h>
  4. using namespace std;
  5.  
  6. int M[3][3];
  7.  
  8.  
  9. int fila[] = {0, 2, 2, 2, 1, 1, 1, 0, 0, 0};
  10. int col[] = {0, 0, 1, 2, 0, 1, 2, 0, 1, 2};
  11.  
  12. int incf[] = {0, 1, 1, -1};
  13. int incc[] = {1, 1, 0, 1};
  14.  
  15. int check() {
  16.    for (int i = 0; i < 3; ++i) {
  17.        for (int j = 0; j < 3; ++j) {
  18.            for (int d = 0; d < 4; ++d) {
  19.                string s;
  20.                int f = i, c = j;
  21.                for (int h = 0; h < 3; ++h) {
  22.                    if (f < 0 or f >= 3 or c < 0 or c >= 3) break;
  23.                    s += char(M[f][c] + '0');
  24.                    f += incf[d];
  25.                    c += incc[d];
  26.                }
  27.                if (s == "111") return 1;
  28.                else if (s == "222") return 2;
  29.            }
  30.        }
  31.    }
  32.    return -1;
  33. }
  34.  
  35. void draw() {
  36.    for (int i = 0; i < 50; ++i) cout << endl;
  37.    for (int i = 0; i < 3; ++i) {
  38.        for (int j = 0; j < 3; ++j) {
  39.            if (M[i][j] == 0) cout << "  ";
  40.            else if (M[i][j] == 1) cout << " X";
  41.            else cout << " O";
  42.            if (j != 2) cout << " |";
  43.        }
  44.        cout << endl;
  45.        if (i != 2) cout << "------------" << endl;
  46.    }
  47. }
  48.  
  49. int rec(int &x, int &y, int torn) {
  50.    int best = -2;
  51.    int z, t;
  52.    for (int i = 0; i < 3; ++i) {
  53.        for (int j = 0; j < 3; ++j) {
  54.            if (M[i][j] != 0) continue;
  55.            M[i][j] = torn;
  56.            if (check() == torn) {
  57.                x = i;
  58.                y = j;
  59.                M[i][j] = 0;
  60.                return 1;
  61.            }
  62.            int aux = rec(z, t, (torn == 1)?2:1);
  63.            if (aux == -2 or aux == 0) {
  64.                if (best < 0) {
  65.                    best = 0;
  66.                    x = i;
  67.                    y = j;
  68.                }
  69.            }
  70.            else if (aux == -1) {
  71.                if (best < 1) {
  72.                    best = 1;
  73.                    x = i;
  74.                    y = j;
  75.                    M[i][j] = 0;
  76.                    return best;
  77.                }
  78.            }
  79.            else if (aux == 1) {
  80.                if (best < -1) {
  81.                    best = -1;
  82.                    x = i;
  83.                    y = j;
  84.                }
  85.            }
  86.            M[i][j] = 0;
  87.        }
  88.    }
  89.    return best;
  90. }
  91.  
  92. void tira_pc() {
  93.    int x, y;
  94.    int aux = rec(x, y, 1);
  95.    M[x][y] = 1;
  96. }
  97.  
  98. void tira_jug() {
  99.    int pos = -1;
  100.    while (pos < 1) {
  101.        cin >> pos;
  102.        if (pos < 1 or pos > 9) pos = -1;
  103.        else if (M[fila[pos]][col[pos]] != 0) pos = -1;
  104.    }
  105.    M[fila[pos]][col[pos]] = 2;
  106. }
  107.  
  108. int main() {
  109.    memset(M, 0, sizeof(M));
  110.    int win = -1, torn = 0;
  111.    int qtt = 0;
  112.    draw();
  113.    while ((win = check()) < 0 and qtt < 9) {
  114.        if (torn == 1) tira_pc();
  115.        else tira_jug();
  116.        torn = 1 - torn;
  117.        draw();
  118.        ++qtt;
  119.    }
  120.    if (win == 1) cout << "Gana el ordenador" << endl;
  121.    else if (win == 2) cout << "Ganas tu" << endl;
  122.    else cout << "Empate" << endl;
  123. }

Se juega con los números:
7 8 9
4 5 6
1 2 3

El ordenador juega con X, el jugador humano con O. Empieza jugando el humano, aunque todo esto son detalles que se arreglan cambiando una o dos líneas.
3  Programación / Programación General / Números de Grundy en: 14 Enero 2012, 00:17 am
A raíz del tema que se habló sobre algoritmia, he escrito este pequeño artículo sobre un tema que considero bastante interesante. Espero que se entienda bien y a ver si sirve para interesar a gente en temas algorítmicos.

En este artículo voy a presentar lo que se conoce como nimbers o números de Grundy. El nombre nimber viene del juego del Nim, que será el primero que analizaremos. No incluiré demostraciones de los resultados que vaya poniendo, dado que no creo que muchos lectores estén familiarizados con demostraciones matemáticas y éste no pretende ser un texto técnico, sino más bien divulgativo.

Presentemos ahora el juego clásico del Nim de una pila. Se trata de un juego para dos jugadores muy tonto. Consideremos una pila con n piedras en que los jugadores van por turnos alternos. Cada turno, el jugador puede retirar una cantidad arbitraria de piedras (como mínimo una y como máximo las que queden). Cuando un jugador no puede hacer nada (no quedan piedras) pierde y el otro gana. El objetivo es dictaminar quién ganará si ambos juegan de forma óptima. En este caso es evidente: gana siempre el primer jugador excepto si hay 0 piedras inicialmente. Si hay alguna piedra, la jugada óptima consiste en retirar todas las piedras, dejando 0 al segundo, que perderá.

Si bien ese caso es muy tonto, presentemos el juego del Nim estándar. Consideremos ahora n pilas diferentes, donde la i-ésima tiene ai piedras. El juego sigue siendo por turnos para dos jugadores. En cada turno, un jugador tiene que escoger una pila que aún tenga piedras y retirar una cantidad de piedras de ella (entre 1 y la cantidad total de la pila). Si no quedan piedras, el jugador pierde. Otra vez el objetivo será estudiar quién gana en caso de que ambos jueguen óptimamente.

Teorema: Consideremos un juego del Nim con n pilas diferentes, donde la i-ésima tiene ai piedras. Jugando óptimamente ambos jugadores, el segundo jugador ganará si y sólo si a1^a2^...^an = 0, donde ^ denota la XOR bit a bit.

Corolario: Para el caso n = 2, el segundo jugador ganará si y sólo si ambas pilas tienen la misma cantidad de piedras. La estrategia óptima consiste en imitar lo que haga el jugador en el turno anterior en la pila contraria, para mantener siempre la misma cantidad en ambas pilas.

Si bien este resultado es interesante, esto puede generalizarse a otros juegos. Consideremos un juego finito por turnos donde cada jugador puede hacer los mismos movimientos y el jugador que no puede mover pierde. Por ejemplo, el ajedrez no entra en esta categoría dado que ambos jugadores no pueden hacer los mismos movimientos (cada uno mueve sólo sus fichas). Para analizar este tipo de juegos definiremos el nimber o número de Grundy de un cierto estado del juego. Para empezar, asignamos al estado en que no se puede tirar (el jugador pierde) el nimber 0.

Definición: Sea J un juego que cumple las condiciones anteriores y w un estado de ese juego. Sea W el conjunto de estados a los que se puede llegar desde w usando un sólo movimiento (es decir, un solo turno). Consideremos el nimber de cada estado de W. Se define el nimber de w como el menor número entero no negativo que no está entre esos nimbers.

Teorema: Sea J un juego que cumple las condiciones anteriores y w un estado de ese juego. Suponiendo que ambos jugadores juegan óptimamente jugando desde w, el primer jugador ganará si y sólo si el nimber de w es diferente de 0.

Considerémos ahora el siguiente juego de ejemplo. Consideremos un juego con una pila con n piedras (como en el Nim), pero en este caso cada turno se pueden retirar 1 ó 2 piedras como mucho (si las hay). Calculemos el nimber para cada n:

- Si n = 0, por definición el nimber es 0.
- Si n = 1, con un turno sólo podemos llegar a n = 0, con nimber 0. El menor entero no negativo al que no se puede llegar es 1, luego 1 es el nimber de n = 1.
- Si n = 2, con un turno se puede llegar a n = 0 y n = 1, con nimbers 0 y 1 respectivamente, luego el nimber de n = 2 es 2.
- Si n = 3, con un turno se puede llegar n = 1 y n = 2, con nimbers 1 y 2 respectivamente. En este caso el menor nimber al que no se llega es el 0, luego el nimber de n = 3 es 0 y es un estado perdedor.

Si se sigue calculando, se ve que la secuencia de nimbers es 0, 1, 2, 0, 1, 2, 0, 1, 2... en la que el patrón es evidente, dado que el juego es muy simple.

Sin embargo, ¿qué pasa si en vez de un juego tenemos dos o más? Pues que el nimber también se puede calcular igual. Supongamos que tenemos un juego J formado por varios juegos J1, J2, ..., Jn en que cada turno el jugador elige uno de los juegos y hace un movimiento. En este caso un estado w está formado por los subestados w1, w2, ..., wn de cada uno de los juegos.

Teorema: En el caso anterior, si N(wi) denota el nimber del i-ésimo subestado, el nimber de w es N(w1)^N(w2)^...^N(wn).

Por ejemplo, consideremos un juego en el que se tiene una circumferencia y sobre ella n puntos distintos. En cada turno, el jugador debe unir dos puntos distintos por un segmento, de forma que no se cruce con ninguno que se haya hecho anteriormente y sin usar un punto más de una vez. En este juego, es fácil ver con un pequeño dibujo que si se unen dos puntos consecutivos, se obtiene el mismo juego con n - 2 puntos. En caso contrario, se obtienen dos juegos diferentes (porque los segmentos no pueden cruzarse): uno tendrá k puntos y el otro n - k - 2. Sin embargo, podemos calcular el nimber de un estado usando el teorema anterior. Pongo un pequeño código en C++ que dada una k calcula el nimber de los juegos para todo n <= k, si no he cometido ningún error:
Código
  1. #include <iostream>
  2. #include <vector>
  3. #include <set>
  4. using namespace std;
  5.  
  6. int grundy(int n, vector<int>& nimber) {
  7.    if (n == 0 or n == 1) return 0; //No hay movimientos posibles
  8.    if (nimber[n] == -1) {
  9.        set<int> A; //Guardare aqui los nimbers a los que se puede llegar
  10.  
  11.        //Itero sobre la cantidad de puntos que dejo en el primer subjuego
  12.        for (int i = 0; i <= n - 2; ++i) A.insert(grundy(i, nimber)^grundy(n - i - 2, nimber));
  13.  
  14.        //Busco el primero que no este, empezando por 0
  15.        nimber[n] = 0;
  16.        while (A.count(nimber[n])) ++nimber[n];
  17.    }
  18.    return nimber[n];
  19. }
  20.  
  21. int main() {
  22.    int k;
  23.    cin >> k;
  24.    vector<int> nimber(k + 1, -1);
  25.    for (int i = 0; i <= k; ++i) cout << "N[" << i << "] = " << grundy(i, nimber) << endl;
  26. }

Como ejercicio para practicar, probad de hacer este problema de la UVa (podéis enviarlo para su corrección automática, es un juez online):
http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=27&page=show_problem&problem=2529
4  Programación / Ejercicios / Programación algorítmica: unos problemillas en: 6 Septiembre 2008, 03:03 am
A raíz de ver los problemas de ohk, me ha gustado la idea y voy a poner también algunos problemas, al mismo estilo de entrada-salida que en los concursos de programación.

Por ahora pondré un par, uno facilísimo (triangulos.pdf) y otro algo más complejo (minas.pdf), si veo que gusta ya me inventaré más de diferentes tipos.

Los pongo adjuntos en pdf. Sobre el primero no hay nada que decir, es muy sencillo, respecto al segundo, como pone en el enunciado, no se puede hacer recursivamente puesto que es muy lento, buscad otro tipo de solución más dinámica xDD

En cuanto a los lenguajes, que cada uno lo haga con aquel que use y lo postee, puesto que así no queda limitado a ningún lenguaje, poniendo comentarios para que el resto podamos entender que hace su programa si no conocemos el lenguaje. La solución la daré si es necesaria en pseudocódigo.

Un saludo de ghastlyX ;)
5  Seguridad Informática / Seguridad / Creación de vacunas en C/C++ en: 17 Marzo 2008, 15:33 pm
Últimamente cada vez abundan más los gusanos que se propagan por MSN, por lo que me he decidido a escribir este manual sobre como programar vacunas específicas para este tipo de malware (aunque se puede extrapolar a otros). Mi objetivo es que haya más gente con capacidad de lanzar vacunas para estos gusanos cutres pero que se propagan como la espuma.

El lenguaje de programación que usaremos será C/C++ con el compilador Dev-C++, utilizando APIs. Si no sabéis que son preguntad a Google o a Wikipedia. Empezaré desde cero y ya adelanto que no voy a hacer un tutorial demasiado avanzado, puesto que quiero que lo entienda todo el mundo. Las APIs que use las explicaré por encima, para una mayor explicación podéis consultar en MSDN:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winprog/winprog/functions_in_alphabetical_order.asp

Análisis del código maligno
Bien, explicaré el método más sencillo. Este consiste en utilizar una máquina virtual y con un programa llamado Regshot mirar que cambios ha realizado en el sistema tras su ejecución. No es el mejor método porque un programa bien hecho puede evitar ser registrado por programas así, pero es mucho más sencillo que el otro método (desensamblarlo) y como este manual pretende ser básico, será el método que usaremos.

Lo primero de todo será instalar un programa para crear máquinas virtuales, yo personalmente uso para eso VMWare. No voy a explicar como se crea una máquina virtual, para eso mirad el siguiente enlace:
http://foro.elhacker.net/index.php/topic,158384.0.html

Una vez tenemos nuestra máquina virtual con el sistema operativo instalado (Windows) ya tan sólo hay que deshabilitar las conexiones (no queremos que se propague el malware ni que ataque a otros sitios mientras lo analizamos) y cerrar todo programa no imprescindible. Hacemos una primera foto con Regshot y luego otra después de haber ejecutado el malware. Entonces hacemos que compare y tendremos nuestra lista de cambios :)

Comencemos a programar
Antes que nada tenéis que tener instalado Dev-C++ o vuestro compilador si usáis otro, una vez hecho, vayamos al grano. Todo programa en C/C++ se divide en funciones, que son trozos de código a los que se puede llamar. Pueden devolver una salida o no (void). En C/C++ hay una función imprescindible que es main, aquella por la que empieza el programa cuando lo ejecutamos. Tiene la siguiente estructura:
Código
  1. int main()
  2. {
  3.    ...
  4. }

La función main es la que menos utilizaremos, puesto que borraremos el gusano con funciones aparte, que llamaremos desde el main. Será necesario utilizar APIs, por lo que habrá que incluir el header (archivo de cabecera) que permite usarlas, que es windows.h. También queremos comunicarnos con el usuario, por lo que hace falta stdio.h (Standard Input/Output). También nos hará falta Tlhelp32.h y ctype.h (por la función toupper). Para incluir headers hay que poner las siguientes líneas al principio del código, antes que ninguna función:
Código
  1. #include <stdio.h>
  2. #include <windows.h>
  3. #include <Tlhelp32.h>
  4. #include <ctype.h>

Matando procesos y eliminando archivos concretos
Para poder eliminar un archivo que se está ejecutando, es necesario matar antes su proceso. Para ello haremos una función que llamaremos KillProcess, que recibirá como entrada una cadena con el nombre del archivo al que hay que matar el proceso. Las funciones (excepto main) se declaran antes de poner el código de otra función y después de los headers incluidos.

Como salida nuestra función devolvera un entero (un número) indicando si ha tenido éxito o no. Para declarar la función habría que usar la sentencia siguiente:
Código
  1. int KillProcess(LPCTSTR lpfilename);

Luego también tendríamos que poner la función en sí, por lo que hasta ahora tendríamos que tener el código así:
Código
  1. #include <stdio.h>
  2. #include <windows.h>
  3. #include <Tlhelp32.h>
  4. #include <ctype.h>
  5.  
  6. int KillProcess(LPCTSTR lpfilename);
  7.  
  8. int KillProcess(LPCTSTR lpfilename)
  9. {
  10.       ...
  11. }
  12.  
  13. int main()
  14. {
  15.    ...
  16. }
  17.  

Ahora veamos como matar el proceso de un archivo. Hay que declarar una serie de variables que usaremos:
Código
  1.     WIN32_FIND_DATA Win32FindData;
  2.     HANDLE handle;
  3.     DWORD exitcode;
  4.     PROCESSENTRY32 pe32;
Estas son las que necesitaremos para poder matar el proceso. No hace falta que los nombres de variable (la segunda palabra) sea la misma, estos son los que usaré yo aquí. Los tipos de variable no los podéis cambiar y tened en cuenta que C/C++ distingue mayúsculas de minúsculas.

Ahora tenemos que hacer como si fuera una foto de todos los procesos que se ejecutan, para ello usaremos la API CreateToolhelp32Snapshot, que tiene la siguiente forma:
Código
  1. HANDLE WINAPI CreateToolhelp32Snapshot(
  2.  __in  DWORD dwFlags,
  3.  __in  DWORD th32ProcessID
  4. );

Luego recorremos los procesos con las APIs Process32First (para el primero) y Process32Next (para los siguientes). Tienen la siguiente forma:
Código
  1. BOOL WINAPI Process32First(
  2.  __in     HANDLE hSnapshot,
  3.  __inout  LPPROCESSENTRY32 lppe
  4. );
  5.  
  6. BOOL WINAPI Process32Next(
  7.  __in   HANDLE hSnapshot,
  8.  __out  LPPROCESSENTRY32 lppe
  9. );

Harán falta otras APIs:
Código
  1. //Cierra un handle abierto
  2. BOOL WINAPI CloseHandle(
  3.  __in  HANDLE hObject
  4. );
  5.  
  6. //Abre un proceso
  7. HANDLE WINAPI OpenProcess(
  8.  __in  DWORD dwDesiredAccess,
  9.  __in  BOOL bInheritHandle,
  10.  __in  DWORD dwProcessId
  11. );
  12.  
  13. //Devuelve el estado de finalización del proceso
  14. BOOL WINAPI GetExitCodeProcess(
  15.  __in   HANDLE hProcess,
  16.  __out  LPDWORD lpExitCode
  17. );
  18.  
  19. //Termina un proceso y sus threads
  20. BOOL WINAPI TerminateProcess(
  21.  __in  HANDLE hProcess,
  22.  __in  UINT uExitCode
  23. );
Para hacer todo esto, traducido a código hay que hacer lo siguiente:
Código
  1.     handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);  //Guardamos en nuestra variable de tipo HANDLE la "foto"
  2.     if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR;    //Si no es correcta la "foto" se acaba la funcion y se devuelve un error          
  3.     pe32.dwSize = sizeof(PROCESSENTRY32); //Definimos la medida de nuestra estructura PROCESSENTRY32
  4.     if(!Process32First(handle, &pe32)) //Si hay un error al mirar el primer proceso
  5.     {
  6.          CloseHandle(handle); //Cierra el HANDLE que usamos
  7.          return ERROR_PROCESO; //Acaba la funcion devolviendo un error
  8.     }
  9.     int i; //Declaramos un par de variables más que hacen falta para pasar a mayusculas dos cadenas
  10.     char lpfilename2[strlen(lpfilename) + 1];
  11.     strcpy(lpfilename2, lpfilename);
  12.     for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); //Pasamos a mayúsculas las dos cadenas a comparar para que si no son exactamente iguales en cuanto a mayusculas o minusculas, también mate el proceso
  13.     for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]);
  14.     while(strcmp(pe32.szExeFile,lpfilename2))  /Mientras el proceso no coincida con el que buscamos...
  15.     {                                    
  16.          if(!Process32Next(handle, &pe32))  //Va mirando el siguiente, acabando la funcion si hay errores
  17.          {
  18.                  CloseHandle(handle);
  19.                  return ERROR_PROCESO;
  20.          }
  21.          for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]); //Pasa a mayusculas por lo que he dicho antes
  22.     }
  23. /**Una vez hemos encontrado el proceso**//
  24.     handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);  //Abrimos el proceso
  25.     if(handle == NULL) //Si sale mal, damos error y salimos de la funcion
  26.     {
  27.               CloseHandle(handle);
  28.               return ERROR_PROCESO;
  29.     }
  30.     else           //Si no sale mal...                              
  31.     {
  32.          GetExitCodeProcess(handle, &exitcode); //Cogemos el exitcode del proceso y lo guardamos en nuestra variable destinada a ese cometido
  33.          TerminateProcess(handle, exitcode); //Matamos el proceso
  34.          CloseHandle(handle);  //Cerramos el HANDLE que habiamos usado
  35.          return 0; //Devolvemos el valor 0, que es el que indicara que todo va bien y acaba la funcion
  36.     }

Como ya supongo que habréis supuesto, return se utiliza para terminar la función y devolver un valor. La doble barra sirve para introducir comentarios de una línea y también son comentario todo aquello entre /* y */. Además, también podéis ver que excepto el cero del final, los return devuelven cosas como ERROR_PROCESO. Esto sirve para llamar de una forma más recordable a una salida, en vez de usar un número. Para usar estos nombres, hay que definirlos antes,  en la zona de los headers. Veamos todo lo que llevamos de código, incluyendo estas definiciones:
Código
  1. #define ERROR_LISTAR 1
  2. #define ERROR_PROCESO 2
  3.  
  4. #include <stdio.h>
  5. #include <windows.h>
  6. #include <Tlhelp32.h>
  7. #include <ctype.h>
  8.  
  9. int KillProcess(LPCTSTR lpfilename);
  10.  
  11. int KillProcess(LPCTSTR lpfilename)
  12. {
  13.     WIN32_FIND_DATA Win32FindData;
  14.     HANDLE handle;
  15.     DWORD exitcode;
  16.     PROCESSENTRY32 pe32;
  17.     handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  18.     if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR;              
  19.     pe32.dwSize = sizeof(PROCESSENTRY32);
  20.     if(!Process32First(handle, &pe32))
  21.     {
  22.          CloseHandle(handle);
  23.          return ERROR_PROCESO;
  24.     }
  25.     int i;
  26.     char lpfilename2[strlen(lpfilename) + 1];
  27.     strcpy(lpfilename2, lpfilename);
  28.     for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  29.     for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]);
  30.     while(strcmp(pe32.szExeFile,lpfilename2))
  31.     {                                    
  32.          if(!Process32Next(handle, &pe32))
  33.          {
  34.                  CloseHandle(handle);
  35.                  return ERROR_PROCESO;
  36.          }
  37.          for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  38.     }
  39.     handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
  40.     if(handle == NULL)
  41.     {
  42.               CloseHandle(handle);
  43.               return ERROR_PROCESO;
  44.     }
  45.     else                                          
  46.     {
  47.          GetExitCodeProcess(handle, &exitcode);
  48.          TerminateProcess(handle, exitcode);
  49.          CloseHandle(handle);
  50.          return 0;
  51.     }
  52. }
  53.  
  54. int main()
  55. {
  56.    ...
  57. }

Bueno, ahora que ya está hecha la función de matar el proceso, vayamos con la de borrar el archivo. Usaremos una función con la misma estructura, recibe como parámetro un archivo y de salida devuelve un entero. Para seguir poniendo de manifiesto mi gran originalidad, la llamaremos FileDelete. La declaración que habría que poner junto con la de la otra función, quedaría así:
Código
  1. int FileDelete(LPCTSTR lpfilename);

Para la función es muy sencillo, tan sólo hay que ver una API, que es DeleteFile:
Código
  1. BOOL WINAPI DeleteFile(
  2.  __in  LPCTSTR lpFileName
  3. );

El código de la función quedaría así:
Código
  1. int FileDelete(LPCTSTR lpfilename)
  2. {
  3.     return DeleteFile(lpfilename);
  4. }

Y el código total por ahora:
Código
  1. #define ERROR_LISTAR 1
  2. #define ERROR_PROCESO 2
  3.  
  4. #include <stdio.h>
  5. #include <windows.h>
  6. #include <Tlhelp32.h>
  7. #include <ctype.h>
  8.  
  9. int KillProcess(LPCTSTR lpfilename);
  10. int FileDelete(LPCTSTR lpfilename);
  11.  
  12. int KillProcess(LPCTSTR lpfilename)
  13. {
  14.     WIN32_FIND_DATA Win32FindData;
  15.     HANDLE handle;
  16.     DWORD exitcode;
  17.     PROCESSENTRY32 pe32;
  18.     handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  19.     if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR;              
  20.     pe32.dwSize = sizeof(PROCESSENTRY32);
  21.     if(!Process32First(handle, &pe32))
  22.     {
  23.          CloseHandle(handle);
  24.          return ERROR_PROCESO;
  25.     }
  26.     int i;
  27.     char lpfilename2[strlen(lpfilename) + 1];
  28.     strcpy(lpfilename2, lpfilename);
  29.     for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  30.     for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]);
  31.     while(strcmp(pe32.szExeFile,lpfilename2))
  32.     {                                    
  33.          if(!Process32Next(handle, &pe32))
  34.          {
  35.                  CloseHandle(handle);
  36.                  return ERROR_PROCESO;
  37.          }
  38.          for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  39.     }
  40.     handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
  41.     if(handle == NULL)
  42.     {
  43.               CloseHandle(handle);
  44.               return ERROR_PROCESO;
  45.     }
  46.     else                                          
  47.     {
  48.          GetExitCodeProcess(handle, &exitcode);
  49.          TerminateProcess(handle, exitcode);
  50.          CloseHandle(handle);
  51.          return 0;
  52.     }
  53. }
  54.  
  55. int FileDelete(LPCTSTR lpfilename)
  56. {
  57.     return DeleteFile(lpfilename);
  58. }
  59.  
  60. int main()
  61. {
  62.    ...
  63. }

Realmente la función FileDelete es inútil, podríamos llamar directamente a la API, que es lo único que hace la función. Si no hago esto es porque más adelante (en próximos capítulos xD) ampliaremos esta función.


Borrando valores del registro
Este será el último apartado de este capítulo, el borrado de valores del registro, para por ejemplo borrar una entrada que pueda usar un gusano para autoejecutarse al inicio. Llamaremos a nuestra función RegKill, que será así:
Código
  1. long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName);

Devolverá un entero largo y como parámetros recibe un HKEY, que será en este caso  HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE o HKEY_USERS, que son valores predefinidos; como segundo parámetro una cadena que indica la ruta de la clave donde está el valor a borrar y como tercer parámetro otra cadena que indica el nombre del valor a borrar.

Usaremos tres APIs para esta función. La primera es RegOpenKeyEx, que abre una clave del registro y tiene la siguiente forma:
Código
  1. LONG WINAPI RegOpenKeyEx(
  2.  __in        HKEY hKey,
  3.  __in_opt    LPCTSTR lpSubKey,
  4.  __reserved  DWORD ulOptions,
  5.  __in        REGSAM samDesired,
  6.  __out       PHKEY phkResult
  7. );

La segunda API es RegDeleteValue, que elimina un valor de una clave del registro y tiene la siguiente forma:
Código
  1. LONG WINAPI RegDeleteValue(
  2.  __in      HKEY hKey,
  3.  __in_opt  LPCTSTR lpValueName
  4. );

La última es RegCloseKey, que cierra el handle abierto y tiene esta forma:
Código
  1. LONG WINAPI RegCloseKey(
  2.  __in  HKEY hKey
  3. );

El código de la función quedaría así:
Código
  1. long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName)
  2. {
  3.     HKEY hregkey; //Definimos la variable donde guardaremos la ruta entera de la clave
  4.     long vuelta; //Definimos la variable donde guardaremos el valor a devolver
  5.     if(RegOpenKeyEx(hkey, lpSubKey, 0, KEY_ALL_ACCESS, &hregkey) == ERROR_SUCCESS) //Si consigue abrir la clave...
  6.     {
  7.          vuelta = RegDeleteValue(hregkey, lpValueName); //Borra el valor de la clave y guarda el resultado en "vuelta"
  8.          RegCloseKey(hregkey);  //Cierra el handle que hemos usado
  9.          return vuelta; //Devuelve "vuelta"
  10.     }
  11.     else return ERROR_OPEN_REG;  //Si no consigue abrir la clave, devuelve este error
  12. }

Hace falta definir al inicio ERROR_OPEN_REG. Definiendo eso e incorporando el código, queda así:
Código
  1. #define ERROR_LISTAR 1
  2. #define ERROR_PROCESO 2
  3. #define ERROR_OPEN_REG 3
  4.  
  5. #include <stdio.h>
  6. #include <windows.h>
  7. #include <Tlhelp32.h>
  8. #include <ctype.h>
  9.  
  10. int KillProcess(LPCTSTR lpfilename);
  11. int FileDelete(LPCTSTR lpfilename);
  12. long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName);
  13.  
  14. int KillProcess(LPCTSTR lpfilename)
  15. {
  16.     WIN32_FIND_DATA Win32FindData;
  17.     HANDLE handle;
  18.     DWORD exitcode;
  19.     PROCESSENTRY32 pe32;
  20.     handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  21.     if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR;              
  22.     pe32.dwSize = sizeof(PROCESSENTRY32);
  23.     if(!Process32First(handle, &pe32))
  24.     {
  25.          CloseHandle(handle);
  26.          return ERROR_PROCESO;
  27.     }
  28.     int i;
  29.     char lpfilename2[strlen(lpfilename) + 1];
  30.     strcpy(lpfilename2, lpfilename);
  31.     for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  32.     for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]);
  33.     while(strcmp(pe32.szExeFile,lpfilename2))
  34.     {                                    
  35.          if(!Process32Next(handle, &pe32))
  36.          {
  37.                  CloseHandle(handle);
  38.                  return ERROR_PROCESO;
  39.          }
  40.          for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  41.     }
  42.     handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
  43.     if(handle == NULL)
  44.     {
  45.               CloseHandle(handle);
  46.               return ERROR_PROCESO;
  47.     }
  48.     else                                          
  49.     {
  50.          GetExitCodeProcess(handle, &exitcode);
  51.          TerminateProcess(handle, exitcode);
  52.          CloseHandle(handle);
  53.          return 0;
  54.     }
  55. }
  56.  
  57. int FileDelete(LPCTSTR lpfilename)
  58. {
  59.     return DeleteFile(lpfilename);
  60. }
  61.  
  62. long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName)
  63. {
  64.     HKEY hregkey;
  65.     long vuelta;
  66.     if(RegOpenKeyEx(hkey, lpSubKey, 0, KEY_ALL_ACCESS, &hregkey) == ERROR_SUCCESS)
  67.     {
  68.          vuelta = RegDeleteValue(hregkey, lpValueName);
  69.          RegCloseKey(hregkey);
  70.          return vuelta;
  71.     }
  72.     else return ERROR_OPEN_REG;
  73. }
  74.  
  75. int main()
  76. {
  77.    ...
  78. }

Ahora que ya tenemos las funciones, falta ver que poner en el main. Esto variará según qué malware queramos eliminar. Imaginemos que tenemos un malware que crea el archivo siguiente:
Código:
C:\gusano.exe

Y se autoejecuta a cada inicio con la siguiente clave:
Código:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\Soy un gusano malo y propagador

La función main deberá llamar a las tres funciones que hemos hecho: primero tendrá que matar el proceso, luego eliminar el archivo y después borrar el valor del registro. Quedaría así nuestro main de forma cutre:
Código
  1. int main()
  2. {
  3.    KillProcess("gusano");
  4.    FileDelete("C:\\gusano");
  5.    RegKill(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Soy un gusano malo y propagador");
  6.    return 0;
  7. }

Pero si nos hemos molestado en poner diferentes valores a las salidas es para tenerlos en cuenta, no para pasar de ellos. Hagamos un main que mire también como les va a nuestras funciones xDD:
Código
  1. int main()
  2. {
  3.    long salida;
  4.  
  5.    salida = KillProcess("gusano.exe");
  6.    if(salida == ERROR_LISTAR) printf("Error al listar los procesos\n");
  7.    else if((salida == ERROR_PROCESO) && (GetLastError() == ERROR_NO_MORE_FILES)) printf("El proceso no existe\n");
  8.    else if(salida == ERROR_PROCESO) printf("Error al manejar los procesos\n");
  9.    else printf("Proceso finalizado correctamente\n");
  10.  
  11.    salida = FileDelete("C:\\gusano.exe");
  12.    if(salida == ERROR_FILE_NOT_FOUND) printf("Archivo no encontrado\n");
  13.    else if(salida == ERROR_ACCESS_DENIED) printf("Acceso denegado\n");
  14.    else if(salida) printf("Archivo eliminado correctamente\n");
  15.    else printf("Error al eliminar el archivo\n");
  16.  
  17.    salida = RegKill(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Soy un gusano malo y propagador");
  18.    if(salida == ERROR_OPEN_REG) printf("Error al abrir el registro\n");
  19.    else if(salida == ERROR_SUCCESS) printf("Valor borrado del registro\n");
  20.    else printf("Error al borrar valor del registro\n");
  21.  
  22.    return 0;
  23. }
El hecho de que las contrabarras aparezcan duplicadas es porque la contrabarra se usa en C/C++ para las secuencias de escape (\n simboliza un salto de línea). Para poner una contrabarra, se ponen dos. Y el código entero sería así:
Código
  1. #define ERROR_LISTAR 1
  2. #define ERROR_PROCESO 2
  3. #define ERROR_OPEN_REG 3
  4.  
  5. #include <stdio.h>
  6. #include <windows.h>
  7. #include <Tlhelp32.h>
  8. #include <ctype.h>
  9.  
  10. int KillProcess(LPCTSTR lpfilename);
  11. int FileDelete(LPCTSTR lpfilename);
  12. long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName);
  13.  
  14. int KillProcess(LPCTSTR lpfilename)
  15. {
  16.     WIN32_FIND_DATA Win32FindData;
  17.     HANDLE handle;
  18.     DWORD exitcode;
  19.     PROCESSENTRY32 pe32;
  20.     handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  21.     if(handle == INVALID_HANDLE_VALUE) return ERROR_LISTAR;              
  22.     pe32.dwSize = sizeof(PROCESSENTRY32);
  23.     if(!Process32First(handle, &pe32))
  24.     {
  25.          CloseHandle(handle);
  26.          return ERROR_PROCESO;
  27.     }
  28.     int i;
  29.     char lpfilename2[strlen(lpfilename) + 1];
  30.     strcpy(lpfilename2, lpfilename);
  31.     for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  32.     for(i = 0; lpfilename2[i] != '\0'; i++) lpfilename2[i] = toupper(lpfilename2[i]);
  33.     while(strcmp(pe32.szExeFile,lpfilename2))
  34.     {                                    
  35.          if(!Process32Next(handle, &pe32))
  36.          {
  37.                  CloseHandle(handle);
  38.                  return ERROR_PROCESO;
  39.          }
  40.          for(i = 0; pe32.szExeFile[i] != '\0'; i++) pe32.szExeFile[i] = toupper(pe32.szExeFile[i]);
  41.     }
  42.     handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
  43.     if(handle == NULL)
  44.     {
  45.               CloseHandle(handle);
  46.               return ERROR_PROCESO;
  47.     }
  48.     else                                          
  49.     {
  50.          GetExitCodeProcess(handle, &exitcode);
  51.          TerminateProcess(handle, exitcode);
  52.          CloseHandle(handle);
  53.          return 0;
  54.     }
  55. }
  56.  
  57. int FileDelete(LPCTSTR lpfilename)
  58. {
  59.     return DeleteFile(lpfilename);
  60. }
  61.  
  62. long RegKill(HKEY hkey, LPCTSTR lpSubKey, LPCTSTR lpValueName)
  63. {
  64.     HKEY hregkey;
  65.     long vuelta;
  66.     if(RegOpenKeyEx(hkey, lpSubKey, 0, KEY_ALL_ACCESS, &hregkey) == ERROR_SUCCESS)
  67.     {
  68.          vuelta = RegDeleteValue(hregkey, lpValueName);
  69.          RegCloseKey(hregkey);
  70.          return vuelta;
  71.     }
  72.     else return ERROR_OPEN_REG;
  73. }
  74.  
  75. int main()
  76. {
  77.    long salida;
  78.  
  79.    salida = KillProcess("gusano.exe");
  80.    if(salida == ERROR_LISTAR) printf("Error al listar los procesos\n");
  81.    else if((salida == ERROR_PROCESO) && (GetLastError() == ERROR_NO_MORE_FILES)) printf("El proceso no existe\n");
  82.    else if(salida == ERROR_PROCESO) printf("Error al manejar los procesos\n");
  83.    else printf("Proceso finalizado correctamente\n");
  84.  
  85.    salida = FileDelete("C:\\gusano.exe");
  86.    if(salida == ERROR_FILE_NOT_FOUND) printf("Archivo no encontrado\n");
  87.    else if(salida == ERROR_ACCESS_DENIED) printf("Acceso denegado\n");
  88.    else if(salida) printf("Archivo eliminado correctamente\n");
  89.    else printf("Error al eliminar el archivo\n");
  90.  
  91.    salida = RegKill(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "Soy un gusano malo y propagador");
  92.    if(salida == ERROR_OPEN_REG) printf("Error al abrir el registro\n");
  93.    else if(salida == ERROR_SUCCESS) printf("Valor borrado del registro\n");
  94.    else printf("Error al borrar valor del registro\n");
  95.  
  96.    return 0;
  97. }

Con esto doy por acabado este primer capítulo, escribiré el siguiente cuando el tiempo lo permita, en el que explicaré como hacer más cosas. Mañana si tengo tiempo pongo un ejercicio para que podáis practicar lo aprendido.

Como deberes os digo que en la función de matar procesos hay un fallo, que no es explotable ni nada, simplemente que algunos procesos según la entrada que demos no los matará. Esto para los que sepan más de C/C++.

Este texto puede ser publicado en cualquier sitio siempre que no se modifique y se cite al autor y la fuente.

Un saludo de ghastlyX ;)
6  Seguridad Informática / Seguridad / Nueva encuesta: Mejor Firewall en: 30 Agosto 2007, 15:04 pm
Con tal de hacer que los resultados sean fiables y actuales, se crea una nueva encuesta y la anterior se despega. Todos a votar y opinar ;D y mirad también este enlace.

Firewalls' ratings
http://www.matousec.com/projects/firewall-challenge/results.php

Un saludo de ghastlyX ;)
7  Seguridad Informática / Seguridad / Nueva encuesta: Mejor Antivirus en: 30 Agosto 2007, 14:53 pm
Para que los resultados sean lo más fiables posibles, se despega la anterior encuesta y se pone esta, con muchos más antivirus, para que la opción "Otros" no se tenga que utilizar casi nada. Todos a votar y dar su opinión ;D

Un saludo de ghastlyX ;)
Páginas: [1]
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines