Skip to content

Master sync #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
266 changes: 225 additions & 41 deletions batch_deobfuscator/batch_interpreter.py

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions tests/test_curl.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,40 @@
{"src": "http://localhost:5572/rc/noop?rutabaga=3&potato=4", "dst": None},
),
),
(
"curl.exe -o C:\\ProgramData\\Pterds\\HErtop.pos 1.1.1.1/4.dat",
(
"curl.exe -o C:\\ProgramData\\Pterds\\HErtop.pos 1.1.1.1/4.dat",
{"src": "1.1.1.1/4.dat", "dst": "C:\\ProgramData\\Pterds\\HErtop.pos"},
),
),
(
r'curl -X POST --fail -H "Content-type: application/x-www-form-urlencoded" -H "Accept: application/json" -H "Authorization: Bearer token=aaaaaaaaaaaaaaaaa" http://server.com/data?style=table',
(
r'curl -X POST --fail -H "Content-type: application/x-www-form-urlencoded" -H "Accept: application/json" -H "Authorization: Bearer token=aaaaaaaaaaaaaaaaa" http://server.com/data?style=table',
{
"src": "http://server.com/data?style=table",
"dst": None,
},
),
),
(
r'curl -X POST --fail -H "Content-type: application/octet-stream" -H "Accept: application/json" -H "Content-Disposition: attachment; filename=myupload.file" -H "Authorization: Bearer token=aaaaaaaaaaaaaaaaa" --data-binary "@some\path\with\my\file.data" http://server.com/upload?overwrite=true',
(
r'curl -X POST --fail -H "Content-type: application/octet-stream" -H "Accept: application/json" -H "Content-Disposition: attachment; filename=myupload.file" -H "Authorization: Bearer token=aaaaaaaaaaaaaaaaa" --data-binary "@some\path\with\my\file.data" http://server.com/upload?overwrite=true',
{
"src": "http://server.com/upload?overwrite=true",
"dst": None,
},
),
),
(
r'curl -X POST -H "Content-type: application/json" -H "Accept: application/json" -H "Authorization: Bearer token=aaaaaaaaaaaaaaaaa" -d "{\"someParameters\": [{\"name\":\"FILE_NAME\" \"value\": \"filename.file\"} {\"name\":\"OTHER_PARAM\" \"value\": \"TRUE\"} {\"name\":\"COMPLEX_PARAM\" \"value\": [\"some\" \"other\" \"value\"]} ]}" http://server.com/data.page >>some\file\output.json',
(
r'curl -X POST -H "Content-type: application/json" -H "Accept: application/json" -H "Authorization: Bearer token=aaaaaaaaaaaaaaaaa" -d "{\"someParameters\": [{\"name\":\"FILE_NAME\" \"value\": \"filename.file\"} {\"name\":\"OTHER_PARAM\" \"value\": \"TRUE\"} {\"name\":\"COMPLEX_PARAM\" \"value\": [\"some\" \"other\" \"value\"]} ]}" http://server.com/data.page >>some\file\output.json',
{"src": "http://server.com/data.page", "dst": None},
),
),
],
)
def test_curl_extraction(statement, download_trait):
Expand Down
46 changes: 46 additions & 0 deletions tests/test_full_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
import tempfile

from batch_deobfuscator.batch_interpreter import BatchDeobfuscator


# Taken from 675228b0360a56b2d3ed661635de4359d72089cb0e089eb60961727706797751
# A Grub file that contains a batch script
# The value for the variable in_check contain itself, so it create an infinite recursion when expanding it
def test_in_check_infinite_recursion():
deobfuscator = BatchDeobfuscator()
script = rb"""
if "%back%"=="" || set back= && set filefnd= && set in_check= ! call Fn.11 "%in_check%" "1" && exit
call Fn.11 "%in_check%" "1" && exit 1
"""
with tempfile.TemporaryDirectory() as temp_dir:
with tempfile.NamedTemporaryFile(dir=temp_dir) as tf:
tf.write(script)
tf.flush()
deobfuscator.analyze(tf.name, temp_dir)

# No assert, just making sure it does not error out.


def test_concat_logical_lines():
deobfuscator = BatchDeobfuscator()
script = rb"""REM download log file
curl -X GET --fail ^
-H "Accept: application/octet-stream" ^
http://server.org/data?accept=data >>met\resultat\output.log"""
with tempfile.TemporaryDirectory() as temp_dir:
with tempfile.NamedTemporaryFile(dir=temp_dir) as tf:
tf.write(script)
tf.flush()
bat_filename, _ = deobfuscator.analyze(tf.name, temp_dir)

with open(os.path.join(temp_dir, bat_filename), "rb") as f:
result = f.read()
lines = result.split(b"\r\n")

assert len(lines) >= 2
assert lines[0] == b"REM download log file"
assert lines[1] == (
rb'curl -X GET --fail -H "Accept: application/octet-stream" '
rb"http://server.org/data?accept=data >>met\resultat\output.log"
)
210 changes: 210 additions & 0 deletions tests/test_net.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import tempfile

from batch_deobfuscator.batch_interpreter import BatchDeobfuscator


def test_net_user():
deobfuscator = BatchDeobfuscator()
deobfuscator.interpret_command("net user")
assert len(deobfuscator.traits) == 0
deobfuscator.interpret_command("net user guest")
assert len(deobfuscator.traits) == 0
deobfuscator.interpret_command("net user administrator")
assert len(deobfuscator.traits) == 0


def test_net_use_user_password():
deobfuscator = BatchDeobfuscator()
cmd = "net use Q: https://webdav.site.com passw'd /user:[email protected]"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "Q:",
"server": "https://webdav.site.com",
"password": "passw'd",
"user": "[email protected]",
},
)


def test_net_use_user():
deobfuscator = BatchDeobfuscator()
cmd = r"net use d: \\server\share /user:Accounts\User1"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "d:",
"server": r"\\server\share",
"user": r"Accounts\User1",
},
)


def test_net_use_no_devicename():
deobfuscator = BatchDeobfuscator()
cmd = r"NET USE C:\TEMP\STUFF"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
print(deobfuscator.traits)
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"server": r"C:\TEMP\STUFF",
},
)


def test_net_use_delete():
deobfuscator = BatchDeobfuscator()
cmd = r"NET USE X: /DELETE"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "X:",
"options": ["delete"],
},
)

cmd = r"NET USE U: /DELETE /y"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 2
assert deobfuscator.traits["net-use"][1] == (
cmd,
{
"devicename": "U:",
"options": ["delete", "auto-accept"],
},
)


def test_net_use_delete_with_server():
deobfuscator = BatchDeobfuscator()
cmd = r"net use f: \\financial\public /delete"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "f:",
"server": r"\\financial\public",
"options": ["delete"],
},
)


def test_net_use_missing_var():
# Probably something like
# net use %UNKNOWN_VAR% /delete
# Which gets resolved to
# net use /delete
deobfuscator = BatchDeobfuscator()
cmd = r"net use /delete"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 0


def test_net_use_redirect():
deobfuscator = BatchDeobfuscator()
cmd = r"NET USE U: \\server\files >> output.log"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "U:",
"server": r"\\server\files",
},
)


def test_net_use_space():
deobfuscator = BatchDeobfuscator()
cmd = r'net use g: "\\server.local\some\path\to\a nice folder" /user:domain\username'
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "g:",
"server": r"\\server.local\some\path\to\a nice folder",
"user": r"domain\username",
},
)


def test_net_use_space_no_quotes():
deobfuscator = BatchDeobfuscator()
cmd = r"NET USE Z: \\server\folder\No Quotes For Some Reason"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "Z:",
"server": r"\\server\folder\No Quotes For Some Reason",
},
)


def test_net_use_from_text_blob():
deobfuscator = BatchDeobfuscator()
# Found in 8d06dd9b902bd1d3fcf55ced6ceb2488903c337dde28e7ad1a9c94e9dc5cfd38
cmd = r"net use x: \\<remote computer name>\C$)."
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 1
assert len(deobfuscator.traits["net-use"]) == 1
assert deobfuscator.traits["net-use"][0] == (
cmd,
{
"devicename": "x:",
"server": r"\\<remote computer name>\C$).",
},
)


def test_net_use_script():
deobfuscator = BatchDeobfuscator()
script = rb"""
net use w: /delete >nul 2>nul
if not exist w: (
net use w: \\server\files /Persistent:NO >nul 2>nul
)
"""
with tempfile.TemporaryDirectory() as temp_dir:
with tempfile.NamedTemporaryFile(dir=temp_dir) as tf:
tf.write(script)
tf.flush()
deobfuscator.analyze(tf.name, temp_dir)

assert "net-use" in deobfuscator.traits
assert len(deobfuscator.traits["net-use"]) == 2
assert deobfuscator.traits["net-use"][0] == (
r"net use w: /delete >nul 2>nul",
{
"devicename": "w:",
"options": ["delete"],
},
)
assert deobfuscator.traits["net-use"][1] == (
r"net use w: \\server\files /Persistent:NO >nul 2>nul",
{
"devicename": "w:",
"server": r"\\server\files",
"options": ["not-persistent"],
},
)
5 changes: 5 additions & 0 deletions tests/test_powershell.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import tempfile

import pytest

from batch_deobfuscator.batch_interpreter import BatchDeobfuscator
Expand Down Expand Up @@ -36,6 +38,9 @@
"powershell -Command \"& {get-process onedrive | add-member -Name Elevated -MemberType ScriptProperty -Value {if ($this.Name -in @('Idle','System')) {$null} else {-not $this.Path -and -not $this.Handle} } -PassThru | Format-Table Name,Elevated}\" > \"%WORKINGDIRONEDRIVE%\\OneDriveElevated.txt\"",
b"& {get-process onedrive | add-member -Name Elevated -MemberType ScriptProperty -Value {if ($this.Name -in @('Idle','System')) {$null} else {-not $this.Path -and -not $this.Handle} } -PassThru | Format-Table Name,Elevated}",
),
("powershell", None),
# echo cHdk | powershell -Encoded
("powershell -Encoded", None),
],
)
def test_extract_powershell(statement, extracted_ps1):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_rundll.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from batch_deobfuscator.batch_interpreter import BatchDeobfuscator


def test_dry_rundll32():
deobfuscator = BatchDeobfuscator()
cmd = r"$WINSYSDIR$\RunDLL32.exe"
deobfuscator.interpret_command(cmd)
assert len(deobfuscator.traits) == 0
Loading