记一次Frida分析锁机病毒的体验
本帖最后由 LoLik 于 2020-5-8 23:04 编辑排版编辑完成
# 目录
> * 病毒信息
> ...
> * 初步分析:
(0)上手条件
(1)在线沙箱
(2)安装观察
(3)静态观察
(4)动态观察
(5)阶段总结
(6)确定分析方向
> .
> * 详细分析:
(1)病毒如何做到的禁用USB PC连接,无法动态分析怎么办?
(2)病毒如何做到的无法关闭音量循环播放噪音?
(3)病毒如何做到的锁屏无法退出?
(4)病毒如何做到的开机自启,APP进程关闭后复活?
(5)解锁算法分析与尝试编写注册机.
# 病毒信息:
> * (1)病毒类型: 锁机勒索
(2)病毒来源: 来着吾爱破解病毒样本区求助贴 https://www.52pojie.cn/thread-1023228-1-1.html
(3)沙箱信息: https://www.appscan.io/app-report.html?id=285e232a42667ad4abeffb9ff2baa188b292ea8f
(4)文件名: com.shimeng.qq2693533893
(5)文件大小 135.2 KB
(6)文件MD5: 4a4792e2a2abf7540f8e1772edd41338
(7)分析总结:
通过广播和服务实现APP复活与自启动,
通过全屏置顶的悬浮窗实现锁屏,
病毒编写了三层勒索,前两次逻辑类似,
最后一次通过HTTP响应内容控制解锁密码..
.
# 初步分析:
### (0)上手条件:
> * 语言基础: JAVA
工具基础: ADB APKTOOL JADX JEB(其实就几个常用命令)
HOOK工具: FRIDA(这个是最重要的,要花时间好好掌握)
ANDROID_SDK: 暂时不会也没关系(不必等材料齐全再下锅)
.
.
### (1)在线沙箱
> * 不打无准备之仗,
在真刀真枪动手之前,
还是要尽可能的收集情报以免走弯路,
我们找一个在线的风险评估和在线监测平台,
上传应用先看看能获取哪些信息.
发现APP使用了很多权限,还有发短信,开机自启动的行为.
.
.
.
### (2)安装观察
> * 场面高能,安装后发现背景和背景音乐非常不健康,
音量被调到最大,循环播放噪音,无法关闭.
屏幕被锁定,无法关闭.
.
.
### (3)静态观察
> AndroidManifest.xml文件
> * 对一个APK进行初步的观察,,要从APP的AndroidManifest.xml开始,因为常识是它描述了APP的入口,
比较明显的可以看到大片段的uses-permission,大片段的action,以及一个Mainactivity
>
.
.
> * 初步上手,搜索学习,得出结论,
uses-permission android:name是声明APP的权限,
receiver action是广播相关的的声明,
>
.
.
> * APP权限很好理解,
uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"
uses-permission android:name="android.permission.RECEIVE_SMS"
APP声明了自启动 发送短信等权限,理解比较直观.
>
.
.
> * 至于广播,不是很理解是个啥子东西,
搜一下什么是安卓广播,看了一下通俗来说,
广播有两方,为播音员和收音机,
>
class 收音机{
1 频道
2 收音机收到广播后的行为
3 收音机权限
4 收音机优先级
5 注册收音机
}
.
.
> * 主要关注的是:
收听的频道,
收听到后做什么
以及将你收听的频道注册到系统.
>
.
.
> * XML中这段代码就是APP在像系统注册收音机,
MyBroadcast就是收音机名字
BOOT_COMPLETED代表着收音机收听的频道是开机启动这个频道
收听到后做什么?这个应该在代码中,要去看MyBroadcast的实现.
receiver android:name=".MyBroadcast"
intent-filter android:priority="2147483647"
action android:name="android.intent.action.BOOT_COMPLETED"
......
.
.
> * 初步可以这么推测,APP有一个叫MyBroadcast的收音装置,
接收了BOOT_COMPLETED等信号,
如果收到了这些信号,
会在它的MyBroadcast类实现中执行一些操作.
.
.
> * 其他java文件,以及目录结构,文件名都比较明显,
> Floatviewutil 应该是UI工具类
> MainAct 是APP入口代码
> MyBroadcast 就是刚才在xml中看到的收音机实现
> MyserviceOne 暂时不知道干嘛的,代码最多,估计app的主要逻辑都会在这里,
> Shell类command 应该是一个执行shell命令的工具类
> UsbLock类 看名字应该是USB有关的
> qq****类 不知道干嘛的,初步看了一下头部导包,大概是一个算法和加解密的类
> logcatBroadcaster 日志接收类,估计是一个监听日志内容的东西.
>
.
.
### (4)动态观察
> * 模拟器启动APK,运行ADB,发现找不到设备,
以为是模拟器的原因,多次尝试后依然无果,
想起了静态分析中看到USBLOCK类,
去翻阅该类代码,果然是APP执行了命令,
运行后即断开USB连接,adb都用不了,
这个环节只好暂时先放弃了.
.
.
### (5)阶段总结
看了一大圈,总结下收获到那些内容.
>* 通过在线沙箱的数据,我们得知了APP有以下重点行为:
1 发短信
2 开机自启动
3 HTTP连接
.
.
> * 通过安装APP进行观察,得知APP有以下明显恶意行为:
1 音量被调到最大
2 循环播放噪音
3 屏幕锁定无法退出
4 开机会自启
.
.
> * 通过初步分析,得知APP以下内容:
1 申请权限,广播频道 AndroidManifest.xml
2 APP入口代码 MainActivity
3 疑似UI工具类: Floatviewutil
4 疑似广播接收器实现类: MyBroadcast
5 疑似shell命令工具类: shell.command
6 疑似USB相关操作: usblock(运行后断开USB连接,无法使用ADB)
7 疑似算法加解密类: qq2693533893
8 监听日志内容: logcatBroadcaster
9 未知内容类:: MyserviceOne(代码最多,猜测为APP的主要实现)
.
.
### (6)确定分析方向
> * 我们根据收集到的信息,
>确定重点分析方向,然后作为着手点,
>后面带着问题去探索各个环节,逐个击破,避免全篇分析代码.
> .
> 1 病毒如何做到的禁用USB PC连接,无法动态分析怎么办?
> 2 病毒如何做到的开机自启,APP进程关闭复活?
> 3 病毒如何做到的锁屏无法退出?
> 4 病毒如何做到的无法关闭音量循环播放噪音?
> 5 解锁算法分析与尝试编写注册机.
.
.
# 详细分析:
##### 1 病毒如何做到的禁用USB PC连接,无法动态分析怎么办?
##### 2 病毒如何做到的开机自启,APP进程关闭复活?
##### 3 病毒如何做到的锁屏无法退出?
##### 4 病毒如何做到的无法关闭音量循环播放噪音?
##### 5 解锁算法分析,尝试编写注册机.
.
.
### 1 病毒如何做到的禁用USB PC连接,无法动态分析怎么办?
> * 安装病毒后运行,发现断开USB操作,
无法连接adb,没法用frida hook,也没法看DDMS,只能静态分析.
JEB静态反编译一下去找刚才的USB类,
发现原理很简单,就是运行了一条命令,禁止了USB连接.
还有一条是windwos是注册表的操作,在手机运行是上没用的,不需要关注.
.
.
> * 第一个想到是可以hook下runtime.exec(),让他执行失败,就不会断开usb,
frida派上用场了,写一段脚本,spawn模式启动apk,先hook再resume,测试成功了,adb保持连接.
但是发现五分钟后自动重启apk,这样hook就断开了,还是会断开USB,有点棘手.
.
.
> * 动态不行就修改smali好了,尝试重打包操作
apktool解包apk
.
.
> * notepad++ 注释掉代码
.
.
> * apktool编译重打包 ,keytool制作密钥库,apksigner对apk签名,
成功安装运行,usb不会再断开连接,,后面可以正常的使用动态工具进程测试分析了.
.
.
.
### 2 病毒如何做到的无法关闭音量,循环播放噪音?
> * 工欲善其事,必先利其器,
在处理下面的问题之前,还先准备好干活的利器.
.
.
> * frida是个很方便的工具,但是还需要完善一下脚本.,
在分析时我发现有个普遍需求,需要hook所有的类,
调用关键逻辑后输出方法执行轨迹,
并且过滤掉一些标准库方法调用信息的干扰.
.
.
> * 其实也有一些基于frida二次开发的工具objection等实现了这种通用功能,
经过尝试后发现不太顺手.
大概是我不会用,
没法很愉快的过滤分类HOOK消息,
没法细粒度筛选HOOK范围.
.
.
> * 所以自己写一些hook所有用户方法的脚本工具,
一脚本方法,对应一消息回调分类,
并且暂时配置了些简单的消息过滤功能.
.
.
> * 使用封装的frida脚本,运行APP,接着一键hook所有用户类,
然后什么都不做,观察下HOOK的情况,发现有三个方法的HOOK信息在反复输出
static android.os.Handler com.shimeng.qq2693533893.MyServiceOne.access$L1000018(com.shimeng.qq2693533893.MyServiceOne)
public void com.shimeng.qq2693533893.MyServiceOne$100000007.run()
public void com.shimeng.qq2693533893.MyServiceOne$100000000.run()
.
.
> * 先看一下public void com.shimeng.qq2693533893.MyServiceOne$100000000.run()在做什么
只有一行代码, 百度搜一下MediaPlayer,发现是安卓播放音乐的,
这个内部类又继承了timetask,这是个定时执行的类,
所以可以断定这段代码99%是播放噪音的的代码,
为了证实猜测,使用frida hook这个方法,取消MediaPlayer.crete的调用,
APP噪音没有了,疑问算是验证了一半,
只是还不知道哪里把音量锁定到最大的.
.
.
> * 接着分析public void com.shimeng.qq2693533893.MyServiceOne$100000007.run()
静态看一下代码,字面上看是音量振动器有关的,
估计就是通过这个实现的无法关掉音量,持续播放噪音,
还是HOOK他看效果,取消run内部的执行,可以关闭音量了,证实了猜测.
.
.
> * 最后分析static android.os.Handler com.shimeng.qq2693533893.MyServiceOne.access$L1000018(com.shimeng.qq2693533893.MyServiceOne)
静态看一下这个代码,发现返回了一个hadler,看不出什么问题,
故技重施,hook替换掉他的返回值,查看一下调用栈,然后看看APP反映.
替换返回值发现APP没有效果,再看一下调用栈,发现是刚才调音量类100..7.run(),调用的它,
不是很懂在做什么,算是无关紧要,这里先告一段落,马上换一条线索.
.
.
.
### 3 毒如何做到的锁屏无法退出?
> * 通过之前初步的分析,
现在有两点线索可以帮助定位锁屏代码的位置.
.
.
> * 其一:
按道理来讲,
解锁通常和锁屏的代码应该组织在一起,
找到了解锁代码,就能找到锁屏代码,
解锁代码还应该和解锁按钮有关联,
.
.
所以思路为:
解锁按钮--->解锁代码--->锁屏代码
HOOK所有用户类,然后点击解锁按钮,
就可以追踪到解锁逻辑,
进而找到锁屏代码.
.
.
> * 其二:
之前初步分析发现了疑似UI工具端类Floatviewutil.
所以要在追踪hook信息时,主要观察这个类的动作.
.
.
> * 实践:
FRIDA启动,一键HOOK所有用户代码,并过滤掉之前循环调用的方法消息
.
.
> * 直接定位到解锁代码public void com.shimeng.qq2693533893.MyServiceOne$100000002.onClick(android.view.View)
JEB静态分析一下,一个简单的if else判断,
if解锁基本块里调用了 v0.this$0.util.removeView(); 以及v0.this$0.sm2();
字面上看,这个util.removeView()就是解锁逻辑了,
点进去一看,果然util就是Floatviewutil类,
现在可以99%断定这个类就是负责锁屏和解锁的代码,
还是以实践结果为标准,接下来FRIDA主动调用,
验证这个类的各个方法,证实下猜测.
.
.
> * 主动调用Floatviewutil.util(),发现锁屏界面弄掉了!
留下了一个空白页面,非常的激动,
激动过后,突然明白过来,
锁屏界面应该不是一个activity,是用其他什么东西实现的,
而撤掉锁屏以后留下的这个空白页面,才是APP的activity.
.
.
> * 为了证实这个猜想,
> adb dumpspys看一下顶层Activity情况,
事实证明的确是这样的,锁屏界面并不是一个Activity,而撤掉锁屏后的空白页是一个Activity
接着,拿着这个结论,去静态分析Floatviewutil的代码.
.
.
> * 静态分析Floatviewutil的代码
一看就这么几个方法,字面意思很好理解,
比较关键的,创建view,移除view,
JADX看一下这两个方法的用例,
调用全部集中在MyServiceOne的m2() sm() sm2()中,
也就是说,锁屏和解锁的行为都是在MyServiceOne类中这三个方法中完成.
.
.
> * 回归到现在的焦点问题,怎么实现的锁屏?以及为什么不能退出?
大致看一下Floatviewutil的导包和调用情况,
锁屏实现就是靠这几个类的方法,
1 ViewGroup // android.view.ViewGroup
2 MotionEvent // android.view.MotionEvent
3 OnTouchListener // android.view.View.OnTouchListener
4 WindowManager // android.view.WindowManager
.
.
> * 从字面上看OnTouchListener就是在监听点击事件
打算使用FRIDA,HOOK一下看看什么情况,
.
.
> * 失败了,说没有找到这个类,
反应过来,这是一个匿名类,
用android.view.View.OnTouchListener是找不到的,
那么怎么才能知道这个匿名类的名字呢?
.
.
> * 还是祭出FRIDA,写一段FIRDA脚本,
获取APP所有用户类名,同时过滤到SDK类和JAVA类,
输出保存到本地文本文件.JSON可视化展示,
在里面找Floatviewutil的内部类.
.
.
> * 顺利找到了这个匿名内部类,名字就叫com.shimeng.qq2693533893.FloatViewUtil$100000000
点击APP锁屏范围,触发了HOOK回调,成功HOOK到了点击事件,
但是点模拟器的退出没有触发HOOK回调消息,HOOK了也没什么效果.
.
.
> * 再看看代码,createfloatview代码里除了OnTouchListener,就只有一个WindowManager对象了,
wm调用了一个addview()和update()方法,
百度一下"android WindowManager 病毒锁屏",果然找到了想要的内容.
.
.
> * "通过addView方法显示一个全屏置顶的悬浮窗口,
根据设置WindowManager.LayoutParams的flags属性,
这个悬浮窗无法取消掉,导致用户的手机无法正常使用"
.
.
> * 也就是说通过flag属性,控制了这个悬浮窗的东西锁定了屏幕,
除了调用WindowManager.removeView()其他操作没有用的,
他这个逻辑是每次触摸屏幕后更新WindowManager,
保持原样,所以HOOK以后没啥效果.
至此,大致弄明白了锁机原理和为什么不能退出的,以及如何退出的问题.
.
.
.
### 4 病毒如何做到的开机自启,APP进程关闭后复活?
> * 还记得前面初步分析XML的时候一些线索,
关闭重启APP自动重启--->手机开机APP自动重启---->手机开机信号在XML中和广播有关(BOOT_COMPLETED)--->APP里有一个广播类MyBroadcast
.
.
> * 按照这个逻辑,可以断定,APP的自启是和MyBroadcast广播类有关的,
在这块的分析中,我们重点关注下MyBroadcast,
找一个突破点着手进行分析探索.
.
.
> * 先来回顾一下收音机的概念,如果对一个收音机进行抽象,会有这么2个要素,收音机频道和行为.
收音机对象 {
收音机收听的频道
收音机接收到信号后的行为
}
.
.
> * 如果要实现开机启动APP,逻辑应该是这样的,
系统发送了一个"开机广播信号"---->APP的收音机收到了这个信号---->收到信号执行一些代码,
.
.
> * 百度去找一下相关的内容,实现"收音机"这样的东西,
一般来说就是继承BroadcastReceiver,然后实现onReceive方法,
在onReceive方法里写收到信号执行xxx操作.
.
.
> * 扫一眼MyBroadcast,这个类完全符合收音机的实现,
再看onReceive()中的代码,行数写了很多,不过只有try块中的最后一行代码,
一个startService(), 另一个是starActiviry().
一个启动服务,另一个启动界面(百度查询startActivity就是启动app)
并且只有当广播信号为BOOT这个时,才运行IF块,
到这里,算是确定了开机自启的原理.
.
.
> * 验证
> 还是通过frida拿掉了APP的悬浮框,把APP关掉,
接着用adb发了一条BOOT广播,看能不能触发这个onReceive()回调方法,
发现模拟器卡住了,APP没法接受到广播.
又重启模拟器,现象是在这个APP没有成功自启,
我猜测大概由于,作者没有很上心,搞的东西兼容性不太好的原因.
.
.
> * 弄清了开机自启的原理,
还有一个不重启手机,但关闭APP后,APP会自启复活的现象.
这个原理我们还不清楚,上面的代码我们分析过了,
starActiviry()是用来启动APP的,
剩下的那个startService()是做什么的呢?
百度一下什么是android service看看有没有什么头绪.
.
.
> * 一番了解,得到结论,安卓有4中进程类型分别是
1前台进程2可视进程3服务进程4缓存/后台进程
.
.
> * 服务进程是什么呢?
大概意思就是这个东西用户看不见摸不到,
但是又作为进程,长期在后台运行着,
默默执行着一些代码.
.
.
> * 含有以startService()方法启动的service
虽然该进程用户不直接可见
但是它们一般做一些用户关注的事情
这些进程一般不会杀死,
除非系统内存不足以保持前台进程和可视进程的运行
.
.
> * 这段信息看完,会感觉安卓服务这个特性,
很适合用来完成APP自启操作,
还是做出假设,去实践验证假设
.
.
> * 猜想:
假设逻辑大概是这样的,写一个服务,
然后监控进程情况,当发现APP进程没有了,
就运行类似刚才的starActiviry()代码复活APP.
.
.
> * 验证:
因为在安卓手机设置里面能看到所有进程,
所以还是用FRIDA拿掉APP的强制悬浮,不关闭APP,切到后台,
打开设置看一下有没有服务在运行,
事实证明的确如此,有一个叫MyServiceOne服务偷偷运行.
.
.
> * 现在我们把这个MyServiceOne服务给关闭了,看APP还不会自己复活,
得到结果,假设完全是正确的,这个APP不会再复活了.
.
###5 解锁算法分析与尝试编写注册机.
> * 到了最重要的环节,
FRIDA一键HOOK脚本启动,
输入一段口令,点击解锁按钮,
触发HOOK信息,定位到算法代码位置.
.
.
> * 关键代码如下:
if(new StringBuffer().append(颜如玉QQ2693533893.getSaltMD5(MyServiceOne.颜如玉(v2.substring(0, 3)))).append(v2.substring(3, v2.length())).toString()
.equals(new StringBuffer().append("9DDEB743E935CE399F1DFAF080775366").append(v3.substring(0, 9)).toString()))
{
v0.this$0.util.removeView();
v0.this$0.sm2();
}
.
.
> * 发现这个病毒作者,使用了大段的魔改的算法,
点进去看起来很混乱,算法复杂 跟踪和hook麻烦,
不想被他吊着鼻子去分析,所以倾向于直接调它的算法算密钥.
.
.
> * 算法逻辑梳理:
SaltMD5(颜如玉(输入的前三)) + 输入的后九位 == 9DDEB743E935CE399F1DFAF080775366 + 随机数XOR加盐MD5后的前9位
.
.
> * 两部分拆开来看,
.
.
其一:
SaltMD5(颜如玉(输入的前三))== 9DDEB743E935CE399F1DFAF080775366,
这部分可以用它的算法,穷举三位数,看看那个数算出来是这个串值.
解锁口令的这一部分是固定不变的.
.
.
> 其二:
输入的后九位 ==随机数XOR后 加盐MD5后的 前9位
分析代码,看他这个随机数,大概10000个,也可以穷举出所有可能.
.
.
> 补充:
需要客户端用户联系勒索病毒作者,
告诉他随机数,才能解锁手机.
所以有一个随机数如何展示给客户端的问题,
这个APP使用了火星文的方式进行了转换,
这个转化方法也需要关注一下.
.
.
> * 算法调用实践:
这一步有三种方法可以实现,
一是,重写一个DEMO,在DEMO中动态载入这个APP的DEX,再反射调用它的算法,
二是,用FRIDA获取方法,主动调用.
三是,直接将它的算法剥离出来,在新的工程中调用.
.
.
> * 因为他这个算法导包不是特别多,
如果可以用第三种,写代码会比较方便,
所以这里我选择了第三种,
将"颜如玉QQ2693533893"这个类通过JEB粘贴出来,
然后修正部分GOTO的问题,交叉引用问题
编译成功了.
.
.
> * 运行穷举代码,尝试计算密钥.
.
.
> * 成成功得到解锁口令,解锁手机,但我还是太年轻了,还有第二层锁屏
.
.
> * 耐着性子继续分析一下二层锁屏,一看甚至还有第三层锁屏,
不过仔细一看,好在套路和第一层是一样的,
仅仅是换了中间的算法,故技重施,爆破第二层锁屏口令,
得99999个结果,顺利打开第三层锁屏.
.
.
> * 来到第三层锁屏界面,定位到代码如下
.
.
> * 可以看到,这块解锁的关键是,
成员变量"坐等前往世界的尽头的小船",
用它和输入值的SHA1进行一个比对,
这里可以使用插LOG重打包看下该变量的数值,
或者直接用FRIDA读取出他的数值,
.
.
> * 但经过FRIDA HOOK String.equals()读取发现,
这个成员居然是NULL值,
也就是说当前,情况不管输入啥,
都不可能解锁第三层锁屏.
.
.
> * 问题是勒索作者怎样实现的让它获得一个值呢,让他完成解锁呢?
通过JADX静态分析的交叉引用,找到了该成员变量的赋值方法,
通过HTTP访问一个网盘地址,然后取得一个子串,
如果子串不为空,调用发送短信的SDK,号码为这个子串内容.
.
.
> * 访问网盘后,发现网盘受到密码保护,
估计作者可以控制它的公开,
猜测是当受到受害人解锁请求,才去开放访问,放出一个号码,
给成员变量"坐等前往世界的尽头的小船"赋值,
再发放给受害人客户端解锁密码,
解锁成功后触发短信发送SDK.
.
.
> * 这里就没法证实了,但是能做个测试,
使用FRIDA主动给成员变量"坐等前往世界的尽头的小船"赋值,
然后客户端界面输入对应口令,试试能不能解锁,
结果成功了.
.
.
> * 最后,又尝试HOOK了FILE类和HTTP类以及Charles抓包等测试,
没有发现其他可以挖掘的东西,到这里本次的分析体验,也就算是告一段落了.
前两层的解锁密码:
. 本帖最后由 赤座灯里 于 2020-4-20 20:37 编辑
很多东西根本没有那么复杂。如果你有安卓开发的基础,很容易就知道楼主说的service和broadcastReceiver就在安卓的四大组件里。逆向建立在开发的基础之上,这个基础应该要有的 反应过来,这是一个匿名类,
用android.view.View.OnTouchListener是找不到的,
那么怎么才能知道这个匿名类的名字呢?objection:android hooking search classes xxx搜一下就能找到所有匿名类,为类名+$
楼主说objection用不顺手,是指不会用,还是指这个工具不行?隔壁培训班都拿这个工具当教学一部分了{:1_925:}足以说明他的强大
很多功能需要自己看这个工具的源码才会知道 本帖最后由 赤座灯里 于 2020-4-20 22:05 编辑
LoLik 发表于 2020-4-20 20:40
我有SDK开发基础,
我就不会写这个贴了,
我明天看IOS,
你能第一次分析安卓APP,做到我这个程度吗?
很好奇可以用frida写那么多东西也可以叫做第一次分析安卓app,那我以后每次写帖子都说自己是第一次分析,这样也省的别人在我帖子下有任何辩驳。 本帖最后由 LoLik 于 2020-5-19 17:13 编辑
赤座灯里 发表于 2020-4-20 20:26
objection:android hooking search classes xxx搜一下就能找到所有匿名类,为类名+$
楼主说objection用不 ...
~~~~~~~~~~~~ 本帖最后由 LoLik 于 2020-5-9 18:24 编辑
赤座灯里 发表于 2020-4-20 20:31
很多东西根本没有那么复杂。如果你有安卓开发的基础,很容易就知道楼主说的service和broadcastReceiver就在 ...
~~~~~~~~ 赤座灯里 发表于 2020-4-20 20:48
很好奇可以用frida写那么多东西也可以叫做第一次分析安卓app,那我以后每次写帖子都说自己是第一次分析 ...
虽然第一次搞APP,
但是有其他平台逆向的经验. 这么吊吗? 膜拜大佬 本帖最后由 LoLik 于 2020-5-9 18:24 编辑
15295828305 发表于 2020-4-20 21:41
这么吊吗?
是菜鸡一个,
心情很差,SORRY.