btyscan 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- btyscan-0.1.0/PKG-INFO +4 -0
- btyscan-0.1.0/btyscan.egg-info/PKG-INFO +4 -0
- btyscan-0.1.0/btyscan.egg-info/SOURCES.txt +7 -0
- btyscan-0.1.0/btyscan.egg-info/dependency_links.txt +1 -0
- btyscan-0.1.0/btyscan.egg-info/entry_points.txt +2 -0
- btyscan-0.1.0/btyscan.egg-info/top_level.txt +1 -0
- btyscan-0.1.0/btyscan.py +198 -0
- btyscan-0.1.0/pyproject.toml +14 -0
- btyscan-0.1.0/setup.cfg +4 -0
btyscan-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
btyscan
|
btyscan-0.1.0/btyscan.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""BTYscan - Termux용 미니 네트워크 스캐너
|
|
3
|
+
|
|
4
|
+
기능: 호스트 탐색 / 포트 스캔 / 서비스 이름 / 배너 확인
|
|
5
|
+
사용법:
|
|
6
|
+
python btyscan.py discover # 내 와이파이 대역 자동 탐색
|
|
7
|
+
python btyscan.py discover 192.168.0.0/24
|
|
8
|
+
python btyscan.py scan 192.168.0.1 # 주요 포트 스캔
|
|
9
|
+
python btyscan.py scan 192.168.0.1 -p 1-1024
|
|
10
|
+
python btyscan.py scan 192.168.0.1 -p 22,80,443 -n (-n: 배너 확인 끔)
|
|
11
|
+
python btyscan.py auto # 탐색 + 발견된 모든 기기 스캔
|
|
12
|
+
|
|
13
|
+
※ 본인 소유 기기/허가받은 네트워크에서만 사용하세요.
|
|
14
|
+
※ 루트 없이 동작하도록 TCP 연결 방식만 사용합니다.
|
|
15
|
+
"""
|
|
16
|
+
import argparse
|
|
17
|
+
import errno
|
|
18
|
+
import ipaddress
|
|
19
|
+
import socket
|
|
20
|
+
import ssl
|
|
21
|
+
import time
|
|
22
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
23
|
+
|
|
24
|
+
SERVICES = {
|
|
25
|
+
21: "ftp", 22: "ssh", 23: "telnet", 25: "smtp", 53: "dns", 80: "http",
|
|
26
|
+
110: "pop3", 111: "rpcbind", 135: "msrpc", 139: "netbios", 143: "imap",
|
|
27
|
+
443: "https", 445: "smb", 465: "smtps", 587: "submission", 993: "imaps",
|
|
28
|
+
995: "pop3s", 1723: "pptp", 3306: "mysql", 3389: "rdp", 5432: "postgres",
|
|
29
|
+
5900: "vnc", 8000: "http-alt", 8080: "http-proxy", 8443: "https-alt",
|
|
30
|
+
}
|
|
31
|
+
TOP_PORTS = sorted(SERVICES)
|
|
32
|
+
PROBE_PORTS = [80, 443, 22, 445, 139, 53, 8080, 62078] # 호스트 탐색용
|
|
33
|
+
HTTP_PORTS = {80, 443, 8000, 8080, 8443}
|
|
34
|
+
TLS_PORTS = {443, 465, 993, 995, 8443}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def service_name(port):
|
|
38
|
+
if port in SERVICES:
|
|
39
|
+
return SERVICES[port]
|
|
40
|
+
try:
|
|
41
|
+
return socket.getservbyport(port, "tcp")
|
|
42
|
+
except OSError:
|
|
43
|
+
return "unknown"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_ports(spec):
|
|
47
|
+
if spec == "top":
|
|
48
|
+
return TOP_PORTS
|
|
49
|
+
if spec == "all":
|
|
50
|
+
return list(range(1, 65536))
|
|
51
|
+
ports = set()
|
|
52
|
+
for part in spec.split(","):
|
|
53
|
+
if "-" in part:
|
|
54
|
+
a, b = part.split("-")
|
|
55
|
+
ports.update(range(int(a), int(b) + 1))
|
|
56
|
+
else:
|
|
57
|
+
ports.add(int(part))
|
|
58
|
+
return sorted(p for p in ports if 0 < p < 65536)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def grab_banner(ip, port, timeout=1.5):
|
|
62
|
+
raw = s = None
|
|
63
|
+
try:
|
|
64
|
+
raw = s = socket.create_connection((ip, port), timeout=timeout)
|
|
65
|
+
if port in TLS_PORTS:
|
|
66
|
+
ctx = ssl.create_default_context()
|
|
67
|
+
ctx.check_hostname = False
|
|
68
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
69
|
+
s = ctx.wrap_socket(raw, server_hostname=ip)
|
|
70
|
+
if port in HTTP_PORTS:
|
|
71
|
+
s.sendall(f"HEAD / HTTP/1.0\r\nHost: {ip}\r\n\r\n".encode())
|
|
72
|
+
data = s.recv(512).decode(errors="ignore")
|
|
73
|
+
except OSError:
|
|
74
|
+
return ""
|
|
75
|
+
finally:
|
|
76
|
+
for x in (s, raw):
|
|
77
|
+
if x:
|
|
78
|
+
x.close()
|
|
79
|
+
lines = [l.strip() for l in data.splitlines() if l.strip()]
|
|
80
|
+
for l in lines:
|
|
81
|
+
if l.lower().startswith("server:"):
|
|
82
|
+
return l[:70]
|
|
83
|
+
return lines[0][:70] if lines else ""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def probe(job):
|
|
87
|
+
ip, port, timeout = job
|
|
88
|
+
s = socket.socket()
|
|
89
|
+
s.settimeout(timeout)
|
|
90
|
+
try:
|
|
91
|
+
return port if s.connect_ex((ip, port)) == 0 else None
|
|
92
|
+
except OSError:
|
|
93
|
+
return None
|
|
94
|
+
finally:
|
|
95
|
+
s.close()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def scan_host(ip, ports, timeout=0.5, banners=True):
|
|
99
|
+
t0 = time.time()
|
|
100
|
+
with ThreadPoolExecutor(100) as ex:
|
|
101
|
+
found = [p for p in ex.map(probe, [(ip, p, timeout) for p in ports]) if p]
|
|
102
|
+
print(f"\n[{ip}] 스캔 완료 ({len(ports)}개 포트, {time.time() - t0:.1f}초)")
|
|
103
|
+
if not found:
|
|
104
|
+
print(" 열린 포트 없음")
|
|
105
|
+
return
|
|
106
|
+
with ThreadPoolExecutor(20) as ex:
|
|
107
|
+
found_banners = list(ex.map(lambda p: grab_banner(ip, p) if banners else "", found))
|
|
108
|
+
print(f" {'PORT':<10}{'SERVICE':<14}BANNER")
|
|
109
|
+
for p, b in zip(found, found_banners):
|
|
110
|
+
print(f" {str(p) + '/tcp':<10}{service_name(p):<14}{b}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def is_up(ip):
|
|
114
|
+
# 열림(0) 또는 거부(ECONNREFUSED)도 응답이 온 것이므로 호스트가 살아있다는 뜻
|
|
115
|
+
for port in PROBE_PORTS:
|
|
116
|
+
s = socket.socket()
|
|
117
|
+
s.settimeout(0.4)
|
|
118
|
+
try:
|
|
119
|
+
r = s.connect_ex((ip, port))
|
|
120
|
+
except OSError:
|
|
121
|
+
r = -1
|
|
122
|
+
finally:
|
|
123
|
+
s.close()
|
|
124
|
+
if r in (0, errno.ECONNREFUSED):
|
|
125
|
+
return ip
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def hostname(ip):
|
|
130
|
+
try:
|
|
131
|
+
return socket.gethostbyaddr(ip)[0]
|
|
132
|
+
except OSError:
|
|
133
|
+
return "-"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def default_net():
|
|
137
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
138
|
+
try:
|
|
139
|
+
s.connect(("8.8.8.8", 80)) # 실제로 패킷을 보내지는 않음
|
|
140
|
+
ip = s.getsockname()[0]
|
|
141
|
+
except OSError:
|
|
142
|
+
raise SystemExit("네트워크에 연결되어 있지 않아요. 대역을 직접 지정해 보세요: "
|
|
143
|
+
"python btyscan.py discover 192.168.0.0/24")
|
|
144
|
+
finally:
|
|
145
|
+
s.close()
|
|
146
|
+
print(f"내 IP: {ip}")
|
|
147
|
+
return f"{ip}/24"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def discover(net):
|
|
151
|
+
hosts = [str(h) for h in ipaddress.ip_network(net, strict=False).hosts()]
|
|
152
|
+
print(f"{net} 호스트 탐색 중 ({len(hosts)}개)...")
|
|
153
|
+
with ThreadPoolExecutor(100) as ex:
|
|
154
|
+
up = [h for h in ex.map(is_up, hosts) if h]
|
|
155
|
+
names = list(ex.map(hostname, up))
|
|
156
|
+
print(f"\n {'IP':<16}HOSTNAME")
|
|
157
|
+
for ip, n in zip(up, names):
|
|
158
|
+
print(f" {ip:<16}{n}")
|
|
159
|
+
print(f"\n{len(up)}대 발견 (방화벽이 모두 막은 기기는 안 보일 수 있어요)")
|
|
160
|
+
return up
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main():
|
|
164
|
+
ap = argparse.ArgumentParser(description="BTYscan - Termux 미니 네트워크 스캐너")
|
|
165
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
166
|
+
|
|
167
|
+
d = sub.add_parser("discover", help="같은 네트워크의 기기 찾기")
|
|
168
|
+
d.add_argument("net", nargs="?", help="예: 192.168.0.0/24 (생략하면 자동)")
|
|
169
|
+
|
|
170
|
+
for name, h in (("scan", "포트 스캔"), ("auto", "탐색 + 전체 스캔")):
|
|
171
|
+
p = sub.add_parser(name, help=h)
|
|
172
|
+
if name == "scan":
|
|
173
|
+
p.add_argument("host")
|
|
174
|
+
else:
|
|
175
|
+
p.add_argument("net", nargs="?")
|
|
176
|
+
p.add_argument("-p", default="top", help="top | all | 1-1024 | 22,80,443")
|
|
177
|
+
p.add_argument("-t", type=float, default=0.5, help="포트당 타임아웃(초)")
|
|
178
|
+
p.add_argument("-n", action="store_true", help="배너 확인 끄기")
|
|
179
|
+
|
|
180
|
+
a = ap.parse_args()
|
|
181
|
+
try:
|
|
182
|
+
if a.cmd == "discover":
|
|
183
|
+
discover(a.net or default_net())
|
|
184
|
+
elif a.cmd == "scan":
|
|
185
|
+
try:
|
|
186
|
+
ip = socket.gethostbyname(a.host)
|
|
187
|
+
except OSError:
|
|
188
|
+
raise SystemExit(f"호스트를 찾을 수 없어요: {a.host}")
|
|
189
|
+
scan_host(ip, parse_ports(a.p), a.t, not a.n)
|
|
190
|
+
elif a.cmd == "auto":
|
|
191
|
+
for ip in discover(a.net or default_net()):
|
|
192
|
+
scan_host(ip, parse_ports(a.p), a.t, not a.n)
|
|
193
|
+
except KeyboardInterrupt:
|
|
194
|
+
print("\n중단됨")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
main()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "btyscan"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.8"
|
|
9
|
+
|
|
10
|
+
[project.scripts]
|
|
11
|
+
btyscan = "btyscan:main"
|
|
12
|
+
|
|
13
|
+
[tool.setuptools]
|
|
14
|
+
py-modules = ["btyscan"]
|
btyscan-0.1.0/setup.cfg
ADDED