freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

APP反编译和回编译
2023-02-08 07:50:41
所属地 广东省

本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如果错漏,欢迎留言指正

APP反编译和回编译

一、APK

APK是啥

  • APK是Android PacKage的缩写,即Android安装包。

    • apk文件头部二进制的标识是PK,dex文件头部二进制的标识的dex x.x(版本号)

  • APK其本质为压缩包,通过修改后缀,即可解压查看(用7z可以直接对apk解压)(相当于把所有需要的文件进行打包压缩)

    • 安卓上可以用NP管理器或者MT管理器来查看apk文件

      • 可以把class.dex文件转化成.jar

1. assets

  • 一般存放的是不会被编译处理的文件,一般是资源性质的文件或者配置文件;

  • 存放需要打包到APK的静态文件,该目录与res目录不同之处在于,assets目录支持任意深度的子目录,我们的开发者可以根据自己的需求来任意部署文件夹的架构

  • 而且res目录下的文件会在.R文件中生成与其对应的资源ID,assets不会自动生成对应的id,访问的时候需要AssetManager类

2. META-INF目录

  • META-INF目录下存放的是签名信息,用来保证apk包的完整性和系统的安全。

  • 在编译生成一个apk包时,会对所有要打包的文件做一个校验计算,并把计算结果放在META-INF目录下。这就保证了apk包里的文件不能被随意替换。

    • 比如拿到一个apk包后,如果想要替换里面的一幅图片,一段代码,或一段版权信息,想直接解压缩、替换再重新打包,基本是不可能的。如此一来就给病毒感染和恶意修改增加了难度,有助于保护系统的安全。

  • META-INF目录中包含的文件有

    • CERT.RSACERT.DSA是开发者利用私钥对APK进行签名的签名文件

    • CERT.SFMANIFEST.MF记录了文件中文件的SHA-1哈希值。

    • 软件修改后需要将里面的证书文件删除(.RSA、.SF、.MF三个文件),否则软件无法安装。

3. res目录

  • res目录存放资源文件。包括图片,字符串等等。

  • res是resource的缩写,这个目录存放的东西是资源文件,存放这个文件夹下的所有文件都会映射到Android工程中的.R文件中,生成对应的资源ID,访问的时候直接使用资源ID,即R.ID.FILENAME,res文件夹下可以包含多个文件夹;

    • anim是存放动画文件的;

    • drawable目录存放图形资源;

    • layout目录存放布局文件;

    • values目录存放一些特征值;

    • colors.xml存放color的颜色值等等

4. lib目录(重点)

  • lib目录下的子目录armeabi存放的是一些so文件。

  • 该目录用来存放应用程序所依赖的native库文件,native库一般是用C/C++进行编写的,这里的lib库可能包含4种不同类型

    • 根据CPU型号的不同,我们大体可以分为ARMARM-v7aMIPSX86,分别对应着ARM架构,ARM-V7架构,MIPS架构和X86架构,这些so库在apk包中构成如图:

    • 其中,不同的CPU架构对应着不同的目录,每个目录中可以存放非常多的对应版本的so库,而且这个目录的结构固定,用户只能按照这个目录来存放自己的so库。

    • 目前市场上使用的移动终端大多是基于ARM或者ARM-v7a架构的。

    • 从厂家上来分是有三种,arm,x86,MIPS,arm系列是绝大多数手机上使用的,x86主要是运用在平板和模拟器上

5. AndroidManifest.xml(重点)

  • 这是Android应用程序的配置文件,用于描述整个Android应用的元数据、权限和配置信息的必需文件。简单的说,这相对于Android应用向Android系统的自我介绍配置文件,Android系统可以根据Androidmanifest.xml文件来完整的了解这个APK应用程序的咨询。

  • 不难想到,每个Android应用程序都必须包含一个Androidmanifest.xml文件,并且它的名字是固定的,是禁止修改的。

  • 该文件是每个应用都必须定义和包含的,它描述了应用的名字、版本、权限、引用的库文件等等信息

    • 如要把apk上传到Google Market上,也要对这个xml做一些配置。

    • 在apk中的AndroidManifest.xml是经过压缩的,可以通过AXMLPrinter2工具解开,具体命令为: java -jar AXMLPrinter2.jar AndroidManifest.xml

6. classes.dex文件(重点)

  • java->dex->dvk

    • 通过ADT,经过复杂的编译,可以把java源代码转换为dex文件。

    • 在Android系统中,dex文件是可以直接在Dalvik虚拟机中加载运行的文件。

  • 那么这个文件的格式是什么样的呢?为什么Android不直接使用class文件,而采用这个不一样文件呢?

    • 其实它是针对嵌入式系统优化的结果,Dalvik虚拟机的指令码并不是标准的Java虚拟机指令码,而是使用了自己独有的一套指令集。如果有自己的编译系统,可以不生成class文件,直接生成.dex文件。

    • dex文件中共用了很多类名称、常量字符串,使它的体积比较小,运行效率也比较高。但归根到底,Dalvik还是基于寄存器的虚拟机的一个实现。

  • DEX是Android系统的可执行文件,包括应用程序的全部操作指令,以及运行时数据。在程序编译过程中,java源文件先被编译成class文件,然后通过dx工具将多个class文件整合为一个dex文件,这样的文件结构使得各个类能够共享数据

  • dex数量不止一个,而且都比较小,很可能是被加固了(不将重要的代码放到dex,dex只是承担初始化的功能(只有需要加载什么库,需要释放什么文件的代码逻辑),把java文件写到so库里面,在app runtime的时候,把lib目录下so文件的java代码释放到内存中去)

7. resources.arsc

  • 编译后的二进制资源文件

  • 用来记录资源文件和资源ID之间的映射关系,用来根据资源ID寻找资源。

  • Android的开发是分模块的,res目录专门用来存放资源文件,当在代码中需要调用资源文件时,只需要调用方法findviewbyid()就可以得到资源文件,每当在res文件夹下放一个文件,aapt就会自动生成对应的ID保存在.R文件,我们调用这个ID就可以,但是只有这个ID还不够,.R文件只是保证编译程序不报错,实际上在程序运行时,系统要根据ID去寻找对应的资源路径,而resources.arsc文件就是用来记录这些ID资源文件位置对应关系的文件。

8. kotlin

  • Kotlin是一种与Java兼容的编程语言,它在Java的基础上添加了一些新的语法和特性,并且支持在Java平台上运行。Kotlin和Java代码可以无缝地互相调用,因此Java开发人员可以很容易地学习Kotlin并将其应用到现有的Java项目中。同时,Kotlin的语法简洁,功能强大,可以提高开发人员的生产力,并且已经成为Android开发的首选语言。

APK打包过程

  • 过程

    • .java->.class->.dex--(+resources.arsc)-->.apk

    • .c->.so->arm

  • Aapt: Android Asset Packaging Tool,在SDK的build-tools出录下。该工具可以查看,创建,更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件。

  • AIDL: Android Interface Definition Language,即 Android接口定义语言。

    • Android系统中的进程之间不能共享内存,因此,需要提供一些可以跨进程访问的服务机制在不同迸程之间进行数据通信--AIDL。

    • AIDL主具负责将AIDL接口定义文件转化为Java代码供程序调用。

  • jarsigner:JDK提供的签名工具可对apk进行签名。

    • jarsigner -verbose -keystore D: ltestlmykeystore -signedjar D:ltestitemplout.apk D:ltestlinput.apk alias

  • Zipalign:提高了优化后的Applications Android系统的交互效率,从而可以使整个系统的运行速度有了较大的提升。

    • 优化的最根本目的是帮助操作系统更高效率的根据请求索引资源

    • resource-handling code统一将Data structure alignment(数据结构对齐标准: DSA)限定为4-byte boundaries。

    • zipalign使用了4字节的边界对齐方式映射内存,空间换时间以提高执行效率,如果不采取对齐的标准,处理器无法准确和快速的在内存地址中定位相关资源。

  • Keystore:密钥库

二、APP安装、卸载流程

app的安装过程

签名校验

在apk下载完成后,系统会对安装包进行签名验证,以确保应用程序的安全性。主要校验的内容包括:如果任何一个检查失败,则系统将拒绝安装该应用程序。

  • 签名证书的链是否有效:证书链是一组证书,它们被用来证明证书的签发者是受信任的。

    • a. 首先,系统会检查安装包中的证书是否是由受信任的发布商签发的。这可以通过检查证书的颁发机构(CA)是否在系统内置的受信任证书颁发机构列表中来完成。

    • b. 如果证书的颁发机构是受信任的,系统会检查证书是否被在线证书验证(OCSP)或在线证书状态协议(OCSP)确认为有效。这是为了确保证书在签发时是有效的,并且在安装应用程序时仍然是有效的。

    • c. 如果证书是有效的,系统会检查证书链中的下一个证书是否是由上一个证书签发的。这会一直检查到根证书。

    • d. 系统会最后检查根证书是否是受信任的,这可以通过检查根证书是否在系统内置的受信任证书颁发机构列表中来完成。

  • 签名是否被篡改:系统会使用预定义的算法(算法可能包括 SHA-256 等)验证签名的完整性。

  • 签名是否过期

  • 应用程序的签名是否与先前安装的应用程序的签名相匹配

文件操作

  1. 将apk文件拷贝到指定目录下. 系统应用是在/system/app, 第三方应用在/data/app下(存储着已安装的APK文件,包括代码文件、资源文件、库文件等)。

  2. 解压apk, 拷贝文件. 创建UID, 创建/data/data/${package_name}目录, 设置权限. 这个就是应用的数据目录(apk安装后运行释放的缓存文件存放在这里,用户密码等信息存放在这里)。

  • app在运行期间有可能会使用到外部存储目录/storage/emulated/0/Android/data/${package_name},该目录只有在app运行时调用相关函数时才创建,app安装后不会创建的。

  1. 从apk中提取dex, 放到/data/dalvik-cache目录.

  2. 解析AndroidManifest.xml文件, 提取信息添加到PMS中, 更新PMS中相应的数据结构. 具体是, 将提取到的包信息更新到/data/system/packages.list/data/system/packages.xml

  3. 发送广播Intent.ACTION_PACKAGE_ADDED或者Intent.ACTION_PACKAGE_REPLACED. 从名字可以判断分别对应全新安装和覆盖安装.

app卸载流程

卸载是安装的逆过程, 删除在创建过程中三个路径下产生的文件夹,以及有可能后面创建的外部存储目录/storage/emulated/0/Android/data/${package_name}

  1. 第一步删除/data/data下面的数据目录,并从PMS的内部数据结构上清除当前卸载的package信息

  2. 第二步就删除/data/app/${package_name}文件,删除存在/data/dalvik-cache文件

adb、adb shell常用命令

adb常用命令

root uid 0 gid 0 
system uid 1000 gid 1000
shell  uid 2000 gid 2000
app  uid >10000 gid >10000

adb devices  #查看当前设备是否链接
adb -s #指定设备名称 后面跟相应的命令 比如:adb -s emulator-5554 shell
adb shell #进入手机管理 
adb install apkpath  -r -f -s  #比如:adb install -r ..\magisk.apk
adb uninstall apkpackname -k   #比如:adb uninstall com.topjohnwu.magisk
adb push #电脑端文件路径  手机端文件路径
adb pull #手机端文件路径   电脑端文件路径
adb reboot  #重启手机
adb reboot recovery #重启恢复模式
adb reboot bootloader #重启引导模式  
adb shell monkey -v -p com.tencent.mobileqq 500
adb shell getprop  #获取手机参数

adb root  #重新启动 adbd 守护进程并在设备上提供 root 权限。这样可以在设备上运行需要 root 权限的 adb 命令,如修改系统级文件和设置。
adb remount #将 /system 分区挂载为可写,允许你使用root权限的 adb shell 进行修改系统文件。
adb shell "su -c xxx" #通过 adb shell 连接到设备并使用 su 命令在设备上运行一条命令 "xxx" 。 "su" 是 Linux 中 superuser(超级用户)的缩写,它允许用户在其他用户权限基础上执行特权操作。 使用 “-c” 参数允许在切换到超级用户权限后立即执行一个命令。
adb devices # 查看手机设备
adb shell getprop ro.product.model # 查看设备型号
adb shell dumpsys battery # 查看电池信息
adb shell settings get secure android_id # 查看设备ID
adb shell dumpsys iphonesubinfo # 查看设备IMEI
adb shell getprop ro.build.version.release # 查看Android版本
adb shell ifconfig  # 查看手机网络信息
adb logcat   # 查看设备日志
adb reboot # 重启手机设备
adb install /path/demo.apk # 安装一个apk
adb uninstall <package> # 卸载一个apk
adb shell ps  # 查看系统运行进程
adb shell ls /path/ # 查看系统磁盘情况
adb shell screencap -p /sdcard/aa.png  # 手机设备截屏
adb pull /sdcard/aa.png ./ # 手机文件下载到电脑
adb push aa.png /data/local/ # 电脑文件上传到手机
adb shell screenrecord /sdcard/ab.mp4 # 手机设备录像
adb shell wm size # 手机屏幕分辨率
adb shell wm density # 手机屏幕密度
adb shell input tap xvalue yvalue # 手机屏幕点击
adb shell input swipe 1000 1500 200 200  # 手机屏幕滑动
adb shell input swipe 1000 1500 0 0 1000 # 手机屏幕带时间滑动
adb shell input text xxxxx # 手机文本输入
adb shell input keyevent xx # 手机键盘事件

adb shell常用命令

#1、安装、卸载应用 
pm list packages  # 列出设备中已经安装的所有应用包(包括系统应用和用户应用)
pm path packname # 查看apk安装的路径
pm install -r -f -s apppath # 安装apk,r 强制安装,f 安装手机内存 s 安装sdcard
pm uninstall -k packname # 卸载应用 -k 保留应用数据 /data/data/packname下的数据 或者 /sdcard/Android/data/packnmae
rm -r /data/data/packname # 彻底删除残留文件  

pm enable packname # 设置应用为不可用,或者组件不可用 组件跟类的完整路径  
pm disable packname # 设置应用可用 
pm setInstallLocation 0 1 2  # 设置应用安装的默认目录 0:auto 1:手机内存 2:sdcard
pm getInstallLocation  # 查看当前设置
pm clear packname # 清除掉应用缓存数据  

#2、结束系统进程 
kill pid

#3、屏幕解锁
rm /data/system/gesture.key
rm /data/syste/locksettings.*

#4、应用及应用数据的备份,移动应用到系统应用。
busybox cp -r -f -p -P source/*  des/

#5、查看短信,联系人数据库 
cat /data/data/com.android.providers.contacts/databases/contacts2.db > /data/lcoal/tmp/1.db 
adb pull /data/lcoal/tmp/1.db pc_path

linux常用指令:(权限)

rm  #移除文件 或 文件夹 rm /data/local/tmp/1.apk
cd  #进入目录 cd /data/local/tmp
cat #查看文件内容 cat /proc/cpuinfo ;复制文件  cat /data/local/tmp/1.apk > /sdcard/1.apk
cp 	#复制文件  cp /data/local/tmp/1.apk /sdcard/1.apk
mv 	#移动文件,重命名文件  mv /data/local/tmp/1.apk /data/local/tmp/2.apk
chmod 	#为文件或目录赋权限  chmod 777 /data/local/tmp/1.apk
chown 	#为文件赋所属者 chown 0.0 /data/local/tmp/1.apk
echo 	#写入文件 如果文件不存在创建并写入 echo '111' > /sdcard/1111.txt
md5sum 	#获取文件md5码 md5sum /system/app/1.apk
halt 	#关机
reboot 	#重启手机
id    	#获取当前用户信息
touch  	#创建一个空文件 touch /data/local/tmp/1.txt
sleep  	#睡眠多少秒 sleep 10
mkdir  	#创建文件夹 mkdir /sdcard/nihao
ps     	#查看当前系统所有进程
ls  	#列出当前文件夹下的文件

mount  	#挂载分区 mount -o remount rw /system
df  	#查看磁盘空间 df /system

实战:刷抖音

#项目/短视频/抖音 #技术栈/python爬虫/用adb模拟点击来控制手机刷抖音 原理::python脚本实现:

import subprocess

package_name = "com.ss.android.ugc.aweme"

# 打开抖音
subprocess.getoutput(f"adb shell am start -n {package_name}/.splash.SplashActivity")

# 刷抖音(在屏幕滑动的命令)
subprocess.getoutput(f"adb shell input swipe 311 952 622 444 400")

# 点赞(在屏幕点击的命令) 开发者选项-输入-指针位置  
subprocess.getoutput(f"adb shell input tap 660 790")

三、 AndroidKiller配置

  • 虽然APK属于压缩文件,但是APK包中的AndroidManifest.xml等文件无法通过直接解压的方式获取内容,需要通过Apktool工具进行反编译。

  • Android Killer 是一款可视化的安卓应用逆向工具,集Apk反编译、Apk打包、Apk签名,编码互转,ADB通信(应用安装-卸载-运行-设备文件管理)等特色功能于一 身,支持logcat日志输出,语法高亮,基于关键字(支持单行代码或多行代码段)项目内搜索,可自定义外部工具;吸收融汇多种工具功能与特点,打造一站 式逆向工具操作体验,大大简化了用户在安卓应用/游戏修改过程中的各类繁琐工作。

配置

  • 先确定安装好JDK

  • 配置Androidkiller

    • 下载地址:https://www.52pojie.cn/thread-319641-1-1.html

  • 更新Apktool

    • 下载地址:https://ibotpeaches.github.io/Apktool/

插件

更新smali插桩插件

  • 在逆向分析APP时,有时需要插入smali代码,打印日志信息、记录方法调用流程,或者是添加弹窗、加载so库等,这些代码都是固定的,再将其封装成一个个插件来使用。

  • 打开AndroidKiller安装包所在位置,找到cfgs文件夹下的injectcode文件夹,将里面的插件删除,替换成新的injectcode。

四、实战

实战1:篡改APK名称、图标

  • 真机环境:Redmi 9A

  • 样本:土豆视频.apk

  • 工具:AndroidKiller
    #项目/短视频/抖音 #技术栈/app逆向/逆功能/篡改APK名称、图标 原理::修改app名称关键字和替换图标资源文件,再用AndroidKiller重新打包、签名生成修改后的apk。

  1. 搜索APK名称并替换

  • 创建工程
    #坑/逆向/app逆向/AndroidKiller/错误 AndriodKiller 在第一次导入项目时,会一直卡在反编译成功......::需要重启AndriodKiller ,在工程中打开刚刚的项目。

  • 替换名称

  1. 搜索APK图标并替换

  • 2.1 确定图标名字

  • 2.2 根据图标名字找到图标所在路径

  • 2.3 替换图标文件

  1. 重新编译

  • 回编译

  • 安装apk


    • #坑/逆向/app逆向/adb adb push权限问题::在Android中,使用adb push命令推送文件到data目录一般需要root权限,但是/data/local/tmp/这个目录并不需要root权限,所以一般把文件push到测试机的/data/local/tmp目录下

  • 改英文名,用pm命令安装也不行


    • #坑/逆向/app逆向/AndroidKiller/签名 AndroidKiller回编译出来的apk无法安装::编译的时候没有对apk签名,选择AndroidKiller


#坑/逆向/app逆向/AndroidKiller/签名 签名后还是无法安装重新打包的apk::需要先把原版卸载掉,才能安装修改后的apk,因为原版的apk签名无法拿到,原版的apk的签名信息和AndroidKiller回编译出来的apk的签名信息不一样,无法共存。用AndroidKiller回编译的时候,签出来的签名是一样的,所以修改后的apk可以共存,从而实现多开。

实战2:修改包名实现分身

  • 真机环境:Redmi 9A

  • 样本:土豆视频.apk

  • 工具:AndroidKiller
    #项目/短视频/抖音 #技术栈/app逆向/逆功能/修改包名实现分身 原理::一类是修改包名,让系统识别为“两个应用程序”。另一类虚拟机技术,比如VirtualApp。目的都是为了防止出现数据冲突或混淆。

  • 方式1:换包名,让手机系统认为这是2个APP,这样的话就能生成2个数据存储路径,此时的多开就等于你打开了两个互不干扰的APP;

  • 条件1:同一个程序修改成不同包名,安装包管理器检测到不同包名,从而判断是不同的app,从而实现共存。

  • 条件2:用AndroidKiller回编译的时候,签出来的签名是一样的,所以修改后的apk可以共存,从而实现多开。

  • 条件3:修改后的app和系统中已经安装的apk的provider中的authorities相同,需要修改。

  1. 修改包名(改成抖音的包名),重新编译

    #坑/逆向/app逆向/AndroidKiller/回编译 adb install的时候,提示INSTALL_FAILED_VERSION_DOWNGRADE::意思是当前设备已安装的版本高于即将进行覆盖安装的版本,所以无法向下安装,尝试修改版本号还是无法安装。其实是因为手机里面已经安装了抖音,修改后的apk的包名是抖音的包名,冲突了。卸载原版抖音即。

#项目/短视频/抖音 #技术栈/app逆向/逆功能/去除软件更新

  • 解决办法1:修改版本号:修改apktool.yml,再重新回编译,还是无法安装。

  • 解决办法2:adb报错信息可能不准确,push到手机上安装,看具体是什么报错信息,发现还是一样的报错,其实是因为手机里面已经安装了抖音,修改后的apk的包名是抖音的包名,冲突了,卸载原版抖音即。


    • #坑/逆向/app逆向/adb 中文名字的文件push到手机会出问题::需要改成英文名字


  • #坑/逆向/app逆向/AndroidKiller/回编译 adb install的时候,提示INSTALL_FAILED_CONFLICTING_PROVIDER::因为系统中已经安装的apk的provider中的authorities相同了,导致在安装到手机时,安装包管理器检测到相同的provider,报错导致的。

  1. 修改内容提供者,重新编译

  • 只改一个可不行,需要全部改

#坑/逆向/app逆向/adb 误删了/data/local/tmp文件夹,新建了一个文件夹,adb push到这个文件夹提示权限不够::新建的文件夹权限发生变化了,参考nexus 5这个目录的权限,改成一样,记得要执行chcon -R u:object_r:shell_data_file:s0 /data/local/tmp
-

  • 解决办法1:新建的文件夹权限发生变化了,参考nexus 5这个目录的权限,改成一样的,有效。

    • 是需要执行chcon -R u:object_r:shell_data_file:s0 /data/local/tmp

      • u:是user之意,它代表创建这个文件的SELinux user。

      • object_r:文件是死的东西,它没法扮演角色,所以在SELinux中,死的东西都用object_r来表示它的role。

      • cgroup:死的东西的Type,和进程的Domain其实是一个意思。它表示acct目录对应的Type是cgroup。

      • s0:MLS的级别。

    • 解决办法2: adb root也遇到了权限问题。手机安装adbd-insecure.apk也没有效果

  • 解决办法3:关闭magisk的hide模式,然后重启手机。但23以后的版本去掉模块在线安装MagiskHide,红米9A是联发科的芯片,只能安装magisk最新版本,这个方法行不通了。替代解决办法:修改magisk的配置文件ro.debuggable为1,还是没有效果

C:\Users\cisco>adb shell
dandelion:/ $ su
dandelion:/ # magisk resetprop ro.debuggable 1
dandelion:/ # stop;start;  #执行完会自动重启
dandelion:/ #

实战3:修改资源去开屏广告

  • 真机环境:Redmi 9A

  • 样本:火柴人.apk

  • 工具:AndroidKiller
    #项目/游戏/单机/火柴人 #技术栈/app逆向/逆功能/去广告 原理::修改入口界面,直接跳转到目标界面,从而跳过广告(同样可以插入自己的广告)

  1. 获取当前app的目标界面

  2. 修改入口界面,直接跳转到目标界面,跳过广告(同样可以插入自己的广告)

  3. 回编译

五、常见错误

反编译和回编译错误

  1. 反编译错误-dex

  • 修复方法:把报错的dex移出来,反编译就没问题了,回编译的时候,把dex文件放回去

  1. 回编译错误-png

  • 修复方法:

    • 方法1:用sdk/tools/draw9patch.bat去修复资源文件

    • 方法2:不反编译资源,加-r参数,只反编译dex文件

# android安全工具 # 逆向分析 # android逆向
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录