Ⅰ arm編程,c語言中嵌入匯編實現1+2+3+...+100
C語言中static關鍵字的常見用法及舉例
在嵌入式系統開發中,目前使用的主要編程語言是C和匯編,
C++已經有相應的編譯器,但是現在使用還是比較少的。在稍大
規模的嵌入式軟體中,例如含有OS,大部分的代碼都是用C編
寫的,主要是因為C語言的結構比較好,便於人的理解,而且有
大量的支持庫。盡管如此,很多地方還是要用到匯編語言,例如
開機時硬體系統的初始化,包括CPU狀態的設定,中斷的使能,
主頻的設定,以及RAM的控制參數及初始化,一些中斷處理方
面也可能涉及匯編。另外一個使用匯編的地方就是一些對性能非
常敏感的代碼塊,這是不能依靠C編譯器的生成代碼,而要手工
編寫匯編,達到優化的目的。而且,匯編語言是和CPU的指令集
緊密相連的,作為涉及底層的嵌入式系統開發,熟練對應匯編語
言的使用也是必須的。
單純的C或者匯編編程請參考相關的書籍或者手冊,這里主要討
論C和匯編的混合編程,包括相互之間的函數調用。下面分四種
情況來進行討論,暫不涉及C++。
1. 在C語言中內嵌匯編
在C中內嵌的匯編指令包含大部分的ARM和Thumb指令,不過其
使用與匯編文件中的指令有些不同,存在一些限制,主要有下面
幾個方面:
a. 不能直接向PC寄存器賦值,程序跳轉要使用B或者BL指令
b. 在使用物理寄存器時,不要使用過於復雜的C表達式,避免物理寄存器沖突
c.
R12和R13可能被編譯器用來存放中間編譯結果,計算表達式值時可能將R0到R3、R12及R14用於子程序調用,因此要避免直接使用這些物理寄存器
d. 一般不要直接指定物理寄存器,而讓編譯器進行分配
內嵌匯編使用的標記是 __asm或者asm關鍵字,用法如下:
__asm
{
instruction [; instruction]
…
[instruction]
}
asm(「instruction [; instruction]」);
下面通過一個例子來說明如何在C中內嵌匯編語言,
#include
void my_strcpy(const char *src, char *dest)
{
char ch;
__asm
{
loop:
ldrb ch, [src], #1
strb ch, [dest], #1
cmp ch, #0
bne loop
}
}
int main()
{
char *a = "forget it and move on!";
char b[64];
my_strcpy(a, b);
printf("original: %s", a);
printf("ed: %s", b);
return 0;
}
在這里C和匯編之間的值傳遞是用C的指針來實現的,因為指針
對應的是地址,所以匯編中也可以訪問。
2. 在匯編中使用C定義的全局變數
內嵌匯編不用單獨編輯匯編語言文件,比較簡潔,但是有諸多限
制,當匯編的代碼較多時一般放在單獨的匯編文件中。這時就需
要在匯編和C之間進行一些數據的傳遞,最簡便的辦法就是使用
全局變數。
/* cfile.c
* 定義全局變數,並作為主調程序
*/
#include
int gVar_1 = 12;
extern asmDouble(void);
int main()
{
printf("original value of gVar_1 is: %d", gVar_1);
asmDouble();
printf(" modified value of gVar_1 is: %d", gVar_1);
return 0;
}
對應的匯編語言文件
;called by main(in C),to double an integer, a global var defined in C
is used.
AREA asmfile, CODE, READONLY
EXPORT asmDouble
IMPORT gVar_1
asmDouble
ldr r0, =gVar_1
ldr r1, [r0]
mov r2, #2
mul r3, r1, r2
str r3, [r0]
mov pc, lr
END
3. 在C中調用匯編的函數
在C中調用匯編文件中的函數,要做的主要工作有兩個,一是在
C中聲明函數原型,並加extern關鍵字;二是在匯編中用
EXPORT導出函數名,並用該函數名作為匯編代碼段的標識,最
後用mov pc, lr返回。然後,就可以在C中使用該函數了。從
C的角度,並不知道該函數的實現是用C還是匯編。更深的原因
是因為C的函數名起到表明函數代碼起始地址的左右,這個和匯
編的label是一致的。
/* cfile.c
* in C,call an asm function, asm_strcpy
* Sep 9, 2004
*/
#include
extern void asm_strcpy(const char *src, char *dest);
int main()
{
const char *s = "seasons in the sun";
char d[32];
asm_strcpy(s, d);
printf("source: %s", s);
printf(" destination: %s",d);
return 0;
}
;asm function implementation
AREA asmfile, CODE, READONLY
EXPORT asm_strcpy
asm_strcpy
loop
ldrb r4, [r0], #1 ;address increment after read
cmp r4, #0
beq over
strb r4, [r1], #1
b loop
over
mov pc, lr
END
在這里,C和匯編之間的參數傳遞是通過ATPCS(ARM
Thumb Procere Call Standard)的規定來進行的。簡單的說就
是如果函數有不多於四個參數,對應的用R0-R3來進行傳遞,多
於4個時藉助棧,函數的返回值通過R0來返回。
4. 在匯編中調用C的函數
在匯編中調用C的函數,需要在匯編中IMPORT 對應的C函數名
,然後將C的代碼放在一個獨立的C文件中進行編譯,剩下的工
作由連接器來處理。
;the details of parameters transfer comes from ATPCS
;if there are more than 4 args, stack will be used
EXPORT asmfile
AREA asmfile, CODE, READONLY
IMPORT cFun
ENTRY
mov r0, #11
mov r1, #22
mov r2, #33
BL cFun
END
/*C file, called by asmfile */
int cFun(int a, int b, int c)
{
return a + b + c;
}
在匯編中調用C的函數,參數的傳遞也是通過ATPCS來實現
的。需要指出的是當函數的參數個數大於4時,要藉助stack,具
體見ATPCS規范
Ⅱ 寫出一個匯編語言的框架程序,內容2個段,數據段和代碼段
呵呵,建議你下了解一下殺毒軟體的工作機制。
金山,360,瑞星等殺毒軟體,都是驅動保護的,bat因為比它高層,所以做不到關閉殺毒軟體。否則連批處理都可輕易攻破,那早就有無數病毒泛濫了。只有匯編語言這樣的底層語言才可以,(其實C語言也能做到)。當然,bat也不是不行,可以用bat匯編,叫做ASCIICoding技術,挺難的。2007年以前國內還沒有一個會的(除非會但不說,那我當然就不知道了)。也建議你學習Rootkit Hook技術,可以讀一讀這篇文章。
瘟神的尾行--Rootkit技術發展史
作者:小金
一. 無法驅逐的「助手」
網管小張正在手忙腳亂的尋找他的手工殺毒工具包,因為他在安裝一個網管工具的時候無意中走了神,點擊了「下一步」按鈕後才驚覺安裝程序的界面里一個不引人注目的角落裡寫著「安裝CNNIC網路實名」這一行小字,而且最開頭部分有一個小小的勾。於是著名的「中國網民的得力助手」便理所當然的在他的機器里安了家。
心裡把廠商罵了十八遍的小張終於翻出了他外出修機時最得意的工具IceSword和超級巡警,果然在進程列表和SSDT列表裡發現了紅色警報,小張笑了笑,對付這些一般用戶無法卸載的惡意流氓,自己可謂經驗豐富了,當下便三下五除二的把CNNIC的進程給終結了,SSDT也給恢復了初始狀態,然後小張去刪除注冊表啟動項——突然發出的一個錯誤提示聲音把小張嚇了一跳,再定睛一看,他的笑容凝固了:「刪除項時出錯」。不會吧?小張急忙去刪除CNNIC目錄,結果徹底愣在了那裡,系統彈出的錯誤提示很明確的告訴他,「無法刪除文件,文件可能正在被使用」。怎麼回事?小張一下子沒了頭緒……
達爾文的進化論告訴我們,「物競天擇,適者生存」,同樣,在這安全與入侵的網路世界裡,也在進行著這樣一場選擇的過程……
二. 被AIDS糾纏的互聯網
。。。。。。。。。
。。。。。。。。。。。。。
。。。。。。。。。。。。。。(網路長度限制。。。想看全篇自己搜吧)
三. 結語
雖然到處都在提倡和諧網路的普及,但是,「健康上網」僅僅是指代那些黃賭毒而已嗎?在利益面前,開發者的正義感越發渺小起來,我們的網路世界,是被瘟神緊緊跟隨著的。技術的斗爭越發激烈,但是用戶的電腦知識是不會跟著時代發展而自動填充的,最終,大眾上網的人民成了這一切技術較量的受害者。
這個荒謬的發展方向,何時才能休止呢?
還有這篇:
對大多數的Windows開發者來說,如何在Win32系統中對API函數的調用進行攔截一直是項極富挑戰性的課題,因為這將是對你所掌握的計算機知識較為全面的考驗,尤其是一些在如今使用RAD進行軟體開發時並不常用的知識,這包括了操作系統原理、匯編語言甚至是機器指令(聽上去真是有點恐怖,不過這是事實)。
當前廣泛使用的Windows操作系統中,像Win 9x和Win NT/2K,都提供了一種比較穩健的機制來使得各個進程的內存地址空間之間是相互獨立,也就是說一個進程中的某個有效的內存地址對另一個進程來說是無意義的,這種內存保護措施大大增加了系統的穩定性。不過,這也使得進行系統級的API攔截的工作的難度也大大加大了。
當然,我這里所指的是比較文雅的攔截方式,通過修改可執行文件在內存中的映像中有關代碼,實現對API調用的動態攔截;而不是採用比較暴力的方式,直接對可執行文件的磁碟存儲中機器代碼進行改寫。
二、API鉤子系統一般框架
通常,我們把攔截API的調用的這個過程稱為是安裝一個API鉤子(API Hook)。一個API鉤子基本是由兩個模塊組成:一個是鉤子伺服器(Hook Server)模塊,一般為EXE的形式;一個是鉤子驅動器(Hook Driver)模塊,一般為DLL的形式。
鉤子伺服器主要負責向目標進程注入鉤子驅動器,使得鉤子驅動器運行在目標進程的地址空間中,這是關鍵的第一步,而鉤子驅動器則負責實際的API攔截處理工作,以便在我們所關心的API函數調用的之前或之後能做一些我們所希望的工作。一個比較常見的API鉤子的例子就是一些實時翻譯軟體(像金山詞霸)中必備的的功能:屏幕抓詞。它主要是對一些Win32 API中的GDI函數進行了攔截,獲取它們的輸入參數中的字元串,然後在自己的窗口中顯示出來。
針對上述關於API鉤子的兩個部分,有以下兩點需要我們重點考慮的: 選用何種DLL注入技術,以及採用何種API攔截機制。
三、注入技術的選用
由於在Win32系統中各個進程的地址是互相獨立的,因此我們無法在一個進程中對另一個進程的代碼進行有效的修改,但如果你要完成API鉤子的工作又必須如此。因此,我們必須採取某種獨特的手段,使得API鉤子(准確的說是鉤子驅動器)能夠成為目標進程中的一部分,才有較大的可能來對目標進程數據和代碼進行有控制的修改。
通常可採用的幾種注入方式:
1.利用注冊表
如果我們准備攔截的進程連接了User32.dll,也就是使用了 User32.dll中的API(一般圖形界面的應用程序都是符合這個條件),那麼就可以簡單把你的鉤子驅動器DLL的名字作為值添加在下面注冊表的鍵下: HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs 值的形式可以為單個DLL的文件名,或者是一組DLL的文件名,相鄰的名稱之間用逗號或空格間隔。所有由該值標識的DLL將在符合條件的應用程序啟動的時候裝載。這是一個操作系統內建的機制,相對其他方式來說危險性較小,但它也有一些比較明顯的缺點:該方法僅適用於NT/2K操作系統,顯然看看鍵的名稱就可以明白;如果需要激活或停止鉤子的注入,只有重新啟動Windows,這個就似乎太不方便了;最後一點也很顯然,不能用此方法向沒有使用User32.dll的應用程序注入DLL,例如控制台應用程序等。另外,不管是否為你所希望,鉤子DLL將注入每一個GUI應用程序,這將導致整個系統性能的下降!
2.建立系統范圍的Windows鉤子
要向某個進程注入DLL,一個十分普遍也是比較簡單的方法就是建立在標準的Windows鉤子的基礎上。Windows鉤子一般是在DLL中實現的,這是一個全局性的Windows鉤子的基本要求,這也很符合我們的需要。當我們成功地調用SetWindowsHookEx函數之後,便在系統中安裝了某種類型的消息鉤子,這個鉤子可以是針對某個進程,也可以是針對系統中的所有進程。一旦某個進程中產生了該類型的消息,操作系統會自動把該鉤子所在的DLL映像到該進程的地址空間中,從而使得消息回調函數(在 SetWindowsHookEx的參數中指定)能夠對此消息進行適當的處理,在這里,我們所感興趣的當然不是對消息進行什麼處理,因此在消息回調函數中只需把消息鉤子向後傳遞就可以了,但是我們所需的DLL已經成功地注入了目標進程的地址空間,從而可以完成後續工作。
我們知道,不同的進程之間是不能直接共享數據的,因為它們活動在不同的地址空間中。但在Windows鉤子 DLL中,有一些數據,例如Windows鉤子句柄HHook,這是由SetWindowsHookEx函數返回值得到的,並且作為參數將在 CallNextHookEx函數和UnhookWindoesHookEx函數中使用,顯然使用SetWindowsHookEx函數的進程和使用 CallNextHookEx函數的進程一般不會是同一個進程,因此我們必須能夠使句柄在所有的地址空間中都是有效的有意義的,也就是說,它的值必須必須在這些鉤子DLL所掛鉤的進程之間是共享的。為了達到這個目的,我們就應該把它存儲在一個共享的數據區域中。
在VC++中我們可以採用預編譯指令#pragma data_seg在DLL文件中創建一個新的段,並且在DEF文件中把該段的屬性設置為"shared",這樣就建立了一個共享數據段。對於使用 Delphi的人來說就沒有這么幸運了:沒有類似的比較簡單的方法(或許是有的,但我沒有找到)。不過我們還是可以利用內存映像技術來申請使用一塊各進程可以共享的內存區域,主要是利用了CreateFileMapping和MapViewOfFile這兩個函數,這倒是一個通用的方法,適合所有的開發語言,只要它能直接或間接的使用Windows的API。
在Borland的BCB中有一個指令#pragma codeseg與VC++中的#pragma data_seg指令有點類似,應該也能起到一樣的作用,但我試了一下,沒有沒有效果,而且BCB的聯機幫助中對此也提到的不多,不知怎樣才能正確的使用(或許是另外一個指令,呵呵)。
一旦鉤子DLL載入進入目標進程的地址空間後,在我們調用UnHookWindowsHookEx函數之前是無法使它停止工作的,除非目標進程關閉。
這種DLL注入方式有兩個優點: 這種機制在Win 9x/Me和Win NT/2K中都是得到支持的,預計在以後的版本中也將得到支持;鉤子DLL可以在不需要的時候,可由我們主動的調用 UnHookWindowsHookEx來卸載,比起使用注冊表的機制來說方便了許多。盡管這是一種相當簡潔明了的方法,但它也有一些顯而易見的缺點:首先值得我們注意的是,Windows鉤子將會降低整個系統的性能,因為它額外增加了系統在消息處理方面的時間;其次,只有當目標進程准備接受某種消息時,鉤子所在的DLL才會被系統映射到該進程的地址空間中,鉤子才能真正開始發揮作用,因此如果我們要對某些進程的整個生命周期內的API調用情況進行監控,用這種方法顯然會遺漏某些API的調用 。
3.使用 CreateRemoteThread函數
在我看來這是一個相當棒的方法,然而不幸的是,CreateRemoteThread這個函數只能在Win NT/2K系統中才得到支持,雖然在Win 9x中這個API也能被安全的調用而不出錯,但它除了返回一個空值之外什麼也不做。該注入過程也十分簡單:我們知道,任何一個進程都可以使用 LoadLibrary來動態地載入一個DLL。但問題是,我們如何讓目標進程(可能正在運行中)在我們的控制下來載入我們的鉤子DLL(也就是鉤子驅動器)呢?有一個API函數CreateRemoteThread,通過它可在一個進程中可建立並運行一個遠程的線程--這個好像和注入沒什麼關系嘛?往下看!
調用該API需要指定一個線程函數指針作為參數,該線程函數的原型如下: Function ThreadProc(lpParam: Pointer): DWORD,我們再來看一下LoadLibrary的函數原型: Function LoadLibrary(lpFileName: PChar): HMole。發現了吧!這兩個函數原型幾乎是一樣的(其實返回值是否相同關系不大,因為我們是無法得到遠程線程函數的返回值的),這種類似使得我們可以把直接把LoadLibrary當做線程函數來使用,從而在目標進程中載入鉤子DLL。
與此類似,當我們需要卸載鉤子DLL時,也可以FreeLibrary作為線程函數來使用,在目標進程中卸載鉤子DLL,一切看來是十分的簡潔方便。通過調用GetProcAddress函數,我們可以得到LoadLibrary函數的地址。由於 LoadLibrary是Kernel32中的函數,而這個系統DLL的映射地址對每一個進程來說都是相同的,因此LoadLibrary函數的地址也是如此。這點將確保我們能把該函數的地址作為一個有效的參數傳遞給CreateRemoteThread使用。 FreeLibrary也是一樣的。
AddrOfLoadLibrary := GetProcAddress(GetMoleHandle(『Kernel32.dll'), 『LoadLibrary');
HRemoteThread := CreateRemoteThread(HTargetProcess, nil, 0, AddrOfLoadLibrary, HookDllName, 0, nil);
要使用CreateRemoteThread,我們需要目標進程的句柄作為參數。當我們用 OpenProcess函數來得到進程的句柄時,通常是希望對此進程有全權的存取操作,也就是以PROCESS_ALL_ACCESS為標志打開進程。但對於一些系統級的進程,直接這樣顯然是不行的,只能返回一個的空句柄(值為零)。為此,我們必須把自己設置為擁有調試級的特權,這樣將具有最大的存取許可權,從而使得我們能對這些系統級的進程也可以進行一些必要的操作。
4.通過BHO來注入DLL
有時,我們想要注入DLL的對象僅僅是Internet Explorer,很幸運,Windows操作系統為我們提供了一個簡單的歸檔方法(這保證了它的可靠性!)―― 利用Browser Helper Objects(BHO)。一個BHO是一個在 DLL中實現的COM對象,它主要實現了一個IObjectWithSite介面,而每當IE運行時,它會自動載入所有實現了該介面的COM對象。
四、攔截機制
在鉤子應用的系統級別方面,有兩類API攔截的機制――內核級的攔截和用戶級的攔截。內核級的鉤子主要是通過一個內核模式的驅動程序來實現,顯然它的功能應該最為強大,能捕捉到系統活動的任何細節,但難度也較大,不在本文的探討范圍之內(尤其對我這個使用Delphi的人來說,還沒涉足這塊領域,因此也無法探討,呵呵)。
而用戶級的鉤子則通常是在普通的DLL中實現整個API的攔截工作,這才是此次重點關注的。攔截API函數的調用,一般可有以下幾種方法:
1. 代理DLL(特洛伊木馬
一個容易想到的可行的方法是用一個同名的DLL去替換原先那個輸出我們准備攔截的API所在的DLL。當然代理DLL也要和原來的一樣,輸出所有函數。但如果想到DLL中可能輸出了上百個函數,我們就應該明白這種方法的效率是不高的,估計是要累死人的。另外,我們還不得不考慮DLL的版本問題,很是麻煩。
2.改寫執行代碼
有許多攔截的方法是基於可執行代碼的改寫,其中一個就是改變在CALL指令中使用的函數地址,這種方法有些難度,也比較容易出錯。它的基本思路是檢索出在內存中所有你所要攔截的API的CALL指令,然後把原先的地址改成為你自己提供的函數的地址。
另外一種代碼改寫的方法的實現方法更為復雜,它的主要的實現步驟是先找到原先的API函數的地址,然後把該函數開始的幾個位元組用一個JMP指令代替(有時還不得不改用一個INT指令),從而使得對該API函數的調用能夠轉向我們自己的函數調用。實現這種方法要牽涉到一系列壓棧和出棧這樣的較底層的操作,顯然對我們的匯編語言和操作系統底層方面的知識是一種考驗。這個方法倒和很多文件型病毒的感染機制相類似。
3.以調試器的身份進行攔截
另一個可選的方法是在目標函數中安置一個調試斷點,使得進程運行到此處就進入調試狀態。然而這樣一些問題也隨之而來,其中較主要的是調試異常的產生將把進程中所有的線程都掛起。它也需要一個額外的調試模塊來處理所有的異常,整個進程將一直在調試狀態下運行,直至它運行結束。
4.改寫PE文件的輸入地址表
這種方法主要得益於現如今Windows系統中所使用的可執行文件(包括EXE文件和DLL文件)的良好結構――PE文件格式(Portable Executable File Format),因此它相當穩健,又簡單易行。要理解這種方法是如何運作的,首先你得對PE文件格式有所理解。
一個PE文件的結構大致如下所示:一般PE文件一開始是一段DOS程序,當你的程序在不支持Windows的環境中運行時,它就會顯示"This Program cannot be run in DOS mode"這樣的警告語句;接著這個DOS文件頭,就開始真正的PE文件內容了,首先是一段稱為"IMAGE_NT_HEADER"的數據,其中是許多關於整個PE文件的消息,在這段數據的尾端是一個稱為Data Directory的數據表,通過它能快速定位一些PE文件中段(section)的地址;在這段數據之後,則是一個"IMAGE_SECTION_HEADER"的列表,其中的每一項都詳細描述了後面一個段的相關信息;接著它就是PE文件中最主要的段數據了,執行代碼、數據和資源等等信息就分別存放在這些段中。
在所有的這些段里,有一個被稱為".idata"的段(輸入數據段)值得我們去注意,該段中包含著一些被稱為輸入地址表(IAT,Import Address Table)的數據列表,每個用隱式方式載入的API所在的DLL都有一個IAT與之對應,同時一個API的地址也與IAT中一項相對應。當一個應用程序載入到內存中後,針對每一個API函數調用,相應的產生如下的匯編指令:
JMP DWORD PTR [XXXXXXXX]
如果在VC++中使用了_delcspec(import),那麼相應的指令就成為:
CALL DWORD PTR [XXXXXXXX]。
不管怎樣,上述方括弧中的總是一個地址,指向了輸入地址表中一個項,是一個DWORD,而正是這個 DWORD才是API函數在內存中的真正地址。因此我們要想攔截一個API的調用,只要簡單的把那個DWORD改為我們自己的函數的地址,那麼所有關於這個API的調用將轉到我們自己的函數中去,攔截工作也就宣告順利的成功了。這里要注意的是,自定義的函數的調用約定應該是API的調用約定,也就是 stdcall,而Delphi中默認的調用約定是register,它們在參數的傳遞方法等方面存在著較大的區別。
另外,自定義的函數的參數形式一般來講和原先的API函數是相同的,不過這也不是必須的,而且這樣的話在有些時候也會出現一些問題,我在後面將會提到。因此要攔截API的調用,首先我們就要得到相應的IAT的地址。系統把一個進程模塊載入到內存中,其實就是把 PE文件幾乎是原封不動的映射到進程的地址空間中去,而模塊句柄HMole實際上就是模塊映像在內存中的地址,PE文件中一些數據項的地址,都是相對於這個地址的偏移量,因此被稱為相對虛擬地址(RVA,Relative Virtual Address)。
於是我們就可以從HMole開始,經過一系列的地址偏移而得到IAT的地址。不過我這里有一個簡單的方法,它使用了一個現有的API函數ImageDirectoryEntryToData,它幫助我們在定位IAT時能少走幾步,省得把偏移地址弄錯了,走上彎路。不過純粹使用RVA從HMole開始來定位IAT的地址其實並不麻煩,而且這樣還更有助於我們對PE文件的結構的了解。上面提到的那個API 函數是在DbgHelp.dll中輸出的(這是從Win 2K才開始有的,在這之前是由ImageHlp.dll提供的),有關這個函數的詳細介紹可參見MSDN。
在找到IAT之後,我們只需在其中遍歷,找到我們需要的API地址,然後用我們自己的函數地址去覆蓋它,下面給出一段對應的源碼:
procere RedirectApiCall; var ImportDesc:PIMAGE_IMPORT_DESCRIPTOR; FirstThunk:PIMAGE_THUNK_DATA32; sz:DWORD;
begin
//得到一個輸入描述結構列表的首地址,每個DLL都對應一個這樣的結構 ImportDesc:=ImageDirectoryEntryToData(Pointer(HTargetMole), true, IMAGE_DIRECTORY_ENTRY_IMPORT, sz);
while Pointer(ImportDesc.Name)<>nil do
begin //判斷是否是所需的DLL輸入描述
if StrIComp(PChar(DllName),PChar(HTargetMole+ImportDesc.Name))=0 then begin
//得到IAT的首地址
FirstThunk:=PIMAGE_THUNK_DATA32(HTargetMole+ImportDesc.FirstThunk);
while FirstThunk.Func<>nil do
begin
if FirstThunk.Func=OldAddressOfAPI then
begin
//找到了匹配的API地址 ......
//改寫API的地址
break;
end;
Inc(FirstThunk);
end;
end;
Inc(ImportDesc);
end;
end;
最後有一點要指出,如果我們手工執行鉤子DLL的退出目標進程,那麼在退出前應該把函數調用地址改回原先的地址,也就是API的真正地址,因為一旦你的DLL退出了,改寫的新的地址將指向一個毫無意義的內存區域,如果此時目標進程再使用這個函數顯然會出現一個非法操作。
五、替換函數的編寫
前面關鍵的兩步做完了,一個API鉤子基本上也就完成了。不過還有一些相關的東西需要我們研究一番的,包括怎樣做一個替換函數。 下面是一個做替換函數的步驟: 首先,不失一般性,我們先假設有這樣的一個API函數,它的原型如下:
function SomeAPI(param1: Pchar;param2: Integer): DWORD;
接著再建立一個與之有相同參數和返回值的函數類型:
type FuncType= function (param1: Pchar;param2: Integer): DWORD;
然後我們把SomeAPI函數的地址存放在OldAddress指針中。接著我們就可以著手寫替換函數的代碼了:
function DummyFunc(param1: Pchar;param2: Integer): DWORD; begin ......
//做一些調用前的操作
//調用被替換的函數,當然也可以不調用
result := FuncType(OldAddress) (param1 , param2);
//做一些調用後的操作
end;
我們再把這個函數的地址保存到NewAddress中,接著用這地址覆蓋掉原先API的地址。這樣當目標進程調用該API的時候,實際上是調用了我們自己的函數,在其中我們可以做一些操作,然後在調用原先的API函數,結果就像什麼也沒發生過一樣。當然,我們也可以改變輸入參數的值,甚至是屏蔽調這個API函數的調用。
盡管上述方法是可行的,但有一個明顯的不足:這種替換函數的製作方法不具有通用性,只能針對少量的函數。如果只有幾個API要攔截,那麼只需照上述說的重復做幾次就行了。但如果有各種各樣的API要處理,它們的參數個數和類型以及返回值的類型是各不相同的,仍然採用這種方法就太沒效率了。
的確是的,上面給出的只是一個最簡單最容易想到的方法,只是一個替換函數的基本構架。正如我前面所提到的,替換函數的與原先的API函數的參數類型不必相同,一般的我們可以設計一個沒有調用參數也沒有返回值的函數,通過一定的技巧,使它能適應各種各樣的API 函數調用,不過這得要求你對匯編語言有一定的了解。
首先,我們來看一下執行到一個函數體內前的系統堆棧情況(這里函數的調用方式為stdcall),函數的調用參數是按照從右到左的順序壓入堆棧的(堆棧是由高端向低端發展的),同時還壓入了一個函數返回地址。在進入函數之前,ESP正指向返回地址。因此,我們只要從ESP+4開始就可以取得這個函數的調用參數了,每取一個參數遞增4。另外,當從函數中返回時,一般在EAX中存放函數的返回值。
了解了上述知識,我們就可以設計如下的一個比較通用的替換函數,它利用了Delphi的內嵌式匯編語言的特性。
Procere DummyFunc;
asm add esp,4 mov eax,esp//得到第一個參數
mov eax,esp+4//得到第二個參數 ......
//做一些處理,這里要保證esp在這之後恢復原樣
call OldAddress //調用原先的API函數 ......
//做一些其它的事情
end;
當然,這個替換函數還是比較簡單的,你可以在其中調用一些純粹用OP語言寫的函數或過程,去完成一些更復雜的操作(要是都用匯編來完成,那可得把你忙死了),不過應該把這些函數的調用方式統一設置為stdcall方式,這使它們只利用堆棧來傳遞參數,因此你也只需時刻掌握好堆棧的變化情況就行了。如果你直接把上述匯編代碼所對應的機器指令存放在一個位元組數組中,然後把數組的地址當作函數地址來使用,效果是一樣的。
六、後記
做一個API鉤子的確是件不容易的事情,尤其對我這個使用Delphi的人來說,為了解決某個問題,經常在OP、C++和匯編語言的資料中東查西找,在程序調試中還不時的發生一些意想不到的事情,弄的自己是手忙腳亂。不過,好歹總算做出了一個 API鉤子的雛形,還是令自己十分的高興,對計算機系統方面的知識也掌握了不少,受益非淺。當初在寫這篇文章之前,我只是想翻譯一篇從網上Down下來的英文資料(網址為 ,文章名叫"API Hook Revealed",示例源代碼是用VC++寫的,這里不得不佩服老外的水平,文章寫得很有深度,而且每個細節都講的十分詳細)。
Ⅲ 嵌入式c語言調用匯編 匯編中用export聲明,還要用import
用import,該標識符表明要調用的函數為本模塊外部定義的
export標識符表示本模塊中定時的符號可以為外部模塊使用
Ⅳ 匯編 import main 和 bl main 有什麼區別嗎(main是c語言中的main)
通用C語言__主入口,初始化裡面的東西,然後調用write自己的主
Ⅳ 如何在android中使用匯編語言
由於Android環境非常復雜,框架都是用java,因此要使用C/C++都需要做很多配置,使用匯編的話需要做更多的工作。
我這邊使用的是最新的Android4.0的開發工具,NDK也是最新支持4.0的。這個NDK與老版本的有一些比較明顯的不同。
由於我用的是Mac OS X,因此配置起來比瘟抖死上的要容易許多,你不需要再裝些雜七雜八的第三方工具,直接可以使用你下載好的NDK。
首先,設置目標路徑——在你的Terminal中進入NDK的根目錄,隨後打NDK_PROJECT_PATH="<你要編譯的項目路徑>"。回車,再輸入export NDK_PROJECT_PATH
回車。
這里要注意的是NDK_PROJECT_PATH=後面的路徑需要加引號,否則無效。
由於NDK默認支持的默認編譯選項僅支持ARMv5到ARMv5TE架構,因此如果要使用比較高級的特性的話有兩種方法:
1、你有辦法將TARGET_ARCH_ABI的值變為armeabi-v7a,俺自己試了一下,木有成功。因此可以使用第二種方法,更簡單便捷:
2、在你的NDK目錄下,找到toolchains,然後找到arm-linux-androideabi-x.y.z目錄,在進去可以發現setup.mk文件。找到-march=armv7-a,將上面的神馬#ifdef都去掉,下面的#endif也都刪了。這樣就能確保編譯器使用ARMv7A來編譯。
完成上述操作之後我們就可以先用最簡單的方式來寫匯編了,即內聯匯編——
static int my_thumb(int mmy)
{
__asm__("movw r0, #1001 \t\n"
"movw r12, #2020 \t\n"
"add r0, r0, r12 \t\n"
"bx lr");
return mmy;
}
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jobject thiz )
{
my_thumb(0);
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
上述代碼其實就是基於NDK自帶的hello-jni項目修改的。最後用ndk-build可以成功編譯。
上面一段代碼是編譯器默認的使用Thumb/Thumb-2編譯的,因此我裡面寫的內聯匯編的指令都是Thumb代碼。
我們下面將講述一下如何使用ARM代碼並使用NEON指令集。
首先,在你的Android.mk中修改LOCAL_SRC_FILES,要將源文件名後面添加.neon後綴,比如LOCAL_SRC_FILES := hello-jni.c改成LOCAL_SRC_FILES := hello-jni.c.neon。
這里要注意的是你真正的源文件名不要修改,就修改LOCAL_SRC_FILES這個符號的值即可。
然後我們再添加新的變數,來指示ARM GCC使用ARM指令集來編譯——LOCAL_ARM_MODE := arm
這樣就OK了。我們修改一下代碼:
static int my_arm(int mmy)
{
__asm__("movw r0, #1001 \t\n"
"movw r12, #2020 \t\n"
"add r0, r0, r12 \t\n"
"vp.32 q0, r0 \t\n"
"bx lr");
return mmy;
}
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jobject thiz )
{
my_arm(0);
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
使用ndk-build後能正常通過編譯。
最後再上個最最高端的。直接寫匯編文件。NDK帶有GAS工具,因此按常理,完全可以寫匯編文件。一般匯編文件的後綴名為.s,因此我們創建一個xxx.s文件即可。
然後我這邊創建一個叫hey.s。在Android.mk中將這個文件添加上:LOCAL_SRC_FILES += hey.s.neon
我們這里看到,為了能在匯編文件中使用NEON指令集,我們在這里也把.neon後綴添加上。匯編器的makefile也認這個標識。
我們編輯hey.s文件:
.text
.align 4
.arm
.globl my_real_arm
my_real_arm:
add r0, r0, #256
vmov q0, q1
vp.32 q0, r0
bx lr
這里要注意的是,在Apple的匯編器中,函數名要加前綴下劃線,而NDK中提供的匯編器則不需要。
我們修改一下hello-jni.c,把這函數調進去:
extern void my_real_arm(int i);
static int my_arm(int mmy)
{
__asm__("movw r0, #1001 \t\n"
"movw r12, #2020 \t\n"
"add r0, r0, r12 \t\n"
"vp.32 q0, r0 \t\n"
"bx lr");
return mmy;
}
jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
jobject thiz )
{
my_real_arm(0);
my_arm(0);
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
當然,我們為了確保編譯器能夠正確地將ARM和Thumb指令集做混合連接,我們可以在剛才的setup.mk中強制在TARGET_CFLAGS標志里加上-mthumb-interwork
在Windows操作系統中試驗,終於發現,只要將Application.mk中的APP_ABI中的標志,將armeabi去掉,僅留下armeabi-v7a就能順利使用neon了。這樣不需要修改setup.mk,也不需要將Sample中的那個標志判斷去掉,非常方便。
下面列一下可用的Android.mk編譯配置文件:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := HelloNeon
LOCAL_SRC_FILES := helloneon.c
LOCAL_ARM_MODE := arm
TARGET_CFLAGS += -mthumb-interwork
TARGET_CFLAGS += -std=gnu11
TARGET_CFLAGS += -O3
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_CFLAGS := -DHAVE_NEON=1
LOCAL_SRC_FILES += neontest.s.neon
LOCAL_ARM_NEON := true
endif
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
$(call import-mole,cpufeatures)
在使用JNI時,只需要在你當前項目工程目錄中添加jni文件夾,然後在裡面根據Sample中所提供的文件布局來做即可。當你用ndk-build(Windows下要在cygwin控制台中用ndk-build.cmd)來編譯時, 如果構建成功,則會在libs文件夾內生成一個libXXX.so。然後用Eclipse ADT重新打開你的項目工程,就會發現jni文件目錄以及生成好的so文件都會在你的工程文件目錄中展現出來。當然,你後面也能直接在Eclipse IDE下編輯.s匯編文件,這樣就更容易閱讀了。
最後,在Android匯編器中如果要注釋某條語句,那麼必須使用C89/90中的注釋符——/* ... */
用分號以及後來C++98中所引入的//形式都不管用。
在最新的NDK版本android-ndk-r8d中加入了ARM-Linux GCC4.7以及當前大紅大紫的LLVM Clang3.1。不過由於LLVM Clang3.1的很多編譯選項與GCC有不少區別,因此在使用Clang3.1的時候需要自己去配置相應的編譯選項。這個版本的NDK默認的編譯器工具鏈使用的是GCC4.6版本。如果要使用GCC4.7,那麼可以在Application.mk文件中添加NDK_TOOLCHAIN_VERSION=4.7;如果要使用Clang3.1,那麼可以在Application.mk中添加NDK_TOOLCHAIN_VERSION=clang3.1。下面給出一個合法的Application.mk的內容:
# Build with LLVM Clang3.1
#NDK_TOOLCHAIN_VERSION=clang3.1
# Build with ARM-Linux GCC4.7
NDK_TOOLCHAIN_VERSION=4.7
# Build only ARMv7-A machine code.
APP_ABI := armeabi-v7a
Ⅵ arm匯編中前面一堆IMPORT作用是什麼
IMPORT 後的標號 來自 外部文件
Ⅶ 用匯編語言編寫一個程序
定義兩個變數,都為int類型的!
num1*num2就行了,問題是你有沒有不同的軟體就是了!
不同的語言系統輸出或者控制台輸出都是不一樣的!
JAVA中
import java.util.*;
public static void main(string[] args)
{
int num1;
int num2;
Scanner input = new Scanner(System in);
num1=input.nextInt();
num2=input.nextInt();
System.out.println("最後的成績為!"+num1*num2);
}
Ⅷ 匯編語言是怎麼調用c語言的程序的
一、 參數傳遞的基本規則(ATPCS(ARM—Thumb Procere Call Standard))
1、 參數傳遞
二、匯編程序、C程序相互調用舉例
1、 C程序調用匯編程序
匯編程序的設計要遵守ATPCS(ARM—Thumb Procere Call Standard),保證程序調用時參數的正確傳遞。在匯編程序中使用EXPORT 偽操作聲明本程序,使得本程序可以被別的程序調用。在C程序使用extern聲明該匯編程序。
下面是一個C程序調用匯編程序的例子。其中匯編程序str實現字元串復制功能,C程序調用str完成字元串復制的工作。
//C程序
#include <stdio.h>
extern void str(char *d, const char *s);
int main( )
{
const char *srcstr=」First string-source」;
char dststr[ ]=」Second string-destination」;
printf(「Before ing:\n」);
printf(「%s\n %s\n」, srcstr,dststr);
str(dststr,srcstr);
printf(「After ing:\n」);
printf(「%s\n %s\n 「,srcstr,dststr);
while(1) ;
}
;匯編程序
AREA S, CODE, READONLY
EXPORT str
Str
LDRB R2, [R1], #1
STRB R2, [R0], #1
CMPR2,#0
BNE Str
MOV PC, LR
END
2、 匯編程序調用C程序
匯編程序的設計要遵守ATPCS,保證程序調用時參數的正確傳遞。在匯編程序中使用IMPORT偽操作聲明將要調用的C程序。下面是一個匯編程序調用C程序的例子。其中在匯編程序中設置好各參數的值。本例中有6個參數,分別使用寄存器R0存放第1個參數,
R1存放第2個參數, R2存放第3個參數, R3存放第4個參數, 第5個、第6個參數利用數據棧傳送。由於利用數據棧傳遞參數,在程序調用結束後要調整數據棧指針。
//C程序g( )返回6個參數的和
int g( int a, int b, int c, int d, int e, int f )
{
printf(「e=%d\n」, e);
printf(「f=%d\n」, f);
return (a+b+c+d+e+f);
}
; 匯編程序調用C程序 g( ) 計算6個整數 i, 2*i, 3*i, 4*i, 5*i, 6*i的和
EXPORT f
AREA f ,CODE, READONLY
IMPORT g
MOV R0, #1
ADD R1, R0, R0
ADD R2, R1, R0
ADD R3, R2, R0
ADD R4, R3, R0
ADD R5, R4, R0
STR R4, [SP, #-4]!
STR R5, [SP, #-4]!
BL g
ADD SP, SP, #4
ADD SP, SP, #4
STOP B STOP
END
Ⅸ 在ARM匯編編程中如何指定某段程序的存儲地址
在要指定代碼的存儲空間不是一件特別簡單的事情,尤其是你想為某個或某幾個函數指定具體的地址。
1,編譯器只有在最終的Link階段才會為代碼和數據分配內存地址,因此指定代碼段的地址一般是通過寫一個link腳本來進行的。Link階段時,編譯器的Linker會讀取你寫的Link腳本,並且按照腳本的規定給代碼分配地址。
2,根據ARM開發工具的不同,link腳本的語法和形式也有所不同。ARM MDK,ARM ADS,Eclips+GCC,Linux GCC, ARM Realview等開發工具都支持Link腳本。
如果你英文還可以,建議你直接找到開發工具的Help手冊去研究。如果你英語實在不行,也可以把開發工具名稱和你代碼的具體情況告訴我,我幫你看看。
Ⅹ C語言和匯編怎樣引用對方定義的變數
C中要使用匯編裡面函數的話 需要在匯編裡面使用export xxx 導出函數標號 C中加extern xxx匯編要使用C裡面的函數的話 需要在匯編里使用import xxx 導入外部標號