从ruby实现时间服务器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/ruby
require 'net/ntp'

def get_ntp_time(srv_addr)
	puts Net::NTP.get(srv_addr).time
end

get_ntp_time(ARGV[0])

运行结果如下:

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

我简单分析了下ntp协议,发现如果自己实现可以用tcp或是udp的方式向ntp服务器端口123(ntp服务端口)发送一些报文,然后接收返回即可。我开始以为报文是任意的,因为以前记得用telnet ip 123也返回了时间字符串(现在觉得可能是记错了啊!)于是有了我的第一次尝试:

#!/usr/bin/ruby
require 'net/ntp'

def get_ntp_time_udp(srv_addr,msg)
	s = UDPSocket.new
	s.connect srv_addr,123

	s.send msg,0
	response,address = s.recvfrom 1024
	puts [response,address]
      s.close
end

def get_ntp_time(srv_addr)
	puts Net::NTP.get(srv_addr).time
end

if ARGV.count == 1
	get_ntp_time(ARGV[0])
else
	get_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.gov
Debug.rb
Emacs 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) n
dzh.rb:2:require 'net/ntp'
(rdb:1) n
dzh.rb:4:def get_ntp_time_udp(srv_addr,msg)
(rdb:1) n
dzh.rb:14:def get_ntp_time(srv_addr)
(rdb:1) n
dzh.rb:18:if ARGV.count == 1
(rdb:1) n
dzh.rb:19:	get_ntp_time(ARGV[0])
(rdb:1) s
dzh.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

  end
end

可以看到net-ntp的get方法使用的是TCP的方式,而且其实现的报文格式正常也是怎么也猜不到哦(),其中写了若干方法实现net字节顺序到本机字节顺序的相互转换。为了简单我们把它改写为UDP的方式吧,我们也不要啥类了,直接写全局方法吧,改写后的方法如下:

#!/usr/bin/ruby
require '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}
        frac
end

def 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

def 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_field
end

def receive_timestamp(raw_data)
         (packet_data_by_field(raw_data)[:recv_time] + bin2frac(packet_data_by_field(raw_data)[:recv_time_fb])) - MY_NTP_ADJ
end

def 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))}"
end

def get_ntp_time(srv_addr)
  puts Net::NTP.get(srv_addr).time
end

if 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 1
ip:["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=>"00110000100110101101101011011
001"}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
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=>"00110000100110101101101011011
001"}
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
GET TIME IS : 2014-12-04 14:45:12 +0800

开源自有开源的好处,否则为了分析这点功能,只有载入IDA pro之类的重型武器了。还有一种办法是用抓包器看net-ntp发送的数据格式,看后照搬!正如看了《星际穿越》后觉得懂得了点神马一样:任何问题都有解决办法,无论什么样的问题!而且解决办法肯定不止一种!

时间: 2024-10-10 13:30:54

从ruby实现时间服务器ntp同步功能也谈“逆向工程”的相关文章

GPS网络时间服务器价格和功能区别

GPS卫星定位系统它可以应用在军事.国防.通信.授时等多个领域.GPS卫星定位系统应用在授时方面,是将卫星信号传送给设备并进行授时.GPS网络时间服务器是接收GPS卫星信号的时间服务器,它可以将卫星时间信号转换为网络.串口.秒脉冲等时间信息,能为用户提供相应的时间信息.GPS网络时间服务器主要输出网络时间信号,能在多种环境中进行授时,并且授时准确使用方便,改变了传统的钟表授时方式.时间服务器的功能和种类都所有不同,根据输出信号分为串口时间服务器,NTP网络时间服务器,CDMA时间服务器,根据卫星

Centos6.5时间服务器NTP搭建

NTP时间服务器安装与配置 第1章 Server端的安装与配置 1.1 查看系统是否已经安装ntp服务组件 rpm -qa | grep "ntp"                #<==查看是否已经安装ntp组件,有如下两个组件说明已经安装 ntpdate-4.2.6p5-1.el6.centos.x86_64 ntp-4.2.6p5-1.el6.centos.x86_64 1.2 yum安装 yum -y install ntp 1.3 ntp服务器端的配置 1.3.1 配置

时间服务器:NTP服务器

GMT时间 经度为零的地点在英国『格林威治』这个城市所在的纵剖面上 格林威治时间为标准时间 (Greenwich Mean Time, GMT 时间) 格林威治以东的区域时间是比较快的(+小时) 因此中国在东八区本地时间 (local time) 会比 GMT 时间快 8 小时 (GMT + 8) 1880 年代的时间标准是以 GMT 时间为主 夏季节约时间(daylight savings) 在夏天的时候,白天的时间会比较长,所以为了节约用电, 因此在夏天的时候某些横跨两个时区的地区会将他们的

时间服务器ntp

配置文件 server 127.127.1.0 fudge 127.127.1.0 stratum 10 driftfile /var/lib/ntp/drift

ntp时间服务器--Linux配置

时间服务器作用: 大数据产生与处理系统是各种计算设备集群的,计算设备将统一.同步的标准时间用于记录各种事件发生时序, 如E-MAIL信息.文件创建和访问时间.数据库处理时间等. 大数据系统内不同计算设备之间控制.计算.处理.应用等数据或操作都具有时序性, 若计算机时间不同步,这些应用或操作或将无法正常进行. 大数据系统是对时间敏感的计算处理系统,时间同步是大数据能够得到正确处理的基础保障,是大数据得以发挥作用的技术支撑. 大数据时代,整个处理计算系统内的大数据通信都是通过网络进行. 时间同步也是

Linux配置ntp时间服务器(全)

时间服务器作用: 大数据产生与处理系统是各种计算设备集群的,计算设备将统一.同步的标准时间用于记录各种事件发生时序, 如E-MAIL信息.文件创建和访问时间.数据库处理时间等. 大数据系统内不同计算设备之间控制.计算.处理.应用等数据或操作都具有时序性, 若计算机时间不同步,这些应用或操作或将无法正常进行. 大数据系统是对时间敏感的计算处理系统,时间同步是大数据能够得到正确处理的基础保障,是大数据得以发挥作用的技术支撑. 大数据时代,整个处理计算系统内的大数据通信都是通过网络进行. 时间同步也是

ntp时间服务器价格解析

关键词:ntp时间服务器价格,时间服务器 ntp时间服务器的价格也关乎着用户的选择,那么怎样能够选择一款价格适中并且功能居多的ntp时间服务器呢.首选我们要了解ntp时间服务器的定义.功能.原理.这样才能选择合适的ntp时间服务器. 那么ntp时间服务器是什么呢,ntp时间服务器是利用NTP网络时间协议进行校时的时间服务器,它是同步计算机的协议.ntp服务器是以卫星信号为时间基准,输出NTP网络.串口.秒脉冲等时间信息给终端设备进行校时.ntp服务器一般是以ntp网络信号授时的,以UTC世界统一

gps网络时间服务器的功能原理

gps网络时间服务器是以gps卫星信号为时间基准,并输出NTP网络时间信息,NTP是网络时间协议是用来同步计算机的一种协议,它可以在网络内采用广播发式传输标准时间数据包,网内需要时间同步的设备对操作系统进行相应设置,使自身的时间自动同步于NTP时间服务器,从而实现全网时间同步. gps网络时间服务器以GPS卫星作为时间基准,输出UTC国际标准时间进行授时.gps网络时间服务器一般由外部信号接收单元.信号处理单元.输出时间单元和授时设备所组成.gps网络时间服务器接收时间信号进行解码处理,并将UT

centos6.5下如何搭建ntp时间服务器

NTP对于我们个人来说有什么用呢,简单的讲,当你的计算机时间不准确了,你可以接入到互联网,从网上同步一下时间. 对于企业来说,当你有成百上千的计算机,都不能直接连接互联网,时间不统一影响业务,如果一台一台的修改,那不得累死人啊.这时搭建一个自己的NTP服务器就显出它的优势了. CentOS 6.5下配置好相关的yum源.确保你的ntp服务器可以连网 1.yum安装ntp服务 [[email protected] ~] yum install -y ntp 2.编译ntp的配置文件/etc/ntp