探索windows 内核的各位都来看看---深入PE内部.....

zhaorong 2001-07-10 06:08:56
俺由于多方面的原因,要彻底的搞清PE的格式,在查了msdn和侯老的windows 95 系统程序设计大奥秘后仍然有许多不解之处,请大家多多发言。
相信大家都知道API的截获和屏幕取词的原理都是相同的,都是截获几个内核dll如 KERNEL32.dll gdi32.dll user.dll等等。。
周天舒对次有很深的研究,他本人共享了他的winnt下的屏幕取词源代码,这里表示对他的感谢!但为何在win98下不行呢?
IMAGE_IMPORT_DESCRIPTOR和IMAGE_THUNK_DATA分别对应于DLL和函数。
它们是PE文件的输入地址表的格式,反正只要这样做就好啦:
BOOL ChangeFuncEntry(HMODULE hmodule)
{
PIMAGE_DOS_HEADER pDOSHeader;
PIMAGE_NT_HEADERS pNTHeader;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;

/*get system functions and my functions' entry*/
pSysFunc1=(DWORD)GetProcAddress(GetModuleHandle("gdi32.dll"),"TextOutA");
pMyFunc1= (DWORD)GetProcAddress(GetModuleHandle("hookdll.dll"),"MyTextOutA");

pDOSHeader=(PIMAGE_DOS_HEADER)hmodule;
if (IsBadReadPtr(hmodule, sizeof(PIMAGE_NT_HEADERS)))
return FALSE;
if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;

pNTHeader=(PIMAGE_NT_HEADERS)((DWORD)pDOSHeader+
(DWORD)pDOSHeader->e_lfanew);
if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
return FALSE;

pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hmodule+
(DWORD)pNTHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
if (pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pNTHeader)
return FALSE;

while (pImportDesc->Name)
{
PIMAGE_THUNK_DATA pThunk;
strcpy(buffer,(char*)((DWORD)hmodule+(DWORD)pImportDesc->Name));
CharLower(buffer);
if(strcmp(buffer,"gdi32.dll"))
{
pImportDesc++;
continue;
}
else
{
pThunk=(PIMAGE_THUNK_DATA)((DWORD)hmodule+(DWORD)pImportDesc->FirstThunk);
while (pThunk->u1.Function)
{
if ((pThunk->u1.Function) == pSysFunc1)
{
VirtualProtect((LPVOID)(&pThunk->u1.Function),
sizeof(DWORD),PAGE_EXECUTE_READWRITE, &dwProtect);
(pThunk->u1.Function)=pMyFunc1;
VirtualProtect((LPVOID)(&pThunk->u1.Function), sizeof(DWORD),dwProtect,&temp);
}
pThunk++;
}
return 1;
}
}
}

为何不行呢?PE格式在win98和winnt的内存的布局有何不同?
请各位发表意见,不要拘束!
...全文
255 22 打赏 收藏 转发到动态 举报
写回复
用AI写文章
22 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhoutianshu 2001-07-13
  • 打赏
  • 举报
回复
天鱼:
你说的和天天说的不是一回事吧?

我是修改了所有模块的import table啊,我是用SetWindowsHookEx来设立全局钩子(我安装的是一个鼠标消息的钩子),所以不存在只修改了自己的模块的import这种说法。
hz1101 2001-07-13
  • 打赏
  • 举报
回复
各位都是高手啊!
mty 2001-07-13
  • 打赏
  • 举报
回复
to plato(天天):
我是修改了所有模块的import table啊,我是用SetWindowsHookEx来设立全局钩子(我安装的是一个鼠标消息的钩子),所以不存在只修改了自己的模块的import这种说法。
另外重申一点,HOOK api函数能成功,只不过是不能实现屏幕取词(翻译功能)了。
to zhaorong(心境如水):
对于这个你可以分两步来做,第一步,Hook本进程的API函数,第二步,Hook其他进程的API函数(这就要设置全局钩子了)
plato 2001-07-12
  • 打赏
  • 举报
回复
mty, 你是hook了你自己的EXE调用user32, kernel32, gdi32.

但是,你没有hook到user32调用gdi32。

user32里面的EDIT重画要用到gdi32,这就是你为什么没有截获的原因。

如果要修改import,则要修改每个模块的import,不光是你自己的EXE,包括你的EXE调用的所有的DLL以及DLL调用的DLL...的import table
plato 2001-07-12
  • 打赏
  • 举报
回复
mty,怎么做的?关注
mty 2001-07-12
  • 打赏
  • 举报
回复
to verybigbug
我在9x下时基本上把所有的gdi32.dll和user32.dll中相关函数都hook了,不过还是不太使
Kevin_qing 2001-07-12
  • 打赏
  • 举报
回复
gz
verybigbug 2001-07-12
  • 打赏
  • 举报
回复
To:zhaorong

你的清单说明不了什么。
你用MFC生产的东西调用mfc42.dll,然后mfc42.dll中可能调用GDI32和user32的。
另外,一般来讲:你的程序种没有调用textout等函数,光用CreateWindow,SetWindowText
等函数的话,你是用不到gdi32的函数的。在画面上显示的东西大都为USER32.DLL中
调用了GDI32的函数。所以,用Hook IAT的技术的话,必须Hook到user32中才行。
在9X下没办法的,所以,不能用该技术实现。
???为什么都喜欢用Hook Api的技术去实现屏幕取词呢????
我就喜欢用Hook Api的技术去调查好程序用了哪些函数实现的。
herlik 2001-07-12
  • 打赏
  • 举报
回复
我找到一个可用的pe程序 网址是
http://www.my169.com/~vc/mfc/misc/winpe.shtml.htm
不过只能看模块中.text,.data,.idata,.rsrc(这块里放置了resource例如图标、菜单、位图等等),.reloc,.edata,.tls,.rdata, 等东东 不过 好象不能改 我也很想知道原因啊!!
greensleeve 2001-07-12
  • 打赏
  • 举报
回复
perfect
zhaorong 2001-07-12
  • 打赏
  • 举报
回复
to :mty(天鱼)
我和你也有同感,我用vc向导生成一个应用程序,用pedump查看它的资源,结果看不到gdi32.dll模块,以下是清单,
File Header
Machine: 014C (i386)
Number of Sections: 0006
TimeDateStamp: 3B4AB076
PointerToSymbolTable: 00000000
NumberOfSymbols: 00000000
SizeOfOptionalHeader: 00E0
Characteristics: 010E
EXECUTABLE_IMAGE
LINE_NUMS_STRIPPED
LOCAL_SYMS_STRIPPED
32BIT_MACHINE

Optional Header
Magic 010B
linker version 6.00
size of code 14000
size of initialized data 5000
size of uninitialized data 0
entrypoint RVA 1F40
base of code 1000
base of data 1000
image base 400000
section align 1000
file align 1000
required OS version 4.00
image version 0.00
subsystem version 4.00
Reserved1 0
size of image 1A000
size of headers 1000
checksum 0
Subsystem 0002 (Windows GUI)
stack reserve size 100000
stack commit size 1000
heap reserve size 100000
heap commit size 1000
RVAs & sizes 10

Data Directory
EXPORT rva: 00000000 size: 00000000
IMPORT rva: 00017000 size: 00000078
RESOURCE rva: 00018000 size: 00000EC0
EXCEPTION rva: 00000000 size: 00000000
SECURITY rva: 00000000 size: 00000000
BASERELOC rva: 00019000 size: 000002F8
DEBUG rva: 00015000 size: 0000001C
COPYRIGHT rva: 00000000 size: 00000000
GLOBALPTR rva: 00000000 size: 00000000
TLS rva: 00000000 size: 00000000
LOAD_CONFIG rva: 00000000 size: 00000000
BOUND_IMPORT rva: 00000000 size: 00000000
IAT rva: 000173C0 size: 00000348
unused rva: 00000000 size: 00000000
unused rva: 00000000 size: 00000000
unused rva: 00000000 size: 00000000

Section Table
01 .text VirtSize: 000134D0 VirtAddr: 00001000
raw data offs: 00001000 raw data size: 00014000
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 60000020
CODE MEM_EXECUTE MEM_READ

02 .rdata VirtSize: 0000077B VirtAddr: 00015000
raw data offs: 00015000 raw data size: 00001000
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 40000040
INITIALIZED_DATA MEM_READ

03 .data VirtSize: 000008D4 VirtAddr: 00016000
raw data offs: 00016000 raw data size: 00001000
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: C0000040
INITIALIZED_DATA MEM_READ MEM_WRITE

04 .idata VirtSize: 000009A1 VirtAddr: 00017000
raw data offs: 00017000 raw data size: 00001000
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: C0000040
INITIALIZED_DATA MEM_READ MEM_WRITE

05 .rsrc VirtSize: 00000EC0 VirtAddr: 00018000
raw data offs: 00018000 raw data size: 00001000
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 40000040
INITIALIZED_DATA MEM_READ

06 .reloc VirtSize: 000004A5 VirtAddr: 00019000
raw data offs: 00019000 raw data size: 00001000
relocation offs: 00000000 relocations: 00000000
line # offs: 00000000 line #'s: 00000000
characteristics: 42000040
INITIALIZED_DATA MEM_DISCARDABLE MEM_READ


Debug Formats in File
Type Size Address FilePtr Charactr TimeData Version
--------------- -------- -------- -------- -------- -------- --------
CODEVIEW 0000002E 00000000 0001A000 00000000 3B4AB076 0.00

Resources
ResDir (0) Named:00 ID:05 TimeDate:00000000 Vers:0.00 Char:0
ResDir (ICON) Named:00 ID:02 TimeDate:00000000 Vers:0.00 Char:0
ResDir (1) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ID: 00000804 DataEntryOffs: 00000168
Offset: 18340 Size: 002E8 CodePage: 0
ResDir (2) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ID: 00000804 DataEntryOffs: 00000178
Offset: 18628 Size: 00128 CodePage: 0
ResDir (DIALOG) Named:00 ID:02 TimeDate:00000000 Vers:0.00 Char:0
ResDir (64) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ID: 00000804 DataEntryOffs: 00000188
Offset: 18778 Size: 000C6 CodePage: 0
ResDir (66) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ID: 00000804 DataEntryOffs: 00000198
Offset: 18840 Size: 000BC CodePage: 0
ResDir (STRING) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ResDir (7) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ID: 00000804 DataEntryOffs: 000001A8
Offset: 18BC0 Size: 0003A CodePage: 0
ResDir (GROUP_ICON) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ResDir (80) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ID: 00000804 DataEntryOffs: 000001B8
Offset: 18750 Size: 00022 CodePage: 0
ResDir (VERSION) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ResDir (1) Named:00 ID:01 TimeDate:00000000 Vers:0.00 Char:0
ID: 00000804 DataEntryOffs: 000001C8
Offset: 18900 Size: 002C0 CodePage: 0

Imports Table:
MFC42D.DLL
Hint/Name Table: 000170AC
TimeDateStamp: 00000000
ForwarderChain: 00000000
First thunk RVA: 000173F4
Ordn Name
1880 (IAT: 80000758)
1860 (IAT: 80000744)
4415 (IAT: 8000113F)
3231 (IAT: 80000C9F)
3702 (IAT: 80000E76)
1033 (IAT: 80000409)
4130 (IAT: 80001022)
1789 (IAT: 800006FD)
2661 (IAT: 80000A65)
4227 (IAT: 80001083)
4229 (IAT: 80001085)
2104 (IAT: 80000838)
3366 (IAT: 80000D26)
3826 (IAT: 80000EF2)
4239 (IAT: 8000108F)
4215 (IAT: 80001077)
4408 (IAT: 80001138)
2340 (IAT: 80000924)
2481 (IAT: 800009B1)
2584 (IAT: 80000A18)
3691 (IAT: 80000E6B)
2473 (IAT: 800009A9)
2585 (IAT: 80000A19)
2341 (IAT: 80000925)
2432 (IAT: 80000980)
2339 (IAT: 80000923)
3143 (IAT: 80000C47)
3144 (IAT: 80000C48)
3142 (IAT: 80000C46)
2431 (IAT: 8000097F)
3367 (IAT: 80000D27)
3784 (IAT: 80000EC8)
3657 (IAT: 80000E49)
2021 (IAT: 800007E5)
1285 (IAT: 80000505)
4492 (IAT: 8000118C)
2986 (IAT: 80000BAA)
528 (IAT: 80000210)
728 (IAT: 800002D8)
706 (IAT: 800002C2)
1862 (IAT: 80000746)
2052 (IAT: 80000804)
574 (IAT: 8000023E)
4195 (IAT: 80001063)
3629 (IAT: 80000E2D)
3948 (IAT: 80000F6C)
4017 (IAT: 80000FB1)
3831 (IAT: 80000EF7)
4753 (IAT: 80001291)
3362 (IAT: 80000D22)
1364 (IAT: 80000554)
3552 (IAT: 80000DE0)
5077 (IAT: 800013D5)
1781 (IAT: 800006F5)
4118 (IAT: 80001016)
5076 (IAT: 800013D4)
3618 (IAT: 80000E22)
4208 (IAT: 80001070)
2078 (IAT: 8000081E)
1310 (IAT: 8000051E)
3069 (IAT: 80000BFD)
3944 (IAT: 80000F68)
3670 (IAT: 80000E56)
2076 (IAT: 8000081C)
1566 (IAT: 8000061E)
3803 (IAT: 80000EDB)
3002 (IAT: 80000BBA)
4064 (IAT: 80000FE0)
1344 (IAT: 80000540)
4191 (IAT: 8000105F)
1830 (IAT: 80000726)
1631 (IAT: 8000065F)
4205 (IAT: 8000106D)
3786 (IAT: 80000ECA)
3658 (IAT: 80000E4A)
1952 (IAT: 800007A0)
1228 (IAT: 800004CC)
2875 (IAT: 80000B3B)
317 (IAT: 8000013D)
1857 (IAT: 80000741)
3524 (IAT: 80000DC4)
3432 (IAT: 80000D68)
1087 (IAT: 8000043F)
4676 (IAT: 80001244)
684 (IAT: 800002AC)
880 (IAT: 80000370)
1212 (IAT: 800004BC)
3355 (IAT: 80000D1B)
3447 (IAT: 80000D77)
492 (IAT: 800001EC)
3070 (IAT: 80000BFE)
4053 (IAT: 80000FD5)
3960 (IAT: 80000F78)
646 (IAT: 80000286)
1906 (IAT: 80000772)
3201 (IAT: 80000C81)
5072 (IAT: 800013D0)
2324 (IAT: 80000914)
454 (IAT: 800001C6)
4475 (IAT: 8000117B)
2993 (IAT: 80000BB1)
413 (IAT: 8000019D)
3365 (IAT: 80000D25)
4176 (IAT: 80001050)
3651 (IAT: 80000E43)
5078 (IAT: 800013D6)
1041 (IAT: 80000411)
1100 (IAT: 8000044C)
1190 (IAT: 800004A6)

MSVCRTD.dll
Hint/Name Table: 00017310
TimeDateStamp: 00000000
ForwarderChain: 00000000
First thunk RVA: 00017658
Ordn Name
301 _initterm (IAT: 0001778E)
155 __setusermatherr (IAT: 0001779A)
181 _adjust_fdiv (IAT: 000177AE)
127 __p__commode (IAT: 000177BE)
109 __getmainargs (IAT: 0001777E)
230 _except_handler3 (IAT: 000177EE)
618 exit (IAT: 0001776C)
167 _acmdln (IAT: 00017774)
422 _onexit (IAT: 0001774C)
208 _controlfp (IAT: 00017802)
106 __dllonexit (IAT: 0001773E)
94 __CxxFrameHandler (IAT: 0001771E)
200 _chkesp (IAT: 00017714)
93 _XcptFilter (IAT: 0001775E)
239 _exit (IAT: 00017756)
135 __p__fmode (IAT: 000177CE)
153 __set_app_type (IAT: 000177DC)
459 _setmbcp (IAT: 00017870)

KERNEL32.dll
Hint/Name Table: 00017078
TimeDateStamp: 00000000
ForwarderChain: 00000000
First thunk RVA: 000173C0
Ordn Name
294 GetModuleHandleA (IAT: 00017810)
336 GetStartupInfoA (IAT: 00017824)

USER32.dll
Hint/Name Table: 00017390
TimeDateStamp: 00000000
ForwarderChain: 00000000
First thunk RVA: 000176D8
Ordn Name
326 GetSystemMetrics (IAT: 00017844)

MFCO42D.DLL
Hint/Name Table: 000172E0
TimeDateStamp: 00000000
ForwarderChain: 00000000
First thunk RVA: 00017628
Ordn Name
798 (IAT: 8000031E)
只有当调用textout(.....)时才调用gdi32.dll
而用delphi建立的程序则不同,无论怎样都调用的gdi32.dll模块
to azuo_lee():那怎样才能修改呢。。。。。。。。。。。。。。。。。。。。。。
请大家各诉己见!!!!!!!!!!



zhaorong 2001-07-12
  • 打赏
  • 举报
回复
请让我叙述一下重点:
1.首先我要做的是如何截获一个正在运行的进程调用的API函数,然后在改变该函数的入口地址
使它指向我自己的函数,也就是别人所说的如何动态改变windows内核。
我知到win98下一定有办法的,象金山词霸,和一些外挂中文汉化软件。
声明:我并不是想做一个类似于金山词霸的软件只是这项技术有很多地方可用。而且我觉得再这项技术中能学到很多东西。
我对advance windows中提到的3种如何向远程进程注入dll还没搞清。
感谢大家。请大家继续
azuo_lee 2001-07-11
  • 打赏
  • 举报
回复
NT下VirtualProtect可以工作,金山词霸就直接改32位GDI;95/98下VirtualProtect无法正常工作,但95/98中大部分的32位GDI函数(至少词霸关心的函数都是)是由对应的16位模块完成的,于是词霸是改了这些16位的模块。
但是95/98下并不是没有32位系统API(>2G)的修改办法,只是这些办法都不理想(不能像16位环境下那么完美),你必须根据不同的情况使用不同的策略。
zhaorong 2001-07-11
  • 打赏
  • 举报
回复
那有办法改写吗?难道非要用vxd才能实现,金山词霸是怎么实现的呢?。。。。。。
mty 2001-07-11
  • 打赏
  • 举报
回复
在NT下我直接用WriteMemory可以解决问题,但98下就要用另一种方法了。在98下,我调试时,发现用WriteMemory根本不行,写进去之后马上就变成以前的内容。后来我用另一种方法,可以Hook windows API(我试过ExtTextOutA),但不能实现屏幕取词了,我怀疑,98/NT的画屏函数不一样,在NT下我发现是(ExtTextOutA, ExtTextOutW, TextOutW---很久了,记不太清了,@-@:P),但98下好象不太行,希望讨论讨论,我的email:mx_feng@163.net

下面是98下hook的部分代码:
BOOL HookAPIByName1(HANDLE hModule/*砆HOOKProcess MODULE*/, LPCSTR szImportMod/*GDI32.DLL*/,LPHOOKAPI pHookApi /*"MessageBoxW"*/)
{
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportMod);

if (pImportDesc == NULL)
{

return FALSE; //惠璶э?APIぃタ谔磞
}
PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->OriginalFirstThunk));
PIMAGE_THUNK_DATA pRealThunk =
(PIMAGE_THUNK_DATA)((DWORD)hModule + (DWORD)(pImportDesc->FirstThunk));
while(pOrigThunk->u1.Function)
{
if((pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)
{
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)hModule + (DWORD)(pOrigThunk->u1.AddressOfData));
if(pByName->Name[0] == '\0')
return FALSE; //Failure
if(strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0)
{
//Modify thunk "Protect"┦
MEMORY_BASIC_INFORMATION mbi_thunk;
VirtualQuery(pRealThunk, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect);
//玂 Old API fuction Poiter
if(pHookApi->pOldProc == NULL)
pHookApi->pOldProc = (DWORD)pRealThunk->u1.Function;
//эAPI fuction poiter
pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc;
//?thunk玂??┦э?
DWORD dwOldProtect;
VirtualProtect(mbi_thunk.BaseAddress, mbi_thunk.RegionSize,
mbi_thunk.Protect, &dwOldProtect);
return TRUE;
}
}
pOrigThunk++;
pRealThunk++;
}
//SetLastError(ERROR_SUCCESS);
return FALSE;
}

BOOL HookAPIByName(HANDLE hModule/*砆HOOKProcess MODULE*/, LPCSTR szImportMod/*GDI32.DLL*/,LPHOOKAPI pHookApi/*﹚ㄧ?,"MessageBoxW"*/)
{
if(CHookFuctionToMy::HookAPIByName1(hModule,szImportMod,pHookApi))
{
return TRUE;
}
else
{
return FALSE;
}

}
neomeng 2001-07-11
  • 打赏
  • 举报
回复
呵呵,学了一点。
verybigbug 2001-07-10
  • 打赏
  • 举报
回复
ChangeFuncEntry调用了几次?只调用一次肯定是不够的。
监视Api的东西用来屏幕取词!!!!大题小作了吧。
NowCan 2001-07-10
  • 打赏
  • 举报
回复
98下你说的那三个dll代码都不能被改写。需要ring0才行。
zhoutianshu 2001-07-10
  • 打赏
  • 举报
回复
应该就是天天说的原因
zhoutianshu 2001-07-10
  • 打赏
  • 举报
回复
应该就是天天说的原因
加载更多回复(2)

16,551

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Creator Browser
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧