注意事项:
我的文章目前只在CSDN上发布!其他平台全是盗版!
从进阶篇开始,我会直接教你们开始制作工具脚本了,注意这里的脚本是使用工具来配合,就是说你使用工具不在手动了,只要执行脚本就行,但是工具要能运行,所以我现在的所有python脚本都会在kali里运行
工具列表:
amass
图标设计
要写出一个成功的工具,当然是要先从我们的图标开始设计了,我就以最简单的图标设计为例

我们先来一个参数来指定解释器,它会自动去python里面找解释器的
python
#!/usr/bin/env python3
然后我们直接来看完整的代码
python
# !/usr/bin/env python3
RED = '\033[91m'
RESET = '\033[0m'
BANNER = fr"""{RED}
__ __ _
/ /_ __ ______ / /_ ____ _(_)
/ __ \/ / / / __ \/ __ \/ __ `/ /
/ / / / /_/ / /_/ / /_/ / /_/ / /
/_/ /_/\__,_/\____/_.___/\__,_/_/
{RESET}"""
print(BANNER)
其中\033[91m是代表颜色的意思
|------------|----------|
| \033[30m | 黑色 |
| \033[31m | 红色 |
| \033[32m | 绿色 |
| \033[33m | 黄色 |
| \033[34m | 蓝色 |
| \033[35m | 紫色 |
| \033[36m | 青色 |
| \033[37m | 白色 |
| \033[90m | 灰色 |
| \033[91m | 亮红色 |
| \033[92m | 亮绿色 |
| \033[93m | 亮黄色 |
| \033[94m | 亮蓝色 |
| \033[95m | 亮紫色 |
| \033[96m | 亮青色 |
| \033[97m | 亮白色 |
| \033[0m | 重置系统默认颜色 |
这里给你们推荐一个网站可以用来把图片转换成ascii列表然后复制上去即可
在线Ascii艺术创作者
https://www.ascii-art-generator.org/
工具命令编写+交互
版本判断
现在在设计好我们的图标后我们就开始写属于我们自己的工具命令脚本了,先来介绍个import要引用的函数,分别是:
import subprocess
import sys
import os
from datetime import datetime
subprocess:调用外部命令(amass)的核心模块,说人话就是用来执行那些工具的关键模块
sys:与Python解释器交互(如sys.exit()强制退出),就是你执行脚本后会弹出那些框框让你输入,就是用的这个
os:操作系统相关(如os.path.join()处理路径),这个的话几乎没用到
datetime:获取当前时间,用于日志时间戳
好了知道这些引用模块的作用,那我们来写吧,首先使用def来定义函数用的,来,我们先看第一段代码
python
def get_amass_version():
"""检测amass版本"""
try:
result = subprocess.run(['amass', '-version'], capture_output=True, text=True, check=True)
version_line = result.stdout.strip().split('\n')[0]
# 提取版本号,如 v4.2.0
version = version_line.split()[-1]
return version
except:
return None
这里我使用了def定义了一个get_amass_version的函数,用来检测amass是否存在,以及版本号
参数解释:
- try:为避免报错变得难看,就使用这个函数
- subprocess.run:用来引用外部kali环境的amass工具来执行命令,这里命令执行的是amass -version也就是查看工具版本号
- capture_output=True:这个是用来让版本号不存入到指定函数里的东西,比如说代码里的result,想要输出版本号就要print(result.stdout)
- text=True:自动把字节转成字符串
- check=True:自动检查命令是否成功,失败就抛异常
- except:如果出错就按照这里的输出
- result.stdout.strip().split('\n')[0] : 其中result.stdout是提取amass的版本号,strip()的意思是提取为字符串,split('\n')[0]是按换行符分割字符串,返回列表,为避免有的工具输出多行,只取第一行的结果
- version = version_line.split()[-1] : 是为了取amass v4.2.0里面的v4.2.0的,当然了,我这里也可以不写,因为这里我使用amass -version也只是输出v4.2.0的而已
- return version : 返回版本号,这一步是最关键的
检测安装
python
def check_amass():
"""检查amass是否已安装"""
try:
subprocess.run(['amass', '-h'], capture_output=True, check=True)
return True
except:
return False
这个没什么技术含量的的了,该解释的刚刚都解释的差不多了,我就不详细说了哈
时间检测
python
def log(message, level='info'):
"""带时间戳日志"""
timestamp = datetime.now().strftime('%H:%M:%S')
if level == 'info':
print(f"[{timestamp}] {CYAN}[*]{RESET} {message}")
elif level == 'success':
print(f"[{timestamp}] {GREEN}[+]{RESET} {message}")
elif level == 'error':
print(f"[{timestamp}] {RED}[-]{RESET} {message}")
elif level == 'warn':
print(f"[{timestamp}] {YELLOW}[!]{RESET} {message}")
这个其实挺简单的了,我一个个说吧
参数解释:
def log(message, level='info'):
#这里面的log是要传两个参数才行
#例子:log("帅哥降临",info)
#然后这里就会如果函数里面有这个了的话他就会使用info默认的颜色来输出:
#[14:32:10] [*] 帅哥降临
timestamp = datetime.now().strftime('%H:%M:%S')
#这里的datetime.now()是获取当前的时间
#strftime('%H:%M:%S')是用来格式话时间的,其中%H、%M、%S分别是小时、分钟、秒
if level == 'info':
print(f"[{timestamp}] {CYAN}[*]{RESET} {message}")
#这里的这些就是按照判断来给返回的东西是什么颜色的,比如说我在一个模块里返回log("错#误",error)那就是红色的,其中{timestamp}是我们刚刚弄的时间{CYAN}[*]{RESET}是我之前#定义的颜色,颜色列表在上面{message}就是你输入的内容了,比如我刚刚输入的错误,他#这里就会输出错误
交互输入
python
def get_user_input():
"""交互式获取用户输入"""
print("\n" + "="*50)
print(" 配置扫描参数".center(48))
print("="*50)
# 获取域名
while True:
print(f"\n{BLUE}[?] 输入目标域名 (如: school.edu.cn): {RESET}", end='')
domain = input().strip()
if domain:
break
log("域名不能为空!", 'error')
# 获取速率限制
while True:
print(f"\n{BLUE}[?] 设置查询速率 (次/秒, 建议10-50): {RESET}", end='')
rate = input().strip()
if rate.isdigit() and int(rate) > 0:
rate = int(rate)
break
log("请输入正整数!", 'error')
# 获取超时时间
while True:
print(f"\n{BLUE}[?] 设置超时时间 (分钟, 默认30): {RESET}", end='')
timeout = input().strip()
if not timeout:
timeout = 30
break
if timeout.isdigit() and int(timeout) > 0:
timeout = int(timeout)
break
log("请输入正整数!", 'error')
# ========== 修复后的模式选择 ==========
while True:
print(f"\n{BLUE}[?] 设置模式 (1=被动枚举, 2=证书透明度): {RESET}", end='')
mode = input().strip()
if not mode: # 直接回车用默认
mode = '1' # 字符串,别搞整数
log("使用默认模式: 被动枚举", 'info')
break
if mode.isdigit() and mode in ['1', '2']: # 判断mode,不是timeout
break
log("请输入 1 或 2!", 'error')
# ========== 修复后的路径选择 ==========
while True:
print(f"\n{BLUE}[?] 设置输出目录 (如: /root/results): {RESET}", end='')
mingzi = input().strip()
if not mingzi:
mingzi = "." # 默认当前目录
log("使用当前目录作为输出路径", 'info')
break
if os.path.isdir(mingzi) and os.access(mingzi, os.W_OK):
break
else:
log(f"错误: 目录 {mingzi} 不存在或无写入权限!", 'error')
return domain, rate, timeout, mode, mingzi
参数解释:
while True:
print(f"\n{BLUE}[?] 输入目标域名 (如: school.edu.cn): {RESET}", end='')
domain = input().strip()
if domain:
break
log("域名不能为空!", 'error')
#这里主要是让用户输入子域名,{BLUE}这些是颜色设置 end是用来结束的,然后domain = input().strip()是以字符串的形式把输入的子域名赋值给domain
while True:print(f"\n{BLUE}[?] 设置查询速率 (次/秒, 建议10-50): {RESET}", end='')
rate = input().strip()
if rate.isdigit() and int(rate) > 0:
rate = int(rate)
break
log("请输入正整数!", 'error')
#这里也差不多,一二行我不介绍了,我介绍if rate.isdigit() and int(rate) > 0:这个吧,这个rate.isdigit()是用来判断输入的是否为数字(0-9)的,防止用户输入什么50a之类的,然后把输入的东西弄成证书类型int,去判断是否大于0
while True:print(f"\n{BLUE}[?] 设置超时时间 (分钟, 默认30): {RESET}", end='')
timeout = input().strip()
if not timeout:
timeout = 30
break
if timeout.isdigit() and int(timeout) > 0:
timeout = int(timeout)
break
log("请输入正整数!", 'error')
#这个也差不多的了,重点就是有默认值了就是if not timeout:就是说tiemout如果没有值的话就默认timeout等于30了,然后timeout.isdigit()都是用来判断输入的东西是否(0-9)而已
最后别忘了最后一行的return domain, rate, timeout如果你不写这个,你前面的所有代码就白写了
构建命令
在弄完上面的那些交互模块了之后,我们该开始写真正让脚本去帮我们执行命令的python模块了
python
def run_amass_scan(domain, rate, timeout, mode, mingzi):
"""执行amass扫描"""
# 确保目录存在
os.makedirs(mingzi, exist_ok=True) # ← 自动创建目录,如果已存在就忽略
# ========== 只根据模式构建命令 ==========
if mode == '1':
# 被动枚举模式
# 完整路径 = 目录 + 域名 + 后缀
output_file = f"{mingzi}/{domain}_beidong.txt" # ← /root/results/school.edu.cn_passive.txt
cmd = f"amass enum -passive -d {domain} | awk '{{print $1}}' > {output_file}"
log(f"模式: 被动枚举", 'success')
elif mode == '2':
# CT日志模式
output_file = f"{mingzi}/{domain}_ct.txt" # ← /root/results/school.edu.cn_ct.txt
cmd = f"amass enum -passive -d {domain} -include crt.sh,googlect,facebookct,censys,certspotter,bufferover | awk '{{print $1}}' | sort -u > {output_file}"
log(f"模式: 证书透明度 (CT日志)", 'success')
log(f"命令: {cmd} 执行成功", 'success')
log("开始扫描,按 Ctrl+C 可随时停止...", 'warn')
try:
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1
)
for line in process.stdout:
print(line, end='')
process.wait()
if process.returncode == 0:
log(f"扫描完成!结果已保存到 {output_file}", 'success')
log(f"发现了 {count_lines(output_file)} 个子域名", 'success')
else:
log("扫描失败", 'error')
except KeyboardInterrupt:
log("\n用户中断扫描", 'warn')
process.terminate()
except Exception as e:
log(f"发生错误: {e}", 'error')
命令解释:
os.makedirs(mingzi, exist_ok=True)
#是用于检测目标路径是否存在的,os.makedirs是递归创建目录,exist_ok=True是检测目录是否存在,不存在就创建不报错
if mode == '1':
被动枚举模式
完整路径 = 目录 + 域名 + 后缀
output_file = f"{mingzi}/{domain}_beidong.txt"
cmd = f"amass enum -passive -d {domain} | awk '{{print $1}}' > {output_file}"
log(f"模式: 被动枚举", 'success')
elif mode == '2':
CT日志模式
output_file = f"{mingzi}/{domain}_ct.txt" # ← /root/results/school.edu.cn_ct.txt
cmd = f"amass enum -passive -d {domain} -include crt.sh,googlect,facebookct,censys,certspotter,bufferover | awk '{{print $1}}' | sort -u > {output_file}"
log(f"模式: 证书透明度 (CT日志)", 'success')
log(f"命令: {cmd} 执行成功", 'success')
log("开始扫描,按 Ctrl+C 可随时停止...", 'warn')
#这个我就简单介绍吧output_file = f"{mingzi}/{domain}_beidong.txt"就是用来拼接路径的,mingzi是路径,domain是文件名也是子域名,然后带去替换到命令里面指定的内容,然后我们把最终的命令赋值给cmd这个值即可
process = subprocess.Popen(
#把执行的结果给赋值process,其中subprocess.Popen是subprocess.run的另一种模式,run是用来执行很快返回的函数,如ls的,subprocess.Popen是用来获取长时间才能获取到结果的东西的,所以这里我们用subprocess.Popen
cmd, #要执行的命令
shell=True, #通过Shell执行(必须,因为用了管道|和重定向>)
stdout=subprocess.PIPE, # 捕获标准输出,不让它直接打印到屏幕
stderr=subprocess.STDOUT, # 把错误输出也合并到标准输出(统一处理)
text=True, # 以文本模式读写(不是二进制),返回字符串
bufsize=1 # 行缓冲,有换行就立即读取(实现实时输出)
)
for line in process.stdout:
print(line, end='')
从
process.stdout里逐行读取的输出内容(字符串)作用:实时打印amass的扫描进度
process.wait()
- 作用:阻塞等待子进程执行完成
if process.returncode == 0:
log(f"扫描完成!结果已保存到 {output_file}", 'success')
log(f"发现了 {count_lines(output_file)} 个子域名", 'success')
else:
log("扫描失败", 'error')
process.returncode:子进程退出码(整数)
0→ 成功非0 → 失败(amass出错、命令语法错误、文件权限问题等)
except KeyboardInterrupt:
log("\n用户中断扫描", 'warn')
process.terminate()
KeyboardInterrupt:异常类型,用户按Ctrl+C时触发
process.terminate():方法,强制终止子进程
except Exception as e:
log(f"发生错误: {e}", 'error')
e:捕获到的异常对象,包含具体错误信息作用:捕获未知错误,防止程序崩溃
注意事项
我这里写的命令是我自己常用的命令,如果你们觉得自己的命令不够好也可以用我的
命令:amass enum -passive -d xxx.edu.cn | awk '{print $1}' > passive_subdomains.txt
命令:amass enum -passive -d xxx.edu.cn -include crt.sh,googlect,facebookct,censys,certspotter,bufferover | awk '{print $1}' | sort -u > ct_subs.txt
主函数编写
终于是到了这步了,现在才是主战场,上面的那些编写的都是函数,是要调用的,调用就是要在主函数里面编辑,主函数是def main():来的,好了看我装X
python
def main():
"""主函数"""
print(BANNER)
# 检查amass是否已安装
if not check_amass():
if not install_amass():
log("无法继续,请先手动安装amass: sudo apt install amass", 'error')
sys.exit(1)
# 主循环:持续扫描多个域名
while True:
try:
# 1. 获取用户输入(5个参数全收)
domain, rate, timeout, mode, mingzi = get_user_input()
# 2. 配置确认(显示有用的信息)
print("\n" + "="*50)
print(" 配置确认".center(48))
print("="*50)
print(f"目标域名: {domain}")
print(f"扫描模式: {'被动枚举' if mode == '1' else '证书透明度'}") # ← 显示模式,不是版本
print(f"查询速率: {rate} 次/秒(未使用)") # ← 提醒用户这参数没用
print(f"超时时间: {timeout} 分钟(未使用)") # ← 提醒用户这参数没用
print(f"输出目录: {mingzi}")
# 3. 二次确认
print(f"\n{YELLOW}[?] 确认开始扫描? (y/n): {RESET}", end='')
if input().lower() == 'y':
run_amass_scan(domain, rate, timeout, mode, mingzi) # ← 5个参数全传
else:
log("已取消", 'warn')
# 4. 询问是否继续
print(f"\n{YELLOW}[?] 是否继续扫描其他域名? (y/n): {RESET}", end='')
if input().lower() != 'y':
break
except KeyboardInterrupt:
log("\n用户退出", 'warn')
break
log("感谢使用,再见!", 'success')
命令解释:
print(BANNER)
- 自然是我们帅气的logo输出了,没什么好说的
if not check_amass():
if not install_amass():
log("无法继续,请先手动安装amass: sudo apt install amass", 'error')
sys.exit(1)
- 这个自然是进行判断的,这里是not就是说如果输出的是False的话就进行下一个判断,如果是安装过了输出的是True的话这里直接就是退出的,不判断了,sys.exit(1)是退出的时候输出1表示异常退出
domain, rate, timeout, mode, mingzi = get_user_input()
2. 配置确认(显示有用的信息)
print("\n" + "="*50)
print(" 配置确认".center(48))
print("="*50)
print(f"目标域名: {domain}")
print(f"扫描模式: {'被动枚举' if mode == '1' else '证书透明度'}")
print(f"查询速率: {rate} 次/秒(未使用)") # ← 提醒用户这参数没用
print(f"超时时间: {timeout} 分钟(未使用)") # ← 提醒用户这参数没用
print(f"输出目录: {mingzi}")
- 这里就很容易明白了,就是domain, rate, timeout, mode, mingzi五个参数赋值给get_user_input(),然后剩下的就是输出了,然值得注意的是 print(f"扫描模式: {'被动枚举' if mode == '1' else '证书透明度'}") 这里使用的是三元表示法,直接在输出里面判断mode,哦别忘了print里面的f是关键,没有他输出里面就不能放{rate}之类的函数
print(f"\n{YELLOW}[?] 确认开始扫描? (y/n): {RESET}", end='')
if input().lower() == 'y':
run_amass_scan(domain, rate, timeout, mode, mingzi) # ← 5个参数全传
else:
log("已取消", 'warn')
- 这里就只是个二次确认而已了,没什么的,lower是用来转小写的
run_amass_scan(domain, rate, timeout, mode, mingzi**)** :调用核心扫描函数,传入5个参数,这个也就是刚刚我带你们写的那个两个模式的定义的函数
print(f"\n{YELLOW}[?] 是否继续扫描其他域名? (y/n): {RESET}", end='')
if input().lower() != 'y':
break
except KeyboardInterrupt:
log("\n用户退出", 'warn')
break
log("感谢使用,再见!", 'success')
- 这里就更简单了,就是问你要不要在继续扫描别的子域名来进行信息收集,KeyboardInterrupt就是异常退出,比如ctrl+C退出的时候,然后其他的就没什么了
if name == 'main':
main()
- 这行代码是 Python 模块的防盗门 ,让脚本既能当主程序 跑,也能当工具库被 import。
运行程序
最后一步,我亲自带你们来用写好的脚本,作为帅哥,我当然是尽职尽责
首先我们保存脚本,然后python 文件名,来执行脚本

这里我随便找一个edu子域名,你们自己找一个,然后参数你们自己看着输入

然后我们输入y开始执行文本

然后我们静静的等待结果

看到这个The enumeration has finished就表示工具执行成功并结束,如果工具没显示这个就结束了说明要么网络或者工具出问题了
总结:
进阶篇第一章就结束了,这里主要是让你们学会写脚本来提升速率,如果你一个个手动敲,得等到什么时候去,而且到后面去工作总不能说什么都手动弄吧,那肯定是不可能的,后面我还会写进阶篇二三,进阶篇二的话我会教你们写好几个工具配合来使用了,这样你的信息收集效率可以提升个十倍差不多吧,好了就先这样,晚安
再次声明
我的文章目前只在CSDN上发布!其他平台全是盗版!
我写这个文章主要是当作学习笔记差不多,我发这个主要是为了记录我的学习过程,但是我不想被别人盗版过去当作是他的成果,毕竟我写这个也是花了挺多的时间的,我这里不设置任何收费的模式,只要关注就能看到,只是为了让你们多学点,我淋过雨也想为你们撑把伞
