这玩意儿用处:
(1)反调试
(2)能让win10 32位程序运行64位代码(其他操作系统的wow64据说实现底层不太一样,不保证同代码能完全运行)
最近在试着转二进制漏洞方向,部署的时候出现了各种版本问题和反复崩溃,看到dynamoRIO下已经有成千的issue,想让开发者来debug肯定是不可能,于是只好自己上了。
在调试的过程中,发现dynamoRIO有一段ASM特别有意思
他是用来在32位程序中执行64位function的一段loader,摘抄见附录
关键的代码只有这么几行
RAW(ea)
DD offset sml_transfer_to_64
DB CS64_SELECTOR
RAW(00)
/* far jmp to next instr w/ 32-bit switch: jmp 0023:<sml_return_to_32> */
push offset sml_return_to_32 /* 8-byte push */
mov dword ptr [esp + 4], CS32_SELECTOR /* top 4 bytes of prev push */
jmp fword ptr [esp]
试了试摘出来写成汇编,效果非常好。
在OD里不仅分析不出64位的汇编,而且单步会直接跟飞。在windbg里只有单步才能分析出x64的代码
写的汇编如下
.386
.model flat, stdcall ;32 bit memory model
option casemap :none ;case sensitive
include Test.inc
.data
strTitle db 'Helloworld!',0
.code
start:
invoke MessageBox,0,addr strTitle,addr strTitle,0
xor eax,eax
mov edi,0FFFFFFFFH
db 0eah
dd offset sys64_start
db 033h
db 0
sys64_start:
db 049h, 08bh, 0d7h ;mov rdx,r15
push 0
push offset sys32_start
mov dword ptr[esp + 4],023h
jmp fword ptr[esp]
sys32_start:
mov eax,0
mov ebx,0
pop eax
pop eax
pop eax
pop eax
invoke MessageBox,0,addr strTitle,addr strTitle,0
retn
end start
至于为什么这么改,外网有篇文章(https://www.malwaretech.com/2014/02/the-0x33-segment-selector-heavens-gate.html)写得特别好,就不画蛇添足了,大概把大意翻译过来
首先,长跳转的机制是修改段寄存器CS来进行寻址和改变权限。段寄存器的结构分为selector、TL和RPL,selector代表段寄存器在GDT中对应的index,TL代表应该查局部表还是全局表(LDT/GDT),RPL是权限位。0x23和0x33的段寄存器如下。
我们知道段寄存器TL=0时还是要去查GDT得到完整的段描述,在GDT中,0x23和0x33的信息如下
可以看到0x23和0x33唯一的区别就是Flags和limits位。
在0x23的32位系统中,Limits位需要设为0xFFFFF来达到最大寻址空间4GB,而在x64 win10中,已经完全放弃了使用段寄存器进行寻址的方式,全部采用分页机制。因此GDT的limit和base全部设为0进行“平坦模式”。
Flags位的结构如下:
Gr(granularity)代表限长单位,在32位系统中一般都设为1代表Limits单位是4K(因此4K*0xFFFFF=4G),如果是0则Limits单位是byte,这个值在64位系统中无意义
L代表64位模式。这一位很多比较远古的书上是保留位
D/B则是32位下的操作数位数,这个和代码段、堆栈段有关系,但是在64位下也没有意义。
综上,通过切换CS段选择器可以在syswow64中左右横跳。
附录:
/* Routines to switch to 64-bit mode from 32-bit WOW64, make a 64-bit
* call, and then return to 32-bit mode.
*/
/*
* int switch_modes_and_load(void *ntdll64_LdrLoadDll,
* UNICODE_STRING_64 *lib,
* HANDLE *result)
* XXX i#1633: this routine does not yet support ntdll64 > 4GB
*/
# define FUNCNAME switch_modes_and_load
DECLARE_FUNC(FUNCNAME)
GLOBAL_LABEL(FUNCNAME:)
/* get args before we change esp */
mov eax, ARG1
mov ecx, ARG2
mov edx, ARG3
/* save callee-saved registers */
push ebx
/* far jmp to next instr w/ 64-bit switch: jmp 0033:<sml_transfer_to_64> */
RAW(ea)
DD offset sml_transfer_to_64
DB CS64_SELECTOR
RAW(00)
sml_transfer_to_64:
/* Below here is executed in 64-bit mode, but with guarantees that
* no address is above 4GB, as this is a WOW64 process.
*/
/* Call LdrLoadDll to load 64-bit lib:
* LdrLoadDll(IN PWSTR DllPath OPTIONAL,
* IN PULONG DllCharacteristics OPTIONAL,
* IN PUNICODE_STRING DllName,
* OUT PVOID *DllHandle));
*/
RAW(4c) RAW(8b) RAW(ca) /* mov r9, rdx : 4th arg: result */
RAW(4c) RAW(8b) RAW(c1) /* mov r8, rcx : 3rd arg: lib */
push 0 /* slot for &DllCharacteristics */
lea edx, dword ptr [esp] /* 2nd arg: &DllCharacteristics */
xor ecx, ecx /* 1st arg: DllPath = NULL */
/* save WOW64 state */
RAW(41) push esp /* push r12 */
RAW(41) push ebp /* push r13 */
RAW(41) push esi /* push r14 */
RAW(41) push edi /* push r15 */
/* align the stack pointer */
mov ebx, esp /* save esp in callee-preserved reg */
sub esp, 32 /* call conv */
and esp, HEX(fffffff0) /* align to 16-byte boundary */
call eax
mov esp, ebx /* restore esp */
/* restore WOW64 state */
RAW(41) pop edi /* pop r15 */
RAW(41) pop esi /* pop r14 */
RAW(41) pop ebp /* pop r13 */
RAW(41) pop esp /* pop r12 */
/* far jmp to next instr w/ 32-bit switch: jmp 0023:<sml_return_to_32> */
push offset sml_return_to_32 /* 8-byte push */
mov dword ptr [esp + 4], CS32_SELECTOR /* top 4 bytes of prev push */
jmp fword ptr [esp]
sml_return_to_32:
add esp, 16 /* clean up far jmp target and &DllCharacteristics */
pop ebx /* restore callee-saved reg */
ret /* return value already in eax */
END_FUNC(FUNCNAME)