本猫以前写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:**************************************************
!