AI逆向|猿人学逆向反混淆练习平台第八题加密分析

张开发
2026/4/9 14:34:27 15 分钟阅读

分享文章

AI逆向|猿人学逆向反混淆练习平台第八题加密分析
关注它不迷路。本文章中所有内容仅供学习交流不可用于任何商业用途和非法用途否则后果自负如有侵权请联系作者立即删除一.题目地址https://match.yuanrenxue.cn/match/8二.抓包分析打开上面的网站后发现是一些验证码:提示需要先通过一次验证码再点击页码获取数据:上面是点击验证码后提交的数据看看返回的数据:然后再点击页面:就能获取到数据了。这题主要考察验证码的识别。直接交给AI吧。三.AI工具我这里使用codeX的cli工具 Gpt-5.4 xhigh 的AI模型。使用的MCP则是JSReverser-MCP提示词:使用JSReverse MCP方式插桩采集完整的输入输出及中间态数据与本地算法进行逻辑一致性和结果正确性对比分析。URL: 【https://match.yuanrenxue.cn/match/8】目标【目标接口https://match.yuanrenxue.cn/api/question/8先识别验证码再获取数据】 触发方式: 【翻面】 约束不使用playwright等浏览器自动化工具不能联网搜索公开案例 cookie:将{sessionid:XXXXXX}加入到请求代码中表示当前登录UA设置:yuanrenxue交付可运行的python脚本运行后打印1-5页的响应数据并计算总和四.AI提供的源码# -*- coding: utf-8 -*-import base64import ioimport itertoolsimport jsonimport pickleimport randomimport timefrom collections import defaultdictfrom pathlib import Pathimport cv2import numpy as npimport requestsfrom PIL import ImageBASE_URL https://match.yuanrenxue.cnSESSION_COOKIE {sessionid: XXXXXX}USER_AGENT yuanrenxuePAGE_SIZE 10TOTAL_PAGES 5CACHE_PATH Path(__file__).with_name(match8_cache.pkl)defnow_ms():returnint(time.time() * 1000)defcell_center(pos, cell_size100): row, col divmod(pos, 3)return {x: col * cell_size cell_size // 2, y: row * cell_size cell_size // 2}classMatch8Solver:def__init__(self):self.session requests.Session()self.session.headers.update( {User-Agent: USER_AGENT,X-Requested-With: XMLHttpRequest,Referer: f{BASE_URL}/match/8,Accept: application/json, text/javascript, */*; q0.01, } )for key, value in SESSION_COOKIE.items():self.session.cookies.set(key, value, domainmatch.yuanrenxue.cn, path/)self.last_request_at 0.0self.samples defaultdict(list)self.load_cache()defload_cache(self):ifnot CACHE_PATH.exists():returntry:with CACHE_PATH.open(rb) as f: cached pickle.load(f)for char, feats in cached.items():self.samples[char].extend(np.array(feat, dtypenp.uint8) for feat in feats)except Exception:self.samples.clear()defsave_cache(self): payload { char: [feat.astype(np.uint8) for feat in feats]for char, feats inself.samples.items()if feats }with CACHE_PATH.open(wb) as f: pickle.dump(payload, f)defrequest(self, method, path, *, expect_statusNone, retry_sleep3.0, **kwargs):if expect_status isNone: expect_status {200}else: expect_status set(expect_status)whileTrue: gap time.time() - self.last_request_atif gap 0.6: time.sleep(0.6 - gap)try: resp self.session.request(method, BASE_URL path, timeout20, **kwargs)except requests.RequestException as exc:print(f[net] {method}{path} - {type(exc).__name__}, {retry_sleep:.1f}s 后重试) time.sleep(retry_sleep)continueself.last_request_at time.time()if resp.status_code 429:print(f[net] {method}{path} - 429, {retry_sleep:.1f}s 后重试) time.sleep(retry_sleep)continueif resp.status_code notin expect_status: resp.raise_for_status()return resp staticmethoddefdecode_json(resp):return json.loads(resp.content.decode(utf-8)) staticmethoddefdecode_image(data_url): raw base64.b64decode(data_url.split(,, 1)[1])return np.array(Image.open(io.BytesIO(raw)).convert(RGB)) staticmethoddefextract_cells(image): h, w image.shape[:2] cell_h h // 3 cell_w w // 3 cells []for pos inrange(9): row, col divmod(pos, 3) cells.append(image[row * cell_h : (row 1) * cell_h, col * cell_w : (col 1) * cell_w])return cells staticmethoddefpreprocess_cell(cell): h, w cell.shape[:2] margin int(min(h, w) * 0.12) cell cell[margin : h - margin, margin : w - margin] hsv cv2.cvtColor(cell, cv2.COLOR_RGB2HSV) gray cv2.cvtColor(cell, cv2.COLOR_RGB2GRAY) colorful hsv[:, :, 1] 40 non_white gray 245 mask np.logical_and(colorful, non_white).astype(np.uint8) * 255 kernel np.ones((3, 3), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) ys, xs np.where(mask 0)iflen(xs) 0:return np.zeros((64, 64), dtypenp.uint8) x1, x2 xs.min(), xs.max() 1 y1, y2 ys.min(), ys.max() 1 crop mask[y1:y2, x1:x2] side max(crop.shape[:2]) 8 canvas np.zeros((side, side), dtypenp.uint8) oy (side - crop.shape[0]) // 2 ox (side - crop.shape[1]) // 2 canvas[oy : oy crop.shape[0], ox : ox crop.shape[1]] crop feature cv2.resize(canvas, (64, 64), interpolationcv2.INTER_AREA)return (feature 32).astype(np.uint8)defadd_labeled_cells(self, captcha, positions, labels): image self.decode_image(captcha[image]) cells self.extract_cells(image)for pos, label inzip(positions, labels): feat self.preprocess_cell(cells[pos])self.samples[label].append(feat)self.save_cache()defchar_cost(self, feature, char): feats self.samples.get(char, [])ifnot feats:returnfloat(inf) stacked np.stack([f.astype(np.float32) for f in feats], axis0) prototype stacked.mean(axis0)returnfloat(np.mean(np.abs(feature.astype(np.float32) - prototype)))defpredict_positions(self, captcha): image self.decode_image(captcha[image]) cell_features [self.preprocess_cell(cell) for cell inself.extract_cells(image)] targets captcha[targets] costs [[self.char_cost(cell_features[pos], char) for pos inrange(9)] for char in targets] best_perm None best_score float(inf)for perm in itertools.permutations(range(9), len(targets)): score sum(costs[idx][pos] for idx, pos inenumerate(perm))if score best_score: best_score score best_perm permreturnlist(best_perm), best_scoredeffetch_captcha(self): resp self.request(GET, /api2/8, params{t: now_ms()}) captcha self.decode_json(resp) targets captcha.get(targets, [])print(f[captcha] id{captcha.get(id)} targets{targets})return captchadefpost_captcha(self, captcha_id, positions): clicks [cell_center(pos) for pos in positions] resp self.request(POST,/api2/8, expect_status{200, 202}, data{captcha_id: captcha_id, clicks: json.dumps(clicks, ensure_asciiFalse)}, )return resp.status_code, self.decode_json(resp)defprobe_captcha(self, captcha): positions random.sample(range(9), 4) status, data self.post_captcha(captcha[id], positions)print(f[probe] pos{positions} status{status} resp{data})if status 200andpickedin data andlen(data[picked]) 4:self.add_labeled_cells(captcha, positions, data[picked])return status, datadefsolve_one_captcha(self): attempts 0whileTrue: attempts 1 captcha self.fetch_captcha() targets captcha[targets] known [char inself.samples for char in targets] sample_counts {char: len(self.samples.get(char, [])) for char in targets}print(f[state] 已知{known} 样本数{sample_counts} 缓存字符{len(self.samples)})ifall(known): positions, score self.predict_positions(captcha)print(f[solve] 尝试 positions{positions} score{score:.4f}) status, data self.post_captcha(captcha[id], positions)print(f[solve] status{status} resp{data})if status 200and data.get(ok):returnTrueif status 200andpickedin data andlen(data[picked]) 4:self.add_labeled_cells(captcha, positions, data[picked])else:self.probe_captcha(captcha) time.sleep(1.2)if attempts % 10 0:print(f[learn] 已循环 {attempts} 次缓存字符 {len(self.samples)})deffetch_page(self, page): params {page: page, pageSize: PAGE_SIZE}whileTrue: resp self.request(GET, /api/question/8, paramsparams) data self.decode_json(resp)if data.get(action) ! captcha:return dataprint(f[page {page}] 需要验证码开始解题)self.solve_one_captcha() time.sleep(0.8)defmain(): solver Match8Solver() all_values []for page inrange(1, TOTAL_PAGES 1): data solver.fetch_page(page)print(fpage {page}: {json.dumps(data, ensure_asciiFalse)}) values data.get(data) or [] all_values.extend(values) time.sleep(0.4) total sum(int(x) for x in all_values)print(fsum: {total})if __name__ __main__: main()AI在分析的时候生成了一个match8_cache.pkl文件这个是和验证码相关的运行结果:可以正常获取到数据。今天的分享就到这里感谢阅读。欢迎加入知识星球学习更多AST和爬虫技巧。

更多文章