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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 2844|回复: 3

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

[复制链接]
发表于 2018-11-2 17:46:17 | 显示全部楼层 |阅读模式

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

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

×
昨天晚上大约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
  1.                 .386
  2.                 .model        flat, stdcall
  3.                 option        casemap:none
  4.                
  5. include                windows.inc
  6. include                kernel32.inc
  7. includelib        kernel32.lib
  8. include                user32.inc
  9. includelib        user32.lib

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


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

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

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


  23. ;代码段
  24.                 .code
  25.                
  26. ;自定义的交换两个数值的函数,进行传址调用
  27. _Swap                proc        a:LPVAR,b:LPVAR
  28.        
  29.                 pushad        ;将所有的寄存器的数值压栈
  30.                 mov        eax,a
  31.                 mov        ebx,b
  32.                
  33.                 ;交换数值
  34.                 mov        ecx,dword ptr [eax]
  35.                 mov        edx,dword ptr [ebx]
  36.                 mov        dword ptr [eax],edx
  37.                 mov        dword ptr [ebx],ecx
  38.                
  39.                 popad        ;将所有的寄存器的数值弹栈
  40.         ret

  41. _Swap endp               

  42. start:
  43.                 mov        dwX,20
  44.                 mov        dwY,30
  45.                
  46.                 invoke        wsprintf,offset szBuffer,offset szFormat,dwX,dwY
  47.                 invoke        MessageBox,NULL,offset szBuffer,offset szCaption,MB_OK
  48.                 invoke        _Swap, offset dwX,offset dwY        ;调用自定义的交换函数
  49.                 invoke        wsprintf,offset szBuffer,offset szFormat,dwX,dwY
  50.                 invoke        MessageBox,NULL,offset szBuffer,offset szCaption,MB_OK
  51.                 invoke        ExitProcess,NULL
  52. end start

复制代码


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


同时IDA的反汇编分析了子程序和形参的入栈出栈和调用方式:
source-ida.asm
  1. sub_401000 proc near

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

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

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

  13. popa
  14. leave
  15. retn        8                                        ;stdcall调用方式平衡堆栈用的,这里我们不予理会
  16. 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页有详细描述,在此不再赘述,代码的改动部分如下:
  1. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  2. ;                实践1,改动代码部分,用ebp来存取参数
  3. ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  4. _Swap                proc        a:LPVAR,b:LPVAR
  5.        
  6.                 pushad       
  7.                 mov        eax,[ebp+08h]
  8.                 mov        ebx,[ebp+0ch]
  9.                
  10.                 mov        ecx,dword ptr [eax]
  11.                 mov        edx,dword ptr [ebx]
  12.                 mov        dword ptr [eax],edx
  13.                 mov        dword ptr [ebx],ecx
  14.                
  15.                 popad
  16.         ret

  17. _Swap endp
复制代码


运行结果同source.asm的结果

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

  13. ;声明一个结构体TESTSTRUCT
  14. TESTSTRUCT        struct
  15. cPoint                POINT        <>        ;定义一个POINT结构体
  16. r                dword        ?
  17. TESTSTRUCT ends


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


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

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


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

  48. _testProc endp

  49. start:               
  50.                 ;以下为输出验证程序
  51.                 invoke        wsprintf,addr szbuffer1,addr szFormatText1,addr stTest
  52.                 invoke        wsprintf,addr szbuffer2,addr szFormatText2,stTest.cPoint.x,stTest.cPoint.y,stTest.r
  53.                 invoke        MessageBox,NULL,addr szbuffer2,offset szCaption,MB_OK
  54.                 invoke         MessageBox,NULL,offset  szbuffer1,offset szCaption,MB_OK
  55.                 invoke        _testProc,addr stTest                ;传址调用子程序
  56.                 invoke        wsprintf,addr szbuffer2,addr szFormatText2,stTest.cPoint.x,stTest.cPoint.y,stTest.r
  57.                 invoke        MessageBox,NULL,addr szbuffer2,offset szCaption,MB_OK
  58.                 invoke        ExitProcess,NULL

  59. end start
复制代码


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


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



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




win32asm子程序参数传递.zip

2.96 KB, 下载次数: 0

回复

使用道具 举报

发表于 2018-11-3 14:30:35 | 显示全部楼层
64位没啥适合独立开发的汇编编译器了,写起来太累人了 对于混合编译来说了解约定基本够用了
回复 赞! 1 靠! 0

使用道具 举报

发表于 2018-11-3 09:02:32 | 显示全部楼层
不适用于在Win64下写64位汇编程序(MASM64的invoke很骚,不能像MASM32那样玩。初学汇编的话建议少玩汇编器提供的宏能力,对学习调用约定没好处)
关于Win64可以参考https://www.0xaa55.com/forum.php?mod=viewthread&tid=16875
回复 赞! 1 靠! 0

使用道具 举报

 楼主| 发表于 2018-11-3 11:25:48 | 显示全部楼层
tangptr@126.com 发表于 2018-11-3 09:02
不适用于在Win64下写64位汇编程序(MASM64的invoke很骚,不能像MASM32那样玩。初学汇编的话建议少玩汇编器 ...

哦哦,谢谢tangptr大佬指导,Win64的那篇帖子好屌
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-22 15:56 , Processed in 0.034391 second(s), 25 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

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