您的位置:首页 > 其它

9种枚举枚举进程的方法及实现

2010-07-02 20:56 295 查看
//

//native api获得进程表
NTSTATUS NativeApiEnumProcess()
{
ULONG pNeededSize=0;
int iCount = 1;
int bOver=0;
NTSTATUS status;
ULONG uSize;
PVOID pSi=NULL;
PSYSTEM_PROCESS_INFORMATION pSpiNext = NULL;
uSize=0x8000;
pSi=ExAllocatePoolWithTag(PagedPool,uSize,'pro1');
if (pSi!=NULL)
{
status=ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,pSi,uSize,&pNeededSize);
DbgPrint("[NativeApi] SUCCESS uSize = %.8X, pNeededSize = %.8X, status = %.8X/n", uSize, pNeededSize, status);
uSize=pNeededSize;
status=ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,pSi,uSize,&pNeededSize);
if (STATUS_SUCCESS==status)
{
pSpiNext=(PSYSTEM_PROCESS_INFORMATION) pSi;
while (TRUE)
{
PEPROCESS Pepr=NULL;
if (pSpiNext->ProcessId==0)
{
KdPrint(("[NativeApi] %d - System Idle Process/n",pSpiNext->ProcessId));
if (PsLookupProcessByProcessId((HANDLE)(pSpiNext->ProcessId),&Pepr)==STATUS_SUCCESS)
{
DbgPrint("%.8X/n",Pepr);
FindAndCheckProcess(TRUE,(ULONG)Pepr);
}

}
else
{
KdPrint(("[NativeApi] %d - %wZ/n",pSpiNext->ProcessId,&pSpiNext->ImageName));

if (pSpiNext->ProcessId)
{
if (PsLookupProcessByProcessId((HANDLE)(pSpiNext->ProcessId),&Pepr)==STATUS_SUCCESS)
{
DbgPrint("%.8X/n",Pepr);
FindAndCheckProcess(TRUE,(ULONG)Pepr);
}

}

iCount++;
}
if (pSpiNext->NextEntryOffset==0)
{
KdPrint(("[NativeApi] EnumProcess Over, Count is: %d/n"),iCount);
bOver=1;
break;
}
pSpiNext = (PSYSTEM_PROCESS_INFORMATION)((ULONG)pSpiNext + pSpiNext->NextEntryOffset);

}
ExFreePoolWithTag(pSi,'pro1');

}
else
{
DbgPrint("[NativeApi] SUCCESS uSize = %.8X, pNeededSize = %.8X, status = %.8X/n", uSize, pNeededSize, status);
return STATUS_UNSUCCESSFUL;
}

}
return STATUS_SUCCESS;

}
//通过进程的ActiveProcessLinks枚举进程+
/************************************************************************/
/* lkd> dt nt!_EPROCESS 89a36da0
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER 0x1cb16ad`6e0b0d84
+0x078 ExitTime : _LARGE_INTEGER 0x0
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : 0x00000344
+0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8993a658 - 0x894e59d8 ] */
/************************************************************************/
NTSTATUS ActivelistEnumProcess()
{

PEPROCESS PCurentProcess=IoGetCurrentProcess();
DbgPrint("%.8X/n",PCurentProcess);
PEPROCESS NextErprocess=NULL;
LIST_ENTRY *Entry=NULL;
LDR_DATA_TABLE_ENTRY *DataTableEntry = NULL;
//IDLE并不在该链表中的
Entry=(LIST_ENTRY*)((ULONG)PCurentProcess+ActivelistOffset);

do
{

NextErprocess =(PEPROCESS)((ULONG)(*(PULONG)((ULONG)PCurentProcess+ActivelistOffset))-ActivelistOffset);
DbgPrint("%.8X/n",NextErprocess);
if (NextErprocess!=NULL)
{
KdPrint(("[Activelist] %d - %s/n",*(PULONG)((ULONG)NextErprocess+ProcessIdOffset),((ULONG)NextErprocess+ImageNameOffset)));
FindAndCheckProcess(FALSE,(ULONG)NextErprocess);
}

Entry=Entry->Blink;
PCurentProcess=NextErprocess;

}while(Entry != ((LIST_ENTRY*)((ULONG)PCurentProcess+ActivelistOffset)));
return STATUS_SUCCESS;
}

//得到CidHandleAddr
ULONG GetCidTableAddr()
{

ULONG pCidHandleAddr;

__asm{
mov eax,fs:[0x34]////FS指向_KPCR// +0x034 KdVersionBlock : Ptr32 Void
mov eax,[eax+0x80]
mov eax,[eax]
mov pCidHandleAddr,eax
}
return pCidHandleAddr;
}

NTSTATUS PspCidTableProcess()
{
ULONG pCidTableAddr;
pCidTableAddr=GetCidTableAddr();

PHANDLE_TABLE pCidHandleTable = NULL;
PHANDLE_TABLE_ENTRY pTable1, *pTable2, **pTable3;
pCidHandleTable=(PHANDLE_TABLE)(PULONG)(pCidTableAddr);
ULONG leverl=(pCidHandleTable->TableCode)&0x3;
//低二位是作为标志的
ULONG PRealHandleTable=(pCidHandleTable->TableCode)& ~0x3;
ULONG MaxHandle=pCidHandleTable->NextHandleNeedingPool;
DbgPrint("leverl %d/n",leverl);
__asm int 3
switch(leverl)
{

case HANDLE_LAYER1:
pTable1=(PHANDLE_TABLE_ENTRY)PRealHandleTable;
for (ULONG index=0;index<MAX_ENT_CNT; index++ )
{
if (pTable1[index].Object!=NULL)
{
ULONG pObject=(ULONG)(pTable1[index].Object) & ~ 0x7;////低三位标志
if (MmIsAddressValid((PULONG)(pObject - 0x10)))
{
POBJECT_TYPE pType = (POBJECT_TYPE)(*(PULONG)(pObject - 0x10));
//+0x018 Body : _QUAD
//+0x008 Type : 0x89b4ead0 _OBJECT_TYPE
if( pType == *PsProcessType )
{
DbgPrint("PId:%d/tPath:%s/n", index*4, (PUCHAR)(pObject+ImageNameOffset) );
FindAndCheckProcess(FALSE,(ULONG)pObject);
}
}
}
}
break;
case HANDLE_LAYER2:
pTable2=(PHANDLE_TABLE_ENTRY *)PRealHandleTable;
//二级表中有几个一级表呢?假如uMax_Handle=0x800,索引时以4步进的,一个一级表最大的句柄数就是MAX_ENT_CNT
for (ULONG index=0;index<MaxHandle/(4*MAX_ENT_CNT);index++)//index为几个一级表
{
pTable1 = pTable2[index];
if (pTable1==NULL)
{
break;
}
else
{
for (ULONG i=0;i<MAX_ENT_CNT; i++)//一级表的最大句柄数就是MAX_ENT_CNT
{
if (pTable1[i].Object!=NULL)
{
ULONG pObject = (ULONG)(pTable1[i].Object) & ~7;
if( MmIsAddressValid( (PULONG)(pObject-0x10) ) )
{
POBJECT_TYPE pType = (POBJECT_TYPE)(*(PULONG)(pObject-0x10));
if( pType == *PsProcessType )
{
DbgPrint("PId:%d/tPath:%s/n", index*MAX_ENT_CNT*4+i*4, (PUCHAR)(pObject+ImageNameOffset) );
FindAndCheckProcess(FALSE,(ULONG)pObject);
}
}
}

}
}

}
break;
case HANDLE_LAYER3:
ULONG index = 0;
//从第三级找到第二级,在从第二级找到第一级
pTable3 = (PHANDLE_TABLE_ENTRY**)(PRealHandleTable);
for( index = 0; index < MaxHandle/(MAX_ADD_CNT*MAX_ENT_CNT*4); index++ )
{
ULONG i = 0;
pTable2 = (PHANDLE_TABLE_ENTRY*)((ULONG)pTable3[index] & ~0x3);
if( pTable2 == NULL )
break;
for( i = 0; i < MAX_ADD_CNT; i++ )// 二级表都是指针。所以一个表有0x1000/4(一个ULONG指针的大小)
{

pTable1 = pTable2[i];
if( pTable1 == NULL )
break;
else
{
ULONG j = 0;
for( j = 0; j < MAX_ENT_CNT; j++ )
{
if( pTable1[j].Object != NULL )
{
ULONG pObject = (ULONG)(pTable1[j].Object) & ~7;
if( MmIsAddressValid( (PULONG)(pObject-0x10) ) )
{
POBJECT_TYPE pType = (POBJECT_TYPE)(*(PULONG)(pObject-0x10));
if( pType == *PsProcessType )
{
DbgPrint("PId:%d/tPath:%s/n", index*MAX_ADD_CNT*MAX_ENT_CNT*4+i*MAX_ENT_CNT*4+j*4,/
(PUCHAR)(pObject+ImageNameOffset) );
FindAndCheckProcess(FALSE,(ULONG)pObject);
}
}
}

}

}
}

}

break;
}
return STATUS_SUCCESS;

}
//通过Handletablelisthead枚举进程
/************************************************************************/
/* 要找到HeadleTableListHead,我们要注意到HandleTableListHead是一个全局的内核变量,因此它一定是在内核文件的某一个段

(Section)里面,并且HandleTableList的其他成员是在动态分配的内存中,所以总是受到内核地址空间的限制。根据这些,
我们需要得到任

何一个进程的HandleTable的指针,然后遍历链表直到找到定位在这个内核地址空间的成员,那么这个成员就是HandleTableListHead了。
*/
/************************************************************************/
NTSTATUS HandletablelistheadEnumProcess()
{
PLIST_ENTRY HanTableListHead=NULL;
NTSTATUS nResult;
PLIST_ENTRY CurrTable=NULL;
PEPROCESS PEprocess=NULL;
PLIST_ENTRY HandleTableList=NULL;
ULONG ulNeededSize, uLoop, uKernelSta,NtoskrnlLast;
PMODULE_LIST pModuleList=NULL;
//获取内核模块基址
ZwQuerySystemInformation(SystemModuleInformation, &ulNeededSize, 0, &ulNeededSize);
pModuleList = (PMODULE_LIST)ExAllocatePool(NonPagedPool, ulNeededSize);
nResult = ZwQuerySystemInformation(SystemModuleInformation, pModuleList, ulNeededSize, NULL);
if (NT_SUCCESS(nResult))
{//ntoskrnl is always first there
uKernelSta =(ULONG) pModuleList->a_Modules[0].p_Base;
NtoskrnlLast=(ULONG) pModuleList->a_Modules[0].p_Base+(ULONG) pModuleList->a_Modules[0].d_Size;
}
ExFreePool(pModuleList);
//获取Handletablelisthead地址
PHANDLE_TABLE HandleTable=*(PHANDLE_TABLE *)((ULONG)PsGetCurrentProcess() + HandleTableOffset);
HandleTableList = (PLIST_ENTRY)((ULONG)HandleTable + HandleTableListOffset);
for (CurrTable=HandleTableList->Flink;CurrTable!=HandleTableList;CurrTable=CurrTable->Flink)
{
if ((ULONG)CurrTable>uKernelSta && (ULONG)CurrTable<NtoskrnlLast)
{
HanTableListHead=CurrTable;
break;
}

}
__asm int 3
//开始枚举
for (CurrTable = HanTableListHead->Flink;
CurrTable != HanTableListHead;
CurrTable = CurrTable->Flink)
{
PEprocess = *(PEPROCESS *)((PUCHAR)CurrTable - HandleTableListOffset + QuotaProcessOffset);
if (PEprocess)
{
KdPrint(("[Handletablelisthead] %d - %s/n",*(PULONG)((ULONG)PEprocess+ProcessIdOffset),(PUCHAR)((ULONG)PEprocess+ImageNameOffset)));
FindAndCheckProcess(FALSE,(ULONG)PEprocess);
}
}

return STATUS_SUCCESS;
}

//从csrss进程中枚举进程,这个是个子系统进程,包含记录了当前的进程

NTSTATUS WalkCsrssEnumProcess()
{
PEPROCESS CurentPro=IoGetCurrentProcess();

DbgPrint("%.8X/n",CurentPro);
__asm int 3
PEPROCESS pTempPro=CurentPro;
PEPROCESS pEProcess = NULL;

NTSTATUS Status = STATUS_SUCCESS;
PHANDLE_TABLE pObjectTable = NULL;
PHANDLE_TABLE_ENTRY table1, *table2, **table3;
ULONG level;
ULONG pRealHandleTable;
ULONG uHandleCount;

//获取csrss的PEPROCESS
/************************************************************************/
/* lkd> dd 8992ada0+0X88
8992ae28 898d0c18 89acad28 000016d0 00024ac4
8992ae38 000001d0 000019a0 000254ec 00000396
dd PEPROCESS=898d0c18-0x88
*/
/************************************************************************/
do
{
DbgPrint("name is %s/n",(PCHAR)((ULONG)pTempPro+ImageNameOffset));
// __asm int 3
if (strcmp((PCHAR)((ULONG)pTempPro+ImageNameOffset),"csrss.exe")==0)
{

/*return pTempPro;*/
//开始枚举
pEProcess=pTempPro;

pObjectTable = (PHANDLE_TABLE)(*(PULONG)((ULONG)pEProcess + HandleTableOffset));
level = pObjectTable->TableCode & 3;
pRealHandleTable = pObjectTable->TableCode & ~3;
uHandleCount = pObjectTable->NextHandleNeedingPool;
switch( level )
{
case 0:
{
ULONG index = 0;
table1 = (PHANDLE_TABLE_ENTRY)pRealHandleTable;
for( index = 0; index < MAX_ENT_CNT; index++ )
{
ULONG pObject = (ULONG)(table1[index].Object) & ~7;
if( MmIsAddressValid( (PULONG)(pObject+0x8) ) )
{
POBJECT_TYPE pType = (POBJECT_TYPE)(*(PULONG)(pObject+0x8));
if( pType == *PsProcessType )
{
PEPROCESS pAddr = (PEPROCESS)(pObject+0x18);
DbgPrint("PId:%d/tPath:%s/n", *(PULONG)((ULONG)pAddr+ProcessIdOffset), (PUCHAR)((ULONG)pAddr+ImageNameOffset) );
FindAndCheckProcess(FALSE,(ULONG)pAddr);
}
}
}
break;

}
case 1:
{
ULONG index = 0;
table2 = (PHANDLE_TABLE_ENTRY*)pRealHandleTable;
for( index = 0; index < uHandleCount/(4*MAX_ENT_CNT); index++ )
{
ULONG i = 0;
table1 = table2[index];
if( table1 == NULL )
break;
for( i = 0; i < MAX_ENT_CNT; i++ )
{
ULONG pObject = (ULONG)(table1[i].Object) & ~7;
if( MmIsAddressValid( (PULONG)(pObject+0x8) ) )
{
POBJECT_TYPE pType = (POBJECT_TYPE)(*(PULONG)(pObject+0x8));
if( pType == *PsProcessType )
{
ULONG pAddr = (ULONG)(pObject+0x18);
DbgPrint("PId:%d/tPath:%s/n", *(PULONG)(pAddr+ProcessIdOffset), (PUCHAR)(pAddr+ImageNameOffset) );
FindAndCheckProcess(FALSE,(ULONG)pAddr);
}
}

}

}
break;
}
case 2:
{
ULONG index = 0;
table3 = (PHANDLE_TABLE_ENTRY**)pRealHandleTable;
for( index = 0; index < uHandleCount/(4*MAX_ENT_CNT*MAX_ADD_CNT); index++ )
{
ULONG i = 0;
table2 = (PHANDLE_TABLE_ENTRY*)((ULONG)table3[index] & ~0x3);
if( table2 == NULL )
break;
for( i = 0; i < MAX_ADD_CNT; i++ )
{
ULONG j = 0;
table1 = table2[i];
if( table1 == NULL )
break;
for( j = 0; j < MAX_ENT_CNT; j++ )
{
ULONG pObject = (ULONG)(table1[j].Object) & ~7;
if( MmIsAddressValid( (PULONG)(pObject+0x8) ) )
{
POBJECT_TYPE pType = (POBJECT_TYPE)(*(PULONG)(pObject+0x8));
if( pType == *PsProcessType )
{
ULONG pAddr = (ULONG)(pObject+0x18);
DbgPrint("PId:%d/tPath:%s/n", *(PULONG)(pAddr+ProcessIdOffset), (PUCHAR)(pAddr+ImageNameOffset) );
FindAndCheckProcess(FALSE,(ULONG)pAddr);
}
}

}
}

}
break;
}
}

//枚举成功
return STATUS_SUCCESS;

}
pTempPro=(PEPROCESS)(*(PULONG)((ULONG)pTempPro+ActivelistOffset)-ActivelistOffset);
} while (CurentPro!=pTempPro);
return STATUS_UNSUCCESSFUL;

}

//通过自身的HANDLETABLE枚举进程
NTSTATUS FromCruentProEnumProcess()
{

PEPROCESS PCurrentProc=IoGetCurrentProcess();
DbgPrint("%.8X/n",PCurrentProc);
PLIST_ENTRY CurrList=NULL;
PEPROCESS PEprocess,Ptemp=PCurrentProc;
ULONG Phandle;
PLIST_ENTRY PCurrentHandleList=NULL;
//__asm int 3
do
{
Phandle=(ULONG)(*(PULONG)((ULONG)PCurrentProc+HandleTableOffset));
DbgPrint("Phandle %.8X/n",Phandle);
PCurrentHandleList=(PLIST_ENTRY)(Phandle+HandleTableListOffset);
CurrList=PCurrentHandleList->Flink;
DbgPrint("Phandle %.8X/n",CurrList);//E1000CEC

// __asm int 3
PEprocess = *(PEPROCESS *)((PUCHAR)CurrList- HandleTableListOffset + QuotaProcessOffset);
if (PEprocess)
{
KdPrint(("[FromCruentPro] %d - %s/n",*(PULONG)((ULONG)PEprocess+ProcessIdOffset),(PUCHAR)((ULONG)PEprocess+ImageNameOffset)));
FindAndCheckProcess(FALSE,(ULONG)PEprocess);
}
else
{
return STATUS_UNSUCCESSFUL;
}
//ULONG Phandle=(ULONG)(*(PULONG)((ULONG)PCurrentProc+HandleTableOffset));
PCurrentProc=PEprocess;

} while (PCurrentProc!=Ptemp);

return STATUS_SUCCESS;

}

//进程的SessionProcessLinks获得进程表
NTSTATUS SessionProcessLinksEnumProcess()
{
PEPROCESS PCur=IoGetCurrentProcess();
PEPROCESS Ptemp=PCur;
//取system下一个的进程
__asm int 3
DbgPrint("%.8X/n",Ptemp);
// Ptemp=(PEPROCESS)((ULONG)(*(PULONG)((ULONG)PCur+ActivelistOffset))-ActivelistOffset);
Ptemp = (PEPROCESS)( *(PULONG)(*(PULONG)((ULONG)Ptemp + ActivelistOffset+4) + 0x4) - ActivelistOffset );//取用户进程
DbgPrint("%.8X/n",Ptemp);
PCur=Ptemp;
do
{
if (MmIsAddressValid(Ptemp))
{
KdPrint(("[SessionProcessLinks] %d - %s/n",*(PULONG)((ULONG)Ptemp+ProcessIdOffset),(PUCHAR)((ULONG)Ptemp+ImageNameOffset)));
FindAndCheckProcess(FALSE,(ULONG)Ptemp);
Ptemp=(PEPROCESS)(*(PULONG)((ULONG)Ptemp+SessionProcessLinksOffset)-SessionProcessLinksOffset);

}
else
{
break;
}

} while (PCur!=Ptemp);

return STATUS_SUCCESS;
}

//通过EPROCESS ---VM---WorkingSetExpansionLinks获取进程
NTSTATUS WorkingSetExpansionLinksEnumProcess()
{

PEPROCESS Ptemp,PCur=IoGetCurrentProcess();
Ptemp=PCur;
__asm int 3
do
{
__try{
if (MmIsAddressValid(Ptemp)==FALSE)
{
break;
}
KdPrint(("[WorkingSetExpansionLinks] %d - %s/n",*(PULONG)((ULONG)Ptemp+ProcessIdOffset),(PUCHAR)((ULONG)Ptemp+ImageNameOffset)));
FindAndCheckProcess(FALSE,(ULONG)Ptemp);
Ptemp=(PEPROCESS)(*(PULONG)((ULONG)Ptemp+WorkingSetExpansionLinksOffset)-WorkingSetExpansionLinksOffset);
if (!Ptemp)
{
break;
}

}
__except(EXCEPTION_EXECUTE_HANDLER)
{
break;
}

} while (Ptemp!=PCur);
return STATUS_SUCCESS;
}

//从TypeList中枚举进程,需要维持一个标志,而且要重启后生效,故....
NTSTATUS TypeListEnumProcess()
{

return STATUS_SUCCESS;
}
/************************************************************************/
/* lkd> !object 88aa84b8//PEROCESS
Object: 88aa84b8 Type: (89b52ca0) Process
ObjectHeader: 88aa84a0 (old version)
HandleCount: 7 PointerCount: 289
lkd> dt nt!_OBJECT_HEADER 88aa84a0
+0x000 PointerCount : 289
+0x004 HandleCount : 7
+0x004 NextToFree : 0x00000007
+0x008 Type : 0x89b52ca0 _OBJECT_TYPE
+0x00c NameInfoOffset : 0 ''
+0x00d HandleInfoOffset : 0 ''
+0x00e QuotaInfoOffset : 0 ''
+0x00f Flags : 0x20 ' '
+0x010 ObjectCreateInfo : 0x8990e3b0 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x8990e3b0
+0x014 SecurityDescriptor : 0xe1fbedf9
+0x018 Body : _QUAD
*/
/************************************************************************/
BOOLEAN IsRealProcess(ULONG uAddr)
{
ULONG uType = 0;
ULONG pObjectTypeAddr = 0;
if (IsValidAddr(uAddr)!=VALID_PAGE)
{
return FALSE;
}
pObjectTypeAddr=uAddr-ObjectHeaderSize+TypeHeaderOffset;
if (IsValidAddr(pObjectTypeAddr)!=VALID_PAGE)
{
return FALSE;
}
else
{
ULONG pObjectType=*(PULONG)pObjectTypeAddr;
if (pObjectType==*(PULONG)PsProcessType)
{
return TRUE;
}
}
return FALSE;

}
//暴力搜索内存MmSystemRangeStart以上查找PROCESS对象
NTSTATUS SerachMemoryEnumProcess()
{
ULONG uStartAddr;
__asm{
mov eax,MmSystemRangeStart
mov eax,[eax]
mov uStartAddr,eax
}
PEPROCESS PCurrent=IoGetCurrentProcess();
KdPrint(("[SerachMemory] %d - %s/n",*(PULONG)((ULONG)PCurrent+ProcessIdOffset),(PUCHAR)((ULONG)PCurrent+ImageNameOffset)));
for (;uStartAddr<(ULONG)PCurrent+0x800000;uStartAddr+=4)
{
ULONG bRet;
bRet=IsValidAddr( uStartAddr );
if (bRet==VALID_PAGE)
{
if ((*(PULONG)uStartAddr & 0xffff0000)==PEBFlags)//这里定位到的是PEB
{
if (IsRealProcess(uStartAddr-PEBOffset))
{
__try{

KdPrint(("[SerachMemory] %d - %s/n",*(PULONG)(uStartAddr-PEBOffset+ProcessIdOffset),(PUCHAR)(uStartAddr-PEBOffset+ImageNameOffset)));
if (*(PULONG)(uStartAddr-PEBOffset+ExitTimeOffset)!=0)
{
//已经推出了
}
else
{
FindAndCheckProcess(FALSE,(ULONG)(uStartAddr-PEBOffset));
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{

}

}
uStartAddr -= 4;
uStartAddr += 0x25c;

}
}
else if( bRet == PDEINVALID )
{
uStartAddr -= 4;
uStartAddr += 0x400000;
}
else
{
uStartAddr -= 4;
uStartAddr += 0x1000;
}

}

}

//剩下的还有通过xp下的2个线程调度表来枚举,不过有些太不稳定了,忽略。。

现在隐藏进程都好难了,呜呜呜

努力努力在努力
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: