Cómo busqué virus en un programa

Cómo busqué virus en un programa

La idea de este artículo me vino a la mente después de una discusión en un chat de Telegram. Alguien publicó un programa para cambiar el hash MD5 del archivo. Otro participante del chat verificó este programa con Virustotal y encontró 2 resultados sospechosos (y 68 seguros). Después de la verificación, este participante acusó al programa de tener una funcionalidad maliciosa (e incluso de robar contraseñas de las cuentas) y a todos los que lo instalaron, de perder algunas neuronas. Intentamos exhortarlo y explicarle que aquí pueden ocurrir falsos positivos, pero fallamos. La conversación dejó de ser adecuada y terminó.

Publicamos y traducimos este artículo con el permiso del titular de los derechos de autor. El autor es Stariy. El artículo fue publicado originalmente en Habr.

Figura 1. Virustotal

Sin embargo, yo (un participante de esta conversación) comencé a comer, respirar y dormir por este problema. Por un lado, si el antivirus encuentra algo, no hay razón para que no lo creamos, debemos verificar estos problemas. Por otro lado, estos no son los antivirus más populares, nada de qué preocuparse. Pero la pregunta más importante es:si no se detectaran problemas, ¿estaríamos tan seguros de la seguridad del programa? ¿Qué hacer en este caso? Además, me preguntaba, ¿cómo se cambia el hash MD5, agregando bytes adicionales (la forma más obvia) o haciendo algo más inteligente que esto?

Entonces, decidí verificarlo y describir mis pensamientos y acciones en este artículo. Tal vez alguien lo encuentre útil. No pretendo ser un experto, solo hurgaremos.

Inspección del programa

Entonces, tengo el archivo MD5_Hash_Changer.exe y sospecho que algo está pasando en este archivo. Primero, inspeccionémoslo con PEiD:

Figura 2. PEiD

El campo con C#/.NET implica que el programa está escrito en C#. Por lo tanto, en algunos casos se puede trabajar con el código sin desensamblador. Entonces, descargué el programa gratuito JetBrains dotPeek, que me permite obtener el código C# del archivo exe (asumiendo, por supuesto, que el programa está en C#). Luego ejecuto dotPeek en el archivo inspeccionado:

Figura 3. Inspección del programa en dotPeek

Primero, veamos la sección Metadatos e inspeccionemos las cadenas usadas que pueden contener nombres, rutas, direcciones IP y otros interesantes.

Figura 4. Recursos de cadenas en dotPeek

Si es necesario, puedo ver de inmediato dónde se usa exactamente una cadena interesante, si la hay. En mi caso, no había nada sospechoso, y pasé a la sección con código. Al final resultó que, el código fuente del programa contiene dos clases:Program y MainForm. La clase Program es bastante estándar y contiene solo el código que inicia la ventana principal de la aplicación:

using System; using System.Windows.Forms;
namespace MD5_Hash_Changer {
  internal static class Program {
    [STAThread] private static void Main() { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run((Form) new MainForm()); 
    } 
  } 
}

La clase MainForm es mucho más grande, inspeccionémosla con más detalle:

Figura 5. Código de aplicación

Aparentemente, cuando el formulario comienza a ejecutarse, la función InitializeComponent() también comienza a ejecutarse. Sin embargo, no hay nada interesante en esta función:la configuración habitual de la interfaz, la configuración de fuentes, los nombres de los botones y otras rutinas. Tuve que inspeccionar todo el código, pero no encontré indicios de actividad en la red ni intentos de acceder a archivos "superfluos" para el programa. Todo es muy transparente e ingenuo. Bueno, como no encontré código malicioso, al menos echaré un vistazo al algoritmo para entender cómo este programa cambia los archivos.

La siguiente función es responsable de esta acción:

private void changeMD5(string[] fileNames) {
  Random random = new Random();
  Thread.Sleep(1000);
  this.Invoke((Delegate) (() => this.btnStartMD5.Enabled = true));
  for (int i = 0; i < fileNames.Length; ++i) {
    if (!this.running) {
      this.Invoke((Delegate) (() => {
        this.btnStartMD5.Text = "Start Change MD5";
        this.running = false; 
      }));
      break; 
    } 
    int length1 = random.Next(2, 7);
    byte[] buffer = new byte[length1];
    for (int index = 0; index < length1; ++index)
      buffer[index] = (byte) 0;
    long length2 = new FileInfo(fileNames[i]).Length;
    if (length2 == 0L) {
      this.Invoke(
        (Delegate) (() => this.dgvMD5.Rows[i].Cells[3].Value = (object) "Empty")
      ); 
    } 
    else {
      using (FileStream fileStream = new FileStream(fileNames[i],
                                                    FileMode.Append)) 
        fileStream.Write(buffer, 0, buffer.Length);
      int bufferSize = length2 > 1048576L ? 1048576 : 4096;
      string md5hash = "";
      using (MD5 md5 = MD5.Create()) {
        using (FileStream inputStream = new FileStream(fileNames[i],
                                                       FileMode.Open,
                                                       FileAccess.Read,
                                                       FileShare.Read,
                                                       bufferSize)) 
          md5hash = BitConverter.ToString(md5.ComputeHash((Stream) inputStream))
                                .Replace("-", "");
      } 
      this.Invoke((Delegate) (() => { 
        if (this.dgvMD5.Rows[i].Cells[2].Value.ToString() != "") 
          this.dgvMD5.Rows[i].Cells[1].Value = 
            this.dgvMD5.Rows[i].Cells[2].Value;
        this.labelItem.Text = (i + 1).ToString();
        this.progressBarStatus.Value = i + 1;
        this.dgvMD5.Rows[i].Cells[2].Value = (object) md5hash;
        this.dgvMD5.Rows[i].Cells[3].Value = (object) "OK"; 
      })); 
    } 
  } 
  this.Invoke((Delegate) (() => { 
    this.btnStartMD5.Text = "Start Change MD5"; this.running = false; 
  }));
}

Como entrada, la función recibe una lista de archivos que deben procesarse. Luego, la función itera estos archivos dentro del bucle. Se genera un búfer de longitud aleatoria (de 2 a 7 bytes) para cada archivo y se rellena con ceros:

int length1 = random.Next(2, 7);
byte[] buffer = new byte[length1];
for (int index = 0; index < length1; ++index) 
  buffer[index] = (byte) 0;

Luego, este búfer se escribe al final del archivo:

using (FileStream fileStream = new FileStream(fileNames[i],
                                              FileMode.Append))
  fileStream.Write(buffer, 0, buffer.Length);

Luego se vuelve a calcular el hash MD5, pero esta vez para el archivo modificado:

using (FileStream inputStream = new FileStream(fileNames[i],
                                               FileMode.Open,
                                               FileAccess.Read,
                                               FileShare.Read,
                                               bufferSize))
  md5hash = BitConverter.ToString(md5.ComputeHash((Stream) inputStream))
                        .Replace("-", "");

Eso es todo. Aquí no pasa nada más interesante. Como puedes ver, el programa es muy trivial y cambia los archivos de alguna manera, pero... Depende de ti decidir si este programa puede serte útil.

Y finalmente, veamos qué está pasando con estos archivos. Tomemos la segunda imagen de este artículo (llamada Figura 1) y ejecutemos un programa en ella. Luego, comparemos el archivo antes del procesamiento con el archivo posterior.

Figura 6. Comparación de archivos

Primero, el tamaño del archivo procesado aumentó en 6 bytes. En segundo lugar, la captura de pantalla muestra que aparecieron 6 bytes cero al final del archivo. Obviamente, este es exactamente el mismo algoritmo que esperaba ver después de estudiar el código fuente.

Nota importante

Al final, debo señalar que la verificación que describí no puede asegurarnos al 100% si la funcionalidad maliciosa está ausente en el código. Hay formas de implementar dicha funcionalidad en el archivo exe en un nivel inferior. Es por eso que le insto a que analice el posible tráfico de red después de iniciar el programa en un espacio aislado, así como a que inspeccione minuciosamente el código ejecutado, pero esto puede requerir habilidades y experiencia específicas. Sin embargo, el algoritmo que se muestra aquí está disponible incluso para un usuario inexperto que está muy lejos de lo contrario.

Enlaces

  • https://www.jetbrains.com/decompiler/
  • https://www.virustotal.com/gui/file/59eed8eb936b73868a189c8cd26368650ae8650ce9016216f8f0b513f4660e7a
  • https://github.com/ewwink/MD5-Hash-Changer/releases