首页
社区
课程
招聘
解析__alloca_probe
发表于: 2012-1-22 00:44 17016

解析__alloca_probe

2012-1-22 00:44
17016

逆向vc编译的程序,经常会看到这样的代码出现在函数头部:

mov     eax, xxxxh
call    __alloca_probe

xxxxh是个立即数,一般大于1000h,即十进制数4096。

这段代码经常出现在函数头prolog之后,如果有异常结构,会出现在SEH或EH之后。

示例1:
                 push    ebp
                 mov     ebp, esp
                 mov     eax, 8080h
                 call    __alloca_probe
                 
示例2:
                 push    ebp
                 mov     ebp, esp
                 and     esp, 0FFFFFFF8h
                 push    0FFFFFFFFh
                 push    offset SEH_4A7AA0
                 mov     eax, large fs:0
                 push    eax
                 mov     large fs:0, esp
                 push    ecx
                 mov     eax, 8080h
                 call    __alloca_probe
                 
事实上__alloca_probe是一个在stack上分配大块内存空间函数,功能同SUB ESP, xxxxh一致。函数本身由编译器提供,编译的时候vc会根据实际情况(stack上申请大块空间,一般大于一个内存页大小)插入到函数体,为函数在stack上提供私有变量空间,分配的空间大小由fastcall调用方式的EAX寄存器传入。
函数源码位于vs\Vcxx\crt\src\intel目录下的chkstk.asm文件。代码很短,就列在这里:

        page    ,132
        title   chkstk - C stack checking routine
;***
;chkstk.asm - C stack checking routine
;
;       Copyright (c) Microsoft Corporation. All rights reserved.
;
;Purpose:
;       Provides support for automatic stack checking in C procedures
;       when stack checking is enabled.
;
;*******************************************************************************

.xlist
        include cruntime.inc
.list

; size of a page of memory

_PAGESIZE_      equ     1000h

        CODESEG

page
;***
;_chkstk - check stack upon procedure entry
;
;Purpose:
;       Provide stack checking on procedure entry. Method is to simply probe
;       each page of memory required for the stack in descending order. This
;       causes the necessary pages of memory to be allocated via the guard
;       page scheme, if possible. In the event of failure, the OS raises the
;       _XCPT_UNABLE_TO_GROW_STACK exception.
;
;       NOTE:  Currently, the (EAX < _PAGESIZE_) code path falls through
;       to the "lastpage" label of the (EAX >= _PAGESIZE_) code path.  This
;       is small; a minor speed optimization would be to special case
;       this up top.  This would avoid the painful save/restore of
;       ecx and would shorten the code path by 4-6 instructions.
;
;Entry:
;       EAX = size of local frame
;
;Exit:
;       ESP = new stackframe, if successful
;
;Uses:
;       EAX
;
;Exceptions:
;       _XCPT_GUARD_PAGE_VIOLATION - May be raised on a page probe. NEVER TRAP
;                                    THIS!!!! It is used by the OS to grow the
;                                    stack on demand.
;       _XCPT_UNABLE_TO_GROW_STACK - The stack cannot be grown. More precisely,
;                                    the attempt by the OS memory manager to
;                                    allocate another guard page in response
;                                    to a _XCPT_GUARD_PAGE_VIOLATION has
;                                    failed.
;
;*******************************************************************************

public _alloca_probe

_chkstk proc

_alloca_probe    =  _chkstk

        cmp     eax, _PAGESIZE_         ; more than one page?
        jae     short probesetup        ;   yes, go setup probe loop
                                        ;   no
        neg     eax                     ; compute new stack pointer in eax
        add     eax,esp
        add     eax,4
        test    dword ptr [eax],eax     ; probe it
        xchg    eax,esp
        mov     eax,dword ptr [eax]
        push    eax
        ret

probesetup:
        push    ecx                     ; save ecx
        lea     ecx,[esp] + 8           ; compute new stack pointer in ecx
                                        ; correct for return address and
                                        ; saved ecx

probepages:
        sub     ecx,_PAGESIZE_          ; yes, move down a page
        sub     eax,_PAGESIZE_          ; adjust request and...

        test    dword ptr [ecx],eax     ; ...probe it

        cmp     eax,_PAGESIZE_          ; more than one page requested?
        jae     short probepages        ; no

lastpage:
        sub     ecx,eax                 ; move stack down by eax
        mov     eax,esp                 ; save current tos and do a...

        test    dword ptr [ecx],eax     ; ...probe in case a page was crossed

        mov     esp,ecx                 ; set the new stack pointer

        mov     ecx,dword ptr [eax]     ; recover ecx
        mov     eax,dword ptr [eax + 4] ; recover return address

        push    eax                     ; prepare return address
                                        ; ...probe in case a page was crossed
        ret
_chkstk endp

        end
        
看得出,_alloca_probe事实上是_chkstk的别名。C约定会在编译之后的函数名前加一个下划线_,因此函数名就变成__alloca_probe。看看函数名,alloca是allocate缩写,而probe是探查、探测的意思,连在一起,应该明白函数名的含义了吧。chkstk当然就是checkstack了。而别名_alloca_probe更能体现函数功能。
源代码每一行都有注释,代码含义很好理解。再看IDA反汇编结果:

.text:004AC130 ; =============== S U B R O U T I N E =======================================
.text:004AC130
.text:004AC130 ; Attributes: library function
.text:004AC130
.text:004AC130 __alloca_probe  proc near               ; CODE XREF: sub_416D50+1Dp
.text:004AC130                                         ; sub_4307A0+1Dp ...
.text:004AC130
.text:004AC130 arg_0           = byte ptr  4
.text:004AC130
.text:004AC130                 cmp     eax, 1000h
.text:004AC135                 jnb     short probesetup
.text:004AC137                 neg     eax
.text:004AC139                 add     eax, esp
.text:004AC13B                 add     eax, 4
.text:004AC13E                 test    [eax], eax
.text:004AC140                 xchg    eax, esp
.text:004AC141                 mov     eax, [eax]
.text:004AC143                 push    eax
.text:004AC144                 retn
.text:004AC145 ; ---------------------------------------------------------------------------
.text:004AC145
.text:004AC145 probesetup:                             ; CODE XREF: __alloca_probe+5j
.text:004AC145                 push    ecx
.text:004AC146                 lea     ecx, [esp+4+arg_0]
.text:004AC14A
.text:004AC14A probepages:                             ; CODE XREF: __alloca_probe+2Cj
.text:004AC14A                 sub     ecx, 1000h
.text:004AC150                 sub     eax, 1000h
.text:004AC155                 test    [ecx], eax
.text:004AC157                 cmp     eax, 1000h
.text:004AC15C                 jnb     short probepages
.text:004AC15E
.text:004AC15E lastpage:
.text:004AC15E                 sub     ecx, eax
.text:004AC160                 mov     eax, esp
.text:004AC162                 test    [ecx], eax
.text:004AC164                 mov     esp, ecx
.text:004AC166                 mov     ecx, [eax]
.text:004AC168                 mov     eax, [eax+4]
.text:004AC16B                 push    eax
.text:004AC16C                 retn
.text:004AC16C __alloca_probe  endp

有源代码,有注释,这个就不用多说了。只是IDA给出的结果有点儿出入,__alloca_probe只有一个参数,就是EAX,而 arg_0 = byte ptr  4是不存在的, lea     ecx, [esp+4+arg_0]应该是lea     ecx, [esp+8]。

总结一下,__alloca_probe函数就是为函数在stack上申请大块空间做私有变量或缓存,由于内存分页机制,申请超过一页的内存空间,有可能在函数运行是没有提交到物理内存,调用这个函数,函数会从stack的高地址向低地址一页一页地探测,也就是函数中的几个测试语句,

test    [eax], eax

test    [ecx], eax

当函数执行到这些代码时,如果此时eax或ecx所指向的虚拟内存地址单元没有提交到物理内存,就会产生一个页异常,系统捕获到页异常,将物理内存提交到虚拟内存。函数执行完毕,所有申请的内存空间都已提交到物理内存。如果申请的空间致使线程栈空间分配溢出,系统产生stack overflow exception 。函数返回时 esp = esp - eax.


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 6
支持
分享
最新回复 (6)
雪    币: 411
活跃值: (252)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
写了这么多,结果最后总结错了~~~~~
2012-1-24 19:53
0
雪    币: 57
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
大侠能在讲讲
test    [ecx], eax这个检测么?还是不太明白...谢谢...
2012-1-24 20:24
0
雪    币: 970
活跃值: (35)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
eax只是个数值,ecx在循环中由esp递减一页,也就是使申请的stack空间增长一页。cpu执行test [ecx],eax时,使用新的ecx地址访问内存,但不保存任何结果,如果该页内存在页表项中已提交物理内存,不发生任何变化;如果此时页表项没有提交,会产生page fault页异常,系统捕获该异常,异常程序查询虚拟内存文件,查看空闲RAM页面,将ecx所在页提交空闲RAM页面,修改页表项属性,以保证该页线性地址空间是有效可访问的,测试完成。
2012-1-28 13:56
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
这个函数调用后,栈中间多了一个值,就是_chkstk的返回地址。这个就是这个函数的开销吧。

这有个帖子,详细描述了栈的增长过程。
异常处理的失效 (上)(栈的增长)
2012-3-14 18:19
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
细想了一下,其实没有开销,因为_chkstk的父函数使用ebp访问局部变量,并不知道这个返回地址的存在,也不会特别的处理它,直接覆盖使用这块空间。
2012-3-14 22:45
0
雪    币: 125
活跃值: (161)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
朋友,我能给个建议么? 你如果觉得错了,你最好指出错误的地方 然后给个正确的答案,好让LZ更正一下
2012-3-17 10:20
0
游客
登录 | 注册 方可回帖
返回
//