Skip to content

Commit da0af08

Browse files
committed
control ntp and dns queries too
1 parent 2e9fcad commit da0af08

File tree

5 files changed

+575
-1
lines changed

5 files changed

+575
-1
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tch-exploit",
3-
"version": "2.0.0-b7",
3+
"version": "2.0.0-rc1",
44
"main": "dist/index.js",
55
"bin": "dist/index.js",
66
"scripts": {

src/dns.coffee

+305
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
{ EventEmitter } = require'events'
2+
{ createSocket } = require('dgram')
3+
{ isIPv6 } = require('net')
4+
5+
dns = require('dns')
6+
7+
bitSlice = (b, offset, length) ->
8+
b >>> 7 - (offset + length - 1) & ~(0xff << length)
9+
10+
bufferify = (ip) ->
11+
if isIPv6 ip
12+
bufferifyV6 ip
13+
else bufferifyV4 ip
14+
15+
bufferifyV4 = (ip) ->
16+
ip = ip.split('.').map (n) -> parseInt n, 10
17+
18+
result = 0
19+
base = 1
20+
21+
i = ip.length - 1
22+
23+
while i >= 0
24+
result += ip[i] * base
25+
base *= 256
26+
i--
27+
28+
buf = Buffer.alloc(4)
29+
buf.writeUInt32BE result
30+
buf
31+
32+
bufferifyV6 = (rawIp) ->
33+
34+
countColons = (x) ->
35+
n = 0
36+
x.replace /:/g, (c) -> n++
37+
n
38+
39+
ip = rawIp.replace(/\/\d{1,3}(?=%|$)/, '').replace(/%.*$/, '')
40+
41+
hexIp = ip
42+
.replace /::/, (two) -> ':' + Array(7 - countColons(ip) + 1).join(':') + ':'
43+
.split ':'
44+
.map (x) -> Array(4 - (x.length)).fill('0').join('') + x
45+
.join('')
46+
47+
Buffer.from hexIp, 'hex'
48+
49+
domainify = (qname) ->
50+
parts = []
51+
52+
i = 0
53+
54+
while i < qname.length and qname[i]
55+
length = qname[i]
56+
offset = i + 1
57+
parts.push qname.slice(offset, offset + length).toString()
58+
59+
i = offset + length
60+
61+
parts.join '.'
62+
63+
qnameify = (domain) ->
64+
qname = Buffer.alloc(domain.length + 2)
65+
offset = 0
66+
67+
domain = domain.split('.')
68+
69+
i = 0
70+
71+
while i < domain.length
72+
qname[offset] = domain[i].length
73+
qname.write domain[i], offset + 1, domain[i].length, 'ascii'
74+
75+
offset += qname[offset] + 1
76+
i++
77+
78+
qname[qname.length - 1] = 0
79+
qname
80+
81+
functionify = (val) ->
82+
(addr, callback) ->
83+
callback null, val
84+
85+
parse = (buf) ->
86+
header = {}
87+
question = {}
88+
89+
b = buf.slice(2, 3).toString('binary', 0, 1).charCodeAt(0)
90+
91+
header.id = buf.slice(0, 2)
92+
header.qr = bitSlice(b, 0, 1)
93+
header.opcode = bitSlice(b, 1, 4)
94+
header.aa = bitSlice(b, 5, 1)
95+
header.tc = bitSlice(b, 6, 1)
96+
header.rd = bitSlice(b, 7, 1)
97+
98+
b = buf.slice(3, 4).toString('binary', 0, 1).charCodeAt(0)
99+
100+
header.ra = bitSlice(b, 0, 1)
101+
header.z = bitSlice(b, 1, 3)
102+
header.rcode = bitSlice(b, 4, 4)
103+
header.qdcount = buf.slice(4, 6)
104+
header.ancount = buf.slice(6, 8)
105+
header.nscount = buf.slice(8, 10)
106+
header.arcount = buf.slice(10, 12)
107+
108+
question.qname = buf.slice(12, buf.length - 4)
109+
question.qtype = buf.slice(buf.length - 4, buf.length - 2)
110+
question.qclass = buf.slice(buf.length - 2, buf.length)
111+
112+
{ header, question }
113+
114+
responseBuffer = (query) ->
115+
question = query.question
116+
header = query.header
117+
qname = question.qname
118+
119+
offset = 16 + qname.length
120+
121+
length = offset
122+
i = 0
123+
124+
while i < query.rr.length
125+
length += query.rr[i].qname.length + 10
126+
i++
127+
128+
buf = Buffer.alloc(length)
129+
130+
header.id.copy buf, 0, 0, 2
131+
132+
buf[2] = 0x00 | header.qr << 7 | header.opcode << 3 | header.aa << 2 | header.tc << 1 | header.rd
133+
buf[3] = 0x00 | header.ra << 7 | header.z << 4 | header.rcode
134+
135+
buf.writeUInt16BE header.qdcount, 4
136+
buf.writeUInt16BE header.ancount, 6
137+
buf.writeUInt16BE header.nscount, 8
138+
buf.writeUInt16BE header.arcount, 10
139+
140+
qname.copy buf, 12
141+
142+
question.qtype.copy buf, 12 + qname.length, question.qtype, 2
143+
question.qclass.copy buf, 12 + qname.length + 2, question.qclass, 2
144+
145+
i = 0
146+
147+
while i < query.rr.length
148+
rr = query.rr[i]
149+
rr.qname.copy buf, offset
150+
offset += rr.qname.length
151+
buf.writeUInt16BE rr.qtype, offset
152+
buf.writeUInt16BE rr.qclass, offset + 2
153+
buf.writeUInt32BE rr.ttl, offset + 4
154+
buf.writeUInt16BE rr.rdlength, offset + 8
155+
buf = Buffer.concat([
156+
buf
157+
rr.rdata
158+
])
159+
offset += 14
160+
i++
161+
buf
162+
163+
response = (query, ttl, to) ->
164+
response = {}
165+
166+
header = response.header = {}
167+
question = response.question = {}
168+
rrs = resolve(query.question.qname, ttl, to)
169+
170+
header.id = query.header.id
171+
header.ancount = rrs.length
172+
header.qr = 1
173+
header.opcode = 0
174+
header.aa = 0
175+
header.tc = 0
176+
header.rd = 1
177+
header.ra = 0
178+
header.z = 0
179+
header.rcode = 0
180+
header.qdcount = 1
181+
header.nscount = 0
182+
header.arcount = 0
183+
184+
question.qname = query.question.qname
185+
question.qtype = query.question.qtype
186+
question.qclass = query.question.qclass
187+
188+
response.rr = rrs
189+
190+
responseBuffer response
191+
192+
resolve = (qname, ttl, to) ->
193+
r = {}
194+
r.qname = qname
195+
r.qtype = if to.length == 4 then 1 else 28
196+
197+
r.qclass = 1
198+
r.ttl = ttl
199+
r.rdlength = to.length
200+
r.rdata = to
201+
202+
[ r ]
203+
204+
lookup = (addr, callback) ->
205+
if net.isIP(addr)
206+
return callback(null, addr)
207+
208+
dns.lookup addr, callback
209+
210+
class Server extends EventEmitter
211+
constructor: (proxy = '8.8.8.8') ->
212+
super
213+
214+
@_socket = createSocket if isIPv6(proxy) then 'udp6' else 'udp4'
215+
216+
routes = []
217+
218+
@_socket.on 'message', (message, rinfo) =>
219+
query = parse message
220+
domain = domainify query.question.qname
221+
222+
routeData = { domain, rinfo }
223+
224+
@emit 'resolve', routeData
225+
226+
respond = (buf) =>
227+
@_socket.send buf, 0, buf.length, rinfo.port, rinfo.address
228+
229+
onerror = (err) =>
230+
@emit 'error', err
231+
232+
onproxy = ->
233+
sock = createSocket if isIPv6(proxy) then 'udp6' else 'udp4'
234+
sock.send message, 0, message.length, 53, proxy
235+
236+
sock.on 'error', onerror
237+
238+
sock.on 'message', (response) ->
239+
respond response
240+
sock.close()
241+
242+
i = 0
243+
244+
while i < routes.length
245+
if routes[i].pattern.test(domain)
246+
route = routes[i].route
247+
break
248+
i++
249+
250+
if not route
251+
return onproxy()
252+
253+
route routeData, (err, to) =>
254+
if typeof to == 'string'
255+
toIp = to
256+
ttl = 1
257+
else
258+
toIp = to.ip
259+
ttl = to.ttl
260+
261+
if err
262+
return onerror(err)
263+
264+
if !toIp
265+
return onproxy()
266+
267+
lookup toIp, (err, addr) =>
268+
if err
269+
return onerror(err)
270+
271+
@emit 'route', domain, addr
272+
273+
respond response(query, ttl, bufferify(addr))
274+
275+
route: (pattern, route) ->
276+
if Array.isArray pattern
277+
pattern.forEach (item) =>
278+
@route item, route
279+
280+
return @
281+
282+
if typeof pattern == 'function'
283+
return @route('*', pattern)
284+
285+
if typeof route == 'string'
286+
return @route(pattern, functionify(route))
287+
288+
pattern = if pattern is '*'
289+
/.?/
290+
else
291+
new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*\\\./g, '(.+)\\.') + '$', 'i')
292+
293+
routes.push { pattern, route }
294+
295+
@
296+
297+
listen: (port) ->
298+
@@_socket.bind port or 53
299+
300+
@
301+
302+
close: (callback) ->
303+
@@_socket.close callback
304+
305+
@

src/ntp/index.coffee

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict'
2+
3+
Packet = require './packet'
4+
5+
{ createSocket } = require 'dgram'
6+
{ EventEmitter } = require 'events'
7+
8+
class NTP extends EventEmitter
9+
10+
constructor: (options, callback) ->
11+
if typeof options is 'function'
12+
callback = options
13+
options = {}
14+
15+
Object.assign @, {
16+
server: 'pool.ntp.org'
17+
port: 123
18+
}, options
19+
20+
@socket = new createSocket 'udp4'
21+
22+
if typeof callback is 'function'
23+
@time callback
24+
25+
time: (callback) ->
26+
{ server, port, timeout } = @
27+
28+
packet = NTP.createPacket()
29+
30+
@socket.send packet, 0, packet.length, port, server, (err) =>
31+
if err
32+
return callback err
33+
34+
@socket.once 'message', (data) ->
35+
@socket.close()
36+
37+
message = NTP.parse(data)
38+
39+
callback err, message
40+
41+
@
42+
43+
@time: (options, callback) ->
44+
new NTP(options, callback)
45+
46+
@createPacket: ->
47+
packet = new Packet
48+
packet.mode = Packet.MODES.CLIENT
49+
packet.originateTimestamp = Date.now()
50+
packet.toBuffer()
51+
52+
@parse: (buffer) ->
53+
message = Packet.parse(buffer)
54+
message.destinationTimestamp = Date.now()
55+
message.time = new Date(message.transmitTimestamp)
56+
57+
T1 = message.originateTimestamp
58+
T2 = message.receiveTimestamp
59+
T3 = message.transmitTimestamp
60+
T4 = message.destinationTimestamp
61+
62+
message.d = T4 - T1 - (T3 - T2)
63+
message.t = (T2 - T1 + T3 - T4) / 2
64+
message
65+
66+
exports.Client = NTP
67+
68+
exports.Server = require './server'
69+
70+
exports.createServer = (options) ->
71+
new exports.Server options
72+
73+
module.exports = NTP

0 commit comments

Comments
 (0)