技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 106|回复: 3
收起左侧

[转载]Win32Asm子程序的参数传递

[复制链接]

11

主题

100

帖子

738

积分

用户组: 大·技术宅

UID
3808
精华
1
威望
16 点
宅币
557 个
贡献
44 次
宅之契约
0 份
在线时间
90 小时
注册时间
2018-5-6
发表于 2018-11-2 17:46:17 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
昨天晚上大约23:55分的时候小弟在进行网上冲浪,就去aogo的论坛看了看(不知道站长和tangptr前辈有没有听说过aogo这个人),发现一篇帖子,是前辈hslwq在07年发的。
阅读完了以后感觉很好,小弟就想自己先消化一下,然后转载一下,巩固自己的知识以外也希望更多人可以从hslwq前辈的帖子中受益。

帖子原址:http://www.aogosoft.com/bbs/mixp ... xt&fileid=74936

我对原帖进行了一些改动,首先就是帖子的源代码的格式的改动,我将原帖中的代码在RadAsm中又敲了一遍,格式用的是罗云彬在win32汇编语言书中的格式
其次就是对帖子的源代码进行了注释,并且删除了一些冗余的代码,比如函数声明等。再者在进行分析程序的时候自己在IDA中重新反汇编分析一遍,然后
对反汇编的代码进行了注释,想要大家看的更加清楚,同时以便自己来检查原帖中的代码的正确性,毕竟实践出真知。

下面进行正文:
本文主要讲解win32汇编中的子程序的传址调用,这点和C语言类似。win32汇编中子程序通过传址调用可以对实参进行修改。程序主要是通过很经典的交换两个数的子程序来对
实参中的两个数值进行交换。

第一个源码source.asm以及反汇编的source-ida.asm是本帖和原帖的精髓所在:
source.asm
[Asm] 纯文本查看 复制代码
		.386
		.model	flat, stdcall
		option	casemap:none
		
include		windows.inc
include		kernel32.inc
includelib	kernel32.lib
include		user32.inc
includelib	user32.lib

;自定义一个dword类型(指针变量),与C语言中的typedef类似
LPVAR		typedef	ptr	dword


;定义常量字符串
		.const
szCaption	db	'Message',0
szFormat	db	'a = %d, b = %d',0	;定义输出格式

;定义数据段
		.data
szBuffer	db	128 dup(?)		;用于存储输出结果

;定义两个dword变量,用于实验
		.data?
dwX		dd	?
dwY		dd	?


;代码段
		.code
		
;自定义的交换两个数值的函数,进行传址调用
_Swap		proc	a:LPVAR,b:LPVAR
	
		pushad	;将所有的寄存器的数值压栈
		mov	eax,a
		mov	ebx,b
		
		;交换数值
		mov	ecx,dword ptr [eax]
		mov	edx,dword ptr [ebx]
		mov	dword ptr [eax],edx
		mov	dword ptr [ebx],ecx
		
		popad	;将所有的寄存器的数值弹栈
	ret

_Swap endp		

start:
		mov	dwX,20
		mov	dwY,30
		
		invoke	wsprintf,offset szBuffer,offset szFormat,dwX,dwY
		invoke	MessageBox,NULL,offset szBuffer,offset szCaption,MB_OK
		invoke	_Swap, offset dwX,offset dwY	;调用自定义的交换函数
		invoke	wsprintf,offset szBuffer,offset szFormat,dwX,dwY
		invoke	MessageBox,NULL,offset szBuffer,offset szCaption,MB_OK
		invoke	ExitProcess,NULL
end start



运行结果为:
firstMsgbox.jpg secondMsgbox.jpg


同时IDA的反汇编分析了子程序和形参的入栈出栈和调用方式:
source-ida.asm
[Asm] 纯文本查看 复制代码
sub_401000 proc near

arg_0= dword ptr  8				;参数一入栈时候,栈顶指针的偏移量
arg_4= dword ptr  0Ch				;参数二入栈时候,栈顶指针的偏移量

push	ebp					;先将ebp入栈,保存ebp的原始值
mov		ebp, esp			;将esp的值赋值给ebp,以后用ebp来进行栈顶指针的移动
pusha

mov		eax, [ebp+arg_0]		;将第一个参数的地址赋值给eax
mov		ebx, [ebp+arg_4]		;将第二个参数的地址赋值给ebx
mov		ecx, [eax]			;将第一个参数地址所对应的数值赋值给ecx
mov		edx, [ebx]			;将第二个参数地址所对应的数值赋值给ecx
mov		[eax],edx			;下面两句用于交换数值
mov		[ebx],ecx

popa
leave
retn	8					;stdcall调用方式平衡堆栈用的,这里我们不予理会
sub_401000	endp



结论:
1、在进入子程序之前,参数入栈,入栈顺序:从右到左,紧接着CALL语句把返回地址入栈再跳转进入子程序执行,而子程序马上把EBP入栈用于保护子程序返回时的堆栈指针。调用完后,由子程序负责清除参数占用的堆栈空间(格式:RET n,本例中函数返回时指令retn 8表示返回后把esp指针加上8,此时ESP=0012FFC4,刚好等于调用之有ESP的值)

2、进入子程序后,子程序先把EBP入栈,并把当前ESP保存在EBP中用于返回时恢复堆栈。由此可见:在子程序中,EBP起到了保存原始ESP的作用,并随时用做存取局部变量与取参数的指针基址。

3、局部变量是子程序临时分配的堆栈空间,可以用ebp为基址进行访问。



下面按照原帖的步骤进行三个小实验:
实践1:改动代码部分,用ebp来存取参数。        实际上这个实验很好理解,能看懂上图中的IDA的反汇编就可以理解这个了,具体ebp指针,参数和局部变量的关系以及堆栈的生长方向在罗云彬win32汇编语言程序设计第三版的第78页有详细描述,在此不再赘述,代码的改动部分如下:
[Asm] 纯文本查看 复制代码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;		实践1,改动代码部分,用ebp来存取参数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Swap		proc	a:LPVAR,b:LPVAR
	
		pushad	
		mov	eax,[ebp+08h]
		mov	ebx,[ebp+0ch]
		
		mov	ecx,dword ptr [eax]
		mov	edx,dword ptr [ebx]
		mov	dword ptr [eax],edx
		mov	dword ptr [ebx],ecx
		
		popad
	ret

_Swap endp


运行结果同source.asm的结果

实践2:通过传递指针,直接返回变量地址
这个实验的程序较上一个程序在数据结构上稍微有些复杂,就是运用了结构体,但是实际上和C语言的结构体指针作参数来传递给子函数一样,这里我也加了很多注释,大家应该可以看的很明白。
[Asm] 纯文本查看 复制代码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	实践二:通过传递指针,直接返回变量地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.386
		.model	flat, stdcall
		option	casemap:none
		
include		windows.inc
include		user32.inc
includelib	user32.lib
include		kernel32.inc
includelib	kernel32.lib

;声明一个结构体TESTSTRUCT
TESTSTRUCT	struct
cPoint		POINT	<>	;定义一个POINT结构体
r		dword	?
TESTSTRUCT ends


;定义指向TESTSTRUCT结构体的结构体指针
LPTESTSTRUCT	typedef	ptr	TESTSTRUCT


		.const
szCaption	db	'MessageBox',0
szFormatText1	db	'变量地址:%08x',0
szFormatText2	db	'变量值:%5d,%5d,%5d',0

		.data
szbuffer1	db	128 dup(?)
szbuffer2	db	128 dup(?)
stTest		TESTSTRUCT	<>	;定义了一个TESTSTRUCT结构体变量stTest


		.code
;子程序参数为指向记录的指针
;功能为通过传递的指针初始化记录的值,并在主程序中重新输出
_testProc	proc	lpstTest:LPTESTSTRUCT
	
		LOCAL	@buf[256]:byte
		pushad
		
		invoke	wsprintf,addr @buf,offset szFormatText1,lpstTest	;输出形参的值,即为实参的地址
		invoke	MessageBox,NULL,addr @buf,offset szCaption,MB_OK
		
		mov	eax,lpstTest		;将结构体的地址赋给eax
		mov	dword ptr [eax],1	;给结构体中的各个成员变量赋初值
		mov	dword ptr [eax+4],2
		mov	dword ptr [eax+8],3
		
		popad
		
		
	ret

_testProc endp

start:		
		;以下为输出验证程序
		invoke	wsprintf,addr szbuffer1,addr szFormatText1,addr stTest
		invoke	wsprintf,addr szbuffer2,addr szFormatText2,stTest.cPoint.x,stTest.cPoint.y,stTest.r
		invoke	MessageBox,NULL,addr szbuffer2,offset szCaption,MB_OK
		invoke 	MessageBox,NULL,offset  szbuffer1,offset szCaption,MB_OK
		invoke	_testProc,addr stTest		;传址调用子程序
		invoke	wsprintf,addr szbuffer2,addr szFormatText2,stTest.cPoint.x,stTest.cPoint.y,stTest.r
		invoke	MessageBox,NULL,addr szbuffer2,offset szCaption,MB_OK
		invoke	ExitProcess,NULL

end start


运行结果:
1.jpg 2.jpg 3.jpg 4.jpg


实践3:获得运行期标号地址修正值
这个实践说实话很明显是dos下16位汇编经常用的招式,感觉在win32汇编中不是很实用,原帖在此处也是作为一个课外话题
[AppleScript] 纯文本查看 复制代码
原帖关于此处内容如下:
call FUNC_START
FUNC_START:
    pop ebx
    sub ebx, offset FUNC_START
    mov [ebp-xx], ebx
分析:如上所述,call 函数会将eip寄存器压入堆栈,而eip表示的是"下一条语句的地址,所以例程中的
pop ebx将把eip的内容放入ebp中即标号FUNC_START对应的地址。在这里, 当程序运行到"call FUNC_START"时, 它表示的是以标号"FUNC_START:"开始的"pop ebx"指令起始地址. 而另一方面, sub指令中的"offset FUNC_START", 在编译时, offset会被转成一个绝对地址. 这样,通过sub操作, 就获得了此段代码在编译期和运行期关于指令地址的修正值. 下面的这句: "mov [ebp-xx], ebx", 实际上只是锦上添花, 它把这个值保存在了某一个自定义的函数局部变量空间内, 以备后续语句方便引用.
接下来,可以用如下方式 对标号数据进行引用:
    mov eax, [ebp-xx]
    mov ebx, dword ptr [DATA_LABLE+eax]
    对于汇编函数中的此类代码进行这样的处理后, 此段二进制执行块就可以被放置在任意地方而不致因为对DATA_LABLE数据地址的错误引用造成程序错误.



最后再次感谢hslwq前辈这么精彩的文章供小弟研读,同时希望各位大佬多多指针文章中的错误,共勉。




win32asm子程序参数传递.zip

2.96 KB, 下载次数: 0

菜鸟一枚,直接指正,不必留情

26

主题

68

帖子

1941

积分

用户组: 管理员

UID
1043
精华
10
威望
43 点
宅币
1654 个
贡献
83 次
宅之契约
0 份
在线时间
288 小时
注册时间
2015-8-15
发表于 2018-11-3 09:02:32 | 显示全部楼层
不适用于在Win64下写64位汇编程序(MASM64的invoke很骚,不能像MASM32那样玩。初学汇编的话建议少玩汇编器提供的宏能力,对学习调用约定没好处)
关于Win64可以参考https://www.0xaa55.com/forum.php?mod=viewthread&tid=16875
flowers for Broken spirits - a woman turned into stake will hold the world in the basin of fire.

11

主题

100

帖子

738

积分

用户组: 大·技术宅

UID
3808
精华
1
威望
16 点
宅币
557 个
贡献
44 次
宅之契约
0 份
在线时间
90 小时
注册时间
2018-5-6
 楼主| 发表于 2018-11-3 11:25:48 | 显示全部楼层
tangptr@126.com 发表于 2018-11-3 09:02
不适用于在Win64下写64位汇编程序(MASM64的invoke很骚,不能像MASM32那样玩。初学汇编的话建议少玩汇编器 ...

哦哦,谢谢tangptr大佬指导,Win64的那篇帖子好屌
菜鸟一枚,直接指正,不必留情

25

主题

86

帖子

1157

积分

用户组: 版主

UID
1821
精华
6
威望
57 点
宅币
891 个
贡献
36 次
宅之契约
0 份
在线时间
204 小时
注册时间
2016-7-12
发表于 2018-11-3 14:30:35 | 显示全部楼层
64位没啥适合独立开发的汇编编译器了,写起来太累人了 对于混合编译来说了解约定基本够用了

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-11-20 13:42 , Processed in 0.111496 second(s), 15 queries , Gzip On, Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表