Ayuda con Scaneer Hexadecimal

Iniciado por **Aincrad**, 3 Agosto 2018, 02:12 AM

0 Miembros y 1 Visitante están viendo este tema.

**Aincrad**

Bueno como dice el titulo. necesito ayuda con un escaner hex. Bueno pondré el code y de ultimo mis preguntas!

Bueno tengo un Listbox con el sig directorio de archivos:

C:\Users\S4LXenox\Desktop\Ransomware Wannacry - copia.exe
C:\Users\S4LXenox\Desktop\eicartest.exe
C:\Users\S4LXenox\Desktop\parite.exe


y con un boton escaneo cada archivo  del listbox :

code del boton:

Código (vbnet) [Seleccionar]
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        listv.Items.Clear()
        For abc = 0 To ListBox1.Items.Count - 1

             Scan(ListBox1.Items(a).ToString)

        Next abc
    End Sub



Y aquí la Función de scaneo hex:

Código (vbnet) [Seleccionar]


Dim xml = <?xml version="1.0"?>
              <signatures>
                  <signature>
                      <name>Eicar-Test-Signature</name>
                      <hex>58354f2150254041505b345c505a58353428505e2937434329377d2445494341522d5354414e4441</hex>
                  </signature>
                  <signature>
                      <name>Hybris.Gen</name>
                      <hex>f649e7cc1e00d37e7f3bc85fff3486ac6de91433aa3a39ef1b114d37b534b8323f6ff67132638a3fe2f2afb4aaf9b7e3b4669bb3cab028298aab533c5d73546cdd396fd58c2c7734c50bca68eb709b889a086fb3db5f8ae533a4d5816e8c5f560983695efa14e291c204b1316e657773</hex>
                  </signature>
</signatures>

Private Sub Scan(ByVal dir As String)


        Dim myStreamReader As StreamReader = Nothing
        myStreamReader = File.OpenText(dir)
        InputString = myStreamReader.ReadToEnd()
        ArrayHold = Encoding.Default.GetBytes(InputString)

        Do
            IndexEnd = Index + 9

            For x As Integer = Index To IndexEnd

                If x > UBound(ArrayHold) Then
                    tempStr = tempStr
                Else
                    tStr = UCase(Convert.ToString(ArrayHold(x), 16))

                    If tStr.Length < 2 Then tStr = "0" & tStr

                    Str.Append(tStr)
                    tempStr = tempStr & Chr(ArrayHold(x))

                End If
            Next

            Index = Index + 10
        Loop While IndexEnd < UBound(ArrayHold)
        For Each signature As XElement In xml.Root.Elements
            If InStr(1, Str.ToString, signature.<hex>.Value, vbTextCompare) Then
                listv.Items.Add(signature.<name>.Value)
                If listv.Items.Count > 0 Then
                    Label1.Text = "Virus"
                Else
                    Label1.Text = "No Virus"
                End If
            End If
        Next

    End Sub





Bueno ahora mis dudas:

1) Cuando Utilizo el OPENFILEDIALOG En el botón y selecciono un virus el sacanner funciona y lo detecta! , Pero cuando coloco el directorio del virus en el listbox asi como puse arriba , el scanner no detecta nada! como corrigo eso?


2) Cuando intento escanear un archivo de mas de 10 mb tarda muchísimo! aunque eso se puede arreglar con un BackgrounWorker y asi no se pegue la app, pero no hay manera de hacer el escaneo hex mas rapido?

Bueno eso es todo, Gracias de antemano!




Serapis

#1
De entrada no dejas claro si los ficheros tienen algún formato específico o no, es adecir asumo que son tal cual señalan sus extensiones (si .exe porque es un ejecutable, etc...)

Los ejecutables típicamente no son muy grandes (es difícil encontrar uno mayor de 100Mb.), así que lee todo el contenido del fichero de una sola tacada en un array de bytes.
Puedes limitar la lectura si no precisas buscar en todo el fichero, si no solo hasta cierto punto (obviamente solo si hay una razón para ello), o también puedes desestimar ficheros más grandes de x tamaño (para leerlos al final, cuando acabes con los más pequeños), pasándolos a una segunda lista...

Si el fichero debe analizarse al completo incluso aunque se haya encontrado una signatura de virus, leerlo completo de una vez es más eficaz, pero si tras un hallazgo puede descartarse seguir buscando en el fichero, con ficheros grandes, por el contrario, sería más eficaz leer buffer a buffer (por ejemplo 1-4Mb. en cualquier caso siempre que operes con ficheros múltiplo de 64kb.), y si tras encontrar un positivo ignoras el resto del fichero y saltas al siguiente... pués así evitas más lecturas d elas necesarias.

Tu problema de rendimiento, se debe a que te emperras en operar con texto, strings, caracteres y conversiones inútiles de texto de forma constante y reconstrucción de cadenas. Lee bytes y evita conversiones y reconstrucción de cadenas, es decir opera con array de bytes.


Por ejemplo, este código es muy deficiente de cara al rendimiento:
Código (vbnet) [Seleccionar]

   tStr = UCase(Convert.ToString(ArrayHold(x), 16)) ' <--- lee del array y hace 2 conversiones (toString y Ucase)
   If tStr.Length < 2 Then tStr = "0" & tStr  ' <---- si la longitud es menor de 2 antecedes un 0, esto es reconstruyes la cadena
   Str.Append(tStr)   ' <---- reconstruyes str con append
   tempStr = tempStr & Chr(ArrayHold(x))  ' <--- reconstruyes tempstr y haces una conversióna string desde array (con la función chr).

Es un código cansinom machacante con cada byte leído...

Realizas múltiples operaciones por cada byte leído en el fichero, que resultan innecesarias si lo planteas correctamente, además "detrás-debajo", hay más trabajo aún del que te imaginas, Append y  '&' son concatenaciones que reconstruyen de nuevo la cadena. Esto es ,buscan memoria libre (del tamaño preciso) y crean una nueva cadena copiando todo el contenido previo y poniendo detrás (ahora que hay espacio libre) el contenido que se concatena... esto por cada byte leído de un fichero, es una enorme pérdida de eficiencia...

Claro, que según se ve, en el primer bucle tu objetivo es preparar la entrada conforme a un xml, para luego hacer una búsqueda de cada valor del xml sobre la entrada preparada... es decir, inicialmente el panteamiento estaría bien si fuera solo para un único fichero y no muy grande... pero no es el caso.

Aquí también puede optimizarse mucho (lo del xml), porque esto (obviando todo el trabajo previo, es decir al margen del mismo), resulta ineficiente... ya que si ordenaras las "signaturas" en el xml, cada vez que haya un positivo, implica que el punto donde se localiza en 'str', puede pasar a ser el punto de inicio de la siguientes signaturas... del mismo fichero (o buffer si lees a tramos, por eso es también más eficiente leer el fichero de una sola vez si no excesivamente grande y si los hay (demasiado grandes), dejarlos apara el final y solo en estos ser ineficientes leyendo en búferes más manejables por si la memoria disponible es crítica))... Lógicamente si un fichero solo tendrá 1 o 2 firmas positivas, no se notará en exceso el beneficio (para ese fichero)
Esto lo vamos a ver más ampliamente detallado debajo, así que tacho para evitar desviarte hacia derroteros no necesarios...


Para que esto mejore notablemente el rendimiento, ambos bucles deben ser fusionados en uno solo... pero no tan simple como eso... ya que es preferible cambiar el planteamiento.
Por tanto, lo mejor es tratar primero las signaturas en el xml. Habría que 'preparalas' y ordenarlas alfabéticamente...
Es decir el tratamiento individualizado, debe hacerse al XML (y no al revés), en vez de a cada fichero (vamos es más rápido hacer una conversión sobre 1 único fichero (el xml), que a 200 ficheros de incluso varios Mb. a hexadecimal)...

Dicho de otro modo, ignora que sea un xml, por mucho que NET tenga funciones óptimas de procesarlos, están pensados para otras cosas, no precisamente para ficheros binarios... de hecho, la idea de los xml, es procesar texto pero que un observador todavía pueda modificarlo manualmente ...por que lo puede ver y entender... aquí en cambio, se va a hacer tabajo 'sucio' donde no se deja nada a la acción externa, luego el xml, está bien para el mantenimiento de firmas, pero para la búsqueda sobre ficheros es preferible su 'reconversión a binario', y no al revés (convertir en hexadecimal todos los ficheros donde se quiere buscar)... una vez hecha esa conversión es cuando procede ordenar las entradas...

...y a partir de ahí, y estando ordenado, y colocadas las entradas en una estructura que aloja arrays de bytes, se busca el primer byte de la entrada, en el fichero de origen...

Más aún conviene meter en un único array el primer byte de cada firma (sin repetición), así puede RECORRERSE EL FICHERO FICHERO DE ORIGEN UNA ÚNICA vez para buscar todas las firmas (que cumplan una "condición"), al mismo tiempo ... y con esto llegamos a lo más óptimo que puede llegarse...

Puede darse el caso que haya tantas firmas que prácticamente todos los bytes (del 0 al 255), sean el byte inicial de alguna firma... De hecho vamos a dar por cierto que sea así, dado el caso de miles de firmas... conviene estudiar el caso para optimizar según uno u otro caso (pero consideremos el 2º)... En cualquier caso debe haber una estructura tal que así (Y YA VAMOS ORDENANDO LAS IDEAS):

1 - La primera acción al abrir la aplicación, es leer el xml, reconvertir las firmas a ASCII (desde hexadecimal si así están) y desmontarlo en un array de esta estructura...
Código (vbnet) [Seleccionar]

Structure DatosDeFirmas
   ByteInicial as byte
   Firma() as byte
   Name as string       ' solo si lo precisas, si los patrones tienen alguna info que precise ser volcada a texto (como pondré de ejemplo)...
End Structure

Sea un array de 8.000 firmas de dicha estructura (ya generado a partir del xml, lo cual se hace una única vez, es decir solo procesamos con estúpidas conversiones un único fichero, esas 8.000 firmas..., incluso aunque no sea de modo eficiente, es un solo fichero una sola vez, y salvo que sea un fichero gigante, no tendrá pérdida de rendimiento significativo generarlo incluso de forma deficiente)... Eso queda a tu esfuerzo... es manejo de texto básico.

2 - Una vez generado el array de estructuras, debe reordenarse por el campo 'firma' (que es un array de bytes)... OJO: no hablamos de ordenar el array Firma, sino el array de estructuras considerando el campo 'firma'.
En realidad resulta más sencillo primero reconvertir las firmas a cadenas de texto ASCII, luego ordenar dicho array (de strings), y ya ordenado trasvasarlo al array de estructura.
Y para no verse obligado a mantener una asociación del índice de la firma antes y después de ordenar, puedes concatenar el 'name' tras la firma, así el cambio de orden transporta consigo el resto de datos, y luego, al trasvasarlo a la estructura firma= Getbytes(x,n) y name tomará la subcadena del final...
Entonces al caso, añadir otro campo llamado Size que señale cuantos caracteres (bytes) consta la firma, resulta útil para varias cosas...

3 - Array de estructuras de la que a su vez se debe crear una tabla de búsqueda... y ahí radica la potencia de búsqueda, ya que solo precisará buscarse ciertas firmas, las que coincidan con ese byte inicial. Y todo se opera a nivel de byte con byte sobre dos arrays... esto será muy rápido...

Ésta es la última parte del tratamiento del array de firmas... crear un índice de búsqueda...
Básicamente creamos una tabla, del mismo tamaño que el array de firmas (+1) y le indicamos a cada item,
que la primera firma que empieza por un byte concreto empieza en el índice x... esto implica que todas las firmas que empiecen por el mismo byte, estarán contiguos (porque las ordenamos previamente), y por tanto en el nuevo array tblBusqueda, apuntan al índice de la firma de la primera firma que comienza con ese byte,
resumiendo todas las firmas que empiezan por el mismo byte comparten el índice de la primera firma que empeiza por ese byte, así sabremos cuando terminar de buscar otra firma...
Código (vbnet) [Seleccionar]

   dim tblBusqueda(0 to ArrayFirmas.lenght+1) as int32
   dim j as int32, k as int32
   dim ptInicio as int32

   'tblBusqueda(ArrayFirmas.lenght+1) = 0  añadir un índice más nos evitará desbordamientos y chequeos para evitarlo. Sol oprecisaremos una condición para el bucle

   ptInicio= -1  ' para forzar ya con el índice 0 la asignación de valores.

   For k = 0 to Arrayfirmas.lenght -1
       if (Arrayfirmas(k).ByteInicial<> ptInicio) then
           ptInicio =  Arrayfirmas(k).ByteInicial  
           j= k
       end if

       tblBusqueda(k) = j
   next
 

un pequeño code de como sería en este punto, la búsqueda usando dicho tabla...
- 4 Es decir el bucle principal de búsqueda una vez leído un fichero...
Código (vbnet) [Seleccionar]

   ' Cuando se genera el array de estructuras de firmas, debe calcularse este valor...
   dim firmamaslarga as byte   ' cantidad de bytes que tiene la firma más larga que contiene el xml...
   ' si este valor tuviera la posibilidad de ser mayor que byte, decláralo como short...
   ' y hay otras partes en le código que deben  cambiarse también de tipo de forma consecuente.

private sub BuscarPatronEnFile(byref Ruta as string)
  dim k as int32
  dim pt as int32
  dim fileOrg() as byte

  fileOrg = leerFichero(ruta) 'se omite esta parte y cualquier posible error de lectura, eso ya lo pones tú...  

   ' bucle principal de búsqueda una vez leído un fichero...
   for k= 0 to fileOrg.length - firmamaslarga ' para evitar desbordamientos de bufer, ni el uso de 'try... que ralenticen la operación de forma innecesaria.
        pt= tblBusqueda( fileOrg(k))
        k += BuscarPatron(fileOrg, k , pt)      ' típicamente sumará 0 si no encontró patrón de virus....
   next
End sub



5 - La función que busca el patrón recorriendo dos arrays de bytes...
Código (vbnet) [Seleccionar]


' declárese de tipo short si firmamaslarga  se declaró como short
private function BuscarPatron(byref Origen() as byte, byval Index as int32, byval IxFirma as int32)  as byte
   dim j as byte, i as byte ' short para ambos si firmamaslarga  se declaró como short
   dim k as int32
   dim f as int32 = IxFirma

   do
       j = Arrayfirmas(ixFirma).Firma.Lenght
       for i = 1 to j-1 ' el byte 0, nos trajo aquí, coincide, luego no requiere 're-visarlo'.
           if (origen(index+i) <> Arrayfirmas(ixFirma).Firma(i) then exit for
       next

       if (i=j) then  ' si se llegó hasta el final del array  implica que se encontró el patrón
           ' Añadir patrón encontrado
           listv.Items.Add(Arrayfirmas(ixFirma).Name)
           Label1.Text = listv.Items.Count.ToString & " Virus"      

           ' en teoría al encontrar una firma en los siguientes 'j' bytes, si se encontró carece de sentido buscar un patrón un byte más allá del punto de inicio de un patrón hallado, por eso se sale y se avanza esos bytes en el fichero de origen.
           Return j  ' -1? prueba...
       end if
   
       ixFirma +=1
   loop while (tblBusqueda(ixFirma).ByteInicial  = f)  ' esto es mientras la firma siguiente comience también por el mismo byte...
   
   ' ixFirma podría desbordar el array, pero como añadimos un ítem más al array, no será el caso
   ' y la condición (solo una) del bucle es suficiente, para delimitar el fin de búsqueda.
   ' Aquí es donde tiene sentido lo que se ponía más arriba...
   'tblBusqueda(ArrayFirmas.lenght+1) = 0  añadir un índice más nos evitará desbordamientos   y chequeos para evitarlo. Solo precisaremos una condición para el bucle
   
   return 0
end sub


No haré todo el código... pero lo principal está hecho...
3 - El código para crear la tabla de búsqueda,
4 - El código para buscar patrones a lo largo de un archivo.
5 - el código para buscar los patrones que puedan ser derivados del byte actual en el fichero (a falta de los cambios que tu precises).

Lo que te falta pués, tu mayor trabajo es operar el xml, para:
1 - extraer de él, las firmas convertidas a la estructura indicada y que
2 - luego deben ser reordenadas en el array de firmas...
Dentro del punto 4, falta también la lectura del fichero de origen para volcarlo en un array de bytes...

Es decir, te he aclarado, la forma a seguir para que el rendimiento sea lo más óptimo posible a la hora de buscar... es posible otras optimizaciones pero son de menor importancia, y en todo caso, habría que verlo una vez tuvieras el código...
La solución ahora podría tener más líneas de código, y quizás resulte más oscuro o menos elegante, pero sin duda será 100 veces más rápido que lo que ahora mismo tienes y que solo valdría para una pequeña cantidad de firmas...




- Algunos comentarios  aparte:
Una dudosa es que cuando encuentras una firma, sobre el texto preparado, parece bastar y no se sigue buscando si aparece más veces. Debe uno decidir si esto es conforme o no, y si lo es conviene dejar comentario del caso, por sí a futuro, relees el código y encuentras este 'defecto', que pueda observarse que es conforme y así fue decidido, y no que fue por omisión...
Esto cae en lo mismo que si sobre un fichero se encuentra un patrón de visrtus, es necesario buscar más patrones de otros virus ???... quizás como en lo reciñén comentado una vez encontrado un patrón de virus sobre un fichero, pueda saltarse al siguiente y dejar comentario pertienente al caso...
Como un falso positivo siempre se puede dar, parece razonable seguir buscando más patrones distintos de virus sobre el fichero, cuantos más patrones hallados en un fichero, más difícil será que se trate de un falso positivo...

- Luego hay cosas menores que pueden ser mejoradas....
--- 0 No veo declaraciones de tipo para diferentes variables:  IndexEnd, Index, tStr , tempStr...

--- 1 En cambio la veo para x in situ... las declaraciones in situ, son muy ´´utiles para variables que aparecen una sola vez, en un solo sitio... pero al ser un bucle, no es precisamenrte el mejor caso donde tiene sentido su forma de hacerlo, es decir en este caso es mejor declaralo al comienzo de la función.

--- 2 Esto resulta redundante:
Código (vbnet) [Seleccionar]

   If x > UBound(ArrayHold) Then
       tempStr = tempStr  ' A = A es perder el tiempo, imagina que hiciéramos lo mismo con cada variable... y cuántas veces lo tendríamos que hacer?... 0 veces está bien.
   Else
       '...
   end if


Mejor así:
Código (vbnet) [Seleccionar]

   If x <= UBound(ArrayHold) Then
       '...
   'else  ' no cambia nada que actualmente no sea lo que es... luego no precisa código para "else".
   end if


--- 3 El siguiente código, también resulta un poco redundante...
Código (vbnet) [Seleccionar]

       For Each signature As XElement In xml.Root.Elements
           If InStr(1, Str.ToString, signature.<hex>.Value, vbTextCompare) Then
               listv.Items.Add(signature.<name>.Value)
               If listv.Items.Count > 0 Then
                   Label1.Text = "Virus"
               Else
                   Label1.Text = "No Virus"
               End If
           End If
       Next


Si se encuentra un patrón, se añade, luego la cuenta ya será como mínimo 1  nunca más cero, luego huelga preguntar cada vez si es 0. Puede ponerse fuera del bucle...
Así:
Código (vbnet) [Seleccionar]

       For Each signature As XElement In xml.Root.Elements
           If InStr(1, Str.ToString, signature.<hex>.Value, vbTextCompare) Then
               listv.Items.Add(signature.<name>.Value)                
           End If
       Next

       If listv.Items.Count > 0 Then
           Label1.Text = "Virus"
       Else
           Label1.Text = "No Virus"
       End If


Bien es cierto que si un fichero fuera excesivamente grande la aparición de una firma, no se haría notar hasta completar la búsqueda por completo (con ejecutables esto sería tan poco tiempo, que no merece la pena, pero bueno supongamos un fichero gigante)... si quiere indicarse el momento exacto cuando aparece (para mantener más puntualmente informado al usuario), podría modificarse así:
Código (vbnet) [Seleccionar]

       Label1.Text = "No Virus"
       For Each signature As XElement In xml.Root.Elements
           If InStr(1, Str.ToString, signature.<hex>.Value, vbTextCompare) Then
               listv.Items.Add(signature.<name>.Value)
               Label1.Text = listv.Items.Count.ToString & " Virus"                                
           End If
       Next




p.d.: He eliminado o tachado un par de comentarios que se podrían prestar a confusión y recalcado puntos y puesto en negrita, que por ser texto plano, puede no dárseles la importancia debida y pasarse por alto.
Nota: Cuando uno escribe código sobre la marcha no repara demasiado a veces en las partes que pueden precisar un "x-1", así que no se descarta esas posibilidades de error, que sin embargo son fáciles de acometer con el entorno de desarrollo abierto...

**Aincrad**