用例风险:源代码未做混淆使攻击者很轻易反编译出源代码导致代码泄漏风险。
执行步骤:使用反编译工具打开应用,如发现代码内未经过混淆,就说明存在应用可进行反编译,记录漏洞,停止测试。
预期结果:安装包中核心模块与敏感数据经过加密或者混淆
整改建议:建议使用Proguard等工具对源码进行进一步混淆,避免造成源码泄漏。
用例风险: Android签名机制是一种有效的身份标识,为了保证应用不被恶意修改后重新发布,需要检查应用签名是否有保护机制。
执行步骤
.apk文件后,删除META-INF/目录下的xx.RSA和xxx.SF文件apk文件进行重新签名,首先生成自己的私钥`keytool -genkey -v -keystore [keystore路径] -alias [密钥别名] -keyalg RSA -keysize 2048 -validity [有效天数]`
apk进行二次签名,签名命令格式如下jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore [keystore名称] [apk文件] [密钥别名]
-sigalg:签名算法名称-digestalg:信息摘要算法-keystore:签名文件执行签名命令
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore android.keystore kaoyan.apk android.keystore
预期结果: 更换签名后,触发应用防御机制,应用无法启动或提示
整改建议: 内部代码实现apk二次打包鉴别机制,在程序运行时校验apk签名是否由官方私钥签名而来。
用例风险:应用权限分配不合理,可能导致用户隐私数据泄露。
执行步骤
AndoridManifest.xml文件,将应用权限和业务功能需要权限做对比,检查申请应用权限是否大于业务需要权限,有即存在安全隐患。预期结果:应用申请合理的系统权限
整改建议:为应用分配合理的系统权限
用例风险:当allowBackup标志值为true时,即可通过adb backup和adb restore来备份和恢复应用程序数据,导致应用数据泄露。
执行步骤
AndroidManifest.xml文件;AndoridManifest.xml文件中的配置是否为:android:allowBackup="true",即为allowBackup开启,记录漏洞,停止测试。预期结果:AllowBackup关闭
整改建议:在AndroidManifest.xml文件设置allowBackup属性值为False。
备注:allowBackup属性未配置时默认为true
用例风险:当debuggable标志值为true时,即表示是App可调试的,存在安全泄露风险。
执行步骤
AndroidManifest.xml文件;AndoridManifest.xml文件中的配置是否为:android: debuggable="true",即为debuggable开启。预期结果 debuggable关闭
整改建议 在AndroidManifest.xml文件设置debuggable属性值,其默认值为false
备注 Debuggable属性未配置时默认为false
用例风险
使用弱加密算法会大大增加黑客攻击的概率,黑客可能会破解隐私数据、猜解密钥、中间人攻击等,造成隐私信息的泄漏,甚至造成财产损失。容易被破解的加密算法被称为弱加密算法,例如可以使用穷举法在有限的时间内破解DES算法。
执行步骤
DES弱加密算法,弱加密代码样例:SecretKeySpec key = new SecretKeySpec(rawKeyData, "DES"); //指定加密方式
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); //设置加密填充模式
cipher.init(Cipher.DECRYPT_MODE, key);
RSA中加密不使用Padding:RSA加密常用的填充模式有三种:RSA_PKCS1_PADDINGRSA_PKCS1_OAEP_PADDINGRSA_NO_PADDING使用RSA公钥时通常会绑定一个Padding,原因是为了防止对RSA算法的攻击。风险代码样例如下:扩展资料:RSA填充模式
Cipher rsa = null;
try {
rsa = javax.crypto.Cipher.getInstance("RSA/NONE/NoPadding");
}
catch (java.security.NoSuchAlgorithmException e) {
}
catch (javax.crypto.NoSuchPaddingException e) {
}
SecretKeySpec key = new SecretKeySpec(rawKeyData, "RSA");
Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding"); //选择了NoPadding加密模式
cipher.init(Cipher.DECRYPT_MODE, key);
512bits常用的密钥长度有
1024bits,2048bits等,使用RSA加密时,建议密钥长度大于1024bit
public static KeyPair getRSAKey() throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512); //密码长度设置为512bits
KeyPair key = keyGen.generateKeyPair();
return key;
}
AES加密使用了ECB模式。
ECB模式是最简单的模式,在其中明文和密文是一一对应的,相同的明文会被加密为相同的密文,这样可以通过观察密文得到明文中重复的组合,并以此为线索来破解密码。
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key);
预期结果:系统未使用包含风险的加密算法
整改建议
DES算法RSA算法加密时不使用NoPaddingECB模式RSA加密时,建议密钥长度大于1024bit用例风险:如果在传输过程中未对敏感数据进行加密传输,存在被恶意攻击者通过网络窃听等手段获取网络数据包中的敏感数据的威胁。
执行步骤
Charles),查看数据包中是否明文包含:用户名密码、IP地址、SIM序列号,或其他用户、系统等敏感信息。预期结果:传输的数据包中未包含敏感信息
整改建议:确保包含重要敏感信息的数据均已加密的形式或者以https形式传输。
用例风险
在密码学和计算机安全领域中,中间人攻击(Man-in-the-middle attack,缩写:MITM)是指攻击者与通讯的两端分别建立独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。在中间人攻击中,攻击者可以拦截通讯双方的通话并插入新的内容。
执行步骤
public class SSLSocketFactory_poc extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public SSLSocketFactory_poc(KeyManager[] keys,KeyStore truststore ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
if(true)
{}
try {
throw new CertificateException("illegal DN, reject the connection");
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(keys, new TrustManager[] { tm }, null);
}
...
}s(), "js2java");
HostnameVerifier hostnameVerifier = new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session)
{
return true; #未实现任何判断语句,直接返回true
};
};
SSLSocketFactory sf = new SSLSocketFactory_poc(null,trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); #允许所有域名
sf.setHostnameVerifier(new AllowAllHostnameVerifier());
预期结果:在使用证书的时候进行相关校验
整改建议:建议开发者对SSL证书进行强校验,包括证书是否合法、主机域名是否合法和证书的有效期。
安全风险
如果日志中包含用户信息、业务信息,攻击者可以通过抓取日志,搜集整理大量的有用信息。比如有时研发开发时为了调试方便会添加一些debug日志,如果在打正式发布包时不将这些log去掉那么很容易泄漏敏感信息。
执行步骤
adb logcat | find "com.youku.phone"(包名)"捕获输出的日志adb logcat | find "com.youku.phone" >C:\Users\Shuqing\Desktop\log.txt将日志保存到指定文件。预期结果:日志中不包含敏感信息
整改建议:为了防止信息泄漏,不要在日志中输出敏感数据
安全风险:敏感数据明文存储在手机上增加了信息泄露的风险
执行步骤
apk安装文件查找是否明文存储用户信息、业务数据、服务信息或其他敏感信息。预期结果:文件中未存放用户或系统敏感信息
整改建议:如果一定要在客户端存放系统敏感数据,建议加密后再存储。
安全风险:应用文件被分配了不合理的权限,导致其他应用可以读取和获取文件内容,增加了内容泄露的风险。
执行步骤
adb shell连接设备cd /data/data/xxxx(包名)ls -al,查看当前目录下所有文件权限。r代表只读,w代表写,x代表可执行,d表示是一个目录。预期结果:
drwxrwx--x,允许多一个执行位x-rw-rw----),即除应用自己以外任何人无法读写;整改建议
MODE_WORLD_WRITEABLE(可写)和MODE_WORLD_READABLE(可读)模式创建进程间通信的文件,如果需要与其他进程应用进行数据共享,请考虑使用content provider;MODE_PRIVATE模式创建内部存储文件,默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容。安全风险:敏感数据直接存储在sqlite数据库导致信息泄露的风险。
执行步骤
/data/data/[package name]/databases/,查找sqlite数据库文件并复制到PC端DB.Browser.for.SQLite打开sqlite文件。预期结果:客户端数据库文件中不存在敏感数据。
整改建议:如果一定要在客户端存放系统或系统敏感数据,建议加密后再存储。
安全风险:获取或者篡改app中存储的敏感信息,如手机号、账号、密码等,在业务运行操作时无法保证数据安全。
执行步骤
drozer连接测试设备run app.provider.info -a appnamerun scanner.provider.finduris -a appnamerun scanner.provider.injection -a appnamerun scanner.provider.traversal -a appname预期结果 无法获取到相关数据信息。
整改建议 使用参数化查询防御SQL注入,限制Provider组件的权限,取消不必要的Provider组件接口。
WebView是Android系统提供能显示Web页面的系统控件,例如混合类型的App中H5界面就是使用了WebView组件。
安全风险:Webview中接口addJavascriptInterface可通过webview对象向页面javascript导出java本地接口,可能导致任意命令执行。
执行步骤
settings.setJavaScriptEnabled(true); //设置开启javascript
settings.setJavaScriptCanOpenWindowsAutomatically(true); //设置允许js弹出alert对话框
mWebView.addJavascriptInterface(new JSInvokeClass(), "js2java"); //注入javascript映射
Web组件远程代码执行的风险。预期结果:系统使用安全接口调用webview
整改建议
addJavascriptInterface导出Java类及方法,并加强访问的url的域控制;安全风险
WebView的过程中开启了setSavePassword保存密码,当用户在WebView中输入的用户名和密码,则会被明文保存到应用。databases/webview.db中。root就可以获取明文保存的密码,造成用户的个人敏感数据泄露。执行步骤
mWebView.getSettings().setSavePassword(true); //设置开启保存密码
mWebView.loadUrl("http://www.example.com");
预期结果:在调用setSavePassword时设定setSavePassword(false)
整改建议:使用WebView.getSettings().setSavePassword(false)来禁止保存密码
安全风险
Android WebView组件加载网页发生证书认证错误时,会调用WebViewClient类的onReceivedSslError方法,如果该方法实现调用了handler.proceed()来忽略该证书错误,则会受到中间人攻击的威胁,可能导致隐私泄露。
执行步骤
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new JsBridge(mContext), JS_OBJECT);
mWebView.loadUrl("http://www.example.org/tests/addjsif/");
mWebView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed(); //忽略证书错误
}
});
onReceivedSslError处理时没有进行cancel,还是使用了proceed(),忽略掉了发生的SSL异常。则说明风险存在。记录漏洞,停止测试。预期结果:正确的处理SSL错误,避免证书错误的风险。
整改建议:当发生证书认证错误时,采用默认的处理方法handler.cancel(),停止加载问题页面。
安全风险:攻击者可以发送恶意的消息,控制Receiver执行恶意动作或者造成信息泄露。
执行步骤
Drozer扫描暴露的broadcast组件run app.broadcast.info -a xxxx -i和相关action信息receiver组件发送空值,run app.broadcast.send --action xxx,查看是否能够造成应用程序崩溃,形成拒绝服务。预期结果 系统为Broadcast组件分配了适当权限。
整改建议 AndroidManifest.xml文件的各receiver标签中,设置android:exported="false";BroadcastReceiver代码中增加消息异常处理机制。
安全风险
应用程序在广播包含敏感信息的消息时,由于未指定具体的接收组件,攻击者可能仿冒receiver来接受来自应用程序的消息,从而窃取敏感信息。
执行步骤
apk获取源代码,在源代码中搜索定位发送广播消息的位置,例如搜索sendBroadcast()。Intent时,是否显式指定了接收该广播的组件名称,以及要发送的广播中是否包含敏感信息。示例代码如下:public class ServerService extends Service {
// ...
private void d() {
// ...
Intent v1 = new Intent();//未显式指定接收组建名称
v1.setAction("com.sample.action.server_running");
v1.putExtra("local_ip", v0.h);
v1.putExtra("port", v0.i);
v1.putExtra("code", v0.g);
v1.putExtra("connected", v0.s);
v1.putExtra("pwd_predefined", v0.r);
if (!TextUtils.isEmpty(v0.t)) {
v1.putExtra("connected_usr", v0.t);
}
}
this.sendBroadcast(v1);
}
3、 如果应用程序未指定接收组件并且广播中包含类似password等信息,则存在信息泄露的风险。
预期结果 :显式的制定接收组建、并且分配了合适的权限
整改建议
action与包名intent.setAction("allow.package.recv.action"); //设置指定的action
intent.setPackage("allow.package.recv.packagename"); //设置指定的包名
sendBroadcast(intent); //发送广播
如果指定特殊的receiver接收可以指定component:
intent.setComponent(newComponentName("allow.package.recv.packagename", "allow.package.recv.classname"));
sendBroadcast(intent);
LocalBroadcastManager,使广播的Intent仅在该进程内部,而不会让同一APP的其他进程或者其他APP接收到。API:sendStickyBroadcastsendStickyBroadcastAsUsersendStickyOrderedBroadcastsendStickyOrderedBroadcastAsUserAndroid SDK文档中也明确说明了存在安全问题, 如果必须使用,广播中不应包含敏感信息,另外需要设置接收权限:
//设置广播权限
sendBroadcast(intent,"broadcast.permission");
//向特定用户发送广播
this.sendBroadcastAsUser(i, null,"broadcast.permission");
this.sendOrderedBroadcastAsUser(i, null, "broadcast.permission", null, null, 0, null, null);
this.sendOrderedBroadcast(i, "broadcast.permission");
this.sendOrderedBroadcast(i, "broadcast.permission", null, null, 0, null, null);
同时在AndroidManifest.xml中如下配置:
<uses-permission android:name="broadcast.permission" />
<permission android:name="broadcast.permission" android:protectionLevel="signature" />
android:protectionLevel为signature,防止其他APP能够非常容易的窃取权限。
安全风险:攻击者可以发送恶意的消息,控制Receiver执行恶意动作或者造成信息泄露。
执行步骤
registerReceiver(),查找动态广播接收器。也可以使用命令:run app.broadcast.info -a com.xxxx -i
android:exported="true"权限的组件。receiver,找到应用程序定义的在接收到消息时的各项参数以及各种处理逻辑。Broadcast组件,是否越权进行操作。预期结果:合理分配Broadcast组件权限
整改建议:
AndroidManifest.xml文件的receiver标签中设android:exported="false"。AndroidManifest.xml中,申明一个私有权限,级别为signature;exported='false',并且不配置intent-filter,对接收来的广播进行验证;安全风险:攻击者可以绕过认证阶段,直接调用后续activity组件。
执行步骤
AndroidManifest.xml中activity组件(关注配置了intent-filter的及未设置export=“false”的组件)。Drozer扫描暴露的Activity:run app.activity.info -a packagenamerun app.activity.start --component 包名 Activity名Activity能否被正常显示,如果可以则会形成越权、信息泄露等风险。预期结果 设定正确的activity权限,避免造成越权或信息泄露。
整改建议
Activity不应配置intent-filter,如果配置了intent-filter需设置exported属性为false;intent以及其携带的信息,当Activity返回数据时候需注意目标Activity是否有泄露信息的风险;安全风险
APP创建Intent传递数据到其他Activity,如果创建Activity时通过addFlags设置了FLAG_ACTIVITY_NEW_TASK,Activity会在另一个Task中打开,
这种情况很可能被其他的Activity劫持读取到Intent内容,跨Task的Activity通过Intent传递敏感信息是不安全的,会导致intent中的敏感数据泄露。
执行步骤
FLAG_ACTIVITY_NEW_TASK标签):Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //设置了FLAG_ACTIVITY_NEW_TASK
startActivity(intent);
Intent i = new Intent("com.xiaomi.mipush.RECEIVE_MESSAGE");
startActivity(i);
FLAG_ACTIVITY_NEW_TASK标签就存在该风险,记录漏洞,停止测试预期结果:不包含FLAG_ACTIVITY_NEW_TASK标志的Intent启动Activity
整改建议:避免使用包含FLAG_ACTIVITY_NEW_TASK标志的Intent启动Activity
安全风险: 攻击者可以利用开放的Provider Content获取系统敏感资源
执行步骤
AndroidManifest.xml文件,定位各Provider,尤其是设置了android:exported="true"的。Drozer扫描:run scanner.provider.finduris -a com.mwr.example.sieveAccessible content URIs说明存在注入风险。预期结果:系统为Content Provider组件分配合适的权限,不存在信息泄露。
整改建议
AndroidManifest.xml文件的各provider标签中,设置android:exported="false";minSdkVersion不低于9;content provider交换数据设置protectionLevel=“signature”验证签名,仅授予那些和本程序应用了相同密钥来签名的程序content provider确保不存储敏感数据;安全风险
APP的实现中定义了一个可以访问本地文件的Content Provider组件,默认的android:exported="true",该Provider实现了openFile()接口
通过此接口可以访问内部存储app_webview目录下的数据,由于后台未能对目标文件地址进行有效判断,可以通过"../"实现目录跨越,导致对任意私有数据的访问。
执行步骤
drozer命令扫描run scanner.provider.traversal -a com.mwr.example.sieveVulnerable Providers说明存在文件遍历风险。预期结果:不存在文件遍历漏洞。
整改建议:系统对在调用文件参数时添加防御。
安全风险:攻击者可以发送恶意的消息,控制Service执行恶意动作或者造成信息泄露。
执行步骤
drozer命令 run app.service.info -a xxxx查看service组件暴露。service,找到应用程序定义的在接收到消息时的各项参数以及各种处理逻辑。Service组件,能否能进行越权操作。如果可以风险存在,停止测试,记录漏洞。预期结果 系统为Service组件分配了适当权限
整改建议
AndroidManifest.xml文件的各receiver标签中,设置android:exported="false"。AndroidManifest.xml中,申明一个私有权限,级别为signature;service应设置为私有;service接收到的数据需需谨慎处理,对调用的接口做校验;安全风险:攻击者可以发送恶意的消息,控制Receiver执行恶意动作或者造成信息泄露。
执行步骤
AndroidManifest.xml文件,定位各Receiver,尤其是设置了android:exported="true"的。run app.service.start --action 服务名 --component 包名 服务名,查看是否能够造成应用程序拒绝服务。预期结果:系统为Service组件分配了适当权限
整改建议: AndroidManifest.xml文件的各组件标签中,设置android:exported="false";组件接收消息代码中增加消息异常处理机制。
备注:其他类型的拒绝服务攻击参考SEC_AN_ PLUS_11.1 intent应用本地拒绝服务漏洞。
安全风险
Android系统中提供了Intent机制来协助应用间的交互与通讯,例如:应用A发出一个intent信息,系统根据intent的描述,负责找到可以解析该intent消息的应用B。
B应用负责接收intent的组件,在解析intent数据时,会通过Intent的getXXXExtra()函数,如果解析为空数据、异常、或是畸形数据,就可能会导致程序崩溃。
执行步骤
Intent传入自定义的序列化对象,被攻击者在组件里解析该序列化数据,可能出现出现找不到类出现ClassNotFoundException异常而崩溃。攻击代码如下:Intent intent = new Intent();
intent.setAction("serializable_action");
intent.setClassName("com.my.test", "com.my.test.MainActivity"); // 指定攻击目标的包名和Activity入口
intent.putExtra("serializable_obj",XXX); //此处是传入畸形数据
startActivity(intent);
IndexOutOfBoundsException异常导致的拒绝服务,如果程序没有对getIntegerArrayListExtra()等获取到的数据数组元素大小的判断,从而导致数组访问越界而导致应用崩溃;攻击应用代码片段:Intent intent = new Intent();
intent.setClassName("com.alibaba.jaq.pocforrefuseservice", "com.alibaba.jaq.pocforrefuseservice.MainActivity");
ArrayList<Integer> user_id = new ArrayList<Integer>(); //定义数组
intent.putExtra("user_id", user_id);
startActivity(intent);
)
预期结果:在使用Intent获取数据时对异常做了充分的处理。
整改建议
建议处理通过Intent.getXXXExtra()获取的数据时进行以下判断,以及用try catch方式进行捕获所有异常,以防止应用出现拒绝服务漏洞:
安全风险
Android应用通常使用PF_UNIX、PF_INET、PF_NETLINK等不同域名的socket来进行本地进程间通信或者远程网络通信,这些socket暴漏了潜在的本地或远程攻击面,历史上也出现过不少利用socket进行拒绝服务、root提权或者远程命令执行的案例。
特别是PF_INET类型的网络socket,可以通过网络与Android应用通信,其原本用于linux环境下开放网络服务,由于缺乏对网络调用者身份或者本地调用者的安全检查机制,在实现不当的情况下,可以突破Android的沙箱限制,对被攻击的应用执行命令,导致比较严重的漏洞。
执行步骤
socket数据是否进行处理,代码示例如下://定义读取socket命令
public static String readCMDFromSocket(InputStream in) {
int MAX_BUFFER_BYTES = 2048;
String msg = "";
byte[] tempbuffer = new byte[MAX_BUFFER_BYTES];
try {
int numReadedBytes = in.read(tempbuffer, 0, tempbuffer.length);
if( numReadedBytes > -1 )
msg = new String(tempbuffer, 0, numReadedBytes, "utf-8");
tempbuffer = null;
} catch (Exception e) {
e.printStackTrace();
}
return msg;
}
...
//处理socket信息
public void handlemsg()
{
...
msg = readCMDFromSocket(in)
if ("exec" == msg)
{
//没有任何的socket命令校验
...
...
}
...
}
socket和内容做任何校验检查,则风险存在。预期结果 对socket数据内容进行校验。
整改建议 直接传递命令或者间接处理敏感信息时,避免使用socket实现。
安全风险
APP中使用了有运行其他程序的代码逻辑,如果执行的代码是第三方库中,可能会存在未知的恶意行为,如果是程序自身代码,若调用逻辑有缺陷可能会导致执行其他恶意的第三方程序,攻击者可能会利用该缺陷执行恶意代码。
执行步骤
Runtime.getRuntime().exec执行第三方程序的代码样例:try {
Process p1 = Runtime.getRuntime().exec(
new String[] { "/system/bin/ls", "-l" },
new String[] { "a=1", "b=2" });
Runtime.getRuntime().load(
"/data/data/com.baidu.seclab/lib/libtest.so");
Runtime.getRuntime().loadLibrary("test");
} catch (IOException e) {
e.printStackTrace();
}
Runtime.getRuntime().exec执行第三方程序后,且检测到调用逻辑中存在缺陷,则风险存在。停止测试,记录漏洞。预期结果 合理使用Runtime.getRuntime().exec等函数,防止恶意调用。
整改建议 合理设置程序逻辑防止恶意调用,如果该行为是非期望行为,移除相关代码。
安全风险
App向服务器提交的数据易被中间人篡改,对用户数据的完整性造成影响,如用户信息被破解利用等问题。
执行步骤
Charles代理工具连接设备代理,启动app,正常操作app;预期结果:请求数据中包含完整性校验字段;
整改建议:添加完整性校验逻辑。
安全风险:
攻击者可以通过劫持键盘窃取用户输入数据,可能带来用户账号密码、敏感数据等泄露的风险,特别是银行金融类App。
执行步骤
预期结果:App在输入时使用自带键盘
整改建议:在App内集成自带键盘,并采用随机分布式键盘。