从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])
运行结果如下:
wisy@wisy-ThinkPad-X61:~/src/ruby_src$ ./dzh.rb pool.ntp.org 2014-12-04 14:06:20 +0800 wisy@wisy-ThinkPad-X61:~/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指令实现单步步入跟踪:
wisy@wisy-ThinkPad-X61:~/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 !@packet_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
看一下运行结果吧:
wisy@wisy-ThinkPad-X61:~/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发送的数据格式,看后照搬!正如看了《星际穿越》后觉得懂得了点神马一样:任何问题都有解决办法,无论什么样的问题!而且解决办法肯定不止一种!
从ruby实现时间服务器ntp同步功能也谈“逆向工程”的更多相关文章
- AIX配置时间服务器(NTP)
xntpd是关于网络时间协议的守护进程,它遵循了因特网时间服务器的通用标准.在启动 xntpd 时, xntpd 会读取 /etc/ntp.conf 配置文件来确定网络中系统时钟服务器,以 ntp 服 ...
- Centos6.5时间服务器NTP搭建
NTP时间服务器安装与配置 第1章 Server端的安装与配置 1.1 查看系统是否已经安装ntp服务组件 rpm -qa | grep "ntp" #<==查看是否已经安装 ...
- 搭建ntp时间服务器 ntp - (Network Time Protocol)
第1章 ntp 1.1 ntp简介 NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议.它的用途是把计算机的时钟同步到世界协调 ...
- Linux配置ntp时间服务器(全)
时间服务器作用: 大数据产生与处理系统是各种计算设备集群的,计算设备将统一.同步的标准时间用于记录各种事件发生时序, 如E-MAIL信息.文件创建和访问时间.数据库处理时间等. 大数据系统内不同计算设 ...
- ntp时间服务器--Linux配置
时间服务器作用: 大数据产生与处理系统是各种计算设备集群的,计算设备将统一.同步的标准时间用于记录各种事件发生时序, 如E-MAIL信息.文件创建和访问时间.数据库处理时间等. 大数据系统内不同 ...
- Linux NTP时间服务器
NTP 时间服务器 ntp也是一种协议 ntp软件(支持ntp协议) CentOS6自带 CentOS7需要安装 chrony软件(支持ntp协议) CentOS7自带 安装ntp CentOS ...
- SYN2102型 NTP网络时间服务器
SYN2102型 NTP网络时间服务器 ntp主时钟服务器ntp时钟服务器厂商使用说明视频链接: http://www.syn029.com/h-pd-57-0_310_1_-1.html 请将 ...
- ntp时间服务器 时间同步
具体两种模式 1.服务器数量比较少,可以直接与时间服务器同步 2.本地服务器较多,在本地自建时间同步服务器, 时间同步的两个命令 ntpd : 校准时间,一点点的校准过来时间的 ...
- Windows Server 中配置权威时间服务器
0" style="box-sizing: inherit; outline: none;"> 若要配置 Windows 时间服务以使用内部硬件时钟,请使用下列方法 ...
随机推荐
- 深入理解MyBatis框架的的配置信息
面对一个框架,最重要的不是说回用其代码就可以了,我们需要了解其思想,这样才能更快更好的掌握这个框架.而对于一个框架,最重要的就是其配置文件的作用及功能了.下面,我就来谈一谈我今天遇到的这个MyBati ...
- Dynamics CRM 2013 SP1 客户表单界面上联系人subgrid上的添加现有联系人功能缺失
CRM2013打了SP1的同学会发现一个问题,客户关联联系人的1:N关系,在表单subgrid中添加联系人时,只能新建而无法添加现有联系人,而这个现象在之前的版本中是没有的. 我们通过工具ribbon ...
- Java-IO之PrintWriter(字符打印输出流)
PrintWriter是字符类型的打印输出流,继承于Writer,用于向文本输出流打印对象的格式化表示形式. PrintWriter的主要函数: PrintWriter(OutputStream ou ...
- 安卓一键分享到qq,微信,微博,官方SDK非第三方
当我们项目中需要集成分享功能时,我们通常会采取一下几个办法: 1.调用系统自带分享 优点:简单快速,几行代码搞定,不需添加任何额外包: 缺点:系统会调出手机内部所有带分享功能的APP,且界面风格跟随系 ...
- Android进阶(三)android httpClient 支持HTTPS的访问方式
项目中Android https请求地址遇到了这个异常(无终端认证): javax.net.ssl.SSLPeerUnverifiedException: No peer certificate 是S ...
- 【unix网络编程第三版】ubuntu端口占用问题
<unix网络编程>一书中的代码并不是能直接运行,有时候需要结合各方面的知识来解决,大家在这本书的时候,一定要把代码都跑通,不难你会错过很多学习的机会! 1.问题描述 本人在阅读<U ...
- JSP连接MySQL时老是遇到驱动错误怎么办?
在使用JSP进行web开发的时候总是会不可避免的遇到各种各样的问题.今天我也来讲一讲我遇到的一些奇葩的问题. 驱动出错 一开始我总是以为是我导入到工程的里的jar包的问题,于是我就试验了好几个连接My ...
- (六十七)Xcode导入XMPPFramework框架
首先下载XMPPFramework框架,将Vendor内容导入到工程中,其中KissXML需要额外的框架,需要通过Xcode设置. 选择工程选项中TARGETS的General标签,最下侧有Linke ...
- 《java入门第一季》模拟用户登陆注册案例集合版
需求:校验用户名和密码,登陆成功后玩猜数字小游戏. 在这里先写集合版.后面还有IO版.数据库版. 一.猜数字小游戏类: 猜数字小游戏的代码见博客:http://blog.csdn.net/qq_320 ...
- javascript之BOM编程Screen(屏幕)对象
这个对象属性相对比较简单.掌握四个方法即可. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" & ...