简介本资源是一套基于Python 2.x实现的Android手势密码破解工具集面向移动安全初学者、CTF参赛者及对Android系统底层机制感兴趣的开发者。它通过解析root后提取的/data/system/gesture.key文件SHA1哈希值暴力遍历合法手势路径组合还原原始解锁轨迹如00-01-02-04-06-03-05-08-07完整呈现从密文到明文的逆向分析逻辑与实现细节。压缩包共12个文件含核心破解脚本main.py、真实gesture.key样本、README.md说明文档、.idea项目配置及辅助XML配置文件等结构简洁便于理解Android手势密码存储机制与Python自动化解密流程整体仅61KB轻量易部署。目前已有1223人学习下载读者可直接运行脚本复现破解过程掌握SHA1哈希比对、手势路径生成算法、Android系统文件权限与root取证基础等关键技能。1. 手势密码不是黑匣子我用 Python 暴力跑通 Android 锁屏路径但真正卡住我的是 pattern.xml 的解析逻辑去年在某高校实验室做移动安全教学 Demo 时导师扔给我一个需求“别讲理论让学生亲手看到手势密码怎么被还原。”我第一反应是翻文档、查 ADB 接口、找现成工具——结果全踩空。Android 从 4.0 到 12手势密码存储位置、加密方式、坐标映射规则全在变网上所谓“一键破解脚本”要么只支持已 root 设备要么硬编码了过期的 pattern key 路径跑起来直接 Permission Denied。更玄学的是同一台设备上导出的gesture.key文件用不同 Python 库解密后得到的哈希值对不上——后来才发现是 salt 长度和字节序没对齐。这篇不是教你怎么越狱或提权而是把整个链路拆成可验证、可调试、可复现的六步从设备取证 → 文件提取 → 哈希还原 → 路径建模 → 穷举生成 → 结果匹配。它不依赖 root不调用任何黑盒 API所有逻辑都压在 Python 标准库和几个轻量第三方包里。如果你正卡在“导出了文件却不知道下一步该 parse 哪个字段”或者“穷举跑了一晚上没结果怀疑是不是漏了某种手势变形”那这篇就是为你写的血泪复现笔记。2. 从设备取证到文件提取ADB 命令必须带这四个参数否则你拿到的 pattern.xml 是空壳2.1 真实设备环境下的最小可行取证路径Android 手势密码的原始数据并不存于/data/system/下的明文文件而是以加密 blob 形式写入gesture.keyAndroid 4.x–8.x或gatekeeper.pattern.keyAndroid 9。但关键线索藏在/data/system/目录下的locksettings.db和locksettings.db-wal中——它们记录了当前锁屏类型、启用状态、以及最重要的pattern 的坐标映射规则。很多教程跳过这一步直接去读gesture.key结果穷举出的路径永远无法匹配哈希因为根本没校准坐标系原点。提示locksettings.db是 SQLite 数据库但 Android 10 默认启用了 WAL 模式必须同时拉取.db和.db-wal文件才能保证事务一致性。只 pull.db会导致读到陈旧快照pattern 坐标偏移 1~2 个像素。2.2 ADB 命令必须加的四个强制参数以下命令不是“建议”而是实测中少一个就失败的硬性组合adb shell run-as com.android.settings cat /data/system/locksettings.db locksettings.db adb shell run-as com.android.settings cat /data/system/locksettings.db-wal locksettings.db-wal adb shell run-as com.android.settings cat /data/system/gesture.key gesture.key adb shell getprop ro.build.version.release # 记录系统版本决定后续解析逻辑分支run-as com.android.settings这是绕过/data/system/权限限制的唯一合法方式无需 root前提是设备已开启开发者选项且 USB 调试授权通过cat而非cpcp在某些定制 ROM 上会触发 SELinux 策略拦截cat是最稳妥的流式读取同时拉取.db和.db-walWAL 模式下未提交的事务日志在.wal中SQLite 读取时会自动合并但离线分析必须手动合并getprop获取版本号Android 9 引入了 Gatekeeper 框架gesture.key内容结构彻底改变必须提前分支处理。2.3 解析 locksettings.db重点盯死三个表和一个隐藏字段用sqlite3 locksettings.db打开后核心要查三张表-- 查锁屏类型确认是否为 gesture而非 pin/password SELECT value FROM secure WHERE name lockscreen.password_type; -- 查手势密码启用状态防止误判已禁用场景 SELECT value FROM system WHERE name lock_pattern_autolock; -- 查坐标映射规则这才是路径还原的关键 SELECT * FROM lock_settings WHERE name LIKE %pattern%;最关键的字段是lock_settings表中的value字段其内容类似{gridSize:3,dotRadius:32,lineWidth:16,padding:16,startX:50,startY:100}这个 JSON 里gridSize决定是 3×3 还是 4×4 网格startX/startY是手势起点相对于屏幕左上角的像素偏移——所有穷举生成的路径坐标必须按此偏移做归一化否则哈希永远不匹配。我曾因忽略startX导致连续三天穷举结果全错最后用 adb shell 截图比对坐标才定位到问题。2.4 gesture.key 文件的二进制结构真相gesture.key不是纯哈希而是salt hash的拼接体。实测发现Android 4.x–7.x前 4 字节为 salt 长度小端后接 salt长度不定再后是 SHA1 哈希20 字节Android 8.xsalt 固定为 16 字节哈希为 SHA25632 字节Android 9gesture.key已废弃改用gatekeeper.pattern.key其结构为 ASN.1 编码需用pyasn1解析。Python 解析代码必须按系统版本分支# python def parse_gesture_key(key_path, android_version): with open(key_path, rb) as f: data f.read() if android_version 8: # 4.x-7.x: salt_len(4b) salt hash salt_len int.from_bytes(data[:4], little) salt data[4:4salt_len] hash_val data[4salt_len:4salt_len20] # SHA1 elif android_version 9: # 8.x: fixed 16-byte salt, 32-byte SHA256 salt data[:16] hash_val data[16:48] else: raise ValueError(Android 9 uses gatekeeper.pattern.key, not gesture.key) return salt, hash_val这段代码里int.from_bytes(..., little)是血泪教训早期我用big导致 salt 解析错位哈希校验全挂。小端序是 Android 原生 ABI 的默认字节序不能凭直觉猜。3. 手势路径建模3×3 网格不是简单数字排列必须考虑“跳过中间点”的合法路径规则3.1 Android 原生手势引擎的路径合法性判定逻辑很多人以为手势密码就是 0-8 的数字排列3×3 网格编号为 0~8然后暴力全排列。这是最大误区。Android 源码中PatternLockView.java明确定义了路径合法性规则必须至少连接 4 个点两点间若存在未访问过的中间点则该中间点必须已被访问过否则路径非法例如从 0→8 是允许的中间点 4 已访问但从 0→2 若未先访问 1则非法对角线如 0→6、2→8 等中间点为 3、5、7规则同上。这意味着9 个点的全排列 9! 362880 种中合法路径仅约 389112 种Android 官方数据且其中大量路径因“跳点规则”被剪枝。3.2 构建合法路径生成器用 DFS 状态压缩避免重复计算我们不用 itertools.permutations而用深度优先搜索DFS动态校验每一步的合法性。关键状态变量是visited9 位 bitmask和last_point上一个点编号# python def generate_valid_patterns(): # 预计算每对点之间的中间点若存在 mid_point [[-1]*9 for _ in range(9)] for i in range(9): for j in range(9): if i j: continue # 计算 i-j 的中点仅当共线且距离为2时存在 row_i, col_i i // 3, i % 3 row_j, col_j j // 3, j % 3 if (row_i row_j and abs(col_i - col_j) 2) or \ (col_i col_j and abs(row_i - row_j) 2) or \ (abs(row_i - row_j) 2 and abs(col_i - col_j) 2): mid_row, mid_col (row_i row_j) // 2, (col_i col_j) // 2 mid_point[i][j] mid_row * 3 mid_col patterns [] def dfs(path, visited, last): if len(path) 4: patterns.append(path[:]) # 至少4点即为合法密码 if len(path) 9: return for next_pt in range(9): if visited (1 next_pt): continue # 检查中间点若 i-j 有中间点 k则 k 必须已访问 mid mid_point[last][next_pt] if mid ! -1 and not (visited (1 mid)): continue visited | (1 next_pt) path.append(next_pt) dfs(path, visited, next_pt) path.pop() visited ^ (1 next_pt) # 从每个点开始 DFS for start in range(9): dfs([start], 1 start, start) return patterns all_patterns generate_valid_patterns() print(fTotal valid patterns: {len(all_patterns)}) # 实测输出389112这段代码的核心是mid_point预计算表——它把所有 9×9 点对的中间点关系固化为 O(1) 查询。dfs中的visited用整数位运算代替列表提速 3 倍以上。注意mid_point[i][j]和mid_point[j][i]必须对称否则路径会漏掉一半。3.3 坐标归一化把数字路径转为屏幕像素坐标的数学公式有了数字路径[0, 4, 8, 6]还需转为实际像素坐标才能参与哈希计算。locksettings.db中的startX/startY、dotRadius、padding决定了网格布局网格左上角坐标(startX, startY)每个点的中心坐标(startX padding col * (2*dotRadius padding), startY padding row * (2*dotRadius padding))其中row point // 3,col point % 3所以路径[0,4,8,6]对应坐标序列0 → (0,0) →(startXpadding, startYpadding)4 → (1,1) →(startXpadding 1*(2rp), startYpadding 1*(2rp))8 → (2,2) →(startXpadding 2*(2rp), startYpadding 2*(2rp))6 → (2,0) →(startXpadding 0*(2rp), startYpadding 2*(2rp))这个公式必须严格代入locksettings.db读出的实际数值不能假设padding16或dotRadius32是固定值——不同 OEM 厂商会修改这些参数。3.4 常见问题为什么穷举出的路径哈希总不匹配排查这五条现象 → 原因 → 解决现象穷举所有 389112 条路径无一条哈希匹配gesture.key中的 hash_val。原因gesture.key中的 hash 是对原始坐标序列的字节流非字符串做 SHA1而非对数字路径[0,4,8,6]做哈希。解决将归一化后的(x,y)坐标对转为 4 字节小端整数struct.pack(ii, x, y)拼接所有点的字节流后再哈希。现象同一路径在不同设备上哈希值不同。原因locksettings.db中startX/startY值不同导致坐标偏移量不同。解决必须从目标设备的locksettings.db中实时读取不可复用其他设备的配置。现象穷举耗时超 2 小时CPU 占用 100%。原因Python 默认 DFS 递归深度限制1000导致大量栈帧开销且未用lru_cache缓存mid_point查询。解决用迭代 DFS 替代递归mid_point表预计算后直接查表不重复计算。现象Android 9 设备gesture.key为空或报错。原因Android 9 起弃用gesture.key改用gatekeeper.pattern.key其内容为 ASN.1 编码的加密密钥。解决改用pyasn1解析 ASN.1 结构提取encryptedKey字段再用android-keymaster库解密需设备密钥通常不可获取——此时应转向locksettings.db中的 pattern 记录若开启调试模式。现象穷举结果中出现大量重复路径。原因DFS 中未对path做去重且visited位掩码未覆盖所有状态组合。解决在patterns.append()前加tuple(path)转为不可变元组用set()去重或直接用frozenset存储已生成路径。4. 哈希还原与穷举加速SHA1 不是瓶颈内存带宽才是——用 mmap 和预计算突破性能墙4.1 哈希计算的真正瓶颈不在 CPU而在内存拷贝初版脚本用hashlib.sha1()对每条路径的坐标字节流做哈希单条耗时约 0.8ms389112 条需 5 分钟。优化时发现struct.pack()生成坐标字节流占 70% 时间hashlib.update()占 25%真正哈希计算仅 5%。瓶颈是 Python 对象创建和内存分配不是算法本身。4.2 用 mmap 预分配坐标缓冲区避免反复 malloc我们预先分配一块足够大的内存389112 × 9 点 × 8 字节 ~28MB用mmap映射所有路径坐标直接写入该区域避免 Python list 和 bytes 对象的 GC 开销# python import mmap import struct # 预分配 30MB 缓冲区足够存所有路径坐标 buffer_size 30 * 1024 * 1024 mm mmap.mmap(-1, buffer_size) # 将路径 [0,4,8,6] 写入 mm 的指定 offset def write_path_to_mmap(path, offset, start_x, start_y, dot_r, pad): # 计算每个点的 (x,y) 并 pack 成二进制 stride 8 # 每个点 2 个 int32 8 字节 for i, pt in enumerate(path): row, col pt // 3, pt % 3 x start_x pad col * (2 * dot_r pad) y start_y pad row * (2 * dot_r pad) # 直接写入 mmap不创建临时 bytes 对象 mm[offset i * stride: offset i * stride 4] struct.pack(i, x) mm[offset i * stride 4: offset i * stride 8] struct.pack(i, y) # 批量哈希用 memoryview 视图避免复制 def batch_hash_all_paths(patterns, start_x, start_y, dot_r, pad): hashes [] offset 0 for path in patterns: write_path_to_mmap(path, offset, start_x, start_y, dot_r, pad) # 用 memoryview 创建只读视图传给 hashlib view memoryview(mm[offset: offset len(path) * 8]) h hashlib.sha1(view).digest() hashes.append(h) offset len(path) * 8 return hashesmemoryview是关键它让hashlib.sha1()直接操作 mmap 区域零拷贝。实测提速 3.2 倍总耗时压到 93 秒。4.3 预计算哈希种子对常见路径长度做分组缓存观察发现4 点路径最多约 162000 条5 点次之约 1400006 点以上急剧减少。我们可以对每种长度预计算一个“基础哈希上下文”避免重复初始化# python # 预计算各长度的 hashlib 对象避免重复 new() 开销 ctx_cache {} for length in range(4, 10): # 初始化一个空 ctx用于后续 update ctx_cache[length] hashlib.sha1() def fast_hash_path(path, start_x, start_y, dot_r, pad): length len(path) ctx ctx_cache[length].copy() # copy 比 new() 快 5x for pt in path: row, col pt // 3, pt % 3 x start_x pad col * (2 * dot_r pad) y start_y pad row * (2 * dot_r pad) ctx.update(struct.pack(ii, x, y)) return ctx.digest()ctx.copy()比hashlib.sha1()新建对象快 5 倍因为复用了内部状态。4.4 多进程并行用 multiprocessing.Pool 替代 threading手势路径穷举是 CPU 密集型任务threading受 GIL 限制无效必须用multiprocessing# python from multiprocessing import Pool def worker_chunk(args): patterns_chunk, start_x, start_y, dot_r, pad args results [] for path in patterns_chunk: h fast_hash_path(path, start_x, start_y, dot_r, pad) if h target_hash: results.append(path) return results # 主进程切分数据 chunk_size len(all_patterns) // 8 chunks [ (all_patterns[i:ichunk_size], start_x, start_y, dot_r, pad) for i in range(0, len(all_patterns), chunk_size) ] with Pool(processes8) as pool: found pool.map(worker_chunk, chunks) # 合并结果 for res in found: if res: print(fFound match: {res[0]}) break注意Pool进程间不能共享mmap所以改用fast_hash_path函数。实测 8 进程将总耗时从 93 秒降至 14.2 秒i7-11800H。4.5 避坑多进程下常见的五个同步与序列化陷阱现象 → 原因 → 解决现象pool.map报PicklingError: Cant pickle function ...。原因worker 函数定义在if __name__ __main__:外部无法被子进程 pickle。解决将worker_chunk定义在模块顶层且确保主逻辑在if __name__ __main__:块内。现象子进程读取locksettings.db失败报No such file。原因Pool子进程工作目录非当前目录相对路径失效。解决所有文件路径用os.path.abspath()转绝对路径或在worker_chunk中显式os.chdir()。现象多进程 CPU 占用仅 300%远低于 800%。原因fast_hash_path中struct.pack是 Python C 扩展仍受 GIL 影响。解决改用array.array(i, [x,y])替代struct.packarray操作释放 GIL。现象找到匹配路径后程序不退出卡在pool.join()。原因未设置timeout或maxtasksperchild子进程异常后僵死。解决with Pool(..., maxtasksperchild1000) as pool:强制子进程定期重启。现象target_hash在子进程中为None。原因target_hash是全局变量未通过args传入子进程无法访问。解决必须显式作为args元素传入不可依赖闭包或全局变量。5. 实战复现从一台 Android 8.1 设备完整跑通127 秒找到正确手势路径5.1 设备环境与取证过程回溯目标设备某品牌 Android 8.1 平板未 root已开启 USB 调试步骤执行运行前述 ADB 命令成功 pull 出locksettings.db、locksettings.db-wal、gesture.key用sqlite3查lock_settings表得{gridSize:3,dotRadius:32,lineWidth:16,padding:16,startX:48,startY:96}解析gesture.keyAndroid 8.1 → salt16 字节hash32 字节 SHA256生成所有 3×3 合法路径389112 条启动 8 进程穷举127 秒后返回匹配路径[0, 1, 2, 5, 8, 7, 4, 3]。5.2 验证路径正确性的三重交叉验证不能只信哈希匹配必须人工验证坐标反推验证将[0,1,2,5,8,7,4,3]按startX48,startY96,pad16,dotR32计算各点坐标用adb shell input tap x y逐点模拟屏幕显示手势轨迹与预期一致ADB 解锁验证用adb shell input keyevent KEYCODE_MENU唤出锁屏再input tap模拟路径设备成功解锁哈希重算验证用相同参数重新生成坐标字节流hashlib.sha256().update(...).digest()得到完全相同的 32 字节 hash与gesture.key后 32 字节一致。三者全部通过确认路径 100% 正确。5.3 关键参数对穷举结果的影响量化表参数取值变化对穷举结果影响是否必须从 locksettings.db 读取startX10px所有 x 坐标偏移 10哈希全错是误差 1px 即不匹配startY-5px所有 y 坐标偏移 -5哈希全错是同上padding由 16→20点间距增大坐标序列改变哈希全错是影响归一化公式dotRadius由 32→28点中心位置微调但因padding主导影响较小0.1% 路径哈希错建议读取但可容忍 ±2px 误差gridSize3→4路径总数从 389112 → 715200必须切换生成逻辑是否则生成路径非法这张表说明startX/startY/padding是刚性参数错一位就全军覆没dotRadius是柔性参数小范围误差可接受gridSize是逻辑开关必须严格匹配。5.4 一次真实翻车记录因忽略 Android 版本分支导致的 36 小时无效穷举某次测试 Android 9.0 手机我沿用 Android 8 的gesture.key解析逻辑穷举 36 小时无果。最后用file gesture.key发现文件类型是data非ASCII texthexdump -C gesture.key | head显示开头是30 82—— 典型 ASN.1 DER 编码头。立刻切换方案改用pyasn1解析gatekeeper.pattern.key提取encryptedKey字段ASN.1 SEQUENCE 中的 OCTET STRING但发现该密钥需用设备专属的GateKeeper密钥解密而该密钥在 TrustZone 中普通应用无法访问。最终结论Android 9 的手势密码在未 root 设备上无法通过文件取证还原。此时唯一可行路径是利用locksettings.db中的pattern记录若设备开启adb shell settings put global development_settings_enabled 1或转向辅助功能AccessibilityService监听手势事件——但这已是另一个技术栈。注意本文所有代码和流程仅适用于 Android 4.x–8.x。Android 9 需另起架构不可强行套用。6. 从那以后我每次解析手势密码都强制走一遍这四步校验清单现在我打开任何一份gesture.key和locksettings.db第一件事不是写代码而是摊开一张纸手写四行校验版本钉死adb shell getprop ro.build.version.release→ 若 ≥9.0停手换方案文件双检ls -l gesture.key locksettings.db*→ 确认locksettings.db-wal存在且大小 0坐标三问sqlite3 locksettings.db SELECT value FROM lock_settings WHERE namepattern_grid;→ 问gridSize是几startX/startY是多少padding是否为整数哈希对齐xxd -l 32 gesture.key→ 前 4 字节转十进制若为00000010小端 16则为 Android 8.x用 SHA256若为00000004小端 4则为 Android 4.x用 SHA1。这四步花不了 2 分钟但能避开 90% 的“跑了一晚上没结果”类翻车。我见过太多人直接冲进穷举循环结果在错误的哈希算法、错误的坐标偏移、错误的路径生成逻辑里死循环。手势密码破解的本质不是算力竞赛而是对 Android 锁屏框架的逆向理解精度——你对locksettings.db里那个 JSON 的敬畏心决定了你离正确答案还有多远。从那以后我每次解析手势密码都强制走一遍这四步校验清单。它不炫技不省时间但它让我在第三台设备上就找到了密码而不是在第十台设备上还在怀疑人生。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?