diff --git a/README.md b/README.md index 94f6235..5463045 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,7 @@ Factual-rules-generator is an open source project which aims to generate yara ru ## Python Dependencies - pefile -- flask -- ast - psutil -- requests @@ -20,6 +17,13 @@ If scripts are run under a Windows machine, some tools are required: - xxd : https://www.vim.org/download.php - cut : http://unxutils.sourceforge.net/ +- sed : http://unxutils.sourceforge.net/ +- curl : https://curl.se/windows/ + + + +- SDelete : https://docs.microsoft.com/en-us/sysinternals/downloads/sdelete +- AsA (AttackSurfaceAnalyzer) : https://github.com/microsoft/AttackSurfaceAnalyzer @@ -27,30 +31,36 @@ If scripts are run under a Windows machine, some tools are required: - Install all python dependencies find in requirements.txt +- Create a share folder to communicate with VM + - Install a Windows VM - Install chocolatey on Windows VM: https://docs.chocolatey.org/en-us/choco/setup + - Complete `bin/OnWindows/Varclient.py` + - Change `bin/OnWindows/client.py` in an exe and put in startup folder -- If use a Linux VM, install it +- If use a Linux VM, install it and: - put `bin/OnLinux/get_Fls_Strings.py` in Linux VM and the script need to be run on startup + - In `bin/OnLinux/get_Fls_Strings.py` the path to the share folder need to be fill - Complete `etc/allVariables.py` -- Add IP adress of the server and share folder in `bin/OnWindows/client.py` at specific lines - -- Change `bin/OnWindows/client.py` in an exe and put in startup folder - - + In `test/` some example of software to install is give, it's use a specific format : -- First, there's the name of the packages to install using choclatey (https://community.chocolatey.org/packages) before `:` +- First, there's the name of the packages to install using chocolatey (https://community.chocolatey.org/packages) before `:`, or the name of the file in case of msi or exe file. - Second, after `:` there's the name of the exe to extract and run it (without extension). +- The second part after `,` follow the same system with the word `installer` first and after `:` the type of installer : + - choco + - msiexec + - exe +- Finally, the third part, `uninstaller` follow by `:` and the uninstaller like choco, msiexec or exe ## Run -`bin/server.py` is the first script to run and `bin/Generator.py` is the second and the last. + `bin/Generator.py` is the only script to run, but fill `etc/allVariables.py` is very important. diff --git a/bin/Generator.py b/bin/Generator.py index 25ca6b4..91357d4 100644 --- a/bin/Generator.py +++ b/bin/Generator.py @@ -1,9 +1,11 @@ import os import re import sys +import json import uuid import time import get_pe +import shutil import datetime import subprocess import pathlib @@ -13,32 +15,98 @@ pathWork += i + "/" sys.path.append(pathWork + "etc") import allVariables -import OnLinux.get_Fls_Strings import automatisation_yara +import OnLinux.get_Fls_Strings + + +# Load of the block list for the name of software +def blockProg(): + f1 = open(pathWork + "etc/blockProg.txt", "r") + l1 = f1.readlines() + f1.close() + return l1 + +def callSubprocessPopen(request, shellUse = False): + if shellUse: + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + else: + p = subprocess.Popen(request, stdout=subprocess.PIPE) + (output, err) = p.communicate() + p_status = p.wait() + +# Write the task into a file for the client +def writeFile(app, uninstall): + if uninstall: + tmp = open(allVariables.pathToInstaller + "/uninstall.txt", "w") + else: + tmp = open(allVariables.pathToInstaller + "/install.txt", "w") + + appSplit = app.split(",") + app = appSplit[0].split(":") + installer = appSplit[1].split(":") + + appstr = '{"%s":"%s"' % (app[0], app[1]) + installerstr = '"%s":"%s"}' % (installer[0], installer[1].rstrip("\n")) + tmp.write(appstr + ", " + installerstr) + tmp.close() +# Get the list of running vms def runningVms(): req = [allVariables.VBoxManage, "list", "runningvms"] return subprocess.run(req, capture_output=True) -def readFile(): - f = open(str(pathProg) + "/tmp","r") - f1 = open(pathWork + "etc/blockProg.txt", "r") +# Get the new name of the software using blocklist +def nameApp(capp): + app = capp.split(",")[0].split(":")[1] - l = f.readline().rstrip() - l1 = f1.readlines() + l1 = blockProg() - f.close() - f1.close() + for line in l1: + if line.split(":")[0] == app: + return line.split(":")[1].rstrip("\n") + return app + +def getUninstall(app, l_app): + block = blockProg() + flag = False + alt = "" + for b in block: + if app == b.split(":")[1].rstrip("\n"): + alt = b.split(":")[0].rstrip("\n") + flag = True + break + + listMultiSoft = list() + + softMulti = "" + flagMulti = False + + with open(pathWork + "etc/MultiSoft.txt", "r") as MultiSoft: + lines = MultiSoft.readlines() + for l in lines : + listMultiSoft = l.split(":") + soft = listMultiSoft[1].split(",") + for s in soft: + if (flag and (s == alt)) or (not flag and (s == app)): + softMulti = listMultiSoft[0] + flagMulti = True + break + + for l in l_app: + loc = l.split(",") - listTmp = [l.split(":")[0],l.split(":")[1].rstrip("\n")] + if flagMulti and (softMulti == loc[0].split(":")[1].rstrip("\n")): + return loc[2].split(":")[1].rstrip("\n") - for line in l1: - if line.split(":")[0] == listTmp[1]: - return [listTmp[0], line.split(":")[1].rstrip("\n")] - return listTmp + if (flag and (alt == loc[0].split(":")[1].rstrip("\n"))) or (not flag and (app == loc[0].split(":")[1].rstrip("\n"))): + return loc[2].split(":")[1].rstrip("\n") -def create_rule(ext, hexa, product_version, l_app): + +# Creation of yara rule for PE informations +def create_rule(ext, hexa, product_version, l_app, uninstaller): app = "" for l in l_app: if l.split(":")[1].rstrip("\n") == ext[0]: @@ -47,15 +115,17 @@ def create_rule(ext, hexa, product_version, l_app): ##Headers of yara rule if app: + print("############################### App\n") rules = "rule %s_%s {\n\tmeta:\n\t\t" % (app, ext[1]) else: rules = "rule %s_%s {\n\tmeta:\n\t\t" % (ext[0], ext[1]) - rules += 'description = "Auto gene for %s"\n\t\t' % (str(ext[0])) + rules += 'description = "Auto generation for %s"\n\t\t' % (str(ext[0])) rules += 'author = "David Cruciani"\n\t\t' rules += 'date = "' + date.strftime('%Y-%m-%d') + '"\n\t\t' rules += 'versionApp = "%s"\n\t\t' % (product_version) - rules += 'uuid = "%s"\n\t' % (str(uuid.uuid1())) + rules += 'uuid = "%s"\n\t\t' % (str(uuid.uuid4())) + rules += 'uninstaller = "%s"\n\t' % (uninstaller) rules += "strings: \n" @@ -67,39 +137,124 @@ def create_rule(ext, hexa, product_version, l_app): return rules -def runAuto(s): +# Creation of yara rule other than PE informations +def runAuto(s, stringProg): pathS = os.path.join(allVariables.pathToStrings, s) if os.path.isfile(pathS): print(s) - automatisation_yara.inditif(pathS, ProductVersion, l_app) + automatisation_yara.inditif(pathS, ProductVersion, l_app, stringProg) + +# Parse of Asa Report +def parseAsa(asaReport, currentApp): + with open(asaReport, "r") as asa_file: + jsonParse = json.loads(asa_file.read()) + + ## Blocklist for unwanted path + with open(pathWork + "etc/blocklistASA.txt", "r") as blockAsa: + blocklistASA = blockAsa.readlines() + + path = "" + ## Important path are in FILE_CREATED + for i in jsonParse["results"]["FILE_CREATED"]: + path += i["Compare"]["Path"] + "\n" + + ## Sed is apply to deleted the unwanted path specified in blocklistASA + filesed = "./" + currentApp + "_Asa_report.txt" + with open(filesed, "w") as write_file: + write_file.write(path) + + request = [allVariables.sed, "-r", "-i"] + s = "/" + j = True + for i in blocklistASA: + if j: + s += i.rstrip("\n") + j = False + else: + s += "|" + i.rstrip("\n") + s += "/d" + request.append(s) + request.append(filesed) + #print(request) + + callSubprocessPopen(request) + + return filesed + if __name__ == '__main__': + logFile = open(pathWork + "bin/logFile.txt", "a") + + ## Get the new name of software and the number of them to install + list_app_string = list() + list_block = blockProg() fapp = open(allVariables.applist, "r") l_app = fapp.readlines() line_count = 0 for line in l_app: + for block in list_block: + if line.split(":")[1].rstrip("\n") == block.split(":")[0]: + list_app_string.append(block.split(":")[1].rstrip("\n")) + break + else: + list_app_string.append(line.split(":")[1].rstrip("\n")) + break if line != "\n": line_count += 1 fapp.close() + ## Do a special strings-grep for better performance during yara generation + stringProg = "stringProg" + if not allVariables.LinuxVM: + r = 'strings %s | grep -i -E "%s' % (allVariables.pathToFirstStringsMachine, list_app_string[0].split(",")[0]) + for i in range(1, len(list_app_string)): + r += " | " + list_app_string[i].split(",")[0] + r += '" > %s' % (stringProg) + print(r) + p = subprocess.Popen(r, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + + + pathMnt = "" res = runningVms() + j=0 + uninstall = False + for i in range(0, line_count*2): + ## Output to know what the program is doing + loc = i - j + if uninstall: + print("\nBoucle n: %s, Uninstall: %s" % (i, l_app[loc % len(l_app)].split(":")[1].split(",")[0])) + try: + os.remove(allVariables.pathToInstaller + "/install.txt") + except: + pass + else: + print("\nBoucle n: %s, Install: %s" % (i, l_app[loc % len(l_app)].split(":")[1].split(",")[0])) + try: + os.remove(allVariables.pathToInstaller + "/uninstall.txt") + except: + pass + + writeFile(l_app[loc], uninstall) - for i in range(0,line_count*2): - print("Boucle n: %s, %s" % (i, l_app[i % len(l_app)].split(":")[1])) res = runningVms() - request = [allVariables.VBoxManage, 'startvm', allVariables.WindowsVM] + request = [allVariables.VBoxManage, 'startvm', allVariables.WindowsVM, '--type', 'headless'] if not allVariables.WindowsVM in res.stdout.decode(): ## Start windows machine - print("Windows Start") + print("[+] Windows Start") p = subprocess.Popen(request, stdout=subprocess.PIPE) (output, err) = p.communicate() p_status = p.wait() + else: + print("[+] Windows Running") - ## wait windows machine to shutdown + ## Wait windows machine to shutdown res = runningVms() + ## Output to see the time that the windows machine is running cptime = 0 while allVariables.WindowsVM in res.stdout.decode(): time.sleep(60) @@ -107,21 +262,23 @@ def runAuto(s): print("\rTime spent: %s min" % (cptime), end="") res = runningVms() - print("\nWindows stop\n") + print("\n[+] Windows stop\n") ## Convert windows machine into raw format qemu = allVariables.qemu vm = allVariables.pathToWindowsVM partage = allVariables.pathToConvert - status = readFile() - - convert_file = "%s%s_%s.img" %(partage, status[1], status[0]) + nApp = nameApp(l_app[loc]) + + if uninstall: + convert_file = "%s%s_uninstall.img" %(partage, nApp) + else: + convert_file = "%s%s_install.img" %(partage, nApp) print("## Convertion ##") - ############### Mettre plutot le nom de l'exe pour la machine linux pour faire un grep -i direct en fonction du nom res = subprocess.call([qemu, "convert", "-f", "vmdk", "-O", "raw", vm, convert_file]) - print("ok\n") + print("## Convertion Finish ##\n") if allVariables.LinuxVM: @@ -130,14 +287,17 @@ def runAuto(s): request = [allVariables.VBoxManage, 'startvm', allVariables.LinuxVM] if not allVariables.LinuxVM in res.stdout.decode(): ## Start ubuntu machine - print("Ubuntu Start") + print("[+] Ubuntu Start") p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() p_status = p.wait() + else: + print("[+] Ubuntu Running") - + ## Wait linux machine to shutdown res = runningVms() - + + ## Output to see the time that the linux machine is running cptime = 0 while allVariables.LinuxVM in res.stdout.decode(): time.sleep(60) @@ -145,44 +305,214 @@ def runAuto(s): print("\rTime spent: %s min" % (cptime), end="") res = runningVms() - print("\nUbuntu stop") + print("\n[+] Ubuntu stop") else: for content in os.listdir(allVariables.pathToConvert): appchemin = os.path.join(allVariables.pathToConvert, content) if os.path.isfile(appchemin): app_status = content.split(".")[0] app = app_status.split("_")[0] - - OnLinux.get_Fls_Strings.fls(appchemin, allVariables.pathToStrings, app_status) - OnLinux.get_Fls_Strings.getStrings(appchemin, app, allVariables.pathToStrings, app_status) + listMultiSoft = list() + + with open(pathWork + "etc/MultiSoft.txt", "r") as MultiSoft: + lines = MultiSoft.readlines() + for l in lines : + if app == l.split(":")[0]: + listMultiSoft = l.split(":")[1].split(",") + listMultiSoft[-1] = listMultiSoft[-1].rstrip("\n") + + if len(listMultiSoft) == 0: + listMultiSoft.append(app) + + print("listMultiSoft: " + str(listMultiSoft)) + + ## Run the fls command + OnLinux.get_Fls_Strings.fls(appchemin, allVariables.pathToStrings, app_status, listMultiSoft) + + ## Run Strings command + OnLinux.get_Fls_Strings.getStrings(appchemin, listMultiSoft, allVariables.pathToStrings, app_status) + + + ## Parsing of the Asa Report + if not uninstall: + if allVariables.pathToAsaReport: + logFile.write("[+] Parsing AsA Report \n") + print("[+] Parsing AsA Report") + ## Parsing + content = l_app[loc].split(":")[0].split(".")[0] + "_install_Asa_compare.json" + logFile.write("AsaReport to search: " + content + "\n") + + chemin = os.path.join(allVariables.pathToAsaReport, content) + logFile.write("Path to AsaReport: " + chemin + "\n") + + if os.path.isfile(chemin): + filesed = parseAsa(chemin, nApp) + ## read path collect by parser + with open(filesed, "r") as read_file: + AsaPath = read_file.readlines() + + ## create mount directory + pathMnt = "./mnt_convert" + if not os.path.isdir(pathMnt): + os.mkdir(pathMnt) + + ## mount the convert image + print("\t[+] Mount") + request = "sudo mount -o loop,ro,noexec,noload,offset=$((512*104448)) " + convert_file + " " + pathMnt + callSubprocessPopen(request, True) + + ## md5 for each file of AsaPath + print("\t[+] Md5 Asa") + for pathMd5 in AsaPath: + pathMd5 = pathMnt + "/" + pathMd5.split(":")[1].rstrip("\n")[1:] + pathMd5 = re.sub(r"\\","/", pathMd5) + pathMd5 = pathMd5.split("/") + + ## Add "" for each folder who contains space caracters + cp = 0 + for sp in pathMd5: + for car in sp: + if car == " ": + pathMd5[cp] = '"' + pathMd5[cp] + '"' + cp += 1 + + ## Reassemble the strings + stringPath = "" + for sp in pathMd5: + stringPath += sp + "/" + + pathMd5 = stringPath[:-1] + + savePath = allVariables.pathToYaraSave + "/" + nApp + logFile.write("savePath :" + savePath + "\n") + + if not os.path.isdir(savePath): + os.mkdir(savePath) + + request = "md5sum " + pathMd5 + " >> " + savePath + "/" + nApp + "_md5" + callSubprocessPopen(request, True) + + request = "sha1sum " + pathMd5 + " >> " + savePath + "/" + nApp + "_sha1" + callSubprocessPopen(request, True) + + ## umount the convert image + print("\t[+] Umount") + request = "sudo umount " + pathMnt + callSubprocessPopen(request, True) + + ## Delete Asa path + os.remove(filesed) + + if i % 2 == 0: + j += 1 + uninstall = True + else: + uninstall = False - ## Suppresson of the current tmp file - os.remove(str(pathProg) + "/tmp") ## Suppression of the current raw disk os.remove(convert_file) - + ## Suppression of mount folder + try: + shutil.rmtree(pathMnt) + except: + pass + ## AutoGeneYara hexa = "" ProductVersion = "" + listProduct = dict() + # Rule for Exe for content in os.listdir(allVariables.pathToShareWindows): + l = blockProg() + c = content.split(".") + for line in l: + if line.split(":")[0] == c[0]: + c[0] = line.split(":")[1].rstrip("\n") + + uninstaller = getUninstall(c[0], l_app) + chemin = os.path.join(allVariables.pathToShareWindows, content) if os.path.isfile(chemin): (hexa, ProductVersion) = get_pe.pe_yara(chemin) - c = content.split(".") - rule = create_rule(c, hexa, ProductVersion, l_app) + rule = create_rule(c, hexa, ProductVersion, l_app, uninstaller) print(rule) - automatisation_yara.save_rule(c[0], c[1], rule, 3) - - s = "@%s@fls_install.tree" % (c[0]) - runAuto(s) - - s = "@%s@fls_uninstall.tree" % (c[0]) - runAuto(s) - - s = "@%s@install.txt" % (c[0]) - runAuto(s) + automatisation_yara.save_rule(c[0], c[1], rule, uninstaller) + listProduct[c[0]] = ProductVersion - s = "@%s@uninstall.txt" % (c[0]) - runAuto(s) \ No newline at end of file + # Rule for strings and fls + for content in os.listdir(allVariables.pathToStrings): + chemin = os.path.join(allVariables.pathToStrings, content) + if os.path.isfile(chemin): + softName = content.split("@")[1] + + uninstaller = getUninstall(softName, l_app) + try: + automatisation_yara.inditif(chemin, listProduct[softName], l_app, stringProg, uninstaller) + except: + automatisation_yara.inditif(chemin, None, l_app, stringProg, uninstaller) + + # Hashlookup + for content in os.listdir(allVariables.pathToYaraSave): + pathFolder = os.path.join(allVariables.pathToYaraSave, content) + if os.path.isdir(pathFolder): + md5File = pathFolder + "/" + content + "_md5" + sha1File = pathFolder + "/" + content + "_sha1" + + if os.path.isfile(md5File): + with open(md5File, "r") as md5Read: + lines = md5Read.readlines() + for line in lines: + lineSplit = line.split(" ") + request = "%s -s -X 'GET' 'https://hashlookup.circl.lu/lookup/md5/%s' -H 'accept: application/json'" % ( allVariables.curl, lineSplit[0].rstrip("\n") ) + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + + jsonResponse = json.loads(output.decode()) + + if "message" in jsonResponse.keys(): + print(jsonResponse["message"]) + else: + pathHash = os.path.join(pathFolder, "HashLookup") + pathHashMd5 = os.path.join(pathHash, "md5") + + if not os.path.isdir(pathHash): + os.mkdir(pathHash) + if not os.path.isdir(pathHashMd5): + os.mkdir(pathHashMd5) + + with open(pathHashMd5 + "/" + lineSplit[0].rstrip("\n"), "w") as fileHash: + fileHash.write(str(jsonResponse)) + #print(jsonResponse) + """else: + print("There's no md5 file")""" + + if os.path.isfile(sha1File): + with open(sha1File, "r") as sha1Read: + lines = sha1Read.readlines() + for line in lines: + request = "%s -s -X 'GET' 'https://hashlookup.circl.lu/lookup/sha1/%s' -H 'accept: application/json'" % ( allVariables.curl, line.split(" ")[0].rstrip("\n") ) + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + + jsonResponse = json.loads(output.decode()) + + if "message" in jsonResponse.keys(): + print(jsonResponse["message"]) + else: + pathHash = os.path.join(pathFolder, "HashLookup") + pathHashSha1 = os.path.join(pathHash, "sha1") + + if not os.path.isdir(pathHash): + os.mkdir(pathHash) + if not os.path.isdir(pathHashSha1): + os.mkdir(pathHashSha1) + + with open(pathHashSha1 + "/" + lineSplit[0].rstrip("\n"), "w") as fileHash: + fileHash.write(str(jsonResponse)) + #print(jsonResponse) + """else: + print("There's no sha1 file")""" diff --git a/bin/OnLinux/get_Fls_Strings.py b/bin/OnLinux/get_Fls_Strings.py index 5b94121..d208381 100644 --- a/bin/OnLinux/get_Fls_Strings.py +++ b/bin/OnLinux/get_Fls_Strings.py @@ -1,10 +1,10 @@ #!/bin/python3 import os -import time +import shutil import subprocess -def fls(cheminMachine, cheminOut, app_status): +def fls(cheminMachine, cheminOut, app_status, listMultiSoft): ## get the longer partition request = "mmls -t dos %s | cut -c43-55 > %slength_partition" % (cheminMachine, cheminOut) subprocess.call(request, shell=True) @@ -35,8 +35,10 @@ def fls(cheminMachine, cheminOut, app_status): offset = int(ls[cpmax].rstrip("\n")) + pathFls1erProg = "%s@%s@fls_%s.tree" % (cheminOut, app_status.split("_")[0], app_status.split("_")[1]) - r = "fls -r -o %s %s > %s@%s@fls_%s.tree" % (str(offset), cheminMachine, cheminOut, app_status.split("_")[0], app_status.split("_")[1]) + r = "fls -r -o %s %s > %s" % (str(offset), cheminMachine, pathFls1erProg) + print("[+] Fls for %s" % (app_status.split("_")[0])) p = subprocess.Popen(r, stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() @@ -45,18 +47,51 @@ def fls(cheminMachine, cheminOut, app_status): f.close() f2.close() + flag1erProg = False + + if len(listMultiSoft) > 1: + for l in listMultiSoft: + if not pathFls1erProg == "%s@%s@fls_%s.tree" % (cheminOut, l, app_status.split("_")[1]): + shutil.copyfile(pathFls1erProg, "%s@%s@fls_%s.tree" % (cheminOut, l, app_status.split("_")[1])) + flag1erProg = True + + if not flag1erProg: + os.remove(pathFls1erProg) + os.remove("%slength_partition" % (cheminOut)) os.remove("%sstart_partition" % (cheminOut)) -def getStrings(appchemin, app, cheminOut, app_status): - r = "strings %s | grep -i %s > %s@%s@%s.txt" % (appchemin, app, cheminOut, app_status.split("_")[0], app_status.split("_")[1]) - print(r) +def getStrings(appchemin, listMultiSoft, cheminOut, app_status): + r = 'strings %s | grep -i -E "' % (appchemin) + + for soft in listMultiSoft: + r += '%s |' % (soft) + r = r[:-1] + if len(listMultiSoft) == 1: + r = r[:-1] + + pathGlob = "%s@%s@%s.txt" % (cheminOut, app_status.split("_")[0], app_status.split("_")[1]) + r += '" > %s' % (pathGlob) + + print("getStrings Request: " + r) + + print("[+] Strings for %s" % (app_status.split("_")[0])) p = subprocess.Popen(r, stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() p_status = p.wait() + if not len(listMultiSoft) == 1: + for soft in listMultiSoft: + request = "grep -i %s %s > %s@%s@%s.txt" % (soft, pathGlob, cheminOut, soft, app_status.split("_")[1]) + + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + + os.remove(pathGlob) + if __name__ == '__main__': @@ -75,7 +110,7 @@ def getStrings(appchemin, app, cheminOut, app_status): getStrings(appchemin, app, cheminOut, app_status) - print("finish") + print("[+] Shutdown in 20 sec") subprocess.call("shutdown -h -t 20", shell=True) diff --git a/bin/OnWindows/VarClient.py b/bin/OnWindows/VarClient.py new file mode 100644 index 0000000..1a8447d --- /dev/null +++ b/bin/OnWindows/VarClient.py @@ -0,0 +1,20 @@ +#Path to folder who will contains software installer and task file (ShareFolder) +pathToInstaller = "\\\VBOXSVR\\PartageVM\\Installer" + +#Path to folder use for the excratction of softawre exe (ShareFolder) +pathToExeExtract = "\\\VBOXSVR\\PartageVM\\exe_extract" + +#Path to Uninstall.exe (Disk) +pathToUninstaller = "\\\VBOXSVR\\PartageVM\\UninstallX64.exe" + +#Path to SDelete (Disk) +pathToSDelete = "C:/Users/Administrateur/Downloads/sdelete64.exe" + +#Path to the file to copy after the uninstallation (ShareFolder) +pathToCopy = "\\\VBOXSVR\\PartageVM\\string_first" + +#Path to Asa.exe (Disk) +pathToAsa = "C:/Users/Administrateur/Downloads/ASA_win_2.3.146-beta/Asa.exe" + +#Path to the folder who will contains AsaReport (ShareFolder) +pathToAsaReport = "\\\VBOXSVR\\PartageVM\\AsaReport\\" diff --git a/bin/OnWindows/client.py b/bin/OnWindows/client.py index 37c8d45..fbf6424 100644 --- a/bin/OnWindows/client.py +++ b/bin/OnWindows/client.py @@ -1,86 +1,205 @@ import os import ast +import glob import time +import shutil import psutil -import requests +import VarClient import subprocess -# put client.exe in the startup folder, windows+r and shell:startup +# put client.exe in the startup folder, "Windows" + "r" and "shell:startup" +logFile = open("\\\VBOXSVR\\PartageVM\\logClient.txt", "a") -def request(host, port): - url = "http://%s:%s/installer" % (host, port) +## Prepare the request depending on the installer +def appManager(status, installer, app): + if installer == "choco": + if status: + return "choco install -y %s" % (app) + else: + return "choco uninstall -y %s" % (app) + elif installer == "msiexec": + if status: + return "msiexec /i %s%s /qn" % (VarClient.pathToInstaller + "\\installer\\", app) + else: + return "msiexec /x %s%s /qn" % (VarClient.pathToInstaller + "\\installer\\", app) + elif installer == "exe": + if status: + return "%s\\%s /s /v\"/qn\"" % (VarClient.pathToInstaller, app) + else: + return "%s %s" % (VarClient.pathToUninstaller, app) + + +def callSubprocess(who, request, shellUse = False): + if shellUse: + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + else: + p = subprocess.Popen(request, stdout=subprocess.PIPE) + (output, err) = p.communicate() + p_status = p.wait() + + try: + logFile.write("%s: %s\n" % (who, output.decode())) + except: + logFile.write("%s: %s\n" % (who, str(output))) + + +def sDelete(): + if VarClient.pathToSDelete: + print("[+] SDelete") + request = "%s -c C:" % (VarClient.pathToSDelete) + + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + + try: + logFile.write("sDelete: " + output.decode('utf-16') + "\n") + except: + logFile.write("sDelete: " + str(output) + "\n") + + try: + logFile.write("sDeleteError: " + err.decode('utf-8') + "\n") + except: + logFile.write("sDeleteError: " + str(err) + "\n") - r = requests.get(url) - applist = r.text[5:-6] - dic = ast.literal_eval(applist) - return r, dic +## Run an asa collect for a later compare +def AsACollect(): + if VarClient.pathToAsa: + print("[+] AsA collect") + request = [VarClient.pathToAsa, "collect", "-a"] + callSubprocess("AsaCollect", request) + +## Compare two asa collect and move the result to the share folder +def AsAExport(app): + if VarClient.pathToAsa: + print("[+] AsA export") + request = [VarClient.pathToAsa, "export-collect"] + callSubprocess("AsaExport", request) + + print("[+] Move AsA report") + request = ["move", ".\\2021*", VarClient.pathToAsaReport + app.split(".")[0] + "_install_Asa_compare.json"] + + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + try: + logFile.write("Move Asa: " + output.decode() + "\n") + except: + logFile.write("Move Asa: " + str(output) + "\n") + + print("[+] Delete Asa Sqlite File") + files = glob.glob('.\\asa.sqlite*', recursive=True) + for f in files: + try: + os.remove(f) + except OSError as e: + print("Error: %s : %s" % (f, e.strerror)) + + if __name__ == '__main__': - adress_server = "" #Exemple: 192.168.1.52 - port = 5000 - - (r, dic) = request(adress_server, port) - - if "uninstaller" in r.url: - for d in dic: - print("unin") - print(d + "\n") - #exit(0) - request = "choco uninstall %s -y" % (d) - p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) - (output, err) = p.communicate() - p_status = p.wait() + for content in os.listdir(VarClient.pathToInstaller): + chemin = os.path.join(VarClient.pathToInstaller, content) + if os.path.isfile(chemin): + f = open(chemin, "r") + l = f.readline() + f.close() - print("uninstall finish\n") - else: - for d in dic: - print("inst") - print(d + "\n") - #exit(0) - request = "choco install %s -y" % (d) - p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) - (output, err) = p.communicate() - p_status = p.wait() - - print("choco install: " + output.decode()) - print("[*] install finish\n") - - # get the past to the app - print("[+] Path to exe search...") - request = ["cd", "/", "&", "dir", "/s", "/b", "%s.exe" % (dic[d])] + dic = ast.literal_eval(l) + key = list(dic.keys()) - p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) - (output, err) = p.communicate() - p_status = p.wait() - - print("[+] Run exe...") - path = output.decode().split("\n")[0].rstrip("\n\r") - - p = subprocess.Popen(path, stdout=subprocess.PIPE, shell=True) + if "uninstall" in content: + print("[*] Uninstallation") + logFile.write("[*] Uninstallation\n") + request = appManager(False, dic[key[1]], key[0]) + print(request) - time.sleep(10) - - if psutil.pid_exists(p.pid): - parent = psutil.Process(p.pid) - children = parent.children(recursive=True) - for child_pid in children: - if psutil.pid_exists(child_pid.pid): - try: - subprocess.check_output("Taskkill /PID %d /F /T" % child_pid.pid) - except: - pass - - - # copy the app on the share folder of the vm - print("[+] Copy exe...") - r = 'copy "' + path + '" \\\VBOXSVR\PartageVM\exe_extract' ## change the last paramaeter - - p = subprocess.Popen(r, stdout=subprocess.PIPE, shell=True) - (output, err) = p.communicate() - p_status = p.wait() + if request: + callSubprocess("AppManager", request) + + if "exe" == dic[key[1]]: + input("\nEnter when finish") + + sDelete() + + print("[*] Uninstall finish") + else: + print("[*] Installation") + logFile.write("[*] Installation\n") + + AsACollect() + + request = appManager(True, dic[key[1]], key[0]) + print(request) + + if request: + callSubprocess("AppManager", request) + + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + try: + logFile.write("AppManager: " + output.decode() + "\n") + except: + logFile.write("AppManager: " + str(output) + "\n") + + if dic[key[1]] == "choco": + print("[+] Output installation: " + output.decode()) + + print("[*] Install finish\n") + + # get the path to the app + print("[+] Path to exe search...") + request = ["cd", "/", "&", "dir", "/s", "/b", "%s.exe" % (dic[key[0]])] + + p = subprocess.Popen(request, stdout=subprocess.PIPE, shell=True) + (output, err) = p.communicate() + p_status = p.wait() + try: + logFile.write("Path search " + output.decode() + "\n") + except: + logFile.write("Path search " + str(output) + "\n") + + path = output.decode().split("\n")[0].rstrip("\n\r") + + + # copy the app on the share folder of the vm + print("[+] Copy exe...") + r = 'copy "' + path + '" ' + VarClient.pathToExeExtract + + pCopy = subprocess.Popen(r, stdout=subprocess.PIPE, shell=True) + (output, err) = pCopy.communicate() + p_status = pCopy.wait() + try: + logFile.write("Copy Exe: " + output.decode() + "\n") + except: + logFile.write("Copy Exe: " + str(output) + "\n") + + # run exe to have more artefacts + print("[+] Run exe...") + p = subprocess.Popen(path, stdout=subprocess.PIPE, shell=True) + time.sleep(20) + + # search for the pid created by the above subprocess and kill it + if psutil.pid_exists(p.pid): + parent = psutil.Process(p.pid) + children = parent.children(recursive=True) + #print(children) + #child_pid = children[0].pid + for child_pid in children: + if psutil.pid_exists(child_pid.pid): + try: + subprocess.check_output("Taskkill /PID %d /F /T" % child_pid.pid) + except: + pass + + AsACollect() + AsAExport(key[0]) os.system("shutdown /s /t 10") \ No newline at end of file diff --git a/bin/automatisation_yara.py b/bin/automatisation_yara.py index ad86e28..df04cbd 100644 --- a/bin/automatisation_yara.py +++ b/bin/automatisation_yara.py @@ -15,7 +15,7 @@ ####Creation of yara rule -def create_rule(ext, s, product_version, flag, l_app): +def create_rule(ext, s, product_version, l_app, uninstaller): app = "" for l in l_app: if l.split(":")[1].rstrip("\n") == ext[1]: @@ -29,11 +29,12 @@ def create_rule(ext, s, product_version, flag, l_app): else: rules = "rule %s_%s {\n\tmeta:\n\t\t" % (ext[1], ext[2]) - rules += 'description = "Auto gene for %s"\n\t\t' % (str(ext[1])) + rules += 'description = "Auto generation for %s"\n\t\t' % (str(ext[1])) rules += 'author = "David Cruciani"\n\t\t' rules += 'date = "' + date.strftime('%Y-%m-%d') + '"\n\t\t' rules += 'versionApp = "%s"\n\t\t' % (product_version) - rules += 'uuid = "%s"\n\t' % (str(uuid.uuid1())) + rules += 'uuid = "%s"\n\t\t' % (str(uuid.uuid4())) + rules += 'uninstaller = "%s"\n\t' % (uninstaller) rules += "strings: \n" @@ -44,51 +45,60 @@ def create_rule(ext, s, product_version, flag, l_app): reg = "" r+=1 for car in regle: - if car in string.ascii_letters or car in string.digits or car == " " or car == "\t": + if car in string.ascii_letters or car in string.digits or car == " ": reg += car elif car in string.punctuation: reg += "\\" + car - ## if file is a tree, split to have only the interesting part - if flag: - reg = str(reg).split("\t")[1] rules += "\t\t$s%s = /%s/\n" % (str(r), reg) ##End of yara rule ## 1.25 is a coefficient to match the rule, which leaves a margin of error - rules += "\tcondition:\n\t\t%s of ($s*)\n}" % (str(int(r/1.25))) + #rules += "\tcondition:\n\t\t%s of ($s*)\n}" % (str(int(r/1.25))) + rules += "\tcondition:\n\t\t ext_var of ($s*)\n}" return rules ###Save of the rule on the disk -def save_rule(ext1, ext2, rules, flag): - chemin = None - if flag == 3: - chemin = os.path.join(allVariables.pathToYaraSave, "exe") - elif flag: - chemin = os.path.join(allVariables.pathToYaraSave, "tree") - else: - chemin = os.path.join(allVariables.pathToYaraSave, "txt") - +def save_rule(ext1, ext2, rules, uninstaller, flag = False): + chemin = os.path.join(allVariables.pathToYaraSave, ext1) if not os.path.isdir(chemin): os.mkdir(chemin) - yara_rule = open("%s/%s_%s.yar" % (chemin, ext1, ext2), "w") + cheminUninstall = os.path.join(chemin, uninstaller) + if not os.path.isdir(cheminUninstall): + os.mkdir(cheminUninstall) + + if flag: + cheminUninstall = os.path.join(cheminUninstall, "tree") + + if not os.path.isdir(cheminUninstall): + os.mkdir(cheminUninstall) + + yara_rule = open("%s/%s_%s.yar" % (cheminUninstall, ext1, ext2), "w") yara_rule.write(rules) yara_rule.close() -def file_create_rule(chemin, file_version, l_app, flag = False): +def file_create_rule(chemin, file_version, l_app, stringProg, uninstaller, flag = False): s = list() f = open(chemin, "r") file_strings = f.readlines() - if allVariables.pathToFirstStringsMachine: + full = "" + + ## First Strings execute for better performance + if stringProg: + first = open(stringProg) + full = first.readlines() + first.close() + ## Strings of the vanilla machine + elif allVariables.pathToFirstStringsMachine: first = open(allVariables.pathToFirstStringsMachine) full = first.readlines() first.close() - + ## Fls of the vanilla machine if allVariables.pathToFirstFls: flsFile = open(allVariables.pathToFirstFls, "r") fls = flsFile.readlines() @@ -106,43 +116,45 @@ def file_create_rule(chemin, file_version, l_app, flag = False): ## the file is not a tree if not flag: ## there's a file who contains some strings about a software on a vanilla machine - if allVariables.pathToFirstStringsMachine: + if full: if ((not len(file_strings[i].split(" ")) > 5 and not len(file_strings[i]) > 30) \ or (len(file_strings[i].split(" ")) == 1 and not len(file_strings[i]) > 50)) \ - and ((ext[1] in file_strings[i] or ext[1].lower() in file_strings[i]) and file_strings[i] not in s) and file_strings[i] not in full: + and ((ext[1] in file_strings[i] or ext[1].lower() in file_strings[i] or ext[1].upper() in file_strings[i]) and file_strings[i] not in s) and file_strings[i] not in full: s.append(file_strings[i]) else: if ((not len(file_strings[i].split(" ")) > 5 and not len(file_strings[i]) > 30) \ or (len(file_strings[i].split(" ")) == 1 and not len(file_strings[i]) > 50)) \ - and (ext[1] in file_strings[i] or ext[1].lower() in file_strings[i]) and file_strings[i] not in s: + and (ext[1] in file_strings[i] or ext[1].lower() in file_strings[i] or ext[1].upper() in file_strings[i]) and file_strings[i] not in s: s.append(file_strings[i]) + ## the file is a tree else: + f_str = str(file_strings[i]).split("\t")[1] if allVariables.pathToFirstFls: - if ((ext[1] in file_strings[i] or ext[1].lower() in file_strings[i] or ext[1].upper() in file_strings[i]) and file_strings[i] not in s) and file_strings[i] not in fls: - s.append(file_strings[i]) + if ((ext[1] in f_str or ext[1].lower() in f_str or ext[1].upper() in f_str) and f_str not in s) and f_str not in fls: + s.append(f_str) else: - if (ext[1] in file_strings[i] or ext[1].lower() in file_strings[i] or ext[1].upper() in file_strings[i]) and file_strings[i] not in s: - s.append(file_strings[i]) + if (ext[1] in f_str or ext[1].lower() in f_str or ext[1].upper() in f_str) and f_str not in s: + s.append(f_str) ## Suppression of the extension ext.append(str(ext[-1:][0].split(".")[0])) del(ext[-2:-1]) ####Creation of yara rule - rules = create_rule(ext, s, file_version, flag, l_app) + rules = create_rule(ext, s, file_version, l_app, uninstaller) print(rules) #exit(0) ###Save of the rule on the disk - save_rule(ext[1], ext[2], rules, flag) + save_rule(ext[1], ext[2], rules, uninstaller, flag) -def inditif(fichier, file_version, l_app): +def inditif(fichier, file_version, l_app, stringProg, uninstaller): try: extension = fichier.split(".")[1] except: @@ -151,6 +163,6 @@ def inditif(fichier, file_version, l_app): ## the file is a tree if fichier.split(".")[1] == "tree": - file_create_rule(fichier, file_version, l_app, True) + file_create_rule(fichier, file_version, l_app, stringProg, uninstaller, True) else: - file_create_rule(fichier, file_version, l_app) + file_create_rule(fichier, file_version, l_app, stringProg, uninstaller) diff --git a/bin/get_pe.py b/bin/get_pe.py index 7732498..8e84778 100644 --- a/bin/get_pe.py +++ b/bin/get_pe.py @@ -21,7 +21,6 @@ def pe_yara(file_pe): f = pe.FileInfo[0] except: return "","" - for fileinfo in pe.FileInfo[0]: if fileinfo.Key.decode() == 'StringFileInfo': string_table = fileinfo.StringTable[0] diff --git a/bin/serveur.py b/bin/serveur.py deleted file mode 100644 index 6243ca1..0000000 --- a/bin/serveur.py +++ /dev/null @@ -1,77 +0,0 @@ -import os -import re -import sys -import time -import flask -import pathlib -pathProg = pathlib.Path(__file__).parent.absolute() -s = "" -for i in re.split(r"/|\\", str(pathProg))[:-1]: - s += i + "/" -sys.path.append(s + "etc") -import allVariables - - -app = flask.Flask(__name__) -#app.config["DEBUG"] = True - -f = open(allVariables.applist, "r") -listapp = f.readlines() - -lapp = dict() -list_app = list() -flagun = False -cp = -1 - -for l in listapp: - l = l.split(":") - lapp[l[0]] = l[1].rstrip(("\n")) - -list_app = list(lapp.keys()) -#print(list_app) -#exit(0) - -def WriteFileP(s, current): - f = open(str(pathProg) + "/tmp", "w") - f.write(s + ":" + current) - f.close() - -@app.route('/', methods=['GET']) -def home(): - return "