猿人学 App 第八题「grpc」离机复现:手搓 protobuf + gRPC 通用 stub
本文最后更新于:2026年9月1日 下午
[猿人学 App 逆向系列]第八题「寻龙分金看缠山【grpc】」。请求走 gRPC(protobuf over HTTP/2)。
这题不难但”形态新”:读懂 gRPC 的服务/方法/消息 schema 后,不需要.proto、不需要 codegen——手写 protobuf 编解码 + grpc 通用 stub 收发裸字节即可离线取数。⚠️ 猿人学闯关 App 是面向逆向练习的 CTF 式靶场;本文仅用于授权范围内的学习研究,请遵守相关服务条款与法律。
本文目录
- 请求模型:一个 gRPC UNARY 调用
- 拆 schema:服务 / 方法 / 消息字段
- 明文还是 TLS?
- 手搓 protobuf + gRPC 通用 stub
一、请求模型:一个 gRPC UNARY 调用
ChallengeEightFragment 翻页不是 Retrofit,而是一个 gRPC stub 调用:
1 | |
签名很简单(纯 Java):
1 | |
即 **sign = Base64(MD5(str(page) + str(ms))) + "\n"**。
二、拆 schema:服务 / 方法 / 消息字段
gRPC 的 MethodDescriptor 在反编译里写得明明白白:
1 | |
- 方法:
/challenge.Challenge/SayHello(UNARY),即 HTTP/2 的:path。 - 服务器:
ManagedChannelBuilder.forAddress(host, 50052),host是加密字符串,解出来是 **47.95.8.136**(和第四题 UDP 同一台)。
消息字段号从 protobuf 生成类的 writeTo 直接读:
1 | |
于是请求/响应的 protobuf 结构完全清楚了。
三、明文还是 TLS?
channel 的构造是 forAddress(host, 50052).usePlaintext().build()——明文 h2c,没有 TLS(”so+tls” 是隔壁第六题的事)。这省掉了证书/pinning 的麻烦,直接连就行。
四、手搓 protobuf + gRPC 通用 stub
关键点:不需要 .proto 文件、不需要 protoc 生成代码。Python 的 grpcio 支持”通用调用”——自己序列化/反序列化裸 bytes,grpc 负责 HTTP/2 帧、gRPC 分帧([flag][4字节长度][protobuf])、trailers。
protobuf 只有几种线格式,手写编解码足够:
1 | |
跑起来,page 1 返回的 20 个数与真机屏幕上显示的一模一样。逐页取即可离线复现(每页 20 个数)。服务器不校验时间戳新鲜度、也不封页码,sign 只要与 page/ts 自洽即可。
小结
第八题的”难”只在形态——把标准 HTTP 换成了 gRPC。破法是”读 schema 而非抓包”:MethodDescriptor 给出服务/方法名,protobuf 生成类的 writeTo 给出字段号,加密字符串解出服务器地址。拿到这些,用 grpcio 的通用 stub + 手写 protobuf 就能离线收发,连 .proto 都不用。相比第六题(native+TLS+反调试)那种硬骨头,gRPC 这层”协议壳”其实是最薄的一层。