zoukankan      html  css  js  c++  java
  • 进程隐藏与进程保护(SSDT Hook 实现)(三)

    文章目录:

    1. 引子:

    2. 获取当前系统下所有进程:

    3. 服务管理(安装,启动,停止,卸载):

    4. 应用程序和内核程序通信:

    5. 小结:

    1. 引子:

    关于这个 SSDT Hook 实现进程隐藏和进程保护呢,这是最后一篇博文了,在文章的结尾处你可以下载到整个项

    目的实例程序以及代码,程序可以在 XPServerWin7 上运行的,当然我说的是32 位操作系统。

    这一篇博文介绍的则是在 Ring3 下编写 MFC 应用程序,并且让应用程序与内核程序通信,即由应用程序

    将需要隐藏的进程或者是需要保护的进程的 PID 传递给内核程序,然后在内核程序中就会将传递进来的这PID

    进行隐藏或者保护 ~

    在这里再给出这个应用程序的一张截图:

    clip_image002

    2. 获取当前系统下所有进程:

    前面提到过,要想获取到系统下的所有进程,有三种方法,

    第一种即是使用 ToolHelp 来获取;

    第二种则是使用 PSAPI 来获取;

    第三种则是使用 ntdll.dll 中的未文档化的 NtQuerySystemInformation 之类的 API 来获取(比较麻烦)。

    而在这里我使用最简单的方式,即通过 PSAPI 中的 EnumProcesses 这个 API 来获取,EnumProcesses API 可以

    获取到当前系统下所有进程的 PID,并且将 PID 存放在作为输出参数的数组当中,

    其原型如下(可以看 MSDN):

    1: BOOL WINAPI EnumProcesses(

    2: __out DWORD* pProcessIds,

    3: __in DWORD cb,

    4: __out DWORD* pBytesReturned

    5: );

    6:

    代码中使用(将获取到所有的 PID,然后将 PID 保存到 vector 容器中)

    1: //遍历当前所有的进程,并且将进程 ID 填充到容器 vectorPID 中

    2: void CSSDTProcessDlg::FillPIDVector()

    3: {

    4: DWORD dwPIDArray[MAX_PROCESS_COUNT];

    5: DWORD dwNeededBytes;

    6: DWORD dwProcCount;

    7:

    8: dwNeededBytes = 0;

    9: dwProcCount = 0;

    10: memset(dwPIDArray, 0, sizeof(DWORD) * MAX_PROCESS_COUNT);

    11: if(NULL != EnumProcesses(dwPIDArray, sizeof(dwPIDArray), &dwNeededBytes))

    12: {

    13: dwProcCount = dwNeededBytes / sizeof(DWORD);

    14: }

    15:

    16: BubbleSort(dwPIDArray, dwProcCount);

    17:

    18: ClearVector();

    19: for(int i=0; i<dwProcCount; i++)

    20: {

    21: PROCESS_BIND procBind;

    22: procBind.dwPID = dwPIDArray[i];

    23: if(dwPIDArray[i] == 0)

    24: {

    25: procBind.state = ProcessStateUnknown;

    26: }

    27: else

    28: {

    29: procBind.state = ProcessStateGeneral;

    30: }

    31: this->m_vctAllProcess.push_back(procBind);

    32: }

    33: }

    3. 服务管理(安装,启动,停止,卸载):

    在 Windows 内核程序中,现在大体可以分为三类:

    第一类是 NT 式驱动程序;

    第二类是 WDM 驱动程序;

    第三类是 WDF 驱动程序;

    其中,对于 NT 式驱动程序,其安装方式是很简单的,因为你可以将 NT 式驱动程序看做一个服务,既然是

    服务的话,自然在 Windows 中可以通过 SCM API 来完成其安装,启动,停止和卸载等功能 ~

    而至于 WDM 和 WDF 的话,如果其中涉及到了设备的话,还必须使用 INF 文件来实现安装 ~ 而我们前面的

    那个 SSDT 内核程序就是基于 NT 式的驱动程序,所以可以通过 SCM API 来实现上面的这些功能。

    至于如何使用 SCM API 来完成服务的安装、启动、停止和卸载功能的话,可以参见笔者的另外一篇博文

    《Windows 服务(附服务开发辅助工具)》,

    博文地址为:http://www.cnblogs.com/BoyXiao/archive/2011/08/07/2130208.html

    下面就只是将服务的安装 API、启动 API、停止 API 和卸载 API 贴出来了 ~

    至于这些代码的细细道来的话,可以参考上面给出的那篇博文

    1: //=====================================================================================//

    2: //Name: bool InstallSvc() //

    3: // //

    4: //Descripion: 安装服务 //

    5: // lpszSvcName 为服务名称, //

    6: // lpszDisplay 为显示在服务控制管理器中的名称, //

    7: // lpszSvcBinaryPath 为服务映像文件所在路径, //

    8: // dwSvcType 为服务类型 //

    9: // dwStartType 为服务启动类型 //

    10: //=====================================================================================//

    11: bool CSSDTProcessDlg::InstallSvc(LPTSTR lpszSvcName, LPTSTR lpszDisplayName,

    12: LPTSTR lpszSvcBinaryPath, DWORD dwSvcType, DWORD dwStartType)

    13: {

    14: SC_HANDLE hSCM = NULL;

    15: SC_HANDLE hSvc = NULL;

    16:

    17: AdjustProcessTokenPrivilege();

    18:

    19: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    20: if(NULL == hSCM)

    21: {

    22: OutputErrorMessage(TEXT("InstallSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));

    23:

    24: return FALSE;

    25: }

    26:

    27: for(int i = 0; i < 3 && (NULL == hSvc); i++)

    28: {

    29: //SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS

    30: hSvc = CreateService(hSCM, lpszSvcName, lpszDisplayName, SERVICE_ALL_ACCESS,

    31: dwSvcType, dwStartType, SERVICE_ERROR_NORMAL,

    32: lpszSvcBinaryPath, NULL, NULL, NULL, NULL, NULL);

    33: if(NULL != hSvc)

    34: {

    35: if(NULL != hSvc)

    36: {

    37: CloseServiceHandle(hSvc);

    38: }

    39: CloseServiceHandle(hSCM);

    40: return TRUE;

    41: }

    42: }

    43:

    44: OutputErrorMessage(TEXT("InstallSvc - CreateService Failed , Error Code Is %d , Error Message Is %s !"));

    45:

    46: CloseServiceHandle(hSCM);

    47:

    48: return FALSE;

    49: }

    50:

    51:

    52: //=====================================================================================//

    53: //Name: bool UnInstallSvc() //

    54: // //

    55: //Descripion: 实现卸载服务 //

    56: //=====================================================================================//

    57: bool CSSDTProcessDlg::UnInstallSvc(LPTSTR lpszSvcName)

    58: {

    59: SC_HANDLE hSCM = NULL;

    60: SC_HANDLE hSvc = NULL;

    61: bool rtResult = FALSE;

    62:

    63: AdjustProcessTokenPrivilege();

    64:

    65: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    66: if(NULL == hSCM)

    67: {

    68: OutputErrorMessage(TEXT("UnInstallSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));

    69:

    70: return FALSE;

    71: }

    72:

    73: hSvc = OpenService(hSCM, lpszSvcName, SERVICE_ALL_ACCESS);

    74: if(NULL == hSvc)

    75: {

    76: OutputErrorMessage(TEXT("UnInstallSvc - OpenService Failed , Error Code Is %d , Error Message Is %s !"));

    77:

    78: CloseServiceHandle(hSCM);

    79:

    80: return FALSE;

    81: }

    82:

    83: rtResult = DeleteService(hSvc);

    84:

    85: CloseServiceHandle(hSvc);

    86: CloseServiceHandle(hSCM);

    87:

    88: return rtResult;

    89: }

    90:

    91:

    92: //=====================================================================================//

    93: //Name: bool StartSvc() //

    94: // //

    95: //Descripion: 实现启动服务 //

    96: //=====================================================================================//

    97: bool CSSDTProcessDlg::StartSvc(LPTSTR lpszSvcName)

    98: {

    99: SC_HANDLE hSCM = NULL;

    100: SC_HANDLE hSvc = NULL;

    101: bool rtResult = FALSE;

    102:

    103: AdjustProcessTokenPrivilege();

    104:

    105: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    106: if(NULL == hSCM)

    107: {

    108: OutputErrorMessage(TEXT("StartSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));

    109:

    110: return FALSE;

    111: }

    112:

    113: hSvc = OpenService(hSCM, lpszSvcName, SERVICE_ALL_ACCESS);

    114: if(NULL == hSvc)

    115: {

    116: OutputErrorMessage(TEXT("StartSvc - OpenService Failed , Error Code Is %d , Error Message Is %s !"));

    117:

    118: CloseServiceHandle(hSCM);

    119:

    120: return FALSE;

    121: }

    122:

    123: rtResult = StartService(hSvc, NULL, NULL);

    124:

    125: CloseServiceHandle(hSvc);

    126: CloseServiceHandle(hSCM);

    127:

    128: if(FALSE == rtResult)

    129: {

    130: if(ERROR_SERVICE_ALREADY_RUNNING == GetLastError())

    131: {

    132: return TRUE;

    133: }

    134: else

    135: {

    136: OutputErrorMessage(TEXT("StartSvc - StartService Failed , Error Code Is %d , Error Message Is %s !"));

    137:

    138: return FALSE;

    139: }

    140: }

    141: else

    142: {

    143: return TRUE;

    144: }

    145: }

    146:

    147:

    148: //=====================================================================================//

    149: //Name: bool StopSvc() //

    150: // //

    151: //Descripion: 实现停止服务 //

    152: //=====================================================================================//

    153: bool CSSDTProcessDlg::StopSvc(LPTSTR lpszSvcName)

    154: {

    155: SC_HANDLE hSCM = NULL;

    156: SC_HANDLE hSvc = NULL;

    157: bool rtResult = FALSE;

    158:

    159: SERVICE_STATUS svcStatus;

    160:

    161: AdjustProcessTokenPrivilege();

    162:

    163: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    164: if(NULL == hSCM)

    165: {

    166: OutputErrorMessage(TEXT("StopSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));

    167:

    168: return FALSE;

    169: }

    170:

    171: hSvc = OpenService(hSCM, lpszSvcName, SERVICE_ALL_ACCESS);

    172: if(NULL == hSvc)

    173: {

    174: OutputErrorMessage(TEXT("StopSvc - OpenService Failed , Error Code Is %d , Error Message Is %s !"));

    175:

    176: CloseServiceHandle(hSCM);

    177:

    178: return FALSE;

    179: }

    180:

    181: rtResult = ControlService(hSvc, SERVICE_CONTROL_STOP, &svcStatus);

    182: if(rtResult == FALSE)

    183: {

    184: OutputErrorMessage(TEXT("StopSvc - ControlService Failed , Error Code Is %d , Error Message Is %s !"));

    185: }

    186: CloseServiceHandle(hSvc);

    187: CloseServiceHandle(hSCM);

    188:

    189: return rtResult;

    190: }

    那么服务的安装和启动放在那里比较合适,而服务的关闭和卸载又放在那里比较合适呢 ?

    由于这个应用程序采用 MFC 开发,自然可以在 OnInitDialog()中安装和启动服务比较合适,而后可以

    在对话框类的析构函数中关闭和卸载掉服务 ~

    安装和启动服务:

    1: wstring wStrSysPath = GetSysFilePath();

    2: BOOL bResult = InstallSvc(((LPTSTR)(LPCTSTR)SSDT01_SERVICE_NAME),

    3: ((LPTSTR)(LPCTSTR)SSDT01_SERVICE_NAME),

    4: ((LPTSTR)(LPCTSTR)wStrSysPath.c_str()),

    5: SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START);

    6: if(FALSE == bResult)

    7: {

    8: MessageBox(_TEXT(" Install SSDT Service Failed , Application Auto Exit ! "),

    9: _TEXT("Application Error"), MB_OK | MB_ICONSTOP);

    10: CDialogEx::OnCancel();

    11: return FALSE;

    12: }

    13: else

    14: {

    15: bResult = StartSvc(SSDT01_SERVICE_NAME);

    16: if(FALSE == bResult)

    17: {

    18: MessageBox(_TEXT(" Start SSDT Service Failed , Application Auto Exit ! "),

    19: _TEXT("Application Error"), MB_OK | MB_ICONSTOP);

    20: CDialogEx::OnCancel();

    21: return FALSE;

    22: }

    23: }

    停止并且将服务卸载掉:

    1: ~CSSDTProcessDlg()

    2: {

    3: //在析构函数中关闭 SSDT 设备句柄

    4: if(this->m_hDevice)

    5: {

    6: CloseHandle(this->m_hDevice);

    7: }

    8:

    9: //当发生析构函数时,停止服务并且卸载服务

    10: StopSvc(SSDT01_SERVICE_NAME);

    11: UnInstallSvc(SSDT01_SERVICE_NAME);

    12: }

    4. 应用程序和内核程序通信:

    由前面的第二篇博文,可以知道,应用程序和内核程序的通信我是通过 DeviceIoControl 来完成的,开

    发过内核程序的都清楚,应用程序和内核程序的通信最普遍的也就通过三个 API 来实现:

    一个是 ReadFile;一个是WriteFile;一个是DeviceIoContrl。

    当然其中属 DeviceIoControl 功能最为强大,完全可以用其替换掉 ReadFile 和 WriteFile。

    DeviceIoControl 原型(详细信息可以参考 MSDN):

    1: BOOL WINAPI DeviceIoControl(

    2: __in HANDLE hDevice,

    3: __in DWORD dwIoControlCode,

    4: __in LPVOID lpInBuffer,

    5: __in DWORD nInBufferSize,

    6: __out LPVOID lpOutBuffer,

    7: __in DWORD nOutBufferSize,

    8: __out LPDWORD lpBytesReturned,

    9: __in LPOVERLAPPED lpOverlapped

    10: );

    11:

    至于如何实现应用程序和内核程序的通信的话,在我的 Demo 中是这样做处理的,首先在 OnInitDialog

    事件中通过 CreateFile 打开我们所安装的服务中创建的设备,(在 NT 式驱动程序中我创建了一个设备,这个

    设备用来实现应用程序和内核程序的通信),然后在对话框类中保存有一个全局变量,这个全局变量即代表所打开

    的这个设备的句柄,

    clip_image004

    既然这个全局变量是保存的我们的设备的句柄,自然我们需要来获取到设备的句柄,并且将句柄赋值给该全

    局变量,而这个呢,又是在 OnInitDialog 中完成的 ~

    1: this->m_hDevice = CreateFile(SSDT01_DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, 0,

    2: NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    3: if(INVALID_HANDLE_VALUE == this->m_hDevice)

    4: {

    5: MessageBox(_TEXT(" Open SSDT Device Failed , Application Auto Exit ! "),

    6: _TEXT("Application Error"), MB_OK | MB_ICONSTOP);

    7:

    8: CDialogEx::OnCancel();

    9: return FALSE;

    10: }

    有了这个设备句柄,我们就可以通过其来实现和内核程序的通信了,因为通过在应用程序中调用

    DeviceIoControl 可以产生 IRP_MJ_DEVICE_CONTROL 的 IRP,然后该 IRP 可以被驱动程序中的

    DeviceIoControl 分发函数所处理 ~

    我们的应用程序只需要将我们所要隐藏或者是需要保护的进程的 PID 通过 DeviceIoControl 传递给内核程序即可 !!!

    所以我们在应用程序中只需要调用 DeviceIoContrl 即可 ~

    下面给出的代码比较凌乱(重点请看 DeviceIoControl 的调用)

    1: //隐藏进程或者取消对进程的隐藏

    2: void CSSDTProcessDlg::OnBnClickedBtnHideorunhide()

    3: {

    4: int nIndex;

    5: DWORD dwPID;

    6: CString cStrText;

    7: CString cStrState;

    8:

    9: DWORD dwOutput;

    10: BOOL bRet;

    11: CHAR inBuffer[10];

    12: CHAR outBuffer[10];

    13: memset(inBuffer, 0, 10);

    14: memset(outBuffer, 0, 10);

    15:

    16: dwPID = this->GetDlgItemInt(IDC_STATIC_SELECTED_PID);

    17: this->GetDlgItemText(ID_BTN_HIDEORUNHIDE, cStrText);

    18:

    19: ultoa(dwPID, inBuffer, 10);

    20:

    21: nIndex = QueryItemIndexByPID(dwPID);

    22: cStrState = this->m_ListCtrlProcess.GetItemText(nIndex, 4);

    23:

    24: if(cStrText.CompareNoCase(_TEXT("Hide")) == 0)

    25: {

    26: //隐藏 dwPID

    27: bRet = DeviceIoControl(this->m_hDevice, IO_INSERT_HIDE_PROCESS, inBuffer, 10,

    28: &outBuffer, 10, &dwOutput, NULL);

    29: if(bRet)

    30: {

    31: this->SetDlgItemText(ID_BTN_HIDEORUNHIDE, _TEXT("UnHide"));

    32: if(cStrState.CompareNoCase(_TEXT("Protect")) == 0)

    33: {

    34: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("HideAndProtect"));

    35: }

    36: else

    37: {

    38: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Hide"));

    39: }

    40: MessageBox(_TEXT(" Hide Process Sucess ! "), _TEXT("Information"), MB_OK |

    41: MB_ICONINFORMATION);

    42: }

    43: else

    44: {

    45: MessageBox(_TEXT(" Hide Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);

    46: }

    47: }

    48: else

    49: {

    50: //解除 dwPID 隐藏

    51: bRet = DeviceIoControl(this->m_hDevice, IO_REMOVE_HIDE_PROCESS, inBuffer, 10,

    52: &outBuffer, 10, &dwOutput, NULL);

    53: if(bRet)

    54: {

    55: this->SetDlgItemText(ID_BTN_HIDEORUNHIDE, _TEXT("Hide"));

    56: if(cStrState.CompareNoCase(_TEXT("Protect")) == 0 ||

    57: cStrState.CompareNoCase(_TEXT("HideAndProtect"))== 0)

    58: {

    59: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Protect"));

    60: }

    61: else

    62: {

    63: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("General"));

    64: }

    65: MessageBox(_TEXT(" UnHide Process Sucess ! "), _TEXT("Information"), MB_OK |

    66: MB_ICONINFORMATION);

    67: }

    68: else

    69: {

    70: MessageBox(_TEXT(" UnHide Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);

    71: }

    72: }

    73: }

    74:

    75:

    76: //保护进程或者取消对进程的保护操作

    77: void CSSDTProcessDlg::OnBnClickedBtnProtectorunprotect()

    78: {

    79: int nIndex;

    80: DWORD dwPID;

    81: CString cStrText;

    82: CString cStrState;

    83:

    84: DWORD dwOutput;

    85: BOOL bRet;

    86: CHAR inBuffer[10];

    87: CHAR outBuffer[10];

    88: memset(inBuffer, 0, 10);

    89: memset(outBuffer, 0, 10);

    90:

    91: dwPID = this->GetDlgItemInt(IDC_STATIC_SELECTED_PID);

    92: this->GetDlgItemText(ID_BTN_PROTECTORUNPROTECT, cStrText);

    93:

    94: ultoa(dwPID, inBuffer, 10);

    95:

    96: nIndex = QueryItemIndexByPID(dwPID);

    97: cStrState = this->m_ListCtrlProcess.GetItemText(nIndex, 4);

    98:

    99: if(cStrText.CompareNoCase(_TEXT("Protect")) == 0)

    100: {

    101: //保护 dwPID 保护

    102: bRet = DeviceIoControl(this->m_hDevice, IO_INSERT_PROTECT_PROCESS, inBuffer, 10,

    103: &outBuffer, 10, &dwOutput, NULL);

    104: if(bRet)

    105: {

    106: this->SetDlgItemText(ID_BTN_PROTECTORUNPROTECT, _TEXT("UnProtect"));

    107: if(cStrState.CompareNoCase(_TEXT("Hide"))== 0)

    108: {

    109: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("HideAndProtect"));

    110: }

    111: else

    112: {

    113: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Protect"));

    114: }

    115: MessageBox(_TEXT(" Protect Process Sucess ! "), _TEXT("Information"), MB_OK |

    116: MB_ICONINFORMATION);

    117: }

    118: else

    119: {

    120: MessageBox(_TEXT(" Protect Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);

    121: }

    122: }

    123: else

    124: {

    125: //解除 dwPID 保护

    126: bRet = DeviceIoControl(this->m_hDevice, IO_REMOVE_PROTECT_PROCESS, inBuffer, 10,

    127: &outBuffer, 10, &dwOutput, NULL);

    128: if(bRet)

    129: {

    130: this->SetDlgItemText(ID_BTN_PROTECTORUNPROTECT, _TEXT("Protect"));

    131: if(cStrState.CompareNoCase(_TEXT("Hide")) == 0 ||

    132: cStrState.CompareNoCase(_TEXT("HideAndProtect")) == 0)

    133: {

    134: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Hide"));

    135: }

    136: else

    137: {

    138: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("General"));

    139: }

    140: MessageBox(_TEXT(" UnProtect Process Sucess ! "), _TEXT("Information"), MB_OK |

    141: MB_ICONINFORMATION);

    142: }

    143: else

    144: {

    145: MessageBox(_TEXT(" UnProtect Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);

    146: }

    147: }

    148: }

    5. 小结:

    介绍这个应用程序呢,还真是不好写,因为感觉整个 Demo 里面却是没有什么好介绍的,无非就是获取到所

    有的进程,然后通过一个 ListCtrl 来显示这些数据,然后用户选择一个进程,单击一下隐藏呢,我就在这个按

    钮的消息处理函数中和内核程序通过 DeviceIoControl 通信一下,将这个进程的 PID 传递给内核程序,其他的

    就都不需要理会了 ~ 所以转来转去的,也没什么好些的,干脆就写到这里得了。

    等下将整个 Demo 打个包,直接提供下载,我这里说得口干舌燥也没什么用,感兴趣的自己下载了源码去慢

    慢玩得了 ~

    最后再总结一个 SSDT Hook 的优点,那就是 SSDT Hook 无论你是 Windows XP 还是 Server 或者 Vista 或

    者 Win7,你都是可以很好的运行程序的,所以你下载的 Demo 你可以放心的在上面的这些操作系统上运行,当然

    64 位的除外,64 位的操作系统虽然我没有做过测试,但是我估摸着会蓝屏的 ~ 有兴趣的可以去蓝一次 ~

    下载 Demo Source Code

  • 相关阅读:
    MAC OS 快捷键一览
    JavaScript检测实例属性, 原型属性
    jQuery $.each用法
    双击和单击事件冲突解决方法
    移动WEB前端开发资源整合
    纯CSS3实现自定义涂鸦风格的边框
    jquery如何阻止子元素相应mouseout事件
    jquery键盘事件全记录
    javascript类型系统之基本数据类型与包装类型
    经验分享:CSS浮动(float,clear)通俗讲解
  • 原文地址:https://www.cnblogs.com/airoot/p/4867407.html
Copyright © 2011-2022 走看看