




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
WindowsPE權(quán)威指南第二部分進(jìn)階(剖析WindowsPE文件格式的原理及其編程技術(shù))目錄\h第二部分PE進(jìn)階\h第12章PE變形技術(shù)\h12.1變形技術(shù)的分類\h12.2變形技術(shù)可用的空間\h12.3PE文件變形原則\h12.4將PE變小的實(shí)例HelloWorldPE\h12.5打造目標(biāo)PE的步驟\h12.6小結(jié)\h第13章PE補(bǔ)丁技術(shù)\h13.1動(dòng)態(tài)補(bǔ)丁\h13.2靜態(tài)補(bǔ)丁\h13.3嵌入補(bǔ)丁程序\h13.4萬(wàn)能補(bǔ)丁碼\h13.5小結(jié)\h第14章在PE空閑空間中插入程序\h14.1什么是PE空閑空間\h14.2添加注冊(cè)表啟動(dòng)項(xiàng)的補(bǔ)丁程序?qū)嵗齖h14.3手工打造目標(biāo)PE的步驟\h14.4開發(fā)補(bǔ)丁工具\(yùn)h14.5小結(jié)\h第15章在PE間隙中插入程序\h15.1什么是PE間隙\h15.2插入HelloWorld的補(bǔ)丁程序?qū)嵗齖h15.3開發(fā)補(bǔ)丁工具\(yùn)h15.4存在綁定導(dǎo)入數(shù)據(jù)的PE補(bǔ)丁程序?qū)嵗齖h15.5小結(jié)\h第16章在PE新增節(jié)中插入程序\h16.1新增PE節(jié)的方法\h16.2在本地建立子目錄的補(bǔ)丁程序?qū)嵗齖h16.3開發(fā)補(bǔ)丁工具\(yùn)h16.4小結(jié)\h第17章在PE最后一節(jié)中插入程序\h17.1網(wǎng)絡(luò)文件下載器補(bǔ)丁程序?qū)嵗齖h17.2開發(fā)補(bǔ)丁工具\(yùn)h17.3小結(jié)\h第三部分PE的應(yīng)用案例\h第18章EXE捆綁器\h18.1基本思路\h18.2EXE執(zhí)行調(diào)度機(jī)制\h18.3字節(jié)碼轉(zhuǎn)換工具h(yuǎn)ex2db\h18.4執(zhí)行調(diào)度程序_host.exe\h18.5宿主程序host.exe\h18.6EXE捆綁器bind.exe\h18.7小結(jié)\h第19章軟件安裝自動(dòng)化\h19.1基本思路\h19.2補(bǔ)丁程序patch.exe\h19.3消息發(fā)送器_Message.exe\h19.4消息發(fā)送器生成工廠MessageFactory.exe\h19.5軟件安裝自動(dòng)化主程序AutoSetup.exe\h19.6小結(jié)\h第20章EXE加鎖器\h20.1基本思路\h20.2免資源文件的窗口程序nores.asm\h20.3免重定位的窗口程序login.asm\h20.4補(bǔ)丁程序patch.asm\h20.5附加補(bǔ)丁運(yùn)行\(zhòng)h20.6小結(jié)\h第21章EXE加密\h21.1基本思路\h21.2加密算法\h21.3開發(fā)補(bǔ)丁工具\(yùn)h21.4處理補(bǔ)丁程序\h21.5小結(jié)\h第22章PE病毒提示器\h22.1基本思路\h22.2手工打造PE病毒提示器\h22.3補(bǔ)丁版的PE病毒提示器\h22.4小結(jié)\h第23章破解PE病毒\h23.1病毒保護(hù)技術(shù)\h23.2PE病毒補(bǔ)丁程序解析\h23.3解毒代碼的編寫\h23.4小結(jié)第二部分PE進(jìn)階第12章PE變形技術(shù)第13章PE補(bǔ)丁技術(shù)第14章在PE空閑空間中插入程序第15章在PE間隙中插入程序第16章在PE新增節(jié)中插入程序第17章在PE最后一節(jié)中插入程序第12章PE變形技術(shù)本章將研究PE文件的可塑性,通過對(duì)PE文件進(jìn)行變形,看是否能通過操作系統(tǒng)的PE加載器。本章的目標(biāo)是通過手工打造一些小的PE程序,以便探究PE文件結(jié)構(gòu)與操作系統(tǒng)PE加載器之間的關(guān)系。研究PE變形技術(shù)不局限于了解PE加載器加載PE的機(jī)制,還在于通過變形可以實(shí)現(xiàn)反調(diào)試、運(yùn)行劫持等。12.1變形技術(shù)的分類所謂變形是指通過改變鏈接器生成的PE文件內(nèi)容,擴(kuò)大或縮小文件尺寸,用以測(cè)試PE加載器的機(jī)制及健壯性。本節(jié)主要講述靜態(tài)PE文件中的四種變形技術(shù),它們依次是:結(jié)構(gòu)重疊技術(shù)空間調(diào)整技術(shù)數(shù)據(jù)轉(zhuǎn)移技術(shù)數(shù)據(jù)壓縮技術(shù)下面分別介紹這四種變形技術(shù)。12.1.1結(jié)構(gòu)重疊技術(shù)結(jié)構(gòu)重疊技術(shù)是指在不影響正常性能的前提下,將某些數(shù)據(jù)結(jié)構(gòu)進(jìn)行重疊的技術(shù)。在縮小PE的變形中將大量使用這種技術(shù)。現(xiàn)在舉例說明,以下是一個(gè)使用了結(jié)構(gòu)重疊的PE文件頭部字節(jié)碼:該文件頭部就是典型的IMAGE_DOS_HEADER和IMAGE_NT_HEADERS兩個(gè)結(jié)構(gòu)的重疊。首先分開來看,如果把這部分?jǐn)?shù)據(jù)看成是IMAGE_DOS_HEADER,則各部分的值為:可以看到,指向PE文件頭部的字段依然是在偏移3Ch處。IMAGE_DOS_HEADER的40個(gè)字節(jié)一個(gè)不缺,所以,它是一個(gè)完整的DOSMZ頭結(jié)構(gòu)。由于兩個(gè)結(jié)構(gòu)并不是從一開始就重疊,所以在IMAGE_DOS_HEADER結(jié)構(gòu)的0ch偏移處兩個(gè)結(jié)構(gòu)開始重疊。從該位置處開始的IMAGE_NT_HEADERS結(jié)構(gòu)各字段的值分別是:從上面的分析可以看出,兩個(gè)結(jié)構(gòu)從以下字段開始發(fā)生重疊:IMAGE_NT_HEADERS.SignatureIMAGE_DOS_HEADER.e_maxalloc+IMAGE_DOS_HEADER.e_ss兩個(gè)結(jié)構(gòu)中的字段發(fā)生了重疊,結(jié)構(gòu)自然也就重疊了。那么為什么結(jié)構(gòu)重疊了卻沒有發(fā)生加載錯(cuò)誤呢?得益于以下三點(diǎn):1)被覆蓋的數(shù)據(jù)可能是另一個(gè)結(jié)構(gòu)中無用的數(shù)據(jù)。2)有用的數(shù)據(jù)可能只對(duì)一個(gè)結(jié)構(gòu)起作用,但有時(shí)被覆蓋的數(shù)據(jù)在兩個(gè)結(jié)構(gòu)中都有用。此種情況下發(fā)生的重疊必須保證重疊的字段在兩個(gè)結(jié)構(gòu)中擁有相同值。3)PE加載器并不檢測(cè)所有的字段。重疊以后的兩個(gè)數(shù)據(jù)結(jié)構(gòu)關(guān)系見圖12-1。圖12-1結(jié)構(gòu)重疊示意圖從圖中可以看出,重疊以后的數(shù)據(jù)明顯變少了。12.1.2空間調(diào)整技術(shù)本小節(jié)以不固定大小的數(shù)據(jù)塊DOSSTUB為例介紹空間調(diào)整技術(shù)。具體思路是,通過調(diào)整字段IMAGE_DOS_HEADER.e_lfanew的值,實(shí)現(xiàn)動(dòng)態(tài)地?cái)U(kuò)充或縮小DOSSTUB塊空間,從而達(dá)到PE變形的目的。這里以第6章的免導(dǎo)入、免重定位的HelloWorld1_1.exe作為藍(lán)本,目標(biāo)是將該P(yáng)E文件擴(kuò)充一個(gè)內(nèi)存頁(yè)大小。以下是詳細(xì)的測(cè)試步驟:使用FlexHex建立一個(gè)大小為5120字節(jié)的HelloWorld1_10.exe程序,并執(zhí)行以下操作:步驟1修改IMAGE_DOS_HEADER.e_lfanew的值,增加一個(gè)頁(yè)面大小1000h,由原來的000000A8更改為000010A8。步驟2將HelloWorld1_1.exe的PE標(biāo)識(shí)符起始位置開始的所有非零數(shù)據(jù)全部復(fù)制到000010A8位置(采用覆蓋方式)。步驟3修改字段IMAGE_OPTIONAL_HEADER32AddressOfEntryPoint的值,由原來的00001124更改為00002124。步驟4修改字段IMAGE_OPTIONAL_HEADER32SizeOfImage的值,由原來的00002000更改為00003000。步驟5修改字段IMAGE_OPTIONAL_HEADER32SizeOfHeaders的值,由原來的00000200更改為00001200。步驟6修改字段IMAGE_SECTION_HEADER.VirtualAddress的值,由原來的00001000更改為00002000。步驟7修改字段IMAGE_SECTION_HEADER.PointerToRawData的值,由原來的00000200更改為00001200。因?yàn)樵撐募]有重定位信息、沒有導(dǎo)入表、沒有數(shù)據(jù)段、沒有數(shù)據(jù)目錄項(xiàng),且只有一個(gè)節(jié),所以本測(cè)試中所有需要修改的參數(shù)都已列出。運(yùn)行chapter12\HelloWorld1_10.exe,發(fā)現(xiàn)可以正常顯示對(duì)話框。在OD中查看內(nèi)存分配,可以看到文件頭部被擴(kuò)充了一個(gè)頁(yè)面大小,如圖12-2所示。在操作系統(tǒng)查看兩個(gè)文件大小之差為4096,十六進(jìn)制剛好是1000h,打造HelloWorld1_10的實(shí)驗(yàn)證明調(diào)整DOS_STUB塊空間是可行的。圖12-2擴(kuò)大后的HelloWorld1_10.exe文件頭部占用的內(nèi)存空間該實(shí)例演示了PE變形中的擴(kuò)大技術(shù)??梢钥吹?,HelloWorld.exe在被加載到虛擬內(nèi)存空間的PE文件頭占用空間的大小,由原來的00001000h變成了00002000h。12.1.3數(shù)據(jù)轉(zhuǎn)移技術(shù)在編程過程中,出于某種考慮,經(jīng)常會(huì)將PE中的一部分?jǐn)?shù)據(jù)轉(zhuǎn)移到另一個(gè)位置。比如,將程序中的變量存儲(chǔ)到文件頭部結(jié)構(gòu)的某個(gè)字段中,將代碼轉(zhuǎn)移到頭部結(jié)構(gòu)的某個(gè)連續(xù)空間中等,這就是數(shù)據(jù)轉(zhuǎn)移技術(shù)。該技術(shù)包括對(duì)變量的存儲(chǔ)和代碼的存儲(chǔ)。1.變量存儲(chǔ)變量存儲(chǔ)的例子節(jié)選自隨書文件chapter12\HelloWorld7.exe的PE頭部,如下所示:000000004D5A48656C6C6F576F726C6450450000MZHelloWorldPE..該示例將程序要顯示的字符串變量移動(dòng)到了文件頭部的IMAGE_DOS_HEADER中,而且與數(shù)據(jù)結(jié)構(gòu)IMAGE_NT_HEADERS的PE標(biāo)識(shí)字段自動(dòng)重合,重合的部分為:50450000該部分既可以認(rèn)為是IMAGE_NT_HEADERS.Signature,也可以認(rèn)為是字符串"HelloWorldPE\0\0"的一部分。該部分變量原來的位置是在一個(gè)獨(dú)立的節(jié)".data"中,占據(jù)文件中的200h個(gè)字節(jié);通過這樣的轉(zhuǎn)移,使得PE產(chǎn)生變形,不僅節(jié)的內(nèi)容沒有了,節(jié)表中也少了一個(gè)描述該節(jié)信息的表項(xiàng)。2.代碼存儲(chǔ)對(duì)代碼的轉(zhuǎn)儲(chǔ)比較普遍,常見的有:PE壓縮、病毒、加密與解密等。文件頭部的連續(xù)空間被認(rèn)為是存儲(chǔ)代碼的好地方,如果連續(xù)空間的長(zhǎng)度無法容納所有的代碼,則可以將代碼分解。例如,看OD對(duì)第1章中HelloWorld.exe的反匯編代碼:指令字節(jié)碼總長(zhǎng)度為36字節(jié)。變形空間中能夠容納這些代碼的有兩處:一個(gè)是IMAGE_DOS_HEADER,另一個(gè)是數(shù)據(jù)目錄表。假設(shè)以上兩處沒有空間能存放這些代碼,我們也可以將這些代碼分開來存儲(chǔ),但分開存儲(chǔ)時(shí)必須要保證調(diào)用指令之間的先后順序。下面是對(duì)HelloWorld.exe反匯編代碼的連續(xù)指令長(zhǎng)度的一個(gè)統(tǒng)計(jì):長(zhǎng)度為6字節(jié)的指令有3個(gè)長(zhǎng)度為5字節(jié)的指令有2個(gè)長(zhǎng)度為2字節(jié)的指令有4個(gè)長(zhǎng)度為1字節(jié)的指令有1個(gè)有了以上的統(tǒng)計(jì)數(shù)據(jù),就可以對(duì)比變形空間中描述的可用連續(xù)字段,將這些指令分別存儲(chǔ)在不同的空間位置。以前三條指令為例:用字段擴(kuò)展PE頭中的BaseOfCode開始的8字節(jié)存儲(chǔ)6A006A00指令,另加一條近跳轉(zhuǎn)指令EB00。用擴(kuò)展PE頭中的MajorOperatingSystemVersion字段開始的8個(gè)字節(jié)存儲(chǔ)6800304000指令,另外加一條近跳轉(zhuǎn)指令EB00。然后根據(jù)兩部分的距離修正跳轉(zhuǎn)指令中的操作數(shù)如下:如上所示,從字段BaseOfCode到字段MajorOperatingSystemVersion,中間隔了三個(gè)雙字的字段,所以第一條近跳轉(zhuǎn)指令中的操作數(shù)為4*3+2=0Eh;另一個(gè)操作數(shù)則要根據(jù)下一條指令所在字段的位置進(jìn)行計(jì)算。這樣,原來的指令:PUSH0PUSH0PUSHHelloWo.00403000就變成了現(xiàn)在的指令:PUSH0PUSH0JmpLoc1:……Loc1:PUSHHelloWo.00403000以上方法在構(gòu)造指令時(shí)非常復(fù)雜,其實(shí)還有一種更好的方法,即通過程序編碼,使鏈接器輔助我們構(gòu)造指令長(zhǎng)度。下面為大家演示,步驟如下。步驟1未修正前的指令字節(jié)碼。以下內(nèi)容節(jié)選自HelloWorld.exe的代碼段:以上所列為沒有修正前的原始指令字節(jié)碼。步驟2修正以后的程序。通過程序?qū)⒃贾噶钭止?jié)碼分解為多個(gè)小塊代碼,詳情見代碼清單12-1。代碼清單12-1分解原始指令代碼(chapter12\exp.asm)1;2;手工修改用的HelloWorld源代碼3;戚利4;2011.2.185;6.3867.modelflat,stdcall8optioncasemap:none910includewindows.inc11includeuser32.inc12includelibuser32.lib13includekernel32.inc14includelibkernel32.lib1516;數(shù)據(jù)段17.data18szTextdb'HelloWorldPE',019;代碼段20.code21start:22pushMB_OK23pushNULL24pushoffsetszText25jmpshort@next126db8dup(0aah);在代碼中加入了8個(gè)字節(jié)27@next1:28pushNULL29callMessageBoxA3031pushNULL32callExitProcess33endstart行26使用偽指令語(yǔ)句db定義了第一塊代碼(行22~25)到第二塊代碼(行28~32)之間的間隔(以字節(jié)計(jì))。步驟3修正后的代碼。下面是加入了補(bǔ)足數(shù)據(jù)的字節(jié)碼:與該字節(jié)碼對(duì)應(yīng)的匯編代碼如下:代碼清單12-1的行25使用了跳轉(zhuǎn)語(yǔ)句(翻譯為指令字節(jié)碼是EB)。在程序源代碼中,開發(fā)者只需要簡(jiǎn)單地使用標(biāo)號(hào)來表明跳轉(zhuǎn)指令要跳轉(zhuǎn)到的位置,以及跳轉(zhuǎn)指令后的操作數(shù),即可由編譯程序自動(dòng)生成。從以上反匯編代碼中可以看到,EB指令后的操作數(shù)為08,這8個(gè)字節(jié)是代碼清單12-1的行26定義的8個(gè)0AAh。通過這種簡(jiǎn)單的方法就可以讓編譯器幫助我們計(jì)算跳轉(zhuǎn)指令的操作數(shù)了。12.1.4數(shù)據(jù)壓縮技術(shù)在編程的過程中,如果指令代碼比較長(zhǎng),還可以先對(duì)代碼實(shí)施壓縮,然后在PE頭部找一塊比較大的連續(xù)區(qū)域存放解壓縮用的代碼。程序被PE加載器加載后,文件頭就基本不再使用了。這時(shí),可以將存儲(chǔ)的壓縮代碼通過PE頭部的解壓縮程序進(jìn)行解壓,解壓后即可通過跳轉(zhuǎn)指令實(shí)施程序指令的轉(zhuǎn)移。由于壓縮以后的代碼不便于通過十六進(jìn)制直觀地看到,所以這種方法在一些病毒程序代碼中比較常見;另外,在一些加殼程序中會(huì)經(jīng)??吹綌?shù)據(jù)壓縮技術(shù)。先來看一個(gè)這種技術(shù)的應(yīng)用,以下是某病毒代碼的頭部信息:該病毒對(duì)兩個(gè)標(biāo)識(shí)字段并沒有進(jìn)行大的改動(dòng)。注意觀察節(jié)表部分內(nèi)容,按照基礎(chǔ)知識(shí)中所介紹的,每個(gè)節(jié)表最少應(yīng)該有40個(gè)字節(jié),在這里明顯大小并不符合。經(jīng)過仔細(xì)分析之后才知道,病毒程序?qū)@部分?jǐn)?shù)據(jù)進(jìn)行了加密處理。下面詳細(xì)分析病毒是如何加密該部分?jǐn)?shù)據(jù)的。>>504500024C01根據(jù)前面所學(xué)的知識(shí),PE頭部應(yīng)該有兩個(gè)“\0”,在這里只是用了00-02來表示這兩個(gè)“\0”,看起來好像使用了簡(jiǎn)單的行程壓縮算法。凡是有連續(xù)“\0”的地方都將0的個(gè)數(shù)作為緊跟在“\0”后面的一項(xiàng)。來看節(jié)SCODE的內(nèi)容,如下所示:根據(jù)以上的猜測(cè)來還原該節(jié)的實(shí)際內(nèi)容如下:根據(jù)恢復(fù)以后的節(jié)來看,對(duì)該算法的猜測(cè)應(yīng)該是沒有問題的。再仔細(xì)分析一下,可以看到,該病毒的作者只對(duì)文件頭部進(jìn)行了加密,其他部分還是沒有更改的。也就是說,要想恢復(fù)這個(gè)PE文件的內(nèi)容,只需要對(duì)頭部進(jìn)行處理即可。知道原理之后,接下來的解密工作就容易多了,代碼清單12-2是解密的源代碼。代碼清單12-2解壓病毒文件頭部數(shù)據(jù)(chapter12\UnEncrpt.asm)1;2;forxxVirusunzipFileHeader3;戚利4;2011.2.195;6.3867.modelflat,stdcall8optioncasemap:none910includewindows.inc11includeuser32.inc12includelibuser32.lib13includekernel32.inc14includelibkernel32.lib15includecomdlg32.inc16includelibcomdlg32.lib171819TOTAL_SIZEequ162h2021;數(shù)據(jù)段22.data23szFileSourcedb'c:\worm2.exe',024szFileDestdb'c:\worm2_bak.exe',025dwTotalSizedd026hFileSrcdd027hFileDstdd028dwTempdd029dwTemp1dd030dwTemp2dd031szCaptiondb'Gotyou',032szTextdb'OK!?^_^',033szBufferdbTOTAL_SIZEdup(0)34szBuffer1db0ffffhdup(0)3536;代碼段37.code3839start:4041;打開文件worm2.exe42invokeCreateFile,addrszFileSource,GENERIC_READ,\43FILE_SHARE_READ,\440,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,045movhFileSrc,eax46;創(chuàng)建另外一個(gè)文件worm2_bak.exe47invokeCreateFile,addrszFileDest,GENERIC_WRITE,\48FILE_SHARE_READ,\490,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,050movhFileDst,eax5152;解壓縮頭部53invokeReadFile,hFileSrc,addrszBuffer,\54TOTAL_SIZE,addrdwTemp,05556movesi,offsetszBuffer57movedi,offsetszBuffer158movecx,TOTAL_SIZE59movdwTemp2,060@@0:61lodsb62movbl,al63subbl,064jz@@165stosb66incdwTemp267dececx68jecxz@F69jmp@@070@@1:71dececx72jecxz@F73lodsb74pushecx75xorecx,ecx76movcl,al77adddwTemp2,ecx78moval,079repstosb80popecx8182dececx83jecxz@F84jmp@@085@@:86invokeWriteFile,hFileDst,addrszBuffer1,\87dwTemp2,addrdwTemp1,NULL8889;關(guān)閉文件90invokeCloseHandle,hFileDst91invokeCloseHandle,hFileSrc9293invokeMessageBox,NULL,offsetszText,\94offsetszCaption,MB_OK95invokeExitProcess,NULL96endstart程序首先打開兩個(gè)文件,一個(gè)是待解壓的文件,用來讀;另一個(gè)是解壓后的文件,用來寫。解壓縮的代碼在行56~84。行61取出一個(gè)字節(jié),然后判斷是否為0,如果是則跳轉(zhuǎn)到標(biāo)號(hào)@@1處執(zhí)行;否則將字節(jié)原樣寫入目標(biāo)緩沖區(qū)szBuffer1。如果取到的是0,則再取一個(gè)字節(jié),該字節(jié)記錄了0的個(gè)數(shù)。將該值賦給cl寄存器,使用語(yǔ)句repstosb將指定個(gè)數(shù)的0存入目標(biāo)緩沖區(qū);然后,調(diào)整循環(huán)次數(shù),并跳轉(zhuǎn)到標(biāo)號(hào)@@0處繼續(xù)執(zhí)行下一個(gè)循環(huán)。最后,將目標(biāo)緩沖區(qū)中已經(jīng)解壓的字節(jié)寫入目標(biāo)文件(行86)。以上描述了四種基本的PE變形技術(shù),下面探討PE變形時(shí)需要遵循的一些原則,以確保最終變形后的PE能被Windows加載器順利加載而不發(fā)生錯(cuò)誤。12.2變形技術(shù)可用的空間要想對(duì)PE進(jìn)行變形,需要掌握PE文件中每個(gè)位置的數(shù)據(jù)的可替換特性,即該位置數(shù)據(jù)是否可以被替換為別的值,某段數(shù)據(jù)是否可以被其他用途利用等??傮w上講,PE中可以用作變形的空間有以下四類。12.2.1文件頭部未用的字段通過基礎(chǔ)知識(shí)部分的學(xué)習(xí)我們知道,在PE文件頭部有許多字段的值可以被修改和利用。也就是說,出于兼容上的考慮,PE頭部的數(shù)據(jù)結(jié)構(gòu)中為將來預(yù)留了很多的字段,這些字段現(xiàn)在有的被強(qiáng)制設(shè)置為0,有的則未加任何限制。這些可以被替換的數(shù)據(jù)見表12-1(以下結(jié)論是筆者測(cè)試得出的,并不保證能適應(yīng)所有的場(chǎng)合)。由于文件頭中大部分字段不是連續(xù)的,受不同PE內(nèi)容的影響很大。比如,上表并沒有列出數(shù)據(jù)目錄表中加載配置和延遲導(dǎo)入表項(xiàng)的空間。如果一個(gè)PE中不存在以上特性,則這些空間中的[x].size域就是可用的。不連續(xù)的空間通常的用途是存放數(shù)據(jù),對(duì)于連續(xù)的但字節(jié)數(shù)不多的空間,則可以存放代碼。比如,以下常用的指令其字節(jié)碼本身就不大:相對(duì)較大的連續(xù)空間則可以存放一段較長(zhǎng)的指令字節(jié)碼。這些空間主要包括:IMAGE_DOS_HEADER中的54個(gè)字節(jié)、標(biāo)準(zhǔn)頭12個(gè)字節(jié)、擴(kuò)展頭14個(gè)字節(jié)、數(shù)據(jù)目錄52個(gè)字節(jié)、每個(gè)節(jié)表項(xiàng)中的20個(gè)字節(jié)。12.2.2大小不固定的數(shù)據(jù)塊通過對(duì)一些大小不固定的數(shù)據(jù)塊進(jìn)行擴(kuò)展,也可以獲取足夠的空間。這些大小不固定的數(shù)據(jù)塊包括:(1)DOSSTUB由于DOSSTUB是為16位系統(tǒng)保留的,其中的任何一個(gè)字節(jié)都可以填充為任意值。(2)PE擴(kuò)展頭IMAGE_OPTIONAL_HEADER32在IMAGE_FILE_HEADER中有一個(gè)字段記錄了PE擴(kuò)展頭的長(zhǎng)度。該字段為:SizeOfOptionalHeader。注意,這個(gè)字段為DW類型,最多能擴(kuò)展一個(gè)字的空間。(3)數(shù)據(jù)目錄項(xiàng)數(shù)據(jù)目錄表的項(xiàng)數(shù)由字段IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSizes來定義,通過修改該值也可以擴(kuò)充或縮小文件頭的尺寸。(4)節(jié)表在節(jié)表數(shù)據(jù)結(jié)構(gòu)中有一個(gè)值SizeOfRawData,表示節(jié)在文件對(duì)齊后的尺寸。修改這個(gè)值也可以起到擴(kuò)充節(jié)大小的作用。注意若修改了一個(gè)節(jié)的大小,其他節(jié)在文件的起始地址都要跟著修改。理論上講,只要是大小不固定的塊,都有被擴(kuò)展的可能。關(guān)鍵的問題是,PE加載器的機(jī)制是否允許修改,大家可以通過實(shí)驗(yàn)自行測(cè)試(2)(3)(4)部分的可行性。后面會(huì)有一個(gè)專門的實(shí)驗(yàn)來驗(yàn)證(1)的可行性。如果可執(zhí)行文件不存在輸出表,那么當(dāng)PE加載器將其加載到內(nèi)存以后,PE的文件頭部分?jǐn)?shù)據(jù)就已經(jīng)是無用的了。這也就意味著,PE文件頭相關(guān)數(shù)據(jù)結(jié)構(gòu)中的所有的字段,在運(yùn)行期均是可隨意填充任何值的。唯一遺憾的是PE加載器在加載完P(guān)E以后,把該段內(nèi)存設(shè)置成了只讀的R屬性。12.2.3因?qū)R產(chǎn)生的補(bǔ)足空間操作系統(tǒng)對(duì)PE文件的強(qiáng)制對(duì)齊特性,使得PE文件的節(jié)中存有大量為對(duì)齊而補(bǔ)足的0。這種機(jī)制同樣影響到文件頭部。由于默認(rèn)對(duì)齊尺寸為200h大小的限制,大部分的系統(tǒng)文件(如記事本、kernel32.dll等)的文件頭部只剩下很少的空間。12.3PE文件變形原則前面對(duì)PE變形時(shí)與字段有關(guān)的空間進(jìn)行了簡(jiǎn)單的分析,本節(jié)重點(diǎn)研究變形時(shí)要遵循的一些原則。在對(duì)PE文件進(jìn)行變形時(shí),改動(dòng)PE數(shù)據(jù)結(jié)構(gòu)中某些字段的值需要遵循一些原則,如果沒有原則地隨意變形,將會(huì)導(dǎo)致生成的目標(biāo)PE文件無法被操作系統(tǒng)識(shí)別并加載。在對(duì)PE進(jìn)行變形時(shí)需要特別注意這些原則。12.3.1關(guān)于數(shù)據(jù)目錄表數(shù)據(jù)目錄表的個(gè)數(shù)必須大于等于2。如果PE文件的最后一個(gè)字節(jié)位于目錄表之間,如介于第3項(xiàng)資源表定義之間,即[DD[2].VirtualAddress]<文件總長(zhǎng)度<[DD[2].isize],則文件中無法定義資源表的大小,PE加載器默認(rèn)資源表的大小為0。一個(gè)完整的數(shù)據(jù)目錄表在普通的PE文件中可讀寫的字段如下,以下截取了測(cè)試用的PE文件的數(shù)據(jù)目錄表。從測(cè)試看,連續(xù)AA的部分可以是任意值。12.3.2關(guān)于節(jié)表PE文件頭中可以沒有節(jié)的定義,但必須將文件頭部的字段IMAGE_FILE_HEADER.NumberOfSections設(shè)置為1。12.3.3關(guān)于導(dǎo)入表導(dǎo)入表是PE的核心。要想在已有的PE中靜態(tài)引入動(dòng)態(tài)鏈接庫(kù)的函數(shù),必須通過變形技術(shù)構(gòu)造一個(gè)合理的導(dǎo)入表(這里的“合理”指的是結(jié)構(gòu)上的合理),或者重構(gòu)已有導(dǎo)入表。在12.5.7小節(jié)將看到一個(gè)只有133字節(jié)的PE文件。在該文件中,PE頭部的數(shù)據(jù)結(jié)構(gòu)中的字段能減的都減了,能重疊的也都重疊了,但即使是在這么短的PE中,導(dǎo)入表的雙橋結(jié)構(gòu)還是存在的。導(dǎo)入表的IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)是順序排列的。前面我們講過,“指向的數(shù)組最后以一個(gè)內(nèi)容全0的結(jié)構(gòu)作為結(jié)束”。其實(shí),這個(gè)條件可以寬限到只判斷IMAGE_IMPORT_DESCRIPTOR.Name1是否為0即可。如果一個(gè)PE文件的結(jié)尾剛好沒有空間存儲(chǔ)該字段對(duì)應(yīng)的值,則系統(tǒng)會(huì)默認(rèn)該字段是存在的,并且其值為0。12.3.4關(guān)于程序數(shù)據(jù)數(shù)據(jù)可以存儲(chǔ)在內(nèi)存中的任何位置,可以位于文件頭,也可以位于其他節(jié)中。代碼和數(shù)據(jù)一樣,可以在內(nèi)存的任何位置,但所在的節(jié)(無論是指定的節(jié)還是文件頭部),其節(jié)的屬性必須可讀、可寫、可執(zhí)行。將代碼段設(shè)置為可寫屬性主要是考慮到某些程序會(huì)將一些變量存儲(chǔ)在代碼段,且在程序中有為該變量賦值的代碼。如果所有的數(shù)據(jù)(程序變量、導(dǎo)入表、IAT等)都在文件頭部,也就是說加載進(jìn)內(nèi)存的PE文件只有文件頭存在,假設(shè)其大小為一個(gè)頁(yè)面1000h,那么操作系統(tǒng)會(huì)因?yàn)镮AT的緣故自動(dòng)將該頁(yè)面設(shè)置為ERW,即可讀、可寫、可執(zhí)行(這和以前我們看到的文件頭部只讀是不一樣的)。12.3.5關(guān)于對(duì)齊節(jié)的對(duì)齊尺寸必須大于或等于文件的對(duì)齊尺寸。由于文件的對(duì)齊尺寸被定義為2的N次冪,所以通常會(huì)將文件對(duì)齊尺寸設(shè)置得更小,并使兩個(gè)的值相等,以達(dá)到縮小PE文件的目的。如本章后面講的兩個(gè)例子中,文件對(duì)齊粒度用了10h,內(nèi)存對(duì)齊粒度用了4h,即:SectionAlignment=FileAlignment=10hSectionAlignment=FileAlignment=4h12.3.6幾個(gè)關(guān)注的字段每當(dāng)修改了程序的尺寸后,程序中相關(guān)的字節(jié)碼的位置、字節(jié)碼的長(zhǎng)度會(huì)發(fā)生或多或少的變化,這種變化勢(shì)必會(huì)影響一些記錄這些位置和大小的字段,表12-2所列字段是在變形時(shí)必須要關(guān)注、指定或修改的。表12-3所列是在變形時(shí)可當(dāng)做固定標(biāo)志的(即對(duì)大多數(shù)EXE文件來說經(jīng)常不變的)字段。表12-3中帶有中括號(hào)[]的表達(dá)式表示由該地址處取出的值作為定位對(duì)應(yīng)字段的偏移。下面通過講解兩個(gè)實(shí)際例子的操作過程,學(xué)習(xí)將PE尺寸變小的方法。12.4將PE變小的實(shí)例HelloWorldPE本節(jié)要分析的源程序與第1章的HelloWorld.asm有一點(diǎn)區(qū)別,即將字符串定義為"HelloWorldPE"。為了能與最終手工打造修改后的PE程序有所區(qū)別,這里將源代碼及最終生成的PE的字節(jié)碼分別列出來。12.4.1源程序HelloWorld的字節(jié)碼(2560字節(jié))要手工打造的源代碼見代碼清單12-3。代碼清單12-3手工修改用的HelloWorld源代碼(chapter12\helloworld.asm)1;2;手工修改用的HelloWorld源代碼3;戚利4;2010.6.105;6.3867.modelflat,stdcall8optioncasemap:none910includewindows.inc11includeuser32.inc12includelibuser32.lib13includekernel32.inc14includelibkernel32.lib1516;數(shù)據(jù)段17.data18szTextdb'HelloWorldPE',019;代碼段20.code21start:22invokeMessageBox,NULL,offsetszText,NULL,MB_OK23invokeExitProcess,NULL24endstart源代碼比較簡(jiǎn)單,程序?qū)崿F(xiàn)了彈出窗口的功能,彈出的窗口中顯示字符串"HelloWorldPE"。將HelloWorld.asm編譯鏈接生成最終的EXE文件,使用FlexHex打開HelloWorld.exe,復(fù)制字節(jié)碼并按照類別分為以下四部分:文件頭部、代碼段、導(dǎo)入表和數(shù)據(jù)段。各部分字節(jié)碼如下。(1)文件頭部=文件頭+節(jié)表+補(bǔ)齊(大小400h)(2)代碼段=代碼+補(bǔ)齊(大小200h)(3)導(dǎo)入表=導(dǎo)入表及相關(guān)結(jié)構(gòu)+補(bǔ)齊(大小200h)(4)數(shù)據(jù)段=數(shù)據(jù)+補(bǔ)齊(大小200h)以上列出了完整的HelloWorld.exe的字節(jié)碼,主要是為了和最終打造生成的較小的PE文件進(jìn)行比對(duì)。下面先跳過手工打造過程,看最終生成的目標(biāo)PE文件。12.4.2目標(biāo)PE文件的字節(jié)碼(432字節(jié))最終打造的目標(biāo)PE見隨書文件chapter12\HelloWorld_7.exe,其所有的字節(jié)碼長(zhǎng)度為432字節(jié),可以在WindowsXPSP3環(huán)境運(yùn)行,在OD中調(diào)試時(shí)其內(nèi)存空間分配如圖12-3所示。圖12-3OD中HelloWorld_7.exe的內(nèi)存分配從圖中可以看出,HelloWorld_7文件中所有的數(shù)據(jù)被加載進(jìn)內(nèi)存后,均被安排到了文件頭的位置,且文件頭部數(shù)據(jù)的訪問屬性被設(shè)置為RWE,即可讀、可寫、可執(zhí)行。以下是該P(yáng)E文件的完整的字節(jié)碼:字節(jié)碼中存在連續(xù)AA字節(jié)的部分都是可以再次利用的空間。12.5節(jié)就從該文件頭部開始,詳細(xì)介紹打造目標(biāo)PE的全過程。12.5打造目標(biāo)PE的步驟12.4節(jié)為我們展示了打造前后PE文件的字節(jié)碼對(duì)比,通過對(duì)比可以發(fā)現(xiàn),打造后的目標(biāo)PE文件盡管變得更小,卻依然具備打造前的PE的所有功能。本節(jié)將詳細(xì)介紹此次打造的全過程。希望讀者能夠全面理解和把握PE文件頭部數(shù)據(jù)結(jié)構(gòu)中各字段的作用,同時(shí),也讓讀者了解改變某些字段的值對(duì)整個(gè)PE文件所產(chǎn)生的影響。12.5.1對(duì)文件頭的處理根據(jù)前面介紹的結(jié)構(gòu)覆蓋技術(shù)和數(shù)據(jù)轉(zhuǎn)移技術(shù)壓縮文件頭,主要操作包括:把NT頭提前,覆蓋DOS頭部分,只保留最重要的e_lfanew字段。因?yàn)閿?shù)據(jù)段的起始地址BaseOfData是一個(gè)可以修改的字段,所以讓BaseOfData剛好落在e_lfanew這里,然后將這一部分更改為指向PE頭的0ch,如下所示:將IMAGE_NT_HEADERS提到前面來并不影響程序的運(yùn)行。除了BaseOfData字段需要改成指向PE頭的指針外,其他都無需改動(dòng)。從偏移02h開始一直到0Ch的數(shù)據(jù)沒有什么用處。于是把數(shù)據(jù)段中的數(shù)據(jù)放到了這里。不幸的是,原來要顯示的字符串"HelloWorldPE"長(zhǎng)度好像超出了這個(gè)范圍;幸運(yùn)的是,字符串里的"PE"剛好和PE文件的標(biāo)志重疊了。如下所示:刪除節(jié).data的內(nèi)容,即從800h處開始的內(nèi)容全部刪除,然后將.rdata節(jié)表后的文件頭數(shù)據(jù)的所有內(nèi)容清零,將節(jié)數(shù)量從原來的3更改為2。12.5.2對(duì)代碼段的處理首先來看HelloWorld.exe代碼段字節(jié)碼反匯編的結(jié)果。1.程序代碼段反匯編代碼使用OD打開HelloWorld.exe,復(fù)制反匯編代碼段內(nèi)容如下:將以上代碼的字節(jié)碼整理出來,然后將這些字節(jié)碼移動(dòng)到數(shù)據(jù)目錄表中。2.將代碼嵌入數(shù)據(jù)目錄表將代碼移動(dòng)到PE文件頭部的數(shù)據(jù)目錄表中,見加黑部分。在覆蓋時(shí)需要注意不要將有用的部分覆蓋。以上顯示的字節(jié)碼中,短跳轉(zhuǎn)代碼指令E8中涉及的偏移部分已經(jīng)做了修改。由于獨(dú)立代碼部分長(zhǎng)度剛好填完數(shù)據(jù)目錄表項(xiàng)03、04和05,免去了按照較短的空閑長(zhǎng)度重新構(gòu)造代碼的麻煩。下面來看對(duì)導(dǎo)入表部分?jǐn)?shù)據(jù)的處理。12.5.3對(duì)導(dǎo)入表的處理按照第4章介紹的導(dǎo)入表重組的方法,將導(dǎo)入表更改為如下字節(jié)碼:可以看到,從0130h開始的16個(gè)字節(jié)為IAT的內(nèi)容,與之相關(guān)的由字段originalFirstThunk指向的數(shù)據(jù)結(jié)構(gòu)則放到了導(dǎo)入表的最后一個(gè)全0的IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)中。因?yàn)榍懊嬲f過,只要保證該結(jié)構(gòu)的name1(框起來的部分)為0,即可滿足導(dǎo)入表結(jié)構(gòu)數(shù)組以全0結(jié)束的條件。從0140h開始部分即為導(dǎo)入表結(jié)構(gòu)數(shù)組。12.5.4對(duì)部分字段值的修正相關(guān)數(shù)據(jù)基本安排就緒,接下來的工作就是修正文件頭部因數(shù)據(jù)遷移而導(dǎo)致的字段的值的變更。主要包括以下幾個(gè)部分。1.定義節(jié).HelloPE由于.HelloPE段中存放了常量、數(shù)據(jù)和代碼,所以該段必須可讀、可寫、可執(zhí)行。下面是節(jié)表中對(duì).HelloPE節(jié)表項(xiàng)中的各字段的賦值:標(biāo)志位:0E00000E0h節(jié)的名字:自定義字符串為:.HelloPE節(jié)區(qū)的實(shí)際尺寸:01b0h節(jié)區(qū)起始RVA:從頭開始,即0000h文件對(duì)齊后的長(zhǎng)度:01b0h節(jié)位于文件的偏移:從頭開始,即0000h注意節(jié)區(qū)的實(shí)際尺寸可以在0800h范圍內(nèi)隨意更改,不受任何影響,這里選擇文件長(zhǎng)度01b0h。.HelloPE節(jié)表項(xiàng)結(jié)構(gòu)的相關(guān)數(shù)據(jù)如下:2.基地址、執(zhí)行入口和代碼段大小裝入的基地址不變,依然是00400000h;而執(zhí)行入口則更改為009Ch,即文件偏移009Ch處。由于可執(zhí)行文件很?。ㄐ∮?00h),所以這里的文件偏移地址即為RVA,無需轉(zhuǎn)換。代碼段大小即整個(gè)文件的大小000001b0h,相關(guān)數(shù)據(jù)如下:3.對(duì)齊尺寸為了讓文件變得更小,文件的對(duì)齊尺寸和內(nèi)存的對(duì)齊尺寸均設(shè)置為00000010h,即16個(gè)字節(jié)。相關(guān)數(shù)據(jù)如下:4.文件頭大小與PE內(nèi)存映像大小所有頭+節(jié)表的大小為00000130h,而PE在內(nèi)存中的映像大小為00001000h,相關(guān)數(shù)據(jù)如下:5.數(shù)據(jù)目錄表中導(dǎo)入表字段導(dǎo)入表的起始RVA=00000140h,長(zhǎng)度為3Ch。相關(guān)數(shù)據(jù)如下:12.5.5修改后的文件結(jié)構(gòu)手動(dòng)修改以后的PE文件結(jié)構(gòu)如圖12-4所示。圖12-4手動(dòng)修改后的PE結(jié)構(gòu)如圖所示,源PE中數(shù)據(jù)段的數(shù)據(jù)存儲(chǔ)在目標(biāo)PE的DOSMZ頭和PE標(biāo)識(shí)之間,源PE的程序代碼存儲(chǔ)在目標(biāo)PE的數(shù)據(jù)目錄表中;文件頭部定義了一個(gè)節(jié)表項(xiàng),導(dǎo)入表和IAT表安排在目標(biāo)PE的尾部。12.5.6修改后的文件分析接下來將使用工具PEInfo和PEComp分別對(duì)比兩個(gè)文件,得到的結(jié)果如下。1.PEInfo運(yùn)行結(jié)果對(duì)比下面來看PEInfo對(duì)目標(biāo)PE的輸出:與源PE相比,目標(biāo)PE中的節(jié)少了,但導(dǎo)入表還是很完整的。模塊的基地址沒有發(fā)生變化,程序代碼由于搬遷到數(shù)據(jù)目錄表中,所以入口地址發(fā)生了變化。2.使用PEComp工具對(duì)比結(jié)果使用PEComp工具打開兩個(gè)PE文件,運(yùn)行結(jié)果如圖12-5所示。圖12-5手工打造的PE程序與源程序?qū)Ρ葟膱D中可以看出,源PE與目標(biāo)PE文件頭部不相同的地方很多。造成這種結(jié)果的最主要的原因是在手工打造時(shí)使用了數(shù)據(jù)轉(zhuǎn)移技術(shù)。12.5.7目標(biāo)文件更小的實(shí)例分析下面看一個(gè)能顯示指定信息對(duì)話框的更小的PE文件miniPE程序,其大小總共為133字節(jié)。該文件的字節(jié)碼如下。1.字節(jié)碼2.源程序生成以上字節(jié)碼的源代碼見代碼清單12-4。為了去除微軟編譯器的提示錯(cuò)誤,避免在鏈接時(shí)追加任何其他內(nèi)容,以及匯編指令調(diào)用時(shí)對(duì)invoke指令的分解,這次使用了Borland公司的Tasm和Tlink作為這個(gè)源文件的編譯器和鏈接器。具體方法可以參照源文件頭部的注釋。代碼清單12-4miniPE程序(chapter12\minipe.asm)從代碼的注釋可以看出,該P(yáng)E使用的數(shù)據(jù)結(jié)構(gòu)包括:IMAGE_DOS_HEADERIMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER32IMAGE_IMPORT_DESCRIPTOR[0]IMAGE_IMPORT_DESCRIPTOR[1].VirtualAddress其中數(shù)據(jù)目錄只用了兩個(gè),且最后一個(gè)還沒有用全,因?yàn)楣?jié)表在程序里沒有定義。對(duì)該代碼的詳細(xì)分析見圖12-6和圖12-7。圖12-6133字節(jié)的PE程序分析一圖12-7133字節(jié)的PE程序分析二如圖所示,為了便于分析,源程序中每行被按照功能劃分為6列,它們依次是:第1列標(biāo)號(hào),用于標(biāo)識(shí)源程序中的一些特殊位置。第2列指令,即匯編源代碼。第3列結(jié)構(gòu)字段名,定義此處的結(jié)構(gòu)和字段。第4列字段的值,為每個(gè)字段賦值。第5列用分號(hào)做的注釋,標(biāo)注該行的含義。第6列對(duì)應(yīng)的字節(jié)碼。12.6小結(jié)本章介紹了PE的變形技術(shù)。所謂變形就是通過技術(shù)手段使PE文件的大小發(fā)生變化,或縮小或擴(kuò)大;無論怎么變,都能保證PE文件能被WindowsPE加載器加載,且能正常運(yùn)行。本章首先介紹了四種變形技術(shù)、PE數(shù)據(jù)結(jié)構(gòu)和PE文件中可以被二次利用的空間,以及變形時(shí)需要遵循的原則;最后,通過對(duì)HelloWorldPE的變形過程進(jìn)行分析,幫助讀者全面理解和把握PE數(shù)據(jù)結(jié)構(gòu)中相關(guān)字段的作用。本章在全書中具有承前啟后的作用,既是對(duì)前面所學(xué)知識(shí)的一個(gè)簡(jiǎn)單回顧和復(fù)習(xí),又能為下一步利用這些技術(shù)實(shí)施靜態(tài)文件補(bǔ)丁和應(yīng)用做好知識(shí)上的儲(chǔ)備。第13章PE補(bǔ)丁技術(shù)第12章介紹了PE變形技術(shù),該技術(shù)研究的是程序的字節(jié)碼;本章來研究PE補(bǔ)丁技術(shù),該技術(shù)側(cè)重于研究使用Masm32編寫的補(bǔ)丁程序,而非程序字節(jié)碼。PE補(bǔ)丁技術(shù)被廣泛應(yīng)用于PE病毒、PE加密解密等領(lǐng)域,通過對(duì)目標(biāo)程序嵌入不同的補(bǔ)丁程序,可以實(shí)現(xiàn)不同的目的。PE補(bǔ)丁分為動(dòng)態(tài)補(bǔ)丁和靜態(tài)補(bǔ)丁,其中靜態(tài)補(bǔ)丁框架由兩部分組成:補(bǔ)丁程序和將補(bǔ)丁程序附加到目標(biāo)PE的補(bǔ)丁工具。13.1動(dòng)態(tài)補(bǔ)丁動(dòng)態(tài)補(bǔ)丁是指目標(biāo)PE處于活動(dòng)狀態(tài)時(shí)(即進(jìn)程)為其實(shí)施的補(bǔ)丁。PE文件被映像加載器裝載到內(nèi)存后,就變成了進(jìn)程,由Windows子系統(tǒng)調(diào)度PE映像里預(yù)先存放的指令代碼完成指定的功能。前面講過,每個(gè)進(jìn)程其存取空間為4GB,各進(jìn)程的地址空間獨(dú)立,相互之間并不影響,動(dòng)態(tài)補(bǔ)丁技術(shù)即要求我們打破這種傳統(tǒng)的認(rèn)識(shí),實(shí)現(xiàn)一個(gè)進(jìn)程可以操作另外一個(gè)進(jìn)程的地址空間。動(dòng)態(tài)補(bǔ)丁常用于游戲修改器、動(dòng)態(tài)調(diào)試、病毒生存等領(lǐng)域。一個(gè)完整的動(dòng)態(tài)補(bǔ)丁一般需要具備以下四個(gè)要素:與其他進(jìn)程通信的能力。良好的讀寫其他進(jìn)程地址空間的能力。能正確識(shí)別要補(bǔ)丁的目標(biāo)進(jìn)程。在其他進(jìn)程地址空間執(zhí)行代碼的能力。下面就針對(duì)以上四點(diǎn)展開討論。13.1.1進(jìn)程間的通信機(jī)制在實(shí)施補(bǔ)丁過程中,兩個(gè)進(jìn)程之間會(huì)相互交換數(shù)據(jù),如補(bǔ)丁程序必須動(dòng)態(tài)獲取目標(biāo)進(jìn)程運(yùn)行的狀態(tài),以確定在什么時(shí)候,什么地點(diǎn)實(shí)施補(bǔ)丁。這些信息的傳遞需要用到Windows系統(tǒng)中進(jìn)程間的數(shù)據(jù)通信機(jī)制。在Windows中,實(shí)現(xiàn)進(jìn)程間通信的機(jī)制有很多方法,歸納一下分為兩大類:一種是通過兩個(gè)進(jìn)程實(shí)施的耦合性強(qiáng)的進(jìn)程間通信。這種通信機(jī)制要求參與通信的兩個(gè)進(jìn)程必須密切配合,兩個(gè)進(jìn)程工作在服務(wù)器/客戶端模式。這類通信機(jī)制主要包括匿名管道、命名管道、郵件槽、遠(yuǎn)程方法調(diào)用等。另外一種是由第三方參與的耦合性相對(duì)較弱的進(jìn)程間通信,比如通過剪貼板、共享內(nèi)存、動(dòng)態(tài)鏈接庫(kù)、映射文件、注冊(cè)表、一般文件、Socket、Windows消息隊(duì)列、信號(hào)量等。以下是常見的進(jìn)程通信機(jī)制。1.管道技術(shù)管道(pipe)是一種具有兩個(gè)端點(diǎn)的通信通道,兩個(gè)端點(diǎn)分別連接兩個(gè)進(jìn)程。管道可以是一個(gè)方向的,也可以是兩個(gè)方向的;連接管道的兩個(gè)端點(diǎn)既可以從管道中讀取數(shù)據(jù),也可以將數(shù)據(jù)寫進(jìn)管道。匿名管道(AnonymousPipe)存在于父進(jìn)程與子進(jìn)程之間,由于它連接了兩個(gè)具有繼承關(guān)系的進(jìn)程,所以該管道不需要名字,管道的創(chuàng)建由父進(jìn)程完成。匿名管道是單機(jī)上實(shí)現(xiàn)子進(jìn)程標(biāo)準(zhǔn)I/O重定向的有效方法,它無法在網(wǎng)絡(luò)上使用,也不能用于兩個(gè)不相關(guān)的進(jìn)程。創(chuàng)建匿名管道的API函數(shù)是CreatePipe。命名管道(NamedPipe)是服務(wù)器進(jìn)程和一個(gè)或多個(gè)客戶進(jìn)程之間通信的單向或雙向管道。創(chuàng)建管道的服務(wù)器端在建立管道時(shí)會(huì)給管道指定一個(gè)名字,其他任何進(jìn)程都可以通過這個(gè)名字打開管道的另一端,并根據(jù)給定的權(quán)限與創(chuàng)建管道的進(jìn)程實(shí)施通信。創(chuàng)建命名管道的API函數(shù)是CreateNamedPipe。2.郵件槽單一的郵件槽(MailSlots)提供了兩個(gè)進(jìn)程間的單向通信能力。由一個(gè)進(jìn)程建立郵件槽從而成為郵件槽服務(wù)器,而其他進(jìn)程,則通過郵件槽的名字向服務(wù)器發(fā)送消息。該消息一直處在郵件槽中直到服務(wù)器讀取它。這種機(jī)制與命名管道的機(jī)制類似,都是基于SOCKET技術(shù)通過端口實(shí)現(xiàn)的,但兩者傳遞數(shù)據(jù)的協(xié)議不同。如果要建立雙向的通信,則客戶端也可以建立相同的郵件槽,從而使得客戶端同時(shí)具備服務(wù)器和客戶端兩種角色。兩個(gè)這樣的進(jìn)程連在一起就形成了一種雙向的可讀寫的通信通道。由于郵件槽使用了不可靠的數(shù)據(jù)報(bào)協(xié)議,所以其通常用于廣播消息。創(chuàng)建郵件槽的API函數(shù)是CreateMailslot。3.剪貼板剪貼板(ClippedBoard)是為應(yīng)用程序之間進(jìn)行數(shù)據(jù)共享而提供的一個(gè)第三方的數(shù)據(jù)存儲(chǔ)區(qū)。兩個(gè)需要傳遞數(shù)據(jù)的進(jìn)程無需進(jìn)行協(xié)商,由一方通過剪切(或復(fù)制)操作實(shí)施數(shù)據(jù)的轉(zhuǎn)移,另一方則可以在任何時(shí)刻(保證剪貼板中數(shù)據(jù)沒有被重新覆蓋)從剪貼板中取回?cái)?shù)據(jù)。進(jìn)程間存取數(shù)據(jù)唯一的限制是兩者必須使用同樣格式的數(shù)據(jù)。4.共享內(nèi)存共享內(nèi)存是文件映射機(jī)制的一個(gè)特例。內(nèi)存映射文件(MemoryMappedFiles)將磁盤不連續(xù)存儲(chǔ)的文件復(fù)制到連續(xù)內(nèi)存空間中,在前面有所介紹。Win32API允許多個(gè)進(jìn)程訪問同一文件映射對(duì)象,各個(gè)進(jìn)程在它自己的地址空間里接收指向內(nèi)存線性文件的指針。通過使用這些指針,不同進(jìn)程就可以讀寫文件的內(nèi)容,從而實(shí)現(xiàn)對(duì)文件中數(shù)據(jù)的共享。Win32API中共享內(nèi)存(SharedMemory)實(shí)際是文件映射的一種特殊情況。進(jìn)程在創(chuàng)建文件映射對(duì)象時(shí)用0xFFFFFFFF來代替正常的文件句柄,表示對(duì)應(yīng)的文件映射對(duì)象是從操作系統(tǒng)頁(yè)面文件來訪問內(nèi)存,其他進(jìn)程只要打開該文件映射對(duì)象就可以訪問該內(nèi)存塊,進(jìn)而實(shí)現(xiàn)多進(jìn)程共享同一段內(nèi)存數(shù)據(jù)的目的。建立內(nèi)存映射對(duì)象的API函數(shù)是CreateFileMapping,其他進(jìn)程訪問內(nèi)存映射文件的API函數(shù)是OpenFileMapping。5.消息機(jī)制消息機(jī)制是Windows應(yīng)用程序的核心,在Windows中發(fā)生的大部分事件都可以用消息來表示。消息可以告訴操作系統(tǒng)發(fā)生了什么,所有的Windows應(yīng)用程序都是消息驅(qū)動(dòng)的??梢哉f,消息機(jī)制是Windows系統(tǒng)間、進(jìn)程間傳遞數(shù)據(jù)的最好的方法。窗口移動(dòng)、鼠標(biāo)點(diǎn)擊、鍵盤按鍵等事件的發(fā)生,以及程序的啟動(dòng)或退出都會(huì)產(chǎn)生標(biāo)準(zhǔn)的Windows消息。這些消息告訴Windows操作系統(tǒng)(或接管了消息處理的程序)當(dāng)前系統(tǒng)(或進(jìn)程)的運(yùn)行狀態(tài)。當(dāng)然,Windows也提供了一些其他的非標(biāo)準(zhǔn)的消息,如異常消息,這些消息與系統(tǒng)的某些機(jī)制相關(guān)。動(dòng)態(tài)補(bǔ)丁程序使用消息機(jī)制,配合內(nèi)存讀寫來實(shí)現(xiàn)進(jìn)程間數(shù)據(jù)傳遞會(huì)相對(duì)容易些。一個(gè)進(jìn)程在運(yùn)行時(shí)會(huì)根據(jù)運(yùn)行狀態(tài)產(chǎn)生各種消息(比如,因異常事件觸發(fā)的異常消息、程序中由中斷指令引發(fā)的調(diào)試消息等),這些消息會(huì)通過進(jìn)程的外露端口(如異常調(diào)試消息經(jīng)由異常端口)傳輸出去。由于引發(fā)異常調(diào)試消息事件EXCEPTION_DEBUG_EVENT的指令最為精簡(jiǎn)(即int3指令,一個(gè)字節(jié),十六進(jìn)制字節(jié)碼為0CCh),所以這種進(jìn)程間傳遞數(shù)據(jù)的方式被普遍用在動(dòng)態(tài)補(bǔ)丁技術(shù)中。通過該消息傳遞數(shù)據(jù)的流程如圖13-1所示。圖13-1動(dòng)態(tài)補(bǔ)丁中的信息傳遞如圖所示,補(bǔ)丁工具通過進(jìn)程內(nèi)存讀寫技術(shù)將補(bǔ)丁代碼寫入目標(biāo)進(jìn)程指定位置(補(bǔ)丁代碼為int3,即0CCh);同時(shí),補(bǔ)丁工具記錄該位置的字節(jié)值。當(dāng)目標(biāo)進(jìn)程執(zhí)行到該位置時(shí)將產(chǎn)生異常,操作系統(tǒng)將該異常產(chǎn)生時(shí)的相關(guān)信息(各寄存器的值等)包裝到數(shù)據(jù)結(jié)構(gòu)EXCEPTION_RECORD中,并查找此時(shí)調(diào)試目標(biāo)進(jìn)程的進(jìn)程(即動(dòng)態(tài)補(bǔ)丁工具),操作系統(tǒng)將該結(jié)構(gòu)通過消息傳遞給動(dòng)態(tài)補(bǔ)丁工具;然后,由動(dòng)態(tài)補(bǔ)丁工具完成對(duì)目標(biāo)進(jìn)程信息的解讀,將解讀以后的相關(guān)信息再有選擇地重新寫回到目標(biāo)進(jìn)程地址空間,以改變目標(biāo)進(jìn)程的運(yùn)行行為或當(dāng)前的進(jìn)程狀態(tài)。13.1.2讀寫進(jìn)程內(nèi)存讀寫其他進(jìn)程內(nèi)存地址空間是動(dòng)態(tài)補(bǔ)丁必須具備的功能,WindowsAPI中提供了讀寫其他進(jìn)程地址空間的函數(shù)。首先介紹這些相關(guān)的函數(shù)。1.相關(guān)函數(shù)Windows的安全機(jī)制不允許一個(gè)進(jìn)程直接讀寫其他進(jìn)程空間的數(shù)據(jù),除非使用了特定的WindowsAPI函數(shù)。這些函數(shù)包括:OpenProcess(通過設(shè)置訪問權(quán)限打開要讀寫的進(jìn)程)ReadProcessMemory(實(shí)現(xiàn)打開進(jìn)程空間數(shù)據(jù)的讀?。¦riteProcessMemory(完成向打開的進(jìn)程空間寫入數(shù)據(jù))以下是這三個(gè)函數(shù)的詳細(xì)介紹。(1)OpenProcess函數(shù)OpenProcess函數(shù)用來打開一個(gè)已存在的進(jìn)程對(duì)象,并返回進(jìn)程的句柄。函數(shù)原型定義如下:HANDLEOpenProcess(DWORDdwDesiredAccess,//訪問權(quán)限BOOLbInheritHandle,//繼承標(biāo)志,若句柄能由子進(jìn)程繼承,則設(shè)置為TRUEDWORDdwProcessId//進(jìn)程號(hào));各參數(shù)解釋如下:1)dwDesiredAccess:訪問權(quán)限。它可以是表13-1所列的值。2)bInheritHandle:繼承標(biāo)志;如果設(shè)置為TRUE,表示繼承打開的進(jìn)程,否則表示不繼承。3)dwProcessId:進(jìn)程的ID號(hào)。4)返回值:如成功,返回值為指定進(jìn)程的句柄;如失敗,返回值為空??烧{(diào)用GetLastError獲得錯(cuò)誤代碼。(2)ReadProcessMemory函數(shù)讀進(jìn)程內(nèi)存函數(shù)。以下是函數(shù)原型:BOOLReadProcessMemory(HANDLEhProcess,//遠(yuǎn)程進(jìn)程句柄PVOIDpvAddressRemote,//遠(yuǎn)程進(jìn)程地址VA值PVOIDpvBufferLocal,//存放數(shù)據(jù)的緩沖區(qū)DWORDdwSize,//緩沖區(qū)大小PDWORDpdwNumBytesRead//讀出的實(shí)際字節(jié)數(shù),是輸出參數(shù));各參數(shù)解釋如下:1)hProcess:遠(yuǎn)程進(jìn)程的句柄,遠(yuǎn)程進(jìn)程即為要操作的進(jìn)程。2)pvAddressRemote:要操作的進(jìn)程的地址空間,該地址為VA。3)pvBufferLocal:存放要操作的數(shù)據(jù)的本地緩沖區(qū)。4)dwSize:本地緩沖區(qū)大小。5)pdwNumBytesRead:輸出參數(shù),表示本次讀取的實(shí)際字節(jié)數(shù)。6)返回值:如成功,返回TRUE,否則返回NULL。(3)WriteProcessMemory函數(shù)寫進(jìn)程內(nèi)存函數(shù)。完整定義如下:BOOLWriteProcessMemory(HANDLEhProcess,//遠(yuǎn)程進(jìn)程句柄PVOIDpvAddressRemote,//遠(yuǎn)程進(jìn)程地址VA值PVOIDpvBufferLocal,//存放數(shù)據(jù)的緩沖區(qū)DWORDdwSize,//緩沖區(qū)大小PDWORDpdwNumBytesRead//讀寫的字節(jié)數(shù),是返回值);讀進(jìn)程內(nèi)存和寫進(jìn)程內(nèi)存的函數(shù)的參數(shù)定義是一樣的,各參數(shù)的解釋如下:1)hProcess:指定將要被讀寫的目標(biāo)進(jìn)程句柄。2)pvAddressRemote:目標(biāo)進(jìn)程中被讀寫的起始線性地址。3)pvBufferLocal:用來接收讀取數(shù)據(jù)的緩沖區(qū)(對(duì)于ReadProcessMemory函數(shù))或者要寫到目標(biāo)進(jìn)程的數(shù)據(jù)緩沖區(qū)(對(duì)于WriteProcessMemory函數(shù))。4)dwSize:要讀寫的字節(jié)數(shù)。5)pdwNumBytesRead:指向一個(gè)雙字變量,供函數(shù)返回實(shí)際讀寫的字節(jié)數(shù);如果不關(guān)心這個(gè)結(jié)果,可以將其設(shè)置為NULL。6)返回值:如果函數(shù)執(zhí)行成功,那么返回值是非0值,執(zhí)行失敗的話返回0。2.讀寫進(jìn)程內(nèi)存實(shí)例分析如果大家經(jīng)常使用PEInfo小工具,就會(huì)發(fā)現(xiàn)該工具在查看一些有大量數(shù)據(jù)的PE時(shí),經(jīng)常會(huì)出現(xiàn)界面“死住”的情況,關(guān)于原因和解決方法已經(jīng)在第2章里講過了,現(xiàn)在來看看通過動(dòng)態(tài)補(bǔ)丁技術(shù)如何解決這一問題。(1)修改代碼為了簡(jiǎn)化問題的描述,配合大家理解進(jìn)程內(nèi)存讀寫,本實(shí)例采用了一些技巧,比如,在需要打補(bǔ)丁的PEInfo.asm中,做了如下修改:步驟1在數(shù)據(jù)段中添加一個(gè)標(biāo)志dwFlag:szFileNamedbMAX_PATHdup(?),0FFhdwFlagdd0FFFFFFFFh;新增加的標(biāo)志szDllEditdb'RichEd20.dll',0szClassEditdb'RichEdit20A',0步驟2在代碼中增加檢測(cè)標(biāo)志位的代碼:.while[esi].VirtualAddresscld……invoke_appendInfo,addr@szBufferpopecx;重定位項(xiàng)數(shù)量xoredi,edi.repeatpushecxinvoke_appendInfo,addr@szBufferpopecx.break.ifdwFlag==1;加入一個(gè)看似永遠(yuǎn)也不可能成立的條件;該標(biāo)志會(huì)由其他進(jìn)程修改!.untilcxz.break.ifdwFlag==1;加入一個(gè)看似永遠(yuǎn)也不可能成立的條件;該標(biāo)志會(huì)由其他進(jìn)程修改!.ifediinvoke_appendInfo,addrszCrLf.endif.endw在兩層循環(huán)中均增加了對(duì)標(biāo)志位的判斷,如果該標(biāo)志位值為1,則退出循環(huán)。因?yàn)閐wFlag在定義時(shí)被設(shè)置為值0xffffffff,并且chapter13\PEInfo.asm中再也沒有與dwFlag有關(guān)的賦值代碼,所以,該程序中這個(gè)退出條件似乎永遠(yuǎn)也不會(huì)發(fā)生。(2)補(bǔ)丁工具源代碼動(dòng)態(tài)補(bǔ)丁技術(shù)是通過修改進(jìn)程內(nèi)存空間數(shù)據(jù),使被修改進(jìn)程發(fā)生程序流向轉(zhuǎn)移的技術(shù)。起到這種作用的程序一般稱為補(bǔ)丁工具,代碼清單13-1是補(bǔ)丁工具的部分源代碼(完整代碼請(qǐng)參照隨書文件chapter13\DPatchPEInfo.asm)。代碼清單13-1讀寫進(jìn)程內(nèi)存的函數(shù)_writeToPEInfo(chapter13\DPatchPEInfo.asm)1;2;讀寫內(nèi)存示例3;測(cè)試方法:首先運(yùn)行PEInfo.exe4;顯示Kernel32.dll的信息5;啟動(dòng)該程序,在kernel32.dll顯示重定位時(shí)單擊菜單第一項(xiàng)6;會(huì)發(fā)現(xiàn)PEInfo.exe的遍歷重定位信息被終止7;8_writeToPEInfoproc9pushad1011;通過標(biāo)題獲得進(jìn)程的handle12invokeGetDesktopWindow13invokeGetWindow,eax,GW_CHILD14invokeGetWindow,eax,GW_HWNDFIRST15movphwnd,eax16invokeGetParent,eax17.if!eax18movparent,119.endif2021moveax,phwnd22.whileeax23.ifparent24movparent,0;復(fù)位標(biāo)志25;得到窗口標(biāo)題文字26invokeGetWindowText,phwnd,addrstrTitle,\27sizeofstrTitle28nop29invokelstrcmp,addrstrTitle,addrszTitle30.if!eax31moveax,phwnd32.break33.endif34.endif3536;尋找這個(gè)窗口的下一個(gè)兄弟窗口37invokeGetWindow,phwnd,GW_HWNDNEXT38movphwnd,eax39invokeGetParent,eax40.if!eax41invokeIsWindowVisible,phwnd42.ifeax43movparent,144.endif45.endif46moveax,phwnd47.endw4849;moveax,phwnd50;invokewsprintf,addrszBuffer,addrszOut1,eax51;invokeMessageBox,NULL,addrszBuffer,NULL,MB_OK5253;根據(jù)窗口句柄獲取進(jìn)程ID54invokeGetWindowThreadProcessId,phwnd,addrhProcessID5556;moveax,hProcessID57;invokewsprintf,addrszBuffer,addrszOut2,eax58;invokeMessageBox,NULL,addrszBuffer,NULL,MB_OK5960invokeOpenProcess,PROCESS_ALL_ACCESS,\61FALSE,hProcessID62.if!eax63invokeMessageBox,NULL,addrszErr1,NULL,MB_OK64jmp@ret65.endif66movhProcess,eax;找到的進(jìn)程句柄在hProcess中676869;invokewsprintf,addrszBuffer,addrszOut3,eax70;invokeMessageBox,NULL,addrszBuffer,NULL,MB_OK717273;將進(jìn)程掛起74;invoke_suspendProcess,hProcess7576;讀內(nèi)存77invokeReadProcessMemory,hProcess,STOP_FLAG_POSITION,\78addrdwFlag,4,NULL79.ifeax80;moveax,dwFlag81;invokewsprintf,addrszBuffer,addrszOut,eax82;invokeMessageBox,NULL,addrszBuffer,NULL,MB_OK8384;寫內(nèi)存,將標(biāo)志位賦值85invokeWriteProcessMemory,hProcess,\86STOP_FLAG_POSITION,\87addrdwPatchDD,4,NULL88.else89invokeMessageBox,NULL,addrszErr2,NULL,MB_OK90jmp@ret91.endif9293;繼續(xù)進(jìn)程的運(yùn)行94;invoke_resumeProcess,hProcess95invokeCloseHandle,hProcess9697@ret:98popad99ret100_writeToPEInfoendpSTOP_FLAG_POSITION是PEInfo.asm中定義的標(biāo)志dwFlag所在進(jìn)程地址空間中的VA值。該位置可以通過以下方法計(jì)算出來:首先,找到dwFlag變量在文件中的位置(加黑部分):如上所示,標(biāo)志字節(jié)dwFlag在文件地址0x00002510處。通過PEInfo小工具查看該文件的所有的節(jié)的相關(guān)信息,結(jié)果如下:根據(jù)RVA和FOA的換算關(guān)系可以得出,停止標(biāo)志位的內(nèi)存地址(VA)為:0x00404115。在DPatchPEInfo.asm的開始部分聲明一個(gè)常量即可:STOP_FLAG_POSITION=00404115h(3)運(yùn)行測(cè)試接下來,就是見證奇跡的時(shí)刻。整體思路是:當(dāng)PEInfo小工具運(yùn)行時(shí),如果用戶發(fā)現(xiàn)要獲取的信息已經(jīng)輸出,則可以通過另外一個(gè)程序終止PEInfo代碼的運(yùn)行(注意,不是終止進(jìn)程的運(yùn)行),看起來好像這個(gè)補(bǔ)丁程序也參與了PEInfo指令代碼的流程控制過程?,F(xiàn)在來看該動(dòng)態(tài)補(bǔ)丁的三要素:1)該補(bǔ)丁可以讀寫PEInfo.exe的進(jìn)程內(nèi)存。2)該補(bǔ)丁的調(diào)用時(shí)機(jī)由用戶判斷。當(dāng)發(fā)現(xiàn)需要的信息已經(jīng)輸出,即選擇菜單選項(xiàng)“文件”|“停止遍歷重定位表”,補(bǔ)丁會(huì)立刻生效,補(bǔ)丁的位置已經(jīng)事先計(jì)算出來了。3)盡管補(bǔ)丁沒有實(shí)現(xiàn)代碼部分,為了簡(jiǎn)化任務(wù),代碼事先已經(jīng)安排到原始程序中等待補(bǔ)丁對(duì)它的激活。以后我們看到的大部分的動(dòng)態(tài)補(bǔ)丁則是將要追加的代碼通過補(bǔ)丁工具直接寫入補(bǔ)丁程序。擴(kuò)展閱讀關(guān)于熱補(bǔ)通常情況下,補(bǔ)丁程序總是在被補(bǔ)丁的程序后期開發(fā)的,有時(shí)候,為了后期補(bǔ)丁方便,有些程序會(huì)事先給自己留一個(gè)“后門”。比如,微軟的大部分內(nèi)核函數(shù),檢查Windows的ntdll.dll中大部分函數(shù)的源代碼,其起始位置的代碼看起來總是這樣的:MOVEDI,EDIPUSHEBPMOVEBP,ESP"movedi,edi"指令為兩個(gè)字節(jié),可以存放短跳轉(zhuǎn)指令,短跳轉(zhuǎn)指令指向的位置可以存放一個(gè)指向長(zhǎng)跳轉(zhuǎn)指令的地址,從而在運(yùn)行期實(shí)現(xiàn)熱補(bǔ)(hotfix)。在VisualC++的編譯器選項(xiàng)中也有類似的參數(shù)/hotpatch可以創(chuàng)建可熱修補(bǔ)的PE映像。以上只是演示了一個(gè)思路,大家可以在此基礎(chǔ)上自行添加功能,比如提升補(bǔ)丁工具的權(quán)限、修改進(jìn)程空間代碼、增強(qiáng)內(nèi)存讀寫能力等。13.1.3目標(biāo)進(jìn)程枚舉在進(jìn)行動(dòng)態(tài)補(bǔ)丁時(shí),有一步是必需的,即獲取目標(biāo)進(jìn)程的句柄或者ID號(hào)。通過枚舉系統(tǒng)進(jìn)程即可獲取這些信息。1.枚舉系統(tǒng)進(jìn)程的方法枚舉Win32子系統(tǒng)進(jìn)程的方法很多,常見的有以下四種。(1)調(diào)用PSAPI.DLL提供的函數(shù)該動(dòng)態(tài)鏈接庫(kù)是微軟WindowsNT開發(fā)小組開發(fā)的與進(jìn)程有關(guān)的函數(shù)集。核心函數(shù)包括:(2)調(diào)用ToolHelpAPI提供的函數(shù)ToolHelp32函數(shù)是一組存儲(chǔ)在Kernel32.dll中的WindowsAPI函數(shù),它能夠通過Snapshot獲得駐
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025屆云南紅河州第一中學(xué)高三3月模擬檢測(cè)試題物理試題含解析
- 湖北省普通高中聯(lián)考協(xié)作體2025屆高三下學(xué)期統(tǒng)練(七)化學(xué)試題含解析
- 曲靖師范學(xué)院《信息資源組織與管理》2023-2024學(xué)年第二學(xué)期期末試卷
- 指甲美容市場(chǎng)調(diào)查問卷
- 關(guān)于家庭花草種植調(diào)查問卷
- 粉煤灰施工方案
- 水泥庫(kù)清庫(kù)施工方案
- 水處理建筑施工方案
- 室外保溫施工方案
- 2025年學(xué)生分班測(cè)試題及答案
- 老舍讀書分享名著導(dǎo)讀《貓城記》
- 學(xué)科國(guó)際發(fā)展趨勢(shì)
- 初一年級(jí)班級(jí)日志記載表(詳)
- 建設(shè)工程安全生產(chǎn)管理習(xí)題庫(kù)及答案
- 項(xiàng)目1 多旋翼無人機(jī)的組裝與調(diào)試
- 供應(yīng)鏈管理:高成本、高庫(kù)存、重資產(chǎn)的解決方案 第2版
- 馬克筆建筑快速表現(xiàn)
- 日本夏日祭活動(dòng)鑒賞
- 中國(guó)教育史筆記全
- 某工業(yè)鍋爐安裝工程監(jiān)理作業(yè)指導(dǎo)書
- 名校《強(qiáng)基計(jì)劃》初升高銜接數(shù)學(xué)講義(上)
評(píng)論
0/150
提交評(píng)論