用户
搜索
  • TA的每日心情
    奋斗
    2020-11-4 09:15
  • 签到天数: 2 天

    连续签到: 1 天

    [LV.1]初来乍到

    i春秋-呆萌菜鸟

    Rank: 1

    1

    主题

    4

    帖子

    52

    魔法币
    收听
    0
    粉丝
    0
    注册时间
    2019-8-20
    发表于 2020-11-20 19:15:50 55020

    本文原创作者 SkYe ,本文属i春秋原创奖励计划,未经许可禁止转载。

    前言

    带师弟入门,整理一下入门栈溢出的题目,其他内容再看看。因为是入门题目,所以基本上都是关闭 PIE 保护。栈溢出入门题目可能不局限如下总结的,诸如利用栈溢出修改局部变量触发某些条件 getshell 等没有总结到位。

    ip 根据题目位数不同分别代指 eip 或 rip ,bp 及 sp 同理。以下小结均是基于程序为动态链接。

    完整后门&溢出长度大于等于 ip

    非常基础入门题目,用于了解学习栈溢出控制寄存器 ip ,关键代码如下:

    int vul()
    {
      char s; // [esp+Ch] [ebp-6Ch]
      puts("input:");
      gets(&s); //栈溢出漏洞,溢出长度不限
      return puts("OK,Bye!");
    }

    通过分析找到后门函数,且这个后门是一个完整可用的后门,就是只要我们调用这个后门函数,不需要其他额外的操作就能 getshell ,通常是 system('/bin/sh')write(1,'flag',32) 等形式。

    int getshell()
    {
      return system("/bin/sh");
    }

    思路

    有完整可用后门的情况下,只要溢出长度能够覆盖 ip 即可。直接利用栈溢出将 getshell 函数地址写入到 eip 位置,结束当前函数后就会去运行 getshell 函数,payload 结构:

    payload = [填充] + [get_shell_addr]

    残缺后门&溢出长度远大于 ip

    栈溢出漏洞函数如下(和上一个小结一样):

    int vul()
    {
      char s; // [esp+Ch] [ebp-6Ch]
      puts("input:");
      gets(&s); //栈溢出漏洞,溢出长度不限
      return puts("OK,Bye!");
    }

    这里指的远大于 ip ,意思是可以溢出覆盖除了 ip 以外的 3 个以上的(一般情况,实际情况实际分析)机器字长(32 位 4 bit ;64 位 8 bit)。

    残缺后门的形式多种多样:提供诸如 system 等关键函数;flag、/bin/sh 等字符串;某些 gadget 等。这里就整理前两种情况,刚好对应上面的完整后门。

    提供关键函数&字符串

    一般给出的是 system 函数,在 ida 中一般呈现形式:

    int getshell()
    {
      return system("echo no no no!!!");
    }

    这样就能通过 system.plt 调用 system 函数。

    字符串 /bin/sh 可以在 ida 中字符串界面可以查询到(shift+f12),存放在 bss 区,在源码中通过定义变量来提供。通过地址来调用这个字符串参数。

    思路

    对比完整后门发现这里是将函数和参数分割,但是两者都在程序中,所以可以通过栈溢出自行构造完整后门,这里就涉及传参方式方面知识:32 位栈传参;64 位前 6 个参数寄存器传参,后续更多参数遵循栈传参。

    自行构造完整后门(调用链)对溢出长度有要求了,至少要能溢出除 ip 以外再多 2 个机器字长,用于写入调用步骤。

    • 32 位程序

      payload=[填充]+[system@plt]+[4bit填充]+[参数]
    • 64 位程序

      64 位系统寄存器传参需要用到 gadget ,可以使用 ROPgadget 查找程序乃至 libc 库(需要泄露基地址)。一般来说前两个参数 rdi rsi 的 gadget 比较常见容易得到的。

      payload=[填充]+[pop_rdi_ret]+[参数]+[system@plt]

    提供关键函数

    关键函数不单单指的 system ,puts 、read 、write 等我们用于写入读取的辅助函数一定程度上也算是关键函数,但是为了方便总结这里的关键函数指的是 system 这类函数。

    system 函数在 ida 中呈现方式同上小结。与上面小结相比缺少的是字符串 /bin/sh ,对比完整后门来说就只剩下 system 。

    这里结合上面小结对比缺少一个 /bin/sh ,那就先构造一个输入的利用链将字符串输入到内存,然后再和上面一样构造 system 利用链,这里就涉及 ROP 利用思想。system 利用链不再重复,与上面小结一致。输入利用链构根据题目出现输入函数不同而实际分析,一般用的都是 read , 也有 scanf 、gets 。

    根据溢出长度不同还能细分为两种情况:一次性利用链;ROP 利用链。

    一次性利用链

    这种情况适用于溢出长度比较大,一般能有 7 个机器字长左右,具体情况具体分析。这种利用的思想是用输入函数 read 将 /bin/sh 写入到 bss 段后调用 system('/bin/sh') 。

    • 32 位程序
    payload=[填充]+[read@plt]+[system@plt]+[参数1]+[binsh写入地址]+[参数3]+[binsh写入地址]

    64 位程序很少用,因为寄存器传参需要用到 gadget 这样会导致需要非常大的溢出空间,构造起来和 32 位思想一样不再重复。

    ROP 利用链

    和一次性利用链思想上最大区别就是运行完自构写入函数后,返回 main(或其他)函数,相当于让程序重新运行一次,有第二次利用漏洞机会,写入 getshell 利用链。

    相比之下优势:payload 长度变短,有效应对溢出长度不足情况。

    • 32 位程序

      payload1=[填充]+[read@plt]+[main]+[参数1]+[binsh写入地址]+[参数3]
      payload2=[填充]+[system@plt]+[4bit填充]+[binsh写入地址]
    • 64 位程序

      pop_rdi 都会有单独的 gadget ,rsi 多数以 pop_rsi_r15 形式出现,rdx 少有 gadget ,这里假设 gadget 情况如下,实际做题实际调整:

      • pop_rdi_ret
      • pop_rsi_r15_ret
      • pop_rdx_ret
      payload1=[填充]+[pop_rdi]+[参数1]+[pop_rdi_r15]+[binsh写入地址]+[8bit填充]+[pop_rdx]+[参数3]+[read@plt]
      payload2=[填充]+[pop_rdi]+[binsh写入地址]+[system@plt]

      可以看到 payload1 还是长的,且 rdx gadget 不一定有。这是可以尝试不传第三个参数,用寄存器 rdx 的原值,这个值有可能符合需要(若需要控制前三个参数了解一下 ret2csu ),payload 简化如下:

      payload1=[填充]+[pop_rdi]+[参数1]+[pop_rdi_r15]+[binsh写入地址]+[8bit填充]+[read@plt]
      payload2=[填充]+[pop_rdi]+[binsh写入地址]+[system@plt]

    提供字符串

    就是给 /bin/sh ,其实这样等于没给,需要用到的函数都需要去 libc 中找,等同于没有后门,故这里不小结,思路和下面无后门一样。

    无后门&溢出长度远大于 ip

    从这种题目开始就接近实际题目了。首先是无后门,也就是既没有 system 也没有 /bin/sh 等条件,只有一个栈溢出的漏洞。这个小结中溢出长度定义是远大于 ip ,就继续沿用上面的漏洞函数 gets ,实际中更多的是用 read 函数输入。

    int vul()
    {
      char s; // [esp+Ch] [ebp-6Ch]
      puts("input:");
      gets(&s); //栈溢出漏洞,溢出长度不限
      return puts("OK,Bye!");
    }

    思路

    既然程序中没有 system 就到 libc 中找,也就是需要泄露 libc 基地址。方法是泄露一个位于 libc 中的函数的真实地址,将真实地址减去函数偏移得到基地址。/bin/sh 也能在 libc 中找到不再赘述。

    泄露地址需要一个(程序中出现过的)输入函数,常见的有 puts 、write 等。泄露完成后需要第二次利用漏洞的机会,所以也就是涉及 ROP 利用思想。

    • 32 位程序

      • puts
      payload1=[填充]+[puts@plt]+[main]+[某函数@got]
      payload2=[填充]+[system@libc_addr]+[4bit填充]+[binsh@libc_addr]
      • write
      payload1=[填充]+[puts@plt]+[main]+[某函数@got]
      payload2=[填充]+[system@libc_addr]+[4bit填充]+[binsh@libc_addr]
    • 64 位程序

      • puts
      payload1=[填充]+[pop_rdi]+[某函数@got]+[puts@plt]+[main]
      payload2=[填充]+[pop_rdi]+[binsh@libc_addr]+[system@libc_addr]
      • write

      稳妥来说需要控制 3 个寄存器:write(1,[输出地址],[输出长度]) 。如果找不到 rdx 的 gadget 和[上面小结](# ROP 利用链)一样尝试看看 rdx 原值是否符合需求,如果要不满足就得用 ret2csu 技巧控制前三个寄存器。

    无后门&溢出长度等于 ip

    溢出只能够刚好覆盖完 ip 就不能再溢出更多字节,显然是不能写入完整的利用链,这种情况可以考虑栈迁移。栈迁移就是将原本写在 ip 后面的利用链写到其他地方,通过控制 bp 、 ip 将栈迁移到利用链的位置,运行提前布置的利用链。

    首先这种题目会有一到两次输入的机会,输入内容可能会是局部变量或者全部变量(bss 段)。栈迁移需要明确的迁移地址,基于存放位置不同难度也不同:

    • 全局变量

      写到 bss 段就比较简单了,迁移地址直接到 ida 查全局变量地址即可。

    • 局部变量

      局部变量在栈上,栈地址是动态的,所以要先泄露栈地址才能进行栈迁移,但是也有用 sub esp,xx 这种神奇 gadget 将栈抬高的骚操作。如果用迁移到栈上一般会有两次输入一次,一次是泄露栈地址,一次是写入利用链。

    需要注意一点:假如写入地址和迁移地址为 addr ,有效利用链需要从 addr+一个机器周期 开始写入,具体自行调试查看 sp 变化。

    思路

    为了便于总结假设一个 32 位程序有两次输入,第一次向全局变量 var_1(地址为:addr)写入,第二次向局部变量 s 写入。

    // 32位程序
    int vul()
    {
      char s; // [esp+Ch] [ebp-6Ch]
      puts("input:");
      gets(&var_1); //栈溢出漏洞,溢出长度不限
      puts("input:");
      read(0,&s,0x74);  //栈溢出漏洞,溢出长度有限
      return puts("OK,Bye!");
    }

    提前用 ROPgadget 找到 leave;ret gadget ,然后构造 payload 如下:

    payload1='a'*4+p32(puts@plt)+p32(main)+p32(puts@got)
    payload2='a'*0x6c+p32(addr)+p32(leave;ret)

    泄露地址后返回 main 函数,第二次运行程序。后续 getshell 可以用 onegadget 或者 system(/bin/sh) 。溢出 ip 为 onegadget 就能 getshell 。system(/bin/sh) 需要再次栈迁移。

    • onegadget

      payload3=[任意内容]
      payload4='a'*0x6c+'a'*4+p32(onegadget)
    • system(/bin/sh)

      payload3='a'*4+p32(system@libc_addr)+p32(main)+p32(binsh@libc_addr)
      payload4='a'*0x6c+p32(addr)+p32(leave;ret)

    对于 32 位程序如果输出函数只有 write 影响不大,而 64 位程序如果输出函数只有 write 没有 puts 等少参数的函数,可能会比较棘手,原因还是在于第三个参数 rdx 缺少 gadget ,需要实际调试查看 rdx 的原值是否需求,如果不符合需要赋值就要 ret2csu ,将 csu 利用链写到迁移地址上。

    尾巴

    这里总结了几种入门题目常见套路,但是套路远远不局限于上文,比如栈溢出修改局部变量,绕过 PIE 保护等等。

    评分

    参与人数 1魔法币 +3 收起 理由
    Haibara_A + 3

    查看全部评分

    发表于 2020-11-22 00:32:09
    这个不错,谢谢楼主分享
    使用道具 举报 回复
    发表于 2020-11-22 23:50:42
    厉害,学到了很多
    使用道具 举报 回复
    发表于 2020-11-23 00:41:57
    vshadow 发表于 2020-11-22 00:32
    这个不错,谢谢楼主分享

    共同学习
    使用道具 举报 回复
    发表于 2020-11-23 00:42:55
    HhhM 发表于 2020-11-22 23:50
    厉害,学到了很多

    冲冲冲
    使用道具 举报 回复
    发表于 2020-11-24 12:06:36
    师傅tql
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册