螢幕取詞核心內幕 (轉)

worldblog發表於2007-12-03
螢幕取詞核心內幕 (轉)[@more@]

螢幕取詞核心內幕

本文只對與幾個關鍵性技術的實現細節進行討論,其它的細節,請參考源。

32位到16位的形式替換

32位程式碼與16位程式碼的資料

  動態修改核心

 1.  32bit到16bit的形式替換(Thunk)

形式替換是指那些允許從16位程式碼32位程式碼或從32位程式碼呼叫16位程式碼的技術。形式替換用於解決試圖在同一操作或同一可程式上使16位程式碼與32位程式碼同時並存的問題,即16位程式碼與32位程式碼的混合程式設計技術。早期的DOS程式及Window3.x上的應用程式均為16位程式,Windows95及雖然也可執行舊的16位程式,但它們的主流發展方向是32位應用程式。與Windows NT不同的是,Windows95不是一個“純”32位,為了兼有令人滿意的速度和與舊的16位程式的良好相容性,其核心本身就是一個16位與32位的混合體,因此也為程式設計者使用形式替換提供了便利。為編寫形式替換程式提供了通用的介面及工具,但因LTW32中的形式替換並不複雜,所以使用了一些程式設計技巧,而避免了使用Microsoft複雜的開發工具。

形式替換中最主要的問題有兩點:①16位與32位資料型別尺寸的變化,如16位程式碼到32位程式碼的一個重要變化是整型數int的長度加倍了 ;②是堆疊操作時,16位使用SS:SP堆疊指標控制棧頂,而在32位模式中使用ESP暫存器作堆疊指標控制棧頂。

在LTW32中,螢幕抓詞功能由16位DLL實現,因而只需實現32位到16位的形式替換,為32位程式碼提供16位DLL的呼叫介面。CALL F PTR是32位程式碼的一種呼叫方法,它可讓32位程式碼呼叫到16位程式碼。作為實現的關鍵,控制16位側與32位側各自的堆疊是程式設計的要點。

32位側的呼叫程式碼:

_asm{

  pusha

  call fword ptr [func16bit] /* func16bit是16位被呼叫者的48位地址 */

  popa

}

16位側的呼叫程式碼:

int dummy;

static char stack[8192];   /* 16位程式碼的臨時堆疊 */

static WORD stack_seg;

static WORD prev_seg;

static DWORD prev_ofs;

static WORD prev_ds;

_asm{

  push ax;

  push bx;

  mov ax, ds;

  mov bx, seg dummy;

  mov ds, bx;

  mov stack_seg, bx;

  mov prev_ds, ax;

  pop bx;

  pop ax;

  mov prev_seg, ss;

  mov dword ptr prev_ofs, esp; /* 儲存32位堆疊指標 */

  mov ss, stack_seg;

  mov sp, offset stack;  /* 設定16位堆疊指標 */

  add sp, 8192;

}

/* 此處加入16位程式碼要實現的功能 */

_asm{

  mov ss, prev_seg;

  mov esp, dword ptr prev_ofs; /* 恢復32位堆疊指標 */

  mov ds, prev_ds;

  lea sp, word ptr [bp-2];

    pop ds;

  pop bp;

  dec bp;

  66h;

  retf;

}

由於呼叫中傳遞的引數有限,所以涉及的程式碼並不多,唯一比較複雜之處在16位側,它臨時轉向一個16位堆疊,服務於來自32位的呼叫者的請求。在16位側入口處,必須存放好32位呼叫者的32位堆疊的指標,並且在16位側返回時恢復它。由程式碼可見,這個過程是不可重入的,即一次只支援一個呼叫者,由於呼叫者只有LTW32的32位側程式碼,所以此限制可以滿足。另外,在16位程式碼返回時,必須恢復32位呼叫者的48位堆疊指標(SS:ESP)而不是32位的CS:SP,而且必須用一個48位地址(16:32)遠返回(66h RETF)到它的32位呼叫者。

處理好這些細節,實現32位到16位的形式替換實際上是很簡單的。

2.  32位程式碼與16位程式碼的資料交換

32位程式碼使用16位段地址加32位線性地址(16:32)的地址形式,16位程式碼使用16位段選擇符(or)加16位偏移(16:16)的地址形式。在Windows 95的32位側,段址28h是系統段,即透過CS=28h或DS=28h可以訪問系統的4G空間,其它應用程式的地址空間都是對映到28h段的4G空間中。Windows95中所有32位程式的地址空間(共4G)的高2G(80000000H~FFFFFFFFH)全部對映到28:80000000~28:FFFFFFFFH。即這塊區域是所有32位程式共享的。這裡一般存放系統DLL、虛擬裝置程式(VxD)、對映、16位應用程式和16位全域性堆等。最後一項很重要,這為32位程式碼與16位程式碼交換資料提供了一個簡便的方法。因為16位程式的段選擇符的基址即是其所對映的系統段中的線性地址,這樣,只要能夠得到這個線性地址,32位程式碼就可以輕易地訪問到16位程式的資料(LTW32 的32位側使用此方法從16位側獲得螢幕截獲的資訊)。而16位段選擇符的線性基址可以透過使用系統呼叫GetSelectorBase()得到,具體實現可參考源程式。線性地址計算的例子如下:

16位地址:07F2:1234H

段選擇符  07F2H的線性基址為:82F41300H

段選擇符  07F2H的尺寸為:4000H

∵ 82F41300H + 1234H = 82F42534

∴ 對應的32位線性地址為28:82F42534H

3.  動態修改Windows核心

如前所述,Windows95不是一個“純”32位作業系統,其核心模組中的USER和GDI均是用16位程式碼實現的。USER32.DLL和GDI32.DLL只是16位的USER.EXE和GDI.EXE的32位呼叫介面。因此,如果螢幕截獲程式用32位程式碼實現,則只能截獲32位應用程式對USER32.DLL和GDI32.DLL的呼叫,無法截獲16位應用程式對USER.EXE和GDI.EXE的呼叫,所以如果想截獲所有應用程式(包括Windows95的桌面程式Explorer)中有關螢幕輸出的系統呼叫,則應該用16位程式碼實現螢幕截獲功能。這就是LTW32為什麼不是“純”32位應用程式的原因。LTW32主要截獲兩個系統呼叫TextOut()和ExtTextOut(),方法很簡單,把這兩個的頭五個位元組修改為一個JMP FAR 指令,使得對這兩個函式的呼叫均轉向螢幕截獲程式。這就涉及到一個關鍵問題:動態修改Windows的程式碼。

在傳統的DOS程式中,動態修改程式程式碼無任何困難,但在Windows中則不然,因為在Windows中,程式碼可被同一程式的多個例項(程式)共享,所以系統不允許應用程式動態的修改程式碼。在16位側,記憶體的可讀、寫屬性是與段選擇符聯絡在一起的。段選擇符基本上可分為兩類:資料段選擇符和程式碼段選擇符。前者可讀、可寫、不可執行;後者可讀、可執行、不可寫。Windows提供了這兩類段選擇符相轉換的系統呼叫。未公開的16位系統呼叫AllocCStoDSAlias()為給定的程式碼段選擇符分配一個具有相同線性基址和尺寸的資料段別名(DS Alias)。透過DS別名可以對給定的程式碼段進行修改。AllocCStoDSAlias()的使用方法如下:

WORD (FAR PASCAL *AllocCStoDSAlias)(WORD);

AllocCStoDSAlias = GetProcAddress(

GetModuleHandle(“KERNEL”), ”ALLOCCSTODSALIAS”);

呼叫引數為給定的程式碼選擇符,呼叫成功時返回一個線性基址和尺寸均與原始碼選擇符相同的DS別名。當不再使用此DS別名時,要用系統呼叫FreeSelector()把DS別名釋放掉。

使用上述技術,就可實現動態修改Windows程式碼,從而改變GDI的系統呼叫TextOut()和ExtTextOut()的執行動作,實時地截獲螢幕輸出,為實現滑鼠隨動翻譯提供可能。

把上述的32位到16位的形式替換、32位程式碼與16位程式碼的資料交換、動態修改Windows核心等技術綜合應用在一起,配合單詞查詢演算法和片語分析演算法就可以實現滑鼠隨動翻譯功能。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-987561/,如需轉載,請註明出處,否則將追究法律責任。

相關文章