TLS上的DNS-使用Stunnel和Lua加密我们的DNS查询


图片来源


DNS (Eng。Domain Name System-域名系统)-用于获取有关域的信息的分布式计算机系统。

TLS传输层安全协议)-提供Internet节点之间的安全数据传输。

消息传出后, “ Google Public DNS悄然启用了TLS支持的DNS”,我决定尝试一下。 我有一个Stunnel ,它将创建一个加密的TCP隧道。 但是程序通常使用UDP协议与DNS通信。 因此,我们需要一个代理,该代理将向与TCP流之间的UDP数据包转发。 我们将其写在Lua上


TCP和UDP DNS数据包之间的整体差异:


4.2.2。 TCP使用
通过TCP连接发送的消息使用服务器端口53(十进制)。 消息的前缀是两个字节的长度字段,该字段给出了消息的长度,但不包括两个字节的长度字段。 此长度字段允许低级处理在开始解析之前组装完整的消息。

RFC1035:域名-实施和规范


也就是说,我们在那里:


  1. 我们从UDP获取一个数据包
  2. 在开头添加几个字节,其中指示此数据包的大小
  3. 发送到TCP通道

而在相反的方向:


  1. 我们从TCP读取了几个字节,我们得到了数据包的大小
  2. 我们从TCP读取了一个数据包
  3. 通过UDP将其发送给收件人

自定义通道


  1. 使用Stunnel配置在目录中下载根证书Root-R2.crt
  2. 将证书转换为PEM
    openssl x509 -inform DER -in Root-R2.crt -out Root-R2.pem -text 
  3. 我们在stunnel.conf中编写:


     [dns] client = yes accept = 127.0.0.1:53 connect = 8.8.8.8:853 CAfile = Root-R2.pem verifyChain = yes checkIP = 8.8.8.8 


也就是说,Stunnel:


  1. 将在127.0.0.1:53接受未加密的TCP
  2. 将打开一个加密的TLS隧道以寻址8.8.8.8:853(Google DNS)
  3. 将来回传输数据

发射隧道


可以使用以下命令检查隧道的运行情况:


 nslookup -vc ya.ru 127.0.0.1 

-vc选项强制nslookup使用与DNS服务器的TCP连接而不是UDP。


结果:


 *** Can't find server name for address 127.0.0.1: Non-existent domain Server: UnKnown Address: 127.0.0.1 Non-authoritative answer: Name: ya.ru Address: ( IP ) 

编写脚本


我正在用Lua 5.3编写。 带有数字的二进制运算已经可用。 好吧,我们将需要Lua Socket模块。


文件名: simple-udp-to-tcp-dns-proxy.lua


 local socket = require "socket" --  lua socket 

--[[--


让我们编写一个简单的函数,使您可以将转储包发送到控制台。 我想看看代理的作用。


--]]--


 function serialize(data) --       az  0-9    HEX  '\xFF' return "'"..data:gsub("[^a-z0-9-]", function(chr) return ("\\x%02X"):format(chr:byte()) end).."'" end 

--[[--


UDP至TCP并返回


我们编写了两个可以在两个数据传输通道上运行的函数。


--]]--


 --    UDP   TCP  function udp_to_tcp_coroutine_function(udp_in, tcp_out, clients) repeat coroutine.yield() --     packet, err_ip, port = udp_in:receivefrom() --  UDP  if packet then -- > - big endian -- I - unsigned integer -- 2 - 2 bytes size tcp_out:send(((">I2"):pack(#packet))..packet) --       TCP local id = (">I2"):unpack(packet:sub(1,2)) --  ID  if not clients[id] then clients[id] = {} end table.insert(clients[id] ,{ip=err_ip, port=port, packet=packet}) --    print(os.date("%c", os.time()) ,err_ip, port, ">", serialize(packet)) --     end until false end --    TCP      UDP function tcp_to_udp_coroutine_function(tcp_in, udp_out, clients) repeat coroutine.yield() --     -- > - big endian -- I - unsigned integer -- 2 - 2 bytes size local packet = tcp_in:receive((">I2"):unpack(tcp_in:receive(2)), nil) --  TCP  local id = (">I2"):unpack(packet:sub(1,2)) --  ID  if clients[id] then for key, client in pairs(clients[id]) do --  query     if packet:find(client.packet:sub(13, -1), 13, true) == 13 then --   udp_out:sendto(packet, client.ip, client.port) --     UDP clients[id][key] = nil --   --     print(os.date("%c", os.time()) ,client.ip, client.port, "<", serialize(packet)) break end end if not next(clients[id]) then clients[id] = nil end end until false end 

--[[--


这两个函数在启动后立即执行coroutine.yield()。 这使您可以将函数的参数传递给第一个调用,然后执行coroutine.resume(co)而无需其他参数。


主要的


现在,主要功能将准备并启动主循环。


--]]--


 function main() local tcp_dns_socket = socket.tcp() --  TCP  local udp_dns_socket = socket.udp() --  UDP  local tcp_connected, err = tcp_dns_socket:connect("127.0.0.1", 53) --   TCP  assert(tcp_connected, err) --    print("tcp dns connected") --      local udp_open, err = udp_dns_socket:setsockname("127.0.0.1", 53) --  UDP  assert(udp_open, err) --    print("udp dns port open") --   UDP   --     Lua        nil --              local coroutines = { [tcp_dns_socket] = coroutine.create(tcp_to_udp_coroutine_function), --   TCP to UDP [udp_dns_socket] = coroutine.create(udp_to_tcp_coroutine_function) --   UDP to TCP } local clients = {} --      --        coroutine.resume(coroutines[tcp_dns_socket], tcp_dns_socket, udp_dns_socket, clients) coroutine.resume(coroutines[udp_dns_socket], udp_dns_socket, tcp_dns_socket, clients) --    socket.select        local socket_list = {tcp_dns_socket, udp_dns_socket} repeat --    -- socket.select   socket_list          --      .  for       for _, in_socket in ipairs(socket.select(socket_list)) do --       local ok, err = coroutine.resume(coroutines[in_socket]) if not ok then --       udp_dns_socket:close() --  UDP  tcp_dns_socket:close() --  TCP  print(err) --   return --    end end until false end 

--[[--


我们启动主要功能。 如果连接突然关闭,一秒钟后,我们将通过调用main再次建立连接。


--]]--


 repeat local ok, err = coroutine.resume(coroutine.create(main)) --  main if not ok then print(err) end socket.sleep(1) --      until false 

检查


  1. 运行漏斗


  2. 运行我们的脚本


     lua5.3 simple-udp-to-tcp-dns-proxy.lua 

  3. 使用命令检查脚本操作


     nslookup ya.ru 127.0.0.1 

    这次没有'-vc',这就是我们编写和启动代理的方式,该代理将UDP DNS请求包装到TCP隧道中。



结果:


 *** Can't find server name for address 127.0.0.1: Non-existent domain Server: UnKnown Address: 127.0.0.1 Non-authoritative answer: Name: ya.ru Address: ( IP ) 

如果一切正常,则可以在连接设置中将DNS服务器指定为“ 127.0.0.1”


结论


现在我们的DNS查询受TLS保护。


附言:我们不会向Google提供有关我们的更多信息


连接后,Windows会尝试通过我们的隧道在Google的DNS服务器上注册我们。 通过取消选中此选项可在高级DNS设置中将其禁用。



仍然需要time.windows.com。 他不再那么个性,但我从未找到如何关闭它的方法。 自动时间同步已禁用。


连结


  1. RFC1035:域名-实施和规范
  2. TLS上的DNS
  3. 简单的udp到tcp-dns-proxy.lua
  4. 手动撰写DNS查询

Source: https://habr.com/ru/post/zh-CN427957/


All Articles