32位、64位汇编语言混合编程终极大法
本帖最后由 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,023h
jmp fword ptr
间接远跳转方式从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:,7 | 计算64位代码入口地址
0040100C | FF2C24 | jmp far fword ptr ss: |间接远跳转
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:,E |修改为返回32位模式的地址
000000013FAC1185 | 66:C74424 04 2300 | mov word ptr ss:,23 |只需要低16位置0x0023,高16位不用
000000013FAC118C | FF2C24 | jmp far fword ptr ss: |间接远跳转
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段寄存器的值。
下面来看一个具体应用的例子:
.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:,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:,r9 |
;000000013FAC114A | 674C:8957 08 | mov qword ptr ds:,r10 |
;000000013FAC114F | 674C:895F 10 | mov qword ptr ds:,r11 |
;000000013FAC1154 | 674C:8967 18 | mov qword ptr ds:,r12 |
;000000013FAC1159 | 674C:896F 20 | mov qword ptr ds:,r13 |
;000000013FAC115E | 674C:8977 28 | mov qword ptr ds:,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位编程,非常方便。
模板如下:
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位指令了。
帖子反复修改了好几处小地方,每次都审核,我都不好意思一直麻烦版主了,还有一点儿补充说明放这里:
进入和离开64位模式时,代码使用了PUSHAD和POPAD,这是为了确保程序稳定性,但也会造成一个小问题,就是64位代码不能用寄存器返回任何结果给32位代码,只能通过内存地址返回结果,因为从任何寄存器返回的结果都被POPAD掉了。实际应用中如果需要用寄存器返回结果,可以把进入64位模式代码中的PUSHAD和离开64位模式代码中的POPAD用NOP取代,这三个指令都是单字节,所以修改也非常容易。修改如下:
Begin64 TEXTEQU <DD 0E8336A90h, 00000000h, 07240483h, 90242CFFh> ;宏定义,进入64位模式的代码
End64 TEXTEQU <DD 000000E8h, 24048300h, 44C7660Eh, 00230424h, 83242CFFh, 909010C4h> ;宏定义,结束64位模式的代码
;进入64位模式前不再保护寄存器,64位代码可以用寄存器返回结果给32位代码
Begin64
;
;(此处为64位代码)
;
End64 何故 发表于 2021-10-14 11:02
看不懂咋办
没什么难度,就是理解jmp far指令的用法,还有 E8 00 00 00 00的技巧。:Dweeqw 居然还有这种操作,学习了。 通俗易懂,点赞
页:
[1]