btyscan 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ Metadata-Version: 2.4
2
+ Name: btyscan
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.8
@@ -0,0 +1,6 @@
1
+ btyscan.py,sha256=BUO0BwwRdfslioh7CzYlyPLjH7EaTz637E5l3a1ZyT8,6716
2
+ btyscan-0.1.0.dist-info/METADATA,sha256=Xgjziui4CS2a08u8k1Ktvk5NkqcssUR9q9bhbX6YUzY,74
3
+ btyscan-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
4
+ btyscan-0.1.0.dist-info/entry_points.txt,sha256=nncUZKR-JmyuG2fkHRzb48krHQ5OJcKNYdMUCCPphLY,41
5
+ btyscan-0.1.0.dist-info/top_level.txt,sha256=FQjQdax3824yftTLW7B_wKYeXbd8WDyqEc3Q8rgLg2w,8
6
+ btyscan-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ btyscan = btyscan:main
@@ -0,0 +1 @@
1
+ btyscan
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()