從ruby實現時間伺服器ntp同步功能也談“逆向工程”

來源:互聯網
上載者:User

標籤:ruby   rdebug   net-ntp   調試   時間同步伺服器   

    本貓以前寫asm和C的時候常常不忘“逆向”一把,後來寫驅動的時候也用VM之類的搭建“雙機”調試環境進行調試;也對於一些小的軟體crack cd-key神馬的不亦樂乎。自從使用鳥所謂的進階動態語言ruby之後,這種黑逆的心態貌似逐漸減弱了...不過逮到機會還是難免心痒痒啊。

    ruby+linux的開源方式早已不要向bin碼一樣還要dis asm,不過有時候想要搞清楚一些功能還是要用點小技巧的,下面就解決一個小的問題給大家展示下這些東東吧

    ntp是一個時鐘同步協議用在伺服器和路由器上,ruby也有很多相關的gem,比如net-ntp,在gem install net-ntp之後可以使用如下代碼擷取ntp伺服器的標準時間:

#!/usr/bin/rubyrequire 'net/ntp'def get_ntp_time(srv_addr)puts Net::NTP.get(srv_addr).timeendget_ntp_time(ARGV[0])
運行結果如下:

[email protected]:~/src/ruby_src$ ./dzh.rb pool.ntp.org2014-12-04 14:06:20 +0800[email protected]:~/src/ruby_src$ ./dzh.rb time.nist.gov2014-12-04 14:07:00 +0800

我簡單分析了下ntp協議,發現如果自己實現可以用tcp或是udp的方式向ntp伺服器連接埠123(ntp服務連接埠)發送一些報文,然後接收返回即可。我開始以為報文是任意的,因為以前記得用telnet ip 123也返回了時間字串(現在覺得可能是記錯了啊!)於是有了我的第一次嘗試:

#!/usr/bin/rubyrequire 'net/ntp'def get_ntp_time_udp(srv_addr,msg)s = UDPSocket.news.connect srv_addr,123s.send msg,0response,address = s.recvfrom 1024puts [response,address]      s.closeenddef get_ntp_time(srv_addr)puts Net::NTP.get(srv_addr).timeendif ARGV.count == 1get_ntp_time(ARGV[0])elseget_ntp_time_udp(ARGV[0],ARGV[1])end

運行要帶2個參數,第二個參數是要發送的報文:./ut.rb time.nist.gov hi , 但是運行後長時間掛起,貌似ntp伺服器沒有返回啊!分析了一下,ntp報文可能不是隨便發的,要有一定格式,但到底是啥格式呢?百度了一下,格式比較複雜,轉換成代碼較麻煩啊!不如看一下net-ntp的實現代碼不是更好嗎?雖然是net-ntp是開源的,但是源檔案在哪呢?怎麼找呢?不如先用ruby的偵錯模式debug一下唄,ruby在運行時加上 -r debug(或是-rdebug)可以實現調試的功能,然後用n指令實現單步,用s指令實現單步步入跟蹤:

[email protected]:~/src/ruby_src$ ruby -rdebug ut.rb time.nist.govDebug.rbEmacs support available./usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:57:        RUBYGEMS_ACTIVATION_MONITOR.enter(rdb:1) n/usr/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:143:    RUBYGEMS_ACTIVATION_MONITOR.exit(rdb:1) ndzh.rb:2:require 'net/ntp'(rdb:1) ndzh.rb:4:def get_ntp_time_udp(srv_addr,msg)(rdb:1) ndzh.rb:14:def get_ntp_time(srv_addr)(rdb:1) ndzh.rb:18:if ARGV.count == 1(rdb:1) ndzh.rb:19:get_ntp_time(ARGV[0])(rdb:1) sdzh.rb:15:puts Net::NTP.get(srv_addr).time(rdb:1) s/var/lib/gems/2.1.0/gems/net-ntp-2.1.2/lib/net/ntp/ntp.rb:67:      sock = UDPSocket.new

這樣起碼可以看到Net::NTP.get的原始碼在哪了。開啟ntp.rb,並不複雜,一共200多行代碼。其中注釋很多都是#:nodoc:,是不是這樣實現作者也沒有找到依據文檔呢?ntp.rb全部原始碼如下:

require 'socket'require 'timeout'module Net #:nodoc:  module NTP    TIMEOUT = 60         #:nodoc:    NTP_ADJ = 2208988800 #:nodoc:    NTP_FIELDS = [ :byte1, :stratum, :poll, :precision, :delay, :delay_fb,                   :disp, :disp_fb, :ident, :ref_time, :ref_time_fb, :org_time,                   :org_time_fb, :recv_time, :recv_time_fb, :trans_time,                   :trans_time_fb ]    MODE = {      0 => 'reserved',      1 => 'symmetric active',      2 => 'symmetric passive',      3 => 'client',      4 => 'server',      5 => 'broadcast',      6 => 'reserved for NTP control message',      7 => 'reserved for private use'    }    STRATUM = {      0 => 'unspecified or unavailable',      1 => 'primary reference (e.g., radio clock)'    }    2.upto(15) do |i|      STRATUM[i] = 'secondary reference (via NTP or SNTP)'    end    16.upto(255) do |i|      STRATUM[i] = 'reserved'    end    REFERENCE_CLOCK_IDENTIFIER = {      'LOCL' => 'uncalibrated local clock used as a primary reference for a subnet without external means of synchronization',      'PPS'  => 'atomic clock or other pulse-per-second source individually calibrated to national standards',      'ACTS' => 'NIST dialup modem service',      'USNO' => 'USNO modem service',      'PTB'  => 'PTB (Germany) modem service',      'TDF'  => 'Allouis (France) Radio 164 kHz',      'DCF'  => 'Mainflingen (Germany) Radio 77.5 kHz',      'MSF'  => 'Rugby (UK) Radio 60 kHz',      'WWV'  => 'Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz',      'WWVB' => 'Boulder (US) Radio 60 kHz',      'WWVH' => 'Kaui Hawaii (US) Radio 2.5, 5, 10, 15 MHz',      'CHU'  => 'Ottawa (Canada) Radio 3330, 7335, 14670 kHz',      'LORC' => 'LORAN-C radionavigation system',      'OMEG' => 'OMEGA radionavigation system',      'GPS'  => 'Global Positioning Service',      'GOES' => 'Geostationary Orbit Environment Satellite'    }    LEAP_INDICATOR = {      0 => 'no warning',      1 => 'last minute has 61 seconds',      2 => 'last minute has 59 seconds)',      3 => 'alarm condition (clock not synchronized)'    }    ###    # Sends an NTP datagram to the specified NTP server and returns    # a hash based upon RFC1305 and RFC2030.    def self.get(host="pool.ntp.org", port="ntp", timeout=TIMEOUT)      sock = UDPSocket.new      sock.connect(host, port)      client_localtime      = Time.now.to_f      client_adj_localtime  = client_localtime + NTP_ADJ      client_frac_localtime = frac2bin(client_adj_localtime)      ntp_msg = (['00011011']+Array.new(12, 0)+[client_localtime, client_frac_localtime.to_s]).pack("B8 C3 N10 B32")      sock.print ntp_msg      sock.flush      read, write, error = IO.select [sock], nil, nil, timeout      if read[0]        client_time_receive = Time.now.to_f        data, _ = sock.recvfrom(960)        Response.new(data, client_time_receive)      else        # For backwards compatibility we throw a Timeout error, even        # though the timeout is being controlled by select()        raise Timeout::Error      end    end    def self.frac2bin(frac) #:nodoc:      bin  = ''      while bin.length < 32        bin += ( frac * 2 ).to_i.to_s        frac = ( frac * 2 ) - ( frac * 2 ).to_i      end      bin    end    private_class_method :frac2bin    class Response      attr_reader :client_time_receive            def initialize(raw_data, client_time_receive)        @raw_data             = raw_data        @client_time_receive  = client_time_receive        @packet_data_by_field = nil      end      def leap_indicator        @leap_indicator ||= (packet_data_by_field[:byte1].bytes.first & 0xC0) >> 6      end      def leap_indicator_text        @leap_indicator_text ||= LEAP_INDICATOR[leap_indicator]      end      def version_number        @version_number ||= (packet_data_by_field[:byte1].bytes.first & 0x38) >> 3      end      def mode        @mode ||= (packet_data_by_field[:byte1].bytes.first & 0x07)      end      def mode_text        @mode_text ||= MODE[mode]      end      def stratum        @stratum ||= packet_data_by_field[:stratum]      end      def stratum_text        @stratum_text ||= STRATUM[stratum]      end      def poll_interval        @poll_interval ||= packet_data_by_field[:poll]      end      def precision        @precision ||= packet_data_by_field[:precision] - 255      end      def root_delay        @root_delay ||= bin2frac(packet_data_by_field[:delay_fb])      end      def root_dispersion        @root_dispersion ||= packet_data_by_field[:disp]      end      def reference_clock_identifier        @reference_clock_identifier ||= unpack_ip(packet_data_by_field[:stratum], packet_data_by_field[:ident])      end      def reference_clock_identifier_text        @reference_clock_identifier_text ||= REFERENCE_CLOCK_IDENTIFIER[reference_clock_identifier]      end      def reference_timestamp        @reference_timestamp ||= ((packet_data_by_field[:ref_time] + bin2frac(packet_data_by_field[:ref_time_fb])) - NTP_ADJ)      end      def originate_timestamp        @originate_timestamp ||= (packet_data_by_field[:org_time] + bin2frac(packet_data_by_field[:org_time_fb]))      end      def receive_timestamp        @receive_timestamp ||= ((packet_data_by_field[:recv_time] + bin2frac(packet_data_by_field[:recv_time_fb])) - NTP_ADJ)      end      def transmit_timestamp        @transmit_timestamp ||= ((packet_data_by_field[:trans_time] + bin2frac(packet_data_by_field[:trans_time_fb])) - NTP_ADJ)      end      def time        @time ||= Time.at(receive_timestamp)      end      # As described in http://tools.ietf.org/html/rfc958      def offset        @offset ||= (receive_timestamp - originate_timestamp + transmit_timestamp - client_time_receive) / 2.0      end    protected      def packet_data_by_field #:nodoc:        if [email protected]_data_by_field          @packetdata = @raw_data.unpack("a C3   n B16 n B16 H8   N B32 N B32   N B32 N B32")          @packet_data_by_field = {}          NTP_FIELDS.each do |field|            @packet_data_by_field[field] = @packetdata.shift          end        end        @packet_data_by_field      end      def bin2frac(bin) #:nodoc:        frac = 0        bin.reverse.split("").each do |b|          frac = ( frac + b.to_i ) / 2.0        end        frac      end      def unpack_ip(stratum, tmp_ip) #:nodoc:        if stratum < 2          [tmp_ip].pack("H8").unpack("A4").bytes.first        else          ipbytes = [tmp_ip].pack("H8").unpack("C4")          sprintf("%d.%d.%d.%d", ipbytes[0], ipbytes[1], ipbytes[2], ipbytes[3])        end      end    end  endend

可以看到net-ntp的get方法使用的是TCP的方式,而且其實現的報文格式正常也是怎麼也猜不到哦(),其中寫了若干方法實現net位元組順序到本機位元組順序的相互轉換。為了簡單我們把它改寫為UDP的方式吧,我們也不要啥類了,直接寫全域方法吧,改寫後的方法如下:

#!/usr/bin/rubyrequire 'net/ntp'MY_NTP_ADJ = 2208988800 #:nodoc:MY_NTP_FIELDS = [ :byte1, :stratum, :poll, :precision, :delay, :delay_fb,                   :disp, :disp_fb, :ident, :ref_time, :ref_time_fb, :org_time,                   :org_time_fb, :recv_time, :recv_time_fb, :trans_time,                   :trans_time_fb ]def bin2frac(bin) #:nodoc:        frac = 0        bin.reverse.split("").each {|b|frac = ( frac + b.to_i ) / 2.0}        fracenddef frac2bin(frac) #:nodoc:      bin  = ''      while bin.length < 32         bin += ( frac * 2 ).to_i.to_s        frac = ( frac * 2 ) - ( frac * 2 ).to_i      end      binenddef packet_data_by_field(raw_data) #:nodoc:          packetdata = raw_data.unpack("a C3   n B16 n B16 H8   N B32 N B32   N B32 N B32")          packet_data_by_field = {}          MY_NTP_FIELDS.each do |field|            packet_data_by_field[field] = packetdata.shift          end          puts "bin:"+"@"*50          puts packet_data_by_field          puts "@"*54          packet_data_by_fieldenddef receive_timestamp(raw_data)         (packet_data_by_field(raw_data)[:recv_time] + bin2frac(packet_data_by_field(raw_data)[:recv_time_fb])) - MY_NTP_ADJenddef get_ntp_time_udp(srv_addr)  s = UDPSocket.new  s.connect srv_addr,123  client_localtime      = Time.now.to_f  client_adj_localtime  = client_localtime + Net::NTP::NTP_ADJ  client_frac_localtime = frac2bin(client_adj_localtime)  bin = (['00011011']+Array.new(12, 0)+[client_localtime, client_frac_localtime.to_s]).pack("B8 C3 N10 B32")  s.send bin,0  response,address = s.recvfrom(1024)  puts "ip:#{address}"  puts "bin:"+"*"*50  puts response  puts "*"*54  puts "GET TIME IS : #{Time.at(receive_timestamp(response))}"enddef get_ntp_time(srv_addr)  puts Net::NTP.get(srv_addr).timeendif ARGV.count == 1  get_ntp_time(ARGV[0])else  get_ntp_time_udp(ARGV[0])end

看一下運行結果吧:

[email protected]:~/src/ruby_src$ ./dzh.rb pool.ntp.org 1ip:["AF_INET", 123, "202.112.29.82", "202.112.29.82"]bin:**************************************************!? _?z??*????_ST???`? ?*?x0??=?*?x0??******************************************************bin:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@{:byte1=>"\x1C", :stratum=>2, :poll=>3, :precision=>233, :delay=>0, :delay_fb=>"0000110100100001", :disp=>0, :disp_fb=>"0000101000100000", :ident=>"5fde7ad2", :ref_time=>3626664177, :ref_time_fb=>"11010000110111000101111101010011", :org_time=>1417675511, :org_time_fb=>"10111000011000001001101000100000", :recv_time=>3626664312, :recv_time_fb=>"00110000100100001001110000111101", :trans_time=>3626664312, :trans_time_fb=>"00110000100110101101101011011001"}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@bin:@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@{:byte1=>"\x1C", :stratum=>2, :poll=>3, :precision=>233, :delay=>0, :delay_fb=>"0000110100100001", :disp=>0, :disp_fb=>"0000101000100000", :ident=>"5fde7ad2", :ref_time=>3626664177, :ref_time_fb=>"11010000110111000101111101010011", :org_time=>1417675511, :org_time_fb=>"10111000011000001001101000100000", :recv_time=>3626664312, :recv_time_fb=>"00110000100100001001110000111101", :trans_time=>3626664312, :trans_time_fb=>"00110000100110101101101011011001"}@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@GET TIME IS : 2014-12-04 14:45:12 +0800

開源自有開源的好處,否則為了分析這點功能,只有載入IDA pro之類的重型武器了。還有一種辦法是用抓包器看net-ntp發送的資料格式,看後照搬!正如看了《星際穿越》後覺得懂得了點神馬一樣:任何問題都有解決辦法,無論什麼樣的問題!而且解決辦法肯定不止一種!

從ruby實現時間伺服器ntp同步功能也談“逆向工程”

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.