周五晚8点参加了猿人学的app逆向比赛,和我的队友"duoduo"兄一起把这道题做出来了,于是简单分享一下思路和过程。
⊙一 .什么是grpc
⊙二.什么是protobuf
⊙三.数据包协议分析
⊙四.sign加密与python代码实现
⊙五.总结
一 .什么是grpc
gRPC 是一个现代开源的高性能远程过程调用 (RPC) 框架,可以在任何环境中运行。它可以通过对负载平衡、跟踪、健康检查和身份验证的可插拔支持有效地连接数据中心内和跨数据中心的服务。它也适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。
gRPC
基于 HTTP/2
协议传输。
客户端传递个函数进去,然后服务端执行成功后给客户端结果。
二.什么是protobuf
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
可以简单理解为,是一种跨语言、跨平台的数据传输格式。与json的功能类似,但是无论是性能,还是数据大小都比json要好很多。
protobuf之所以可以跨语言,就是因为数据定义的格式为.proto
格式,需要基于protoc编译为对应的语言。
三.数据包协议分析
初看java层
主要的逻辑是:把时间戳和每次下拉之后页数自动加1后的数据放入到函数中去执行,java层被混淆的面目全非,中间掉用了一个sign 为native的方法,于是换种思路从数据包上面开始分析。
3.1抓包
拿到这道题的时候受前面几道题目的影响以为直接可以抓到包,使用Drony转发到charle上后发现没有这道题的数据包,初步怀疑使用了其他的协议,然后在pc打开wireshark进行数据包的拦截,同时开启frida hook 第四题中的sign。
发现 frida hook的值和wireshark抓到的数据包相同,这也就坐实了这个数据包为第四题的发包函数。
然后我就想着能不能再找到收到的响应呢?
在wireshark输入过滤条件
然后一个一个包来看
确实找到了返回数据。
3.2数据包分析
于是找来两个不同时间发送的数据包来对比下差异
也就是说结尾位置的为一些重要的参数,我们能够伪造到然后加上前面的数据段不久可以请求服务器产生数据了?
那么如何伪造这些参数呢?
在数据的hexdump中只能看到sign的身影,那么page和时间戳的身影跑哪去了,经过了查阅资料后发现grpc数据的传送经过了protobuf的编码,字符串在这里可以显现,但是时间戳在hexdump里面并没有被显现出来。
那么开始编写protobuf文件吧
syntax = "proto3";//指定版本为proto3,默认为proto2
message SearchRequest {
uint32 page =1;
uint64 time = 2;
string sign =3;
}
然后执行:protoc --proto_path=./ --python_out=./ test.proto
def bytes2hex(byte_arr: bytes) -> str:
return byte_arr.hex()
import test_pb2
# 实例化协议对象
ser = test_pb2.SearchRequest()
ser.page = 1
ser.time = 1652580021332
ser.sign ="b94a543f66e23656"
# 对数据进行序列化
data = ser.SerializeToString()
print(type(data))
print(bytes2hex(data))
这个时候当事人1和当事人2都非常开心,是不是就可以解决了?解决了就可以睡大觉了
然后十分钟:连接服务器 把tcp头部和数据拼接起来发给服务器。。。。。经过实验 失败,服务器会莫名断开连接。
四.sign加密与python代码实现
于是想了下python如何grpc的请求和服务器交互,说干就干.经过了一番查阅资:需要以下条件
服务器的地址和端口(废话),前文中我们提到grpc 那么就有要被执行的函数,有被执行的函数就得有接受函数执行完的结构。
于是乎 下面的代码就出来了
syntax = "proto3";
package challenge;
message app4 {
uint32 page =1;
uint64 time = 2;
string sign =3;
}
message HelloReply {
repeated string message = 1;
}
service Challenge{
rpc SayHello (app4) returns (HelloReply) {}
}
# -*- coding: utf-8 -*-
import grpc
import data_pb2,data_pb2_grpc
_HOST = '180.76.60.***'
_PORT = '9901'
def run():
conn = grpc.insecure_channel(_HOST + ':' + _PORT)
client = data_pb2_grpc.ChallengeStub(channel=conn)
page_time = "1:1652586112285"
sign = "92979f42250a942b"
page_time = "4:1652593296974"
sign = "f9f5c6fe5b3da911"
page = page_time.split(":")[0]
time = page_time.split(":")[1]
response = client.SayHello(data_pb2.app4(page = int(page),sign = sign,time =int(time)))
print( str(response))
if __name__ == '__main__':
run()
在终端下执行:
#python3 -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. data.proto
即可生成grpc和probuf协议的文件。
run一下
发现返回的数据结构怎么这么多位,服务器抽风了还是我的程序抽风了,后来"duoduo"兄说前面的004说明后面有四个值是对的。
于是协议模拟的问题告一段落。
要让整个算法跑出来算100页的数据,还需要sign参数
frida 代码如下
var signclass1 = Java.use("com.yuanrenxue.match2022.fragment.challenge.ChallengeFourFragment");
signclass1.sign.implementation = function (arg1,arg2) {
console.log("");
console.log("page_time= ""+arg1+""");
console.log("sign= ""+this.sign(arg1,arg2)+""");
return this.sign(arg1,arg2);
}
发现sign :
第一个参数为page:时间戳。第二个参数就是时间就是时间戳
由于比赛原因 谁先搞完就能排名高,掏出unidbg(也想还原来着,那么短时间不太现实)
写unidbg调用
主要代码如下
public String callsign(String input,long j){
List<Object> list=new ArrayList<>(10);
list.add(vm.getJNIEnv());
list.add(0);
list.add(vm.addLocalObject(new StringObject(vm,input)));
list.add(j);
//Number number=module.callFunction(emulator,0xdf0,list.toArray())[0];
Number number = module.callFunction(emulator, "Java_com_yuanrenxue_match2022_fragment_challenge_ChallengeFourFragment_sign", list.toArray());
return vm.getObject(number.intValue()).getValue().toString();
}
然后排名上升两名 end。
五.总结
要去看更多的东西才能扩宽自己的眼界,有些技术不是难,而是没接触过(hhh接触过也不一定会),还有"duoduo"兄 yyds。
我是BestToYou,分享工作或日常学习中关于二进制逆向和分析的一些思路和一些自己闲暇时刻调试的一些程序,文中若有错误的地方,恳请大家联系我批评指正。
原文始发于微信公众号(二进制科学):猿人学-app逆向比赛第四题grpc题解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论