首页
社区
课程
招聘
栈溢出漏洞利用(绕过ASLR)
发表于: 2021-9-17 23:05 28725

栈溢出漏洞利用(绕过ASLR)

2021-9-17 23:05
28725

背景介绍
  原文:https://sploitfun.wordpress.com/2015/05/08/bypassing-aslr-part-iii

  另外需要说明一下,原文只是一个系列中的一篇文章:

  学习这篇之前,最好先把前面的都搞明白,本文的细节,只针对Part III using GOT overwrite and GOT dereference这篇。

1. 漏洞程序

准备工作:

程序说明:

2. 问题分解
  面对一个复杂的问题时,通常需要自上而下、以大化小、分而治之,直接上图:

  换个方式来说明最终目标的话,就是要让vuln执行这样一段代码:

  并且仅仅是通过构造输入数据达到这个目的,而不是修改vlun.c源码,重新编译出新的vlun可执行文件。
  为了方便描述,先假设可以构造出如下堆栈布局:

  这显然可以使vuln按照上述的期望进行执行,但问题是我们无法向栈内构造这样的数据,原因如下:

  不过,这些问题都可以化解:

对于execve(),可以通过GOT overwrite的方法执行,它依据的原理是,vuln进程执行到攻击者构造的逻辑时,GOT[getuid]这个内存单元,存储的一定已经是getuid()函数的加载地址,另外,execve与getuid加载地址的偏移,和它们在libc.so动态库文件中的偏移,是一样的,这样,通过输入数据构造逻辑,将这个偏移加到GOT[getuid],并触发getuid@PLT执行,即可执行execve()。GOT的原理,32位elf格式中的10种重定位类型同样介绍的很清楚。

  上述这些,明显还没有将问题彻底分解,它们都依赖"通过输入数据构造的逻辑",比如,具体如何实现:GOT[getuid]+=(execve-getuid)?
  由于gcc编译时没有加"-z execstack"选项,即栈所在的内存区间,没有可执行权限,无法使用往栈里构造shellcode的方法,这种情况下可以使用ROP的方法,从代码中寻找一些以"ret"指令结束的代码片段,拼凑出需要的完整逻辑,还是直接上图:

目标②:ebx = GOT[getuid]-0x5d5b04c4
GOT[getuid]运行时地址,根据vuln可执行文件就可以得到:

从而可以事先计算出ebx的静态值:ebx = 0xaaa99b40,因此,作者找一个gadget: "popl %ebx; ret;",这样,通过往栈里构造0xaaa99b40和这个gadget的地址即可达到目的;

目标④:-0xe0(%ebx,%esi,4) = 0x0804861c
作者发现,如果上述的call指令,调用_fini函数,可以达到不修改eax的目的,也就是要让-0xe0(%ebx,%esi,4)这个地址值(注意它前面有*
号),存储的是_fini函数地址0x0804861c:

通过这样执行objdump命令,可以发现0x8049f3c这个地址处,存的是0x0804861c,从而又将目标转换成:-0xe0(%ebx,%esi,4) = 0x0804861c,这可以由目标⑦实现。另外,_fini函数中又会执行call指令,不过call的目标地址是确定的,由目标⑥保证再次call到的函数不修改eax即可;

3. 堆栈布局结果
  不管是写一段程序,还是学习一段程序,先有一个概括性的图,往往能事半功倍,所以我给栈的布局,画了一张高清大图:

  根据上述的问题分解过程,再去理解为什么要这样布局,难度就不大了,接下来可以顺着这个布局,看一下具体的执行过程:

gadget7
指向代码片段"movb $0x00000001, 0x0804A028; addl $0x04, %esp; popl %ebx; popl %ebp; ret;",并且gadget7所在位置,是main()函数返回地址的存储位置,这是通过写溢出覆盖的,那么,main()函数返回时,就会执行这段指令。使用这个代码片段,其实是有点“迫不得已”的,真正需要的其实只是"movb $0x00000001, 0x0804A028; ret;",但是没能在vuln中直接找到。
gadget7执行后:

另外,"addl $0x04, %esp; popl %ebx; popl %ebp;"使esp向上移动了4*3个字节,所以利用脚本在构造输入数据时,需要在gadget7后面安排4*3字节的dummy数据,保证gadget6距离gadget7 4*3字节,进而保证gadget7中的"ret"指令,能继续执行到gadget6,后续还有很多小的代码片段,都是这样连续在一起执行的,从而完成一个完整的逻辑,这也正是ROP的原理,以及gadget为什么要以"ret"指令结束的原因。

gadget2
gadget4~7,相当于迂回实现了gadget3的作用,与gadget2一起,服务于gadget1,gadget2执行结果:

但是,由于上方正好遇到服务于gadget4的0xfffff530,所以特地让gadget2指向代码片段"popl %ebx; popl %ebp; ret;",保证esp能够跳过0xfffff530的存储位置,到达gadget1的存储位置。

  不过,到这个时候,离最终目标,还有另外一半路程:触发setuid(0)和execve()函数的执行。

  根据上半程的"经验"+栈的详细布局图,不难看出,后续逻辑,是找了另外一块地盘,按照上图布局,构造了一个迁移栈(两个布局中的①~⑨标号,是一一对应的"逻辑-结果"),保证"/bin/sh"地址、{ "/bin/sh"地址, NULL }数组地址,都是固定的,从而绕过ASLR保护机制。
  最后说明2点:

为了迁移到新栈帧,溢出栈的末尾布局如下:

leave指令等于:

4. 利用脚本执行结果演示

 
// vuln.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main (int argc, char **argv) {
    char buf[256];
    int i;
    seteuid(getuid());
    if(argc < 2) {
        puts("Need an argument\n");
        exit(-1);
    }
    strcpy(buf, argv[1]);
    printf("%s\nLen:%d\n", buf, (int)strlen(buf));
    return 0;
}
// vuln.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 
int main (int argc, char **argv) {
    char buf[256];
    int i;
    seteuid(getuid());
    if(argc < 2) {
        puts("Need an argument\n");
        exit(-1);
    }
    strcpy(buf, argv[1]);
    printf("%s\nLen:%d\n", buf, (int)strlen(buf));
    return 0;
}
# 编译vuln.c,并修改vuln可执行文件属性
  sudo gcc -fno-stack-protector -o vuln vuln.c
  sudo chown root vuln
  sudo chgrp root vuln
  sudo chmod +s vuln
# 打开ASLR
  sudo sh -c 'echo 2 > /proc/sys/kernel/randomize_va_space'
# 编译vuln.c,并修改vuln可执行文件属性
  sudo gcc -fno-stack-protector -o vuln vuln.c
  sudo chown root vuln
  sudo chgrp root vuln
  sudo chmod +s vuln
# 打开ASLR
  sudo sh -c 'echo 2 > /proc/sys/kernel/randomize_va_space'
char *p = "/bin/sh";
char **argv = { p, NULL };
 
setuid(0);
execve(p, argv, NULL);
char *p = "/bin/sh";
char **argv = { p, NULL };
 
setuid(0);
execve(p, argv, NULL);
# 查看execve-getuid偏移
ldd vuln
objdump -S /lib/i386-linux-gnu/libc.so.6 | grep "getuid"
objdump -S /lib/i386-linux-gnu/libc.so.6 | grep "execve"
# 查看execve-getuid偏移
ldd vuln
objdump -S /lib/i386-linux-gnu/libc.so.6 | grep "getuid"
objdump -S /lib/i386-linux-gnu/libc.so.6 | grep "execve"
objdump -R vuln
objdump -R vuln
objdump -sj .dynamic vuln | grep '1c860408'
objdump -sj .dynamic vuln | grep '1c860408'

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2021-9-18 12:45 被jmpcall编辑 ,原因: 添加附件
上传的附件:
收藏
免费 4
支持
分享
最新回复 (7)
雪    币: 139
活跃值: (1175)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
先mark
2021-9-23 10:04
1
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
大神您好,如果这篇文章里的例子是64位的程序,有没有好的方法处理地址带有00的情况
2022-3-7 13:18
0
雪    币: 3481
活跃值: (11143)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
4
从原理上讲,欺骗漏洞程序执行strcpy(),间接的构造最终需要的地址,64位并不比32位多什么限制,不过strcpy()的次数可能要多些,你的担心是什么?
2022-3-7 15:19
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
谢谢大神的回复。64位的地址都是带有00的,当写在payload里面的时候,strcpy拷贝就会把payload截断,请问一下这个怎么处理或者转化比较好
2022-3-7 23:56
0
雪    币: 3481
活跃值: (11143)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
6
你可以再仔细看看这篇帖子或者原文的内容,虽然这里是用32位程序举例的,但也遇到需要往溢出区间构造0值的问题了,比如需要给setid()构造0参数值,你可能是还没看明白:溢出数据本身,又执行了多次strcpy()
2022-3-8 09:48
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
不是构造0,是rop指令所在的地址都是带00的啊,这样溢出的时候就被strcpy截断了
2022-3-8 14:06
0
雪    币: 3481
活跃值: (11143)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
8
mb_uuhlubck 不是构造0,是rop指令所在的地址都是带00的啊,这样溢出的时候就被strcpy截断了
这就要问专业的大佬了,我是业余学着玩玩,开始没有考虑到这种情况。
2022-3-8 15:15
0
游客
登录 | 注册 方可回帖
返回
//