Ⅰ 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 导入外部标号