首页
论坛
专栏
课程

[原创]堆栈分配检测函数chkstk执行过程分析

LuckyG 2012-3-10 15:05 8011
在分配较大空间的临时变量的时候系统会插入chkstk函数来检测是否超过堆栈上已经分配的空间
IDA pro反汇编识图



代码版

.text:00401F40 ___chkstk       proc near               ; CODE XREF: _main+2Ap
.text:00401F40                 push    ecx                   ; 保存ecx的值
.text:00401F41                 mov     ecx, esp            ; 把chkstk函数栈顶地址给ecx,为后面的分配做准备
.text:00401F43                 add     ecx, 8                 ; 找到未调用函数前的栈顶 chkstk函数调用前压入一个返回地址+push的ecx=8
.text:00401F46
.text:00401F46 probe:                                            ; CODE XREF: ___chkstk+1Bj
.text:00401F46                 cmp     eax, 1000h      ; 比较 eax 跟 4K(系统默认内存页大小)
.text:00401F4B                 jb      short done          ; 如果需要的不是一个页就跳到 done 处
.text:00401F4D                 sub     ecx, 1000h        ; ecx 地址减去4096 也就是准备分配4K内存页
.text:00401F53                 or      dword ptr [ecx], 0 ; 系统进行实际分配内存
.text:00401F53                                                       ; 这个时候之前只不过是分配虚存,内存没有 commit ,
.text:00401F53                                                       ; 这个时候对这个内存地址进行读写操作都会引发一个 page fault 异常
.text:00401F53                                                       ; (_XCPT_GUARD_PAGE_VIOLATION), OS捕获这个异常,检查一定的条件,
.text:00401F53                                                       ; 适合的时候就把这个内存页 commit 了,即分配了实际的物理内存
.text:00401F56                 sub     eax, 1000h          ; 将eax的值减去已经分配的4K内存
.text:00401F5B                 jmp     short probe       ; 调回继续判断是否还需要一个页内存
.text:00401F5D ; ---------------------------------------------------------------------------
.text:00401F5D
.text:00401F5D done:                                              ; CODE XREF: ___chkstk+Bj
.text:00401F5D                 sub     ecx, eax             ; 求得还需要的空间(堆栈负增长 高地址代表的空间较小)
.text:00401F5F                 or      dword ptr [ecx], 0 ; 系统进行实际分配内存
.text:00401F62                 mov     eax, esp             ; 把当年函数的exp赋给eax,为恢复ecx作准备 exp处放的是原始的ecx
.text:00401F64                 mov     esp, ecx             ; 把分配好的顶部地址赋给esp
.text:00401F66                 mov     ecx, [eax]           ; 恢复ecx的值(eax=chkstk函数的exp,exp地址处存放的是前面push的ecx)
.text:00401F68                 mov     eax, [eax+4]       ; 找到返回地址 chkstk+4地址就是函数调用前压入的返回地址
.text:00401F6B                 jmp     eax                     ; 返回到执行chkstk函数之前的地址继续执行

图形识图


堆栈里面内容布局如下,

最近没用OD堆栈什么的都忘记了,一般c语言写的函数调用函数之前都会

.text:00401290                 push    ebp
.text:00401291                 mov     ebp, esp
.text:00401293                 sub     esp     154h

这种,不过这些都是编译器帮忙生成的函数调用处理过程,有些时候如果你不是用的vc自然就不一定会遵循这些了

我今天的这个程序就是里面的系统函数chkstk就是用asm写的没有任何参数压栈动作也没有处理 ebp 什么的

就是拿eax来传递参数的,函数调用前会把函数返回地址压栈(汇编编译器应该做了这个工作了吧?否则我的分析就有问题了
编译器的安装目录下面有这个ASM文件
;_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

        push    ecx

; Calculate new TOS.

        lea     ecx, [esp] + 8 - 4      ; TOS before entering function + size for ret value
        sub     ecx, eax                ; new TOS

; Handle allocation size that results in wraparound.
; Wraparound will result in StackOverflow exception.

        sbb     eax, eax                ; 0 if CF==0, ~0 if CF==1
        not     eax                     ; ~0 if TOS did not wrapped around, 0 otherwise
        and     ecx, eax                ; set to 0 if wraparound

        mov     eax, esp                ; current TOS
        and     eax, not ( _PAGESIZE_ - 1) ; Round down to current page boundary

cs10:
        cmp     ecx, eax                ; Is new TOS
        jb      short cs20              ; in probed page?
        mov     eax, ecx                ; yes.
        pop     ecx
        xchg    esp, eax                ; update esp
        mov     eax, dword ptr [eax]    ; get return address
        mov     dword ptr [esp], eax    ; and put it at new TOS
        ret

; Find next lower page and probe
cs20:
        sub     eax, _PAGESIZE_         ; decrease by PAGESIZE
        test    dword ptr [eax],eax     ; probe page.
        jmp     short cs10

_chkstk endp

        end 

[防守篇]2018看雪.TSRC CTF 挑战赛(团队赛)11月1日征题开启!

上传的附件:
最新回复 (8)
LuckyG 2012-3-10 15:07
2

0

网上的资料
大概流程
1) “chkstk Routine is a helper routine for the C compiler. For x86
compilers, _chkstk Routine is called when the local variables exceed
4096 bytes; for x64 compilers it is 4K and 8K respectively.”
即, 当一个函数局部变量超过一个页面大小4k的时候, 编译器会自动插入这个函数。插入这个函数的位置在:
push ebp
mov ebp,esp
mov eax,1000D4h
call _chkstk()
2) 这个函数做什么?当函数为堆栈分配的页面不够时, (堆栈默认大小为1M), 堆栈需要
更多的页面时调用这个函数。
当堆栈使用大于分配的大小(默认1M)时,产生_XCPT_UNABLE_TO_GROW_STACK.
3) 当调用这个函数时, 首先外面对eax进行赋值(已经分配的堆栈大小 + 即将分配给函数局部变量的堆栈大小)
4) 调用 _chkstk() 首先,保持当期esp到eax中,然后开始判断:如果分配的大小大于一个页面, 到第5)步(大多都先第5)步);否则到第6)步。
5) 当需要分配的大小大于一个页面, 则增加一个页面。“sub eax 1000h” 表示堆栈栈顶下移1000h,[由于堆栈是高地址(栈底部)->低地址(栈顶部)分布],所以堆栈扩大了1000h; “sub ecx 1000h”表示分配了1000h(1个页面)之后还需要多少空间;
“test dword ptr [ecx],eax” 表示分配空间,这个时候之前只不过是分配虚存,内存没有 commit ,这个时候对这个内存地址进行读写操作都会引发一个 page fault 异常(_XCPT_GUARD_PAGE_VIOLATION), OS捕获这个异常,检查一定的条件,适合的时候就把这个内存页 commit 了,即分配了实际的物理内存。然后再次比较需要多少内存,如果还是超过1页(1000h), 则重复第5)步,否则到第6)步。
6) 还需要分配的堆栈空间小于1页的时候,“sub ecx,eax” 堆栈继续扩大(扩大了剩余大小的空间);然后“mov esp,ecx”,保存到原来的esp;并且通过“test”为堆栈分配空间。
7) 最后,esp的值不再是原来的值,堆栈的大小变成: 堆栈原来大小 + 局部变量需要的堆栈大小 + xx (push 用的一点堆栈的零头)
如果你的函数中需要一个很大的局部变量,那就要注意一点了。因为局部变量是在存放在堆栈中的,在分配的时候需要会调用上面的函数,并检测地址是否有效。在这个时刻,如果你是在驱动中,那么很可能会出现蓝屏。在应用层可能会崩溃。因此,如果需要使用比较大的数据时,最好是通过动态分配来得到内存。
LuckyG 2012-3-11 08:53
3

0

不知道分析的对不对,有人来讨论下
ronging 2012-3-14 22:38
4

0

这个函数调用后,栈里多了2个值,一个是_chkstk的返回地址,一个是push ecx,为什么不在退出时把它们的值弹出来?
ronging 2012-3-14 22:46
5

0

细想了一下,其实没有必要,因为_chkstk的父函数使用ebp访问局部变量,并不知道这个返回地址的存在,也不会特别的处理它,直接覆盖使用这块空间。
guxinyi 5 2012-3-18 15:07
6

0

___chkstk这个函数还是很有用的。。
vcvcc 2012-3-19 13:05
7

0

VC的crt源代码中有:
<vc_installation_path>\VC\crt\src\intel\chkstk.asm

下面是VC2008的chkstk.asm中的函数说明:

;***
;_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.
;
;*******************************************************************************
evilor 2012-3-19 14:28
8

0

我擦 我调用个函数后他都给我call一下chkstk
LuckyG 2012-3-21 17:45
9

0

;_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

        push    ecx

; Calculate new TOS.

        lea     ecx, [esp] + 8 - 4      ; TOS before entering function + size for ret value
        sub     ecx, eax                ; new TOS

; Handle allocation size that results in wraparound.
; Wraparound will result in StackOverflow exception.

        sbb     eax, eax                ; 0 if CF==0, ~0 if CF==1
        not     eax                     ; ~0 if TOS did not wrapped around, 0 otherwise
        and     ecx, eax                ; set to 0 if wraparound

        mov     eax, esp                ; current TOS
        and     eax, not ( _PAGESIZE_ - 1) ; Round down to current page boundary

cs10:
        cmp     ecx, eax                ; Is new TOS
        jb      short cs20              ; in probed page?
        mov     eax, ecx                ; yes.
        pop     ecx
        xchg    esp, eax                ; update esp
        mov     eax, dword ptr [eax]    ; get return address
        mov     dword ptr [esp], eax    ; and put it at new TOS
        ret

; Find next lower page and probe
cs20:
        sub     eax, _PAGESIZE_         ; decrease by PAGESIZE
        test    dword ptr [eax],eax     ; probe page.
        jmp     short cs10

_chkstk endp

        end
返回