一般來(lái)說(shuō),編譯連接之后的代碼只能在固定的位置(這里的位置是指偏移地址)上執(zhí)行,如果直接將其拷貝到其他位置(偏移地址跟編譯時(shí)的地址不同)上運(yùn)行時(shí)會(huì)發(fā)生不可預(yù)料的錯(cuò)誤。
這是因?yàn)樵趨R編語(yǔ)言中對(duì)靜態(tài)變量的尋址通常是用直接尋址方式,這種方式直接使用變量的絕對(duì)偏移地址,如果被使用的變量也隨代碼一起被移動(dòng)到目標(biāo)地址,那對(duì)該變量的訪問(wèn)將會(huì)是對(duì)一個(gè)無(wú)效數(shù)據(jù)的訪問(wèn)。比如下面這段代碼:
Org 100H
Add SI,SI
Mov AX,Var1[SI]
Ret
Var1 DW 0,1,2,3,4,5,6,7,8,9
它的作用是從字?jǐn)?shù)組Var1中取出SI所指的那個(gè)節(jié)點(diǎn)的數(shù)據(jù)給AX并返回。這段程序編譯后的代碼如下:
0F05:0100 03F6 ADD SI,SI
0F05:0102 2E8B840801 MOV AX,CS:[SI+0108]
0F05:0107 C3 RET
0F05:0100 -00 00 01 00 02 00 03 00 ........
0F05:0110 04 00 05 00 06 00 07 00-08 00 09 00 ............
注意紅色的數(shù)字,它就是數(shù)組Var1的絕對(duì)偏移地址。此時(shí)如果我們把子程序GetData拷貝到偏移地址200H處,可以得到如下代碼:
0F05:0200 03F6 ADD SI,SI
0F05:0202 2E8B840801 MOV AX,CS:[SI+0108]
0F05:0207 C3 RET
0F05:0200 -00 00 01 00 02 00 03 00 ........
0F05:0210 04 00 05 00 06 00 07 00-08 00 09 00 ............
再次注意紅色的數(shù)字!此時(shí)數(shù)組Var1已經(jīng)被移動(dòng)到偏移地址208H處,而代碼中對(duì)數(shù)組的訪問(wèn)仍然使用的是編譯時(shí)的偏移地址。如果該處的數(shù)據(jù)正好被另的模塊更改過(guò)的話,后果就 ......
但是加密軟件生成的保護(hù)外殼和病毒代碼在加到宿主程序中時(shí)可以放在任何偏移地址。這就說(shuō)明編寫能夠在任何偏移地址運(yùn)行的代碼并不是不可能,只是對(duì)代碼有一定的要求。要想寫出能在任何偏移地址運(yùn)行的代碼必須要滿足幾個(gè)條件中的任意一條:
1.不引用變量,不使用NEAR/FAR跳轉(zhuǎn)或調(diào)用
2.只使用動(dòng)態(tài)變量
2.將對(duì)靜態(tài)變量的訪問(wèn)變?yōu)橄鄬?duì)尋址
不使用變量和NEAR/FAR跳轉(zhuǎn)自然不會(huì)生成使用直接尋址的代碼,當(dāng)然可以拷貝到任何地址運(yùn)行了,不過(guò)不能用變量限制太大了吧。
只使用動(dòng)態(tài)變量是很好的方法,所有的變量都存放在堆棧中,不管代碼被移動(dòng)到什么地址都很方便,而且它還帶來(lái)一個(gè)額外的好處:減小代碼尺寸。但它也有缺點(diǎn):就象C語(yǔ)言的子程序頭所做的一樣,你需要手工計(jì)算所用到的變量的總尺寸,運(yùn)行時(shí)從堆棧中為它們留出同樣大小的一塊,然后為每一個(gè)變量設(shè)置一個(gè)地址(比如:[ESP+4],{ESP+6]之類)。這些工作在高級(jí)語(yǔ)言中由編譯器代勞,但在匯編中得自己做,實(shí)在是太麻煩了。對(duì)靜態(tài)變量的訪問(wèn)必須用到它的絕對(duì)地址,但絕對(duì)地址也可以看作是對(duì)于偏移地址0000H的相對(duì)地址!因此我們就可以利用這一點(diǎn)將對(duì)靜態(tài)變量的訪問(wèn)變?yōu)閷?duì)相對(duì)于代碼的相對(duì)地址的訪問(wèn),這樣一來(lái)靜態(tài)數(shù)據(jù)跟隨代碼一起移動(dòng)就不會(huì)訪問(wèn)不到了。我們來(lái)看下面這段代碼:
01 Org 100H
02 Call Stub_Start
03 Stub_Start:
04 Pop BX
05 Sub BX,Offset Stub_Start
06 GetData:
07 Add SI,SI
08 Mov AX,Var1[SI+BX]
09 Var1 DW 0,1,2,3,4,5,6,7,8,9
最前面兩條語(yǔ)句Call Sub_Start和Pop BX取得標(biāo)號(hào)Sub_Start在運(yùn)行時(shí)的偏移地址(注意是運(yùn)行時(shí)的偏移地址!如果這段代碼被拷貝到內(nèi)存中的其他地方,那這個(gè)地址和用Offset Sub_Start得到的地址是不同的)。然后在下一條語(yǔ)句Sub BX,Offset Sub_Start計(jì)算代碼被移動(dòng)了多少字節(jié),然后在后面的內(nèi)存訪問(wèn)中加上代碼被移動(dòng)的字節(jié)數(shù)就是變量此時(shí)的實(shí)際地址了。比如,編譯好之后的代碼如下:
0F05:0100 E80000 CALL 0103
0F05:0103 5B POP BX
0F05:0104 81EB0301 SUB BX,0103
0F05:0108 03F6 ADD SI,SI
0F05:010A 2E8B800F01 MOV AX,CS:[BX+SI+010F]
0F05:0100 00 .
0F05:0110 00 01 00 02 00 03 00 04-00 05 00 06 00 07 00 08 ................
0F05:0120 00 09 00 ...
下面是運(yùn)行記錄
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0100 NV UP EI PL NZ NA PO NC
0F05:0100 E80000 CALL 0103
AX=0000 BX=0000 CX=0123 DX=0000 SP=FFFE BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0103 NV UP EI PL NZ NA PO NC
0F05:0103 5B POP BX
AX=0000 BX=0103 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0104 NV UP EI PL NZ NA PO NC
0F05:0104 81EB0301 SUB BX,0103
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0108 NV UP EI PL ZR NA PE NC
0F05:0108 03F6 ADD SI,SI
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=010A NV UP EI PL NZ NA PO NC
0F05:010A 2E8B800F01 MOV AX,CS:[BX+SI+010F] CS:0113=0002
AX=0002 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=010F NV UP EI PL NZ NA PO NC
0F05:010F 0000 ADD [BX+SI],AL DS:0004=00
在第二條語(yǔ)句得到該語(yǔ)句的當(dāng)前偏移地址0103H,再減去該語(yǔ)句編譯時(shí)的偏移地址0103H就得到代碼當(dāng)前地址與編譯時(shí)地址之差,然后在訪問(wèn)變量時(shí)地址加上這個(gè)差就能正確地訪問(wèn)到變量。下面再看把代碼拷貝到偏移地址0200H處的運(yùn)行結(jié)果。
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0200 NV UP EI PL NZ NA PO NC
0F05:0200 E80000 CALL 0103
AX=0000 BX=0000 CX=0123 DX=0000 SP=FFFE BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0203 NV UP EI PL NZ NA PO NC
0F05:0203 5B POP BX
AX=0000 BX=0203 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0204 NV UP EI PL NZ NA PO NC
0F05:0204 81EB0301 SUB BX,0103
AX=0000 BX=0100 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0208 NV UP EI PL ZR NA PE NC
0F05:0208 03F6 ADD SI,SI
AX=0000 BX=0100 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=020A NV UP EI PL NZ NA PO NC
0F05:020A 2E8B800F01 MOV AX,CS:[BX+SI+010F] CS:0213=0002
AX=0002 BX=0100 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=020F NV UP EI PL NZ NA PO NC
0F05:020F 0000 ADD [BX+SI],AL DS:0104=00
因?yàn)榇a被移動(dòng)了0100H,所以在第二條語(yǔ)句得到的偏移地址是0203H,減去0103H就得到代碼當(dāng)前地址與編譯時(shí)地址之差0100H,然后在訪問(wèn)變量時(shí)地址加上這個(gè)差,正確地從Var1的第3個(gè)單元中取出了數(shù)據(jù)。
綜上所述就是編寫可在任意地址執(zhí)行的代碼的技巧了。只要將代碼中所有的直接尋址操作按上面的方法修改,產(chǎn)生的代碼就可以被拷貝到任何地方使用
這是因?yàn)樵趨R編語(yǔ)言中對(duì)靜態(tài)變量的尋址通常是用直接尋址方式,這種方式直接使用變量的絕對(duì)偏移地址,如果被使用的變量也隨代碼一起被移動(dòng)到目標(biāo)地址,那對(duì)該變量的訪問(wèn)將會(huì)是對(duì)一個(gè)無(wú)效數(shù)據(jù)的訪問(wèn)。比如下面這段代碼:
Org 100H
Add SI,SI
Mov AX,Var1[SI]
Ret
Var1 DW 0,1,2,3,4,5,6,7,8,9
它的作用是從字?jǐn)?shù)組Var1中取出SI所指的那個(gè)節(jié)點(diǎn)的數(shù)據(jù)給AX并返回。這段程序編譯后的代碼如下:
0F05:0100 03F6 ADD SI,SI
0F05:0102 2E8B840801 MOV AX,CS:[SI+0108]
0F05:0107 C3 RET
0F05:0100 -00 00 01 00 02 00 03 00 ........
0F05:0110 04 00 05 00 06 00 07 00-08 00 09 00 ............
注意紅色的數(shù)字,它就是數(shù)組Var1的絕對(duì)偏移地址。此時(shí)如果我們把子程序GetData拷貝到偏移地址200H處,可以得到如下代碼:
0F05:0200 03F6 ADD SI,SI
0F05:0202 2E8B840801 MOV AX,CS:[SI+0108]
0F05:0207 C3 RET
0F05:0200 -00 00 01 00 02 00 03 00 ........
0F05:0210 04 00 05 00 06 00 07 00-08 00 09 00 ............
再次注意紅色的數(shù)字!此時(shí)數(shù)組Var1已經(jīng)被移動(dòng)到偏移地址208H處,而代碼中對(duì)數(shù)組的訪問(wèn)仍然使用的是編譯時(shí)的偏移地址。如果該處的數(shù)據(jù)正好被另的模塊更改過(guò)的話,后果就 ......
但是加密軟件生成的保護(hù)外殼和病毒代碼在加到宿主程序中時(shí)可以放在任何偏移地址。這就說(shuō)明編寫能夠在任何偏移地址運(yùn)行的代碼并不是不可能,只是對(duì)代碼有一定的要求。要想寫出能在任何偏移地址運(yùn)行的代碼必須要滿足幾個(gè)條件中的任意一條:
1.不引用變量,不使用NEAR/FAR跳轉(zhuǎn)或調(diào)用
2.只使用動(dòng)態(tài)變量
2.將對(duì)靜態(tài)變量的訪問(wèn)變?yōu)橄鄬?duì)尋址
不使用變量和NEAR/FAR跳轉(zhuǎn)自然不會(huì)生成使用直接尋址的代碼,當(dāng)然可以拷貝到任何地址運(yùn)行了,不過(guò)不能用變量限制太大了吧。
只使用動(dòng)態(tài)變量是很好的方法,所有的變量都存放在堆棧中,不管代碼被移動(dòng)到什么地址都很方便,而且它還帶來(lái)一個(gè)額外的好處:減小代碼尺寸。但它也有缺點(diǎn):就象C語(yǔ)言的子程序頭所做的一樣,你需要手工計(jì)算所用到的變量的總尺寸,運(yùn)行時(shí)從堆棧中為它們留出同樣大小的一塊,然后為每一個(gè)變量設(shè)置一個(gè)地址(比如:[ESP+4],{ESP+6]之類)。這些工作在高級(jí)語(yǔ)言中由編譯器代勞,但在匯編中得自己做,實(shí)在是太麻煩了。對(duì)靜態(tài)變量的訪問(wèn)必須用到它的絕對(duì)地址,但絕對(duì)地址也可以看作是對(duì)于偏移地址0000H的相對(duì)地址!因此我們就可以利用這一點(diǎn)將對(duì)靜態(tài)變量的訪問(wèn)變?yōu)閷?duì)相對(duì)于代碼的相對(duì)地址的訪問(wèn),這樣一來(lái)靜態(tài)數(shù)據(jù)跟隨代碼一起移動(dòng)就不會(huì)訪問(wèn)不到了。我們來(lái)看下面這段代碼:
01 Org 100H
02 Call Stub_Start
03 Stub_Start:
04 Pop BX
05 Sub BX,Offset Stub_Start
06 GetData:
07 Add SI,SI
08 Mov AX,Var1[SI+BX]
09 Var1 DW 0,1,2,3,4,5,6,7,8,9
最前面兩條語(yǔ)句Call Sub_Start和Pop BX取得標(biāo)號(hào)Sub_Start在運(yùn)行時(shí)的偏移地址(注意是運(yùn)行時(shí)的偏移地址!如果這段代碼被拷貝到內(nèi)存中的其他地方,那這個(gè)地址和用Offset Sub_Start得到的地址是不同的)。然后在下一條語(yǔ)句Sub BX,Offset Sub_Start計(jì)算代碼被移動(dòng)了多少字節(jié),然后在后面的內(nèi)存訪問(wèn)中加上代碼被移動(dòng)的字節(jié)數(shù)就是變量此時(shí)的實(shí)際地址了。比如,編譯好之后的代碼如下:
0F05:0100 E80000 CALL 0103
0F05:0103 5B POP BX
0F05:0104 81EB0301 SUB BX,0103
0F05:0108 03F6 ADD SI,SI
0F05:010A 2E8B800F01 MOV AX,CS:[BX+SI+010F]
0F05:0100 00 .
0F05:0110 00 01 00 02 00 03 00 04-00 05 00 06 00 07 00 08 ................
0F05:0120 00 09 00 ...
下面是運(yùn)行記錄
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0100 NV UP EI PL NZ NA PO NC
0F05:0100 E80000 CALL 0103
AX=0000 BX=0000 CX=0123 DX=0000 SP=FFFE BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0103 NV UP EI PL NZ NA PO NC
0F05:0103 5B POP BX
AX=0000 BX=0103 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0104 NV UP EI PL NZ NA PO NC
0F05:0104 81EB0301 SUB BX,0103
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0108 NV UP EI PL ZR NA PE NC
0F05:0108 03F6 ADD SI,SI
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=010A NV UP EI PL NZ NA PO NC
0F05:010A 2E8B800F01 MOV AX,CS:[BX+SI+010F] CS:0113=0002
AX=0002 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=010F NV UP EI PL NZ NA PO NC
0F05:010F 0000 ADD [BX+SI],AL DS:0004=00
在第二條語(yǔ)句得到該語(yǔ)句的當(dāng)前偏移地址0103H,再減去該語(yǔ)句編譯時(shí)的偏移地址0103H就得到代碼當(dāng)前地址與編譯時(shí)地址之差,然后在訪問(wèn)變量時(shí)地址加上這個(gè)差就能正確地訪問(wèn)到變量。下面再看把代碼拷貝到偏移地址0200H處的運(yùn)行結(jié)果。
AX=0000 BX=0000 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0200 NV UP EI PL NZ NA PO NC
0F05:0200 E80000 CALL 0103
AX=0000 BX=0000 CX=0123 DX=0000 SP=FFFE BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0203 NV UP EI PL NZ NA PO NC
0F05:0203 5B POP BX
AX=0000 BX=0203 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0204 NV UP EI PL NZ NA PO NC
0F05:0204 81EB0301 SUB BX,0103
AX=0000 BX=0100 CX=0123 DX=0000 SP=0000 BP=0000 SI=0002 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=0208 NV UP EI PL ZR NA PE NC
0F05:0208 03F6 ADD SI,SI
AX=0000 BX=0100 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=020A NV UP EI PL NZ NA PO NC
0F05:020A 2E8B800F01 MOV AX,CS:[BX+SI+010F] CS:0213=0002
AX=0002 BX=0100 CX=0123 DX=0000 SP=0000 BP=0000 SI=0004 DI=0000
DS=0EF5 ES=0EF5 SS=0F05 CS=0F05 IP=020F NV UP EI PL NZ NA PO NC
0F05:020F 0000 ADD [BX+SI],AL DS:0104=00
因?yàn)榇a被移動(dòng)了0100H,所以在第二條語(yǔ)句得到的偏移地址是0203H,減去0103H就得到代碼當(dāng)前地址與編譯時(shí)地址之差0100H,然后在訪問(wèn)變量時(shí)地址加上這個(gè)差,正確地從Var1的第3個(gè)單元中取出了數(shù)據(jù)。
綜上所述就是編寫可在任意地址執(zhí)行的代碼的技巧了。只要將代碼中所有的直接尋址操作按上面的方法修改,產(chǎn)生的代碼就可以被拷貝到任何地方使用