code2012

加油,坚持,努力,自信
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

转:EasyHook远程代码注入

Posted on 2013-10-09 12:59  code2012  阅读(2984)  评论(0编辑  收藏  举报
 

EasyHook远程代码注入

  

    最近一段时间由于使用MinHook的API挂钩不稳定,经常因为挂钩地址错误而导致宿主进程崩溃。听同事介绍了一款智能强大的挂钩引擎EasyHook。它比微软的detours好的一点是它的x64注入支持是免费开源的。不想微软的detours,想搞x64还得购买。

    好了,闲话不多说,先下载EasyHook的开发库,当然有兴趣的同学可以下载源码进行学习。下载地址:http://easyhook.codeplex.com/releases/view/24401。我给的这个是2.6版本的。

    EasyHook提供了两种模式的注入管理。一种是托管代码的注入,另一种是非托管代码的注入。我是学习C++的,所以直接学习了例子中的非托管项目UnmanagedHook。里面给了一个简单的挂钩MessageBeep API的示例。我需要将其改造成支持远程注入的。下面先给出钩子DLL代码:

  1. // dllmain.cpp : 定义 DLL 应用程序的入口点。  
  2. #include "stdafx.h"  
  3. #include "HookApi.h"  
  4. #include "easyhook.h"  
  5. #include "ntstatus.h"  
  6.   
  7. ptrCreateFileW realCreateFileW = NULL;  
  8. ptrCreateFileA realCreateFileA = NULL;  
  9. HMODULE                 hKernel32 = NULL;  
  10. TRACED_HOOK_HANDLE      hHookCreateFileW = new HOOK_TRACE_INFO();  
  11. TRACED_HOOK_HANDLE      hHookCreateFileA = new HOOK_TRACE_INFO();  
  12. NTSTATUS                statue;  
  13. ULONG                   HookCreateFileW_ACLEntries[1] = {0};  
  14. ULONG                   HookCreateFileA_ACLEntries[1] = {0};  
  15.   
  16. int PrepareRealApiEntry()  
  17. {  
  18.     OutputDebugString(L"PrepareRealApiEntry()\n");  
  19.   
  20.     // 获取真实函数地址  
  21.     HMODULE hKernel32 = LoadLibrary(L"Kernel32.dll");  
  22.     if (hKernel32 == NULL)  
  23.     {  
  24.         OutputDebugString(L"LoadLibrary(L\"Kernel32.dll\") Error\n");  
  25.         return -6002;  
  26.     }  
  27.     OutputDebugString(L"LoadLibrary(L\"Kernel32.dll\") OK\n");  
  28.   
  29.     realCreateFileW = (ptrCreateFileW)GetProcAddress(hKernel32, "CreateFileW");  
  30.     if (realCreateFileW == NULL)  
  31.     {  
  32.         OutputDebugString(L"(ptrCreateFileW)GetProcAddress(hKernel32, \"CreateFileW\") Error\n");  
  33.         return -6007;  
  34.     }  
  35.     OutputDebugString(L"(ptrCreateFileW)GetProcAddress(hKernel32, \"CreateFileW\") OK\n");  
  36.   
  37.     realCreateFileA = (ptrCreateFileA)GetProcAddress(hKernel32, "CreateFileA");  
  38.     if (realCreateFileA == NULL)  
  39.     {  
  40.         OutputDebugString(L"(ptrCreateFileA)GetProcAddress(hKernel32, \"CreateFileA\") Error\n");  
  41.         return -6007;  
  42.     }  
  43.     OutputDebugString(L"(ptrCreateFileA)GetProcAddress(hKernel32, \"CreateFileA\") OK\n");  
  44.   
  45.     return 0;  
  46. }  
  47.   
  48. void DoHook()  
  49. {  
  50.     OutputDebugString(L"DoHook()\n");  
  51.   
  52.     statue = LhInstallHook(realCreateFileW,  
  53.         MyCreateFileW,  
  54.         /*(PVOID)0x12345678*/NULL,  
  55.         hHookCreateFileW);  
  56.     if(!SUCCEEDED(statue))  
  57.     {  
  58.         switch (statue)  
  59.         {  
  60.         case STATUS_NO_MEMORY:  
  61.             OutputDebugString(L"STATUS_NO_MEMORY\n");  
  62.             break;  
  63.         case STATUS_NOT_SUPPORTED:  
  64.             OutputDebugString(L"STATUS_NOT_SUPPORTED\n");  
  65.             break;  
  66.         case STATUS_INSUFFICIENT_RESOURCES:  
  67.             OutputDebugString(L"STATUS_INSUFFICIENT_RESOURCES\n");  
  68.             break;  
  69.         default:  
  70.             WCHAR dbgstr[512] = {0};  
  71.             wsprintf(dbgstr, L"%d\n", statue);  
  72.             OutputDebugString(dbgstr);  
  73.         }  
  74.         OutputDebugString(L"LhInstallHook(GetProcAddress(hKernel32, \"CreateFileW\"),MyCreateFileW,(PVOID)0x12345678,hHookCreateFileW); Error\n");  
  75.         return;  
  76.     }  
  77.     OutputDebugString(L"Hook CreateFileW OK\n");  
  78.   
  79.     statue = LhInstallHook(realCreateFileA,  
  80.         MyCreateFileA,  
  81.         /*(PVOID)0x12345678*/NULL,  
  82.         hHookCreateFileA);  
  83.     if(!SUCCEEDED(statue))  
  84.     {  
  85.         switch (statue)  
  86.         {  
  87.         case STATUS_NO_MEMORY:  
  88.             OutputDebugString(L"STATUS_NO_MEMORY\n");  
  89.             break;  
  90.         case STATUS_NOT_SUPPORTED:  
  91.             OutputDebugString(L"STATUS_NOT_SUPPORTED\n");  
  92.             break;  
  93.         case STATUS_INSUFFICIENT_RESOURCES:  
  94.             OutputDebugString(L"STATUS_INSUFFICIENT_RESOURCES\n");  
  95.             break;  
  96.         default:  
  97.             WCHAR dbgstr[512] = {0};  
  98.             wsprintf(dbgstr, L"%d\n", statue);  
  99.             OutputDebugString(dbgstr);  
  100.         }  
  101.         OutputDebugString(L"LhInstallHook(GetProcAddress(hKernel32, \"CreateFileA\"),MyCreateFileA,(PVOID)0x12345678,hHookCreateFileA); Error\n");  
  102.         return;  
  103.     }  
  104.     OutputDebugString(L"Hook CreateFileA OK\n");  
  105.   
  106.       
  107.         // 一定要调用这个函数,否则注入的钩子无法正常运行。  
  108.         LhSetExclusiveACL(HookCreateFileA_ACLEntries, 1, hHookCreateFileA);  
  109.     LhSetExclusiveACL(HookCreateFileW_ACLEntries, 1, hHookCreateFileW);  
  110.   
  111. }  
  112.   
  113. void DoneHook()  
  114. {  
  115.     OutputDebugString(L"DoneHook()\n");  
  116.   
  117.     // this will also invalidate "hHook", because it is a traced handle...  
  118.     LhUninstallAllHooks();  
  119.   
  120.     // this will do nothing because the hook is already removed...  
  121.     LhUninstallHook(hHookCreateFileA);  
  122.     LhUninstallHook(hHookCreateFileW);  
  123.   
  124.     // now we can safely release the traced handle  
  125.     delete hHookCreateFileA;  
  126.     hHookCreateFileA = NULL;  
  127.   
  128.     delete hHookCreateFileW;  
  129.     hHookCreateFileW = NULL;  
  130.   
  131.     // even if the hook is removed, we need to wait for memory release  
  132.     LhWaitForPendingRemovals();  
  133. }  
  134.   
  135. BOOL APIENTRY DllMain( HMODULE hModule,  
  136.                        DWORD  ul_reason_for_call,  
  137.                        LPVOID lpReserved  
  138.                      )  
  139. {  
  140.     switch (ul_reason_for_call)  
  141.     {  
  142.     case DLL_PROCESS_ATTACH:  
  143.         {  
  144.             OutputDebugString(L"DllMain::DLL_PROCESS_ATTACH\n");  
  145.   
  146.             // 准备好原始地址与目的地址  
  147.             int errCode = PrepareRealApiEntry();  
  148.             if (errCode != 0)  
  149.             {  
  150.                 OutputDebugString(L"PrepareRealApiEntry() Error\n");  
  151.                 return FALSE;  
  152.             }  
  153.   
  154.             // 开始挂钩  
  155.             DoHook();  
  156.   
  157.             break;  
  158.         }  
  159.     case DLL_THREAD_ATTACH:  
  160.         {  
  161.             OutputDebugString(L"DllMain::DLL_THREAD_ATTACH\n");  
  162.   
  163.             break;  
  164.         }  
  165.     case DLL_THREAD_DETACH:  
  166.         {  
  167.             OutputDebugString(L"DllMain::DLL_THREAD_DETACH\n");  
  168.   
  169.             break;  
  170.         }  
  171.           
  172.     case DLL_PROCESS_DETACH:  
  173.         {  
  174.             OutputDebugString(L"DllMain::DLL_PROCESS_DETACH\n");  
  175.   
  176.             // 卸载钩子  
  177.             DoneHook();  
  178.   
  179.             break;  
  180.         }  
  181.     }  
  182.     return TRUE;  
  183. }  
  1. <pre name="code" class="cpp">// HookSvr.cpp  
  2.   
  3. #include "stdafx.h"  
  4. #include "HookApi.h"  
  5. #include "easyhook.h"  
  6.   
  7. HANDLE WINAPI MyCreateFileW(  
  8.               __in     LPCWSTR lpFileName,  
  9.               __in     DWORD dwDesiredAccess,  
  10.               __in     DWORD dwShareMode,  
  11.               __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,  
  12.               __in     DWORD dwCreationDisposition,  
  13.               __in     DWORD dwFlagsAndAttributes,  
  14.               __in_opt HANDLE hTemplateFile  
  15.               )  
  16. {  
  17.     HANDLE hHandle = NULL;  
  18.   
  19.     // 执行钩子  
  20.     if (realCreateFileW == NULL)  
  21.     {  
  22.         OutputDebugString(L"realCreateFileW is NULL\n");  
  23.         return INVALID_HANDLE_VALUE;  
  24.     }  
  25.     else  
  26.     {  
  27.         OutputDebugString(L"realCreateFileW is not NULL\n");  
  28.         hHandle = (realCreateFileW)(lpFileName, dwDesiredAccess, dwShareMode,  
  29.             lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);  
  30.   
  31.         OutputDebugString(L"MyCreateFileW : ");  
  32.         OutputDebugString(lpFileName);  
  33.         OutputDebugString(L"\n");  
  34.     }  
  35.   
  36.     return hHandle;  
  37. }  
  38.   
  39. HANDLE WINAPI MyCreateFileA(  
  40.                   __in     LPCSTR lpFileName,  
  41.                   __in     DWORD dwDesiredAccess,  
  42.                   __in     DWORD dwShareMode,  
  43.                   __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,  
  44.                   __in     DWORD dwCreationDisposition,  
  45.                   __in     DWORD dwFlagsAndAttributes,  
  46.                   __in_opt HANDLE hTemplateFile  
  47.                   )  
  48. {  
  49.     HANDLE hHandle = NULL;  
  50.   
  51.     // 执行钩子  
  52.     if (realCreateFileA == NULL)  
  53.     {  
  54.         OutputDebugString(L"realCreateFileA is NULL\n");  
  55.         return INVALID_HANDLE_VALUE;  
  56.     }  
  57.     else  
  58.     {  
  59.         OutputDebugString(L"realCreateFileA is not NULL\n");  
  60.         hHandle = (realCreateFileA)(lpFileName, dwDesiredAccess, dwShareMode,  
  61.             lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);  
  62.   
  63.         OutputDebugString(L"MyCreateFileW : ");  
  64.         OutputDebugStringA(lpFileName);  
  65.         OutputDebugString(L"\n");  
  66.     }  
  67.   
  68.     return hHandle;  
  69. }</pre><br>  
  70. 钩子这一部分我弄了比较久,主要是API不熟悉,不过好在弄好了。  
  71. <pre></pre>  
  72. <p><br>  
  73. </p>  
  74. <p></p><pre name="code" class="cpp">// HookSvr.h  
  75.   
  76. #pragma once  
  77. #include <Windows.h>  
  78.   
  79. #ifndef _M_X64  
  80. #pragma comment(lib, "EasyHook32.lib")  
  81. #else  
  82. #pragma comment(lib, "EasyHook64.lib")  
  83. #endif  
  84.   
  85. HANDLE WINAPI MyCreateFileW(  
  86.     __in     LPCWSTR lpFileName,  
  87.     __in     DWORD dwDesiredAccess,  
  88.     __in     DWORD dwShareMode,  
  89.     __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,  
  90.     __in     DWORD dwCreationDisposition,  
  91.     __in     DWORD dwFlagsAndAttributes,  
  92.     __in_opt HANDLE hTemplateFile  
  93.     );  
  94.   
  95. typedef HANDLE (WINAPI *ptrCreateFileW)(  
  96.     __in     LPCWSTR lpFileName,  
  97.     __in     DWORD dwDesiredAccess,  
  98.     __in     DWORD dwShareMode,  
  99.     __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,  
  100.     __in     DWORD dwCreationDisposition,  
  101.     __in     DWORD dwFlagsAndAttributes,  
  102.     __in_opt HANDLE hTemplateFile  
  103.     );  
  104.   
  105. extern ptrCreateFileW realCreateFileW;  
  106.   
  107. HANDLE WINAPI MyCreateFileA(  
  108.     __in     LPCSTR lpFileName,  
  109.     __in     DWORD dwDesiredAccess,  
  110.     __in     DWORD dwShareMode,  
  111.     __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,  
  112.     __in     DWORD dwCreationDisposition,  
  113.     __in     DWORD dwFlagsAndAttributes,  
  114.     __in_opt HANDLE hTemplateFile  
  115.     );  
  116.   
  117. typedef HANDLE (WINAPI *ptrCreateFileA)(  
  118.                                         __in     LPCSTR lpFileName,  
  119.                                         __in     DWORD dwDesiredAccess,  
  120.                                         __in     DWORD dwShareMode,  
  121.                                         __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,  
  122.                                         __in     DWORD dwCreationDisposition,  
  123.                                         __in     DWORD dwFlagsAndAttributes,  
  124.                                         __in_opt HANDLE hTemplateFile  
  125.                                         );  
  126.   
  127. extern ptrCreateFileA realCreateFileA;</pre><br>  
  128. <br>  
  129. <p></p>  
  130. <p>接下来是注入工具,这里指提供核心代码。本来EasyHook还提供了一个叫<span style="color:black">Rh</span>InjectLibrary()方法直接注入,这种方法相当稳定,推荐使用。我本来也用它,但是发现注入会失败,所以就采用了比较通用的远程注入代码,如下:</p>  
  131. <pre name="code" class="cpp">BOOL RtlFileExists(WCHAR* InPath)  
  132. {  
  133.     HANDLE          hFile;  
  134.   
  135.     if((hFile = CreateFileW(InPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)  
  136.         return FALSE;  
  137.   
  138.     CloseHandle(hFile);  
  139.   
  140.     return TRUE;  
  141. }  
  142.   
  143. BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege)  
  144. {  
  145.     TOKEN_PRIVILEGES tp;  
  146.     HANDLE hToken;  
  147.     LUID luid;  
  148.   
  149.     if( !OpenProcessToken(GetCurrentProcess(),  
  150.         TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,   
  151.         &hToken) )  
  152.     {         
  153.         return FALSE;  
  154.     }  
  155.   
  156.     if( !LookupPrivilegeValue(NULL,             // lookup privilege on local system  
  157.         lpszPrivilege,    // privilege to lookup   
  158.         &luid) )          // receives LUID of privilege  
  159.     {         
  160.         return FALSE;   
  161.     }  
  162.   
  163.     tp.PrivilegeCount = 1;  
  164.     tp.Privileges[0].Luid = luid;  
  165.     if( bEnablePrivilege )  
  166.         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;  
  167.     else  
  168.         tp.Privileges[0].Attributes = 0;  
  169.   
  170.     // Enable the privilege or disable all privileges.  
  171.     if( !AdjustTokenPrivileges(hToken,   
  172.         FALSE,   
  173.         &tp,   
  174.         sizeof(TOKEN_PRIVILEGES),   
  175.         (PTOKEN_PRIVILEGES) NULL,   
  176.         (PDWORD) NULL) )  
  177.     {         
  178.         return FALSE;   
  179.     }   
  180.   
  181.     if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )  
  182.     {  
  183.         //The token does not have the specified privilege.  
  184.         return FALSE;  
  185.     }   
  186.   
  187.     return TRUE;  
  188. }  
  189.   
  190. typedef DWORD (WINAPI *PFNTCREATETHREADEX)  
  191. (   
  192.  PHANDLE                 ThreadHandle,    
  193.  ACCESS_MASK             DesiredAccess,   
  194.  LPVOID                  ObjectAttributes,    
  195.  HANDLE                  ProcessHandle,   
  196.  LPTHREAD_START_ROUTINE  lpStartAddress,      
  197.  LPVOID                  lpParameter,     
  198.  BOOL                   CreateSuspended,      
  199.  DWORD                   dwStackSize,     
  200.  DWORD                   dw1,   
  201.  DWORD                   dw2,   
  202.  LPVOID                  Unknown   
  203.  );   
  204.   
  205. BOOL MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pThreadProc, LPVOID pRemoteBuf)  
  206. {  
  207.     HANDLE      hThread = NULL;  
  208.     FARPROC     pFunc = NULL;  
  209.     BOOL bHook;  
  210.   
  211.     // 判断系统版本  
  212.     OSVERSIONINFO osvi;  
  213.     //BOOL bIsWindowsXPorLater;  
  214.   
  215.     ZeroMemory(&osvi, sizeof(OSVERSIONINFO));  
  216.     osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);  
  217.   
  218.     GetVersionEx(&osvi);  
  219.   
  220.     if (osvi.dwMajorVersion == 6)  
  221.     {  
  222.         bHook = TRUE;  
  223.     }  
  224.     else  
  225.     {  
  226.         bHook = FALSE;  
  227.     }  
  228.   
  229.     if(bHook)    // Vista, 7, Server2008  
  230.     {  
  231.         pFunc = GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateThreadEx");  
  232.         if( pFunc == NULL )  
  233.         {  
  234.             //GetLastError());  
  235.             return FALSE;  
  236.         }  
  237.   
  238.         OutputDebugString(L"MyCreateRemoteThread");  
  239.         ((PFNTCREATETHREADEX)pFunc)(&hThread,  
  240.             0x1FFFFF,  
  241.             NULL,  
  242.             hProcess,  
  243.             pThreadProc,  
  244.             pRemoteBuf,  
  245.             FALSE,  
  246.             NULL,  
  247.             NULL,  
  248.             NULL,  
  249.             NULL);  
  250.         if( hThread == NULL )  
  251.         {             
  252.             return FALSE;  
  253.         }  
  254.     }  
  255.     else                    // 2000, XP, Server2003  
  256.     {  
  257.         hThread = CreateRemoteThread(hProcess,   
  258.             NULL,   
  259.             0,   
  260.             pThreadProc,   
  261.             pRemoteBuf,   
  262.             0,   
  263.             NULL);  
  264.         if( hThread == NULL )  
  265.         {             
  266.             return FALSE;  
  267.         }  
  268.     }  
  269.   
  270.     if( WAIT_FAILED == WaitForSingleObject(hThread, INFINITE) )  
  271.     {         
  272.         return FALSE;  
  273.     }  
  274.   
  275.     return TRUE;  
  276. }  
  277.   
  278. BOOL InjectDll(DWORD dwPID, const wchar_t *szDllName)  
  279. {  
  280.     HANDLE hProcess = NULL;  
  281.     LPVOID pRemoteBuf = NULL;  
  282.     FARPROC pThreadProc = NULL;  
  283.     DWORD dwBufSize = wcslen(szDllName)*sizeof(wchar_t)+2;  
  284.   
  285.     if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )  
  286.     {         
  287.         return FALSE;  
  288.     }  
  289.   
  290.     pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize,   
  291.         MEM_COMMIT, PAGE_READWRITE);  
  292.   
  293.     WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName,   
  294.         dwBufSize, NULL);  
  295.   
  296.     pThreadProc = GetProcAddress(GetModuleHandle(L"kernel32.dll"),   
  297.         "LoadLibraryW");  
  298.   
  299.     if( !MyCreateRemoteThread(hProcess, (LPTHREAD_START_ROUTINE)pThreadProc, pRemoteBuf) )  
  300.     {         
  301.         return FALSE;  
  302.     }  
  303.   
  304.     VirtualFreeEx(hProcess, pRemoteBuf, dwBufSize, MEM_RELEASE);  
  305.     CloseHandle(hProcess);  
  306.     return TRUE;  
  307. }  
  308.   
  309. int DoInject(DWORD aPid, const WCHAR *aFullpath)  
  310. {  
  311.     if (wcslen(aFullpath) <= 0)  
  312.     {  
  313.         return -1;  
  314.     }  
  315.   
  316.     //判断dll是否存在  
  317.     HANDLE hFile = CreateFile(aFullpath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);  
  318.     if(hFile != INVALID_HANDLE_VALUE)  
  319.     {                 
  320.         DWORD dwsize = GetFileSize(hFile, NULL);  
  321.         CloseHandle(hFile);  
  322.         if (dwsize < 10)  
  323.         {             
  324.             return -2;  
  325.         }         
  326.     }  
  327.     else  
  328.     {                 
  329.         return -3;  
  330.     }  
  331.   
  332.     BOOL bSuc=SetPrivilege(SE_DEBUG_NAME, TRUE);  
  333.     bSuc=InjectDll((DWORD)aPid, aFullpath);  
  334.     if (bSuc)  
  335.     {  
  336.         return -4;  
  337.     }  
  338.   
  339.     return 0;  
  340. }  
  341.   
  342.   
  343. // 真实注入的时候应该这样调用  
  344. DoInject(m_processId, L"E:\\src\\easyhook\\trunk\\Debug\\x86\\HookSvr.dll");  
  345.   
  346.   
  347. </pre><br>  
  348. 这样就能保证注入的钩子能正常工作了。  
 
 
查看评论
4楼 SpiritMFC 2013-03-26 15:43发表 [回复]
你好~ 能提供能运行的源码嘛?
我用你的方法正常exe可以hook成功,
但是DLL注入后钩子无法工作。
困扰数天的问题了。
求帮助!
还有 C++的话这个库能实现全局钩子嘛?
Re: baggiowangyu 2013-03-29 09:24发表 [回复]
回复SpiritMFC:我给的例子就是源代码了哇,那时候研究到那里就没有继续往下了。应该是你注入之后挂钩写的不对导致的。

你指的全局钩子是什么概念?全局消息钩子么?目前我知道的是这个可以实现指定进程的挂钩。
3楼 lsssml1990 2012-10-26 12:22发表 [回复]
这个代码可以直接用不?
2楼 baggiowangyu 2012-06-23 14:02发表 [回复]
一同学习,一同学习
1楼 Wentasy 2012-06-19 11:46发表 [回复]
不错,学习了。