commit ae235e54b483a43c3c1c4b55c2a5b3c23ba7c9e0 Author: 苏晓晴 <37541680+Suxiaoqinx@users.noreply.github.com> Date: Fri Oct 4 15:39:12 2024 +0800 icp备案查询 diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b99a08 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +"# icp_api" diff --git a/crack.py b/crack.py new file mode 100644 index 0000000..7d876ac --- /dev/null +++ b/crack.py @@ -0,0 +1,100 @@ +import base64 +import onnxruntime +import cv2 +import numpy as np + + +class Crack: + def __init__(self): + pass + + def read_base64_image(self, base64_string): + # 解码Base64字符串为字节串 + img_data = base64.b64decode(base64_string) + + # 将解码后的字节串转换为numpy数组(OpenCV使用numpy作为其基础) + np_array = np.frombuffer(img_data, np.uint8) + + # 使用OpenCV的imdecode函数将字节数据解析为cv::Mat对象 + img = cv2.imdecode(np_array, cv2.IMREAD_COLOR) + return img + + def detect(self, big_img): + confidence_thres = 0.7 + iou_thres = 0.7 + session = onnxruntime.InferenceSession("yolov8.onnx") + model_inputs = session.get_inputs() + + self.big_img = self.read_base64_image(big_img) + img_height, img_width = self.big_img.shape[:2] + img = cv2.cvtColor(self.big_img, cv2.COLOR_BGR2RGB) + img = cv2.resize(img, (512, 192)) + image_data = np.array(img) / 255.0 + image_data = np.transpose(image_data, (2, 0, 1)) + image_data = np.expand_dims(image_data, axis=0).astype(np.float32) + input = {model_inputs[0].name: image_data} + output = session.run(None, input) + outputs = np.transpose(np.squeeze(output[0])) + rows = outputs.shape[0] + boxes, scores = [], [] + x_factor = img_width / 512 + y_factor = img_height / 192 + for i in range(rows): + classes_scores = outputs[i][4:] + max_score = np.amax(classes_scores) + if max_score >= confidence_thres: + x, y, w, h = outputs[i][0], outputs[i][1], outputs[i][2], outputs[i][3] + left = int((x - w / 2) * x_factor) + top = int((y - h / 2) * y_factor) + width = int(w * x_factor) + height = int(h * y_factor) + boxes.append([left, top, width, height]) + scores.append(max_score) + indices = cv2.dnn.NMSBoxes(boxes, scores, confidence_thres, iou_thres) + new_boxes = [boxes[i] for i in indices] + # print(new_boxes) + if len(new_boxes) != 5: + return False + return new_boxes + + def siamese(self, small_img, boxes): + session = onnxruntime.InferenceSession("siamese.onnx") + positions = [165, 200, 231, 265] + result_list = [] + for x in positions: + if len(result_list) == 4: + break + raw_image2 = self.read_base64_image(small_img) + raw_image2 = raw_image2[11:11 + 28, x:x + 26] + img2 = cv2.cvtColor(raw_image2, cv2.COLOR_BGR2RGB) + img2 = cv2.resize(img2, (105, 105)) + image_data_2 = np.array(img2) / 255.0 + image_data_2 = np.transpose(image_data_2, (2, 0, 1)) + image_data_2 = np.expand_dims(image_data_2, axis=0).astype(np.float32) + for box in boxes: + raw_image1 = self.big_img[box[1]:box[1] + box[3] + 2, box[0]:box[0] + box[2] + 2] + img1 = cv2.cvtColor(raw_image1, cv2.COLOR_BGR2RGB) + img1 = cv2.resize(img1, (105, 105)) + + image_data_1 = np.array(img1) / 255.0 + image_data_1 = np.transpose(image_data_1, (2, 0, 1)) + image_data_1 = np.expand_dims(image_data_1, axis=0).astype(np.float32) + + inputs = {'input': image_data_1, "input.53": image_data_2} + output = session.run(None, inputs) + output_sigmoid = 1 / (1 + np.exp(-output[0])) + res = output_sigmoid[0][0] + # print(res) + if res >= 0.7: + # print("\n") + # print(res) + # print(box) + result_list.append([box[0], box[1]]) + break + return result_list + + +if __name__ == '__main__': + crack = Crack() + boxes = crack.detect("./1.png") + print(crack.siamese("./2.png", boxes)) diff --git a/main.py b/main.py new file mode 100644 index 0000000..1503c3b --- /dev/null +++ b/main.py @@ -0,0 +1,152 @@ +import base64 +import json +import requests +import hashlib +import time +from urllib import parse +from crack import Crack +import uuid +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend + + +def auth(): + t = str(round(time.time())) + data = { + "authKey": hashlib.md5(("testtest" + t).encode()).hexdigest(), + "timeStamp": t + } + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Content-Type": "application/x-www-form-urlencoded", + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn" + } + try: + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/auth", headers=headers, + data=parse.urlencode(data)).text + return json.loads(resp)["params"]["bussiness"] + except Exception: + time.sleep(5) + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/auth", headers=headers, + data=parse.urlencode(data)).text + return json.loads(resp)["params"]["bussiness"] + + +def getImage(): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Token": token, + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn" + } + payload = { + "clientUid": "point-" + str(uuid.uuid4()) + } + try: + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/getCheckImagePoint", + headers=headers, json=payload).json() + return resp["params"], payload["clientUid"] + except Exception: + time.sleep(5) + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/getCheckImagePoint", + headers=headers, json=payload).json() + return resp["params"], payload["clientUid"] + + +def aes_ecb_encrypt(plaintext: bytes, key: bytes, block_size=16): + backend = default_backend() + cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) + + padding_length = block_size - (len(plaintext) % block_size) + plaintext_padded = plaintext + bytes([padding_length]) * padding_length + + encryptor = cipher.encryptor() + ciphertext = encryptor.update(plaintext_padded) + encryptor.finalize() + + return base64.b64encode(ciphertext).decode('utf-8') + + +def generate_pointjson(big_img, small_img, secretKey): + boxes = crack.detect(big_img) + if boxes: + print("文字检测成功") + else: + print("文字检测失败,请重试") + raise Exception("文字检测失败,请重试") + points = crack.siamese(small_img, boxes) + print("文字匹配成功") + new_points = [[p[0] + 20, p[1] + 20] for p in points] + pointJson = [{"x": p[0], "y": p[1]} for p in new_points] + # print(json.dumps(pointJson)) + enc_pointJson = aes_ecb_encrypt(json.dumps(pointJson).replace(" ", "").encode(), secretKey.encode()) + return enc_pointJson + + +def checkImage(uuid_token, secretKey, clientUid, pointJson): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Token": token, + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn" + } + data = { + "token": uuid_token, + "secretKey": secretKey, + "clientUid": clientUid, + "pointJson": pointJson + } + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/checkImage", headers=headers, + json=data).json() + if resp["code"] == 200: + # print(resp["params"]) + return resp["params"]["sign"] + return False + + +def query(sign, uuid_token, domain): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Token": token, + "Sign": sign, + "Uuid": uuid_token, + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn", + "Content-Type": "application/json", + "Cookie": "__jsluid_s="+str(uuid.uuid4().hex[:32]) + } + data = {"pageNum": "", "pageSize": "", "unitName": domain, "serviceType": 1} + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/icpAbbreviateInfo/queryByCondition", + headers=headers, data=json.dumps(data).replace(" ","")).text + return resp + + +crack = Crack() +token = auth() +time.sleep(0.1) +print("正在获取验证码") +params, clientUid = getImage() +pointjson = generate_pointjson(params["bigImage"], params["smallImage"], params["secretKey"]) +time.sleep(0.5) +sign = checkImage(params["uuid"], params["secretKey"], clientUid, pointjson) +time.sleep(0.5) +if sign: + print(query(sign, params["uuid"],"baidu.com")) +else: + print("failed") diff --git a/mainapi.py b/mainapi.py new file mode 100644 index 0000000..03ec1bb --- /dev/null +++ b/mainapi.py @@ -0,0 +1,211 @@ +import base64 +import json +import requests +import hashlib +import time +import os +from urllib import parse +import uuid +from flask import Flask, request, jsonify ,Response +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.backends import default_backend +from crack import Crack # 假设 Crack 是一个你自己实现的类 + +app = Flask(__name__) + +CACHE_DIR = './cache' # 缓存文件目录 + +# 创建缓存目录(如果不存在) +if not os.path.exists(CACHE_DIR): + os.makedirs(CACHE_DIR) + + +def auth(): + t = str(round(time.time())) + data = { + "authKey": hashlib.md5(("testtest" + t).encode()).hexdigest(), + "timeStamp": t + } + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Content-Type": "application/x-www-form-urlencoded", + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn" + } + try: + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/auth", headers=headers, + data=parse.urlencode(data)).text + return json.loads(resp)["params"]["bussiness"] + except Exception: + time.sleep(5) + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/auth", headers=headers, + data=parse.urlencode(data)).text + return json.loads(resp)["params"]["bussiness"] + + +def getImage(token): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Token": token, + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn" + } + payload = { + "clientUid": "point-" + str(uuid.uuid4()) + } + try: + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/getCheckImagePoint", + headers=headers, json=payload).json() + return resp["params"], payload["clientUid"] + except Exception: + time.sleep(5) + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/getCheckImagePoint", + headers=headers, json=payload).json() + return resp["params"], payload["clientUid"] + + +def aes_ecb_encrypt(plaintext: bytes, key: bytes, block_size=16): + backend = default_backend() + cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend) + + padding_length = block_size - (len(plaintext) % block_size) + plaintext_padded = plaintext + bytes([padding_length]) * padding_length + + encryptor = cipher.encryptor() + ciphertext = encryptor.update(plaintext_padded) + encryptor.finalize() + + return base64.b64encode(ciphertext).decode('utf-8') + + +def generate_pointjson(big_img, small_img, secretKey): + crack = Crack() # 假设 Crack 是一个你实现的类 + boxes = crack.detect(big_img) + if boxes: + print("文字检测成功") + else: + print("文字检测失败,请重试") + raise Exception("文字检测失败,请重试") + points = crack.siamese(small_img, boxes) + print("文字匹配成功") + new_points = [[p[0] + 20, p[1] + 20] for p in points] + pointJson = [{"x": p[0], "y": p[1]} for p in new_points] + enc_pointJson = aes_ecb_encrypt(json.dumps(pointJson).replace(" ", "").encode(), secretKey.encode()) + return enc_pointJson + + +def checkImage(uuid_token, secretKey, clientUid, pointJson, token): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Token": token, + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn" + } + data = { + "token": uuid_token, + "secretKey": secretKey, + "clientUid": clientUid, + "pointJson": pointJson + } + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/checkImage", headers=headers, + json=data).json() + if resp["code"] == 200: + return resp["params"]["sign"] + return False + + +def query(sign, uuid_token, domain, token): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + "Referer": "https://beian.miit.gov.cn/", + "Token": token, + "Sign": sign, + "Uuid": uuid_token, + "Connection": "keep-alive", + "Accept": "application/json, text/plain, */*", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "zh-CN,zh;q=0.9", + "Origin": "https://beian.miit.gov.cn", + "Content-Type": "application/json", + "Cookie": "__jsluid_s=" + str(uuid.uuid4().hex[:32]) + } + data = {"pageNum": "", "pageSize": "", "unitName": domain, "serviceType": 1} + resp = requests.post("https://hlwicpfwc.miit.gov.cn/icpproject_query/api/icpAbbreviateInfo/queryByCondition", + headers=headers, data=json.dumps(data).replace(" ", "")).text + return resp + + +def save_to_cache(domain, data): + """保存数据到缓存文件""" + cache_path = os.path.join(CACHE_DIR, f"{domain}.json") + with open(cache_path, 'w', encoding='utf-8') as f: + json.dump(data, f) + + +def load_from_cache(domain): + """从缓存文件中加载数据""" + cache_path = os.path.join(CACHE_DIR, f"{domain}.json") + if os.path.exists(cache_path): + with open(cache_path, 'r', encoding='utf-8') as f: + return json.load(f) + return None + + +@app.route('/query', methods=['GET']) +def query_api(): + try: + # 从 URL 查询参数中获取 domain 和 type + domain = request.args.get('domain') + query_type = request.args.get('type', '') # 默认为空字符串 + + if not domain: + return jsonify({"status": "failed", "message": "Missing 'domain' parameter"}), 400 + + # 如果 type=cache,则检查缓存 + if query_type == 'cache': + cached_data = load_from_cache(domain) + if cached_data: + json_data = json.dumps({"status": "successful", "data": cached_data}) + return Response(json_data, content_type='application/json') + + # 如果没有缓存,或未指定 type=cache,则继续进行查询 + crack = Crack() + token = auth() + time.sleep(0.1) + params, clientUid = getImage(token) + pointjson = generate_pointjson(params["bigImage"], params["smallImage"], params["secretKey"]) + time.sleep(0.5) + sign = checkImage(params["uuid"], params["secretKey"], clientUid, pointjson, token) + time.sleep(0.5) + + if sign: + result = query(sign, params["uuid"], domain, token) + response = json.loads(result) + json_data = json.dumps({"status": "successful", "data": response['params']['list']}) + + # 如果 type=cache,则将查询结果缓存 + if query_type == 'cache': + save_to_cache(domain, response['params']['list']) + + return Response(json_data, content_type='application/json') + + else: + return jsonify({"status": "failed", "message": "Captcha verification failed"}), 400 + + except Exception as e: + return jsonify({"status": "error", "message": str(e)}), 500 + + +if __name__ == '__main__': + app.run(debug=True, port=5000) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..817c2b3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +requests~=2.31.0 +cryptography~=42.0.5 +onnxruntime~=1.17.1 +numpy~=1.26.4 +opencv-python-headless~=4.10.0.84 +Flask~=3.0.3 \ No newline at end of file diff --git a/siamese.onnx b/siamese.onnx new file mode 100644 index 0000000..adbf1ea Binary files /dev/null and b/siamese.onnx differ diff --git a/yolov8.onnx b/yolov8.onnx new file mode 100644 index 0000000..3ee376c Binary files /dev/null and b/yolov8.onnx differ diff --git a/逆向分析.md b/逆向分析.md new file mode 100644 index 0000000..c3b7550 --- /dev/null +++ b/逆向分析.md @@ -0,0 +1,174 @@ +# 新版备案查询验证码分析 + +## 获取Token + +URL: https://hlwicpfwc.miit.gov.cn/icpproject_query/api/auth + +Payload: + +```json +{ + "authKey": hashlib.md5(("testtest" + t).encode()).hexdigest(), + "timeStamp": str(round(time.time())) +} +``` + +Response: + +```json +{ + "code":200, + "msg":"操作成功", + "params":{ + "bussiness":"Token", + "expire":300000, + "refresh":"RefreshToken" + }, + "success":true +} + +``` + +> 注意: 以下所有请求均需携带Token请求头 + +## 获取验证码图片 + +URL: https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/getCheckImagePoint + +Payload: + +```json +{ + clientUid: "point-"+"随机UUID" +} +``` + +Response: + +```json +{ + "code": 200, + "msg": "操作成功", + "params": { + "bigImage": "base64图片", + "secretKey": "随机AES密钥", + "smallImage": "base64图片", + "uuid": "随机字符串", + "wordCount": 4 + }, + "success": true +} + +``` + + + +## 校验结果 + +URL: https://hlwicpfwc.miit.gov.cn/icpproject_query/api/image/checkImage + +Payload: + +```json +{ + "token":"uuid", + "secretKey":"secretKey", + "clientUid":"clientUid", + "pointJson":"" +} +``` + +pointJson算法: + +- 加密方法 AES-128-ECB +- 填充方法 pkcs7 + +- 密钥 `secretKey` + +- 内容 `'[{"x":135,"y":43},{"x":43,"y":119},{"x":402,"y":61},{"x":179,"y":78}]'` + + 其中x,y分别对应每个字的坐标,有顺序要求 + +- 输出 Base64 + + + +Response: + +```json +{ + "code": 200, + "msg": "操作成功", + "params": { + "sign": "签名", + "smallImage": "验证成功的base64图片" + }, + "success": true +} + +``` + + + +## 查询备案 + +URL: https://hlwicpfwc.miit.gov.cn/icpproject_query/api/icpAbbreviateInfo/queryByCondition + +Payload + +```json +{ + "pageNum":"", + "pageSize":"", + "unitName":"域名", + "serviceType":1 +} +``` + +Response + +```json +{ + "code": 200, + "msg": "操作成功", + "params": { + "endRow": 0, + "firstPage": 1, + "hasNextPage": false, + "hasPreviousPage": false, + "isFirstPage": true, + "isLastPage": true, + "lastPage": 1, + "list": [ + { + "contentTypeName": "", + "domain": "", + "domainId": "", + "leaderName": "", + "limitAccess": "", + "mainId": "", + "mainLicence": "", + "natureName": "", + "serviceId": "", + "serviceLicence": "", + "unitName": "", + "updateRecordTime": "" + } + ], + "navigatePages": 8, + "navigatepageNums": [ + 1 + ], + "nextPage": 1, + "pageNum": 1, + "pageSize": 10, + "pages": 1, + "prePage": 1, + "size": 1, + "startRow": 0, + "total": 1 + }, + "success": true +} +``` +