吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3729|回复: 6
收起左侧

[其他转载] 32位、64位汇编语言混合编程终极大法

[复制链接]
longs75 发表于 2021-10-14 09:08
本帖最后由 longs75 于 2021-10-14 15:32 编辑

论坛中有两个贴子专门讲述了32位、64位汇编语言混合编程的实现方法。
首先感谢:“SysWOW64的奇技淫巧”的作者
https://www.52pojie.cn/thread-1232264-1-1.html
给出了从32位模式进入64位模式、以及从64位模式返回32位模式的实现方法。

其次感谢:“32位内联64位代码”的作者
https://www.52pojie.cn/thread-1271576-1-1.html
尝试在32位汇编程序中规范化使用64位汇编指令的设想。

但是,如何让编程者摆脱在32位、64位两种模式中进行切换的细节,在程序的任何地方可以随意切换两种编程模式,类似下面这样:
……
(32位代码)
Begin64位指令
(64位代码)
End64位指令
(32位代码)
……
这样就使得32位、64位汇编语言混合编程这种高级技术白菜化了。

我这篇文章就是要解决这个问题。

在上面两个贴子的实现方法中,存在一个问题,就是在通过 jmp far切换指令模式时,都使用了绝对地址:
“SysWOW64的奇技淫巧”中使用了“jmp far 0x33:(绝对地址)”直接远跳转方式进入64位模式,使用
push offset sys32_start           ;绝对地址入栈
mov dword ptr[esp + 4],023h
jmp fword ptr[esp]
间接远跳转方式从64位模式返回32位模式。

“32位内联64位代码”贴子中,进入和退出64位模式都使用的是间接远跳转方式,同样都使用了绝对地址。

如果代码使用了绝对地址,代码就不可重用,因为每次使用时绝对地址不同,代码也会不一样。所以要对进入和退出64位模式这两个代码段进行改造,不使用绝对地址,解决代码重用问题。

改造如下:
一、进入64位模式代码:
00401000 | 60                         | pushad                                  | 保护寄存器
00401001 | 6A 33                      | push 33                                       | 立即数0x00000033入栈
00401003 | E8 00000000                | call a32to64_2.401008                         | call $0,32位EIP入栈
00401008 | 830424 07                  | add dword ptr ss:[esp],7                      | 计算64位代码入口地址
0040100C | FF2C24                     | jmp far fword ptr ss:[esp]                    |间接远跳转
0040100F | 90                         | nop                                           |这里开始是64位模式了

第二行至第五行是关键代码。
第三行称为“E8 00 00 00 00”大法,作用是把下一条指令的地址(EIP的值)入栈,并转入下一条指令继续运行。这个地址是在程序运行时动态获得的,是个相对地址,
第四行直接修改这个值为64位代码入口
第五行远跳转到64位代码入口,也就是第六行代码处。
上面这四行指令,使用了相对寻址,不依赖具体的内存地址,所以指令代码是固定的,放在任意内存地址都可以正确执行,不需要修改代码。

同样方式对退出64位代码进行改造:
二、退出64位模式代码:
000000013FAC117C | E8 00000000              | call empty64.13FAC1181                  | call $0,64位RIP入栈
000000013FAC1181 | 830424 0E                | add dword ptr ss:[rsp],E                |修改为返回32位模式的地址
000000013FAC1185 | 66:C74424 04 2300        | mov word ptr ss:[rsp+4],23              |只需要低16位置0x0023,高16位不用
000000013FAC118C | FF2C24                   | jmp far fword ptr ss:[rsp]              |间接远跳转
FAC118F | 83C4 10                  | add esp,10                              |,平衡堆栈,这里开始已经是32位模式了
FAC1190 | 61                         | popad                                   | 恢复寄存器

上面两个代码段需要说明一点:在32位模式下,“E8 00 00 00 00”将32位的EIP入栈,在64位模式下,“E8 00 00 00 00”将64位的RIP入栈,因此堆栈中的地址长度是不同的。
Jmp far 远跳转需要一个fword (三字)数据,即一个32位的地址和一个16位cs段寄存器的值。

下面来看一个具体应用的例子:
[Asm] 纯文本查看 复制代码
.386
.model flat, stdcall
option casemap :none

include user32.inc
include kernel32.inc

includelib user32.lib
includelib kernel32.lib

Begin64 TEXTEQU <DD 0E8336A60h, 00000000h, 07240483h, 90242CFFh>                    ;宏定义,进入64位模式的代码
End64 TEXTEQU <DD 000000E8h, 24048300h, 44C7660Eh, 00230424h, 83242CFFh, 906110C4h> ;宏定义,结束64位模式的代码
.data
        strTitle        db '问题',0
        strQustion db '我在干什么?',0
        db 100 dup(0)
        
.CODE
START:
        
        lea esi,strTitle
        lea edi,strQustion
        ;
        Begin64   ;进入64位指令模式
        ;
   DD 51415041h, 53415241h, 55415441h, 0B8415641h, 0B8B0F0B4h, 06894467h, 0D2CEB949h, 0A7D1DAD4h
   DD 0BA49B0CFh, 0BBCE3233h, 3436EBD3h, 0BBCEBB49h, 0EEC1B8D6h, 0BC49A3C4h, 0D0C7BDCAh, 0ACA3BBBBh
   DD 0D2CEBD49h, 0A6B9C9B3h, 0BE49CBC1h, 0A1A3A1A3h, 0A1A3A1A3h, 0F894C67h, 57894C67h, 894C6708h
   DD 4C67105Fh, 67186789h, 206F894Ch, 77894C67h, 415E4128h, 415C415Dh, 415A415Bh, 90584159h
        ;
        
        ;    地址                操作码                          汇编语言                           注释
        ;000000013FAC10F2 | 41:50                    | push r8                                 |
        ;000000013FAC10F4 | 41:51                    | push r9                                 |  所有用到的寄存器必须保护  
        ;000000013FAC10F6 | 41:52                    | push r10                                |
        ;000000013FAC10F8 | 41:53                    | push r11                                |  否则调用API函数就会异常
        ;000000013FAC10FA | 41:54                    | push r12                                |
        ;000000013FAC10FC | 41:55                    | push r13                                |  因为Windows不知道你会弄这一手
        ;000000013FAC10FE | 41:56                    | push r14                                |
        ;000000013FAC1100 | 41:B8 B4F0B0B8           | mov r8d,B8B0F0B4                        |  几乎每个新增的64位寄存器都用到了
        ;000000013FAC1106 | 6744:8906                | mov dword ptr ds:[esi],r8d              |
        ;000000013FAC110A | 49:B9 CED2D4DAD1A7CFB0   | mov r9,B0CFA7D1DAD4D2CE                 |  就是为了试验一下是不是好用
        ;000000013FAC1114 | 49:BA 3332CEBBD3EB3634   | mov r10,3436EBD3BBCE3233                |
        ;000000013FAC111E | 49:BB CEBBD6B8C1EEC4A3   | mov r11,A3C4EEC1B8D6BBCE                |  为什么寄存器要赋这些数?
        ;000000013FAC1128 | 49:BC CABDC7D0BBBBA3AC   | mov r12,ACA3BBBBD0C7BDCA                |
        ;000000013FAC1132 | 49:BD CED2B3C9B9A6C1CB   | mov r13,CBC1A6B9C9B3D2CE                |  运行一下你就知道了
        ;000000013FAC113C | 49:BE A3A1A3A1A3A1A3A1   | mov r14,A1A3A1A3A1A3A1A3                |
        ;000000013FAC1146 | 674C:890F                | mov qword ptr ds:[edi],r9               |  
        ;000000013FAC114A | 674C:8957 08             | mov qword ptr ds:[edi+8],r10            |
        ;000000013FAC114F | 674C:895F 10             | mov qword ptr ds:[edi+10],r11           |
        ;000000013FAC1154 | 674C:8967 18             | mov qword ptr ds:[edi+18],r12           |
        ;000000013FAC1159 | 674C:896F 20             | mov qword ptr ds:[edi+20],r13           |
        ;000000013FAC115E | 674C:8977 28             | mov qword ptr ds:[edi+28],r14           |
        ;000000013FAC1163 | 41:5E                    | pop r14                                 |
        ;000000013FAC1165 | 41:5D                    | pop r13                                 |
        ;000000013FAC1167 | 41:5C                    | pop r12                                 |
        ;000000013FAC1169 | 41:5B                    | pop r11                                 |
        ;000000013FAC116B | 41:5A                    | pop r10                                 |
        ;000000013FAC116D | 41:59                    | pop r9                                  |
        ;000000013FAC116F | 41:58                    | pop r8                                  |
        ;000000013FAC1171 | 90                       | nop                                     |
        
        End64   ;结束64位模式,返回32位模式
        ;
        invoke MessageBox,0,edi,esi,0
        invoke ExitProcess,0
        
end START


总结一下:
在程序开头定义两个宏,分别是Begin64和End64,然后在程序中任意地方都可以使用这两个宏进行64位编程,非常方便。
模板如下:
[Asm] 纯文本查看 复制代码
Begin64 TEXTEQU <DD 0E8336A60h, 00000000h, 07240483h, 90242CFFh>                    ;宏定义,进入64位模式的代码
End64 TEXTEQU <DD 000000E8h, 24048300h, 44C7660Eh, 00230424h, 83242CFFh, 906110C4h> ;宏定义,结束64位模式的代码

Begin64
;
;(此处为64位代码)
;
End64


==========================
补充说明:
有同学问:既然是从32位模式进入64位模式,我能不能在32位Windows中使用,比如在XP中使用?
答:不能。32位Windows系统,CPU的运行模式是保护模式,只支持32位指令集;64位Windows系统,CPU的运行模式是IA-32e模式,IA-32e模式有两个子模式:64位模式和兼容模式,我们这个切换方法,实际上只是在IA-32e模式的64位模式和兼容模式这两个子模式中进行切换,跟保护模式完全没有关系,所以不能在32位Windows系统中使用。

汇编指令学习建议:
以前学习16位汇编语言时,有个Emu8086软件非常好用,可以一边运行指令代码,一边观察寄存器、标志位的值,对理解汇编指令的含义很有帮助。但这个软件只支持16位汇编指令,有没有一个类似的软件,帮助我们学习32位指令和64位指令呢?
答案是:有!用跟踪调试软件x64dbg完全可以实现Emu8086的学习功能。

x64dbg有两个主模块,分别是:x64dbg和x32dbg,它判断EXE文件是64位时,自动调用x64dbg,在64位模式下调试程序,判断EXE文件是32位时,自动调用x32dbg,在兼容模式下调试程序。根据这个特点,我们编写一个空文件(以汇编语言格式为例):
.data
db 4096 dup(0)   ;定义4kb数据段
.code
START:
db 4095 dup(90h)  ;定义4kb代码段,用NOP指令填充,最后ret操作系统。
ret
end START

将这个空文件分别编译成64位模式和32位模式,用x64dbg加载,就可以练习64位和32位指令了。








免费评分

参与人数 4吾爱币 +9 热心值 +4 收起 理由
搜索曾经的回忆 + 1 + 1 哈哈,终于知道为什么崩溃了
tlf + 1 热心回复!
Venda + 1 + 1 热心回复!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| longs75 发表于 2021-10-15 09:13
帖子反复修改了好几处小地方,每次都审核,我都不好意思一直麻烦版主了,还有一点儿补充说明放这里:

进入和离开64位模式时,代码使用了PUSHAD和POPAD,这是为了确保程序稳定性,但也会造成一个小问题,就是64位代码不能用寄存器返回任何结果给32位代码,只能通过内存地址返回结果,因为从任何寄存器返回的结果都被POPAD掉了。实际应用中如果需要用寄存器返回结果,可以把进入64位模式代码中的PUSHAD和离开64位模式代码中的POPAD用NOP取代,这三个指令都是单字节,所以修改也非常容易。修改如下:

[Asm] 纯文本查看 复制代码
Begin64 TEXTEQU <DD 0E8336A90h, 00000000h, 07240483h, 90242CFFh>                    ;宏定义,进入64位模式的代码
End64 TEXTEQU <DD 000000E8h, 24048300h, 44C7660Eh, 00230424h, 83242CFFh, 909010C4h> ;宏定义,结束64位模式的代码

;进入64位模式前不再保护寄存器,64位代码可以用寄存器返回结果给32位代码
 
Begin64
;
;(此处为64位代码)
;
End64
 楼主| longs75 发表于 2021-10-16 21:13

没什么难度,就是理解jmp far指令的用法,还有 E8 00 00 00 00的技巧。
yuleniwo 发表于 2021-10-29 09:14
头像被屏蔽
tlf 发表于 2021-10-29 14:10
提示: 作者被禁止或删除 内容自动屏蔽
hiodis 发表于 2022-1-3 06:20
通俗易懂,点赞
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-18 11:35

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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