我来对这幅图描述下,首先说明,该技术只适用32-bitWindows。该技术理解起来不难,主要利用了函数栈的特性(这也是只适用32位Windows的原因),该技术的核心就是在要执行代码之前,修改代码段的权限,执行完毕后,再次修改代码段的权限。这样就伪造了一种代码所在段为不可执行的假象。
该结构的作用,是一些准备工作,一些函数的地址,跳板栈的地址,APC ROP的地址等。
该结构为APC ROP,只用其中一个即可,为硬编码的数据,注释中写出了对应的汇编代码,主要的作用为执行跳板栈里面的内容。(为什么要用ROP:避免DEP违例)。
到这里,要用到的几个数据结构就介绍完了,在介绍详细的流程之前,先给出函数调用栈的图示。
接下来为详细的实现过程,首先为我们目标代码分配空间,然后是找到APC ROP这段硬编码再dll中的位置,至于怎么找到的,参考原文。
这段指令并不常见,作者从mshtml.dll中恰好搜索到了这段指令,接下来,为跳板栈分配空间,并填充跳板栈的内容,然后为马上要用到的结构体开辟空间,并填充。最后,去执行我们的代码。
目标代码由汇编语言编写:
最开始定义的是Configuration结构体,也是之前所说的,最后开辟空间的。
紧接着,进入到核心代码的位置:
根据之前的描述,可知,esp+4内的内容,是函数的第一个参数,对应的是刚刚定义的结构体实例的位置,并把该值存入ebx寄存器。同时,将跳板底部设为栈底。
这段代码,判断是否已经执行过一遍,如果执行过一遍,则可以直接去执行目标代码。(由于将设置段和代码段混到一起,所以需要设置)
要隐藏的目标代码,这里弹了个弹窗,没什么好说的。效果如图(没什么用。。。
修改代码段的访问权限,并将返回地址设置为WaitForSingleObjectEx,实现对该函数的调用。
配合上面,实现“函数调用”,这里需要停下来观察栈内数据,如图:
终于到这里了,我们先看下函数栈:
和正常的函数栈一样,要注意的是,0x04b70038是跳板栈的地址。指令的第一条,是弹栈,至于给哪个寄存器,无所谓,重点是把返回地址弹掉。第二条指令,依旧是弹栈,不过,弹出到的是栈顶寄存器,也就说,函数栈的位置变了,变成了我们的跳板栈,跳板栈的内容再贴出来:
这里,就绕过了DEP保护,不用去执行栈里的代码来修改目标代码段的内存属性。可以从下图中看到,通过精心构造堆栈,使接下来执行的使目标代码了。
通过精心构造函数栈,达到控制程序流的目的,实现了对代码段访问权限的动态调整。以上的代码只是PoC,将目标代码和设置APC的代码放到一起了,如果需要,可以将两段代码分开。这种方法的弊端就是由于是依靠函数栈对控制流的劫持,所以只能支持x86模式。
盗用作者原图(逃
Visual Studio 2015 Community
NASM(防止没有该环境,附件中已上传编译好的bin文件)
Visual Studio 2015 Community
NASM(防止没有该环境,附件中已上传编译好的bin文件)
0x2 介绍
该方法实现的功能是:将一个程序的所有可执行代码隐藏到不可执行内存。还是把作者对该技术的流程图搬上来:
继续盗用(2333
0x3 详细分析
这里先思考一个问题:在代码段通过VirtualProtect函数修改成不可执行后,如何修改回来?这里使用了APC,而调用APC中的函数,需要使用一些函数进行唤醒,由于代码段不可执行,所以这里使用ROP来改变VirtualProtect的返回地址为WaitForSingleObject等函数的地址实现对线程的唤醒。APC中的函数继续使用ROP将返回地址劫持到跳板占,跳板栈实现了对目标代码段权限的修改,并控制EIP为要执行的代码段首地址,即完成了整个过程。
struct SetupConfiguration {
uint32_t initialized;
void* setup_address;
uint32_t setup_length;
void* VirtualProtectEx;
void* WaitForSingleObjectEx;
void* CreateWaitableTimer;
void* SetWaitableTimer;
void* MessageBox;
void* tramp_addr;
void* sleep_handle;
uint32_t interval;
void* target;
uint8_t shadow[8];
};
该结构的作用,是一些准备工作,一些函数的地址,跳板栈的地址,APC ROP的地址等。
struct StackTrampoline {
void* VirtualProtectEx;
void* return_address;
void* current_process;
void* address;
uint32_t size;
uint32_t protections;
void* old_protections_ptr;
uint32_t old_protections;
void* setup_config;
};
这个就是跳板栈了,从结构顺序可以看出来,修改完权限后,就跳到目标代码了。
vector<vector<uint8_t>> rop_gadget_candidates = {
{ 0x59, 0x5C, 0xC3 }, // pop ecx; pop esp; ret
{ 0x58, 0x5C, 0xC3 } // pop eax; pop esp; ret
};
该结构为APC ROP,只用其中一个即可,为硬编码的数据,注释中写出了对应的汇编代码,主要的作用为执行跳板栈里面的内容。(为什么要用ROP:避免DEP违例)。
到这里,要用到的几个数据结构就介绍完了,在介绍详细的流程之前,先给出函数调用栈的图示。
struct SetupConfiguration {
uint32_t initialized;
void* setup_address;
uint32_t setup_length;
void* VirtualProtectEx;
void* WaitForSingleObjectEx;
void* CreateWaitableTimer;
void* SetWaitableTimer;
void* MessageBox;
void* tramp_addr;
void* sleep_handle;
uint32_t interval;
void* target;
uint8_t shadow[8];
};
该结构的作用,是一些准备工作,一些函数的地址,跳板栈的地址,APC ROP的地址等。
struct StackTrampoline {
void* VirtualProtectEx;
void* return_address;
void* current_process;
void* address;
uint32_t size;
uint32_t protections;
void* old_protections_ptr;
uint32_t old_protections;
void* setup_config;
};
这个就是跳板栈了,从结构顺序可以看出来,修改完权限后,就跳到目标代码了。
vector<vector<uint8_t>> rop_gadget_candidates = {
{ 0x59, 0x5C, 0xC3 }, // pop ecx; pop esp; ret
{ 0x58, 0x5C, 0xC3 } // pop eax; pop esp; ret
};
该结构为APC ROP,只用其中一个即可,为硬编码的数据,注释中写出了对应的汇编代码,主要的作用为执行跳板栈里面的内容。(为什么要用ROP:避免DEP违例)。
到这里,要用到的几个数据结构就介绍完了,在介绍详细的流程之前,先给出函数调用栈的图示。
struct StackTrampoline {
void* VirtualProtectEx;
void* return_address;
void* current_process;
void* address;
uint32_t size;
uint32_t protections;
void* old_protections_ptr;
uint32_t old_protections;
void* setup_config;
};
这个就是跳板栈了,从结构顺序可以看出来,修改完权限后,就跳到目标代码了。
vector<vector<uint8_t>> rop_gadget_candidates = {
{ 0x59, 0x5C, 0xC3 }, // pop ecx; pop esp; ret
{ 0x58, 0x5C, 0xC3 } // pop eax; pop esp; ret
};
该结构为APC ROP,只用其中一个即可,为硬编码的数据,注释中写出了对应的汇编代码,主要的作用为执行跳板栈里面的内容。(为什么要用ROP:避免DEP违例)。
到这里,要用到的几个数据结构就介绍完了,在介绍详细的流程之前,先给出函数调用栈的图示。
vector<vector<uint8_t>> rop_gadget_candidates = {
{ 0x59, 0x5C, 0xC3 }, // pop ecx; pop esp; ret
{ 0x58, 0x5C, 0xC3 } // pop eax; pop esp; ret
};
该结构为APC ROP,只用其中一个即可,为硬编码的数据,注释中写出了对应的汇编代码,主要的作用为执行跳板栈里面的内容。(为什么要用ROP:避免DEP违例)。
到这里,要用到的几个数据结构就介绍完了,在介绍详细的流程之前,先给出函数调用栈的图示。
函数调用关系
这段指令并不常见,作者从mshtml.dll中恰好搜索到了这段指令,接下来,为跳板栈分配空间,并填充跳板栈的内容,然后为马上要用到的结构体开辟空间,并填充。最后,去执行我们的代码。
reinterpret_cast<callable>(setup_memory)(&config);
目标代码由汇编语言编写:
STRUC Configuration
.initialized: RESD 1 ;0x0
.setup_addr: RESD 1 ;0x4
.setup_length: RESD 1 ;0x8
.VirtualProtectEx: RESD 1 ;0xC
.WaitForSingleObjectEx: RESD 1 ;0x10
.CreateWaitableTimer: RESD 1 ;0x14
.SetWaitableTimer: RESD 1 ;0x18
.MessageBox: RESD 1 ;0x1C
.trampoline_addr: RESD 1 ;0x20
.sleep_handle: RESD 1 ;0x24
.interval: RESD 1 ;0x28
.gadget: RESD 1 ;0x2C
.shadow: RESD 2 ;0x30 config end
.stack: RESB 0x10000 ;0x10030 stack space
.trampoline: RESD 9 ;0x10034 trampoline
ENDSTRUC
最开始定义的是Configuration结构体,也是之前所说的,最后开辟空间的。
紧接着,进入到核心代码的位置:
mov ebx, [esp+4] ; Configuration in ebx now esp->return address
lea esp, [ebx + Configuration.trampoline - 4] ; Bottom of "stack"
mov ebp, esp
reinterpret_cast<callable>(setup_memory)(&config);
目标代码由汇编语言编写:
STRUC Configuration
.initialized: RESD 1 ;0x0
.setup_addr: RESD 1 ;0x4
.setup_length: RESD 1 ;0x8
.VirtualProtectEx: RESD 1 ;0xC
.WaitForSingleObjectEx: RESD 1 ;0x10
.CreateWaitableTimer: RESD 1 ;0x14
.SetWaitableTimer: RESD 1 ;0x18
.MessageBox: RESD 1 ;0x1C
.trampoline_addr: RESD 1 ;0x20
.sleep_handle: RESD 1 ;0x24
.interval: RESD 1 ;0x28
.gadget: RESD 1 ;0x2C
.shadow: RESD 2 ;0x30 config end
.stack: RESB 0x10000 ;0x10030 stack space
.trampoline: RESD 9 ;0x10034 trampoline
ENDSTRUC
最开始定义的是Configuration结构体,也是之前所说的,最后开辟空间的。
紧接着,进入到核心代码的位置:
mov ebx, [esp+4] ; Configuration in ebx now esp->return address
lea esp, [ebx + Configuration.trampoline - 4] ; Bottom of "stack"
mov ebp, esp
STRUC Configuration
.initialized: RESD 1 ;0x0
.setup_addr: RESD 1 ;0x4
.setup_length: RESD 1 ;0x8
.VirtualProtectEx: RESD 1 ;0xC
.WaitForSingleObjectEx: RESD 1 ;0x10
.CreateWaitableTimer: RESD 1 ;0x14
.SetWaitableTimer: RESD 1 ;0x18
.MessageBox: RESD 1 ;0x1C
.trampoline_addr: RESD 1 ;0x20
.sleep_handle: RESD 1 ;0x24
.interval: RESD 1 ;0x28
.gadget: RESD 1 ;0x2C
.shadow: RESD 2 ;0x30 config end
.stack: RESB 0x10000 ;0x10030 stack space
.trampoline: RESD 9 ;0x10034 trampoline
ENDSTRUC
最开始定义的是Configuration结构体,也是之前所说的,最后开辟空间的。
紧接着,进入到核心代码的位置:
mov ebx, [esp+4] ; Configuration in ebx now esp->return address
lea esp, [ebx + Configuration.trampoline - 4] ; Bottom of "stack"
mov ebp, esp
mov ebx, [esp+4] ; Configuration in ebx now esp->return address
lea esp, [ebx + Configuration.trampoline - 4] ; Bottom of "stack"
mov ebp, esp
当前栈顶
“config”具体内容
trampoline_addr具体内容
根据之前的描述,可知,esp+4内的内容,是函数的第一个参数,对应的是刚刚定义的结构体实例的位置,并把该值存入ebx寄存器。同时,将跳板底部设为栈底。
mov edx, [ebx + Configuration.initialized]
cmp edx, 0
jne reset_trampoline
这段代码,判断是否已经执行过一遍,如果执行过一遍,则可以直接去执行目标代码。(由于将设置段和代码段混到一起,所以需要设置)
; Create the timer
push 0
push 0
push 0
mov ecx, [ebx + Configuration.CreateWaitableTimer]
call ecx
mov [ebx + Configuration.sleep_handle], eax
; Set the timer
push 0
mov ecx, [ebx + Configuration.trampoline_addr]
push ecx
mov ecx, [ebx + Configuration.gadget]
push ecx
mov ecx, [ebx + Configuration.interval]
push ecx
lea ecx, [ebx + Configuration.shadow]
push ecx
mov ecx, [ebx + Configuration.sleep_handle]
push ecx
mov ecx, [ebx + Configuration.SetWaitableTimer]
call ecx
根据之前的描述,可知,esp+4内的内容,是函数的第一个参数,对应的是刚刚定义的结构体实例的位置,并把该值存入ebx寄存器。同时,将跳板底部设为栈底。
mov edx, [ebx + Configuration.initialized]
cmp edx, 0
jne reset_trampoline
这段代码,判断是否已经执行过一遍,如果执行过一遍,则可以直接去执行目标代码。(由于将设置段和代码段混到一起,所以需要设置)
; Create the timer
push 0
push 0
push 0
mov ecx, [ebx + Configuration.CreateWaitableTimer]
call ecx
mov [ebx + Configuration.sleep_handle], eax
; Set the timer
push 0
mov ecx, [ebx + Configuration.trampoline_addr]
push ecx
mov ecx, [ebx + Configuration.gadget]
push ecx
mov ecx, [ebx + Configuration.interval]
push ecx
lea ecx, [ebx + Configuration.shadow]
push ecx
mov ecx, [ebx + Configuration.sleep_handle]
push ecx
mov ecx, [ebx + Configuration.SetWaitableTimer]
call ecx
mov edx, [ebx + Configuration.initialized]
cmp edx, 0
jne reset_trampoline
这段代码,判断是否已经执行过一遍,如果执行过一遍,则可以直接去执行目标代码。(由于将设置段和代码段混到一起,所以需要设置)
; Create the timer
push 0
push 0
push 0
mov ecx, [ebx + Configuration.CreateWaitableTimer]
call ecx
mov [ebx + Configuration.sleep_handle], eax
; Set the timer
push 0
mov ecx, [ebx + Configuration.trampoline_addr]
push ecx
mov ecx, [ebx + Configuration.gadget]
push ecx
mov ecx, [ebx + Configuration.interval]
push ecx
lea ecx, [ebx + Configuration.shadow]
push ecx
mov ecx, [ebx + Configuration.sleep_handle]
push ecx
mov ecx, [ebx + Configuration.SetWaitableTimer]
call ecx
; Create the timer
push 0
push 0
push 0
mov ecx, [ebx + Configuration.CreateWaitableTimer]
call ecx
mov [ebx + Configuration.sleep_handle], eax
; Set the timer
push 0
mov ecx, [ebx + Configuration.trampoline_addr]
push ecx
mov ecx, [ebx + Configuration.gadget]
push ecx
mov ecx, [ebx + Configuration.interval]
push ecx
lea ecx, [ebx + Configuration.shadow]
push ecx
mov ecx, [ebx + Configuration.sleep_handle]
push ecx
mov ecx, [ebx + Configuration.SetWaitableTimer]
call ecx
具体值参考上面截图,这里就是向APC队列插入函数,函数的地址,即gadget,为硬编码内容的地址。
; Set the initialized bit
mov [ebx + Configuration.initialized], dword 1 <br>
具体值参考上面截图,这里就是向APC队列插入函数,函数的地址,即gadget,为硬编码内容的地址。
; Set the initialized bit
mov [ebx + Configuration.initialized], dword 1 <br>
; Set the initialized bit
mov [ebx + Configuration.initialized], dword 1 <br>
和上面说的一样,第一次执行会用到,设置标志位。
reset_trampoline:
mov ecx, [ebx + Configuration.VirtualProtectEx]
mov [ebx + Configuration.trampoline], ecx
这里的设置应该是防止第二次运行的时候,栈遭到破坏,所以重新设置函数地址。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Arbitrary code goes here. Note that the
;;;; default stack is pretty small (65k).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Pop a MessageBox as example
push 0 ; null
push 0x656c796f ; oyle
push 0x67726167 ; garg
mov ecx, esp
push 0x40 ; Info box
push ecx ; ptr to 'gargoyle' on stack
push ecx ; ptr to 'gargoyle' on stack
push 0
mov ecx, [ebx + Configuration.MessageBox]
call ecx
mov esp, ebp
要隐藏的目标代码,这里弹了个弹窗,没什么好说的。效果如图(没什么用。。。
效果图
; Setup arguments for WaitForSingleObjectEx x1
push 1
push 0xFFFFFFFF
mov ecx, [ebx + Configuration.sleep_handle]
push ecx
push 0 ; Return address never ret'd
; Setup arguments for WaitForSingleObjectEx x2
push 1
push 0xFFFFFFFF
mov ecx, [ebx + Configuration.sleep_handle]
push ecx
; Tail call to WaitForSingleObjectEx
mov ecx, [ebx + Configuration.WaitForSingleObjectEx]
push ecx
这里连续对WaitForSingleObjectEx函数设置两遍参数,如果只设置一遍,可能会出现地址为0,无权限执行的情况。(具体情况可动手尝试,我实验了几次,由于WaitForSingleObjectEx函数执行完会返回到下一条指令的地址,会出现上述情况,具体可动手跟踪即可)。
; Setup arguments for VirtualProtectEx
lea ecx, [ebx + Configuration.shadow]
push ecx
push 2 ; PAGE_READONLY
mov ecx, [ebx + Configuration.setup_length]
push ecx
mov ecx, [ebx + Configuration.setup_addr]
push ecx
push dword 0xffffffff
; Tail call to WaitForSingleObjectEx
mov ecx, [ebx + Configuration.WaitForSingleObjectEx]
push ecx
修改代码段的访问权限,并将返回地址设置为WaitForSingleObjectEx,实现对该函数的调用。
; Jump to VirtualProtectEx
mov ecx, [ebx + Configuration.VirtualProtectEx]
jmp ecx
配合上面,实现“函数调用”,这里需要停下来观察栈内数据,如图:
当前栈
两个WaitForSingleObjectEx参数中间的0x00000000这个地址永远都不会访问到,因为会在调用完APC ROP后,返回地址被返回到要跳板栈(下面会说到)。由当前堆栈,可以看出,修改完权限后,不会继续执行代码,而是会执行WaitForSingleObjectEx。(ROP)为了更明显,还是截了一张执行完将要返回的图。
返回地址
由上图可知,接下来调用WaitForSingleObjectEx,使之执行apc队列中的函数。
和上面说的一样,第一次执行会用到,设置标志位。
reset_trampoline:
mov ecx, [ebx + Configuration.VirtualProtectEx]
mov [ebx + Configuration.trampoline], ecx
这里的设置应该是防止第二次运行的时候,栈遭到破坏,所以重新设置函数地址。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-3-23 09:26
被sudozhange编辑
,原因: 添加bin文件