博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
进程隐藏与进程保护(SSDT Hook 实现)(三)
阅读量:5363 次
发布时间:2019-06-15

本文共 18977 字,大约阅读时间需要 63 分钟。

文章目录:

                 

1. 引子:

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

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

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

5. 小结:

                

1. 引子:

                         

关于这个 SSDT Hook 实现进程隐藏和进程保护呢,这是最后一篇博文了,

在文章的结尾处呢你可以下载到整个项目的实例程序以及代码,

程序可以在 XP、Server、Win7 上运行的,当然我是说的 32 位操作系统。       

                  

《进程隐藏与进程保护(SSDT Hook 实现)(一)》呢把 SSDT Hook 的原理说得差不多了,

博文地址:

《进程隐藏与进程保护(SSDT Hook 实现)(二)》则把 SSDT Hook 的实现说得差不多了,

博文地址:

             

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

即由应用程序将需要隐藏的进程或者是需要保护的进程的 PID 传递给内核程序,

然后在内核程序中就会将传递进来的这个 PID 进行隐藏或者保护 ~

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

        

                

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
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 服务(附服务开发辅助工具)》,

博文地址为:

下面就只是将服务的安装 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 式驱动程序中我创建了一个设备,这个设备用来实现应用程序和内核程序的通信),

然后在对话框类中保存有一个全局变量,这个全局变量即代表所打开的这个设备的句柄,

       

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

而这个呢,又是在 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

                                    

           

版权所有,迎转载,但转载请注明: 转载自 

            

          

 

转载于:https://www.cnblogs.com/BoyXiao/archive/2011/09/05/2168115.html

你可能感兴趣的文章
git diff 的用法
查看>>
一段sql的优化
查看>>
十进制与十六进制的相互转换
查看>>
在Flex中用Validator检测数字、字符串、Email.
查看>>
[leetcode]4Sum
查看>>
POJ1062 昂贵的聘礼
查看>>
【零基础学习iOS开发】【02-C语言】08-基本运算
查看>>
Java 将指定字符串连接到此字符串的结尾 concat()
查看>>
Hibernate Criterion
查看>>
Python知识
查看>>
我们为什么要搞长沙.NET技术社区(三)
查看>>
杭电acm Cake
查看>>
js函数中this的指向
查看>>
c++ 引用方式传递数组
查看>>
HBase学习之路 (九)HBase phoenix的使用
查看>>
LeetCode() Remove Duplicates from Sorted Array II
查看>>
【svn】idea svn 文件上会出现一个破书
查看>>
cocos2d-x 3.0 场景切换特效汇总(转)
查看>>
The SortedMap Interface
查看>>
SniperOJ-leak-x86-64
查看>>