• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            codeArt

            codeArt

            UTF-8編碼規(guī)則

            1字節(jié) 0xxxxxxx 
            2字節(jié) 110xxxxx 10xxxxxx 
            3字節(jié) 1110xxxx 10xxxxxx 10xxxxxx 
            4字節(jié) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
            5字節(jié) 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
            6字節(jié) 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
            所以,UTF-8編碼的實際位數(shù)是31

            posted @ 2010-08-17 07:41 codeArt 閱讀(219) | 評論 (0)編輯 收藏

            Word編程c++參考

            http://www.feiesoft.com/vba/word/
            http://hopf.chem.brandeis.edu/yanglingfa/Qt/COM/wd/html/main.html


            posted @ 2010-07-15 17:20 codeArt 閱讀(411) | 評論 (0)編輯 收藏

            dumpbin

            在查看lib的信息時,可以用DUMPBIN。EXE來得到某個DLL中所輸出的符號的清單。如下面的

            命令:dumpbin /exports Cmpnt1.dll
            如:
            C:\WINDOWS\system32>dumpbin -exports msgsvc.dll
            Microsoft (R) COFF Binary File Dumper Version 6.00.8447
            Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


            Dump of file msgsvc.dll

            File Type: DLL

              Section contains the following exports for msgsvc.dll

                       0 characteristics
                41107F60 time date stamp Wed Aug 04 14:17:04 2004
                    0.00 version
                       1 ordinal base
                       2 number of functions
                       2 number of names

                ordinal hint RVA      name

                      1    0 00004ABF ServiceMain
                      2    1 00004595 SvchostPushServiceGlobals

              Summary

                    1000 .data
                    1000 .reloc
                    1000 .rsrc
                    8000 .text


            C:\>dumpbin
            Microsoft (R) COFF Binary File Dumper Version 6.00.844
            Copyright (C) Microsoft Corp 1992-1998. All rights res

            usage: DUMPBIN [options] [files]

               options:

                  /ALL
                  /ARCH
                  /ARCHIVEMEMBERS
                  /DEPENDENTS
                  /DIRECTIVES
                  /DISASM
                  /EXPORTS
                  /FPO
                  /HEADERS
                  /IMPORTS
                  /LINENUMBERS
                  /LINKERMEMBER[:{1|2}]
                  /LOADCONFIG
                  /OUT:filename
                  /PDATA
                  /RAWDATA[:{NONE|BYTES|SHORTS|LONGS}[,#]]
                  /RELOCATIONS
                  /SECTION:name
                  /SUMMARY
                  /SYMBOLS


            若要運行 DUMPBIN,請使用下列語法:

            DUMPBIN [options] files...

            指定一個或多個二進制文件,以及控制信息所需的任何選項。 DUMPBIN 將該信息顯示到標準輸出。可以將輸出重定向到文件,或者使用 /OUT 選項為輸出指定文件名。

            當在文件上運行 DUMPBIN 但未指定選項時,DUMPBIN 顯示 /SUMMARY 輸出。

            當鍵入命令 dumpbin 但沒有任何其他命令行輸入時,DUMPBIN 顯示匯總其選項的用法語句。

            DUMPBIN 選項

            /ALL

            此選項顯示除代碼反匯編外的所有可用信息。使用 /DISASM 顯示反匯編。可以與 /ALL 一起使用 /RAWDATA:NONE 來省略文件的原始二進制詳細資料。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /ARCHIVEMEMBERS

            此選項顯示有關庫成員對象的最少信息。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /CLRHEADER file

            此處:

            file
            /clr 生成的圖像文件。

            備注

            CLRHEADER 顯示有關在任何托管程序中使用的 .NET 頭的信息。輸出顯示 .NET 頭及其中各節(jié)的位置和大小(以字節(jié)計)。

            File Format Spec.doc 描述 .NET 頭中的信息。NET SDK 將 File Format Spec.doc 安裝在 Tools Developers Guide 目錄中。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /DIRECTIVES

            此選項轉儲圖像中由編譯器生成的 .directive 節(jié)。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /DEPENDENTS

            轉儲圖像從中導入函數(shù)的 DLL 的名稱。不要轉儲導入函數(shù)名。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /DISASM

            此選項顯示代碼段的反匯編,如果出現(xiàn)在文件中則使用符號。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /EXPORTS

            此選項顯示從可執(zhí)行文件或 DLL 導出的所有定義。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /FPO

            此選項顯示框架指針優(yōu)化 (FPO) 記錄。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /HEADERS

            此選項顯示文件頭和每節(jié)的頭。當用于庫時,顯示每個成員對象 的頭。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /IMPORTS[:file]

            此選項顯示導入到可執(zhí)行文件或 DLL 的 DLL 列表(靜態(tài)鏈接的和延遲加載)和上述每個 DLL 的各個導入。

            可選 file 規(guī)范允許指定僅顯示某個 DLL 的導入。例如:

            dumpbin /IMPORTS:msvcrt.dll

            此選項顯示的輸出與 /EXPORTS 輸出相似。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /LINENUMBERS

            此選項顯示 COFF 行號。如果對象文件是用程序數(shù)據(jù)庫 (/Zi)、C7 兼容 (/Z7) 或僅限行號 (/Zd) 編譯的,則它包含行號。如果可執(zhí)行文件或 DLL 是與生成調試信息 (/DEBUG) 鏈接的,則它包含 COFF 行號。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /LINKERMEMBER[:{1|2}]

            此選項顯示庫中定義的公共符號。指定參數(shù) 1 將按對象順序顯示符號及其偏移量。指定參數(shù) 2 將顯示對象的偏移量和索引號,然后按字母順序列出這些符號及每個符號的對象索引。若要兩個輸出都獲得,指定不帶數(shù)字參數(shù)的 /LINKERMEMBER。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /LOADCONFIG

            此選項轉儲 IMAGE_LOAD_CONFIG_DIRECTORY 結構,此結構是由 Windows NT 加載程序使用并在 WINNT.H 中定義的可選結構。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /OUT:filename

            此選項指定輸出的 filename。默認情 況下,DUMPBIN 將信息顯示到標準輸出。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /PDBPATH[:VERBOSE] filename

            此處:

            filename
            要為其查找匹配 .pdb 文件的 .dll 或 .exe 文件名。
            VERBOSE(可選)
            報告曾嘗試在其中定位 .pdb 文件的所有目錄。

            備注

            /PDBPATH 將沿調試器搜索 .pdb 文件的同一路徑搜索計算機,并將報告哪些 .pdb 文件(若有)和 filename 中指定的文件相對應。

            使用 Visual Studio 調試器時可能會遇到問題,這是因為調試器對調試文件的不同版本使用 .pdb 文件。

            /PDBPATH 將沿下列路徑搜索 .pdb 文件:

            • 檢查可執(zhí)行文件駐留的位置。
            • 檢查寫入可執(zhí)行文件的 PDB 的位置。這通常是圖像被鏈接時的位置。
            • 沿 Visual Studio IDE 中配置的搜索路徑檢查。
            • 沿 _NT_SYMBOL_PATH 和 _NT_ALT_SYMBOL_PATH 環(huán)境變量中的路徑檢查。
            • 在 Windows 目錄中檢查。
            /PDATA

            僅用于 RISC 處理器。

            此選項從圖像或對象轉儲異常表 (.pdata)。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /RAWDATA[:{1|2|4|8|NONE[,number]]

            此選項顯示文件中每節(jié)的原始內容。參數(shù)控制顯示格式,如下所 示:

            參數(shù) 結果
            1 默認值。內容以十六進制字節(jié)顯 示,如果內容具有打印的表示形式,則還顯示為 ASCII 字符。
            2 內容顯示為十六進制的 2 字節(jié)值。
            4 內容顯示為十六進制的 4 字節(jié)值。
            8 內容顯示為十六進制的 8 字節(jié)值。
            NONE 取消顯示原始數(shù)據(jù)。此參數(shù)對控制 /ALL 輸出很有用。
            Number 顯示的行被設置為每行具有 number 個值的寬度。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /RELOCATIONS

            此選項顯示對象或圖像中的任何重定位。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /SECTION:section

            此選項限制與指定的 section 有關的信息的輸出。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /SUMMARY

            此選項顯示有關節(jié)的最少信息(包括總大小)。如果未指定其他 選項,則此選項為默認值。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /SYMBOLS

            此選項顯示 COFF 符號表。符號表存在于所有對象文件中。而對于圖像文件,只有當它是與 /DEBUG 鏈接的時,它才包含 COFF 符號表。

            下面是關于 /SYMBOLS 輸出的說明。通過查閱 winnt.h(IMAGE_SYMBOL 和 IMAGE_AUX_SYMBOL)或 COFF 文檔,可找到有關 /SYMBOLS 輸出含義的附加信息。

            假設有下列示例轉儲:

            Dump of file main.obj
            File Type: COFF OBJECT

            COFF SYMBOL TABLE
            000 00000000 DEBUG notype Filename | .file
            main.cpp
            002 000B1FDB ABS notype Static | @comp.id
            003 00000000 SECT1 notype Static | .drectve
            Section length 26, #relocs 0, #linenums 0, checksum 722C964F
            005 00000000 SECT2 notype Static | .text
            Section length 23, #relocs 1, #linenums 0, checksum 459FF65F, selection 1 (pick no duplicates)
            007 00000000 SECT2 notype () External | _main
            008 00000000 UNDEF notype () External | ?MyDump@@YAXXZ (void __cdecl MyDump(void))

            String Table Size = 0x10 bytes

            Summary

            26 .drectve
            23 .text

            對于以符號號碼開頭的行,下列說明描述了含有與用戶相關的信 息的列:

            • 開頭的 3 位數(shù)字是符號索引/號碼。
            • 如果第三列包含 SECTx,則 符號在對象文件的那一節(jié)中定義。但如果出現(xiàn) UNDEF,則它不在那個對象中定義并且必須在其他地方被解析。
            • 第五列 (Static, External) 說明符號是否只在那個對象的內部可見,或者是否是公共的(外部可見)。靜態(tài)符號 _sym 不會鏈接到公共符號 _sym;這些符號是名為 _sym 的函數(shù)的兩種不同實例。

            編號行中的最后一列是符號名(修飾名和未修飾名)。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。

            /UNWINDINFO

            在程序圖像(例如 exe 和 dll)中轉儲結構化異常處理 (SEH) 表的展開描述符。/UNWINDINFO 僅適用于 IA64 圖像。

            只有 /HEADERS DUMPBIN 選項可用于由 /GL 編譯器選項產(chǎn)生的文件。


            posted @ 2010-07-12 15:07 codeArt 閱讀(663) | 評論 (0)編輯 收藏

            開源協(xié)議

            當Adobe、Microsoft、Sun等一系列巨頭開始表現(xiàn)出對”開源”的青睞時,”開源”的時代即將到來!

            最初來自:sinoprise.com/read.php?tid-662-page-e-fpage-1.html(遺憾的是這個鏈接已經(jīng)打不開 了),我基本未改動,只是進行了一些排版和整理。
            參考文獻:http://www.fsf.org/licensing/licenses/

            現(xiàn)今存在的開源協(xié)議很多,而經(jīng)過Open Source Initiative組織通過批準的開源協(xié)議目前有58種(http://www.opensource.org/licenses/alphabetical)。 我們在常見的開源協(xié)議如BSD, GPL, LGPL,MIT等都是OSI批準的協(xié)議。如果要開源自己的代碼,最好也是選擇這些被批準的開源協(xié)議。

            這里我們來看四種最常用的開源協(xié)議及它們的適用范圍,供那些準備開源或者使用開源產(chǎn)品的開發(fā)人員/廠家參考。

            BSD開源協(xié)議( original BSD license FreeBSD license Original BSD license

            BSD開源協(xié)議是一個給于使用者很大自由的協(xié)議。基本上使用者可以”為所欲為”,可以自由的使用,修改源代碼,也可以將修改后的代碼作為開源或者專 有軟件再發(fā)布。

            但”為所欲為”的前提當你發(fā)布使用了BSD協(xié)議的代碼,或則以BSD協(xié)議代碼為基礎做二次開發(fā)自己的產(chǎn)品時,需要滿足三個條件:

            1. 如果再發(fā)布的產(chǎn)品中包含源代碼,則在源代碼中必須帶有原來代碼中的BSD協(xié)議。
            2. 如果再發(fā)布的只是二進制類庫/軟件,則需要在類庫/軟件的文檔和版權聲明中包含原來代碼中的BSD協(xié)議。
            3. 不可以用開源代碼的作者/機構名字和原來產(chǎn)品的名字做市場推廣。

            BSD 代碼鼓勵代碼共享,但需要尊重代碼作者的著作權。BSD由于允許使用者修改和重新發(fā)布代碼,也允許使用或在BSD代碼上開發(fā)商業(yè)軟件發(fā)布和銷售,因此是對 商業(yè)集成很友好的協(xié)議。而很多的公司企業(yè)在選用開源產(chǎn)品的時候都首選BSD協(xié)議,因為可以完全控制這些第三方的代碼,在必要的時候可以修改或者二次開發(fā)。

            Apache Licence 2.0( Apache License, Version 2.0Apache License, Version 1.1Apache License, Version 1.0

            Apache Licence是著名的非盈利開源組織Apache采用的協(xié)議。該協(xié)議和BSD類似,同樣鼓勵代碼共享和尊重原作者的著作權,同樣允許代碼修改,再發(fā)布 (作為開源或商業(yè)軟件)。需要滿足的條件也和BSD類似:

            1. 需要給代碼的用戶一份Apache Licence
            2. 如果你修改了代碼,需要再被修改的文件中說明。
            3. 在延伸的代碼中(修改和有源代碼衍生的代碼中)需要帶有原來代碼中的協(xié)議,商標,專利聲明和其他原來作者規(guī)定需要包含的說明。
            4. 如果再發(fā)布的產(chǎn)品中包含一個Notice文件,則在Notice文件中需要帶有Apache Licence。你可以在Notice中增加自己的許可,但不可以表現(xiàn)為對Apache Licence構成更改。

            Apache Licence也是對商業(yè)應用友好的許可。使用者也可以在需要的時候修改代碼來滿足需要并作為開源或商業(yè)產(chǎn)品發(fā)布/銷售。

            GPL( GNU General Public License

            我們很熟悉的Linux就是采用了GPL。GPL協(xié)議和BSD, Apache Licence等鼓勵代碼重用的許可很不一樣。GPL的出發(fā)點是代碼的開源/免費使用和引用/修改/衍生代碼的開源/免費使用,但不允許修改后和衍生的代 碼做為閉源的商業(yè)軟件發(fā)布和銷售。這也就是為什么我們能用免費的各種linux,包括商業(yè)公司的linux和linux上各種各樣的由個人,組織,以及商 業(yè)軟件公司開發(fā)的免費軟件了。

            GPL協(xié)議的主要內容是只要在一個軟件中使用(“使用”指類庫引用,修改后的代碼或者衍生代碼)GPL 協(xié)議的產(chǎn)品,則該軟件產(chǎn)品必須也采用GPL協(xié)議,既必須也是開源和免費。 這就是所謂的”傳染性” 。GPL協(xié)議的產(chǎn)品作 為一個單獨的產(chǎn)品使用沒有任何問題,還可以享受免費的優(yōu)勢。

            由于GPL嚴格要求使用了GPL類庫的軟件產(chǎn)品必須使用GPL協(xié)議,對于使用GPL協(xié)議的開源代碼,商業(yè)軟件或者對代碼有保密要求的部門就不適合集 成/采用作為類庫和二次開發(fā)的基礎。

            其它細節(jié)如再發(fā)布的時候需要伴隨GPL協(xié)議等和BSD/Apache等類似。

            LGPL( GNU Lesser General Public License

            LGPL是GPL的一個為主要為類庫使用設計的開源協(xié)議。和GPL要求任何使用/修改/衍生之GPL類庫的的軟件必須采用GPL協(xié)議不同。LGPL 允許商業(yè)軟件通過類庫引用(link)方式使用LGPL類庫而不需要開源商業(yè)軟件的代碼。這使得采用LGPL協(xié)議的開源代碼可以被商業(yè)軟件作為類庫引用并 發(fā)布和銷售。

            但是如果修改LGPL協(xié)議的代碼或者衍生,則所有修改的代碼,涉及修改部分的額外代碼和衍生的代碼都必須采用LGPL協(xié)議。因此LGPL協(xié)議的開源 代碼很適合作為第三方類庫被商業(yè)軟件引用,但不適合希望以LGPL協(xié)議代碼為基礎,通過修改和衍生的方式做二次開發(fā)的商業(yè)軟件采用。

            GPL/LGPL都保障原作者的知識產(chǎn)權,避免有人利用開源代碼復制并開發(fā)類似的產(chǎn)品

            MIT(MIT

            MIT是和BSD一樣寬范的許可協(xié)議,作者只想保留版權,而無任何其他了限制.也就是說,你必須在你的發(fā)行版里包含原許可協(xié)議的聲明,無論你是以二 進制發(fā)布的還是以源代碼發(fā)布的.


            posted @ 2010-07-09 12:17 codeArt 閱讀(167) | 評論 (0)編輯 收藏

            注入代碼到其他進程的方法

            http://www.codeproject.com/KB/threads/winspy.aspx

            Ⅰ. Windows 鉤子

            示例程序:HookSpy 和 HookInjEx

            Windows鉤子的主要作用就是監(jiān)視某個線程的消息流動。一般可分為:
            1. 局部鉤子,只監(jiān)視你自己進程中某個線程的消息流動。
            2. 遠程鉤子,又可以分為:
            a. 特定線程的,監(jiān)視別的進程中某個線程的消息;
            b. 系統(tǒng)級的,監(jiān)視整個系統(tǒng)中正在運行的所有線程的消息。

                如果被掛鉤(監(jiān)視)的線程屬于別的進程(情況2a和2b),你的鉤子過程(hook procedure)必須放在一個動態(tài)連接庫(DLL)中。系統(tǒng)把這包含了鉤子過程的DLL映射到被掛鉤的線程的地址空間。Windows會映射整個 DLL而不僅僅是你的鉤子過程。這就是為什么windows鉤子可以用來向其他線程的地址空間注入代碼的原因了。

                在這里我不想深入討論鉤子的問題(請看MSDN中對SetWindowsHookEx的說明),讓我再告訴你兩個文檔中找不到的訣竅,可能會有用:
            1. 當SetWindowHookEx調用成功后,系統(tǒng)會自動映射這個DLL到被掛鉤的線程,但并不是立即映射。因為所有的Windows鉤子都是基于消息 的,直到一個適當?shù)氖录l(fā)生后這個DLL才被映射。比如:
            如 果你安裝了一個監(jiān)視所有未排隊的(nonqueued)的消息的鉤子(WH_CALLWNDPROC),只有一個消息發(fā)送到被掛鉤線程(的某個窗口)后這 個DLL才被映射。也就是說,如果在消息發(fā)送到被掛鉤線程之前調用了UnhookWindowsHookEx那么這個DLL就永遠不會被映射到該線程(雖 然SetWindowsHookEx調用成功了)。為了強制映射,可以在調用SetWindowsHookEx后立即發(fā)送一個適當?shù)南⒌侥莻€線程。

                同理,調用UnhookWindowsHookEx之后,只有特定的事件發(fā)生后DLL才真正地從被掛鉤線程卸載。

            2. 當你安裝了鉤子后,系統(tǒng)的性能會受到影響(特別是系統(tǒng)級的鉤子)。然而如果你只是使用的特定線程的鉤子來映射DLL而且不截獲如何消息的話,這個缺陷也可 以輕易地避免。看一下下面的代碼片段:
            BOOL APIENTRY DllMain( HANDLE hModule,
                                   DWORD ul_reason_for_call,
                                   LPVOID lpReserved )
            {
                if( ul_reason_for_call == DLL_PROCESS_ATTACH )
                {
                    //用 LoadLibrary增加引用次數(shù)
                    char lib_name[MAX_PATH];
                    ::GetModuleFileName( hModule, lib_name, MAX_PATH );
                    ::LoadLibrary( lib_name );

                    // 安全卸載鉤子
                    ::UnhookWindowsHookEx( g_hHook );
                }   
                return TRUE;
            }

                我們來看一下。首先,我們用鉤子映射這個DLL到遠程線程,然后,在DLL被真正映射進去后,我們立即卸載掛鉤(unhook)。一般來說當?shù)谝粋€消息到 達被掛鉤線程后,這DLL會被卸載,然而我們通過LoadLibrary來增加這個DLL的引用次數(shù),避免了DLL被卸載。

                剩下的問題是:使用完畢后如何卸載這個DLL?UnhookWindowsHookEx不行了,因為我們已經(jīng)對那個線程取消掛鉤(unhook)了。你可 以這么做:
            ○在你想要卸載這個DLL之前再安裝一個鉤子;
            ○發(fā)送一個“特殊”的消息到遠程線程;
            ○在你的新鉤子的鉤子過程(hook procedure)中截獲該消息,調用FreeLibrary 和 (譯者注:對新鉤子調用)UnhookwindowsHookEx。
            現(xiàn)在,鉤子只在映射DLL到遠程進程和從遠程進程卸載DLL時使用,對被掛鉤線程的性能沒有影響。也就是說,我們找到了一種(相比第二部分討論的 LoadLibrary技術)WinNT和Win9x下都可以使用的,不影響目的進程性能的DLL映射機制。

                但是,我們應該在何種情況下使用該技巧呢?通常是在DLL需要在遠程進程中駐留較長時間(比如你要子類[subclass]另一個進程中的控件)并且你不 想過于干涉目的進程時比較適合使用這種技巧。我在HookSpy中并沒有使用它,因為那個DLL只是短暫地注入一段時間――只要能取得密碼就足夠了。我在 另一個例子HookInjEx中演示了這種方法。HookInjEx把一個DLL映射進“explorer.exe”(當然,最后又從其中卸載),子類了 其中的開始按鈕,更確切地說我是把開始按鈕的鼠標左右鍵點擊事件顛倒了一下。

                Ⅱ. CreateRemoteThread 和 LoadLibrary 技術
            示例程序:LibSpy
                通常,任何進程都可以通過LoadLibrary動態(tài)地加載DLL,但是我們如何強制一個外部進程調用該函數(shù)呢?答案是 CreateRemoteThread。
            讓我們先來看看LoadLibrary和FreeLibrary的函數(shù)聲明:

            HINSTANCE LoadLibrary(
            LPCTSTR lpLibFileName   // address of filename of library module
            );

            BOOL FreeLibrary(
            HMODULE hLibModule      // handle to loaded library module
            );

            再和CreateRemoteThread的線程過程(thread procedure)ThreadProc比較一下:
            DWORD WINAPI ThreadProc(
            LPVOID lpParameter   // thread data
            );

                你會發(fā)現(xiàn)所有的函數(shù)都有同樣的調用約定(calling convention)、都接受一個32位的參數(shù)并且返回值類型的大小也一樣。也就是說,我們可以把LoadLibrary/FreeLibrary的指 針作為參數(shù)傳遞給CrateRemoteThread。

                然而,還有兩個問題(參考下面對CreateRemoteThread的說明)

                1. 傳遞給ThreadProc的lpStartAddress 參數(shù)必須為遠程進程中的線程過程的起始地址。
                2. 如果把ThreadProc的lpParameter參數(shù)當做一個普通的32位整數(shù)(FreeLibrary把它當做HMODULE)那么沒有如何 問題,但是如果把它當做一個指針(LoadLibrary把它當做一個char*),它就必須指向遠程進程中的內存數(shù)據(jù)。

                第一個問題其實已經(jīng)迎刃而解了,因為LoadLibrary和FreeLibrary都是存在于kernel32.dll中的函數(shù),而kernel32可 以保證任何“正常”進程中都存在,且其加載地址都是一樣的。(參看附錄A)于是LoadLibrary/FreeLibrary在任何進程中的地址都是一 樣的,這就保證了傳遞給遠程進程的指針是個有效的指針。

                第二個問題也很簡單:把DLL的文件名(LodLibrary的參數(shù))用WriteProcessMemory復制到遠程進程。

                所以,使用CreateRemoteThread和LoadLibrary技術的步驟如下:
                1. 得到遠程進程的HANDLE(使用OpenProcess)。
                2. 在遠程進程中為DLL文件名分配內存(VirtualAllocEx)。
                3. 把DLL的文件名(全路徑)寫到分配的內存中(WriteProcessMemory)
                4. 使用CreateRemoteThread和LoadLibrary把你的DLL映射近遠程進程。
                5. 等待遠程線程結束(WaitForSingleObject),即等待LoadLibrary返回。也就是說當我們的DllMain(是以 DLL_PROCESS_ATTACH為參數(shù)調用的)返回時遠程線程也就立即結束了。
                6. 取回遠程線程的結束碼(GetExitCodeThtread),即LoadLibrary的返回值――我們DLL加載后的基地址(HMODULE)。
                7. 釋放第2步分配的內存(VirtualFreeEx)。
                8. 用CreateRemoteThread和FreeLibrary把DLL從遠程進程中卸載。調用時傳遞第6步取得的HMODULE給 FreeLibrary(通過CreateRemoteThread的lpParameter參數(shù))。
                9. 等待線程的結束(WaitSingleObject)。

                同時,別忘了在最后關閉所有的句柄:第4、8步得到的線程句柄,第1步得到的遠程進程句柄。

                現(xiàn)在我們看看LibSpy的部分代碼,分析一下以上的步驟是任何實現(xiàn)的。為了簡單起見,沒有包含錯誤處理和支持Unicode的代碼。
            HANDLE hThread;
            char    szLibPath[_MAX_PATH]; // "LibSpy.dll"的文件名
                                           // (包含全路徑!);
            void*   pLibRemote;             // szLibPath 將要復制到地址
            DWORD   hLibModule;   //已加載的DLL的基地址(HMODULE);
            HMODULE hKernel32 = ::GetModuleHandle("Kernel32");

            //初始化 szLibPath
            //...

            // 1. 在遠程進程中為szLibPath 分配內存
            // 2. 寫szLibPath到分配的內存
            pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),
                                           MEM_COMMIT, PAGE_READWRITE );
            ::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,
                                  sizeof(szLibPath), NULL );


            // 加載 "LibSpy.dll" 到遠程進程
            // (通過 CreateRemoteThread & LoadLibrary)
            hThread = ::CreateRemoteThread( hProcess, NULL, 0,
                        (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                                   "LoadLibraryA" ),
                         pLibRemote, 0, NULL );
            ::WaitForSingleObject( hThread, INFINITE );

            //取得DLL的基地址
            ::GetExitCodeThread( hThread, &hLibModule );

            //掃尾工作
            ::CloseHandle( hThread );
            ::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );

            我們放在DllMain中的真正要注入的代碼(比如為SendMessage)現(xiàn)在已經(jīng)被執(zhí)行了(由于DLL_PROCESS_ATTACH),所 以現(xiàn)在可以把DLL從目的進程中卸載了。

            // 從目標進程卸載LibSpu.dll
            // (通過 CreateRemoteThread & FreeLibrary)
            hThread = ::CreateRemoteThread( hProcess, NULL, 0,
                        (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                                   "FreeLibrary" ),
                        (void*)hLibModule, 0, NULL );
            ::WaitForSingleObject( hThread, INFINITE );

            // 掃尾工作
            ::CloseHandle( hThread );

            進程間通訊
                到目前為止,我們僅僅討論了任何向遠程進程注入DLL,然而,在多數(shù)情況下被注入的DLL需要和你的程序以某種方式通訊(記住,那個DLL是被映射到遠程 進程中的,而不是在你的本地程序中!)。以密碼間諜為例:那個DLL需要知道包含了密碼的的控件的句柄。很明顯,這個句柄是不能在編譯期間硬編碼 (hardcoded)進去的。同樣,當DLL得到密碼后,它也需要把密碼發(fā)回我們的程序。

                幸運的是,這個問題有很多種解決方案:文件映射(Mapping),WM_COPYDATA,剪貼板等。還有一種非常便利的方法#pragma data_seg。這里我不想深入討論因為它們在MSDN(看一下Interprocess Communications部分)或其他資料中都有很好的說明。我在LibSpy中使用的是#pragma data_seg。

                你可以在本文章的開頭找到LibSpy及源代碼的下載鏈接。

            Ⅲ.CreateRemoteThread和 WriteProcessMemory技術
            示例程序:WinSpy

                另一種注入代碼到其他進程地址空間的方法是使用WriteProcessMemory API。這次你不用編寫一個獨立的DLL而是直接復制你的代碼到遠程進程(WriteProcessMemory)并用 CreateRemoteThread執(zhí)行之。

                讓我們看一下CreateRemoteThread的聲明:
            HANDLE CreateRemoteThread(
            HANDLE hProcess,        // handle to process to create thread in
            LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security
                                                         // attributes
            DWORD dwStackSize,      // initial thread stack size, in bytes
            LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread
                                                         // function
            LPVOID lpParameter,     // argument for new thread
            DWORD dwCreationFlags, // creation flags
            LPDWORD lpThreadId      // pointer to returned thread identifier
            );

            和CreateThread相比,有一下不同:

            ●增加了hProcess參數(shù)。這是要在其中創(chuàng)建線程的進程的句柄。
            ●CreateRemoteThread的lpStartAddress參數(shù)必須指向遠程進程的地址空間中的函數(shù)。這個函數(shù)必須存在于遠程進程中,所以我 們不能簡單地傳遞一個本地ThreadFucn的地址,我們必須把代碼復制到遠程進程。
            ●同樣,lpParameter參數(shù)指向的數(shù)據(jù)也必須存在于遠程進程中,我們也必須復制它。

                現(xiàn)在,我們總結一下使用該技術的步驟:

                1. 得到遠程進程的HANDLE(OpenProcess)。
                2. 在遠程進程中為要注入的數(shù)據(jù)分配內存(VirtualAllocEx)、
                3. 把初始化后的INJDATA結構復制到分配的內存中(WriteProcessMemory)。
                4. 在遠程進程中為要注入的數(shù)據(jù)分配內存(VirtualAllocEx)。
                5. 把ThreadFunc復制到分配的內存中(WriteProcessMemory)。
                6. 用CreateRemoteThread啟動遠程的ThreadFunc。
                7. 等待遠程線程的結束(WaitForSingleObject)。
                8. 從遠程進程取回指執(zhí)行結果(ReadProcessMemory 或 GetExitCodeThread)。
                9. 釋放第2、4步分配的內存(VirtualFreeEx)。
                10. 關閉第6、1步打開打開的句柄。

                另外,編寫ThreadFunc時必須遵守以下規(guī)則:
                1. ThreadFunc不能調用除kernel32.dll和user32.dll之外動態(tài)庫中的API函數(shù)。只有kernel32.dll和 user32.dll(如果被加載)可以保證在本地和目的進程中的加載地址是一樣的。(注意:user32并不一定被所有的Win32進程加載!)參考附 錄A。如果你需要調用其他庫中的函數(shù),在注入的代碼中使用LoadLibrary和GetProcessAddress強制加載。如果由于某種原因,你需 要的動態(tài)庫已經(jīng)被映射進了目的進程,你也可以使用GetMoudleHandle代替LoadLibrary。同樣,如果你想在ThreadFunc中調 用你自己的函數(shù),那么就分別復制這些函數(shù)到遠程進程并通過INJDATA把地址提供給ThreadFunc。
                2. 不要使用static字符串。把所有的字符串提供INJDATA傳遞。為什么?編譯器會把所有的靜態(tài)字符串放在可執(zhí)行文件的“.data”段,而僅 僅在代碼中保留它們的引用(即指針)。這樣,遠程進程中的ThreadFunc就會執(zhí)行不存在的內存數(shù)據(jù)(至少沒有在它自己的內存空間中)。
                3. 去掉編譯器的/GZ編譯選項。這個選項是默認的(看附錄B)。
                4. 要么把ThreadFunc和AfterThreadFunc聲明為static,要么關閉編譯器的“增量連接(incremental linking)”(看附錄C)。
                5. ThreadFunc中的局部變量總大小必須小于4k字節(jié)(看附錄D)。注意,當degug編譯時,這4k中大約有10個字節(jié)會被事先占用。
                6. 如果有多于3個switch分支的case語句,必須像下面這樣分割開,或用if-else if代替:

            switch( expression ) {
                case constant1: statement1; goto END;
                case constant2: statement2; goto END;
                case constant3: statement2; goto END;
            }
            switch( expression ) {
                case constant4: statement4; goto END;
                case constant5: statement5; goto END;
                case constant6: statement6; goto END;
            }
            END:
            (參考附錄E)

                如果你不按照這些游戲規(guī)則玩的話,你注定會使目的進程掛掉!記住,不要妄想遠程進程中的任何數(shù)據(jù)會和你本地進程中的數(shù)據(jù)存放在相同內存地址!(參看附錄 F)
            (原 話如此:You will almost certainly crash the target process if you don't play by those rules. Just remember: Don't assume anything in the target process is at the same address as it is in your process.)


            GetWindowTextRemote(A/W)

                所有取得遠程edit中文本的工作都被封裝進這個函數(shù):GetWindowTextRemote(A/W):
            int GetWindowTextRemoteA( HANDLE hProcess, HWND hWnd, LPSTR lpString );
            int GetWindowTextRemoteW( HANDLE hProcess, HWND hWnd, LPWSTR lpString );

            參數(shù):
            hProcess
            目的edit所在的進程句柄
            hWnd
            目的edit的句柄
            lpString
            接收字符串的緩沖

            返回值:
            成功復制的字符數(shù)。

                讓我們看以下它的部分代碼,特別是注入的數(shù)據(jù)和代碼。為了簡單起見,沒有包含支持Unicode的代碼。

            INJDATA

            typedef LRESULT     (WINAPI *SENDMESSAGE)(HWND,UINT,WPARAM,LPARAM);

            typedef struct {   
                HWND hwnd;                    // handle to edit control
                SENDMESSAGE fnSendMessage;   // pointer to user32!SendMessageA

                char psText[128];    // buffer that is to receive the password
            } INJDATA;


                INJDATA是要注入遠程進程的數(shù)據(jù)。在把它的地址傳遞給SendMessageA之前,我們要先對它進行初始化。幸運的是unse32.dll在所有 的進程中(如果被映射)總是被映射到相同的地址,所以SendMessageA的地址也總是相同的,這也保證了傳遞給遠程進程的地址是有效的。

            ThreadFunc

            static DWORD WINAPI ThreadFunc (INJDATA *pData)
            {
                pData->fnSendMessage( pData->hwnd, WM_GETTEXT,    // 得到密碼
                                      sizeof(pData->psText),
                                      (LPARAM)pData->psText );
                return 0;
            }

            // This function marks the memory address after ThreadFunc.
            // int cbCodeSize = (PBYTE) AfterThreadFunc - (PBYTE) ThreadFunc.
            static void AfterThreadFunc (void)
            {
            }

            ThreadFunc是遠程線程實際執(zhí)行的代碼。
                ●注意AfterThreadFunc是如何計算ThreadFunc的代碼大小的。一般地,這不是最好的辦法,因為編譯器會改變你的函數(shù)中代碼的順序 (比如它會把ThreadFunc放在AfterThreadFunc之后)。然而,你至少可以確定在同一個工程中,比如在我們的WinSpy工程中,你 函數(shù)的順序是固定的。如果有必要,你可以使用/ORDER連接選項,或者,用反匯編工具確定ThreadFunc的大小,這個也許會更好。

            如何用該技術子類(subclass)一個遠程控件
            示例程序:InjectEx

                讓我們來討論一個更復雜的問題:如何子類屬于其他進程的一個控件?

                首先,要完成這個任務,你必須復制兩個函數(shù)到遠程進程:
                1. ThreadFunc,這個函數(shù)通過調用SetWindowLong API來子類遠程進程中的控件,
                2. NewProc, 那個控件的新窗口過程(Window Procedure)。

                然而,最主要的問題是如何傳遞數(shù)據(jù)到遠程的NewProc。因為NewProc是一個回調(callback)函數(shù),它必須符合特定的要求(譯者注:這里 指的主要是參數(shù)個數(shù)和類型),我們不能再簡單地傳遞一個INJDATA的指針作為它的參數(shù)。幸運的我已經(jīng)找到解決這個問題的方法,而且是兩個,但是都要借 助于匯編語言。我一直都努力避免使用匯編,但是這一次,我們逃不掉了,沒有匯編不行的。

            解決方案1
            看下面的圖片:

            不知道你是否注意到了,INJDATA緊挨著NewProc放在NewProc的前面?這樣的話在編譯期間NewProc就可以知道INJDATA 的內存地址。更精確地說,它知道INJDATA相對于它自身地址的相對偏移,但是這并不是我們真正想要的。現(xiàn)在,NewProc看起來是這個樣子:
            static LRESULT CALLBACK NewProc(
            HWND hwnd,       // handle to window
            UINT uMsg,       // message identifier
            WPARAM wParam,   // first message parameter
            LPARAM lParam ) // second message parameter
            {
                INJDATA* pData = (INJDATA*) NewProc; // pData 指向
                                                      // NewProc;
                pData--;              // 現(xiàn)在pData指向INJDATA;
                                      // 記住,INJDATA 在遠程進程中剛好位于
                                      // NewProc的緊前面;

                //-----------------------------
                // 子類代碼
                // ........
                //-----------------------------

                //調用用來的的窗口過程;
                // fnOldProc (由SetWindowLong返回) 是被ThreadFunc(遠程進程中的)初始化
                // 并且存儲在遠程進程中的INJDATA里的;
                return pData->fnCallWindowProc( pData->fnOldProc,
                                                hwnd,uMsg,wParam,lParam );
            }

                然而,還有一個問題,看第一行:
            INJDATA* pData = (INJDATA*) NewProc;

                pData被硬編碼為我們進程中NewProc的地址,但這是不對的。因為NewProc會被復制到遠程進程,那樣的話,這個地址就錯了。

                用C/C++沒有辦法解決這個問題,可以用內聯(lián)的匯編來解決。看修改后的NewProc:

            static LRESULT CALLBACK NewProc(
            HWND hwnd,      // handle to window
            UINT uMsg,      // message identifier
            WPARAM wParam, // first message parameter
            LPARAM lParam ) // second message parameter
            {
                // 計算INJDATA 的地址;
                // 在遠程進程中,INJDATA剛好在
                //NewProc的前面;
                INJDATA* pData;
                _asm {
                    call    dummy
            dummy:
                    pop     ecx         // <- ECX 中存放當前的EIP
                    sub     ecx, 9      // <- ECX 中存放NewProc的地址
                    mov     pData, ecx
                }
                pData--;

                //-----------------------------
                // 子類代碼
                // ........
                //-----------------------------

                // 調用原來的窗口過程
                return pData->fnCallWindowProc( pData->fnOldProc,
                                                hwnd,uMsg,wParam,lParam );
            }

                這是什么意思?每個進程都有一個特殊的寄存器,這個寄存器指向下一條要執(zhí)行的指令的內存地址,即32位Intel和AMD處理器上所謂的EIP寄存器。因 為EIP是個特殊的寄存器,所以你不能像訪問通用寄存器(EAX,EBX等)那樣來訪問它。換句話說,你找不到一個可以用來尋址EIP并且對它進行讀寫的 操作碼(OpCode)。然而,EIP同樣可以被JMP,CALL,RET等指令隱含地改變(事實上它一直都在改變)。讓我們舉例說明32位的Intel 和AMD處理器上CALL/RET是如何工作的吧:

                當我們用CALL調用一個子程序時,這個子程序的地址被加載進EIP。同時,在EIP被改變之前,它以前的值會被自動壓棧(在后來被用作返回指令指針 [return instruction-pointer])。在子程序的最后RET指令自動把這個值從棧中彈出到EIP。

                現(xiàn)在我們知道了如何通過CALL和RET來修改EIP的值了,但是如何得到他的當前值?
            還記得CALL把EIP的值壓棧了嗎?所以為了得到EIP的值我們調用了一個“假(dummy)函數(shù)”然后彈出棧頂值。看一下編譯過的NewProc:

            Address   OpCode/Params   Decoded instruction
            --------------------------------------------------
            :00401000 55          push ebp            ; entry point of
                                                           ; NewProc
            :00401001 8BEC            mov ebp, esp
            :00401003 51              push ecx
            :00401004 E800000000      call 00401009       ; *a*    call dummy
            :00401009 59          pop ecx            ; *b*
            :0040100A 83E909          sub ecx, 00000009   ; *c*
            :0040100D 894DFC          mov [ebp-04], ecx   ; mov pData, ECX
            :00401010 8B45FC          mov eax, [ebp-04]
            :00401013 83E814          sub eax, 00000014   ; pData--;
            .....
            .....
            :0040102D 8BE5            mov esp, ebp
            :0040102F 5D              pop ebp
            :00401030 C21000          ret 0010

                a. 一個假的函數(shù)調用;僅僅跳到下一條指令并且(譯者注:更重要的是)把EIP壓棧。
                b. 彈出棧頂值到ECX。ECX就保存的EIP的值;這也就是那條“pop ECX”指令的地址。
                c. 注意從NewProc的入口點到“pop ECX”指令的“距離”為9字節(jié);因此把ECX減去9就得到的NewProc的地址了。

                這樣一來,不管被復制到什么地方,NewProc總能正確計算自身的地址了!然而,要注意從NewProc的入口點到“pop ECX”的距離可能會因為你的編譯器/鏈接選項的不同而不同,而且在Release和Degub版本中也是不一樣的。但是,不管怎樣,你仍然可以在編譯期 知道這個距離的具體值。
                1. 首先,編譯你的函數(shù)。
                2. 在反匯編器(disassembler)中查出正確的距離值。
                3. 最后,使用正確的距離值重新編譯你的程序。

                這也是InjectEx中使用的解決方案。InjectEx和HookInjEx類似,交換開始按鈕上的鼠標左右鍵點擊事件。


            解決方案2

                在遠程進程中把INJDATA放在NewProc的前面并不是唯一的解決方案。看一下下面的NewProc:
            static LRESULT CALLBACK NewProc(
            HWND hwnd,      // handle to window
            UINT uMsg,      // message identifier
            WPARAM wParam, // first message parameter
            LPARAM lParam ) // second message parameter
            {
                INJDATA* pData = 0xA0B0C0D0;    // 一個假值

                //-----------------------------
                // 子類代碼
                // ........
                //-----------------------------

                // 調用以前的窗口過程
                return pData->fnCallWindowProc( pData->fnOldProc,
                                                hwnd,uMsg,wParam,lParam );
            }

                這里,0XA0B0C0D0僅僅是INJDATA在遠程進程中的地址的占位符(placeholder)。你無法在編譯期得到這個值,然而你在調用 VirtualAllocEx(為INJDATA分配內存時)后確實知道INJDATA的地址!(譯者注:就是VirtualAllocEx的返回值)

                我們的NewProc編譯后大概是這個樣子:
            Address   OpCode/Params     Decoded instruction
            --------------------------------------------------
            :00401000 55                push ebp
            :00401001 8BEC              mov ebp, esp
            :00401003 C745FCD0C0B0A0    mov [ebp-04], A0B0C0D0
            :0040100A ...
            ....
            ....
            :0040102D 8BE5              mov esp, ebp
            :0040102F 5D                pop ebp
            :00401030 C21000            ret 0010

                編譯后的機器碼應該為:558BECC745FCD0C0B0A0......8BE55DC21000。

                現(xiàn)在,你這么做:
                1. 把INJDATA,ThreadFunc和NewFunc復制到目的進程。
                2. 改變NewPoc的機器碼,讓pData指向INJDATA的真實地址。
                比如,假設INJDATA的的真實地址(VirtualAllocEx的返回值)為0x008a0000,你把NewProc的機器碼改為:

            558BECC745FCD0C0B0A0......8BE55DC21000 <- 修改前的 NewProc 1   
            558BECC745FC00008A00......8BE55DC21000 <- 修改后的 NewProc

                也就是說,你把假值 A0B0C0D0改為INJDATA的真實地址2
                3. 開始指向遠程的ThreadFunc,它子類了遠程進程中的控件。

                ¹ 你可能會問,為什么A0B0C0D0和008a0000在編譯后的機器碼中為逆序的。這時因為Intel和AMD處理器使用littl-endian標記 法(little-endian notation)來表示它們的(多字節(jié))數(shù)據(jù)。換句話說:一個數(shù)的低字節(jié)(low-order byte)在內存中被存放在最低位,高字節(jié)(high-order byte)存放在最高位。
            想像一下,存放在四個字節(jié)中的單詞“UNIX”,在big-endia系統(tǒng)中被存儲為“UNIX”,在little-endian系統(tǒng)中被存儲為 “XINU”。

                ² 一些蹩腳的破解者用類似的方法來修改可執(zhí)行文件的機器碼,但是一個程序一旦載入內存,就不能再更改自身的機器碼(一個可執(zhí)行文件的.text段是寫保護 的)。我們能修改遠程進程中的NewProc是因為它所處的那塊內存在分配時給予了PAGE_EXECUTE_READWRITE屬性。

                何時使用CreateRemoteThread和WriteProcessMemory技術

               通過CreateRemoteThread和WriteProcessMemory來注入代碼的技術,和其他兩種方法相比,不需要一個額外的DLL文件, 因此更靈活,但也更復雜更危險。一旦你的ThreadFunc中有錯誤,遠程線程會立即崩潰(看附錄F)。調試一個遠程的ThreadFunc也是場惡 夢,所以你應該在僅僅注入若干條指令時才使用這個方法。要注入大量的代碼還是使用另外兩種方法吧。

                再說一次,你可以在文章的開頭部分下載到WinSpy,InjectEx和它們的源代碼。


                寫在最后的話

                最后,我們總結一些目前還沒有提到的東西:

                方法 適用的操作系統(tǒng) 可操作的進程進程   
                I. Windows鉤子 Win9x 和WinNT 僅限鏈接了USER32.DLL的進程1   
                II. CreateRemoteThread & LoadLibrary 僅WinNT2 所有進程3,包括系統(tǒng)服務4   
                III. CreateRemoteThread & WriteProcessMemory 近WinNT 所有進程,包括系統(tǒng)服務

                1. 很明顯,你不能給一個沒有消息隊列的線程掛鉤。同樣SetWindowsHookEx也對系統(tǒng)服務不起作用(就算它們連接了USER32)。
                2. 在Win9x下沒有CreateRemoteThread和VirtualAllocEx(事實上可以在9x上模擬它們,但是到目前為止還只是個神話)
               3. 所有進程 = 所有的Win32進程 + csrss.exe
                本地程序(native application)比如smss.exe, os2ss.exe, autochk.exe,不使用Win32 APIs,也沒有連接到kernel32.dll。唯一的例外是csrss.exe,win32子系統(tǒng)自身。它是一個本地程序,但是它的一些庫(比如 winsrv.dll)需要Win32 DLL包括kernel32.dll.
                4.如果你向注入代碼到系統(tǒng)服務或csrss.exe,在打開遠程進程的句柄(OpenProcess)之前把你的進程的優(yōu)先級調整為 “SeDebugprovilege”(AdjustTokenPrivileges)。


                大概就這些了吧。還有一點你需要牢記在心:你注入的代碼(特別是存在錯誤時)很容易就會把目的進程拖垮。記住:責任隨權利而來(Power comes with responsibility)!

                這篇文章中的很多例子都和密碼有關,看過這篇文章后你可能也會對Zhefu Zhang(譯者注:大概是一位中國人,張哲夫??)寫的Supper Password Spy++感興趣。他講解了如何從IE的密碼框中得到密碼,也說了如何保護你的密碼不被這種攻擊。

                 最后一點:讀者的反饋是文章作者的唯一報酬,所以如果你認為這篇文章有作用,請留下你的評論或給它投票。更重要的是,如果你發(fā)現(xiàn)有錯誤或bug;或你認為 什么地方做得還不夠好,有需要改進的地方;或有不清楚的地方也都請告訴我。

            感謝
                首先,我要感謝我在CodeGuru(這篇文章最早是在那兒發(fā)表的)的讀者,正是由于你們的鼓勵和支持這篇文章才得以從最初的1200單詞發(fā)展到今天這樣 6000單詞的“龐然大物”。如果說有一個人我要特別感謝的話,他就是Rado Picha。這篇文章的一部分很大程度上得益于他對我的建議和幫助。最后,但也不能算是最后,感謝Susan Moore,他幫助我跨越了那個叫做“英語”的雷區(qū),讓這篇文章更加通順達意。
            ――――――――――――――――――――――――――――――――――――
            附錄
            A) 為什么kernel32.dll和user32.dll中是被映射到相同的內存地址?
            我的假定:以為微軟的程序員認為這么做可以優(yōu)化速度。讓我們來解釋一下這是為什么。
            一般來說,一個可執(zhí)行文件包含幾個段,其中一個為“.reloc”段。

            當鏈接器生成EXE或DLL時,它假定這個文件會被加載到一個特定的地址,也就是所謂的假定/首選加載/基地址(assumed /preferred load/base address)。內存映像(image)中的所有絕對地址都時基于該“鏈接器假定加載地址”的。如果由于某些原因,映像沒有加載到這個地址,那么PE加 載器(PE loader)就不得不修正該映像中的所有絕對地址。這就是“.reloc”段存在的原因:它包含了一個該映像中所有的“鏈接器假定地址”與真正加載到的 地址之間的差異的列表(注意:編譯器產(chǎn)生的大部分指令都使用一種相對尋址模式,所以,真正需要重定位[relocation]的地方并沒有你想像的那么 多)。如果,從另一方面說,加載器可以把映像加載到鏈接器首選地址,那么“.reloc”段就會被徹底忽略。

            但是,因為每一個Win32程序都需要kernel32.dll,大部分需要user32.dll,所以如果總是把它們兩個映射到其首選地址,那么 加載器就不用修正kernel32.dll和user32.dll中的任何(絕對)地址,加載時間就可以縮短。

            讓我們用下面的例子來結束這個討論:
            把一個APP.exe的加載地址改為kernel32的(/base:"0x77e80000")或 user32的(/base:"0x77e10000")首選地址。如果App.exe沒有引入UESE32,就強制LoadLibrary。然后編譯 App.exe,并運行它。你會得到一個錯誤框(“非法的系統(tǒng)DLL重定位”),App.exe無法被加載。

            為什么?當一個進程被創(chuàng)建時,Win2000和WinXP的加載器會檢查kernel32.dll和user32.dll是否被映射到它們的首選地 址(它們的名稱是被硬編碼進加載器的),如果沒有,就會報錯。在WinNT4 中ole32.dll也會被檢查。在WinNT3.51或更低版本中,則不會有任何檢查,kernel32.dll和user32.dll可以被加載到任 何地方。唯一一個總是被加載到首選地址的模塊是ntdll.dll,加載器并不檢查它,但是如果它不在它的首選地址,進程根本無法創(chuàng)建。

            總結一下:在WinNT4或更高版本的操作系統(tǒng)中:
            ●總被加載到它們的首選地址的DLL有:kernel32.dll,user32.dll和ntdll.dll。
            ●Win32程序(連同csrss.exe)中一定存在的DLL:kernel32.dll和ntdll.dll。
            ●所有進程中都存在的dll:ntdll.dll。

            B) /GZ編譯開關
            在Debug時,/GZ開關默認是打開的。它可以幫你捕捉一些錯誤(詳細內容參考文檔)。但是它對我們的可執(zhí)行文件有什么影響呢?

            當/GZ被使用時,編譯器會在每個函數(shù),包含函數(shù)調用中添加額外的代碼(添加到每個函數(shù)的最后面)來檢查ESP棧指針是否被我們的函數(shù)更改過。但 是,等等,ThreadFunc中被添加了一個函數(shù)調用?這就是通往災難的道路。因為,被復制到遠程進程中的ThreadFunc將調用一個在遠程進程中 不存在的函數(shù)。

            C) static函數(shù)和增量連接(Incremental linking)
            增量連接可以縮短連接的時間,在增量編譯時,每個函數(shù)調用都 是通過一個額外的JMP指令來實現(xiàn)的(一個例外就是被聲明為static的函數(shù)!)這些JMP允許連接器移動函數(shù)在內存中的位置而不用更新調用該函數(shù)的 CALL。但是就是這個JMP給我們帶來了麻煩:現(xiàn)在ThreadFunc和AfterThreadFunc將指向JMP指令而不是它們的真實代碼。所 以,當計算ThreadFunc的大小時:
            const int cbCodeSize = ((LPBYTE) AfterThreadFunc - (LPBYTE) ThreadFunc);
            你實際得到的將是指向ThreadFunc和AfterThreadFunc的JMP指令之間的“距離”。現(xiàn)在假設我們的ThreadFunc在 004014C0,和其對應的JMP指令在00401020
            :00401020   jmp 004014C0
            ...
            :004014C0   push EBP          ; ThreadFunc的真實地址
            :004014C1   mov EBP, ESP
            ...
            然后,
            WriteProcessMemory( .., &ThreadFunc, cbCodeSize, ..);
            將把“JMP 004014C0”和其后的cbCodeSize范圍內的代碼而不是ThreadFunc復制到遠程進程。遠程線程首先會執(zhí)行“JMP 004010C0”,然后一直執(zhí)行到這個進程代碼的最后一條指令(譯者注:這當然不是我們想要的結果)。

            然而,如果一個函數(shù)被聲明為static,就算使用增量連接,也不會被替換為JMP指令。這就是為什么我在規(guī)則#4中說把ThreadFunc和 AfterThreadFunc聲明為static或禁止增量連接的原因了。(關于增量連接的其他方面請參看Matt Pietrek寫的“Remove Fatty Deposits from Your Applications Using Our 32-bit Liposuction Tools”)

            D) 為什么ThreadFunc只能有4K的局部變量?
            局部變量總是保存在棧上的。假設一個函數(shù)有256字節(jié)的局部變量,當進入該函數(shù)時(更確切地說是在functions prologue中),棧指針會被減去256。像下面的函數(shù):
            void Dummy(void) {
                BYTE var[256];
                var[0] = 0;
                var[1] = 1;
                var[255] = 255;
            }
            會被編譯為類似下面的指令:
            :00401000   push ebp
            :00401001   mov ebp, esp
            :00401003   sub esp, 00000100           ; change ESP as storage for
                                                     ; local variables is needed
            :00401006   mov byte ptr [esp], 00      ; var[0] = 0;
            :0040100A   mov byte ptr [esp+01], 01   ; var[1] = 1;
            :0040100F   mov byte ptr [esp+FF], FF   ; var[255] = 255;
            :00401017   mov esp, ebp                ; restore stack pointer
            :00401019   pop ebp
            :0040101A   ret

            請注意在上面的例子中ESP(棧指針)是如何被改變的。但是如果一個函數(shù)有多于4K的局部變量該怎么辦?這種情況下,棧指針不會被直接改變,而是通 過一個函數(shù)調用來正確實現(xiàn)ESP的改變。但是就是這個“函數(shù)調用”導致了ThreadFunc的崩潰,因為它在遠程進程中的拷貝將會調用一個不存在的函 數(shù)。

            讓我們來看看文檔關于棧探針(stack probes)和/Gs編譯選項的說明:
            “/Gssize選項是一個允許你控制棧探針的高級特性。棧探針是編譯器插入到每個函數(shù)調用中的一系列代碼。當被激活時,棧探針將溫和地按照存儲函數(shù)局部 變量所需要的空間大小來移動

            如果一個函數(shù)需要大于size指定的局部變量空間,它的棧探針將被激活。默認的size為一個頁的大小(在80x86上為4k)。這個值可以使一個 Win32程序和Windows NT的虛擬內存管理程序和諧地交互,在運行期間向程序棧增加已提交的內存總數(shù)。

            我能確定你們對上面的敘述(“棧探針將溫和地按照存儲函數(shù)局部變量所需要的空間大小來移動”)感到奇怪。這些編譯選項(他們的描述!)有時候真的讓 人很惱火,特別是當你想真的了解它們是怎么工作的時候。打個比方,如果一個函數(shù)需要12kb的空間來存放局部變量,棧上的內存是這樣“分配”的
            sub    esp, 0x1000    ; 先“分配”4 Kb
            test [esp], eax      ; touches memory in order to commit a
                                  ; new page (if not already committed)
            sub    esp, 0x1000    ; “分配”第二個 4 Kb
            test [esp], eax      ; ...
            sub    esp, 0x1000
            test [esp], eax

            注意棧指針是如何以4Kb為單位移動的,更重要的是每移動一步后使用test對棧底的處理(more importantly, how the bottom of the stack is "touched" after each step)。這可以確保了在“分配”下一個頁之前,包含棧底的頁已經(jīng)被提交。

            繼續(xù)閱讀文檔的說明:
            “每一個新的線程會擁有(receives)自己的棧空間,這包括已經(jīng)提交的內存和保留的內存。默認情況下每個線程使 用1MB的保留內存和一個頁大小的以提交內存。如果有必要,系統(tǒng)將從保留內存中提交一個頁。”(看MSDN中GreateThread > dwStackSize > “Thread Stack Size”)

            ..現(xiàn)在為什么文檔中說“這個值可以使一個Win32程序和Windows NT的虛擬內存管理程序和諧地交互”也很清楚了。

            E) 為什么我要把多于3個case分支的swith分割開來呢?
            同樣,用例子來說明會簡單些:
            int Dummy( int arg1 )
            {
                int ret =0;

                switch( arg1 ) {
                case 1: ret = 1; break;
                case 2: ret = 2; break;
                case 3: ret = 3; break;
                case 4: ret = 0xA0B0; break;
                }
                return ret;
            }
            將會被編譯為類似下面的代碼:
            Address   OpCode/Params    Decoded instruction
            --------------------------------------------------
                                                         ; arg1 -> ECX
            :00401000 8B4C2404         mov ecx, dword ptr [esp+04]
            :00401004 33C0             xor eax, eax     ; EAX = 0
            :00401006 49               dec ecx          ; ECX --
            :00401007 83F903           cmp ecx, 00000003
            :0040100A 771E             ja 0040102A

            ; JMP to one of the addresses in table ***
            ; note that ECX contains the offset
            :0040100C FF248D2C104000   jmp dword ptr [4*ecx+0040102C]

            :00401013 B801000000       mov eax, 00000001   ; case 1: eax = 1;
            :00401018 C3               ret
            :00401019 B802000000       mov eax, 00000002   ; case 2: eax = 2;
            :0040101E C3               ret
            :0040101F B803000000       mov eax, 00000003   ; case 3: eax = 3;
            :00401024 C3               ret
            :00401025 B8B0A00000       mov eax, 0000A0B0   ; case 4: eax = 0xA0B0;
            :0040102A C3               ret
            :0040102B 90               nop

            ; 地址表 ***
            :0040102C 13104000         DWORD 00401013   ; jump to case 1
            :00401030 19104000         DWORD 00401019   ; jump to case 2
            :00401034 1F104000         DWORD 0040101F   ; jump to case 3
            :00401038 25104000         DWORD 00401025   ; jump to case 4

            看到switch-case是如何實現(xiàn)的了嗎?
            它沒有去測試每個case分支,而是創(chuàng)建了一個地址表(address table)。我們簡單地計算出在地址表中偏移就可以跳到正確的case分支。想想吧,這真是一個進步,假設你有一個50個分支的switch語句,假如 沒有這個技巧,你不的不執(zhí)行50次CMP和JMP才能到達最后一個case,而使用地址表,你可以通過一次查表即跳到正確的case。使用算法的時間復雜 度來衡量:我們把O(2n)的算法替換成了O(5)的算法,其中:
            1. O代表最壞情況下的時間復雜度。
            2. 我們假設計算偏移(即查表)并跳到正確的地址需要5個指令。

            現(xiàn)在,你可能認為上面的情況僅僅是因為case常量選擇得比較好,(1,2,3,4,5)。幸運的是,現(xiàn)實生活中的大多數(shù)例子都可以應用這個方案, 只是偏移的計算復雜了一點而已。但是,有兩個例外:
            ●如果少于3個case分支,或
            ●如果case常量是完全相互無關的。(比如 1, 13, 50, 1000)。
            最終的結果和你使用普通的if-else if是一樣的。

            有趣的地方:如果你曾經(jīng)為case后面只能跟常量而迷惑的話,現(xiàn)在你應該知道為什么了吧。這個值必須在編譯期間就確定下來,這樣才能創(chuàng)建地址表。

            回到我們的問題!
            注意到0040100C處的JMP指令了嗎?我們來看看Intel的文檔對十六進制操作碼FF的說明:
            Opcode   Instruction Description
            FF /4    JMP r/m32   Jump near, absolute indirect, address given in r/m32

            JMP使用了絕對地址!也就是說,它的其中一個操作數(shù)(在這里是0040102C)代表一個絕對地址。還用多說嗎?現(xiàn)在遠程的ThreadFunc 會盲目第在地址表中004101C然后跳到這個錯誤的地方,馬上使遠程進程掛掉了。

            F) 到底是什么原因使遠程進程崩潰了?
            如果你的遠程進程崩潰了,原因可能為下列之一:
            1. 你引用了ThreadFunc中一個不存在的字符串。
            2. ThreadFunc中一個或多個指令使用了絕對尋址(看附錄E中的例子)
            3. ThreadFunc調用了一個不存在的函數(shù)(這個函數(shù)調用可能是編譯器或連接器添加的)。這時候你需要在反匯編器中尋找類似下面的代碼:
            :004014C0    push EBP         ; entry point of ThreadFunc
            :004014C1    mov EBP, ESP
            ...
            :004014C5    call 0041550     ; 在這里崩潰了
                                          ; remote process
            ...
            :00401502    ret
            如果這個有爭議的CALL是編譯器添加的(因為一些不該打開的編譯開關比如/GZ打開了),它要么在ThreadFunc的開頭要么在 ThreadFunc接近結尾的地方

            不管在什么情況下,你使用CreateRemoteThread & WriteProcessMemory技術時必須萬分的小心,特別是編譯器/連接器的設置,它們很可能會給你的ThreadFunc添加一些帶來麻煩的東 西。



            posted @ 2010-07-06 16:41 codeArt 閱讀(1737) | 評論 (0)編輯 收藏

            Windows 2000內存篇 分頁機制介紹(轉)

            基本概念

                 Windows 2000 使用基于分頁機制的虛擬內存。每個進程有4GB的虛擬地址空間。基于分頁機制,這4GB地址空間的一些部分被映射了物理內存,一些部分映射硬盤上的交換文 件,一些部分什么也沒有映射。程序中使用的都是4GB地址空間中的虛擬地址。而訪問物理內存,需要使用物理地址。

                下面我們看看什么是物理地址,什么是虛擬地址。

                 物理地址  (physical address): 放在尋址總線上的地址。放在尋址總線上,如果是讀,電路根據(jù)這個地址每位的值就將相應地址的物理內存中的數(shù)據(jù)放到數(shù)據(jù)總線中傳輸。如果是寫,電路根據(jù)這個 地址每位的值就將相應地址的物理內存中放入數(shù)據(jù)總線上的內容。物理內存是以字節(jié)(8位)為單位編址的。

                 虛擬地址  (virtual address): 4G虛擬地址空間中的地址,程序中使用的都是虛擬地址。

                如果CPU寄存器中的分頁標志位被設置,那么執(zhí)行內存操作的機器指令時,CPU會自動根據(jù)頁目錄和頁表中的信息,把虛擬地址轉換成物理地址,完成該指令。 比如 mov eax,004227b8h ,這是把地址004227b8h處的值賦給寄存器的匯編代碼,004227b8這個地址就是虛擬址。CPU在執(zhí)行這行代碼時,發(fā)現(xiàn)寄存器中的分頁標志位已 經(jīng)被設定,就自動完成虛擬地址到物理地址的轉換,使用物理地址取出值,完成指令。對于Intel CPU 來說,分頁標志位是寄存器CR0的第31位,為1表示使用分頁,為0表示不使用分頁。對于初始化之后的 Win2k 我們觀察CR0 ,發(fā)現(xiàn)第31位為1。表明Win2k是使用分頁的。

                使用了分頁機制之后,4G的地址空間被分成了固定大小的頁,每一頁或者被映射到物理內存,或者被映射到硬盤上的交換文件中,或者沒有映射任何東西。對于一 般程序來說,4G的地址空間,只有一小部分映射了物理內存,大片大片的部分是沒有映射任何東西。物理內存也被分頁,來映射地址空間。對于32bit的 Win2k,頁的大小是4K字節(jié)。CPU用來把虛擬地址轉換成物理地址的信息存放在叫做頁目錄和頁表的結構里。

                物理內存分頁,一個物理頁的大小為4K字節(jié),第0個物理頁從物理地址 0x00000000 處開始。由于頁的大小為4KB,就是0x1000字節(jié),所以第1頁從物理地址 0x00001000處開始。第2頁從物理地址0x00002000處開始。可以看到由于頁的大小是4KB,所以只需要32bit的地址中高20bit來 尋址物理頁。

                 頁表 ,一個頁表的大小為4K字節(jié),放在一個物理頁 中。由1024個4字節(jié)的頁表項組成。頁表項的大小為4個字節(jié)(32bit),所以一個頁表中有1024個頁表項。頁表中的每一項的內容(每項4個字 節(jié),32bit)高20bit用來放一個物理頁的物理地址,低12bit放著一些標志。

                 頁目錄 ,一個頁目錄大小為4K字節(jié),放在一個物理頁 中。由1024個4字節(jié)的頁目錄項組成。頁目錄項的大小為4個字節(jié)(32bit),所以一個頁目錄中有1024個頁目錄項。頁目錄中的每一項的內容(每項 4個字節(jié))高20bit用來放一個頁表(頁表放在一個物理頁中)的物理地址,低12bit放著一些標志。 對于x86系統(tǒng),頁目錄的物理地址放 在CPU的CR3寄存器中。

                 CPU把虛擬地址轉換成物理地址:

                 一個虛擬地址,大小4個字節(jié)(32bit),包含著找到物理地址的信息,分為3個部分:第22位到第31位這10位(最高10位)是頁目錄中的索引,第 12位到第21位這10位是頁表中的索引,第0位到第11位這12位(低12位)是頁內偏移。對于一個要轉換成物理地址的虛擬地址,CPU首先根據(jù)CR3 中的值,找到頁目錄所在的物理頁。然后根據(jù)虛擬地址的第22位到第31位這10位(最高的10bit)的值作為索引,找到相應的頁目錄項 (PDE,page directory entry),頁目錄項中有這個虛擬地址所對應頁表的物理地址。有了頁表的物理地址,根據(jù)虛擬地址的第12位到第21位這10位的值作為索引,找到該頁表 中相應的頁表項(PTE,page table entry),頁表項中就有這個虛擬地址所對應物理頁的物理地址。最后用虛擬地址的最低12位,也就是頁內偏移,加上這個物理頁的物理地址,就得到了該虛 擬地址所對應的物理地址。

                 一個頁目錄有1024項,虛擬地址最高的10bit剛好可以索引1024項(2的10次方等于1024)。一個頁表也有1024項,虛擬地址中間部分的 10bit,剛好索引1024項。虛擬地址最低的12bit(2的12次方等于4096),作為頁內偏移,剛好可以索引4KB,也就是一個物理頁中的每個 字節(jié)。

                 一個虛擬地址轉換成物理地址的計算過程就是,處理器通過CR3找到當前頁目錄所在物理頁,取虛擬地址的高10bit,然后把這10bit右移2bit(因 為每個頁目錄項4個字節(jié)長,右移2bit相當于乘4)得到在該頁中的地址,取出該地址處PDE(4個字節(jié)),就找到了該虛擬地址對應頁表所在物理頁,取虛 擬地址第12位到第21位這10位,然后把這10bit右移2bit(因為每個頁表項4個字節(jié)長,右移2bit相當于乘4)得到在該頁中的地址,取出該地 址處的PTE(4個字節(jié)),就找到了該虛擬地址對應物理頁的地址,最后加上12bit的頁內偏移得到了物理地址。

                 32bit的一個指針,可以尋址范圍0x00000000-0xFFFFFFFF,4GB大小。也就是說一個32bit的指針可以尋址整個4GB地址空間 的每一個字節(jié)。一個頁表項負責4K的地址空間和物理內存的映射,一個頁表1024項,也就是負責1024*4k=4M的地址空間的映射。一個頁目錄項,對 應一個頁表。一個頁目錄有1024項,也就對應著1024個頁表,每個頁表負責4M地址空間的映射。1024個頁表負責1024*4M=4G的地址空間映 射。一個進程有一個頁目錄。所以以頁為單位,頁目錄和頁表可以保證4G的地址空間中的每頁和物理內存的映射。

                 每個進程都有自己的4G地址空間,從0x00000000-0xFFFFFFFF。通過每個進程自己的一套頁目錄和頁表來實現(xiàn)。由于每個進程有自己的頁目 錄和頁表,所以每個進程的地址空間映射的物理內存是不一樣的。兩個進程的同一個虛擬地址處(如果都有物理內存映射)的值一般是不同的,因為他們往往對應不 同的物理頁。

                 4G地址空間中低2G,0x00000000-0x7FFFFFFF是用戶地址空間,4G地址空間中高2G,
            0x80000000-0xFFFFFFFF 是系統(tǒng)地址空間。訪問系統(tǒng)地址空間需要程序有ring0的權限。


            posted @ 2010-07-06 16:36 codeArt 閱讀(170) | 評論 (0)編輯 收藏

            用C語言擴展Python的功能

            Pyton和C分別有著各自的優(yōu)缺點,用Python開發(fā)程序速度快,可靠性高,并且有許多現(xiàn)成模塊可供使用,但執(zhí)行速度相對較 慢;C語言則正好相反,其執(zhí)行速度快,但開發(fā)效率低。為了充分利用兩種語言各自的優(yōu)點,比較好的做法是用Python開發(fā)整個軟件框架,而用C語言實現(xiàn)其 關鍵模塊。本文介紹如何利用C語言來擴展Python的功能,并輔以具體的實例講述如何編寫Python的擴展模塊。

            一、簡介

            Python是一門功能強大的高級腳本語言,它的強大不僅表現(xiàn)在其自身的功能上,而且還表現(xiàn)在其良好的可擴展性上,正因如 此,Python已經(jīng)開始受到越來越多人的青睞,并且被屢屢成功地應用于各類大型軟件系統(tǒng)的開發(fā)過程中。

            與其它普通腳本語言有所不同,Python程序員可以借助Python語言提供的API,使用C或者C++來對Python進行功能性 擴展,從而即可以利用Python方便靈活的語法和功能,又可以獲得與C或者C++幾乎相同的執(zhí)行性能。執(zhí)行速度慢是幾乎所有腳本語言都具有的共性,也是 倍受人們指責的一個重要因素,Python則通過與C語言的有機結合巧妙地解決了這一問題,從而使腳本語言的應用范圍得到了很大擴展。

            在用Python開發(fā)實際軟件系統(tǒng)時,很多時候都需要使用C/C++來對Python進行擴展。最常見的情況是目前已經(jīng)存在一個用C編 寫的庫,需要在Python語言中使用該庫的某些功能,此時就可以借助Python提供的擴展功能來實現(xiàn)。此外,由于Python從本質上講還是一種腳本 語言,某些功能用Python實現(xiàn)可能很難滿足實際軟件系統(tǒng)對執(zhí)行效率的要求,此時也可以借助Python提供的擴展功能,將這些關鍵代碼段用C或者 C++實現(xiàn),從而提供程序的執(zhí)行性能。

            本文主要介紹Python提供的C語言擴展接口,以及如何使用這些接口和C/C++語言來對Python進行功能性擴展,并輔以具體的 實例講述如何實現(xiàn)Python的功能擴展。





            回頁首


            二、Python的C語言接口

            Python是用C語言實現(xiàn)的一種腳本語言,本身具有優(yōu)良的開放性和可擴展性,并提供了方便靈活的應用程序接口(API),從而使得 C/C++程序員能夠在各個級別上對Python解釋器的功能進行擴展。在使用C/C++對Python進行功能擴展之前,必須首先掌握Python解釋 所提供的C語言接口。

            2.1 Python對象(PyObject)

            Python是一門面向對象的腳本語言,所有的對象在Python解釋器中都被表示成PyObject,PyObject結構包含 Python對象的所有成員指針,并且對Python對象的類型信息和引用計數(shù)進行維護。在進行Python的擴展編程時,一旦要在C或者C++中對 Python對象進行處理,就意味著要維護一個PyObject結構。

            在Python的C語言擴展接口中,大部分函數(shù)都有一個或者多個參數(shù)為PyObject指針類型,并且返回值也大都為PyObject 指針。

            2.2 引用計數(shù)

            為了簡化內存管理,Python通過引用計數(shù)機制實現(xiàn)了自動的垃圾回收功能,Python中的每個對象都有一個引用計數(shù),用來計數(shù)該對 象在不同場所分別被引用了多少次。每當引用一次Python對象,相應的引用計數(shù)就增1,每當消毀一次Python對象,則相應的引用就減1,只有當引用 計數(shù)為零時,才真正從內存中刪除Python對象。

            下面的例子說明了Python解釋器如何利用引用計數(shù)來對Pyhon對象進行管理:

            例1:refcount.py
            class refcount:
            # etc.
            r1 = refcount() # 引用計數(shù)為1
            r2 = r1 # 引用計數(shù)為2
            del(r1) # 引用計數(shù)為1
            del(r2) # 引用計數(shù)為0,刪除對象

            在C/C++中處理Python對象時,對引用計數(shù)進行正確的維護是一個關鍵問題,處理不好將很容易產(chǎn)生內存泄漏。Python的C語 言接口提供了一些宏來對引用計數(shù)進行維護,最常見的是用Py_INCREF()來增加使Python對象的引用計數(shù)增1,用Py_DECREF()來使 Python對象的引用計數(shù)減1。

            2.3 數(shù)據(jù)類型

            Python定義了六種數(shù)據(jù)類型:整型、浮點型、字符串、元組、列表和字典,在使用C語言對Python進行功能擴展時,首先要了解如 何在C和Python的數(shù)據(jù)類型間進行轉化。

            2.3.1 整型、浮點型和字符串

            在Python的C語言擴展中要用到整型、浮點型和字符串這三種數(shù)據(jù)類型時相對比較簡單,只需要知道如何生成和維護它們就可以了。下面 的例子給出了如何在C語言中使用Python的這三種數(shù)據(jù)類型:

            例2:typeifs.c
            // build an integer
            PyObject* pInt = Py_BuildValue("i", 2003);
            assert(PyInt_Check(pInt));
            int i = PyInt_AsLong(pInt);
            Py_DECREF(pInt);
            // build a float
            PyObject* pFloat = Py_BuildValue("f", 3.14f);
            assert(PyFloat_Check(pFloat));
            float f = PyFloat_AsDouble(pFloat);
            Py_DECREF(pFloat);
            // build a string
            PyObject* pString = Py_BuildValue("s", "Python");
            assert(PyString_Check(pString);
            int nLen = PyString_Size(pString);
            char* s = PyString_AsString(pString);
            Py_DECREF(pString);

            2.3.2 元組

            Python語言中的元組是一個長度固定的數(shù)組,當Python解釋器調用C語言擴展中的方法時,所有非關鍵字(non- keyword)參數(shù)都以元組方式進行傳遞。下面的例子示范了如何在C語言中使用Python的元組類型:

            例3:typetuple.c
            // create the tuple
            PyObject* pTuple = PyTuple_New(3);
            assert(PyTuple_Check(pTuple));
            assert(PyTuple_Size(pTuple) == 3);
            // set the item
            PyTuple_SetItem(pTuple, 0, Py_BuildValue("i", 2003));
            PyTuple_SetItem(pTuple, 1, Py_BuildValue("f", 3.14f));
            PyTuple_SetItem(pTuple, 2, Py_BuildValue("s", "Python"));
            // parse tuple items
            int i;
            float f;
            char *s;
            if (!PyArg_ParseTuple(pTuple, "ifs", &i, &f, &s))
            PyErr_SetString(PyExc_TypeError, "invalid parameter");
            // cleanup
            Py_DECREF(pTuple);

            2.3.3 列表

            Python語言中的列表是一個長度可變的數(shù)組,列表比元組更為靈活,使用列表可以對其存儲的Python對象進行隨機訪問。下面的例 子示范了如何在C語言中使用Python的列表類型:

            例4:typelist.c
            // create the list
            PyObject* pList = PyList_New(3); // new reference
            assert(PyList_Check(pList));
            // set some initial values
            for(int i = 0; i < 3; ++i)
            PyList_SetItem(pList, i, Py_BuildValue("i", i));
            // insert an item
            PyList_Insert(pList, 2, Py_BuildValue("s", "inserted"));
            // append an item
            PyList_Append(pList, Py_BuildValue("s", "appended"));
            // sort the list
            PyList_Sort(pList);
            // reverse the list
            PyList_Reverse(pList);
            // fetch and manipulate a list slice
            PyObject* pSlice = PyList_GetSlice(pList, 2, 4); // new reference
            for(int j = 0; j < PyList_Size(pSlice); ++j) {
            PyObject *pValue = PyList_GetItem(pList, j);
            assert(pValue);
            }
            Py_DECREF(pSlice);
            // cleanup
            Py_DECREF(pList);

            2.3.4 字典

            Python語言中的字典是一個根據(jù)關鍵字進行訪問的數(shù)據(jù)類型。下面的例子示范了如何在C語言中使用Python的字典類型:

            例5:typedic.c
            // create the dictionary
            PyObject* pDict = PyDict_New(); // new reference
            assert(PyDict_Check(pDict));
            // add a few named values
            PyDict_SetItemString(pDict, "first",
            Py_BuildValue("i", 2003));
            PyDict_SetItemString(pDict, "second",
            Py_BuildValue("f", 3.14f));
            // enumerate all named values
            PyObject* pKeys = PyDict_Keys(); // new reference
            for(int i = 0; i < PyList_Size(pKeys); ++i) {
            PyObject *pKey = PyList_GetItem(pKeys, i);
            PyObject *pValue = PyDict_GetItem(pDict, pKey);
            assert(pValue);
            }
            Py_DECREF(pKeys);
            // remove a named value
            PyDict_DelItemString(pDict, "second");
            // cleanup
            Py_DECREF(pDict);





            回頁首


            三、Python的C語言擴展

            3.1 模塊封裝

            在了解了Python的C語言接口后,就可以利用Python解釋器提供的這些接口來編寫Python的C語言擴展,假設有如下一個C 語言函數(shù):

            例6:example.c
            int fact(int n)
            {
            if (n <= 1)
            return 1;
            else
            return n * fact(n - 1);
            }

            該函數(shù)的功能是計算某個給定自然數(shù)的階乘,如果想在Python解釋器中調用該函數(shù),則應該首先將其實現(xiàn)為Python中的一個模塊, 這需要編寫相應的封裝接口,如下所示:

            例7: wrap.c
            #include <Python.h>
            PyObject* wrap_fact(PyObject* self, PyObject* args)
            {
            int n, result;

            if (! PyArg_ParseTuple(args, "i:fact", &n))
            return NULL;
            result = fact(n);
            return Py_BuildValue("i", result);
            }
            static PyMethodDef exampleMethods[] =
            {
            {"fact", wrap_fact, METH_VARARGS, "Caculate N!"},
            {NULL, NULL}
            };
            void initexample()
            {
            PyObject* m;
            m = Py_InitModule("example", exampleMethods);
            }

            一個典型的Python擴展模塊至少應該包含三個部分:導出函數(shù)、方法列表和初始化函數(shù)。

            3.2 導出函數(shù)

            要在Python解釋器中使用C語言中的某個函數(shù),首先要為其編寫相應的導出函數(shù),上述例子中的導出函數(shù)為wrap_fact。在 Python的C語言擴展中,所有的導出函數(shù)都具有相同的函數(shù)原型:

            PyObject* method(PyObject* self, PyObject* args);

            該函數(shù)是Python解釋器和C函數(shù)進行交互的接口,帶有兩個參數(shù):self和args。參數(shù)self只在C函數(shù)被實現(xiàn)為內聯(lián)方法 (built-in method)時才被用到,通常該參數(shù)的值為空(NULL)。參數(shù)args中包含了Python解釋器要傳遞給C函數(shù)的所有參數(shù),通常使用Python的 C語言擴展接口提供的函數(shù)PyArg_ParseTuple()來獲得這些參數(shù)值。

            所有的導出函數(shù)都返回一個PyObject指針,如果對應的C函數(shù)沒有真正的返回值(即返回值類型為void),則應返回一個全局的 None對象(Py_None),并將其引用計數(shù)增1,如下所示:

            PyObject* method(PyObject *self, PyObject *args) 
            {
            Py_INCREF(Py_None);
            return Py_None;
            }

            3.3 方法列表

            方法列表中給出了所有可以被Python解釋器使用的方法,上述例子對應的方法列表為:

            static PyMethodDef exampleMethods[] = 
            {
            {"fact", wrap_fact, METH_VARARGS, "Caculate N!"},
            {NULL, NULL}
            };

            方法列表中的每項由四個部分組成:方法名、導出函數(shù)、參數(shù)傳遞方式和方法描述。方法名是從Python解釋器中調用該方法時所使用的名 字。參數(shù)傳遞方式則規(guī)定了Python向C函數(shù)傳遞參數(shù)的具體形式,可選的兩種方式是METH_VARARGS和METH_KEYWORDS,其中 METH_VARARGS是參數(shù)傳遞的標準形式,它通過Python的元組在Python解釋器和C函數(shù)之間傳遞參數(shù),若采用METH_KEYWORD方 式,則Python解釋器和C函數(shù)之間將通過Python的字典類型在兩者之間進行參數(shù)傳遞。

            3.4 初始化函數(shù)

            所有的Python擴展模塊都必須要有一個初始化函數(shù),以便Python解釋器能夠對模塊進行正確的初始化。Python解釋器規(guī)定所 有的初始化函數(shù)的函數(shù)名都必須以init開頭,并加上模塊的名字。對于模塊example來說,則相應的初始化函數(shù)為:

            void initexample() 
            {
            PyObject* m;
            m = Py_InitModule("example", exampleMethods);
            }

            當Python解釋器需要導入該模塊時,將根據(jù)該模塊的名稱查找相應的初始化函數(shù),一旦找到則調用該函數(shù)進行相應的初始化工作,初始化 函數(shù)則通過調用Python的C語言擴展接口所提供的函數(shù)Py_InitModule(),來向Python解釋器注冊該模塊中所有可以用到的方法。

            3.5 編譯鏈接

            要在Python解釋器中使用C語言編寫的擴展模塊,必須將其編譯成動態(tài)鏈接庫的形式。下面以RedHat Linux 8.0為例,介紹如何將C編寫的Python擴展模塊編譯成動態(tài)鏈接庫:

            [xiaowp@gary code]$ gcc -fpic -c -I/usr/include/python2.2 \
            -I /usr/lib/python2.2/config \
            example.c wrapper.c
            [xiaowp@gary code]$ gcc -shared -o example.so example.o wrapper.o

            3.6 引入Python解釋器

            當生成Python擴展模塊的動態(tài)鏈接庫后,就可以在Python解釋器中使用該擴展模塊了,與Python自帶的模塊一樣,擴展模塊 也是通過import命令引入后再使用的,如下所示:

            [xiaowp@gary code]$ python
            Python 2.2.1 (#1, Aug 30 2002, 12:15:30)
            [GCC 3.2 20020822 (Red Hat Linux Rawhide 3.2-4)] on linux2
            Type "help", "copyright", "credits" or "license" for more information.
            >>> import example
            >>> example.fact(4)
            24
            >>>





            回頁首


            四、結束語

            作為一門功能強大的腳本語言,Python將被更加廣泛地應用于各個領域。為了克服腳本語言執(zhí)行速度慢的問題,Python提供了相應 的C語言擴展接口,通過將影響執(zhí)行性能的關鍵代碼用C語言實現(xiàn),可以很大程度上提高用Python編寫的腳本在運行時的速度,從而滿足實際需要。



            參考資料

            1. 可以從Python( http://www.python.org)網(wǎng)站著手 了解所有關于Python的內容。
            2. 可以在Python網(wǎng)站上找到正式的Python C/API文檔( http://www.python.org/doc/current/api/api.html)。
            3. 可以在Python網(wǎng)站上找到正式的編寫Python擴展模塊的文檔( http://www.python.org/doc/current/api/api.html)。


            關于作者


            肖文鵬,是北京理工大學計算機系的一名碩士研究生,主要從事操作系統(tǒng)和分布式計算環(huán)境的研究,喜愛Linux和 Python。你可以通過 xiaowp@263.net與他取得聯(lián)系。


            posted @ 2010-07-06 14:03 codeArt 閱讀(289) | 評論 (0)編輯 收藏

            com的使用例子

            1. 創(chuàng)建myCom.dll,該COM只有一個組件,兩個接口IGetRes--方法Hello(),
            IGetResEx
            --方法HelloEx()
            2.在工程中導入組件或類型庫
               #import 
            "組件所在目錄\myCom.dll" no_namespace
                   或
               #import 
            "類型庫所在目錄\myCom.tlb"
               
            using namespace MYCOM;

            --Method 1-------------------------------------------------------
               CoInitialize(NULL);
            CLSID clsid;
            CLSIDFromProgID(OLESTR(
            "myCom.GetRes"),&clsid);
               CComPtr
            <IGetRes> pGetRes;//智能指針
               pGetRes.CoCreateInstance(clsid);
               pGetRes
            ->Hello();
               pGetRes.Release();
            //小心哦!!請看最后的“注意”
               CoUninitialize();
            --Method 2---------------------------------------------------------
               CoInitialize(NULL);
               CLSID clsid;
               HRESULT hr
            =CLSIDFromProgID(OLESTR("myCom.GetRes"),&clsid);
               IGetRes 
            *ptr;
               hr
            =CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,
                             __uuidof(IGetRes),(LPVOID
            *)&ptr);
               ptr
            ->Hello();
               CoUninitialize();
            --Method 3--------------------------------------------------------
               CoInitialize(NULL);
               HRESULT hr;
               CLSID clsid;
               hr
            =CLSIDFromProgID(OLESTR("myCom.GetRes"),&clsid);
               IGetRes
            * ptr;
               IGetResEx
            * ptrEx;

            //使用CoCreateClassObject創(chuàng)建一個組件(特別是 mutilThreads)的多個對象的
                時候,效率更高.
               IClassFactory
            * p_classfactory;
               hr
            =CoGetClassObject(clsid,CLSCTX_INPROC_SERVER,
                                  NULL,IID_IClassFactory,
                                 (LPVOID
            *)&p_classfactory);
               p_classfactory
            ->CreateInstance(NULL,__uuidof(IGetRes),
                                                      (LPVOID
            *)&ptr);
               p_classfactory
            ->CreateInstance(NULL,__uuidof(IGetResEx),
                                                      (LPVOID
            *)&ptrEx);
               ptr
            ->Hello();
               ptrEx
            ->HelloEx();
               CoUninitialize();
            --Method 4--------------------------------------------------------
                直接從dll中得到DllGetClassObject,接著生成類對象及類實例(這方法可以
            使組件不用在注冊表里注冊,這是最原始的方法,但這樣做沒什么意義,至少失去了COM
            對用戶的透明性),不推薦使用.

            typedef HRESULT (__stdcall 
            * pfnHello)(REFCLSID,REFIID,void**);
               pfnHello fnHello
            = NULL;
               HINSTANCE hdllInst 
            = LoadLibrary("組件所在目錄\myCom.dll");
               fnHello
            =(pfnHello)GetProcAddress(hdllInst,"DllGetClassObject");
               
            if (fnHello != 0)
               {
               IClassFactory
            * pcf = NULL;
               HRESULT hr
            =(fnHello)(CLSID_GetRes,IID_IClassFactory,(void**)&pcf);
               
            if (SUCCEEDED(hr) && (pcf != NULL))
               {
               IGetRes
            * pGetRes = NULL;
               hr 
            = pcf->CreateInstance(NULL, IID_IFoo, (void**)&pGetRes);
               
            if (SUCCEEDED(hr) && (pFoo != NULL))
               {
               pGetRes
            ->Hello();
               pGetRes
            ->Release();
               }
               pcf
            ->Release();
               }
               }
               FreeLibrary(hdllInst);
            --Method 5-------------------------------------------------------
                通過ClassWizard利用類型庫生成包裝類,不過前提是com組件的接口必須是派
            生自IDispatch,具體方法:
               調出添加類向導(.NET中),選擇類型庫中MFC類,打開,選擇
            "文件",選擇
            "myCom.dll""myCom.tlb", 接下來會出來該myCom中的所有接口,選擇你想
            生成的接口包裝類后,向導會自動生成相應的.h文件.這樣你就可以在你的MFC中
            像使用普通類那樣使用組件了.

               CoInitialize(NULL);
               CGetRes getRest;
               
            if (getRest.CreateDispatch("myCom.GetRes"!= 0)
               {
               getRest.Hello();
               getRest.ReleaseDispatch();
               }
               CoUninitialize();
            --注意--------------------------------------------------------------
                COM中的智能指針實際上是重載了
            ->的類,目的是為了簡化引用記數(shù),幾不需要程序
            員顯示的調用AddRef()和Release(),但是為什么我們在Method 1中
            pGetRes.Release(), 問題在與,我們的智能指針pGetRes生命周期的結束是在
            CoUninitialize()之后,CoInitialize所開的套間在 CoUninitialize()后已經(jīng)被
            關閉,而pGetRes此時發(fā)生析構,導致了程序的崩潰,解決這個問題的另一個方法是
               CoInitialize(NULL);
            CLSID clsid;
            CLSIDFromProgID(OLESTR(
            "myCom.GetRes"),&clsid);
            {
               CComPtr
            <IGetRes> pGetRes;//智能指針
               pGetRes.CoCreateInstance(clsid);
               pGetRes
            ->Hello();
            }
               CoUninitialize();
            -----------------------------------------------------------------
            轉自http:
            //hi.baidu.com/qxwandy/blog/item/00d116fbfa0d5062024f56a1.html

            posted @ 2010-07-06 10:16 codeArt 閱讀(461) | 評論 (0)編輯 收藏

            windows線程池API


            posted @ 2010-07-05 17:54 codeArt 閱讀(418) | 評論 (0)編輯 收藏

            Windbg 附帶的調試工具

            Debugging Tools for Windows 工具列表:
            名稱 描述
            agestore.exe 方便的文件刪除工具,可以根據(jù)最近訪問的日期來刪除文件。
            cdb.exe 命令行的user mode調試器,事實上和NTSD相同。
            dbengprx.exe 用于在兩臺不同機器之間轉發(fā)數(shù)據(jù)的輕量級代理服務器。
            dbgrpc.exe 用于查詢和顯示RPC信息的工具。
            dbgrpc.exe 用于遠程調試的進程服務器。
            dumpchk.exe 用于驗證內存dump文件的工具。
            gflags.exe 用于啟用或系統(tǒng)監(jiān)測的配 置工具。
            kd.exe kernel mode調試器。
            kdbgctrl.exe 用于控制和配置kernel mode調試連接的工具。
            kdsrv.exe 在kernel mode調試過程中使用的連接服務器。
            kill.exe 基于命令行的中止進程的工具。
            logger.exe 記錄進程運行期活動(比如函數(shù)調用等)的工具。
            logviewer.exe 查看logger.exe生成的日志文件的工具。
            ntsd.exe 命令行的user mode調試器,事實上和CDB相同。
            remote.exe 用于遠程控制命令行程序的工具。
            rtlist.exe 遠程進程列表查看器。
            symchk.exe 用于驗證symbol文件或者從symbol服務器上下載symbol文件 的工具。
            symstore.exe 用于創(chuàng)建和維護symbol存儲的工具。
            tlist.exe 列出所有運行中的進程的工具。
            umdh.exe 用于內存泄漏檢測的工具。
            windbg.exe 帶GUI界面的user mode和kernel mode的調試器。


            posted @ 2010-07-05 17:09 codeArt 閱讀(587) | 評論 (0)編輯 收藏

            僅列出標題
            共3頁: 1 2 3 
            <2010年9月>
            2930311234
            567891011
            12131415161718
            19202122232425
            262728293012
            3456789

            導航

            統(tǒng)計

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            文章檔案

            編程與開源

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            国内精品久久人妻互换| 粉嫩小泬无遮挡久久久久久| 精品国产婷婷久久久| 久久精品国产精品青草app| 精品久久久久久成人AV| 国产亚州精品女人久久久久久| 久久国产精品成人影院| 国产精品久久免费| 久久亚洲中文字幕精品一区| 久久婷婷五月综合色高清| 国产精品VIDEOSSEX久久发布 | 亚洲中文字幕久久精品无码APP | 久久综合色老色| 久久人人爽人人爽人人片av高请| 四虎国产永久免费久久| 久久久久亚洲AV无码观看| 精品国产福利久久久| 久久精品中文无码资源站| 久久人人爽人人爽AV片| 久久精品国产99国产电影网 | 日韩精品久久无码中文字幕| 91亚洲国产成人久久精品| 99久久国产综合精品女同图片| 久久久九九有精品国产| 久久精品国产久精国产思思| 国产精品99久久久精品无码| 人人狠狠综合久久亚洲高清| 久久综合丝袜日本网| 精品久久一区二区三区| 国产精品久久久久久吹潮| 久久天天躁狠狠躁夜夜avapp | 久久免费精品一区二区| 久久国产欧美日韩精品| 久久久噜噜噜www成人网| 亚洲AV成人无码久久精品老人| 亚洲午夜精品久久久久久app| 久久国产精品二国产精品| 国产午夜精品久久久久九九电影| 青青青青久久精品国产| 国産精品久久久久久久| 亚洲日本va午夜中文字幕久久|