Skip to content

Commit 41aebd9

Browse files
committed
5.3.0
1 parent 1b1b34e commit 41aebd9

5 files changed

Lines changed: 165 additions & 5 deletions

File tree

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Knock Subdomain Scan v5.2.0
1+
# Knock Subdomain Scan v5.3.0
22

33
Knockpy is a python3 tool designed to quickly enumerate subdomains on a target domain through dictionary attack.
44

@@ -54,7 +54,9 @@ optional arguments:
5454
5555
--no-http-code CODE [CODE ...]
5656
http code list to ignore
57-
57+
58+
--dns DNS use custom DNS ex. 8.8.8.8
59+
5860
-w WORDLIST wordlist file to import
5961
-o FOLDER report folder to store json results
6062
-t SEC timeout in seconds
@@ -81,6 +83,11 @@ optional arguments:
8183
- DNS requests only, no http(s) requests will be made. This way the response will be much faster and you will get the IP address and the Subdomain.
8284
- The subdomain will be cyan in color if it is an ```alias``` and in that case the real host name will also be provided.
8385

86+
### Custom DNS
87+
```$ knockpy domain.com --dns 8.8.8.8```
88+
89+
- by default it uses the pre-configured DNS on your system (ex. /etc/resolv.conf).
90+
8491
### Set threads
8592
```$ knockpy domain.com -th 50```
8693

knockpy/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"remote"
2323
]
2424
},
25+
"dns": "",
2526
"api": {
2627
"virustotal": ""
2728
},

knockpy/dns_socket.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import socket
2+
import ipaddress
3+
4+
# https://github.com/1ocalhost/py_cheat/blob/master/dns_lookup.py
5+
6+
def parse_dns_string(reader, data):
7+
res = ''
8+
to_resue = None
9+
bytes_left = 0
10+
11+
for ch in data:
12+
if not ch:
13+
break
14+
15+
if to_resue is not None:
16+
resue_pos = chr(to_resue) + chr(ch)
17+
res += reader.reuse(resue_pos)
18+
break
19+
20+
if bytes_left:
21+
res += chr(ch)
22+
bytes_left -= 1
23+
continue
24+
25+
if (ch >> 6) == 0b11 and reader is not None:
26+
to_resue = ch - 0b11000000
27+
else:
28+
bytes_left = ch
29+
30+
if res:
31+
res += '.'
32+
33+
return res
34+
35+
36+
class StreamReader:
37+
def __init__(self, data):
38+
self.data = data
39+
self.pos = 0
40+
41+
def read(self, len_):
42+
pos = self.pos
43+
if pos >= len(self.data):
44+
raise
45+
46+
res = self.data[pos: pos+len_]
47+
self.pos += len_
48+
return res
49+
50+
def reuse(self, pos):
51+
pos = int.from_bytes(pos.encode(), 'big')
52+
# fix if pos == 107 convert it in -> pos = 75
53+
if chr(pos).islower(): pos = ord(chr(pos).upper())
54+
return parse_dns_string(None, self.data[pos:])
55+
56+
57+
def make_dns_query_domain(domain):
58+
def f(s):
59+
return chr(len(s)) + s
60+
61+
parts = domain.split('.')
62+
parts = list(map(f, parts))
63+
return ''.join(parts).encode()
64+
65+
66+
def make_dns_request_data(dns_query):
67+
req = b'\xaa\xbb\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00'
68+
req += dns_query
69+
req += b'\x00\x00\x01\x00\x01'
70+
return req
71+
72+
73+
def add_record_to_result(result, type_, data, reader):
74+
if type_ == 'A':
75+
item = str(ipaddress.IPv4Address(data))
76+
elif type_ == 'CNAME':
77+
item = parse_dns_string(reader, data)
78+
else:
79+
return
80+
81+
result.setdefault(type_, []).append(item)
82+
83+
84+
def parse_dns_response(res, dq_len, req):
85+
reader = StreamReader(res)
86+
87+
def get_query(s):
88+
return s[12:12+dq_len]
89+
90+
data = reader.read(len(req))
91+
assert(get_query(data) == get_query(req))
92+
93+
def to_int(bytes_):
94+
return int.from_bytes(bytes_, 'big')
95+
96+
result = {}
97+
res_num = to_int(data[6:8])
98+
for i in range(res_num):
99+
reader.read(2)
100+
type_num = to_int(reader.read(2))
101+
102+
type_ = None
103+
if type_num == 1:
104+
type_ = 'A'
105+
elif type_num == 5:
106+
type_ = 'CNAME'
107+
108+
reader.read(6)
109+
data = reader.read(2)
110+
data = reader.read(to_int(data))
111+
add_record_to_result(result, type_, data, reader)
112+
113+
return result
114+
115+
116+
def dns_lookup(domain, address):
117+
dns_query = make_dns_query_domain(domain)
118+
dq_len = len(dns_query)
119+
120+
req = make_dns_request_data(dns_query)
121+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
122+
sock.settimeout(2)
123+
124+
try:
125+
sock.sendto(req, (address, 53))
126+
res, _ = sock.recvfrom(1024 * 4)
127+
result = parse_dns_response(res, dq_len, req)
128+
except Exception:
129+
return
130+
finally:
131+
sock.close()
132+
133+
return result
134+
135+
136+
def _gethostbyname_ex(domain, address):
137+
result = dns_lookup(domain, address)
138+
if "CNAME" in result:
139+
host = result["CNAME"][-1]
140+
result["CNAME"].remove(host)
141+
result["CNAME"].append(domain)
142+
ipv4 = result["A"]
143+
return (host, result["CNAME"], ipv4)
144+
return (domain, [], result["A"])
145+

knockpy/knockpy.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
import colorama
99
import argparse
1010
import socket
11+
from . import dns_socket
1112
import requests
1213
import random
13-
import bs4
14+
import bs4
1415
import time
1516
import json
1617
import sys
@@ -30,6 +31,8 @@
3031
class Request():
3132
def dns(target):
3233
try:
34+
if config["dns"]:
35+
return dns_socket._gethostbyname_ex(target, config["dns"])
3336
return socket.gethostbyname_ex(target)
3437
except:
3538
return []
@@ -383,7 +386,7 @@ def plot(report):
383386
plt.show()
384387

385388
class Start():
386-
__version__ = "5.2.0"
389+
__version__ = "5.3.0"
387390

388391
def msg_rnd():
389392
return ["happy hacking ;)", "good luck!", "never give up!",
@@ -482,6 +485,7 @@ def arguments():
482485
parser.add_argument("--no-remote", help="remote wordlist ignore", action="store_true", required=False)
483486
parser.add_argument("--no-http", help="http requests ignore\n\n", action="store_true", required=False)
484487
parser.add_argument("--no-http-code", help="http code list to ignore\n\n", nargs="+", dest="code", type=int, required=False)
488+
parser.add_argument("--dns", help="use custom DNS ex. 8.8.8.8\n\n", dest="dns", required=False)
485489
parser.add_argument("-w", help="wordlist file to import", dest="wordlist", required=False)
486490
parser.add_argument("-o", help="report folder to store json results", dest="folder", required=False)
487491
parser.add_argument("-t", help="timeout in seconds", nargs=1, dest="sec", type=int, required=False)
@@ -524,6 +528,9 @@ def arguments():
524528
if args.wordlist:
525529
config["wordlist"]["local"] = args.wordlist
526530

531+
if args.dns:
532+
config["dns"] = args.dns
533+
527534
return domain
528535

529536

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup(
77
name="knockpy",
8-
version="5.2.0",
8+
version="5.3.0",
99
description="Knock is a python tool designed to quickly enumerate subdomains on a target domain through dictionary attack.",
1010
url="https://github.com/guelfoweb/knock",
1111
author="Gianni 'guelfoweb' Amato",

0 commit comments

Comments
 (0)