ctf-一人成军

一人成军:

这道题的前置条件有点多,一个root过的真机或者模拟器,要懂frida脚本怎么写等等。

还有此题有一个严重的漏洞,关键加密函数被导出(写入了导出表),使得可以脱机进行请求伪造,比赛唯二解都是这么做的,属于非预期了,但却不是最简单的解法。

预期解:

使用命令行frida -f com.tower.redrock -l test.js 或者frida -n com.tower.redrock -l test.js,这是两种模式,前一种是spawn模式,可以在程序没有启动的时候启动并注入,或者重新启动并注入已经运行的程序,后者attach模式,是注入正在运行的程序,得手动开启app。有兴趣的可以自己了解一下两者的原理。

成功注入脚本后,输入用户名,然后点击一次注册(返回注册成功),接着点击助力(返回助力成功,如果返回”不能给自己助力”,就是脚本没注入成功),一共连续点击六次助力(因为脚本里写了六个助力者,都应该返回“助力成功”),最后再点击一次注册,这个时候就能拿到flag了。

至于脚本为什么这么写,原因如下:

逆向分析这部分不细致讲,反正唯一的一个网络请求是Java层调native层函数,用的静态注册,很好找。

image

进去找到分析json是怎么拼的,{\"a\":\"%d\",\"username\":\"%s\",\"fstatus\":%d,\"fingerprint\":\"%s\",\"mac\":\"%s\",\"bt_mac\":\"%s\",\"serial\":\"%s\",\"baseband\":\"%s\",这些字段除了a,username不需要去试,其他每一个设备信息字段都应该去试一试替换,当username一致,但是我们替换掉某个字段信息时,点击助力能返回“助力成功”,就代表这个字段就是要找的设备唯一认证字段,可以给个脚本(没试过能不能跑通,可以自己改改):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//就以fingerprint为例
function hook_getprop(){
const addr=Module.findBaseAddress("libc.so").add(偏移);//偏移根据使用架构所在so中偏移确定,代码段的getprop函数的偏移,注意不是plt段的getprop函数的偏移
Interceptor.attach(addr,{
onEnter:function(args){
this.flag=false;
this.args1=args[1];
if(Memory.readCString(args[0])=="ro.serialno"){
this.flag=true;
}
},
onLeave:function(retval){
if(this.flag){
let value=fingerprint[count.next().value]
console.log(`当前fingerprint:${value}`);
Memory.writeUtf8String(this.args1, value);
}
}
});
}

hook_getprop();

试过之后应该能发现是fingerprint这个字段,然后我们就hook替换就行,脚本里写了一个生成器,每次触发hook就会替换成不一样的值,这样就不用每个助力者都单独hook了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const fingerprint=["注册用软指纹","第一个助力者","第二个助力者","第三个助力者","第四个助力者","第五个助力者","第六个助力者(下一步点击注册)","注册用软指纹"];//内容任意、首尾一致就行

function* idGen() {
let id = 0;
while (true) yield id++;
}
const count=idGen()
function hook_getprop(){
const addr=Module.findBaseAddress("libc.so").add(偏移);//偏移根据使用架构所在so中偏移确定
Interceptor.attach(addr,{
onEnter:function(args){
t··his.flag=false;
this.args1=args[1];
if(Memory.readCString(args[0])=="ro.build.fingerprint"){
this.flag=true;
}
},
onLeave:function(retval){
if(this.flag){
let value=fingerprint[count.next().value]
console.log(`当前fingerprint:${value}`);
Memory.writeUtf8String(this.args1, value);
}
}
});
}

hook_getprop();

为什么hook libc.so?因为libredrocksdk.so在编译的时候编译器选择直接调用系统库的导出函数,没有封装,那就只有直接hook函数定义的地方,就是libc.so

如何查看偏移?可以把libc.so导出到电脑上,用ida查看。也可以直接用frida的Module.enumerateExports("libc.so"),查看_system_property_get的内存地址,然后Module.findBaseAddress("libc.so")查看libc.so的内存地址,两者做差就是偏移。

赛事服务端已经销毁了,现在可以自己搭建复现,服务端和配套客户端在下面这个压缩包。

reproduction.zip