Skip to content

Instantly share code, notes, and snippets.

@notdodo
Last active June 20, 2023 10:33
Show Gist options
  • Save notdodo/3d5ac56cd837c3f79d2c687f3e75cac1 to your computer and use it in GitHub Desktop.
Save notdodo/3d5ac56cd837c3f79d2c687f3e75cac1 to your computer and use it in GitHub Desktop.
Deobfuscate a powershell script with re-ordering obfuscation
#!/usr/bin/env python3
#
# AUTHOR: Edoardo Rosa notdodo https://github.com/notdodo
# https://twitter.com/_d_0_d_o_
#
# Sample: ("{0}{1}{4}{3}{5}{2}" -f 'CONv','er','G','R','tTo-SecURest','In')
# Decoded output: CONvertTo-SecURestRInG
#
try:
from pathlib import Path
import argparse
import re
import sys
except ModuleNotFoundError:
print("Please install pathlib, argparse")
DEBUG = False
description = "Clean an obfuscated string re-ordered powershell script"
charset = r"\w|\d|\n|\s|,|.|\-|=|/|:|#|_|{|}|\[|\]"
# Find format reoder pattern
regex_finder = r"\(\"([{\d+}]+)\"\s*-f\s*(['" + charset + "',]+)\)"
# Extract positions
regex_position = r"{(\d+)}"
# Extract payload strings
regex_content = r"'([" + charset + "]+)'"
# Match custom placeholder
regex_placeholder = r"({#subs_\d+})"
# Match [char]\d
regex_char = r"\[char\](\d+)"
# Match concatenation of strings
regex_concat = r"\((('|\"[\w|\s|\$]+'|\"\+|.)+)\)"
def decode(source, destination):
# Open file and return the content in string
def open_ps1(path):
try:
with open(str(path), "r") as ps1:
ps1_content = ps1.read()
except:
print("Error opening file")
sys.exit(-1)
return ps1_content
# Create a copy of the file decoded
def save_to_file(path, content):
try:
with open(path, "w+") as dec:
dec.write(content)
except:
print("Error on saving the file")
sys.exit(-1)
# Remove known issues
def clean_before(ps1_content):
matches = re.finditer(regex_char, ps1_content, re.IGNORECASE)
for _, word in enumerate(matches):
total = word.group()
letter = int(word.groups()[0], 10)
ps1_content = ps1_content.replace(total, "'" + chr(letter) + "'")
ps1_content = ps1_content.replace("`", "")
return ps1_content
# For each pattern found resolve it a regenerate the content
def parse_content(ps1_content):
ps1_content = clean_before(ps1_content)
count_matches = 0
occurrences = {}
while re.search(regex_finder, ps1_content, re.IGNORECASE):
matches = re.finditer(regex_finder, ps1_content, re.IGNORECASE)
for _, word in enumerate(matches):
if DEBUG:
print(word.groups()[0])
print(word.groups()[1])
# Array of indexes (in string)
positions = re.findall(
regex_position,
word.groups()[0].replace("\n", "").strip(),
re.IGNORECASE,
)
# Array of string segments
contents = re.findall(
regex_content,
word.groups()[1].replace("\n", "").strip(),
re.IGNORECASE,
)
if len(positions) == len(contents):
# out = ''.join([contents[int(p)].strip() for p in positions])
out = ""
for p in positions:
# Resolve second level expressions
if re.match(regex_placeholder, contents[int(p)]):
out += occurrences["'" + contents[int(p)] + "'"]
else:
out += contents[int(p)].strip()
placeholder = "'{#subs_" + str(count_matches) + "}'"
# Resolve and save expression or sub-expression
occurrences[placeholder] = out
# Put a placeholder for later substitution
ps1_content = ps1_content.replace(word.group(), placeholder)
count_matches += 1
else:
print("Did you include all chars in 'charset' var?")
print("[i] EXCEPTION OCCURRED IN: " + word.group())
sys.exit(-1)
# Print all resolved 1st level/basic expressions
subs = re.finditer(regex_placeholder, ps1_content, re.IGNORECASE)
for _, word in enumerate(subs):
ps1_content = ps1_content.replace(
"'" + word.group() + "'", occurrences["'" + word.group() + "'"]
)
return ps1_content, occurrences
new_content, occurrences = parse_content(open_ps1(source))
save_to_file(destination, new_content)
print("Decoded {} occurrences:".format(len(occurrences)))
print(destination)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=description)
parser.add_argument("file", help="Input file")
args = parser.parse_args()
ps1 = Path(args.file)
if ps1.is_file():
ps1 = Path(args.file).resolve()
decode_file = str(ps1.parent) + "/decoded_" + str(ps1.name)
decode(ps1, decode_file)
else:
print(ps1)
print("Not a valid file!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment