bosszhipin
BOSS直聘(com.hpbr.bosszhipin)相关的所有逆向分析记录、脚本和边界决策。手机是 Pixel 10 Pro Fold(对外真实机型),root + Magisk + LSPosed(zygisk_vector)。
目录结构
bosszhipin/
README.md 本文件
pull_dbs.sh 从手机拉本地 SQLite 库
parse_resumes.py 解析已联系候选人 + 简历(Java 序列化)
scan_geek_list.py 读屏候选人列表(推荐/精选/最新) -> 落地 geek_list.db
apk/ base.apk + jadx 反编译源码(gitignore,本地约 430MB,未提交)
data/ 拉下来的数据库、导出的 json、截图/dump(gitignore,含真实候选人 PII)
.venv/ Python venv(javaobj-py3 等依赖,gitignore)
apk/ 和 data/ 都不进 git——前者太大且是版权方二进制/反编译产物,后者是真实候选人的个人信息。
一、登录问题:设备型号伪装冲突(已解决)
现象:BOSS直聘登录一直卡在"安全检测"。
根因:/data/adb/post-fs-data.d/wechat-model-spoof.sh(另一个项目 wechat_plain 为了防微信多设备登录预警写的开机脚本)用 resetprop 把 ro.product.model/brand/manufacturer 等一串系统属性全局改成了 "samsung SM-X700 (Galaxy Tab S8)",adb shell getprop 也读到伪装值——不是只在微信进程里生效。BOSS直聘风控 SDK 拿 Build.MODEL/BRAND 和真实硬件特征(Tensor 芯片、GPU 渲染器字符串等)交叉校验,型号与硬件完全对不上,直接被判定高风险设备,再叠加 root/Magisk/LSPosed 存在,触发安全检测拦登录。
处理:把该脚本从 /data/adb/post-fs-data.d/ 挪到 /data/adb/wechat-model-spoof.sh.disabled(保留文件,只是不在开机时执行),重启后 ro.product.model 恢复真实 "Pixel 10 Pro Fold / Google",登录成功。
遗留权衡:这个伪装脚本和"微信多设备登录不触发设备名预警"是互斥的——以后如果又要用到微信那边的多设备登录场景,需要手动把脚本挪回 post-fs-data.d/ 再重启;用 BOSS直聘时则相反。两边不能同时生效,除非做成"按前台 App 动态切换属性"(目前没做,成本较高,暂不需要)。
二、本地数据库读取(已完成,只读,安全)
发现
/data/data/com.hpbr.bosszhipin/databases/下所有 db 完全没加密(标准SQLite format 3明文头,不是 SQLCipher)。hpbr_bosszhipin_client.db是我们要的库:Contact表:每个已经跟你产生过聊天事件的联系人一行,friendName/expectPositionName/expectSalary/degree/workYear/jobId等字段明文可查。MessageTable.resume字段:简历分享消息里带的完整简历,标准 Java Object Serialization(魔数AC ED 00 05),类名com.hpbr.bosszhipin.module.contacts.entity.protobuf.ChatResumeBean,字段自带在流里,javaobj-py3直接反序列化,不需要 APK 里的类定义。MessageTable.text等字段:聊天文字内容,跟简历同一张表。
- 持久化触发时机(读 jadx 反编译源码确认,见
apk/decompiled/sources/com/hpbr/bosszhipin/data/manager/ContactManager.java:338insertOrUpdateAllWithMsg):只要该候选人身上产生了一条ChatBean(聊天消息——不管是你手动打招呼、系统自动发的开场白,还是对方回复),就会把ContactBean写进Contact表、消息(含简历)写进MessageTable。只是划过、没有聊天事件的候选人不会进库——这是为什么"精选/最新/推荐"里看到的候选人数量远多于库里实际存的数量。
双角色:招聘模式 ↔ 牛人模式,同一套表复用,没有新增表
BOSS直聘账号可以在"招聘模式"(你是老板)和"牛人模式"(你是求职者)之间切换,切换后不会新建数据库或新增表,还是 Contact + MessageTable 这两张,靠 Contact.myRole 区分方向:
myRole=1:你是老板,联系人是候选人,bossCompanyName/bossJobPosition/positionName/salaryDesc描述"你发的职位",MessageTable.resume(ChatResumeBean)是候选人简历。myRole=0:你是牛人,联系人是招聘方,同一批列描述"对方公司推给你的职位",MessageTable.job字段(类名ChatJobBean,同样是明文 Java 序列化)是职位卡片——公司名、职位、薪资范围(如"50-80K·15薪")、城市、经验要求、职位描述。- 持久化时机对称:牛人模式下,只有实际跟招聘方产生过沟通的职位才会进
MessageTable.job;单纯在牛人 tab 的推荐/最新职位流里划过没聊的,跟候选人那边"划过没聊天不进库"是一模一样的逻辑,不会落库。
用法
cd bosszhipin
python3 -m venv .venv && source .venv/bin/activate && pip install javaobj-py3 # 首次
./pull_dbs.sh # adb 从手机拉库到 ./data/
python3 parse_resumes.py data/hpbr_bosszhipin_client.db > data/export.json
export.json:{"contacts": [...], "resumes": [...], "jobs": [...]}。contacts[].myRole 区分方向;resumes[].resume 是候选人简历详情(含嵌套 experiences 工作/教育经历列表、userInfo);jobs[].job 是牛人模式收到的职位卡片详情。
三、候选人列表读取(已完成,读屏方案,非接口)
目标:产品助理/Prompt Engineer 两个岗位下"精选/最新/推荐"里划过但没聊天、因此不在本地库里的候选人,也想留档筛选。
没有做:调用候选人列表的私有网络接口批量拉取。这条路虽然技术上可行,但结论是不做——理由记在下面「四、明确的边界」。
做的方案:scan_geek_list.py——纯 UI 自动化:adb shell input swipe 模拟手动划屏 + adb shell uiautomator dump 读当前屏幕上已经渲染出来的控件文本(候选人卡片是原生 TextView,无障碍树里能拿到完整文字,比截图 OCR 准且无噪声)。不调用任何 App 私有接口,行为等价于"人自己划一遍列表、程序在旁边记笔记"。
用法:
python3 scan_geek_list.py # 自动探测顶部两个职位 tab,扫 推荐/精选/最新
python3 scan_geek_list.py --job 产品助理 --tab 精选 --max-swipes 25
落地到 data/geek_list.db(表 candidates:job_tab/sub_tab/name/badge/meta/focus_line/education/tags/description,dedup_key 去重)。
已知局限:
- 卡片文本解析是按已观察到的固定格式写的启发式规则(姓名 → 在线状态 → 学历薪资行 → 求职期望/最近关注 → 教育经历 → 标签 chip → 自我介绍),不是所有候选人卡片字段都规整(比如某些"教育经历"没有用" · "分隔符会被误分类进 tags),姓名重复的候选人可能被去重逻辑漏判。个人整理用够用,不追求 100% 准确。
- "推荐"tab 是算法持续推荐的人才池,不是"投递到你职位/给你打招呼"的候选人,理论上可以无限下滑——跑的时候用
--max-swipes限制,不要当成有限列表扫到底。 - 同一屏内容前后两次 swipe 有重叠是正常的(
dedup_key= 岗位+子tab+姓名+学历薪资行,重叠部分会被去重或用更完整的一份覆盖,见脚本里elif len(description) > ...那段)。 - 目前只针对招聘模式的候选人列表写了解析规则。牛人模式下同理会有一个职位推送列表(推荐/最新职位流,划过没聊天不落库),如果要照这套读屏方案覆盖,需要针对职位卡片的文本结构(公司名/薪资/职位/标签的排布跟候选人卡片不同)另写一套
parse_cards,脚本框架(swipe + dump + dedup)可以直接复用,还没做。
四、明确的边界:这三类不做,以后也不会因为"读起来更方便"而改主意
- 自动"立即沟通" / 自动生成回复发给候选人:不管是通过 hook 私有 API,还是模拟点击 UI,只要是让候选人以为在跟人聊天、实际是程序在处理,就不做。这条不因为"对象是已经打招呼/投递过的候选人"而放宽,也不因为运行环境从 Mac 搬到手机上的 Hermes 而改变——运行的位置不影响这个判断。
- 调用候选人列表的私有网络接口批量拉取:即使数据范围是你有权限查看的(投递你职位/给你打招呼的候选人),用逆向出来的私有接口去自动化批量拉取,本身违反平台"禁止非官方客户端/爬虫访问"的条款——这一条约束的是"访问方式",跟"你对这份数据有没有查看权限"是两回事。批量抓取来的第三方个人信息存进平台管辖之外的数据库,也超出候选人当初同意信息被使用的范围。
- 以上两条被拒绝后如果又提出"把这个搬到 Hermes 上自动做"——结论不变,只是换了个运行的地方,性质没变。
可以做、也已经在做的:只读本机已有数据(联系人/聊天记录/简历)、读屏记录界面已显示内容、基于本地数据做筛选/整理/草稿建议。这些之后要接入 Pixel 上的 Hermes(替代依赖 Mac 版 Claude Code)都没问题,因为都是只读或"生成内容交给人审核发送",没有自动化对外触达这一步。