1---
2-- This library implements a minimal subset of the BitCoin protocol
3-- It currently supports the version handshake and processing Addr responses.
4--
5-- The library contains the following classes:
6--
7-- * NetworkAddress - Contains functionality for encoding and decoding the
8--                    BitCoin network address structure.
9--
10-- * Request - Classs containing BitCoin client requests
11--     o Version - The client version exchange packet
12--
13-- * Response - Class containing BitCoin server responses
14--     o Version - The server version exchange packet
15--     o VerAck  - The server version ACK packet
16--     o Addr    - The server address packet
17--     o Inv     - The server inventory packet
18--
19-- * Helper - The primary interface to scripts
20--
21--@author Patrik Karlsson <patrik@cqure.net>
22--@author Andrew Orr <andrew@andreworr.ca>
23--@copyright Same as Nmap--See https://nmap.org/book/man-legal.html
24
25--
26-- Version 0.2
27--
28-- Created 11/09/2011 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
29-- Revised 17/02/2012 - v0.2 - fixed count parsing
30--                           - changed version/verack handling to support
31--                             February 20th 2012 bitcoin protocol switchover
32
33local ipOps = require "ipOps"
34local match = require "match"
35local nmap = require "nmap"
36local os = require "os"
37local stdnse = require "stdnse"
38local string = require "string"
39local table = require "table"
40local openssl = stdnse.silent_require('openssl')
41_ENV = stdnse.module("bitcoin", stdnse.seeall)
42
43-- A class that supports the BitCoin network address structure
44NetworkAddress = {
45
46  NODE_NETWORK = 1,
47
48  -- Creates a new instance of the NetworkAddress class
49  -- @param host table as received by the action method
50  -- @param port table as received by the action method
51  -- @return o instance of NetworkAddress
52  new = function(self, host, port)
53    local o = {
54      host = "table" == type(host) and host.ip or host,
55      port = "table" == type(port) and port.number or port,
56      service = NetworkAddress.NODE_NETWORK,
57    }
58    setmetatable(o, self)
59    self.__index = self
60    return o
61  end,
62
63  -- Creates a new instance of NetworkAddress based on the data string
64  -- @param data string of bytes
65  -- @return na instance of NetworkAddress
66  fromString = function(data)
67    assert(26 == #data, "Expected 26 bytes of data")
68
69    local na = NetworkAddress:new()
70    local ipv6_prefix, ipv4_addr
71    na.service, ipv6_prefix, ipv4_addr, na.port = string.unpack("<I8 c12 c4 >I2", data)
72    if ipv6_prefix == "\0\0\0\0\0\0\0\0\0\0\xff\xff" then
73      -- IPv4
74      na.host = ipOps.str_to_ip(ipv4_addr)
75    else
76      na.host = ipOps.str_to_ip(ipv6_prefix .. ipv4_addr)
77    end
78    return na
79  end,
80
81  -- Converts the NetworkAddress instance to string
82  -- @return data string containing the NetworkAddress instance
83  __tostring = function(self)
84    local ipv6_addr = ipOps.ip_to_str(self.host)
85    return string.pack("<I8 c16 >I2", self.service, ipv6_addr, self.port )
86  end
87}
88
89-- The request class container
90Request = {
91
92  -- The version request
93  Version = {
94
95    -- Creates a new instance of the Version request
96    -- @param host table as received by the action method
97    -- @param port table as received by the action method
98    -- @param lhost string containing the source IP
99    -- @param lport number containing the source port
100    -- @return o instance of Version
101    new = function(self, host, port, lhost, lport)
102      local o = {
103        host = host,
104        port = port,
105        lhost= lhost,
106        lport= lport,
107      }
108      setmetatable(o, self)
109      self.__index = self
110      return o
111    end,
112
113    -- Converts the Version request to a string
114    -- @return data as string
115    __tostring = function(self)
116      local magic = 0xD9B4BEF9
117      local cmd = "version"
118      local len = 85
119      -- ver: 0.4.0
120      local ver = 0x9c40
121
122      cmd = cmd .. ('\0'):rep(12 - #cmd)
123
124      -- NODE_NETWORK = 1
125      local services = 1
126      local timestamp = os.time()
127      local ra = NetworkAddress:new(self.host, self.port)
128      local sa = NetworkAddress:new(self.lhost, self.lport)
129      local nodeid = openssl.rand_bytes(8)
130      local useragent = "\0"
131      local lastblock = "\0\0\0\0"
132
133      -- Construct payload in order to calculate checksum for the header
134      local payload = (string.pack("<I4 I8 I8", ver, services, timestamp)
135        .. tostring(ra) .. tostring(sa) .. nodeid .. useragent .. lastblock)
136
137      -- Checksum is first 4 bytes of sha256(sha256(payload))
138      local checksum = openssl.digest("sha256", payload)
139      checksum = openssl.digest("sha256", checksum)
140
141      -- Construct the header without checksum
142      local header = string.pack("<I4 c12 I4", magic, cmd, len)
143
144      -- After 2012-02-20, version messages require checksums
145      header = header .. checksum:sub(1,4)
146
147      return header .. payload
148    end,
149  },
150
151  -- The GetAddr request
152  GetAddr = {
153
154    -- Creates a new instance of the Version request
155    -- @param host table as received by the action method
156    -- @param port table as received by the action method
157    -- @param lhost string containing the source IP
158    -- @param lport number containing the source port
159    -- @return o instance of Version
160    new = function(self, host, port, lhost, lport)
161      local o = {
162        host = host,
163        port = port,
164        lhost= lhost,
165        lport= lport,
166      }
167      setmetatable(o, self)
168      self.__index = self
169      return o
170    end,
171
172    -- Converts the Version request to a string
173    -- @return data as string
174    __tostring = function(self)
175      local magic = 0xD9B4BEF9
176      local cmd = "getaddr"
177      local len = 0
178      local chksum = 0xe2e0f65d
179      cmd = cmd .. ('\0'):rep(12 - #cmd)
180
181      return string.pack("<I4 c12 I4 I4", magic, cmd, len, chksum)
182    end
183  },
184
185  VerAck = {
186
187    new = function(self)
188      local o = {}
189      setmetatable(o, self)
190      self.__index = self
191      return o
192    end,
193
194    __tostring = function(self)
195      local cmd = "verack"
196      cmd = cmd .. ('\0'):rep(12 - #cmd)
197      return string.pack("<I4 c12 I4 I4", 0xD9B4BEF9, cmd, 0, 0xe2e0f65d)
198    end,
199
200   },
201
202  -- The pong message is sent in response to a ping message.
203  Pong = {
204    new = function(self)
205      local o = {}
206      setmetatable(o, self)
207      self.__index = self
208      return o
209    end,
210
211    __tostring = function(self)
212      local magic = 0xD9B4BEF9
213      local cmd = "pong"
214      local len = 0
215      local chksum = 0xe2e0f65d
216      cmd = cmd .. ('\0'):rep(12 - #cmd)
217
218      return string.pack("<I4 c12 I4 I4", magic, cmd, len, chksum)
219    end,
220
221  }
222
223}
224
225-- The response class container
226Response = {
227
228  Header = {
229    size = 24,
230    new = function(self)
231      local o = {
232        magic = 0,
233        cmd = "",
234        length = 0,
235        checksum = 0,
236      }
237      setmetatable(o, self)
238      self.__index = self
239      return o
240    end,
241
242    parse = function(data)
243      local header = Response.Header:new()
244
245      local cmd
246      header.magic, cmd, header.length, header.checksum = string.unpack(">I4 c12 I4 I4", data)
247      header.cmd = string.unpack("z", cmd)
248      return header
249    end,
250  },
251
252
253  Alert = {
254
255    type = "Alert",
256    -- Creates a new instance of Version based on data string
257    -- @param data string containing the raw response
258    -- @return o instance of Version
259    new = function(self, data)
260      local o = {
261        data = data,
262      }
263      setmetatable(o, self)
264      self.__index = self
265      o:parse()
266      return o
267    end,
268
269    -- Parses the raw data and builds the Version instance
270    parse = function(self)
271      local pos = Response.Header.size + 1
272      self.header = Response.Header.parse(self.data)
273
274      local data
275      pos, data = Util.decodeVarString(self.data, pos)
276
277      --
278      -- TODO: Alert decoding goes here
279      --
280
281      return
282    end,
283  },
284
285
286  -- The version response message
287  Version = {
288
289    -- Creates a new instance of Version based on data string
290    -- @param data string containing the raw response
291    -- @return o instance of Version
292    new = function(self, data)
293      local o = { data = data }
294      setmetatable(o, self)
295      self.__index = self
296      o:parse()
297      return o
298    end,
299
300    -- Parses the raw data and builds the Version instance
301    parse = function(self)
302      local ra, sa, cmd, nodeid, pos
303
304      -- After 2012-02-20, version messages contain checksums
305      self.magic, cmd, self.len, self.checksum, self.ver_raw, self.service,
306        self.timestamp, ra, sa, nodeid,
307        pos = string.unpack("<I4 c12 I4 I4 I4 I8 I8 c26 c26 c8", self.data)
308      pos, self.user_agent = Util.decodeVarString(self.data, pos)
309      self.lastblock, pos = string.unpack("<I4", self.data, pos)
310      self.nodeid = stdnse.tohex(nodeid)
311      self.cmd = string.unpack("z", cmd)
312
313      local function decode_bitcoin_version(n)
314        if ( n < 31300 ) then
315          local minor, micro = n // 100, n % 100
316          return ("0.%d.%d"):format(minor, micro)
317        else
318          local minor, micro = n // 10000, (n // 100) % 100
319          return ("0.%d.%d"):format(minor, micro)
320        end
321      end
322
323      self.ver = decode_bitcoin_version(self.ver_raw)
324      self.sa = NetworkAddress.fromString(sa)
325      self.ra = NetworkAddress.fromString(ra)
326    end,
327  },
328
329  -- The verack response message
330  VerAck = {
331
332    -- Creates a new instance of VerAck based on data string
333    -- @param data string containing the raw response
334    -- @return o instance of Version
335    new = function(self, data)
336      local o = { data = data }
337      setmetatable(o, self)
338      self.__index = self
339      o:parse()
340      return o
341    end,
342
343    -- Parses the raw data and builds the VerAck instance
344    parse = function(self)
345      local cmd
346      -- After 2012-02-20, VerAck messages contain checksums
347      self.magic, cmd, self.checksum = string.unpack("<I4 c12 I4", self.data)
348      self.cmd = string.unpack("z", cmd)
349    end,
350  },
351
352  -- The Addr response message
353  Addr = {
354
355    -- Creates a new instance of VerAck based on data string
356    -- @param data string containing the raw response
357    -- @return o instance of Addr
358    new = function(self, data, version)
359      local o = { data = data, version=version }
360      setmetatable(o, self)
361      self.__index = self
362      o:parse()
363      return o
364    end,
365
366    -- Parses the raw data and builds the Addr instance
367    parse = function(self)
368      local pos, count
369      local cmd
370      self.magic, cmd, self.len, self.chksum, pos = string.unpack("<I4 c12 I4 I4", self.data)
371      self.cmd = string.unpack("z", cmd)
372      pos, count = Util.decodeVarInt(self.data, pos)
373
374      self.addresses = {}
375      for c=1, count do
376        if ( self.version > 31402 ) then
377          local timestamp, data
378          timestamp, data, pos = string.unpack("<I4 c26", self.data, pos)
379          local na = NetworkAddress.fromString(data)
380          table.insert(self.addresses, { ts = timestamp, address = na })
381        end
382      end
383
384    end,
385  },
386
387  -- The inventory server packet
388  Inv = {
389
390    -- Creates a new instance of Inv based on data string
391    -- @param data string containing the raw response
392    -- @return o instance of Inv
393    new = function(self, data, version)
394      local o = { data = data, version=version }
395      setmetatable(o, self)
396      self.__index = self
397      o:parse()
398      return o
399    end,
400
401    -- Parses the raw data and builds the Inv instance
402    parse = function(self)
403      local cmd
404      self.magic, cmd, self.len, self.chksum = string.unpack("<I4 c12 I4 I4", self.data)
405      self.cmd = string.unpack("z", cmd)
406      -- TODO parse inv_vect
407    end,
408  },
409
410  -- Receives the packet and decodes it
411  -- @param socket socket connected to the server
412  -- @param version number containing the server version
413  -- @return status true on success, false on failure
414  -- @return response instance of response packet if status is true
415  --         err string containing the error message if status is false
416  recvPacket = function(socket, version)
417    local status, header = socket:receive_buf(match.numbytes(24), true)
418    if ( not(status) ) then
419      return false, "Failed to read the packet header"
420    end
421
422    local magic, cmd, len, checksum = string.unpack("<I4 c12 I4 I4", header)
423    local data = ""
424    cmd = string.unpack("z", cmd)
425
426    -- the verack and ping has no payload
427    if ( 0 ~= len ) then
428      status, data = socket:receive_buf(match.numbytes(len), true)
429      if ( not(status) ) then
430        return false, "Failed to read the packet header"
431      end
432    else
433      -- The ping message is sent primarily to confirm that the TCP/IP connection is still valid.
434      if( cmd == "ping" ) then
435        local req = Request.Pong:new()
436
437        local status, err = socket:send(tostring(req))
438        if ( not(status) ) then
439          return false, "Failed to send \"Pong\" reply to server"
440        else
441          return Response.recvPacket(socket, version)
442        end
443      end
444    end
445    return Response.decode(header .. data, version)
446  end,
447
448  -- Decodes the raw packet data
449  -- @param data string containing the raw packet
450  -- @param version number containing the server version
451  -- @return status true on success, false on failure
452  -- @return response instance of response packet if status is true
453  --         err string containing the error message if status is false
454  decode = function(data, version)
455    local magic, cmd = string.unpack("<I4 z", data)
456    if ( "version" == cmd ) then
457      return true, Response.Version:new(data)
458    elseif ( "verack" == cmd ) then
459      return true, Response.VerAck:new(data)
460    elseif ( "addr" == cmd ) then
461      return true, Response.Addr:new(data, version)
462    elseif ( "inv" == cmd ) then
463      return true, Response.Inv:new(data)
464    elseif ( "alert" == cmd ) then
465      return true, Response.Alert:new(data)
466    else
467      return true, ("Unknown command (%s)"):format(cmd)
468    end
469  end,
470}
471
472Util = {
473
474  varIntLen = {
475    [0xfd] = 2,
476    [0xfe] = 4,
477    [0xff] = 8,
478  },
479
480  -- Decodes a variable length int
481  -- @param data string of data
482  -- @param pos the location within the string to decode
483  -- @return pos the new position
484  -- @return count number the decoded argument
485  decodeVarInt = function(data, pos)
486    local count, pos = string.unpack("B", data, pos)
487    if count >= 0xfd then
488      count, pos = string.unpack("<I" .. Util.varIntLen[count], data, pos)
489    end
490    return pos, count
491  end,
492
493  decodeVarString = function(data, pos)
494    local count, pos = string.unpack("B", data, pos)
495    local str
496    if count < 0xfd then
497      str, pos = string.unpack("s1", data, pos - 1)
498    else
499      str, pos = string.unpack("<s" .. Util.varIntLen[count], data, pos)
500    end
501    return pos, str
502  end,
503
504}
505
506-- The Helper class used as a primary interface to scripts
507Helper = {
508
509  -- Creates a new Helper instance
510  -- @param host table as received by the action method
511  -- @param port table as received by the action method
512  -- @param options table containing additional options
513  --    <code>timeout</code> - the socket timeout in ms
514  -- @return instance of Helper
515  new = function(self, host, port, options)
516    local o = {
517      host = host,
518      port = port,
519      options = options or {}
520    }
521    setmetatable(o, self)
522    self.__index = self
523    return o
524  end,
525
526  -- Connects to the BitCoin Server
527  -- @return status true on success false on failure
528  -- @return err string containing the error message in case status is false
529  connect = function(self)
530    self.socket = nmap.new_socket()
531    self.socket:set_timeout(self.options.timeout or 10000)
532    local status, err = self.socket:connect(self.host, self.port)
533
534    if ( not(status) ) then
535      return false, err
536    end
537    status, self.lhost, self.lport = self.socket:get_info()
538    return status, (status and nil or self.lhost)
539  end,
540
541  -- Performs a version handshake with the server
542  -- @return status, true on success false on failure
543  -- @return version instance if status is true
544  --         err string containing an error message if status is false
545  exchVersion = function(self)
546    if ( not(self.socket) ) then
547      return false
548    end
549
550    local req = Request.Version:new(
551      self.host, self.port, self.lhost, self.lport
552    )
553
554    local status, err = self.socket:send(tostring(req))
555    if ( not(status) ) then
556      return false, "Failed to send \"Version\" request to server"
557    end
558
559    local version
560    status, version = Response.recvPacket(self.socket)
561
562    if not status or not version then
563      return false, "Failed to read \"Version\" response from server: " .. (version or "nil")
564    elseif version.cmd ~= "version"  then
565      return false, ('"Version" request got %s from server'):format(version.cmd)
566    end
567
568    if ( version.ver_raw > 29000 ) then
569      local status, verack = Response.recvPacket(self.socket)
570    end
571
572    local verack = Request.VerAck:new()
573    local status, err = self.socket:send(tostring(verack))
574    if ( not(status) ) then
575      return false, "Failed to send \"Version\" request to server"
576    end
577
578    self.version = version.ver_raw
579    return status, version
580  end,
581
582  getNodes = function(self)
583    local req = Request.GetAddr:new(
584      self.host, self.port, self.lhost, self.lport
585    )
586
587    local status, err = self.socket:send(tostring(req))
588    if ( not(status) ) then
589      return false, "Failed to send \"GetAddr\" request to server"
590    end
591
592    local status, response = Response.recvPacket(self.socket, self.version)
593    local all_addrs = {}
594    local limit = 10
595    -- Usually sends an addr response with 1 address,
596    -- then some other stuff like getheaders or ping,
597    -- then one with hundreds of addrs.
598    while status and #all_addrs <= 1 and limit > 0 do
599      limit = limit - 1
600      status, response = Response.recvPacket(self.socket, self.version)
601      if status and response.cmd == "addr" then
602        for _, addr in ipairs(response.addresses) do
603          all_addrs[#all_addrs+1] = addr
604        end
605      end
606    end
607
608    return #all_addrs > 0, all_addrs
609  end,
610
611  -- Reads a message from the server
612  -- @return status true on success, false on failure
613  -- @return response instance of response packet if status is true
614  --         err string containing the error message if status is false
615  readMessage = function(self)
616    assert(self.version, "Version handshake has not been performed")
617    return Response.recvPacket(self.socket, self.version)
618  end,
619
620  -- Closes the connection to the server
621  -- @return status true on success false on failure
622  -- @return err code, if status is false
623  close = function(self)
624    return self.socket:close()
625  end
626}
627
628return _ENV;
629