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