我感觉这玩意儿应该有人写过,但中文互联网怎么搜都搜不到,顺便看了看英文资料。发一个自己的实现好了
这玩意儿用处:
(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)
通过一个长跳转,进入CS64_SELECTOR段选择子后,进入64位模式
/* 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)